x64 下的 segmentation 情形

6.1、 x64 下的物理資源及系統數據結構

6.1.1、 segment registers

  x64 體系在硬件級上最大限度地削弱了 segmentation 段式管理。採用平坦內存管理模式,因此體現出來的思想是 base 爲 0、limit 忽略。
  但是,x64 還是對 segmentation 提供了某種程度上的支持。體現在 FS 與 GS 的與衆不同。
  segment registers 的 selector 與原來的 x86 下意義不變。


在 64 bit 模式下:

(1)code register(CS)
  ● CS.base = 0(強制爲 0,實際上等於無效)
  ● CS.limit = invalid
  ● attribute:僅 CS.L 、CS.D、CS.P、CS.C 以及 CS.DPL 屬性是有效的。

情景提示:
  64 bit 模式下的 code segment descriptors 中的 L 位、D 位、P 位、C 位以及 DPL 域是有效的。code segment descriptor 加載到 CS 後僅 CS.L 、CS.D、CS.P、CS.C 以及 CS.DPL 屬性是有效的。


  在 compatibility 模式下 code segment descriptor 和 CS 寄存器與原來 x86 意義相同。


(2)data registers (DS、ES 以及 SS)
  ● DS.base = 0(強制爲 0,實際上等於無效)
  ● DS.limit = invalid
  ● DS.attribute = invalid:所有的屬性域都是無效的。

  data registers 的所有域都是無效的。data segment 的 attribute 是無效的,那麼也包括 DPL、D/B 屬性。

  在 64 bit 模式下,所有的 data segment 都具有 readable/writable 屬性,processor 對 data segment 的訪問不進行權限 check 以及 limit 檢查。


(3)FS 與 GS 
  ● FS.base 是完整是的 64 位。
  ● FS.limit = invalid
  ● FS.attribute = invalid

  與其它 data registers 不同的是,FS 與 GS 的 base 是有效的。支持完整的 64 位地址。但是 limit 和 attribute 依舊無效的。


1、爲 FS 和 GS 加載非 0 的 64 位 base 值,使用以下指令:
  
  mov fs, ax

  pop fs

--------------------------------------------  
  這條指令只能爲 fs 提供 32 位的 base 值,這根本的原因是:data segment descriptor 提供的 base 是 32 位值。在 x64 裏的 segment descriptor 是 8 個字節。也就是 base  是 4 個字節。通過 selector 加載 base 值,只能獲取 32 位地址值。


2、爲 fs 和 gs 提供 64 位地址值,可以使用以下指令:

  mov ecx, C0000100                   /* FS.base msr 地址 */
  mov edx, FFFFF800
  mov eax, 0F801000                    
   wrmsr                                        /* 寫 FS.base */
--------------------------------------------------
  上面代碼爲 FS.base 提供 0xFFFFF8000F801000 地址。


  mov ecx, C0000101                   /* GS.base msr 地址 */
  mov edx, FFFFF800
  mov eax, 0F801000                    
   wrmsr                                        /* 寫 GS.base */
--------------------------------------------------
  上面代碼爲 GS.base 提供 0xFFFFF8000F801000 地址。


  另一種方法是使用 swapgs 指令,這條指令將 kernelGS 地址與 GS.base 交換。
  



6.1.2、 descriptors 結構

  x64 體系已經不提供對 segmentation 的支持(或者說最大程度削弱了),對於 segment descriptor 來說,還是停留在 x86 的階段,絕大部分的功能已經去掉。但是對於 system descriptor 來說,它是被擴展爲 16 個字節,是 128 位的數據結構。
  因此,descriptors 結構要分兩部分來看。

1、segment descriptors
  包括 code segment descriptor 和 data segment descriptor,code segment descriptor 除這幾個屬性: P 位、C 位、D 位、L 位以及 DPL 外,其它都是無效的(當然 code segment descriptor 的 S 位爲 0 表示 segment descriptor,code / data 位爲 1 表示 code segment)。
  data segment descriptor 除了 P 屬性外,其它都是無效的(當然 data segment descriptor 的 S 屬性爲 0,code/data 屬性爲 0)。
  segment descriptor  還是 8 個字節 64 位的數據結構,沒有被擴展爲 16 個字節,根本的原因是 base 域是無效的。



2、 system descriptors
  包括 LDT descriptor、TSS descriptor 。這些 descriptor 被擴展爲 16 個字節共 128 位。descriptor 的 base 域被擴展爲 64 位值。用來在 64 位的線性地址空間中定位。

  在 64 bit 模式下,LDT / TSS descriptor 被擴展爲 64 位的 descriptor,base 是 64 位值。在 compatibility 模式下,LDT / TSS 依舊是 32 位的 descriptor。 
  


3、 gate descriptor 
  long mode 下不存在 task gate。所有的 gate(call、interrupt / trap) 都 64 位的。gate 所索引的 code segment 是 64 位的 code segment(L = 1 && D = 0)



情景提示:
  1、long mode 下的 segment descriptor 與 x86 原有的 segment descriptor 格式完全一致,只是在 64 bit 模式中 descriptor 的大部分域是無效的。
  2、64 bit 模式下的 system descriptor 被擴展爲 16 個字節。由於 system descriptor 中的 base 是有效的,base 被擴展爲 64 位,故 system descriptor 被擴展爲 128 位。





6.1.3、 descriptor table

1、long mode 下的 GDT / LDT 表


試想一下,以下情景:
  當 processor 處於 long mode 下,而要執行的目標代碼是 32 位的,也就是說 processor 將要進入 compatibility mode(兼容模式)去執行 x86 的 32 位代碼,此時變得有玄妙 ... ...


  1、首先,processor 處於 long mode,這表明此時:processor 既可以執行 64 位代碼也可以進入 compatibility mode 去執行 32 位代碼。什麼情形下進入 compatibility mode 呢?這是根據目標代碼的 descriptor 的 L 屬性,目標代碼的 descriptor 加載進入 CS 後,即 CS.L 決定是 64 bit mode 還是 compatibility mode。
  當 CS.L = 1 時,processor 處於 64 bit mode, CS.L = 0 時,processor 處於 compatibility mode。

  2、其次,一個既可執行 64 位代碼,又可 32 位代碼的 64 位 OS ,它的核心 kernel 及系統服務例程、相關的系統庫是運行在 64 bit 模式下的。原有的 32 位代碼運行 compatibility 模式下。
  所以,在 GDT 表中可能同時存在 32 位的 descriptor 和 64 位的 LDT/TSS descriptor、64 位的 call gate descriptor。

  3、最後,processor 是怎樣識別哪個是 32 位的 descriptor?哪個是 64 位的 call gate descriptor ?在 32 與 64 位 descriptor 相互存在的 descriptor table 裏又是如何去正確讀取所要的 descriptor ?


-----------------------------------------------------------------------------------------------
  在 long mode 下所有的 gate 都是 64 位的,所有的 segment descriptors 都是 32 位的。processor 根據 descriptor 的 types 來判斷哪些是 64 位,哪些是 32 位。


  這樣需要解決一個 32 位 descriptor 與 64 位 descriptor 重疊的問題,思考以下的指令:

call [call_gate]             /* call gate */


  若 [call_gate] 裏放着的 selector 是 0x20,若它是 64 位的 call gate descriptor selector。那麼 processor 將進入 64 位模式執行 64 位代碼。這個 selector 中的 SI 值是 4,所以 GDT 表中的第 4 項是個 64 位的 call gate descirptor。
  這個 64 位的 call gate descriptor 結構有 128 位寬。64 位的 base 中,低 32 位 base 在前 8 個字節裏,高 32 位 base 在後 8 個字節裏的前2 個字節。


在這種情形下,當程序中,使用了以下指令,意圖去訪問 32 位的 code segment descriptor 時,就會產生問題:
  
  call 0x28:00000000                  /* 意圖訪問 32 位的 code segment */

  這個 selector 的 SI 是 5,所以 processor 剛好訪問到 64 位的 call gate descriptor 的高半部分。這個高半部分是 base 的高 32 位值。當程序中以 32 位 code segment descriptor 形式訪問 64 位的 call gate descriptor 高半部分,這時 32 位的 code segment descriptor 與 64 位 call gate 的高半部分重疊了,這根本不是想要訪問的 32 位 code segment descriptor,這就產生了不可預測的結果。

  也就是說:以 64 位 gate 的高半部分作爲 32 位的 descriptor 訪問將現出不可預製的結果。

情景提示:
  這個原因產生的根本原因是:GDT 的索引是固定爲以 8 字節寬度進行索引。在 64 位模式下,不會以 16 字節爲寬度進行索引,還是以 8 字節寬度進行索引,這導致 32 位的 descriptor 可能會與 64 位 descriptor 部分重疊了。


  解決這個問題的設計思想是,將 64 位的 gate descriptor 或 LDT descriptor、TSS descriptor 的高半部分的對應 descriptor 的 type 的域置爲 0000。
  當 type = 0000 時,表示這個 descriptor 是非法的,這樣會引發 #GP 異常。

  所以,當上述的情形下,64 位的 call gate desciptor 的高半部分的 type 域定義爲 0000 時,以 32 位 descriptors 方式訪問 64 位 call gate 的高半部分時,將產生 #GP 異常。這樣將避免產生不可預測的結果改變產生 #GP 異常,從而提醒軟件設計人員進行更改。


這種實施策略相當於:

char *p = 0;           
*p = 'a';                      /* 產生異常 */
--------------------------------------------
  OS 將反饋給程序員這將產生異常,目的是將不可預測的結果改爲可預測的錯誤結果。從而避免錯誤。




2、 long mode 下的 IDT 表

  前面提到,在 long mode 下:GDT/LDT 中是以 8 個字節進行索引的。那麼:在 IDT 中則是以 16 個字節索引的。

情景提示:
  1、GDT / LDT 是以 8 個字節進行索引 descriptors。
  2、IDT 是以 16 個字節進行索引 descriptors。



造成這種在不同 descriptor table 中按不同 size 進行索引的根本原因是:

  1、在 GDT / LDT 中,可以同時存放 32 位與 64 位的 descriptors,這些 descriptors 包括:32 位的 segment descriptors、64 位的 system descriptors(LDT descriptors、TSS descriptors),還有就是 64 位的 call gate descriptors。
  所以,在 GDT / LDT 中只能以原來的 8 個字節進行索引,那麼在 64 位的 system descriptors 中,必須在其高 8 字節的 type 屬性裏設爲: type = 0000 以防止 32 位 descriptors 與 64 位 descriptors 重疊而產生問題。

  2、在 IDT 中,只能存放 64 位的 descriptors,所以不像 GDT / LDT 那樣需要兼容 32 位的 descriptors。因此,在 IDT 中固定以 16 個字節進行索引 descriptor entrys。

  IDT 是不能存放 call gate descriptors 的,只能存放 interrupt /trap descriptors 。task gate 在 long mode 下不存在。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章