上節內容是從實模式進入到保護模式,只是進入保護模式打印了一個字母P。但是沒有體現出保護模式的優勢,也沒有從保護模式中返回。這節就是要體驗保護模式下讀寫大地址內存的能力和從保護模式返回到實模式。
這節要做的內容如下:首先在屏幕的第11行輸出In Protect Mode now. ^-^。然後在屏幕第12行輸出內存中起始地址為5MB的連續的8個字節。然後向這個以5MB開始的內存中寫入ABCDEFGH。再次在第13行輸出這8個字節。結果演示如下:
源代碼300多行,很長,分段講述,主要講新增的部分
首先是GDT段
[SECTION .gdt] ; GDT ; 段基址, 段界限 , 屬性 LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符 LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ; Normal 描述符 LABEL_DESC_CODE32: Descriptor 0, SegCode32Len-1, DA_C+DA_32; 非一致代碼段, 32 LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C ; 非一致代碼段, 16 LABEL_DESC_DATA: Descriptor 0, DataLen-1, DA_DRW ; Data LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA+DA_32; Stack, 32 位 LABEL_DESC_TEST: Descriptor 0500000h, 0ffffh, DA_DRW LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 顯存首地址 ; GDT 結束 GdtLen equ $ - LABEL_GDT ; GDT長度 GdtPtr dw GdtLen - 1 ; GDT界限 dd 0 ; GDT基地址 ; GDT 選擇子 SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDT SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT SelectorCode16 equ LABEL_DESC_CODE16 - LABEL_GDT SelectorData equ LABEL_DESC_DATA - LABEL_GDT SelectorStack equ LABEL_DESC_STACK - LABEL_GDT SelectorTest equ LABEL_DESC_TEST - LABEL_GDT SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT ; END of [SECTION .gdt]
相比於上一節,這裡新增了下面幾個描述符表項:
LABEL_DESC_NORMAL:這個描述符用在從保護模式返回實模式的過程,為了讓對應的段描述符告訴緩沖寄存器中含有合適的界限和屬性。這兒有點不明白
LABEL_DESC_CODE16:在保護模式返回到實模式的過程中用到。用來將SeletorNormal選擇子賦給ds、es、fs、gs、ss,跟上面LABEL_DESC_NORMAL合作實現返回實模式的准備工作。在書上43頁
LABEL_DESC_DATA:數據段描述符
LABEL_DESC_STACK:棧段描述符
LABEL_DESC_TEST:用來測試的大地址內存(5MB起始的內存)
自然地,需要增加了相應的選擇子。
接下來新增了數據段。
[SECTION .data1] ; 數據段 ALIGN 32 [BITS 32] LABEL_DATA: SPValueInRealMode dw 0 ; 字符串 PMMessage: db "In Protect Mode now. ^-^", 0 ; 在保護模式中顯示 OffsetPMMessage equ PMMessage - $$ StrTest: db "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0 OffsetStrTest equ StrTest - $$ DataLen equ $ - LABEL_DATA ; END of [SECTION .data1]
對於上面的代碼
SPValueInRealMode:實模式中棧頂指針的值會保存在這裡。干什麼用?
ALIGN 32:這是個偽指令,告訴編譯器本偽指令下面的內存變量必須從下一個能被32整除的地址開始分配。
接下來你會發現定義的兩個數據塊PMMessage和StrTest下面都定義了另外一個符號Offset_PMMessage和OffsetStrTest,用來表示對應的上一個字符串相對於本節開始處(也就是LABEL_DATA處)的偏移。定義這兩個符號是因為在保護模式下需要用到這個偏移來定位字符串的位置。而不再需要實模式下的地址。
你看到GDT中關於數據段的段基址並沒有初始化,所以需要對數據段描述符進行初始化,在[SECTION .s16]中,初始化代碼如下:
; 初始化數據段描述符 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_DATA mov word [LABEL_DESC_DATA + 2], ax shr eax, 16 mov byte [LABEL_DESC_DATA + 4], al mov byte [LABEL_DESC_DATA + 7], ah
回顧一下,保護模式下段值仍然是用16位的寄存器來存儲。段值×16+偏移地址在此時它僅僅變成了一個索引,這個索引指向的就是GDT的一個表項,這個表項裡面詳細定義了段的起始地址、界限、屬性等內容。在書的31頁有詳細介紹。
接下來定義了全局堆棧段,因為保護模式下用到了堆棧,這些都是需要我們自己定義的。因此需要在實模式下新建堆棧段。
定義堆棧段的源代碼如下:
; 全局堆棧段 [SECTION .gs] ALIGN 32 [BITS 32] LABEL_STACK: times 512 db 0 TopOfStack equ $ - LABEL_STACK - 1 ; END of [SECTION .gs]
這裡定義的棧的大小是512字節,棧頂是TopOfStack。
但計算棧頂的時候減一是為什麼呢?這兒我也沒搞清楚。棧為空時,ss:esp指向的棧的最底部單元下面的單元,這個單元的偏移地址應該為棧最底部的雙字(因為是32位的堆棧段)單元的偏移地址+4。但是這裡我無論怎麼計算都不是在所說的位置,好像棧空間小於512字節了,難道是為了避免訪問越界,減1更有保障?
在[SECTION .s16]有對應的堆棧段描述符如下:
; 初始化堆棧段描述符 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_STACK mov word [LABEL_DESC_STACK + 2], ax shr eax, 16 mov byte [LABEL_DESC_STACK + 4], al mov byte [LABEL_DESC_STACK + 7], ah
再看看GDT中的關於堆棧的表項,屬性是DA_DRWA+DA_32。DA_DRWA表示存在的已訪問可讀寫數據段類型值。DA_32表示他是一個32位的堆棧段。
在[SECTION .s32]有對應的堆棧初始化代碼如下:
mov ax, SelectorStack mov ss, ax ; 堆棧段選擇子 mov esp, TopOfStack
從上面可以看到:
數據段,棧段,代碼段的定義是獨立的;
數據段,棧段,32位代碼段描述符的初始化是在16位代碼段中定義的;
數據段,棧段相應寄存器的初始化要看它門在哪個代碼段中使用,比如說這裡的棧段是在32位代碼段中使用的,所以ss,esp寄存器的初始化過程實在32位代碼段中進行的。
這段用來對GDT進行初始化並進入保護模式,詳細的介紹參考上一節文章。
[SECTION .s16] [BITS 16] LABEL_BEGIN: mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, 0100h mov [LABEL_GO_BACK_TO_REAL+3], ax mov [SPValueInRealMode], sp ; 初始化 16 位代碼段描述符 mov ax, cs movzx eax, ax shl eax, 4 add eax, LABEL_SEG_CODE16 mov word [LABEL_DESC_CODE16 + 2], ax shr eax, 16 mov byte [LABEL_DESC_CODE16 + 4], al mov byte [LABEL_DESC_CODE16 + 7], ah ; 初始化 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 ; 初始化數據段描述符 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_DATA mov word [LABEL_DESC_DATA + 2], ax shr eax, 16 mov byte [LABEL_DESC_DATA + 4], al mov byte [LABEL_DESC_DATA + 7], ah ; 初始化堆棧段描述符 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_STACK mov word [LABEL_DESC_STACK + 2], ax shr eax, 16 mov byte [LABEL_DESC_STACK + 4], al mov byte [LABEL_DESC_STACK + 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, 並跳轉到 Code32Selector:0 處
mov [LABEL_GO_BACK_TO_REAL+3], ax、mov [SPValueInRealMode], sp:這條指令是干什麼的呢?先告訴你它是為了後面從保護模式返回到實模式用,具體為什麼是這樣在下面有詳細的介紹
程序剛加載就會執行這個代碼段,之後跳到32位代碼段中運行,32位代碼段如下。
相應的代碼如下。雖然很長,但很簡單,
首先,
是初始化相應段的寄存器。包括數據段、測試段、視頻段(屏幕)、堆棧段
然後,
顯示字符串In Protect Mode now. ^-^
然後,
顯示從內存地址5MB起始的8個字節,然後向內存5MB起始的地址中寫入數據段中的字符串,然後再顯示從內存5MB起始的8個字節。每次顯示完成後都執行換行操作。
最後通過跳轉指令jmp SelectorCode16:0返回到實模式,到這裡32位代碼段就結束了。又回到了實模式。下面的一小節介紹跳轉到的目的地做了什麼工作。
先附上32位代碼段,裡面有詳細的注釋供參考
[SECTION .s32]; 32 位代碼段. 由實模式跳入. [BITS 32] LABEL_SEG_CODE32: mov ax, SelectorData mov ds, ax ; 數據段選擇子 mov ax, SelectorTest mov es, ax ; 測試段選擇子 mov ax, SelectorVideo mov gs, ax ; 視頻段選擇子 mov ax, SelectorStack mov ss, ax ; 堆棧段選擇子 mov esp, TopOfStack ; 下面顯示一個字符串 mov ah, 0Ch ; 0000: 黑底 1100: 紅字 xor esi, esi xor edi, edi mov esi, OffsetPMMessage ; 源數據偏移 mov edi, (80 * 10 + 0) * 2 ; 目的數據偏移。屏幕第 10 行, 第 0 列。 cld ; 置標志位DF的置為零 .1: lodsb ; al=((ds)*16+(esi))、(esi)=(esi)+1 test al, al ; al^al,結果不保存,只影響標志位 jz .2 ; 如果ZF標志位為1,轉移到.2 mov [gs:edi], ax ; 否則,輸出到屏幕 add edi, 2 ; 屏幕偏移寄存器加2 jmp .1 ; 跳轉到.1 .2: ; 顯示完畢 call DispReturn ; 換行 call TestRead ; 讀出內存5MB起始的連續8字節內容 call TestWrite ; 向5MB起始的內存中寫入字符串 call TestRead ; 再一次讀出內存5MB起始的連續8字節內容 ; 如果正確,讀出的內存與寫入的內容相同 ; 到此停止 jmp SelectorCode16:0 ; 跳轉到准備工作(返回實模式的准備工作代碼) ; ------------------------------------------------------------------------ TestRead: ;輸出到屏幕函數 xor esi, esi ;esi置0 mov ecx, 8 ; 向屏幕讀出8個字節 .loop: mov al, [es:esi] ; 將內存字節內容傳送到al中 call DispAL ; 以16進制的形式顯示出來 inc esi ; 內存地址增加1 loop .loop ; 繼續循環讀出到屏幕 call DispReturn ; 調用換行函數 ret ; 函數返回 ; TestRead 結束----------------------------------------------------------- ; ------------------------------------------------------------------------ TestWrite: ; 寫入內存函數 push esi ; 保存寄存器esi內容 push edi ; 保存寄存器edi內容 xor esi, esi ; esi置0 xor edi, edi ; edi置0 mov esi, OffsetStrTest ; 源數據偏移 cld ; DF標志位置0 .1: ; 因此內存-內存沒有直接通路,所以下面需要al中轉 lodsb ; al=((ds)*16+(esi))、(esi)=(esi)+1 test al, al ; al^al,結果不保存,只影響標志位 jz .2 ; 如果ZF標志位為0,跳轉到.2 mov [es:edi], al ; 將字符寫入內存 inc edi ; 內存偏移到下一位 jmp .1 ; 循環到寫入的字符串為0時結束 .2: pop edi ; 還原子函數用到的寄存器edi、esi,注意順序 pop esi ret ; TestWrite 結束---------------------------------------------------------- ; ------------------------------------------------------------------------ ; 以16進制顯示 AL 中的數字 ; 默認地: ; 數字已經存在 AL 中 ; edi 始終指向要顯示的下一個字符的位置 ; 被改變的寄存器: ; ax, edi ; ------------------------------------------------------------------------ DispAL: push ecx ; 保存子程序中用到的寄存器ecx、edx push edx mov ah, 0Ch ; 0000: 黑底 1100: 紅字 mov dl, al ; al高4位,低4位分開處理 shr al, 4 ; 先處理高四位 mov ecx, 2 ; 循環兩次,第一次高四位,第二次低四位 .begin: and al, 01111b cmp al, 9 ; 如果大於9,要顯示A~F的ASCii碼 ja .1 add al, '0' ; 如果小於或等於9,顯示0~9的ASCii碼 jmp .2 .1: sub al, 0Ah add al, 'A' .2: mov [gs:edi], ax ; 輸出到屏幕上 add edi, 2 ; 屏幕偏移指針+2 mov al, dl ; 處理低4位 loop .begin add edi, 2 ; edi要時刻指向要顯示下一個字符的位置 pop edx ; 還原子函數用到的寄存器edx、ecx,注意順序 pop ecx ret ; 子程序返回 ; DispAL 結束------------------------------------------------------------- ; ------------------------------------------------------------------------ DispReturn: ; 實現屏幕輸出的換行 push eax ; 保存用到的eax、ebx push ebx mov eax, edi ; mov bl, 160 ; div bl ; 執行結束後,ax存放的是當前行的行號碼 and eax, 0FFh ; 執行結束後,eax存放的是當前行的行號碼 inc eax ; 讓eax存放下一行的行號碼 mov bl, 160 ; mul bl ; 執行結束後,eax存放的是下一行的行首偏移值 mov edi, eax ; 將下一行的行首偏移置傳送到edi中 pop ebx ; 還原子函數用到的寄存器ebx、eax,注意順序 pop eax ret ; 子程序返回 ; DispReturn 結束--------------------------------------------------------- SegCode32Len equ $ - LABEL_SEG_CODE32 ; END of [SECTION .s32]
從保護模式返回到實模式有點復雜。因為在准備結束保護模式回到實模式之前,需要加載一個合適的描述符選擇子到有關的寄存器,以使對應段描述符高速緩沖寄存器中含有合適的段界限和屬性。而且,我們不能從32位的代碼段返回實模式,只能從16位的代碼段中返回。這是因為無法實現從32位代碼段返回時cs告訴緩沖寄存器中的屬性符合實模式的要求(實模式不能改變屬性)。
所以,在這裡,我們新增一個Normal描述符。在返回實模式之前把對應選擇子SelectorNormal加載到ds、es和ss,就是上面所說的這個原因
下面就來看一下保護模式返回到實模式前用到的16位代碼段:
; 16 位代碼段. 由 32 位代碼段跳入, 跳出後到實模式 [SECTION .s16code] ALIGN 32 [BITS 16] LABEL_SEG_CODE16: ; 跳回實模式: mov ax, SelectorNormal mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax mov eax, cr0 and al, 11111110b mov cr0, eax LABEL_GO_BACK_TO_REAL: jmp 0:LABEL_REAL_ENTRY ; 段地址會在程序開始處被設置成正確的值 Code16Len equ $ - LABEL_SEG_CODE16 ; END of [SECTION .s16code]
這段代碼前面6條mov指令就是上面所說的目的:為了使對應段描述符告訴緩存寄存器中含有合適的段界限和屬性,需要加載一個合適的描述符選擇子到有關寄存器中。這邊我現在也不太了解,先記住吧
接下來的3條指令實現將cr0的PE位(第0位)置零,代表運行(將要運行)在保護模式下。
還記得上面提到的mov [LABEL_GO_BACK_TO_REAL+3], ax指令嗎?上面只是說它要用在從保護模式返回到實模式中,這裡就詳細說一下它為什麼能夠實現這樣的功能:
你看這裡的jmp指令的段地址是0,但是這是程序剛加載到內存中的時候,隨著運行到mov [LABEL_GO_BACK_TO_REAL+3], ax會發生什麼呢?
首先看一下jmp 0:LABEL_REAL_ENTRY的機器碼:
BYTE1 BYTE2 BYTE3 BYTE4 BYTE5 0EAhoffsetSegment
由上圖可以看出,LABEL_GO_BACK_TO_REAL+3恰好就是Segment的地址,而執行mov [LABEL_GO_BACK_TO_REAL+3], ax之前ax的值已經是實模式下的cs(假設記為cs_real_mode)了,所以它這條mov指令將把cs保存到segment的位置,等到jmp指令執行時,它已經不再是:
jmp 0:LABEL_REAL_ENTRY
而是:
jmp cs_real_mode:LABEL_REAL_ENTRY
這條指令將會跳轉到標號LABEL_REAL_ENTRY處。現在已經跳回到實模式了,接下來就是要重新設置各個寄存器的值,並回復sp的值,然後關閉A20,打開中斷,重新回到原來的樣子。LABEL_REAL_ENTRY的代碼如下:
LABEL_REAL_ENTRY: ; 從保護模式跳回到實模式就到了這裡 mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, [SPValueInRealMode] in al, 92h ; `. and al, 11111101b ; | 關閉 A20 地址線 out 92h, al ; / sti ; 開中斷 mov ax, 4c00h ; `. int 21h ; / 回到 DOS ; END of [SECTION .s16]
這裡我們又看到了SPValueInRealMode,還記得上面沒有詳細說的指令mov [SPValueInRealMode], sp嗎?從這而很容易可以看出,它保存實模式下sp的值,也是為了現在回到實模式回復sp的值。
關閉A20地址先,開中斷之後,通過int 21h中斷返回到DOS。
這樣整個程序的運行過程就結束了哈哈。
通過nasm編譯成.com文件,這裡面還有如何突破引導扇區512字節的限制。弄明白了再詳細記錄。
下面是主程序的完整源代碼
; ========================================== ; pmtest2.asm ; 編譯方法:nasm pmtest2.asm -o pmtest2.com ; ========================================== %include "pm.inc" ; 常量, 宏, 以及一些說明 org 0100h jmp LABEL_BEGIN [SECTION .gdt] ; GDT ; 段基址, 段界限 , 屬性 LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符 LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ; Normal 描述符 LABEL_DESC_CODE32: Descriptor 0, SegCode32Len-1, DA_C+DA_32; 非一致代碼段, 32 LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C ; 非一致代碼段, 16 LABEL_DESC_DATA: Descriptor 0, DataLen-1, DA_DRW ; Data LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA+DA_32; Stack, 32 位 LABEL_DESC_TEST: Descriptor 0500000h, 0ffffh, DA_DRW LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 顯存首地址 ; GDT 結束 GdtLen equ $ - LABEL_GDT ; GDT長度 GdtPtr dw GdtLen - 1 ; GDT界限 dd 0 ; GDT基地址 ; GDT 選擇子 SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDT SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT SelectorCode16 equ LABEL_DESC_CODE16 - LABEL_GDT SelectorData equ LABEL_DESC_DATA - LABEL_GDT SelectorStack equ LABEL_DESC_STACK - LABEL_GDT SelectorTest equ LABEL_DESC_TEST - LABEL_GDT SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT ; END of [SECTION .gdt] [SECTION .data1] ; 數據段 ALIGN 32 [BITS 32] LABEL_DATA: SPValueInRealMode dw 0 ; 字符串 PMMessage: db "In Protect Mode now. ^-^", 0 ; 在保護模式中顯示 OffsetPMMessage equ PMMessage - $$ StrTest: db "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0 OffsetStrTest equ StrTest - $$ DataLen equ $ - LABEL_DATA ; END of [SECTION .data1] ; 全局堆棧段 [SECTION .gs] ALIGN 32 [BITS 32] LABEL_STACK: times 512 db 0 TopOfStack equ $ - LABEL_STACK - 1 ; END of [SECTION .gs] [SECTION .s16] [BITS 16] LABEL_BEGIN: mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, 0100h mov [LABEL_GO_BACK_TO_REAL+3], ax mov [SPValueInRealMode], sp ; 初始化 16 位代碼段描述符 mov ax, cs movzx eax, ax shl eax, 4 add eax, LABEL_SEG_CODE16 mov word [LABEL_DESC_CODE16 + 2], ax shr eax, 16 mov byte [LABEL_DESC_CODE16 + 4], al mov byte [LABEL_DESC_CODE16 + 7], ah ; 初始化 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 ; 初始化數據段描述符 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_DATA mov word [LABEL_DESC_DATA + 2], ax shr eax, 16 mov byte [LABEL_DESC_DATA + 4], al mov byte [LABEL_DESC_DATA + 7], ah ; 初始化堆棧段描述符 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_STACK mov word [LABEL_DESC_STACK + 2], ax shr eax, 16 mov byte [LABEL_DESC_STACK + 4], al mov byte [LABEL_DESC_STACK + 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, 並跳轉到 Code32Selector:0 處 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; LABEL_REAL_ENTRY: ; 從保護模式跳回到實模式就到了這裡 mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, [SPValueInRealMode] in al, 92h ; `. and al, 11111101b ; | 關閉 A20 地址線 out 92h, al ; / sti ; 開中斷 mov ax, 4c00h ; `. int 21h ; / 回到 DOS ; END of [SECTION .s16] [SECTION .s32]; 32 位代碼段. 由實模式跳入. [BITS 32] LABEL_SEG_CODE32: mov ax, SelectorData mov ds, ax ; 數據段選擇子 mov ax, SelectorTest mov es, ax ; 測試段選擇子 mov ax, SelectorVideo mov gs, ax ; 視頻段選擇子 mov ax, SelectorStack mov ss, ax ; 堆棧段選擇子 mov esp, TopOfStack ; 下面顯示一個字符串 mov ah, 0Ch ; 0000: 黑底 1100: 紅字 xor esi, esi xor edi, edi mov esi, OffsetPMMessage ; 源數據偏移 mov edi, (80 * 10 + 0) * 2 ; 目的數據偏移。屏幕第 10 行, 第 0 列。 cld .1: lodsb test al, al jz .2 mov [gs:edi], ax add edi, 2 jmp .1 .2: ; 顯示完畢 call DispReturn call TestRead call TestWrite call TestRead ; 到此停止 jmp SelectorCode16:0 ; ------------------------------------------------------------------------ TestRead: xor esi, esi mov ecx, 8 .loop: mov al, [es:esi] call DispAL inc esi loop .loop call DispReturn ret ; TestRead 結束----------------------------------------------------------- ; ------------------------------------------------------------------------ TestWrite: push esi push edi xor esi, esi xor edi, edi mov esi, OffsetStrTest ; 源數據偏移 cld .1: lodsb test al, al jz .2 mov [es:edi], al inc edi jmp .1 .2: pop edi pop esi ret ; TestWrite 結束---------------------------------------------------------- ; ------------------------------------------------------------------------ ; 顯示 AL 中的數字 ; 默認地: ; 數字已經存在 AL 中 ; edi 始終指向要顯示的下一個字符的位置 ; 被改變的寄存器: ; ax, edi ; ------------------------------------------------------------------------ DispAL: push ecx push edx mov ah, 0Ch ; 0000: 黑底 1100: 紅字 mov dl, al shr al, 4 mov ecx, 2 .begin: and al, 01111b cmp al, 9 ja .1 add al, '0' jmp .2 .1: sub al, 0Ah add al, 'A' .2: mov [gs:edi], ax add edi, 2 mov al, dl loop .begin add edi, 2 pop edx pop ecx ret ; DispAL 結束------------------------------------------------------------- ; ------------------------------------------------------------------------ DispReturn: push eax push ebx mov eax, edi mov bl, 160 div bl and eax, 0FFh inc eax mov bl, 160 mul bl mov edi, eax pop ebx pop eax ret ; DispReturn 結束--------------------------------------------------------- SegCode32Len equ $ - LABEL_SEG_CODE32 ; END of [SECTION .s32] ; 16 位代碼段. 由 32 位代碼段跳入, 跳出後到實模式 [SECTION .s16code] ALIGN 32 [BITS 16] LABEL_SEG_CODE16: ; 跳回實模式: mov ax, SelectorNormal mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax mov eax, cr0 and al, 11111110b mov cr0, eax LABEL_GO_BACK_TO_REAL: jmp 0:LABEL_REAL_ENTRY ; 段地址會在程序開始處被設置成正確的值 Code16Len equ $ - LABEL_SEG_CODE16 ; END of [SECTION .s16code]