实模式与保护模式
作者:fireaxe_hq@hotmail.com 博客:fireaxe.blog.chinaunix.net? ? 1.1 ??????? 保護模式的概念
1.1.1 ?什么是保護模式
在IA32下,CPU有兩種工作模式:實模式和保護模式。在實模式下,段寄存器含有段值,為訪問存儲器形成物理地址時,處理器引用相應的某個段寄存器并將其值乘以16,形成20位的段基地址。計算公式如下:物理地址 = 段值*16 + 偏移。
其中段值和偏移都是16位的,這樣通過“段:偏移”的方式達到了1MB的尋址能力。
在保護模式下,寄存器是32位的,但是為了兼容性,地址仍然用“段:偏移”的方式來表示,只不過這時的保護模式下的“段”的概念已經發生了根本的改變,雖然段值仍然由原來的cs、ds等寄存器表示,但是此時它僅僅是一個索引,叫做選擇子,指向一個數據結構(叫做GDT,Global Descriptor Table全局描述符表或者LDT,Local Descriptor Table局部描述符表)的一個表項(叫做Descriptor描述符)。
1.1.2 ?地址轉換方式
實模式下的地址轉換方式是段地址加偏移量。段地址左移4位加偏移量得到線性地址。假設我們在ES中存入0x1000,在DI中存入0xFFFF,則線性地址ES:DI=0x1000*0x10+0xFFFF=0x1FFFF。
保護模式下的地址轉換方式也是段地址加偏移量。段地址不是直接放在段寄存器中,而是以段寄存器中的值為偏移量(選擇子)、Gdtr寄存器中的值作為基址,找到段地址。假設上面的數據不變ES=0x1000,DI=0xFFFF,現在ES:DI等于什么呢?公式如下:(注:0x1000=1000000000000b= 10 0000 0000 0 00)ES:DI=全局描述符表中第0x200項描述符給出的段基址+0xFFFF。實模式下稱ES為“段寄存器”,而到了保護模式就說是“選擇子”。其實它們都是一種映射,只是映射規則不同而已:在實模式下這個“地址轉換方式”是“左移4位”;在保護模式下是“查全局/局部描述表”。前者是系統定義的映射方式,后者是用戶自定義的轉換方式。
1.1.3 ?GDT描述符
保護模式下引入描述符來描述各種數據段,所有的描述符均為8個字節(0-7),由第5個字節說明描述符的類型,類型不同,描述符的結構也有所不同。若干個描述符集中在一起組成描述符表,而描述符表本身也是一種數據段,也使用描述符進行描述。從現在起,“地址轉換”由描述符表來完成,從這個意義上說,描述符表是一張地址轉換函數表。
(1) P:? 存在(Present)位。
P=1 表示描述符對地址轉換是有效的,或者說該描述符所描述的段存在,即在內存中;
P=0 表示描述符對地址轉換無效,即該段不存在。使用該描述符進行內存訪問時會引起異常。
(2) DPL:? 表示描述符特權級(Descriptor Privilege level),共2位。它規定了所描述段的特權級,用于特權檢查,以決定對該段能否訪問。
(3) S:?? 說明描述符的類型。
對于存儲段描述符而言,S=1,以區別與系統段描述符和門描述符(S=0)。
(4) TYPE: 說明存儲段描述符所描述的存儲段的具體屬性。
數據段類型
類型值?????????? 說明
?????? ----------------------------------
?????? 0??????????? 只讀
?????? 1??????????? 只讀、已訪問
?????? 2??????????? 讀/寫
?????? 3??????????? 讀/寫、已訪問
?????? 4??????????? 只讀、向下擴展
?????? 5??????????? 只讀、向下擴展、已訪問
?????? 6??????????? 讀/寫、向下擴展
?????? 7??????????? 讀/寫、向下擴展、已訪問
?
代碼段類型???
類型值?????????? 說明
----------------------------------
?????? 8??????????? 只執行
?????? 9??????????? 只執行、已訪問
?????? A??????????? 執行/讀
?????? B??????????? 執行/讀、已訪問
?????? C??????????? 只執行、一致碼段
?????? D??????????? 只執行、一致碼段、已訪問
?????? E??????????? 執行/讀、一致碼段
?????? F??????????? 執行/讀、一致碼段、已訪問
?
系統段類型
類型編碼?????? 說明
?????? ----------------------------------
?????? 0??????????? <未定義>
?????? 1??????????? 可用286TSS
?????? 2??????????? LDT
?????? 3??????????? 忙的286TSS
?????? 4??????????? 286調用門
?????? 5??????????? 任務門
?????? 6??????????? 286中斷門
?????? 7??????????? 286陷阱門
?????? 8??????????? 未定義
?????? 9??????????? 可用386TSS
?????? A??????????? <未定義>
?????? B??????????? 忙的386TSS
?????? C??????????? 386調用門
?????? D??????????? <未定義>
?????? E??????????? 386中斷門
?????? F??????????? 386陷阱門
?
(5) G:??? 段界限粒度(Granularity)位。
?????? G=0 表示界限粒度為字節;
?????? G=1 表示界限粒度為4K 字節。
注意,界限粒度只對段界限有效,對段基地址無效,段基地址總是以字節為單位。
?
(6) D:??? D位是一個很特殊的位,在描述可執行段、向下擴展數據段或由SS寄存器尋址的段(通常是堆棧段)的三種描述符中的意義各不相同。
?? ⑴ 在描述可執行段的描述符中,D位決定了指令使用的地址及操作數所默認的大小。
?????? ① D=1表示默認情況下指令使用32位地址及32位或8位操作數,這樣的代碼段也稱為32位代碼段;
?????? ② D=0 表示默認情況下,使用16位地址及16位或8位操作數,這樣的代碼段也稱為16位代碼段,它與80286兼容。可以使用地址大小前綴和操作數大小前綴分別改變默認的地址或操作數的大小。
?? ⑵ 在向下擴展數據段的描述符中,D位決定段的上部邊界。
?????? ① D=1表示段的上部界限為4G;
?????? ② D=0表示段的上部界限為64K,這是為了與80286兼容。
? ?⑶ 在描述由SS寄存器尋址的段描述符中,D位決定隱式的堆棧訪問指令(如PUSH和POP指令)使用何種堆棧指針寄存器。
?????? ① D=1表示使用32位堆棧指針寄存器ESP;
?????? ② D=0表示使用16位堆棧指針寄存器SP,這與80286兼容。
?
(7) AVL:? 軟件可利用位。80386對該位的使用未左規定,Intel公司也保證今后開發生產的處理器只要與80386兼容,就不會對該位的使用做任何定義或規定。
描述符類型值說明,其中:
?????? DA_? : Descriptor Attribute
????? ?D??? : 數據段
?????? C??? : 代碼段
?????? S??? : 系統段
?????? R??? : 只讀
?????? RW?? : 讀寫
?????? A??? : 已訪問
?????? 其它 : 可按照字面意思理解
----------------------------------------------------------------------------
DA_32?????????? EQU?????? 4000h???? ; 32 位段
DA_DPL0???????????? EQU?????? ? 00h???? ; DPL = 0
DA_DPL1???????????? EQU?????? ? 20h???? ; DPL = 1
DA_DPL2???????????? EQU?????? ? 40h???? ; DPL = 2
DA_DPL3???????????? EQU?????? ? 60h???? ; DPL = 3
----------------------------------------------------------------------------
?存儲段描述符類型值說明
----------------------------------------------------------------------------
DA_DR????????? EQU?????? 90h? ; 存在的只讀數據段類型值
DA_DRW????????????? EQU?????? 92h? ; 存在的可讀寫數據段屬性值
DA_DRWA??????????? EQU?????? 93h? ; 存在的已訪問可讀寫數據段類型值
DA_C??????????? EQU?????? 98h? ; 存在的只執行代碼段屬性值
DA_CR????????? EQU?????? 9Ah ; 存在的可執行可讀代碼段屬性值
DA_CCO????????????? EQU?????? 9Ch ; 存在的只執行一致代碼段屬性值
DA_CCOR??????????? EQU?????? 9Eh ; 存在的可執行可讀一致代碼段屬性值
----------------------------------------------------------------------------
?系統段描述符類型值說明
----------------------------------------------------------------------------
DA_LDT??????? EQU?????? ? 82h???? ; 局部描述符表段類型值
DA_TaskGate? EQU?????? ? 85h???? ; 任務門類型值
DA_386TSS?? EQU?????? ? 89h???? ; 可用 386 任務狀態段類型值
DA_386CGate?????? EQU?????? ? 8Ch???? ; 386 調用門類型值
DA_386IGate EQU?????? ? 8Eh???? ; 386 中斷門類型值
DA_386TGate EQU?????? ? 8Fh???? ; 386 陷阱門類型值
----------------------------------------------------------------------------
1.1.4 ?Gdtr寄存器
保護模式下,地址轉換通過查詢GDT表實現。那么,系統如何知道GDT在內存中的位置呢?
在80x86系列中使用寄存器Gdtr實現,GDTR寄存器長度為6字節(48位),其中低2字節表示GDT表長度長度(limit),高4字節表示GDT表基址(實際是用于校驗Seletor是否越界)。
利用Gdtr寄存器只能找到GDT表的基址,而要找到對應的表項則需要使用Selector了。
1.1.5 ?Selector選擇子
選擇子是一個2字節的數,共16位,最低2位表示RPL,第3位表示查表是利用GDT(全局描述符表)還是LDT(局部描述符表)進行,最高13位給出了所需的描述符在GDT描述符表中的地址。(注:13位正好足夠尋址8K項)
在程序運行時,段寄存器中保存的就是Selector。CPU通過Gdtr寄存器加段寄存器中保存的Selector的方式索引到對應的GDT表項,找到實際的段地址,然后加上指令中的地址,才能得到線性地址。如果沒有使用頁表結構,則這個線性地址就是物理地址了。
?RPL(Requested Privilege Level): 請求特權級,用于特權檢查。
?TI(Table Indicator): 引用描述符表指示位
?????? TI=0 指示從全局描述符表GDT中讀取描述符;
?????? TI=1 指示從局部描述符表LDT中讀取描述符。
----------------------------------------------------------------------------
選擇子類型值說明
??? SA_ : Selector Attribute
SA_RPL0????????????? EQU?????? 0???? ; ┓
SA_RPL1????????????? EQU?????? 1???? ; ┣ RPL
SA_RPL2????????????? EQU?????? 2???? ; ┃
SA_RPL3????????????? EQU?????? 3???? ; ┛
?
SA_TIG???????? EQU?????? 0???? ; ┓TI
SA_TIL????????? EQU?????? 4???? ; ┛
----------------------------------------------------------------------------
?
| ; usage: Descriptor Base, Limit, Attr ;??????? Base:? dd ;??????? Limit: ?dd (low 20 bits available) ;??????? Attr:? ?dw (lower 4 bits of higher byte are always 0) %macro Descriptor 3 ?????? dw?? %2 & 0FFFFh??????????????????????????? ; 段界限 1???????????????????????? (2 字節) ?????? dw?? %1 & 0FFFFh??????????????????????????? ; 段基址 1???????????????????????? (2 字節) ?????? db??? (%1 >> 16) & 0FFh???????????? ; 段基址 2???????????????????????? (1 字節) ?????? dw?? ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)????? ; 屬性 1 + 段界限 2 + 屬性 2???????? (2 字節) ?????? db??? (%1 >> 24) & 0FFh???????????? ; 段基址 3???????????????????????? (1 字節) %endmacro ; 共 8 字節 |
1.1.6 ?CR0寄存器
CR0是x86系列cpu的保護控制寄存器。CR0的第0位PE位用于設置CPU工作模式。
PE=0:實模式
PE=1:保護模式
1.1??進入保護模式的主要步驟:
1.1.1 ?GDT表
GDT表是存儲GDT描述符的數組,每一個描述符對應著一個段。GDT表中的第一項必須為全0。表項類型為結構體Descriptor,結構如下:
| ; usage: Descriptor Base, Limit, Attr ;? ??%1??? Base:? dd ;??? %2??? Limit: dd (low 20 bits available) ;??? %3??? Attr:? dw (lower 4 bits of higher byte are always 0) %macro Descriptor 3 ???dw??%2 & 0FFFFh????????; 段界限 1??? (2 字節) ???dw??%1 & 0FFFFh??????? ; 段基址 1??? (2 字節) ???db??(%1 >> 16) & 0FFh??; 段基址 2??? (1 字節) ???dw??((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)?; 屬性 1 + 段界限 2 + 屬性 2? (2 字節) ???db??(%1 >> 24) & 0FFh??; 段基址 3????(1 字節) %endmacro ; 共 8 字節 |
根據nasm編譯器的規則,結構體定義以%macro開始,以%endmacro結束。結構體名為Descriptor,宏名后的3表示該結構體有三個成員變量。
GDT表的定義方式如下:
| [SECTION .gdt] ; GDT?????????????????????? ????Base Addr?? Limit?????? ???Attrib LABEL_DES_GDT:??? Descriptor ??0,???? ?????0,????????? ???0 LABEL_DES_CODE32: Descriptor?? 0, ?????????Code32Len - 1,? DA_C + DA_32 LABEL_DES_VIDEO:? Descriptor? ?0B8000h, ???0ffffh,?? ??????DA_DRW ; GDT end |
這里可以看到給結構體賦值的方式,每一項對應結構體中的一個成員變量。以表項LABEL_DES_VIDEO為例,0B8000h對應%1,0ffffh對應%2,DA_DRW對應%3。
注意:表項LABEL_DES_CODE32保存的是我們要跳入的32位代碼段的GDT表項,其中Base Addr是0,因為此時還不知道32為代碼段的地址,在后面的16為代碼段中才會設置它。
1.1.2 ?Gdtr
| GdtLen ?equ $ - LABEL_DES_GDT?? ; Gdt Len GdtPtr? ?dw ?GdtLen - 1????? ; Gdt Limit ??????? ?dd? 0?????????????? ; Gdt Base Addr |
GDT表的地址保存在寄存器Gdtr中,該地址首先通過上面的語句計算而得后存在變量GdtPtr中,之后才加載。GdtPtr低2字節保存GDT表長度,通過“$ - LABEL_DES_GDT”計算而得。GdtPtr高4字節保存GDT表基址,這里先置0,后面會存入地址。
?
1.1.3 ?Selector選擇子
| ; Gdt Selector SelectorCode32? equ LABEL_DES_CODE32 - LABEL_DES_GDT SelectorVideo?? equ LABEL_DES_VIDEO - LABEL_DES_GDT ? |
選擇子Selector中保存的是各段對應GDT表項相對地址,通過上面的語句計算而得。
?
1.1.4 ?16位段與32位段
| [SECTION .s16] [BITS 16] …… [SECTION .s32] [BITS 32] …… |
由于本程序實現了有實模式到保護模式的跳轉,所以需要在程序中定義兩個代碼段,一個是16位段;一個是32位段。
1.1.5 ?初始化32位代碼段描述符
| ?????? ; Init 32 bits code segment descriptor ??? xor eax, eax ??? mov ax, cs ??? shl eax, 4 ??? add eax, LABEL_CODE32 ??? mov word [LABEL_DES_CODE32 + 2], ax ??? shr eax, 16 ??? mov byte [LABEL_DES_CODE32 + 4], al ??? mov byte [LABEL_DES_CODE32 + 7], ah ? |
首先要設置32為代碼段對應GDT表項的基址。基址對應著表項中的第2、3、4、7,四個字節。
1.1.6 ?準備加載gdtr
| ??? ; Prepare for loading gdtr ??? xor eax, eax ??? mov ax, cs ??? shl eax, 4 ??? add eax, LABEL_DES_GDT ??? mov dword [GdtPtr + 2], eax |
這段代碼用于設置變量GdtPtr,長度為2字節。
1.1.7 ?加載gdtr
| ??? ; Load gdtr ??? lgdt [GdtPtr] |
此處加載gdtr寄存器,x86使用專門的指令lgdt實現gdtr寄存器的設置。
1.1.8 ?關閉中斷
| ??? ; close int ??? Cli |
由于實模式與保護模式下的中斷模式不同,所以在實模式/保護模式轉換時必須關閉中斷。
1.1.9 ?打開A20
| ??? ; open A20 ??? in al, 92h ??? or al, 00000010b ??? out 92h, al |
80286及之前的cpu最大尋址空間為1M,超過1M的地址(也就是使用了A20之后的地址線),會開始從0重新計算,即使跳入保護空間,也無法使用超過1M的地址。
80386開始,增加了保護模式,尋址空間變為4G。為了與80286之前的cpu兼容,超過1M的地址平時是關閉的,只有打開A20后才能訪問超過1M的地址。
上面只是打開A20的一種方式,但不是唯一的方式。
1.1.10 ????????? 設置cr0的PE位
| ??? ; open protected mode ??? mov eax, cr0 ??? or? eax, 1 ??? mov cr0, eax |
此處打開cr0的PE位,實際上就使能了保護模式,也就是說,“mov cr0, eax”這一句后,系統就運行于保護模式下了。但是,此時ics的值仍然是實模式下的值,我們需要把代碼段的Selector選擇子裝入cs。
1.1.11 ????????? 跳入保護模式
| ??? ; jump into protected mode ??? jmp dword SelectorCode32:0??? ? |
這個跳轉是為了設置cs寄存器,其目的是選擇子SelectorCode32對應的GDT描述符LABEL_DESC_CODE32對應的段首地址,即標號LABEL_??? CODE32C處。
該句指令還在16位段中執行,而目標地址卻是32位的,從這一點上看,他是混合16位于32位的代碼。所以,jmp后的dword就必不可少了。如果沒有dword,編譯出來的只是16位代碼。假設目標地址偏移量為0x12345678,執行jmp SelectorCode32:0x12345678,則等價于jmp SelectorCode32:0x5678。
Nasm編譯器的好處之一就是可以通過jmp后添加dword,指定編譯后為32位指令。
至此,我們真正跳入了保護模式。
?
1.1.12 ????????? 顯示字符
| LABEL_CODE32: ??? mov eax, SelectorVideo ??? mov gs, eax ??? mov edi, (80 * 6 + 6) * 2????? ; Line: 6, Column: 6 ??? mov ah, 0Ch???????????????? ??????? ; 0000: background(black)??? 1100: foreground(red) ??? mov al, 'P' ??? mov [gs:edi], ax |
這是進入保護模式后執行的代碼。SelectorVideo選擇子對應著顯存的基址,我們通過把‘P’放入顯存,實現了字符‘P’的顯示。
? 今天終于實現了由匯編進入c程序,由于C程序都是在32位模式下操作的,所以需要由匯編中進入保護模式,然后才能進入C程序。保護模式下與實模式有一點不同,要注意個段的屬性。
???? 實模式下段地址設置:
?mov ax,?cs
?mov ds, ax
?mov ss, ax
?mov es, ax
?mov sp, 01000h
????? 保護模式下段地址設置:
?mov eax, SelectorStack
?mov ds, eax
?mov ss, eax
?mov es, eax
?mov sp, 01000h
???????實模式中可以用代碼段寄存器cs來設置堆棧、數據等段寄存器;保護模式中斷寄存器是設置了代碼段屬性的(DA_C + DA_32),所以只能用用于代碼段,堆棧、數據段等需要進行數據操作的段,必須設置其他GDT段(屬性為DA_DRW)。
?
總結
- 上一篇: babyos (三)——利用BIOS I
- 下一篇: bios x86保护模式下的软盘操作