天天看點

32位保護模式彙編語言[1]:實模式與保護模式的故事

此文檔在WORD軟體編輯完成,釋出在CSDN網站上會有一定差異。

漫遊在機器指令的海洋,徘徊于實模式與保護模式之間,出入之間,自由無限。

詳細代碼在我的百度網盤

在文章中提到的生成段描述符例程: http://pan.baidu.com/s/1mgtDjok

本文原始WORD版本、插圖、源代碼: http://pan.baidu.com/s/1mgLYMxi

若發現問題,請在此文檔結尾直接回複或是通過電子郵箱<[email protected]>聯系我. 

說明:此文檔是我的實驗感悟第二版,相比第一版做了優化。

1. 去掉了生成段描述符生成的宏,在MBR時期,用測試好代碼手工寫,進入PM後,調用例程。

2. 增加了運作前期清屏操作

3. 删掉了字元輸出的特殊效果

4. 删減了廢話字元串,使得使MBR指令縮減

背景:進跟随着處理器更新換代,Intel于1985年設計研發出80386,如今我們所使用的作業系統基本都相容于Intel所設計的80386。

32位比過去的16位用起來更爽

n 資料總線擴大到32位,使得資料傳輸速度加快。

n 可尋址記憶體空間大大擴大,從8086的1MB到80386的4GB

n 相容性:能用“實模式”繼續運作原來的8086/8088上的“老古董”

n 采用多種複雜技術使得指令執行速度變快

80386所提出的32位保護模式核心主要圍繞:

n 不同任務之間的保護

n 同一任務之内的保護

通過以上使得多任務環境穩定高效。(為了相容前代CPU 8086/8088, 80368 CPU剛啟動時預設處于實模式,需要自己切換。)更多背景内容以後慢慢引進。

第一部分 我們首先看一下 實模式 與 保護模式 切換大體的問題。

CPU 加電啟動、檢測、初始化後,讀取外部儲存設備的以結尾低位元組為0x55且高位元組為0xaa的512位元組的資料到記憶體0x00007c00處,作為MBR (Master Boot Record),使得作業系統得以運作。

本次實驗平台

1. 作業系統:  Windows 7 Ultimate Service Pack 1

2. CPU: Intel Pentium(R) Dual-Core CPU

3. 工具Bochs x86 Emulator

32位保護模式彙編語言[1]:實模式與保護模式的故事

大體步驟:(運作效果如上)

1.建立全局描述符表;

2.加載全局描述符表;

3.屏蔽中斷;

4.打開A20位址線;

5.CR0寄存器PE位置1,打開保護模式;

6.清空指令預取隊列,正式進入保護模式。

第二部分 詳細解釋

全局描述符表(GDT, Global Descriptor Table)的有關資料:

GDT每個表項長度是8位元組。主要是對各種段的保護資訊的描述。

比如

對資料段寫東西時候超過資料段邊界了要阻止它;

對隻讀段寫内容時候CPU要阻止它。

但是這些零散的保護規章制度怎麼才能有效工作呢,需要有一個東西統一存放這些資訊,就放入GDT内部(實際上還可以放入别的地方,但暫時忽略掉),放入了GDT内部CPU怎麼知道哪裡是GDT呢,這裡有一個GDTR寄存器指向GDT,指向的方法是維護一個資料結構,存放GDT基位址,GDT界限(= 長度 - 1)

lgdt   [GDT_ptr]

相信大家就明白這句話是幹什麼了的。

段描述符(Descriptor)

既然是加載到GDTR(Global Descriptor Table Register),自然就想知道加載了什麼。查閱Intel 64 and IA-32 Architectures Software Developer’s Manual Volume 3 System Programming 我們得到了如下結構

32位保護模式彙編語言[1]:實模式與保護模式的故事

你所見的所有奇葩的格式都是為了相容80286這個“敗家子”16位保護模式的無奈之舉。

你足夠熟練的話,可以自行對着表格拿演算紙得出64位段描述符,像我一樣有些生疏的話,可以在我的網盤裡面下載下傳一個我曾經寫的生成描述符的例程及其demo。

目前為止,32位段基址,20位段界限,還有奇葩的格式,這些是我們所需的。

TYPE字段是幹嘛的呢?

先别急,回憶一下我們的8086方式(實模式)下的彙編,有哪幾個段?

代碼段,資料段,堆棧段,附加段

實際上堆棧段、附加段也屬于資料段,這樣,在保護模式下CPU就隻認兩種段:代碼段、資料段

32位保護模式彙編語言[1]:實模式與保護模式的故事

現在沒必要掌握所有的,隻需記住分類即可。

現在準備了這些,你可能會有一個疑問,為什麼第一個段描述符為全零?

這是處理機的要求。

A20位址線問題

我們都知道8086位址線20根,編碼為A0, A1, A2, ..., A19,當位址是????:FFFF時候再增加時候将産生進位,而更高位是沒有的,是以增加後的位址是????:0000,這樣就産生了“回繞”的特點。

但是有個問題:80286位址線增加到24根, 80386增加到32根,将進位了,不會産生“回繞”,但實際上當時CPU還沒有完全換成32位的,很多程式仍然依靠8086的這個“回繞”的特性在很多計算機上跑着。

為了不失去市場,一種解決方案由此出爐:

在第21根位址線(即A20)上裝一個0x92端口控制的電路,強制輸出低電平,也就是0,這樣以後,當産生位址進位時候,由于A20上強制為零,進位丢失,順理成章的回繞到????:0000上來,完成了對8086程式的相容。而下面的代碼作用是打開A20門,設定端口0x92标志位,不要幹預A20位址線信号,這樣就可以通路到超過1MB的存儲空間了。

; Configure A20 to enable whole 4 GB  Memory space.

in   al, 0x92

or   al, 0000_0010b

out  0x92, al

進入保護模式

在CR0(Control Register 0)裡面有一個PE(Protection Enable),置位即啟用保護模式。

32位保護模式彙編語言[1]:實模式與保護模式的故事

mov   eax, cr0

or    eax, 1

mov   cr0, eax

到此為止,CPU的PM機制已生效。

使用jmp dword: 清空流水線、裝載段選擇子(Selector)

PE置位後,CPU已經按照32位開始取指令、譯碼,但是代碼段選擇子寄存器所指向的代碼段無效且CPU流水線中存在無效結果,繼續執行會發生錯誤,需要jmp dword來處理。于是就是所見的

; Now into 32-bit Protection.

;Load CS with  Code_Selector

; EIP with ___32_bit_entry

jmp  dwordCode_Selector:0

小插曲:一些編譯器不支援類似的指令,是以無奈的編碼就由此而生了:

db          0xEA ; Operand Code: jmp

dd             0 ; Offset Address

dw Code_Selector ; 32-bit Code Segment Selector

半路出來的段選擇子(Selector)

段選擇子其實就是一種說明一個段描述符在全局描述符表(或其他表, 目前我們隻認為是從全局描述符表的)内的編号。

32位保護模式彙編語言[1]:實模式與保護模式的故事

從  Intel 64 And IA-32 Architectures Software Developer’s Manual  Volume 3a: System Programming中我們可以了解到以下資訊:

Index(Bits 3 through 15) — Selects one of 8192 descriptors in the GDT or LDT. The processor multiplies the index value by 8 (the number of bytes in a segment descriptor) and adds the result to the base address of the GDT or LDT (from the GDTR or LDTR register, respectively).

索引值 (位3 到 位15) ---從GDT或LDT内8192個選擇一個,處理機将用該值乘以8(段描述符的位元組大小),然後把得到的結果加到GDT/LDT的基址(從GDTR/LDTR擷取)。

小插曲:每個段描述符長度為8位元組,展現在二進制位上是3個整二進制位,是以第三個段描述符首位址減去第零個段描述符首位址即為第三個段的段選擇子。

保護模式下的尋址示意

如圖

以代碼為例

mov  bx, word [ds:esi]

實際上在我的Bochs Emulator對這條語句的解釋是這樣的:

mov  bx, word ptr ds:[esi]

這是兩種文法,不用去理它。

32位保護模式彙編語言[1]:實模式與保護模式的故事

接着簡述保護模式下尋址方式:(源代碼中相關部分優化掉了,詳細的請讀第一版該文檔)

從DS選擇子(Selector)取出索引值,進入GDT,找到對應的段描述符,取出段基址,加上偏移位址esi,讀出一個兩個位元組到bx寄存器。

很簡單吧?從上面可以看出,段描述符裡面有段基址,而段基址是從GDT取到的。

注意上圖内右上角,我們需要注意Offset(Effective Address)  即有效的偏移位址。在邏輯位址中,偏移位址在0到段界限上就是有效偏移。原文:For expand-up segments, the offset in a logical address can range from 0 to the segment limit. 

若超界,之前我沒有注意到對段基址重新設定以後,引用的段内偏移要減去段基址,結果通路越界,你才怎麼了,CPU二話不說,立即重新開機,後來才知道,通路越界導緻引起通用保護異常(#GP, General-Protection Exception),立即當機重新開機。

後記

進入了32位保護模式後怎麼切換回16位的實模式,我做了多次試驗,直接從32位代碼跳到16位是要被CPU阻止的,原因暫時明白,但解決方案已經找到:

32位保護模式代碼段工作完成後,跳到16位保護模式代碼段作中轉站進行如下:

清理PE位,跳到16位實模式代碼段關閉A20.