zoukankan      html  css  js  c++  java
  • 段页式访存——逻辑地址到线性地址的转换

    继续底层知识,想要看懂 PWN 题和理解汇编代码,必须要搞懂这些底层知识啊。搞懂 movl 8(%ebp), %eax(IA-32 架构)真的不容易。。。

    movl 8(%ebp), %eax(IA-32)

    首先我们来看这条指令什么意思:把内存中某个地址的 32 位数据,放入 eax 寄存器中。你可以理解为地址为:%ebp + 8。但是,这只是虚拟地址。而且在 IA-32 架构中,虚拟存储空间是段页式。也就是说,在执行这条命令的时候,为了找到主存的物理地址,要经过段和页两种结构。搞懂这两种东西真的不容易!来吧,让我们开始吧。

    存储地址要经过以下阶段:

    逻辑地址 -------> 线性地址 -------> 物理地址

    (以下内容全是 IA-32 架构)

    分段过程

    分段过程的实质就是逻辑地址 -------> 线性地址 的过程

    整个过程如下图所示:

     
     

    逻辑地址实际是由 48 位组成的,前 16 位包括「段选择符」后 32 位「段内偏移量」

    • 啥是「段」

      还记得「可执行文件」中的段吗?有代码段、数据段。。。

    • 啥是「段选择符」

      这个先按下不表,主要作用是可以用「段选择符」找到所需段

    • 那什么又是「段内偏移量」

      「段内偏移量」其实就是指令地址相对于段基址的偏移量,也就是说,如果能找到段的位置(与段选择符)有关,就能找到,对应偏移量的指令地址

    如何通过「段选择符」找到段基址

    之前我们说过:逻辑地址一共有 48 位。前 16 位是段选择符。

     
     

    这 16 位的格式如上图。

    • 索引:「描述符表」的索引(Index)
    • TI:如果 TI 是 0。「描述符表」是「全局描述符表(GDT)」,如果 TI 是 1。「描述符表」是「局部描述表(LDT)」
    • RPL:段的级别。为 0,位于最高级别的内核态。为 11,位于最低级别的用户态。在 linux 中也仅有这两种级别。

    整体过程就是:

    通过索引在描述符表中找到段基址,用图片叙述就是这样(图片左边):

     
     

    其中 GDT 和 LDT 的首地址,存在用户不可见的寄存器中:

     
     

    下面的内容将详细介绍这些你不知道的名词

    什么是「描述符表」

    实际上就是「段表」,由「段描述符(段表项)」组成。有三种类型:

    • 全局描述符 GDT:只有一个,用来存放系统内用来存放系统内每个任务共用的描述符,例如,内核代码段、内核数据段、用户代码段、用户数据段以及 TSS(任务状态段)等都属于 GDT 中描述的段。
    • 局部描述符表 LDT:存放某任务(即用户进程)专用的描述符
    • 中断描述符表 IDT:包含 256 个中断门、陷阱门和任务门描述符

    什么是「段描述符」

    段描述符就是表项,一种记录每个段信息的数据结构。我们之前说到的「段选择符」就是描述符表(段表)中的索引。

    一图讲清楚段描述符:

     
     

    一个段描述符的大小是 8B。现在把段描述符的每个部分讲清楚:

    • B31~B0:32 位基地址(段的基地址)
    • L19~L0:20 位界限,表示段中的最大页号
    • G:与界限的单位有关。设置 G = 1,以页(4 KB)为单位,所以最大段为 4KB × 2^{20} = 4GB;G = 0 时,以字节位单位,所以最大段为 2^{20}B = 1MB
    • D:D = 1 表示段内偏移量为 32 位宽,D = 0 表示段内偏移量为 16 位宽
    • P:P = 1 表示存在,P = 0 表示不存在。Linux 总把 P 置 1,不会以段为单位淘汰。
    • DPL:访问段时对当前特权级的最低等级要求。因此,只有 CPL 为 0(内核态)时才可访问 DPL 为 0 的段,任何进程都可访问 DPL 为 3 的段(0 最高、3 最低)
    • S:S = 0 系统控制描述符,S = 1 普通的代码段或数据段描述符
    • TYPE:段的访问权限或系统控制描述符类型
    • A:A = 1 已被访问过,A = 0 未被访问过。(通常 A 包含在 TYPE 字段中)

    问一个问题

    像 GDT 表这样的表存在哪里?存在主存里面!

    好,既然存在主存里面,就有一个速度的问题。这种问题的解决办法就是用 Cache

    用 Cache 解决分段问题

    学习如何用 Cache 解决分段问题之前。我们得先知道逻辑地址的前 16 位地址怎么来的。

    逻辑地址的前 16 位是段选择符,「段选择符」存在寄存器中:

     
     

    每个寄存器的作用如下:

     
     

    也就是每个进程的每个段都有相应段的段选择符寄存器

    有了「段选择符」以后,就能拿到对应表中的段基址了。那 Cache 又是怎么派上用场的呢?

     
     

    只有第一次取地址的时候才会去主存中访问 GDT 表。之后根据「段选择符」选择地址的时候,都是在 Cache 里面去找,不用再访问主存

     
     

    (MMU 是 Memory Management Unit 内存管理单元)

    一点小变化

    为使能移植到绝大多数流行处理器平台,Linux 简化了分段机制

     
     

    为了简化,初始化的时候,所有的基地址都是 0

    总结

    小小的总结一下:如何从逻辑地址 -------> 线性地址

     
     
     
      
     



    作者:madao756
    链接:https://www.jianshu.com/p/fd2611cc808e
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 相关阅读:
    初学微信小程序 TodoList
    设计一个基于svg的涂鸦组件(一)
    基于51单片机的12864驱动
    java 使用xom对象数据序列化为xml、反序列化、Preferences相关操作小案例
    ios UIWebView 播放优酷土豆视频
    VMware Player 使用错误集锦
    Django 使用UEditor
    Entity Framework底层操作封装V2版本号(3)
    cocos2dx笔记1:概述
    oracle10g精简版安装步骤
  • 原文地址:https://www.cnblogs.com/pipci/p/12403976.html
Copyright © 2011-2022 走看看