日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[翻译]Global Descriptor Table-GDT

發(fā)布時間:2025/4/16 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [翻译]Global Descriptor Table-GDT 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

全局描述符表(GDT)是 Intel x86 系列處理器(從 80286 開始)所使用的一種數據結構,目的是為了在程序運行期間劃分具有不同屬性的內存區(qū)域,比如:可以運行、可寫入等區(qū)域的起始地址與訪問權限。這些區(qū)域被稱作段。

全局描述符表除了可以保存段信息外還可以保存其它信息。全局描述符表中的每個表項(描述子)長度為 8-byte,全局描述符表的選擇子可以為:任務狀態(tài)描述子(TSS)、本地描述符表描述子或者調用門描述子。調用門在 x86 不同特權級中轉移控制權非常重要,但是現在的操作系統(tǒng)很少使用這種機制。

同時存在的還有局部描述符表(LDT)。局部描述符表用來存儲程序內部的段信息,而全局描述符表用來描述全局的段信息。x86 系列的處理器具有一種機制,可以在發(fā)生某些事件時自動切換局部描述符表,但是針對全局描述符表卻沒有這樣的機制。

程序可以訪問的內存通常被限制在一個段內。在 386 及以后的處理器中,由于 32 位的段內偏移與段大小的原因,有可能使段覆蓋全部可尋址的空間,并且相關的段對用戶來說也使透明的。

程序如果想使用某個段,需要在全局描述符表或者局部描述符表中找到該段對應的索引。這個索引被稱為段選擇子。為了使用相應的段,段選擇子必須被首先加載到段寄存器。除了可以通過機器指令讀取或者設置全局描述符表(還有中斷描述符表)的內存地址外,指令所引用的內存地址存在于一個隱式的段,有時有兩個。大部分情況下,默認的段寄存器可以通過在地址前面加一個段地址來替換。加載段選擇子到段寄存器的過程中,程序會自動讀取全局描述符表或者局部描述符表,并將相關信息保存到處理器中。在全局描述符表或者局部描述符表被加載后,對二者的修改并不會起作用,除非重新將相應的表加載到寄存器。

64 位下全局描述符表

全局描述符表在 64 位下仍然是合法可用的。相應的寄存器位數從 48 位擴展到了 80 位,64 位的段選擇子是平坦的、無限制的(從 0x0000000000000000 到 0xFFFFFFFFFFFFFFFF)。64 位版本的 Windows 仍然禁止對全局表述附表的 hook 操作,如果進行這種操作會引發(fā)一個系統(tǒng)錯誤。

=========================================================================

386 處理器保護機制的重要方面就是全局描述符表。全局描述符表定義了一些內存區(qū)域的基本訪問權限。可以使用全局描述符表中一個表項來描述一個段非法訪問異常,這樣,在進程進行了非法操作時,內核可以有機會終止進程。現代的大部分操作系統(tǒng)使用分頁來實現這個機制:這種機制更加通用,給了上層更加大的靈活性。全局描述符表同樣可以定義一塊內存區(qū)域是可執(zhí)行的,還是普通數據。全局描述符表有能力定義任務狀態(tài)段(TSS)。TSS 用來實現基于硬件的多任務,這里不討論。但是需要說明的是 TSS 并不是實現多任務的唯一方法。

GRUB 已經為我們加載了一個全局描述符表,如果我們重寫了 GRUB 已經加載了的內存(全局描述符表所占空間),我們會破壞全局描述符表,并會引發(fā)一個 “triple fault” 錯誤。后果是引起機器重啟。我們應該在有權限訪問的內存中定義自己的全局描述符表,從而避免這個問題。這就需要我們重建全局描述符表,告訴處理器全局描述符表的位置,最后需要重新設置CS, DS, ES, FS, and GS,將其對應到我們自己的全局描述符表。CS 通常被稱為代碼段寄存器。代碼段寄存器可以向處理器提供代碼段在全局描述符表中偏移量,同時還提供了當前可執(zhí)行代碼的訪問權限。同樣,DS 向處理器提供了當前數據的訪問權限。ES, FS, GS 只是改變 DS,對我們來說不重要。

全局描述符表是一個每個表項 64 位的表。每個表項定義了可以使用的內存區(qū)域的:起始,長度,訪問權限。通用規(guī)則是:全局描述符表的第一個表項是 0,是一個空的描述符。段寄存器不應該被設置為 0,否則引發(fā)一個保護錯,保護錯是處理器的一種保護機制。保護錯與處理器的其它保護機制在 http://www.osdever.net/bkerndev/Docs/isrs.htm 有詳細介紹。

每個表項還定義了處理器當前正在運行的代碼是在系統(tǒng)層(Ring0)還是在應用程序層(Ring3)。還有其它的 Ring,但是不重要。現在主要的操作系統(tǒng)都只使用 Ring0 與 Ring3。作為一個基本規(guī)則:如果應用程序訪問 Ring0 ,會引發(fā)一個異常。這是為了讓應用程序不會使系統(tǒng)崩潰。在全局描述符表部分所涉及的 Ring,主要是定了處理器是否可以執(zhí)行某些特權指令。某些指令是特權級的,意味著只能在高特權的 Ring 中執(zhí)行。例如:cli,sti 會禁用或者啟用中斷。如果允許應用程序使用 cli 與 sti,那它就可以終止內核的運行。

全局描述服表項

765430
PDPLDTType
P - Segment is present? (1 = Yes)
DPL - Which Ring (0 to 3)
DT - Descriptor Type
Type - Which type?
?
765430
GD0ASeg Len. 19:16
G - Granularity (0 = 1byte, 1 = 4kbyte)
D - Operand Size (0 = 16bit, 1 = 32-bit)
0 - Always 0
A - Available for System (Always set to 0)?

在作為練習的系統(tǒng)內核中,我們會定義一個具有 3 個表項的全局描述符表。為什么 3 個?我們需要一個 'dummy' descriptor,來演示處理的保護特性。我們還需要一個代碼段,一個數據段。我們使用 lgdt 指令來讓處理器重新加載全局描述符表。使用 lgdt 指令需要一個指針,該指針指向一個 48 位的結構。48 位結構的前 16 位定義了全局描述符表的大小,剩下的 32 位是全局描述符表在內存中的起始地址。

我們可以簡單的使用具有 3 個元素的數組來定義全局描述符表。

全局描述符表的一個實現:


#include < system.h >/* Defines a GDT entry. We say packed, because it prevents the * compiler from doing things that it thinks is best: Prevent * compiler "optimization" by packing */ struct gdt_entry {unsigned short limit_low;unsigned short base_low;unsigned char base_middle;unsigned char access;unsigned char granularity;unsigned char base_high; } __attribute__((packed));/* Special pointer which includes the limit: The max bytes * taken up by the GDT, minus 1. Again, this NEEDS to be packed */ struct gdt_ptr {unsigned short limit;unsigned int base; } __attribute__((packed));/* Our GDT, with 3 entries, and finally our special GDT pointer */ struct gdt_entry gdt[3]; struct gdt_ptr gp;/* This will be a function in start.asm. We use this to properly * reload the new segment registers */ extern void gdt_flush();

; This will set up our new segment registers. We need to do ; something special in order to set CS. We do what is called a ; far jump. A jump that includes a segment as well as an offset. ; This is declared in C as 'extern void gdt_flush();' global _gdt_flush ; Allows the C code to link to this extern _gp ; Says that '_gp' is in another file _gdt_flush:lgdt [_gp] ; Load the GDT with our '_gp' which is a special pointermov ax, 0x10 ; 0x10 is the offset in the GDT to our data segmentmov ds, axmov es, axmov fs, axmov gs, axmov ss, axjmp 0x08:flush2 ; 0x08 is the offset to our code segment: Far jump! flush2:ret ; Returns back to the C code!

/* Setup a descriptor in the Global Descriptor Table */ void gdt_set_gate(int num, unsigned long base, unsigned long limit, unsigned char access, unsigned char gran) {/* Setup the descriptor base address */gdt[num].base_low = (base & 0xFFFF);gdt[num].base_middle = (base >> 16) & 0xFF;gdt[num].base_high = (base >> 24) & 0xFF;/* Setup the descriptor limits */gdt[num].limit_low = (limit & 0xFFFF);gdt[num].granularity = ((limit >> 16) & 0x0F);/* Finally, set up the granularity and access flags */gdt[num].granularity |= (gran & 0xF0);gdt[num].access = access; }/* Should be called by main. This will setup the special GDT * pointer, set up the first 3 entries in our GDT, and then * finally call gdt_flush() in our assembler file in order * to tell the processor where the new GDT is and update the * new segment registers */ void gdt_install() {/* Setup the GDT pointer and limit */gp.limit = (sizeof(struct gdt_entry) * 3) - 1;gp.base = &gdt;/* Our NULL descriptor */gdt_set_gate(0, 0, 0, 0, 0);/* The second entry is our Code Segment. The base address* is 0, the limit is 4GBytes, it uses 4KByte granularity,* uses 32-bit opcodes, and is a Code Segment descriptor.* Please check the table above in the tutorial in order* to see exactly what each value means */gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF);/* The third entry is our Data Segment. It's EXACTLY the* same as our code segment, but the descriptor type in* this entry's access byte says it's a Data Segment */gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF);/* Flush out the old GDT and install the new changes! */gdt_flush(); }


=========================================================================

在 Intel 架構的處理器中,更確切的說是在保護模式下,內存管理與中斷服務程序的控制是通過描述符表(tables of descriptors)來實現的。每個描述符的表項保存了處理器在某個時間要用的信息(例如:服務例程,任務,代碼,數據等等)。如果你試著為段寄存器設置一個新值,處理器會進行關于安全與訪問控制的檢查。一旦通過檢查,處理器會在內部寄存器中緩存這些值。

Intel 系列的處理器定義了三張表:中斷描述符表,全局表述附表,局部描述符表。可以通過 LIDT,LGDT,LLDT 三個指令來加載這三張表。在大多數情況下,系統(tǒng)只是在啟動時告訴處理器這三張表的位置,然后在以后的運行過程中通過指針來讀取或者修改這三張表。

全局描述符表中應該存放什么信息?

如果完成的話,表中應該保存如下信息:

× 處理器從不引用的空指針。如果不設置,某些模擬器會抱怨缺少 limit exception。某些情況下,只是在這個位置保存指向全局描述符表自身的指針。

× 代碼段描述子。對于內核來說,這個表項的類型為 0x9A。

× 數據段描述子。因為無法向代碼段中寫數據,因此需要添加數據段,類型為 0x92。

× 任務狀態(tài)描述子。最好為這個段保留一定空間。

× 其它描述子空間。例如:用戶界別(user-level),局部描述符表,更多的任務狀態(tài)描述子。

Sysenter/Sysexit

如果你想使用 Intel 的 Sysenter/Sysexit 例程,那么全局描述符表必須這樣組織:

× 前面提到的一些段(NULL 描述子,kernel stuff,等等)

× DPL0 代碼段描述子。Sysenter 使用。

× DPL0 數據段描述子。Sysenter 棧使用。

× DPL3 代碼段描述子。Sysexit 后需要執(zhí)行的代碼。

× DPL3 數據段描述子。在 Sysexit 后,用戶態(tài)的棧。

× 其它描述子。

DPL0 代碼段的內容被加載到 MSR。其它值通過這個值來計算。具體參考 Intel 的手冊。

平坦模式初始化(

Flat Setup

如果想使用不需要翻譯(查表,轉換)的 4 G 空間:

GDT[0] = {.base=0, .limit=0, .type=0}; // Selector 0x00 cannot be used GDT[1] = {.base=0, .limit=0xffffffff, .type=0x9A}; // Selector 0x08 will be our code GDT[2] = {.base=0, .limit=0xffffffff, .type=0x92}; // Selector 0x10 will be our data GDT[3] = {.base=&myTss, .limit=sizeof(myTss), .type=0x89}; // You can use LTR(0x18)

在這種模式下,沒有辦法保護代碼段不可寫,因為代碼段與數據段是重疊的。

微內核模式初始化(Small Kernel Setup)

如果基于某種原因需要將代碼段與數據段分開,并且假設每個有 4M 空間,起始與 4M。


GDT[0] = {.base=0, .limit=0, .type=0}; // Selector 0x00 cannot be used GDT[1] = {.base=0x04000000, .limit=0x03ffffff, .type=0x9A}; // Selector 0x08 will be our code GDT[2] = {.base=0x08000000, .limit=0x03ffffff, .type=0x92}; // Selector 0x10 will be our data GDT[3] = {.base=&myTss, .limit=sizeof(myTss), .type=0x89}; // You can use LTR(0x18)

這意味著在物理內存 4M 空間內的信息可以從 CS:0 開始訪問。物理內存 8M 空間內的信息可以從 DS:0 開始訪問。這不是一個好的設計。

具體應該怎么操作呢?

禁用中斷

如果中斷處于開啟狀態(tài),就禁用中斷,否則是自找麻煩。

向表格中填充數據

我們現在還沒有給出 GDT[] 的具體結構。這是有目的的。描述子的實際結構因為某種原因有點混亂。地址被分成三個部分,無法進行編碼限制,同時還要正確設置很多標志位。


/*** /param target A pointer to the 8-byte GDT entry* /param source An arbitrary structure describing the GDT entry*/
void encodeGdtEntry(uint8_t *target, struct GDT source) {// Check the limit to make sure that it can be encodedif ((source.limit > 65536) && (source.limit & 0xFFF) != 0xFFF)) {kerror("You can't do that!");}if (source.limit > 65536) {// Adjust granularity if requiredsource.limit = source.limit >> 12;target[6] = 0xC0;} else {target[6] = 0x40;}?? // Encode the limittarget[0] = source.limit & 0xFF;target[1] = (source.limit >> 8) & 0xFF;target[6] |= (source.limit >> 16) & 0xF;?? // Encode the base target[2] = source.base & 0xFF;target[3] = (source.base >> 8) & 0xFF;target[4] = (source.base >> 16) & 0xFF;target[7] = (source.base >> 24) & 0xFF;?? // And... Typetarget[5] = source.type; }

告訴處理器去哪里尋找全局表述附表

使用 LGDT 指令設置。

對于實模式

線性地址是通過段基址右移 4 位,然后加上段內偏移獲得的。假設 GDT 和 GDT_end 兩個符號在當前的數據段:


gdtr DW 0 ; For limit storageDD 0 ; For base storage?? setGdt:XOR EAX, EAXMOV AX, DSSHL EAX, 4ADD EAX, ''GDT''MOV [gdtr + 2], eaxMOV EAX, ''GDT_end''SUB EAX, ''GDT''MOV [gdtr], AXLGDT [gdtr]RET

平坦的保護模式下

平坦意味著數據段的地址從 0 開始。如果通過 GRUB 進行引導就是這種情況。假設你調用 setGdt(GDT, sizeof(GDT)):

gdtr DW 0 ; For limit storageDD 0 ; For base storage?? setGdt:MOV EAX, [esp + 4]MOV [gdtr + 2], EAXMOV AX, [ESP + 8]MOV [gdtr], AXLGDT [gdtr]RET

?

非平坦的保護模式

如果數據段的起始地址不是 0,就屬于這種模式。

需要:

"MOV EAX, ..."

"ADD EAX, base_of_your_data_segment_which_you_should_know"

"MOV ..., EAX"

?

重新設置各個段寄存器

無論怎么修改全局描述符表,如果沒有設置各個段寄存器,修改不會起作用。


reloadSegments:; Reload CS register containing code selector:JMP 0x08:reload_CS ; 0x08 points at the new code selector .reload_CS:; Reload data segment registers:MOV AX, 0x10 ; 0x10 points at the new data selectorMOV DS, AXMOV ES, AXMOV FS, AXMOV GS, AXMOV SS, AXRET

?

為什么局部描述符表很特別?

像全局描述符表一樣,局部描述符表也包含關于內存區(qū)域的描述子,但是這些描述子被稱作門。每個任務都可以有自己的局部描述符表,當使用硬件任務切換時,處理器會自動切換到正確的局部描述符表。

因為對于每個任務來說,局部描述符表可能不同,局部描述符表不是一個保存系統(tǒng)相關信息的地方,例如:任務狀態(tài)描述子,或者其它局部描述符表,而這些是全局描述符表的責任。因為全局表述附表經常改變,因此他的設置與全局描述符表還有中斷描述符表有些不同。局部描述符表不是通過直接設置其地址與大小完成的,那些信息被保存在了全局描述符表(選擇子的類型是 LDT), 對應選擇子的信息如下:

GDTR (base + limit)+-- GDT ------------+| | SELECTOR ---> [LDT descriptor ]----> LDTR (base + limit)| | +-- LDT ------------+| | | |... ... ... ...+-------------------+ +-------------------+ 在 386+處理器的分頁機制下,局部描述符表已經沒用了。已經沒有必要設置多個局部描述符選擇子,因此對于系統(tǒng)開發(fā)來說可以忽略處理器的這個特性了。

?

什么是中斷描述符表?是否需要中斷描述符表?

參考:http://wiki.osdev.org/index.php?title=Interrupts_for_dummies&action=edit

=========================================================================

GDT 的加載使用 LDGT 指令。GDT 的結構如下:

GDTR

offset?是表格自身的虛擬地址。size?為表格的大小減一。原因是:size?的最大值是 65535,而全局描述符表對打可以還有 65536 byte(8192 個表項)。每個表項 8byte,其復雜的結構如下:

A GDT Entry

What "Limit 0:15" means is that the field contains bits 0-15 of the?limit?value. The?base?is a 32 bit value containing the linear address where the segment begins. The?limit, a 20 bit value, tells the maximum addressable unit (either in 1 byte units, or in pages). Hence, if you choose page granularity (4 KiB) and set the?limit?value to 0xFFFFF the segment will span the full 4 GiB address space. Here is the structure of the access byte and flags:

GDT Bits

The bit fields are:

  • Pr:?Present bit. This must be?1?for all valid selectors.
  • Privl:?Privilege, 2 bits. Contains the?ring level, 0 = highest (kernel), 3 = lowest (user applications).
  • Ex:?Executable bit. If?1?code in this segment can be executed, ie. a code selector. If?0?it is a data selector.
  • DC:?Direction bit/Conforming bit.
    • Direction bit for data selectors: Tells the direction.?0?the segment grows up.?1?the segment grows down, ie. the?offset?has to be greater than the?base.
    • Conforming bit for code selectors:
      • If?1?code in this segment can be executed from an equal or lower privilege level. For example, code in ring 3 can far-jump to?conforming?code in a ring 2 segment. The?privl-bits represent the highest privilege level that is allowed to execute the segment. For example, code in ring 0 cannot far-jump to a conforming code segment with?privl==0x2, while code in ring 2 and 3 can. Note that the privilege level remains the same, ie. a far-jump form ring 3 to a?privl==2-segment remains in ring 3 after the jump.
      • If?0?code in this segment can only be executed from the ring set in?privl.
  • RW:?Readable bit/Writable bit.
    • Readable bit for code selectors: Whether read access for this segment is allowed. Write access is never allowed for code segments.
    • Writable bit for data selectors: Whether write access for this segment is allowed. Read access is always allowed for data segments.
  • Ac:?Accessed bit. Just set to?0. The CPU sets this to?1?when the segment is accessed.
  • Gr:?Granularity bit. If?0?the?limit?is in 1 B blocks (byte granularity), if?1?the?limit?is in 4 KiB blocks (page granularity).
  • Sz:?Size bit. If?0?the selector defines 16 bit protected mode. If?1?it defines 32 bit protected mode. You can have both 16 bit and 32 bit selectors at once.

轉載于:https://www.cnblogs.com/Proteas/archive/2010/11/28/2335682.html

總結

以上是生活随笔為你收集整理的[翻译]Global Descriptor Table-GDT的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。