進入保護模式(二)——《x86彙編語言:從實模式到保護模式》讀書筆記14

首先來段題外話:以前我發現我貼出的代碼都沒有行號,給講解帶來不便。因此從如今起,我要給代碼加上行號。我寫博客用的這個插入代碼的插件,確實不支持自動插入行號。我真的沒有找到什麼好方法,無奈之下,只能按照網友的說法,在VIM中給每行代碼加上行號,而後再貼出來。緩存

在VIM中每一行都添加上行號的方法是:安全

:%s/^/\=line(".")/學習

對,只要執行這個命令就能夠了。至於爲何這樣寫,能夠參考個人另外一篇博文spa

《在VIM中添加行號的方法》http://blog.csdn.net/longintchar/article/details/50569851         
        
.net

 

咱們接着上篇博文 進入保護模式(一)——《x86彙編語言:從實模式到保護模式》讀書筆記12 說。插件

 

(五)設置PE位

44         cli                                ;保護模式下中斷機制還沒有創建,應 
45                                            ;禁止中斷 
46         mov eax,cr0
47         or eax,1
48         mov cr0,eax                        ;設置PE位

第44行,用於關中斷。由於保護模式下的中斷和實模式不一樣,因此原來的中斷向量表再也不適用,BIOS中斷也不能再用,由於它們都是實模式下的代碼。在從新配置保護模式下的中斷環境以前,咱們必須關中斷。code

CR0是處理器內部的一個控制寄存器,也是32位的(以下圖,圖片來自趙炯的《Linux內核徹底剖析》)。blog

它的bit0是保護模式容許位(Protection Enable,PE)。當PE=1時,則處理器進入保護模式。索引

cr0

第46~48用於設置CR0的bit0爲1.圖片

(六)關於段寄存器

咱們知道,32位模式下,段寄存器有CS,DS,ES,SS,FS,GS. 這些段寄存器每一個都分爲2個部分,一個是16位的可見部分,一個是隱藏部分,稱爲描述符高速緩存器,用來存放段的線性基地址、段界限和段屬性。以下圖:

New0002段寄存器的格式

1.實模式下的內存訪問

在32位處理器上的實模式下,假如執行下面的代碼。

mov cx,0x2000
mov ds,cx
mov [0xc0],al

CPU在把0x2000傳送到DS的同時,還會把0x2000左移4位(0x20000),傳送到DS描述符高速緩存寄存器(段基地址部分僅低20位有效,高12位所有是0)。此後,只要不改變DS的內容,那麼每次訪問內存都直接使用DS描述符高速緩存寄存器的內容做爲段地址。

2.保護模式下的內存訪問

在保護模式下,實模式的6個段寄存器叫作「段選擇器」。儘管在訪問內存的時候也要指定一個段,可是傳送到段選擇器的內容不是邏輯段地址,而是段選擇子(也叫段選擇符)

以下圖(圖片來自趙炯的《Linux內核徹底剖析》)所示,段選擇子由三部分組成。

  • 請求特權級RPL(Requested Privilege Level):提供了段保護信息,咱們之後會學習。如今只需設置爲00便可。
  • 表指示標誌TI(Table Index):TI=0時,表示描述符在GDT中;TI=1時,表示描述符在LDT(咱們之後會學習)中。
  • 索引值(Index):描述符在GDT或者LDT中的索引項號。

段選擇子

爲了說明保護模式下的內存訪問,咱們回到代碼。

56         mov cx,00000000000_10_000B         ;加載數據段選擇子(0x10)
57         mov ds,cx
58
59         ;如下在屏幕上顯示"Protect mode OK." 
60         mov byte [0x00],'P'  
61         mov byte [0x02],'r'
62         mov byte [0x04],'o'
63         mov byte [0x06],'t'
64         mov byte [0x08],'e'
65         mov byte [0x0a],'c'
66         mov byte [0x0c],'t'

第5六、57行,將段選擇子10000b傳到段選擇器DS中,從段選擇子能夠看出,RPL=0;TI=0(表示GDT);索引號爲2;

當處理器執行任何改變段選擇器的指令時(好比mov、jmp far、call far、iret、retf等),就將指令中提供的索引號*8做爲偏移地址,同GDTR寄存器中的線性基地址相加,而後訪問GDT。若是沒有什麼問題(好比超過了GDT的界限),就把找到的描述符加載到不可見的描述符高速緩存寄存器。此後,每當有訪問內存的指令時,就再也不訪問GDT中的描述符,而是直接使用段寄存器的描述符高速緩存寄存器。

結合代碼來講,第57行,處理器把2*8(=16)做爲偏移地址,同GDTR的內容(內容爲0x00007e00)相加,獲得0x0000_7e16,根據這個地址找到描述符(就是咱們以前建立的#2描述符)

27         ;建立#2描述符,保護模式下的數據段描述符(文本模式下的顯示緩衝區) 
28         mov dword [bx+0x10],0x8000ffff     
29         mov dword [bx+0x14],0x0040920b

而後,把這個描述符加載到高速緩存寄存器(包括線性基地址0x000b8000,段界限,段屬性)。

第60行,執行這條指令時,處理器用DS描述符高速緩存寄存器中的線性基地址(0x000b8000,文本模式的顯存起始地址)加上指令中的偏移量0x00,造成32位的物理地址0x000b8000,並將字符‘P’寫入該處。

不只僅是訪問數據段,處理器訪問代碼段取指令的時候,也是採用相同的方法。假設CS描述符高速緩存寄存器已經裝載了正確的32位線性基地址,那麼處理器取指令的時候,會使用CS描述符高速緩存寄存器中的32位線性基地址加上EIP中的偏移量,構成32位的物理地址,根據這個物理地址從內存中取得指令。

(七)清空流水線並串行化處理器

正如前文所述,即便在實模式下,段寄存器的高速緩存寄存器也被用於訪問內存。當處理器進入保護模式後,高速緩存寄存器的內容依然殘留,可是這些內容在保護模式下是無效的。所以,比較安全的作法是儘快刷新段選擇器,包括描述符高速緩存寄存器。

另外,在進入保護模式以前,不少指令已經進入了流水線。由於處理器工做在實模式下,因此它們都是按照16位操做數和地址長度進行譯碼的,即便是那些用bits32編譯的指令,爲了防止執行結果不正確,因此必須清空流水線。還用,那些經過亂序執行獲得的中間結果也是無效的,因此必須清理掉,讓處理器串化執行。

爲了達到上述目的,咱們能夠採用遠轉移指令jmp或者遠過程調用指令call。遇到這類指令,處理器通常會清空流水線而且串化執行;另外一方面,遠轉移會從新加載CS,並刷新描述符高速緩存寄存器的內容。因此,強烈建議在設置了PE位後,馬上用jmp或者call轉移到當前指令流的下一條指令上。

因而代碼中有:

50         ;如下進入保護模式... ...
51         jmp dword 0x0008:flush             ;16位的描述符選擇子:32位偏移
52                                            ;清流水線並串行化處理器 
53         [bits 32] 
54
55    flush:
56         mov cx,00000000000_10_000B         ;加載數據段選擇子(0x10)
57         mov ds,cx

第51行,是一條遠轉移指令。若是你忘記了jmp的用法,沒有關係,能夠參考個人另外一篇博文8086處理器的無條件轉移指令——《x86彙編語言:從實模式到保護模式》讀書筆記13

這條指令和位於它前面的指令同樣,是默認用[bits 16]編譯的。可是由於使用了關鍵字dword(注意:這裏的dword是修飾偏移地址flush的),因此編譯後的偏移地址是32位的。

若是51行這樣寫:

51         jmp  0x0008:flush             ;16位的描述符選擇子:16位偏移

這樣寫是不嚴謹的。由於這樣編譯出來的目標地址是16位的。若是flush表明的地址是0x12345678,那麼編譯後會被截斷成爲0x5678,這顯然是錯的。因此這個跳轉必定要加dword.

注意:由於設置了PE位,因此如今已經處於保護模式下了。因此處理器會把第一個操做數(0x0008)理解爲段選擇子,而不是是模式下的邏輯段基址。當51行的指令執行時,處理器會把選擇子0x0008(索引號爲1,TI=0,RPL=00)加載到CS,並把#1描述符(定義了一個代碼段,基地址是0x7c00,段界限是0x1ff,長度爲0x200)加載到CS描述符高速緩存寄存器中。因此程序會轉移到基地址爲0x0000_7c00的代碼段內的某個位置執行。這個位置取決於偏移地址。偏移地址就是標號flush的彙編地址(由於指定了dword,因此編譯後是32位的),處理器會用這個32位的數值來代替EIP的原有內容。因而,程序就轉移到flush處了。

第53行,使用了僞指令[bits 32],從這之後,指令是按照32位編譯的。由於指令執行到這裏的時候,已經真真正正地進入了保護模式了。

(八)進入保護模式的主要步驟

咱們總結一下進入保護模式的主要步驟:

1.安裝段描述符,構造GDT

2.用lgdt指令加載GDTR

3.打開A20

4.設置CR0的PE位爲1

5.跳轉,真正進入保護保護模式。

(九)在屏幕上顯示字符

55    flush:
56         mov cx,00000000000_10_000B         ;加載數據段選擇子(0x10)
57         mov ds,cx
58
59         ;如下在屏幕上顯示"Protect mode OK." 
60         mov byte [0x00],'P'  
61         mov byte [0x02],'r'
62         mov byte [0x04],'o'
63         mov byte [0x06],'t'
64         mov byte [0x08],'e'
65         mov byte [0x0a],'c'
66         mov byte [0x0c],'t'
67         mov byte [0x0e],' '
68         mov byte [0x10],'m'
69         mov byte [0x12],'o'
70         mov byte [0x14],'d'
71         mov byte [0x16],'e'
72         mov byte [0x18],' '
73         mov byte [0x1a],'O'
74         mov byte [0x1c],'K'

5六、57行,前文已經說過,令DS指向文本模式的顯示緩衝區。

60~74行,就是在屏幕左上角顯示"Protect mode OK." 須要說明的是:不論是實模式仍是保護模式,外圍設備是不受影響的。

最後注意一點:

保護模式下,不容許使用mov指令改變段寄存器CS的內容。好比

mov cs,ax

這樣寫是不對的。這樣作會致使處理器產生一個無效操做碼的異常中斷。

相關文章
相關標籤/搜索