进入保护模式(二)——《x86汇编语言:从实模式到保护模式》读书笔记14
首先來段題外話:之前我發(fā)現(xiàn)我貼出的代碼都沒有行號,給講解帶來不便。所以從現(xiàn)在起,我要給代碼加上行號。我寫博客用的這個插入代碼的插件,確實(shí)不支持自動插入行號。我真的沒有找到什么好方法,無奈之下,只能按照網(wǎng)友的說法,在VIM中給每行代碼加上行號,然后再貼出來。
在VIM中每一行都添加上行號的方法是:
:%s/^/\=line(".")/
對,只要執(zhí)行這個命令就可以了。至于為什么這樣寫,可以參考我的另一篇博文
《在VIM中添加行號的方法》http://blog.csdn.net/longintchar/article/details/50569851 ? ? ? ??
? ? ? ??
?
我們接著上篇博文 進(jìn)入保護(hù)模式(一)——《x86匯編語言:從實(shí)模式到保護(hù)模式》讀書筆記12 說。
?
(五)設(shè)置PE位
44 cli ;保護(hù)模式下中斷機(jī)制尚未建立,應(yīng) 45 ;禁止中斷 46 mov eax,cr0 47 or eax,1 48 mov cr0,eax ;設(shè)置PE位第44行,用于關(guān)中斷。因?yàn)楸Wo(hù)模式下的中斷和實(shí)模式不同,所以原來的中斷向量表不再適用,BIOS中斷也不能再用,因?yàn)樗鼈兌际菍?shí)模式下的代碼。在重新配置保護(hù)模式下的中斷環(huán)境之前,我們必須關(guān)中斷。
CR0是處理器內(nèi)部的一個控制寄存器,也是32位的(如下圖,圖片來自趙炯的《Linux內(nèi)核完全剖析》)。
它的bit0是保護(hù)模式允許位(Protection Enable,PE)。當(dāng)PE=1時,則處理器進(jìn)入保護(hù)模式。
第46~48用于設(shè)置CR0的bit0為1.
(六)關(guān)于段寄存器
我們知道,32位模式下,段寄存器有CS,DS,ES,SS,FS,GS. 這些段寄存器每個都分為2個部分,一個是16位的可見部分,一個是隱藏部分,稱為描述符高速緩存器,用來存放段的線性基地址、段界限和段屬性。如下圖:
1.實(shí)模式下的內(nèi)存訪問
在32位處理器上的實(shí)模式下,假如執(zhí)行下面的代碼。
mov cx,0x2000 mov ds,cx mov [0xc0],alCPU在把0x2000傳送到DS的同時,還會把0x2000左移4位(0x20000),傳送到DS描述符高速緩存寄存器(段基地址部分僅低20位有效,高12位全部是0)。此后,只要不改變DS的內(nèi)容,那么每次訪問內(nèi)存都直接使用DS描述符高速緩存寄存器的內(nèi)容作為段地址。
2.保護(hù)模式下的內(nèi)存訪問
在保護(hù)模式下,實(shí)模式的6個段寄存器叫做“段選擇器”。盡管在訪問內(nèi)存的時候也要指定一個段,但是傳送到段選擇器的內(nèi)容不是邏輯段地址,而是段選擇子(也叫段選擇符)。
如下圖(圖片來自趙炯的《Linux內(nèi)核完全剖析》)所示,段選擇子由三部分組成。
- 請求特權(quán)級RPL(Requested Privilege Level):提供了段保護(hù)信息,我們以后會學(xué)習(xí)。現(xiàn)在只需設(shè)置為00即可。
- 表指示標(biāo)志TI(Table Index):TI=0時,表示描述符在GDT中;TI=1時,表示描述符在LDT(我們以后會學(xué)習(xí))中。
- 索引值(Index):描述符在GDT或者LDT中的索引項號。
為了說明保護(hù)模式下的內(nèi)存訪問,我們回到代碼。
56 mov cx,00000000000_10_000B ;加載數(shù)據(jù)段選擇子(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'第56、57行,將段選擇子10000b傳到段選擇器DS中,從段選擇子可以看出,RPL=0;TI=0(表示GDT);索引號為2;
當(dāng)處理器執(zhí)行任何改變段選擇器的指令時(比如mov、jmp far、call far、iret、retf等),就將指令中提供的索引號*8作為偏移地址,同GDTR寄存器中的線性基地址相加,然后訪問GDT。如果沒有什么問題(比如超過了GDT的界限),就把找到的描述符加載到不可見的描述符高速緩存寄存器。此后,每當(dāng)有訪問內(nèi)存的指令時,就不再訪問GDT中的描述符,而是直接使用段寄存器的描述符高速緩存寄存器。
結(jié)合代碼來說,第57行,處理器把2*8(=16)作為偏移地址,同GDTR的內(nèi)容(內(nèi)容為0x00007e00)相加,得到0x0000_7e16,根據(jù)這個地址找到描述符(就是我們之前創(chuàng)建的#2描述符)
27 ;創(chuàng)建#2描述符,保護(hù)模式下的數(shù)據(jù)段描述符(文本模式下的顯示緩沖區(qū)) 28 mov dword [bx+0x10],0x8000ffff 29 mov dword [bx+0x14],0x0040920b然后,把這個描述符加載到高速緩存寄存器(包括線性基地址0x000b8000,段界限,段屬性)。
第60行,執(zhí)行這條指令時,處理器用DS描述符高速緩存寄存器中的線性基地址(0x000b8000,文本模式的顯存起始地址)加上指令中的偏移量0x00,形成32位的物理地址0x000b8000,并將字符‘P’寫入該處。
不僅僅是訪問數(shù)據(jù)段,處理器訪問代碼段取指令的時候,也是采用相同的方法。假設(shè)CS描述符高速緩存寄存器已經(jīng)裝載了正確的32位線性基地址,那么處理器取指令的時候,會使用CS描述符高速緩存寄存器中的32位線性基地址加上EIP中的偏移量,構(gòu)成32位的物理地址,根據(jù)這個物理地址從內(nèi)存中取得指令。
(七)清空流水線并串行化處理器
正如前文所述,即使在實(shí)模式下,段寄存器的高速緩存寄存器也被用于訪問內(nèi)存。當(dāng)處理器進(jìn)入保護(hù)模式后,高速緩存寄存器的內(nèi)容依然殘留,但是這些內(nèi)容在保護(hù)模式下是無效的。因此,比較安全的做法是盡快刷新段選擇器,包括描述符高速緩存寄存器。
另外,在進(jìn)入保護(hù)模式之前,很多指令已經(jīng)進(jìn)入了流水線。因?yàn)樘幚砥鞴ぷ髟趯?shí)模式下,所以它們都是按照16位操作數(shù)和地址長度進(jìn)行譯碼的,即使是那些用bits32編譯的指令,為了防止執(zhí)行結(jié)果不正確,所以必須清空流水線。還用,那些通過亂序執(zhí)行得到的中間結(jié)果也是無效的,所以必須清理掉,讓處理器串化執(zhí)行。
為了達(dá)到上述目的,我們可以采用遠(yuǎn)轉(zhuǎn)移指令jmp或者遠(yuǎn)過程調(diào)用指令call。遇到這類指令,處理器一般會清空流水線并且串化執(zhí)行;另一方面,遠(yuǎn)轉(zhuǎn)移會重新加載CS,并刷新描述符高速緩存寄存器的內(nèi)容。所以,強(qiáng)烈建議在設(shè)置了PE位后,立刻用jmp或者call轉(zhuǎn)移到當(dāng)前指令流的下一條指令上。
于是代碼中有:
50 ;以下進(jìn)入保護(hù)模式... ... 51 jmp dword 0x0008:flush ;16位的描述符選擇子:32位偏移 52 ;清流水線并串行化處理器 53 [bits 32] 54 55 flush: 56 mov cx,00000000000_10_000B ;加載數(shù)據(jù)段選擇子(0x10) 57 mov ds,cx第51行,是一條遠(yuǎn)轉(zhuǎn)移指令。如果你忘記了jmp的用法,沒有關(guān)系,可以參考我的另一篇博文8086處理器的無條件轉(zhuǎn)移指令——《x86匯編語言:從實(shí)模式到保護(hù)模式》讀書筆記13。
這條指令和位于它前面的指令一樣,是默認(rèn)用[bits 16]編譯的。但是因?yàn)槭褂昧岁P(guān)鍵字dword(注意:這里的dword是修飾偏移地址flush的),所以編譯后的偏移地址是32位的。
如果51行這樣寫:
51 jmp 0x0008:flush ;16位的描述符選擇子:16位偏移這樣寫是不嚴(yán)謹(jǐn)?shù)摹R驗(yàn)檫@樣編譯出來的目標(biāo)地址是16位的。如果flush代表的地址是0x12345678,那么編譯后會被截斷成為0x5678,這顯然是錯的。所以這個跳轉(zhuǎn)一定要加dword.
注意:因?yàn)樵O(shè)置了PE位,所以現(xiàn)在已經(jīng)處于保護(hù)模式下了。所以處理器會把第一個操作數(shù)(0x0008)理解為段選擇子,而不是是模式下的邏輯段基址。當(dāng)51行的指令執(zhí)行時,處理器會把選擇子0x0008(索引號為1,TI=0,RPL=00)加載到CS,并把#1描述符(定義了一個代碼段,基地址是0x7c00,段界限是0x1ff,長度為0x200)加載到CS描述符高速緩存寄存器中。所以程序會轉(zhuǎn)移到基地址為0x0000_7c00的代碼段內(nèi)的某個位置執(zhí)行。這個位置取決于偏移地址。偏移地址就是標(biāo)號flush的匯編地址(因?yàn)橹付薲word,所以編譯后是32位的),處理器會用這個32位的數(shù)值來代替EIP的原有內(nèi)容。于是,程序就轉(zhuǎn)移到flush處了。
第53行,使用了偽指令[bits 32],從這以后,指令是按照32位編譯的。因?yàn)橹噶顖?zhí)行到這里的時候,已經(jīng)真真正正地進(jìn)入了保護(hù)模式了。
(八)進(jìn)入保護(hù)模式的主要步驟
我們總結(jié)一下進(jìn)入保護(hù)模式的主要步驟:
1.安裝段描述符,構(gòu)造GDT
2.用lgdt指令加載GDTR
3.打開A20
4.設(shè)置CR0的PE位為1
5.跳轉(zhuǎn),真正進(jìn)入保護(hù)保護(hù)模式。
(九)在屏幕上顯示字符
55 flush: 56 mov cx,00000000000_10_000B ;加載數(shù)據(jù)段選擇子(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'56、57行,前文已經(jīng)說過,令DS指向文本模式的顯示緩沖區(qū)。
60~74行,就是在屏幕左上角顯示"Protect mode OK." 需要說明的是:不管是實(shí)模式還是保護(hù)模式,外圍設(shè)備是不受影響的。
最后注意一點(diǎn):
保護(hù)模式下,不允許使用mov指令改變段寄存器CS的內(nèi)容。比如
mov cs,ax
這樣寫是不對的。這樣做會導(dǎo)致處理器產(chǎn)生一個無效操作碼的異常中斷。
與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的进入保护模式(二)——《x86汇编语言:从实模式到保护模式》读书笔记14的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: matlab 判断两个矩阵有元素相等_M
- 下一篇: 编写一个求方程ax2 + bx + c