段頁式訪存——邏輯地址到線性地址的轉換

繼續底層知識,想要看懂 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)爲單位,所以最大段爲 math?formula=4KB%20%C3%97%202%5E%7B20%7D%20%3D%204GBuploading.4e448015.gif轉存失敗重新上傳取消4KB × 2^{20} = 4GB;G = 0 時,以字節位單位,所以最大段爲 math?formula=2%5E%7B20%7DB%20%3D%201MBuploading.4e448015.gif轉存失敗重新上傳取消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
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章