[翻译]Global Descriptor Table-GDT
全局描述符表(GDT)是 Intel x86 系列處理器(從 80286 開始)所使用的一種數(shù)據(jù)結(jié)構(gòu),目的是為了在程序運(yùn)行期間劃分具有不同屬性的內(nèi)存區(qū)域,比如:可以運(yùn)行、可寫入等區(qū)域的起始地址與訪問權(quán)限。這些區(qū)域被稱作段。
全局描述符表除了可以保存段信息外還可以保存其它信息。全局描述符表中的每個(gè)表項(xiàng)(描述子)長(zhǎng)度為 8-byte,全局描述符表的選擇子可以為:任務(wù)狀態(tài)描述子(TSS)、本地描述符表描述子或者調(diào)用門描述子。調(diào)用門在 x86 不同特權(quán)級(jí)中轉(zhuǎn)移控制權(quán)非常重要,但是現(xiàn)在的操作系統(tǒng)很少使用這種機(jī)制。
同時(shí)存在的還有局部描述符表(LDT)。局部描述符表用來(lái)存儲(chǔ)程序內(nèi)部的段信息,而全局描述符表用來(lái)描述全局的段信息。x86 系列的處理器具有一種機(jī)制,可以在發(fā)生某些事件時(shí)自動(dòng)切換局部描述符表,但是針對(duì)全局描述符表卻沒有這樣的機(jī)制。
程序可以訪問的內(nèi)存通常被限制在一個(gè)段內(nèi)。在 386 及以后的處理器中,由于 32 位的段內(nèi)偏移與段大小的原因,有可能使段覆蓋全部可尋址的空間,并且相關(guān)的段對(duì)用戶來(lái)說(shuō)也使透明的。
程序如果想使用某個(gè)段,需要在全局描述符表或者局部描述符表中找到該段對(duì)應(yīng)的索引。這個(gè)索引被稱為段選擇子。為了使用相應(yīng)的段,段選擇子必須被首先加載到段寄存器。除了可以通過機(jī)器指令讀取或者設(shè)置全局描述符表(還有中斷描述符表)的內(nèi)存地址外,指令所引用的內(nèi)存地址存在于一個(gè)隱式的段,有時(shí)有兩個(gè)。大部分情況下,默認(rèn)的段寄存器可以通過在地址前面加一個(gè)段地址來(lái)替換。加載段選擇子到段寄存器的過程中,程序會(huì)自動(dòng)讀取全局描述符表或者局部描述符表,并將相關(guān)信息保存到處理器中。在全局描述符表或者局部描述符表被加載后,對(duì)二者的修改并不會(huì)起作用,除非重新將相應(yīng)的表加載到寄存器。
64 位下全局描述符表
全局描述符表在 64 位下仍然是合法可用的。相應(yīng)的寄存器位數(shù)從 48 位擴(kuò)展到了 80 位,64 位的段選擇子是平坦的、無(wú)限制的(從 0x0000000000000000 到 0xFFFFFFFFFFFFFFFF)。64 位版本的 Windows 仍然禁止對(duì)全局表述附表的 hook 操作,如果進(jìn)行這種操作會(huì)引發(fā)一個(gè)系統(tǒng)錯(cuò)誤。
=========================================================================
386 處理器保護(hù)機(jī)制的重要方面就是全局描述符表。全局描述符表定義了一些內(nèi)存區(qū)域的基本訪問權(quán)限??梢允褂萌置枋龇碇幸粋€(gè)表項(xiàng)來(lái)描述一個(gè)段非法訪問異常,這樣,在進(jìn)程進(jìn)行了非法操作時(shí),內(nèi)核可以有機(jī)會(huì)終止進(jìn)程?,F(xiàn)代的大部分操作系統(tǒng)使用分頁(yè)來(lái)實(shí)現(xiàn)這個(gè)機(jī)制:這種機(jī)制更加通用,給了上層更加大的靈活性。全局描述符表同樣可以定義一塊內(nèi)存區(qū)域是可執(zhí)行的,還是普通數(shù)據(jù)。全局描述符表有能力定義任務(wù)狀態(tài)段(TSS)。TSS 用來(lái)實(shí)現(xiàn)基于硬件的多任務(wù),這里不討論。但是需要說(shuō)明的是 TSS 并不是實(shí)現(xiàn)多任務(wù)的唯一方法。
GRUB 已經(jīng)為我們加載了一個(gè)全局描述符表,如果我們重寫了 GRUB 已經(jīng)加載了的內(nèi)存(全局描述符表所占空間),我們會(huì)破壞全局描述符表,并會(huì)引發(fā)一個(gè) “triple fault” 錯(cuò)誤。后果是引起機(jī)器重啟。我們應(yīng)該在有權(quán)限訪問的內(nèi)存中定義自己的全局描述符表,從而避免這個(gè)問題。這就需要我們重建全局描述符表,告訴處理器全局描述符表的位置,最后需要重新設(shè)置CS, DS, ES, FS, and GS,將其對(duì)應(yīng)到我們自己的全局描述符表。CS 通常被稱為代碼段寄存器。代碼段寄存器可以向處理器提供代碼段在全局描述符表中偏移量,同時(shí)還提供了當(dāng)前可執(zhí)行代碼的訪問權(quán)限。同樣,DS 向處理器提供了當(dāng)前數(shù)據(jù)的訪問權(quán)限。ES, FS, GS 只是改變 DS,對(duì)我們來(lái)說(shuō)不重要。
全局描述符表是一個(gè)每個(gè)表項(xiàng) 64 位的表。每個(gè)表項(xiàng)定義了可以使用的內(nèi)存區(qū)域的:起始,長(zhǎng)度,訪問權(quán)限。通用規(guī)則是:全局描述符表的第一個(gè)表項(xiàng)是 0,是一個(gè)空的描述符。段寄存器不應(yīng)該被設(shè)置為 0,否則引發(fā)一個(gè)保護(hù)錯(cuò),保護(hù)錯(cuò)是處理器的一種保護(hù)機(jī)制。保護(hù)錯(cuò)與處理器的其它保護(hù)機(jī)制在 http://www.osdever.net/bkerndev/Docs/isrs.htm 有詳細(xì)介紹。
每個(gè)表項(xiàng)還定義了處理器當(dāng)前正在運(yùn)行的代碼是在系統(tǒng)層(Ring0)還是在應(yīng)用程序?qū)?#xff08;Ring3)。還有其它的 Ring,但是不重要。現(xiàn)在主要的操作系統(tǒng)都只使用 Ring0 與 Ring3。作為一個(gè)基本規(guī)則:如果應(yīng)用程序訪問 Ring0 ,會(huì)引發(fā)一個(gè)異常。這是為了讓應(yīng)用程序不會(huì)使系統(tǒng)崩潰。在全局描述符表部分所涉及的 Ring,主要是定了處理器是否可以執(zhí)行某些特權(quán)指令。某些指令是特權(quán)級(jí)的,意味著只能在高特權(quán)的 Ring 中執(zhí)行。例如:cli,sti 會(huì)禁用或者啟用中斷。如果允許應(yīng)用程序使用 cli 與 sti,那它就可以終止內(nèi)核的運(yùn)行。
全局描述服表項(xiàng)
|
|
|
在作為練習(xí)的系統(tǒng)內(nèi)核中,我們會(huì)定義一個(gè)具有 3 個(gè)表項(xiàng)的全局描述符表。為什么 3 個(gè)?我們需要一個(gè) 'dummy' descriptor,來(lái)演示處理的保護(hù)特性。我們還需要一個(gè)代碼段,一個(gè)數(shù)據(jù)段。我們使用 lgdt 指令來(lái)讓處理器重新加載全局描述符表。使用 lgdt 指令需要一個(gè)指針,該指針指向一個(gè) 48 位的結(jié)構(gòu)。48 位結(jié)構(gòu)的前 16 位定義了全局描述符表的大小,剩下的 32 位是全局描述符表在內(nèi)存中的起始地址。
我們可以簡(jiǎn)單的使用具有 3 個(gè)元素的數(shù)組來(lái)定義全局描述符表。
全局描述符表的一個(gè)實(shí)現(xiàn):
#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 架構(gòu)的處理器中,更確切的說(shuō)是在保護(hù)模式下,內(nèi)存管理與中斷服務(wù)程序的控制是通過描述符表(tables of descriptors)來(lái)實(shí)現(xiàn)的。每個(gè)描述符的表項(xiàng)保存了處理器在某個(gè)時(shí)間要用的信息(例如:服務(wù)例程,任務(wù),代碼,數(shù)據(jù)等等)。如果你試著為段寄存器設(shè)置一個(gè)新值,處理器會(huì)進(jìn)行關(guān)于安全與訪問控制的檢查。一旦通過檢查,處理器會(huì)在內(nèi)部寄存器中緩存這些值。
Intel 系列的處理器定義了三張表:中斷描述符表,全局表述附表,局部描述符表??梢酝ㄟ^ LIDT,LGDT,LLDT 三個(gè)指令來(lái)加載這三張表。在大多數(shù)情況下,系統(tǒng)只是在啟動(dòng)時(shí)告訴處理器這三張表的位置,然后在以后的運(yùn)行過程中通過指針來(lái)讀取或者修改這三張表。
全局描述符表中應(yīng)該存放什么信息?
如果完成的話,表中應(yīng)該保存如下信息:
× 處理器從不引用的空指針。如果不設(shè)置,某些模擬器會(huì)抱怨缺少 limit exception。某些情況下,只是在這個(gè)位置保存指向全局描述符表自身的指針。
× 代碼段描述子。對(duì)于內(nèi)核來(lái)說(shuō),這個(gè)表項(xiàng)的類型為 0x9A。
× 數(shù)據(jù)段描述子。因?yàn)闊o(wú)法向代碼段中寫數(shù)據(jù),因此需要添加數(shù)據(jù)段,類型為 0x92。
× 任務(wù)狀態(tài)描述子。最好為這個(gè)段保留一定空間。
× 其它描述子空間。例如:用戶界別(user-level),局部描述符表,更多的任務(wù)狀態(tài)描述子。
Sysenter/Sysexit
如果你想使用 Intel 的 Sysenter/Sysexit 例程,那么全局描述符表必須這樣組織:
× 前面提到的一些段(NULL 描述子,kernel stuff,等等)
× DPL0 代碼段描述子。Sysenter 使用。
× DPL0 數(shù)據(jù)段描述子。Sysenter 棧使用。
× DPL3 代碼段描述子。Sysexit 后需要執(zhí)行的代碼。
× DPL3 數(shù)據(jù)段描述子。在 Sysexit 后,用戶態(tài)的棧。
× 其它描述子。
DPL0 代碼段的內(nèi)容被加載到 MSR。其它值通過這個(gè)值來(lái)計(jì)算。具體參考 Intel 的手冊(cè)。
平坦模式初始化(
Flat Setup)如果想使用不需要翻譯(查表,轉(zhuǎn)換)的 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) 在這種模式下,沒有辦法保護(hù)代碼段不可寫,因?yàn)榇a段與數(shù)據(jù)段是重疊的。
微內(nèi)核模式初始化(Small Kernel Setup)
如果基于某種原因需要將代碼段與數(shù)據(jù)段分開,并且假設(shè)每個(gè)有 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)
這意味著在物理內(nèi)存 4M 空間內(nèi)的信息可以從 CS:0 開始訪問。物理內(nèi)存 8M 空間內(nèi)的信息可以從 DS:0 開始訪問。這不是一個(gè)好的設(shè)計(jì)。
具體應(yīng)該怎么操作呢?
禁用中斷
如果中斷處于開啟狀態(tài),就禁用中斷,否則是自找麻煩。
向表格中填充數(shù)據(jù)
我們現(xiàn)在還沒有給出 GDT[] 的具體結(jié)構(gòu)。這是有目的的。描述子的實(shí)際結(jié)構(gòu)因?yàn)槟撤N原因有點(diǎn)混亂。地址被分成三個(gè)部分,無(wú)法進(jìn)行編碼限制,同時(shí)還要正確設(shè)置很多標(biāo)志位。
/*** /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 指令設(shè)置。
對(duì)于實(shí)模式
線性地址是通過段基址右移 4 位,然后加上段內(nèi)偏移獲得的。假設(shè) GDT 和 GDT_end 兩個(gè)符號(hào)在當(dāng)前的數(shù)據(jù)段:
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
平坦的保護(hù)模式下
平坦意味著數(shù)據(jù)段的地址從 0 開始。如果通過 GRUB 進(jìn)行引導(dǎo)就是這種情況。假設(shè)你調(diào)用 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?
非平坦的保護(hù)模式
如果數(shù)據(jù)段的起始地址不是 0,就屬于這種模式。
需要:
"MOV EAX, ..."
"ADD EAX, base_of_your_data_segment_which_you_should_know"
"MOV ..., EAX"
?
重新設(shè)置各個(gè)段寄存器
無(wú)論怎么修改全局描述符表,如果沒有設(shè)置各個(gè)段寄存器,修改不會(huì)起作用。
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
?
為什么局部描述符表很特別?
像全局描述符表一樣,局部描述符表也包含關(guān)于內(nèi)存區(qū)域的描述子,但是這些描述子被稱作門。每個(gè)任務(wù)都可以有自己的局部描述符表,當(dāng)使用硬件任務(wù)切換時(shí),處理器會(huì)自動(dòng)切換到正確的局部描述符表。
因?yàn)閷?duì)于每個(gè)任務(wù)來(lái)說(shuō),局部描述符表可能不同,局部描述符表不是一個(gè)保存系統(tǒng)相關(guān)信息的地方,例如:任務(wù)狀態(tài)描述子,或者其它局部描述符表,而這些是全局描述符表的責(zé)任。因?yàn)槿直硎龈奖斫?jīng)常改變,因此他的設(shè)置與全局描述符表還有中斷描述符表有些不同。局部描述符表不是通過直接設(shè)置其地址與大小完成的,那些信息被保存在了全局描述符表(選擇子的類型是 LDT), 對(duì)應(yīng)選擇子的信息如下:
GDTR (base + limit)+-- GDT ------------+| | SELECTOR ---> [LDT descriptor ]----> LDTR (base + limit)| | +-- LDT ------------+| | | |... ... ... ...+-------------------+ +-------------------+ 在 386+處理器的分頁(yè)機(jī)制下,局部描述符表已經(jīng)沒用了。已經(jīng)沒有必要設(shè)置多個(gè)局部描述符選擇子,因此對(duì)于系統(tǒng)開發(fā)來(lái)說(shuō)可以忽略處理器的這個(gè)特性了。?
什么是中斷描述符表?是否需要中斷描述符表?
參考:http://wiki.osdev.org/index.php?title=Interrupts_for_dummies&action=edit
=========================================================================
GDT 的加載使用 LDGT 指令。GDT 的結(jié)構(gòu)如下:
GDTRoffset?是表格自身的虛擬地址。size?為表格的大小減一。原因是:size?的最大值是 65535,而全局描述符表對(duì)打可以還有 65536 byte(8192 個(gè)表項(xiàng))。每個(gè)表項(xiàng) 8byte,其復(fù)雜的結(jié)構(gòu)如下:
A GDT EntryWhat "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 BitsThe 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.
轉(zhuǎn)載于:https://www.cnblogs.com/Proteas/archive/2010/11/28/2335682.html
總結(jié)
以上是生活随笔為你收集整理的[翻译]Global Descriptor Table-GDT的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 未能加载文件或程序集“SqlServer
- 下一篇: 南方人物周刊:智能手机割据战