源碼如下: 運行結果如下,在屏幕最右邊有一個紅色的P: 源碼解析: 1.首先程式跳轉至LABEL_BEGIN處,jmp LABEL_BEGIN。將ds、es、ss段寄存器全部初始化為當前代碼段。 2.初始化32位代碼段描述符 在實模式下,也就是8086的16位的CPU的定址方式是段x16+偏移,而在 ...
源碼如下:
; ========================================== ; pmtest1.asm ; 編譯方法:nasm pmtest1.asm -o pmtest1.bin ; ========================================== %include "pm.inc" ; 常量, 巨集, 以及一些說明 org 07c00h jmp LABEL_BEGIN [SECTION .gdt] ; GDT ; 段基址, 段界限 , 屬性 LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符 LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32 ; 非一致代碼段 LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 顯存首地址 ; GDT 結束 GdtLen equ $ - LABEL_GDT ; GDT長度 GdtPtr dw GdtLen - 1 ; GDT界限 dd 0 ; GDT基地址 ; GDT 選擇子 SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT ; END of [SECTION .gdt] [SECTION .s16] [BITS 16] LABEL_BEGIN: mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, 0100h ; 初始化 32 位代碼段描述符 xor eax, eax mov ax, cs shl eax, 4 add eax, LABEL_SEG_CODE32 mov word [LABEL_DESC_CODE32 + 2], ax shr eax, 16 mov byte [LABEL_DESC_CODE32 + 4], al mov byte [LABEL_DESC_CODE32 + 7], ah ; 為載入 GDTR 作准備 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_GDT ; eax <- gdt 基地址 mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址 ; 載入 GDTR lgdt [GdtPtr] ; 關中斷 cli ; 打開地址線A20 in al, 92h or al, 00000010b out 92h, al ; 準備切換到保護模式 mov eax, cr0 or eax, 1 mov cr0, eax ; 真正進入保護模式 jmp dword SelectorCode32:0 ; 執行這一句會把 SelectorCode32 裝入 cs, ; 並跳轉到 SelectorCode32:0 處 ; END of [SECTION .s16] [SECTION .s32]; 32 位代碼段. 由實模式跳入. [BITS 32] LABEL_SEG_CODE32: mov ax, SelectorVideo mov gs, ax ; 視頻段選擇子(目的) mov edi, (80 * 11 + 79) * 2 ; 屏幕第 11 行, 第 79 列。 mov ah, 0Ch ; 0000: 黑底 1100: 紅字 mov al, 'P' mov [gs:edi], ax ; 到此停止 jmp $ SegCode32Len equ $ - LABEL_SEG_CODE32 ; END of [SECTION .s32]
運行結果如下,在屏幕最右邊有一個紅色的P:
源碼解析:
1.首先程式跳轉至LABEL_BEGIN處,jmp LABEL_BEGIN。將ds、es、ss段寄存器全部初始化為當前代碼段。
2.初始化32位代碼段描述符
在實模式下,也就是8086的16位的CPU的定址方式是段x16+偏移,而在保護模式下,段寄存器值變成了一個索引,這個索引指向段描述符,也就是GDT中對應的那個描述符,描述符中包含了最終的基地址。
38-41行表示將當前程式代碼段左移4位也就是x16,再加上LABEL_SEG_CODE32的偏移地址,最終形成LABEL_SEG_CODE32的物理地址,42-45表示把此物理地址載入到段描述符對應的段基址位置,段描述符格式如下圖所示
段基地址0-15位也就是LABEL_DESC_CODE32 + 2地址處,將16位的ax傳入,第43行向右移動16位表示已經傳入的16位消除,然後將剩餘的兩個8位高低寄存器值傳入對應的段基地址處。
Descriptor是在pm.inc中定義的巨集,如下圖所示,表示的就是段描述符的格式:
3.載入GDTR,GDRT的結構圖如下所示
GDTR是唯一的一個指向段描述符表的寄存器,48-55行就是把段描述符表的基地址載入到GDTR寄存器當中。48行清除eax值,49-52是把LABEL_GDT的物理地址載入到20行的GdtPtr的雙字處,也就是GDT基地址。55行把GdtPtr地址的內容載入到GDTR,雙位元組dw對應的是16位界限,雙字dd對應的是32位基地址。
4.關中斷和打開A20
保護模式下中斷處理機制和實模式不同,所以先關閉中斷,指令為cli。打開A20為歷史原因防止偏移超出最大值時回滾。58-63行
5.切換到保護模式
只要把寄存器cr0的第0位置為1就行了。66-68行
6.真正進入保護模式的代碼段
jmp dword SelectorCode32:0,這一行是真正進入保護模式的代碼,SelectorCode32是個段選擇子,段選擇子的作用是找到對應的段描述符從而找到對應的段基地址。段選擇子的格式如下圖所示
從位3開始表示為段描述符表的索引,這句代碼執行完之後就會把描述符LABEL_DESC_CODE32中的段基址也就是LABEL_SEG_CODE32載入到cs。
dword的作用是因為當前還是16位的代碼,如果沒有dword,SelectorCode32:0的偏移地址如果超出16位那麼只會截取16位。
到此已經完全進入32位保護模式的代碼了。
7.顯示字元
接下來就會跳轉到LABEL_SEG_CODE32處運行指令了,80-81行把段選擇子SelectorVideo傳入段寄存器gs中,也就是描述符LABEL_DESC_VIDEO的段基地址0B8000h,這就是顯存的基地址。83行是顯存的偏移地址,84-85行是字元及字元屬性,86行為最終顯示字元。
8.描述符屬性
代碼段的屬性是DA_C + DA_32,根據pm.inc中的定義,DA_C是98h
對應的二進位為10011000。根據第2條的格式,DA_C其實是上面的第8-15位,第15位P是1表明這個段在記憶體中存在,S位是1表明這個段是代碼段或數據段,TYPE為1000也就是8表明這個段是只執行的代碼段,TYPE格式如下圖所示
DA_32是4000h,
4000h為100000000000000,表明是從上面的位8開始計算的後面15位也就是D/B位為1,表明這個段是32位的代碼段。