oslab oranges 一個操做系統的實現 實驗二 認識保護模式

 https://github.com/yyu/osfs00git

實驗目的:github

理解x86架構下的段式內存管理windows

掌握實模式和保護模式下段式尋址的組織方式、數組

關鍵數據結構、代碼組織方式數據結構

掌握實模式與保護模式的切換架構

掌握特權級的概念,以及不一樣特權之間的轉移ide

 

實驗內容:函數

1. 認真閱讀章節資料,掌握什麼是保護模式,弄清關鍵數據結構:oop

GDT、descriptor、selector、GDTR, 及其之間關係,閱讀測試

pm.inc文件中數據結構以及含義,寫出對宏Descriptor的分析

2. 調試代碼,/a/ 掌握從實模式到保護模式的基本方法,畫出代碼

流程圖,若是代碼/a/中,第71行有dword前綴和沒有前綴,編

譯出來的代碼有區別麼,爲何,請調試截圖。

3. 調試代碼,/b/,掌握GDT的構造與切換,從保護模式切換回實

模式方法

4. 調試代碼,/c/,掌握LDT切換

5. 調試代碼,/d/掌握一致代碼段、非一致代碼段、數據段的權限

訪問規則,掌握CPL、DPL、RPL之間關係,以及段間切換的基

本方法

6. 調試代碼,/e/掌握利用調用門進行特權級變換的轉移

代碼對應iso中chapter3 

 

實驗解決問題與課後動手改:

1. GDT、Descriptor、Selector、GDTR結構,及其含義是什麼?他

們的關聯關係如何?pm.inc所定義的宏怎麼使用?

2. 從實模式到保護模式,關鍵步驟有哪些?爲何要關中斷?爲

什麼要打開A20地址線?從保護模式切換回實模式,又須要哪些

步驟?

3. 解釋不一樣權限代碼的切換原理,call, jmp,retf使用場景如何,

可以互換嗎?

4. 課後動手改:

1. 自定義添加1個GDT代碼段、1個LDT代碼段,GDT段內要對一個內

存數據結構寫入一段字符串,而後LDT段內代碼段功能爲讀取並打

印該GDT的內容;

2. 自定義2個GDT代碼段A、B,分屬於不一樣特權級,功能自定義,要

求實現A-->B的跳轉,以及B-->A的跳轉。

 

 

實驗環境:

 

VMwareWorkstationPro 15.5.0

 

Ubuntu 12.04.5 desktop i386 32位

 

bochs 2.6.9

 

 

 

關鍵技術:

 

  1. bochs使用
  2. 實模式,保護模式及其關鍵數據結構GDT,LDT,Descriptor、Selector等
  3. 特權級變換

 

 

 

實驗步驟:

 

1.認真閱讀章節資料,掌握什麼是保護模式,弄清關鍵數據結構:

 

GDT、descriptor、selector、GDTR, 及其之間關係,閱讀

 

pm.inc文件中數據結構以及含義,寫出對宏Descriptor的分析

 

 

 

GDT即爲Global Descriptor Table(全局描述符表)又叫段描述符表,爲保護模式下的一個數據結構。其中包含多個descriptor,定義了段的起始地址,界限屬性等。

 

descriptor爲段描述符,包含段基址,段界限,段屬性。其結構如圖

 

 

 

 

Selector爲選擇子,有其數據結構。在pmtest1.asm程序中,其做用就是偏移,對應描述符相對於GDT基址的偏移。

 

 

 

 

GDTR爲GDT寄存器。結構與GDTPTR相似,6字節,前兩字節GDT界限,後4字節GDT基地址。

 

 

 

四者關係:

GDT中包含多個descriptor,descriptor包含段的信息,包含段基址,界限屬性等。多個selector包含對應descriptor相對於GDT的偏移,因而selector發揮了相似 指向descriptor的做用。而GDTR中包含了GDT基地址與界限。四者綜合就能夠得到某個descriptor的地址。而保護模式下尋址就先靠GDTR找到GDT,而後根據descriptor找到對應段的地址,而後再加上段內偏移offset,就獲得某個線性地址。

如圖所示

 

 

對宏Descriptor分析:

結構如圖:

 

 

8字節。從低地址開始前兩字節爲段界限1,而後三個字節爲段基址1,而後兩個字節byte5,byte6包含段屬性以及段界限2,最後一字節爲段基址2.因爲歷史緣由,段界限和段基址都分開存放。程序中descriptor由pm.inc中的宏descriptor生成。

代碼:

%macro Descriptor 3 ;macro定義宏。 3表示有三個參數

    dw    %2 & 0FFFFh                ; 段界限1

    dw    %1 & 0FFFFh                ; 段基址1

    db    (%1 >> 16) & 0FFh            ; 段基址2

    dw    ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)    ; 屬性1 + 段界限2 + 屬性2

    db    (%1 >> 24) & 0FFh            ; 段基址3%endmacro ; 字節

 

macro表明宏開始。宏名Descriptor,3表明有三個參數。

參數1-3分別爲段基址,界限,屬性。

好比LABEL_DESC_VIDEO:  Descriptor 0B8000h,           0ffffh, DA_DRW         ; 顯存首地址

利用宏Descriptor定義了基址爲0B8000H的段LABEL_DESC_VIDEO.

0B8000H爲顯存首地址。利用該段在屏幕中顯示數據。

以後第一行dw 爲兩字節。   %2 & 0FFFFh, 至關於取段界限的低位,寫入這兩字節。

而後dw,dd去段基址1,2,構成三字節段基址,至關於上面結構圖的段基址1.

而後dw兩字節構成段屬性,段界限2.

而後dw兩字節構成段基址3.

其中段基址爲該段起始地址,界限爲長度。

 

2. 調試代碼,/a/ 掌握從實模式到保護模式的基本方法,畫出代碼

流程圖,若是代碼/a/中,第71行有dword前綴和沒有前綴,編

譯出來的代碼有區別麼,爲何,請調試截圖。

 

流程圖:pmtest1.asm 用文字描述以下

1)定義GDT  [SECTION .gdt]

其中定義了一個空descriptor,一個32位代碼段,一個顯存descriptor

其中32位代碼段只初始化了段界限,段屬性

2)進入[SECTION .s16] 16位代碼段(實模式)

修改GDT值:修改32位段描述符值

LABEL_SEG_CODE32的物理地址(即 [SECTION .s32]這個段的物理地址)賦給eax,而後把它分紅三部分賦給描述符DESC_CODE32中的相應位置。因爲DESC_CODE32的段 界限和屬性已經指定,因此至此,DESC_CODE32的初始化所有完成。

(將段寄存器段界限段屬性由符合實模式要求到符合保護模式要求)

以後賦值gdtr寄存器:

GDT的物理地址填充到了GdtPtr這個6字節的數據結構中。

lgdt [GdtPtr] 將GdtPtr指示的6字節加載到寄存器gdtr

以後關中斷。

以後打開A20地址線。

修改cr0寄存器:PE位置1。

此時cs的值仍然是實模式下的值,把代碼段的選擇子裝入cs:

jmp dword SelectorCode32:0 ,進入32位代碼段[SECTION .s32]

3)進入32位代碼段[SECTION .s32]

進行屏幕顯示操做。

 

調試代碼a:

將程序編譯爲.com文件,使用dos運行。(由於引導扇區只有512字節,程序高於512字節就不方便了)

代碼a有dword前綴調試:

(1)準備freedocs.img

(2)bximage生成pm.img

(3)修改bochs

 

 

重點是

floppya: 1_44=freedos.img, status=inserted

floppyb: 1_44=pm.img, status=inserted

boot: a

 

(1)bochs格式化B盤

Sudo bochs

dos format b:

 

 

(5)修改pmtest1,org改成0100h,並編譯爲pmtest1.com

 

 

 

 

 

(6)pmtest1.com複製到pm.img

sudo mount -o loop pm.img /mnt/floppy

會出現了錯誤

mount point /mnt/floppy does not exist

先建立文件夾

 

 

而後
sudo losetup /dev/loop0 pm.img  建立loop設備,而後操做loop設備,就是對pm.img數據的操做了

 

 

sudo mount /dev/loop0/ /mnt/floppy loop設備掛載到/mnt/floppy上

 

 

而後

 sudo cp pmtest1.com /mnt/floppy/ 賦值

而後卸載

sudo umount /mnt/floppy/

 

 

以後再作一次遇到問題

 

 

 

解決,卸載

 

 

 

 另外發現了 sudo cp pmtest2.com /mnt/floppy/ 賦值並非覆蓋。也就是說cp了先cp了pmtest1.com,而後不格式化(format b:),直接cp  pmtest2.com,那麼兩個程序均可以運行。

(7)dos下運行pmtest1.com

Sudo bochs

B:\pmtest1.com 運行

可見右側出現一個紅色的P

 

 

代碼a無dword前綴調試:

(1)修改pmtest1.asm,刪掉第71行的dword,存爲pmtestd.asm,並編譯爲pmtestd.com

 

(2)dos運行

陷入循環而且無紅色的P在屏幕右側

 

 

失敗緣由:

jmp dword SelectorCode32:0 ; 執行這一句會把 SelectorCode32 裝入 cs,  //selector16位,dword兩字節,高位selector,低位偏移0.(由於聲明瞭這段是16位代碼,因此一個字兩字節)
; 並跳轉到 Code32Selector:0 處

刪除dword 後只有16位。cs寄存器沒有正確設置,沒有跳轉到32位代碼段,故顯示失敗

3. 調試代碼,/b/,掌握GDT的構造與切換,從保護模式切換回實

模式方法

分析:pmtest2.asm

在前面程序的基礎上,新建一個段,這個段以5MB爲基址,遠遠超出實模式下1MB的界限。咱們

先讀出開始處8字節的內容,而後寫入一個字符串,再從中讀出8字節。若是讀寫成功的話,兩次讀出的內容應該是不一樣的,並且第

二次讀出的內容應該是咱們寫進的字符串。字符串是保存在數據段中的,也是新增長的。

1)LABEL_DESC_STACK:  Descriptor 爲全局堆棧段[SECTION .gs]的descriptor,初始化在[SECTION .gs]和[SECTION.16]完成。Descriptor屬性爲DA_DRWA+DA_32,DA_32代表是32位堆棧段。

2)LABEL_DESC_DATA:Descriptor 爲[SECTION .data1]  ; 數據段的descriptor,初始化在[SECTION .data1] 完成,其中包含了要寫入的字符串

3)LABEL_DESC_CODE32: Descriptor 32位代碼段(保護模式)[SECTION .s32]. 由實模式跳入.

[SECTION .s32]中咱們改變了ss和esp(代碼3.5第174行到177行),這樣,在32位代碼段中全部的堆棧操做將會在新增的 堆棧段中進行。

這個段的開頭初始化了ds、es和gs,讓ds指向新增的數據段,es指向新增的5MB內存 的段,gs指向顯存(第167行到第172行)。接着顯示一行字符串,以後就開始讀寫大地址內存了(第198行到第200行)。因爲要讀 兩次相同的內存,咱們把讀的過程寫進一個函數TestRead,寫內存的內容也寫進函數TestWrite,這兩個函數的入口分別在第206行 和第222行。能夠看到,在TestRead中還調用了DispAL和DispReturn這兩個函數(第253行和第286行),DispAL將al中的字節用十 六進制數形式顯示出來,字的前景色仍然是紅色;DispReturn模擬一個回車的顯示,其實是讓下一個字符顯示在下一行的開頭 處。要注意的一個細節是,在程序的整個執行過程當中,edi始終指向要顯示的下一個字符的位置。因此,若是程序中除顯示字符外 還用到edi,須要事先保存它的值,以避免在顯示時產生混亂。

4)保護模式中字符串尋址:TestWrite中用到一個常量OffsetStrTest,它的定義在代碼3.4第47行。注意,咱們用到這個字符串的時候並無用直接標 號StrTest,而是又定義了一個符號OffsetStrTest,它等於StrTest-$$。$$的含義表明當前 節(section)開始處的地址。因此StrTest-$$表示字符串StrTest相對於本節的開始處(即LABEL_DATA處)的偏移。容易發現數據段的基址即是LABEL_DATA的物理地址。因而OffsetStrTest既是字符串相對LABEL_DATA的偏移,也是其在數據段中的偏移。咱們在保護模式下須要用到的正是這個偏移,而再也不是實模式下的地址。前文中提到過的section的一點妙用指 的即是這裏的$$,它不是沒有替代品,而是這樣作思路會比較清晰。OffsetPMMessage的情形與此相似。

6)返回實模式

概述:

先回憶開中斷:加載寄存器,以後關中斷。以後打開A20地址線。修改cr0寄存器:PE位置1。此時cs的值仍然是實模式下的值,把代碼段的選擇子裝入cs(修改段界限,段屬性。)

 

關中斷差很少就是完成上述的逆向操做:

加載一個合適的描述符選擇子到有關段寄存器,以使對應段描述符高速緩衝寄存器中含有合適的段界限和屬性,從新設置各個段寄存器的值,好比cr0PE位置0.恢復sp(堆棧指針寄存器)的值,修改段界限,段屬性,而後關閉A20,打開中斷,從新回到原來的樣子。

(將段寄存器段界限段屬性由符合保護模式要求到符合實模式要求)

爲了能從保護模式恢復實模式的寄存器,須要先保存到系統本身的堆棧段。在[SECTION.16]中完成。

mov sp, 0100h

...

而後32位代碼段的操做在自定義的堆棧段[SECTION .STACK]完成。兩者互不干擾,方便了恢復。

 

 

詳述:

從實模式進入保護模式時直接用一個跳轉就能夠了,可是返回的時候卻稍稍複雜一些。由於在準備結束保護模式回到實模 式以前,須要加載一個合適的描述符選擇子到有關段寄存器,以使對應段描述符高速緩衝寄存器中含有合適的段界限和屬性。而 且,咱們不能從32位代碼段返回實模式,只能從16位代碼段中返回這是由於沒法實現從32位代碼段返回時cs高速緩衝寄存器中的 屬性符合實模式的要求(實模式不能改變段屬性)。

因此,在這裏,咱們新增一個Normal描述符(代碼3.4第15行)。在返回實模式以前把對應選擇子SelectorNormal加載到ds、 es和ss,就是上面所說的這個緣由。

LABEL_DESC_NORMAL: Descriptor對應選擇子SelectorNormal。對應段 [SECTION .s16code],16 位代碼段. 由 32 位代碼段跳入, 跳出後到實模式。

這個段是由[SECTION .s32]中的jmp SelectorCode16:0跳進來的。開頭的語句把 SelectorNormal賦給ds、es、fs、gs和ss,完成咱們剛剛提到的使命。而後就清cr0的PE位,接下來的跳轉看上去好像不太對,因 爲段地址是0。其實這裏只是暫時這樣寫罷了,在程序的一開始處能夠看到代碼3.8中的這幾句。

67 mov ax, cs

...

73 mov [LABEL_GO_BACK_TO_REAL+3], ax

mov [LABEL_GO_BACK_TO_REAL+3], ax的做用就是爲回到實模式的這個跳轉指令指定正確的段地址,這條指令的機器碼如圖3.9 所示。 

 

 

3.9告訴咱們,LABEL_GO_BACK_TO_REAL+3剛好就是Segment的地址,而第73行執行以前ax的值已是實模式下的cs(咱們記 作cs_real_mode)了,因此它將把cs保存到Segment的位置,等到jmp指令執行時,它已經再也不是:

jmp 0:LABEL_REAL_ENTRY

而變成了:

jmp cs_real_mode:LABEL_REAL_ENTRY

它將跳轉到標號LABEL_REAL_ENTRY處。

在跳回實模式以後,程序從新設置各個段寄存器的值,恢復sp的值,而後關閉A20,打開中斷,從新回到原來的樣子

144 LABEL_REAL_ENTRY: ; 從保護模式跳回到實模式就到了這裏

...

159 int 21h ; / 回到 DOS

 

調試:

編譯pmtest2.asm爲pmtest2.com

bochs dos 下運行

 

 

 

 

第一行爲開始內存5MB處全是零。而後寫入了41,42,...48,也就是16進制的A,B,C,D...H,在代碼pmtest2.asm中DATA段的寫入的str。

同時看到,程序執行結束後再也不像上一個程序那樣進入死循環,而是從新出現了DOS提示符。這說明咱們從新回到了實模式下

DOS。

 4.調試代碼,/c/,掌握LDT切換

分析:

LDT與GDT都是描述符table,L表明Local,局部。簡單來講,LDT是一種描述符表,與GDT差很少,只不過它的選擇子的TI位必 須置爲1。在運用它時,須要先用lldt指令加載ldtr,lldt的操做數selector是GDT中用來描述LDT的描述符。(也就是說LDT至關於GDT中描述的一個段,對應有特殊的寄存器ldtr,而該段中又有一些描述符描述一些LDT段,只屬於這個LDT。)

 

pmtest3.asm中增長了兩個節[SCTION .ldt][SECTION .la]。(原來有omtest2.asm中的各個段)。其中[SCTION .ldt]在GDT中有對應的descriptor和selector  LABEL_DESC_LDT: 。而[SECTION .la]是LDT描述的段,在GDT無定義。

[SCTION .ldt]是增長的LDT,其中有一個descriptor,對應[SECTION .la]。

[SECTION .la]中包含顯示的字符L,在屏幕顯示。實現時調用了GDT中 的SelectorVideo。

轉換到LDT的過程:先由實模式跳轉到GDT中的32位代碼段[SECTION .s32](保護模式),而後在[SECTION .s32]中

mov ax, SelectorLDT

lldt ax

加載ldtr(成爲當前LDTR),

而後。jmp SelectorLDTCodeA。由於SelectorLDTCodeA的TI位爲1,因此係統從當前LDT尋找相應描述符。跳轉到LDT中descriptor描述的段[SECTION .la]顯示L後,而後jmp SelectorCode16:0,跳回GDT中描述的16位代碼段,而後返回實模式。其中SelectorLDT在GDT中定義,指向LDT地址。

 

[SECTION .s32]第217行到第220行,指令lldt,功能和lgdt也差很少, 負責加載ldtr,它的操做數是一個選擇子,這個選擇子對應的就是用來描述LDT的那個描述符(標號LABEL_DESC_LDT)。

本例用到的LDT中只有一個描述符(標號LABEL_LDT_DESC_CODEA處),這個描述符跟GDT中的描述符沒什麼分別。選擇子卻不同,多出了一個屬性SA_TIL。能夠在pm.inc中找到它的定義:

SA_TIL EQU 4

由圖3.5可知,SA_TIL將選擇子SelectorLDTCodeA的TI位置爲1。實際上,這一位即是區別GDT的選擇子和LDT的選擇子的關鍵所在。若是TI被置位,那麼系統將從當前LDT中尋找相應描 述符。也就是說,當代碼3.10中用到SelectorLDTCodeA時,系統會從LDT中找到LABEL_LDT_DESC_CODEA描述符,並跳轉到相應的段中。

 

這個LDT很簡單,只有一個代碼段。咱們還能夠在其中增長更多的段,好比數據段、堆棧段等,這樣一來,咱們能夠把一個單獨的任務所用到的全部東西封裝在一個LDT中。

 

經過幾個簡單的例子,咱們對IA32的分段機制大體已經有所瞭解了。「保護模式」中「保護」二字究竟是什麼含義? 在描述符中段基址和段界限定義了一個段的範圍,對超越段界限以外的地址的訪問是被禁止的,這無疑是對段的一種保護。另外,有點複雜的段屬性做爲對一個段各個方面的定義規定和限制了段的行爲和性質,從功能上來說,這仍然是一種保護。

 

調試:

編譯pmtest3.asm爲pmtest3.com,在dos運行

 

 

5. 調試代碼,/d/掌握一致代碼段、非一致代碼段、數據段的權限

訪問規則,掌握CPL、DPL、RPL之間關係,以及段間切換的基

本方法

分析:

(1)特權級

IA32的分段機制中,特權級總共有4個特權級別,從高到低分別是0、一、二、3。數字越小表示的特權級越大,較爲核心的代碼和數據,將被放在特權級較高的層級中。處理器將用這樣的機制來避免低特權級的任務在不被 容許的狀況下訪問位於高特權級的段。若是處理器檢測到一個訪問請求是不合法的,將會產生常規保護錯誤(#GP)。

 

 

(2)CPL,DPL,RPL

CPL是存寄存器如CS中,

RPL是代碼中根據不一樣段跳轉而肯定,以動態刷新CS裏的CPL.

DPL是在GDT/LDT描述符表中,靜態的。

一致代碼段:

  簡單理解,就是操做系統拿出來被共享的代碼段,能夠被低特權級的用戶直接調用訪問的代碼。一般這些共享代碼,是"不訪問"受保護的資源和某些類型異常處理。好比一些數學計算函數庫,爲純粹的數學運算計算,被做爲一致代碼段。

一致代碼段的限制做用:

特權級高的程序不容許訪問特權級低的數據:核心態不容許調用用戶態的數據.

特權級低的程序能夠訪問到特權級高的數據.可是特權級不會改變:用戶態仍是用戶態.

非一致代碼段:

爲了不低特權級的訪問而被操做系統保護起來的系統代碼.

非一致代碼段的限制做用

只容許同級間訪問.

絕對禁止不一樣級訪問:核心態不用用戶態.用戶態也不使用核心態.

 一般低特權代碼必須經過"門"來實現對高特權代碼的訪問和調用。不一樣級別代碼段之間轉移規則,是經過CPL/RPL/DPL來校驗。先來理解這幾個概念。

 

CPL(Current PrivilegeLevel)

CPL是當前執行的程序或任務的特權級。它被存儲在cs和ss的第0位和第1位上。在一般狀況下,CPL等於代碼所在的段的 特權級。當程序轉移到不一樣特權級的代碼段時,處理器將改變CPL。

在遇到一致代碼段時,狀況稍稍有點特殊,一致代碼段能夠被相同或者更低特權級的代碼訪問。當處理器訪問一個與 CPL特權級不一樣的一致代碼段時,CPL不會被改變。

 DPL(Descriptor Privilege Level)

DPL表示段或者門的特權級。它被存儲在段描述符或者門描述符的DPL字段中,正如咱們先前所看到的那樣。噹噹前代碼段試圖訪問一個段或者門時,DPL將會和CPL以及段或門選擇子的RPL相比較,根據段或者門類型的不一樣,DPL將會被區別 對待,下面介紹一下各類類型的段或者門的狀況。

數據段DPL規定了能夠訪問此段的最低特權級。好比,一個數據段的DPL是1,那麼只有運行在CPL爲0或者 1的程序纔有權訪問它。

非一致代碼段(不使用調用門的狀況下):DPL規定訪問此段的特權級。好比,一個非一致代碼段的特 權級爲0,那麼只有CPL爲0的程序才能夠訪問它。

調用門:DPL規定了當前執行的程序或任務能夠訪問此調用門的最低特權級(這與數據段的規則是一致的)。

一致代碼段和經過調用門訪問的非一致代碼段DPL規定了訪問此段的最高特權級。好比,一個一致代 碼段的DPL是2,那麼CPL爲0和1的程序將沒法訪問此段。

TSS:DPL規定了能夠訪問此TSS的最低特權級(這與數據段的規則是一致的)。(TSS 全稱task state segment,是在操做系統進程管理的過程當中,任務(進程)切換時的任務現場信息。)

 

RPL(Requested PrivilegeLevel)

RPL是經過段選擇子的第0位和第1位表現出來的。處理器經過檢查RPL和CPL來確認一個訪問請求是否合法。即使提出訪問請求的段有足夠的特權級,若是RPL不夠也是不行的。也就是說,若是RPL的數字比CPL大(數字越大特權級越低), 那麼RPL將會起決定性做用,反之亦然。

操做系統過程每每用RPL來避免低特權級應用程序訪問高特權級段內的數據。當操做系統過程(被調用過程)從一個應用程序(調用過程)接收到一個選擇子時,將會把選擇子的RPL設成調用者的特權級。因而,當操做系統用這個選擇子 去訪問相應的段時,處理器將會用調用過程的特權級(已經被存到RPL中),而不是更高的操做系統過程的特權級(CPL)進行特權檢驗。這樣,RPL就保證了操做系統不會越俎代庖地表明一個程序去訪問一個段,除非這個程序自己是有權限的。

例子:

的數據段的選擇子的RPL改成3:

SelectorData equ LABEL_DESC_DATA-LABEL_GDT+SA_RPL3

再運行一下,發生了什麼?

Bochs重啓了,系統崩潰了,在控制檯你能看到這樣的字樣:

load_seg_reg(DS): RPL & CPL must be <= DPL

容易理解,崩潰的緣由在於咱們違反了特權級的規則,用RPL=3的選擇子去訪問DPL=1的段,因而引發異常。而咱們又沒有相應 的異常處理模塊,因而最爲嚴重的狀況就發生了。 

(3)不一樣特權級代碼段間轉移

程序從一個代碼段轉移到另外一個代碼段以前,目標代碼段的選擇子會被加載到cs中。做爲加載過程的一部分,處理器將會檢查描述符的界限、類型、特權級等內容。若是檢驗成功,cs將被加載,程序控制將轉移到新的代碼段中,從eip指示的位置開始執 行。

程序控制轉移的發生,能夠是由指令jmp、call、ret、sysenter、sysexit、int n 或iret引發的,也能夠由中斷和異常機制 引發。

使用jmp或call指令能夠實現下列4種轉移:

1. 目標操做數包含目標代碼段的段選擇子。

2. 目標操做數指向一個包含目標代碼段選擇子的調用門描述符。

3. 目標操做數指向一個包含目標代碼段選擇子的TSS。

4. 目標操做數指向一個任務門,這個任務門指向一個包含目標代碼段選擇子的TSS。

4 種方式能夠看作是兩大類,一類是經過jmp和call的直接轉移(上述第1種),另外一類是經過某個描述符的間接轉移(上述 第二、三、4種)。下面就來分別看一下。

 

(4)經過jmp或call直接轉移

若是目標是非一致代碼段,要求CPL必須等於目標段的

DPL,同時要求RPL小於等於DPL;若是目標是一致代碼段,則要求CPL大於或者等於目標段的DPL,RPL此時不作檢查。當轉移到一致

代碼段中後,CPL會被延續下來,而不會變成目標代碼段的DPL。也就是說,經過jmp和call所能進行的代碼段間轉移是很是有限

的,對於非一致代碼段,只能在相同特權級代碼段之間轉移。遇到一致代碼段也最多能從低到高,並且CPL不會改變。若是想自由

地進行不一樣特權級之間的轉移,顯然須要其餘幾種方式,即運用門描述符或者TSS。

 

(5)基本的調用門進行段轉移(先不涉及特權級轉換,用門特權級轉換見6./e/)

門:門也是一種描述符,門描述符的結構如圖3.13

 

 

能夠看到,門描述符和咱們前面提到的描述符有很大不一樣,它主要是定義了目標代碼對應段的選擇子、入口地址的偏移和一些 屬性等。但是,雖然這樣的結構跟代碼段以及數據段描述符大不相同,咱們仍然看到,第5個字節(BYTE5)倒是徹底一致的,都表 示屬性。在這個字節內,各項內容的含義與前面提到的描述符也別無二致,這顯然是必要的,以便識別描述符的類型。在這裏,S 位將是0

直觀來看,一個門描述了由一個選擇子和一個偏移所指定的線性地址,程序正是經過這個地址進 行轉移的。門描述符分爲4種:

調用門(Call gates)

中斷門(Interrupt gates)

陷阱門(Trap gates)

任務門(Task gates)

其中,中斷門和陷阱門是特殊的調用門,將會在後面提到,咱們先來介紹調用門。在這個例子中,咱們用到調用門。爲簡單起見,先不涉及任何特權級變換,而是先來關注它的工做方法。

pmtest3.asm的基礎上修改成pmtest4.asm

 

增長一個代碼段做爲經過調用門轉移的目標段

添加[SECTION .sdset]:調用selectvideo在屏幕上顯示C。由於打算用call指令調用將要創建的調用門,因此,在這段代碼的結尾處調用了一個retf指令。

而後加入該段的descriptor以及selector,並初始化

 

 

而後添加調用門的descriptor以及selector

使用宏GATE(在pm.inc定義)初始化門的descriptor

SelectorCodeDest就是這個調用門要調用的段的selector,也就是咱們剛剛在上面定義的段的selector

 

 

 

 

 

 

而後就準備好了要被調用的段以及調用門

 

下面進行調用

Call 測試調用門後retf,至關於繼續運行,從235行開始繼續。

調用門準備就緒,它指向的位置是SelectorCodeDest:0,即標號LABEL_SEG_CODE_DEST處的代碼

用一個call指令來使用這個調用門是個好主意 :

 

233 ; 測試調用門(無特權級變換),將打印字母'C'

⇒ 234 call SelectorCallGateTest:0

...

241 jmp SelectorLDTCodeA:0 ; 跳入局部任務,將打印字母'L'

 

這個call指令被放在進入局部任務以前,因爲咱們新加的代碼以指令retf結尾,因此最終代碼將會跳回 到call指令的下面繼續執行。因此,咱們最終看到的結果應該是在pmtest3.exe執行結果的基礎上多出一個紅色的字母C。

 

其實調用門本質上只不過是個入口地址,只是增長了若干的屬性而已。在咱們的例子中所用到的調用門徹底等同於一個地址,咱們甚至能夠把使用調用門進行跳轉的指令修改成跳轉到調用門內指定的地址的指令:

call SelectorCodeDest:0

運行一下,效果是徹底相同的。(下面是更復雜的狀況)

6)使用調用門進行轉移時特權級檢驗的規則。

假設咱們想由代碼A轉移到代碼B,運用一個調用門G,即調用門G中的目標選擇子指向代碼B的段。實際上,咱們涉及了這麼幾個要素:CPL、RPL、代碼B的DPL(記作DPL_B)、調用門G的DPL(記作DPL_G)。根據3.2.3.1中提到的,A訪問G這個調用門時,規則至關於訪問一個數據段,要求CPL和RPL都小於或者等於DPL_G。換句話說,CPL和RPL需在更高的特權級上。

除了這一步要符合要求以外,系統還將比較CPL和DPL_B。若是是一致代碼段的話,要求DPL_B≤CPL;若是是非一致代碼段的話,call指令和jmp指令又有所不一樣。在用call指令時,要求DPL_B≤CPL;在用jmp指令時,只能是DPL_B=CPL。

綜上所述,調用門使用時特權檢驗的規則如表所示。

 

 

 

也就是說,經過調用門和call指令,能夠實現從低特權級到高特權級的轉移,不管目標代碼段是一致的仍是非一致的。

 

 

調試:

編譯pmtest4.asm爲pmtest4.com,在dos運行

pmtest3.asm的基礎上又多顯示了C。是調用門調用的段的輸出

 

 

6.調試代碼,/e/掌握利用調用門進行特權級變換的轉移

分析:

(1)跳轉與堆棧

經過調用門和call指令,能夠實現從低特權級到高特權級的轉移,不管目標代碼段是一致的仍是非一致的。 那麼如何進行高特權級向低特權級轉換?

有特權級變換的轉移的複雜之處,不但在於嚴格的特權級檢驗,還在於特權級變化的時候,堆棧也要發生變化。處理器的這種 機制避免了高特權級的過程因爲棧空間不足而崩潰。並且,若是不一樣特權級共享同一個堆棧的話,高特權級的程序可能所以受到有意或無心的干擾。

在咱們的程序中,指令call DispReturn和call SelectorCodeDest:0顯然不一樣。與在實模式下相似,若是一個調用或跳轉指 令是在段間而不是段內進行的,那麼咱們稱之爲「長」的(Far jmp/call),反之,若是在段內則是「短」的(Near jmp/call)。  (與windows不一樣)

那麼長的和短的jmp或call有什麼分別呢?對於jmp而言,僅僅是結果不一樣罷了,短跳轉對應段內,而長跳轉對應段間;而call 則稍微複雜一些,由於call指令是會影響堆棧的,長調用和短調用對堆棧的影響是不一樣的。咱們下面的討論只考慮32位的狀況.

 

對於短調用來講,call指令執行時下一條指令的eip壓棧,到ret指令執行時,這個eip會被從堆棧中彈出,如圖所示。

先從右向左壓棧參數,而後壓棧下一條指令eip,(從高地址到低地址壓棧)eip寄存器存儲着咱們cpu要讀取指令的地址每次cpu執行都要先讀取eip寄存器的值,而後定位eip指向的內存地址。Esp是當前堆棧的指針寄存器,指向當前堆棧的底部位置。

 

 

能夠看出,調用者的eip被壓棧,而在此以前參數已經入棧。圖中的「調用者

eip」對應nop指令地址。而在函數foo調用最後一條指令ret(帶有參數)返回以前和以後,堆棧的變化如圖所示。可見esp指向的內存中,存放着call後下一條指令的地址(nop)

 

 

長調用的狀況與此相似,容易想到,返回的時候跟調用的時候同樣也是「長」轉移,因此返回的時候也需

要調用者的cs,因而call指令執行時被壓棧的就不只有eip,還應該有cs,如圖所示。

 

 

帶參數的ret指令執行先後的情形如圖所示。

 

 

(2)經過調用門進行特權級轉換

call一個調用門也是長調用,狀況跟上面 所說的長調用差很少。但是因爲一些緣由堆棧發生了切換,也就是說,call指令執行先後的堆棧已經 再也不是同一個。咱們在堆棧A中壓入參數和返回時地址,等到須要使用它們的時候堆棧已經變成B了。Intel提供了這樣一種機制,將堆棧A的諸多內容複製到堆棧B中,如圖所示。 

 

 

事實上,因爲每個任務最多均可能在4個特權級間轉移,因此,每一個任務實際上須要4個堆棧。可 是,咱們只有一個ss和一個esp,那麼當發生堆棧切換,咱們該從哪裏得到其他堆棧的ss和esp呢?這裏涉及同樣TSS(Task-State Stack),它是一個數據結構,裏面包含多個字段,32位TSS如圖所示。

 

 

能夠看出,TSS包含不少個字段,可是在這裏,咱們只關注偏移4到偏移27的3個ss和3個esp。當發生堆棧切換時,內層的ss和 esp就是從這裏取得的。

好比,咱們當前所在的是ring3,當轉移至ring1時,堆棧將被自動切換到由ss1和esp1指定的位置。因爲只是在由外層到內層 (低特權級到高特權級)切換時新堆棧纔會從TSS中取得,因此TSS中沒有位於最外層的ring3的堆棧信息。

 

新堆棧的問題已經解決,下面就是CPU在整個過程當中所作的工做:

1. 根據目標代碼段的DPL(新的CPL)從TSS中選擇應該切換至哪一個ss和esp。

2. 從TSS中讀取新的ss和esp。在這過程當中若是發現ss、esp或者TSS界限錯誤都會致使無效TSS異常(#TS)。

3. 對ss描述符進行檢驗,若是發生錯誤,一樣產生#TS 異常。

4. 暫時性地保存當前ss和esp的值。

5. 加載新的ss和esp。

6. 將剛剛保存起來的ss和esp的值壓入新棧。

7. 從調用者堆棧中將參數複製到被調用者堆棧(新堆棧)中,複製參數的數目由調用門中Param Count一項來決定。若是 Param Count是零的話,將不會複製參數。

8. 將當前的cs和eip壓棧。

9. 加載調用門中指定的新的cs和eip,開始執行被調用者過程。

 

在第7步中,解釋了調用門中Param Count的做用,Param Count只有5位,也就是說,最多隻能複製31個參數。若是參數多於31個該怎麼辦呢?這時可讓其中的某個參數變成指向一 個數據結構的指針,或者經過保存在新堆棧裏的ss和esp來訪問舊堆棧中的參數。

 

此刻結合TSS結構和上述步驟,能夠理解經過調用門進行由外層到內層調用的全過程。那麼,正如call指令對 應ret,調用門也面臨返回的問題。經過長短call和ret的堆棧變化這兩組對比,咱們發現,ret基本上是call的反過程,只

是帶參數的ret指令會同時釋放事先被壓棧的參數。

實際上,ret這個指令不只能夠實現短返回和長返回,並且能夠實現帶有特權級變換的長返回。由被調用者到調用者的返回過 程中,處理器的工做包含如下步驟:

1. 檢查保存的cs中的RPL以判斷返回時是否要變換特權級。

2. 加載被調用者堆棧上的cs和eip(此時會進行代碼段描述符和選擇子類型和特權級檢驗)。

3. 若是ret指令含有參數,則增長esp的值以跳過參數,而後esp將指向被保存過的調用者ss和esp。注意,ret的參數必須 對應調用門中的Param Count 的值。

4. 加載ss和esp,切換到調用者堆棧,被調用者的ss和esp被丟棄。在這裏將會進行ss描述符、esp以及ss段描述符的檢驗。

5. 若是ret指令含有參數,增長esp的值以跳過參數(此時已經在調用者堆棧中)。

6. 檢查ds、es、fs、gs的值,若是其中哪個寄存器指向的段的DPL小於CPL(此規則不適用於一致代碼段),那麼一個空描述符會被加載到該寄存器。

如圖所示

 

 

綜上所述,使用調用門的過程實際上分爲兩個部分,一部分是從低特權級到高特權級,經過調用門和call指令來實現;另外一部

分則是從高特權級到低特權級,經過ret指令來實現。

(3)進入ring3

ret指令執行前,堆棧中應該已經準備好了目標代碼段的cs、eip,以及ss和esp,另外,還可能有參數。這些能夠是處理器壓入棧的,也能夠由咱們本身壓棧。在咱們的例子中,在ret前的堆棧如圖3.22所示。 

 

 

這樣,執行ret以後就能夠轉移到低特權級代碼中了。在(pmtest4.asm)基礎上作一下修改(造成 pmtest5a.asm)。如上面的圖3.22所示,咱們至少要添加一個ring3的代碼段和一個ring3的堆棧段。

 

(4)pmtest5a.asm 由ring0到ring3轉移

首先,咱們以前的代碼都運行在ring0!

添加一個ring3代碼段[SECTION .ring3],一個ring3堆棧段[SECTION .s3]

這個ring3代碼段很是簡單,跟[SECTION .la]和[SECTION .sdest]的內容差很少,一樣是打印一個字符。

須要注意,因爲這段代碼運行在ring3,而在其中因爲要寫顯存而訪問到了VIDEO段,爲了避免會產生錯誤,咱們把VIDEO段的DPL 修改成3。

25 LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW + DA_DPL3

 

392行讓程序再也不繼續執行。392 jmp $

之因此這樣作,是爲了先驗證一下由ring0到ring3的轉移是否成功。若是屏幕上出 現紅色的3,而且停住不動,再也不返回DOS,則說明轉移成功。

 

新段對應的描述符LABEL_DESC_CODE_RING3的屬性加上了DA_DPL3,讓它的DPL變成了3

相應選擇子SelectorCodeRing3的SA_RPL3將RPL也設成了3。

同時有堆棧段的descriptor LABEL_DESC_STACK3以及selector SelectorStack3,以及初始化,在此略去。

 

這樣,代碼段和堆棧段都已經準備好了。讓咱們將ss、esp、cs、eip依次壓棧,而且執行retf指令。

266 push SelectorStack3

267 push TopOfStack3

107268 push SelectorCodeRing3

269 push 0

270 retf

此段代碼放在顯示完字符串「In Protect Mode now.」後當即執行。

編譯,運行。

會看到了紅色的3在「In Protect Mode now.」下方顯示。在這代表咱們由ring0到ring3的轉移成功完成。

(5)pmtest5b.asm 在ring3中使用調用門

修改pmtest4中提到的調用門的selectorSelectorCallGateTest以及descriptorLABEL_CALL_GATE_TEST:的DPL,RPL

而後修改[SECTION .ring3]代碼,在死循環前添加

call SelectorCallGateTest:0。

修改描述符和選擇子是爲了知足CPL和RPL 都小於等於調用門DPL的條件。

編譯運行

出現錯誤。由於從低特權級到高特權級轉移的時候,須要用到 TSS。

 

(6)pmtest5c.asm 添加TSS,在ring3中使用調用門

由於從低特權級到高特權級轉移的時候,須要用到 TSS,在pmtest5c.asm中準備一個TSS

TSS做爲數據結構有其descriptor LABEL_DESC_TSS,selector SelectorTSS以及段[SECTION .TSS]。定義及初始化見代碼

能夠看出,除了0級堆棧以外,其餘各個字段咱們都沒作任何初始化。由於在本例中,咱們只用到這一部分。

添加初始化TSS描述符的代碼以後,TSS就準備好了,咱們須要在特權級變換以前加載它

311 call DispReturn

312

⇒ 313 mov ax, SelectorTSS

⇒ 314 ltr ax

315

316 push SelectorStack3

317 push TopOfStack3

318 push SelectorCodeRing3

319 push 0

320 retf

以後編譯運行,成功。顯示call調用門的C以及ring3段的3.

 

(7)pmtest5.asm 返回實模式

到目前爲止,咱們已經成功實現了兩次從高特權級到低特權級以及一次從低特權級到高特權級的轉移(ring0-ring3-ring-0-ring3,ring0打印「In protect mode」,而後到ring3打印3,而後ring3callgate到ring0打印L,而後返回ring3),最終在低特權級的代碼[SECTION .ring3] 中讓程序停住。咱們已經具有了在各類特權級下進行轉移的能力,而且熟悉了調用門這種典型門描述符的用法。

 

爲了讓咱們的程序可以順利地返回實模式,咱們將調用局部任務的代碼加入到調用門的目標代碼([SECTION .sdest])。最後,程序將由這裏進入局部任務,而後經由原路返回實模式。(ring3打印3,調用門,調用門打印C,調用局部任務LDT打印L,而後在局部任務jmp SelectorCode16:0返回16位代碼段,以後返回實模式)

346 [SECTION .sdest]; 調用門目標段

347 [BITS 32]

...

⇒ 359 mov ax, SelectorLDT

⇒ 360 lldt ax

361

⇒ 362 jmp SelectorLDTCodeA:0 ; 跳入局部任務,將打印字母'L'

編譯運行,結果應爲顯示in protect mode ,3,c,l,而後返回實模式能夠繼續運行

 

調試:

編譯爲.com文件運行

pmtest5a

 

 

pmtest5b

 

 

 

pmtest5c

 

 

pmtest5

 

 

7.課後手動改:

1)自定義添加1個GDT代碼段、1個LDT代碼段,GDT段內要對一個內

存數據結構寫入一段字符串,而後LDT段內代碼段功能爲讀取並打印該GDT的內容;

參考pmtest3.com

修改[SECTION .data1],修改字符串爲StrTest: db "JUST MONIKA", 0

修改[SECTION .s32]; 32 位代碼段. 由實模式跳入.

改成以下,至關於直接跳到LDT中的descriptor

.........................

[SECTION .s32]; 32 位代碼段. 由實模式跳入.

[BITS 32]

LABEL_SEG_CODE32:

; Load LDT

mov ax, SelectorLDT

lldt ax

jmp SelectorLDTCodeA:0 ; 跳入局部任務

SegCode32Len equ $ - LABEL_SEG_CODE32

; END of [SECTION .s32]

........................

 

修改LDT中的段; CodeA (LDT, 32 位代碼段)[SECTION .la]

功能改成顯示GDT中[SECTION .DATA]段的字符串StrTest

........................

; CodeA (LDT, 32 位代碼段)

[SECTION .la]

ALIGN 32

[BITS 32]

LABEL_CODE_A:

mov ax, SelectorData

mov ds, 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, OffsetStrTest ; 源數據偏移

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: ; 顯示完畢

jmp SelectorCode16:0

CodeALen equ $ - LABEL_CODE_A

; END of [SECTION .la]

.........................

而後編譯運行。運行時順序爲實模式跳轉保護模式[SECTION .s32],而後[SECTION .s32]加載LDT的ldtr,而後跳轉LDT的[SECTION .la]段,該段中先在屏幕顯示[SECTION .DATA]段的字符串StrTest,而後跳回實模式

代碼保存爲pmtestmy.asm,編譯爲pmtestmy.com。

編譯運行

(2)自定義2個GDT代碼段A、B,分屬於不一樣特權級,功能自定義,要求實現A-->B的跳轉,以及B-->A的跳轉。

參考pmtest5,實現了ring0->ring3->ring0->ring3d的跳轉

 

 

 

二.是書上內容的節選,代碼裏有一點註釋。再翻翻書的保護模式那一章吧

 

x86 CPU的基本模式:實模式、保護模式

 

– 實模式

 

• 地址總線寬度:20bit

 

• 寄存器和數據總線寬度:16bit

 

• 尋址空間是多少?

 

• 實模式:PA=Segment*16+Offset

 

 

pmtest1.asm

; ========================================== ; pmtest1.asm ; 編譯方法:nasm pmtest1.asm -o pmtest1.bin ; ==========================================

%include    "pm.inc" ; 常量, 宏, 以及一些說明 org 07c00h jmp LABEL_BEGIN [SECTION .gdt] ;定義一個段,段名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長度 :equ至關於起個別名。S爲當前位置。s-LABEL_GDT,就是當前位置減去.gdt起始位置,也就是.gdt長度 GdtPtr dw GdtLen - 1 ; GDT界限 。GdtPtr也是個小的數據結構,它有6字節,前2字節是GDT的界限,後4字節是GDT的基地址
 dd 0 ; GDT基地址 ; GDT 選擇子 SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT ;直觀地看,它好像是DESC_VIDEO這個描述符相對於GDT基址的偏移。實際上有其數據結構,其名選擇子 SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT ;

; END of [SECTION .gdt]

[SECTION .s16]
[BITS 16]   ;代表是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,  //selector16位,dword兩字節,高位selector,低位偏移0.(由於聲明瞭這段是16位代碼,因此一個字兩字節)
; 並跳轉到 Code32Selector: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]

 

 

好了,首先看[SECTION .gdt]這個段,其中的Descriptor是在pm.inc中定義的宏(見代碼3.2)。先不要管具體的意義是什
麼,看字面咱們能夠知道,這個宏表示的不是一段代碼,而是一個數據結構,它的大小是8字節。 

 

在段[SECTION.gdt]中並列有3個Descriptor,看上去是個結構數組,你必定猜到了,這個數組的名字叫作GDT。
GdtLen是GDT的長度,GdtPtr也是個小的數據結構,它有6字節,前2字節是GDT的界限,後4字節是GDT的基地址。
另外還定義了兩個形如SelectorXXXX的常量,至因而作什麼用的,咱們暫且無論它。
再往下到了一個代碼段,[BITS 16]明確地指明瞭它是一個16位代碼段。你會發現,這段程序修改了一些GDT中的值,而後執行
了一些不常見的指令,最後經過jmp指令實現一個跳轉(第71行)。正如代碼註釋中所說的,這一句將「真正進入保護模式」。實
際上,它將跳轉到第三個section,即[SECTION .s32]中,這個段是32位的,執行最後一小段代碼。這段代碼看上去是往某個地址
處寫入了2字節,而後就進入了無限循環。 
 
能夠看到,在屏幕中部右側,出現了一個紅色的字母「P」,而後不再動了。不難猜到,程序的最後一部分代碼中寫入的兩個字節是寫進了顯存中。
如今,大體的感性認識已經有了,但你必定有一些疑惑,什麼是GDT?那些看上去怪怪的指令到底在作什麼?如今咱們先來總結一下,在這個程序中,咱們瞭解到什麼,有哪些疑問。
 
咱們瞭解到的內容以下:
程序定義了一個叫作GDT的數據結構。
後面的16位代碼進行了一些與GDT有關的操做。
程序最後跳到32位代碼中作了一點操做顯存的工做。
 
咱們不明就裏的內容以下:
GDT是什麼?它是幹什麼用的?
程序對GDT作了什麼?
那個jmp SelectorCode32:0跟咱們從前用過的jmp有什麼不一樣? 
 
 
在IA32下,CPU有兩種工做模式:實模式和保護模式。直觀地看,當咱們打開本身的PC,開始時CPU是工做在實模式下的,通過某種機制以後,才進入保護模式。在保護模式下,CPU有着巨大的尋址能力,併爲強大的32位操做系統提供了更好的硬件保障。
 
咱們先來回憶一下舊政策。Intel 8086是16位的CPU,它有着16位的寄存器(Register)、16位的數據總線(Data Bus)以及20位的地址總線(Address Bus)和1MB的尋址能力。一個地址是由段和偏移兩部分組成的,物理地址遵循這樣的計算公式:
物理地址(Physical Address)=段值(Segment)×16+偏移(Offset)
其中,段值和偏移都是16位的。
 
從80386開始,Intel家族的CPU進入32位時代。80386有32位地址線,因此尋址空間能夠達到4GB。因此,單從尋址這方面說,使用16位寄存器的方法已經不夠用了。這時候,咱們須要新的方法來提供更大的尋址能力。固然,慢慢地你能看到,保護模式的優勢不只僅在這一個方面。
 
在實模式下,16位的寄存器須要用「段:偏移」這種方法才能達到1MB的尋址能力,現在咱們有了32位寄存器,一個寄存器就能夠尋址4GB的空間,是否是今後段值就被拋棄了呢?實際上並無,新政策下的地址仍然用「段:偏移」這樣的形式來表示,只不過保護模式下「段」的概念發生了根本性的變化。
 
實模式下,段值仍是能夠看作是地址的一部分的,段值爲XXXXh表示以XXXX0h開始的一段內存。
 
而保護模式下,雖然段值仍然由原來16位的cs、ds等寄存器表示,但此時它僅僅變成了一個索引,這個索引指向一個數據結構的一個表項,表項中詳細定義了段的起始地址、界限、屬性等內容。這個數據結構,就是GDT(實際上還多是LDT,這個之後再介紹)。GDT中的表項也有一個專門的名字,叫作描述符(Descriptor)。
也就是說,GDT的做用是用來提供段式存儲機制,這種機制是經過段寄存器和GDT中的描述符共同提供的。爲了全面地瞭解它,
咱們來看一下圖3.4所示的描述符的結構。
 
 
 

 

 

 

 

這個示意圖表示的是代碼段和數據段描述符,此外,描述符的種類還有系統段描述符和門描述符,下文會有介紹。
除了BYTE5和BTYE6中的一堆屬性看上去有點複雜之外,其餘三個部分倒還容易理解,它們分別定義了一個段的基址和界限。不過,因爲歷史問題,它們都被拆開存放。
至於那些屬性,咱們暫時先無論它。
好了,咱們回頭再來看看代碼3.1,Descriptor這個宏用比較自動化的方法把段基址、段界限和段屬性安排在一個描述符中合適的位置,有興趣的讀者能夠研究這個宏的具體內容。
本例的GDT中共有3個描述符,爲方便起見,在這裏咱們分別稱它們爲DESC_DUMMY、DESC_CODE32和DESC_VIDEO。
其中DESC_VIDEO的段基址是0B8000h,顧名思義,這個描述符指向的正是顯存。
如今咱們已經知道,GDT中的每個描述符定義一個段,那麼cs、ds等段寄存器是如何和這些段對應起來的呢?你可能注意到了,在[SECTION.s32]這個段中有兩句代碼是這樣的(第80行和第81行):
 
mov ax, SelectorVideo
mov gs, ax
 
看上去,段寄存器gs的值變成了SelectorVideo,咱們在上文中能夠看到,SelectorVideo是這樣定義的(第25行):
SelectorVideo equ LABEL_DESC_VIDEO-LABEL_GDT
直觀地看,它好像是DESC_VIDEO這個描述符相對於GDT基址的偏移。實際上,它有一個專門的名稱,叫作選擇子(Selector),它也不是一個偏移,而是稍稍複雜一些,它的結構如圖3.5所示。

 

 

 

 

不難理解,當TI和RPL都爲零時,選擇子就變成了對應描述符相對於GDT基址的偏移,就好像咱們程序中那樣。
看到這裏,讀者確定已經明白了第86行的意思,gs值爲SelectorVideo,它指示對應顯存的描述符DESC_VIDEO,這條指令將把
ax的值寫入顯存中偏移位edi的位置。
總之,整個的尋址方式如圖3.6所示。

 

 

 

注意圖3.6中「段:偏移」形式的邏輯地址(Logical Address)通過段機制轉化成「線性地址」(Linear Address),而不是「物理地址」(Physical Address),
其中的緣由咱們之後會提到。在上面的程序中,線性地址就是物理地址。另外,包含描述符的,不只能夠是GDT,也能夠是LDT。 

 

明白了這些,離明白整個程序的距離已經只剩一層窗紙了。由於只剩下[SECTION .s16]這一段尚未分析。不過,既然[SECTION .s32]是32位的程序,而且在保護模式下執行,那麼[SECTION .s16]的任務必定是從實模式向保護模式跳轉了。下面咱們就來看一下實模式是如何轉換到保護模式的。

 

讓咱們到[SECTION .s16]這段,先看一下初始化32位代碼段描述符的這一段,代碼首先將LABEL_SEG_CODE32的物理地址(即
[SECTION .s32]這個段的物理地址)賦給eax,而後把它分紅三部分賦給描述符DESC_CODE32中的相應位置。因爲DESC_CODE32的段
界限和屬性已經指定,因此至此,DESC_CODE32的初始化所有完成。
 
接下來的動做把GDT的物理地址填充到了GdtPtr這個6字節的數據結構中,而後執行了一條指令(第55行):
 
lgdt [GdtPtr]

 

這一句的做用是將GdtPtr指示的6字節加載到寄存器gdtr,gdtr的結構如圖3.7所示。

 

 

 

 

 

 

 

 

 

 

 

pm.inc

; 描述符圖示 ; 圖示一 ; ; ------ ┏━━┳━━┓高地址 ; ┃ 7 ┃ 段 ┃ ; ┣━━┫ ┃ ; 基 ; 字節 7 ┆ ┆ ┆ ; 址 ; ┣━━┫ ② ┃ ; ┃ 0 ┃ ┃ ; ------ ┣━━╋━━┫ ; ┃ 7 ┃ G ┃ ; ┣━━╉──┨ ; ┃ 6 ┃ D ┃ ; ┣━━╉──┨ ; ┃ 50 ┃ ; ┣━━╉──┨ ; ┃ 4 ┃ AVL┃ ; 字節 6 ┣━━╉──┨ ; ┃ 3 ┃ ┃ ; ┣━━┫ 段 ┃ ; ┃ 2 ┃ 界 ┃ ; ┣━━┫ 限 ┃ ; ┃ 1 ┃ ┃ ; ┣━━┫ ② ┃ ; ┃ 0 ┃ ┃ ; ------ ┣━━╋━━┫ ; ┃ 7 ┃ P ┃ ; ┣━━╉──┨ ; ┃ 6 ┃ ┃ ; ┣━━┫ DPL┃ ; ┃ 5 ┃ ┃ ; ┣━━╉──┨ ; ┃ 4 ┃ S ┃ ; 字節 5 ┣━━╉──┨ ; ┃ 3 ┃ ┃ ; ┣━━┫ T ┃ ; ┃ 2 ┃ Y ┃ ; ┣━━┫ P ┃ ; ┃ 1 ┃ E ┃ ; ┣━━┫ ┃ ; ┃ 0 ┃ ┃ ; ------ ┣━━╋━━┫ ; ┃ 23 ┃ ┃ ; ┣━━┫ ┃ ; ┃ 22 ┃ ┃ ; ┣━━┫ 段 ┃ ; ; 字節 ┆ ┆ 基 ┆ ; 2, 3, 4 ; ┣━━┫ 址 ┃ ; ┃ 1 ┃ ① ┃ ; ┣━━┫ ┃ ; ┃ 0 ┃ ┃ ; ------ ┣━━╋━━┫ ; ┃ 15 ┃ ┃ ; ┣━━┫ ┃ ; ┃ 14 ┃ ┃ ; ┣━━┫ 段 ┃ ; ; 字節 0,1┆ ┆ 界 ┆ ; ; ┣━━┫ 限 ┃ ; ┃ 1 ┃ ① ┃ ; ┣━━┫ ┃ ; ┃ 0 ┃ ┃ ; ------ ┗━━┻━━┛低地址 ; ; 圖示二 ; 高地址………………………………………………………………………低地址 ; |   7   |   6   |   5   |   4   |   3   |   2   |   1   |   0    | ; |7654321076543210765432107654321076543210765432107654321076543210|    <- 共 8 字節 ; |--------========--------========--------========--------========| ; ┏━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━┓ ; ┃31..24┃   (見下圖)   ┃     段基址(23..0)    ┃ 段界限(15..0)┃ ; ┃ ┃ ┃ ┃ ┃ ; ┃ 基址2┃③│②│ ①┃基址1b│ 基址1a ┃ 段界限1 ┃ ; ┣━━━╋━━━┳━━━╋━━━━━━━━━━━╋━━━━━━━┫ ; ┃ %6 ┃  %5  ┃  %4  ┃  %3  ┃     %2       ┃       %1 ┃ ; ┗━━━┻━━━┻━━━┻━━━┻━━━━━━━┻━━━━━━━┛ ; │ \_________ ; │ \__________________ ; │ \________________________________________________ ; │ \ ; ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓ ; ┃ 7654321076543210 ┃ ; ┣━━╋━━╋━━╋━━╋━━┻━━┻━━┻━━╋━━╋━━┻━━╋━━╋━━┻━━┻━━┻━━┫ ; ┃ G ┃ D ┃ 0  ┃ AVL┃   段界限 2 (19..16) ┃ P ┃ DPL ┃ S ┃ TYPE ┃ ; ┣━━┻━━┻━━┻━━╋━━━━━━━━━━━╋━━┻━━━━━┻━━┻━━━━━━━━━━━┫ ; ┃ ③: 屬性 2      ┃    ②: 段界限 2 ┃ ①: 屬性1 ┃ ; ┗━━━━━━━━━━━┻━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━┛ ; 高地址 低地址 ; ; ; 說明: ; ; (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 陷阱門類型值 ; 選擇子圖示: ; ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓ ; ┃ 1514131211109876543210 ┃ ; ┣━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━╋━━╋━━┻━━┫ ; ┃ 描述符索引 ┃ TI ┃ RPL ┃ ; ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━┻━━━━━┛ ; ; 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 ;macro定義宏。 3表示有三個參數 dw %2 & 0FFFFh ; 段界限1 dw %1 & 0FFFFh ; 段基址1 db (%1 >> 16) & 0FFh ; 段基址2 dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)    ; 屬性1 + 段界限2 + 屬性2 db (%1 >> 24) & 0FFh ; 段基址3 %endmacro ; 共 8 字節 ; ; 門 ; usage: Gate Selector, Offset, DCount, Attr ; Selector: dw ; Offset: dd ; DCount: db ; Attr: db %macro Gate 4 dw (%2 & 0FFFFh) ; 偏移1 dw %1 ; 選擇子 dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ; 屬性 dw ((%2 >> 16) & 0FFFFh) ; 偏移2 %endmacro ; 共 8 字節 ; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
相關文章
相關標籤/搜索