存儲器的保護(一)——《x86彙編語言:從實模式到保護模式》讀書筆記18

本文是原書第12章的學習筆記。程序員

說句題外話,這篇博文是補寫的,由於讓我誤刪了,可惡的是CSDN的回收站裏找不到!哭泣的臉哭泣的臉 好吧,那就再寫一遍,我有堅強的意志。司馬遷曰:「文王拘而演《周易》;仲尼厄而做《春秋》;屈原放逐,乃賦《離騷》;左丘失明,厥有《國語》;孫子臏腳,《兵法》修列;不韋遷蜀,世傳《呂覽》……」好了,不煽情了,進入正題。小程序

第12章的代碼以下。緩存

1         ;代碼清單12-1
2         ;文件名:c12_mbr.asm
3         ;文件說明:硬盤主引導扇區代碼 
4         ;建立日期:2011-10-27 22:52
5
6         ;設置堆棧段和棧指針 
7         mov eax,cs      
8         mov ss,eax
9         mov sp,0x7c00
10      
11         ;計算GDT所在的邏輯段地址
12         mov eax,[cs:pgdt+0x7c00+0x02]      ;GDT的32位線性基地址 
13         xor edx,edx
14         mov ebx,16
15         div ebx                            ;分解成16位邏輯地址 
16
17         mov ds,eax                         ;令DS指向該段以進行操做
18         mov ebx,edx                        ;段內起始偏移地址 
19
20         ;建立0#描述符,它是空描述符,這是處理器的要求
21         mov dword [ebx+0x00],0x00000000
22         mov dword [ebx+0x04],0x00000000  
23
24         ;建立1#描述符,這是一個數據段,對應0~4GB的線性地址空間
25         mov dword [ebx+0x08],0x0000ffff    ;基地址爲0,段界限爲0xfffff
26         mov dword [ebx+0x0c],0x00cf9200    ;粒度爲4KB,存儲器段描述符 
27
28         ;建立保護模式下初始代碼段描述符
29         mov dword [ebx+0x10],0x7c0001ff    ;基地址爲0x00007c00,512字節 
30         mov dword [ebx+0x14],0x00409800    ;粒度爲1個字節,代碼段描述符 
31
32         ;建立以上代碼段的別名描述符
33         mov dword [ebx+0x18],0x7c0001ff    ;基地址爲0x00007c00,512字節
34         mov dword [ebx+0x1c],0x00409200    ;粒度爲1個字節,數據段描述符
35
36         mov dword [ebx+0x20],0x7c00fffe
37         mov dword [ebx+0x24],0x00cf9600
38         
39         ;初始化描述符表寄存器GDTR
40         mov word [cs: pgdt+0x7c00],39      ;描述符表的界限   
41 
42         lgdt [cs: pgdt+0x7c00]
43      
44         in al,0x92                         ;南橋芯片內的端口 
45         or al,0000_0010B
46         out 0x92,al                        ;打開A20
47
48         cli                                ;中斷機制還沒有工做
49
50         mov eax,cr0
51         or eax,1
52         mov cr0,eax                        ;設置PE位
53      
54         ;如下進入保護模式... ...
55         jmp dword 0x0010:flush             ;16位的描述符選擇子:32位偏移
56                                             
57         [bits 32]                          
58  flush:                                     
59         mov eax,0x0018                      
60         mov ds,eax
61      
62         mov eax,0x0008                     ;加載數據段(0..4GB)選擇子
63         mov es,eax
64         mov fs,eax
65         mov gs,eax
66      
67         mov eax,0x0020                     ;0000 0000 0010 0000
68         mov ss,eax
69         xor esp,esp                        ;ESP <- 0
70      
71         mov dword [es:0x0b8000],0x072e0750 ;字符'P'、'.'及其顯示屬性
72         mov dword [es:0x0b8004],0x072e074d ;字符'M'、'.'及其顯示屬性
73         mov dword [es:0x0b8008],0x07200720 ;兩個空白字符及其顯示屬性
74         mov dword [es:0x0b800c],0x076b076f ;字符'o'、'k'及其顯示屬性
75
76         ;開始冒泡排序 
77         mov ecx,pgdt-string-1              ;遍歷次數=串長度-1 
78  @@1:
79         push ecx                           ;32位模式下的loop使用ecx 
80         xor bx,bx                          ;32位模式下,偏移量能夠是16位,也能夠 
81  @@2:                                      ;是後面的32位 
82         mov ax,[string+bx] 
83         cmp ah,al                          ;ah中存放的是源字的高字節 
84         jge @@3 
85         xchg al,ah 
86         mov [string+bx],ax 
87  @@3:
88         inc bx 
89         loop @@2 
90         pop ecx 
91         loop @@1
92      
93         mov ecx,pgdt-string
94         xor ebx,ebx                        ;偏移地址是32位的狀況 
95  @@4:                                      ;32位的偏移具備更大的靈活性
96         mov ah,0x07
97         mov al,[string+ebx]
98         mov [es:0xb80a0+ebx*2],ax          ;演示0~4GB尋址。
99         inc ebx
100         loop @@4
101      
102         hlt 
103
104;-------------------------------------------------------------------------------
105     string           db 's0ke4or92xap3fv8giuzjcy5l1m7hd6bnqtw.'
106;-------------------------------------------------------------------------------
107     pgdt             dw 0
108                      dd 0x00007e00      ;GDT的物理地址
109;-------------------------------------------------------------------------------                             
110     times 510-($-$$) db 0
111                      db 0x55,0xaa

1.設置堆棧段和棧指針

6         ;設置堆棧段和棧指針 
7         mov eax,cs      
8         mov ss,eax
9         mov sp,0x7c00

第七、8兩行,你可能以爲有點怪異,可是這麼寫是能夠的。關於緣由,做者已經在書中說明了。oop

[bits 16]
mov ds,ax        ;8E D8

[bits 32]
mov ds,ax        ;66 8E D8

mov ds,eax       ;8E D8

以上代碼每一行的註釋是指令編譯後產生的機器碼。學習

對於某些老式的編譯器,在編譯「mov ds,ax」這條指令時,16位和32位的編譯結果是不一樣的:在32位模式下,會添加前綴0x66(由於編譯器認爲源操做數AX是16位的,因此要添加0x66以反轉默認操做數的大小)。spa

可是,若是添加了0x66,處理器在執行時就會多花去一個時鐘週期,這樣的指令又用得很頻繁,因此不論是16位仍是32位模式,它們被設計爲相同的機器指令,都是8ED8,不須要指令前綴。但是某些編譯器太執拗了,它們依然會加上指令前綴0x66. 好吧,爲了照顧它們,程序員想出了一個辦法,就是用這樣的形式:.net

mov ds,eax

你別說,還真的有效,果真生成了不加前綴的8ED8!設計

說到這裏,NASM編譯器仍是很是優秀的,至少他不會那麼執拗。無論處理器模式怎麼變化,也無論指令形式如何,如下代碼編譯後都是一個結果:3d

[bits 16]
mov ds,ax       ;8E D8
mov ds,eax      ;8E D8

[bits 32]
mov ds,ax        ;8E D8
mov ds,eax       ;8E D8

說了這麼多,其實我就是把做者講的內容又講了一遍。無論你理解了沒有,反正我是有點糊塗了。指針

由於剛開始的這段代碼,是在16位模式下執行的,編譯也是按照16位來編譯的,因此按照16位的寫法就能夠了。如下這樣寫,簡單明瞭。

7         mov ax,cs      
8         mov ss,ax

反彙編後,生成的機器碼以下:

但是,若是按照配書程序,那麼反彙編後成了:

看到了嗎,第一行多了前綴0x66,執行時會多用掉一個指令週期。

我我的認爲,寫代碼用通俗的寫法就好,能讓人看懂的代碼纔是好代碼。OK,這個問題就到這裏,咱們繼續。

2.建立GDT

11         ;計算GDT所在的邏輯段地址
12         mov eax,[cs:pgdt+0x7c00+0x02]      ;GDT的32位線性基地址 
13         xor edx,edx
14         mov ebx,16
15         div ebx                            ;分解成16位邏輯地址 
16
17         mov ds,eax                         ;令DS指向該段以進行操做
18         mov ebx,edx                        ;段內起始偏移地址
106;-------------------------------------------------------------------------------
107     pgdt             dw 0
108                      dd 0x00007e00      ;GDT的物理地址
109;-------------------------------------------------------------------------------

第12行,就是把GDT的物理地址0x7e00傳送到EAX,至於爲何給標號pgdt加上(0x7c00+0x02),相信你已經明白了,若是不明白,看看個人圖。

 

第13行到15行,實際上是作除法運算,把物理地址分解爲段地址和偏移地址: EDX:EAX / 16 = EAX(獲得段地址) …EDX(獲得偏移地址)

第17到18行,DS:EBX就指向了GDT的開頭。

20         ;建立0#描述符,它是空描述符,這是處理器的要求
21         mov dword [ebx+0x00],0x00000000
22         mov dword [ebx+0x04],0x00000000  
23
24         ;建立1#描述符,這是一個數據段,對應0~4GB的線性地址空間
25         mov dword [ebx+0x08],0x0000ffff    ;基地址爲0,段界限爲0xfffff
26         mov dword [ebx+0x0c],0x00cf9200    ;粒度爲4KB,存儲器段描述符 
27
28         ;建立2#描述符,保護模式下初始代碼段描述符
29         mov dword [ebx+0x10],0x7c0001ff    ;基地址爲0x00007c00,512字節 
30         mov dword [ebx+0x14],0x00409800    ;粒度爲1個字節,代碼段描述符 
31
32         ;建立3#描述符,上面代碼段的別名描述符
33         mov dword [ebx+0x18],0x7c0001ff    ;基地址爲0x00007c00,512字節
34         mov dword [ebx+0x1c],0x00409200    ;粒度爲1個字節,數據段描述符

第20~30行分別建立了3個描述符,相信你們都很熟悉了。須要說明的是33~34行,建立了一個代碼段的別名描述符。這樣作用意何在呢?

在保護模式下,代碼段是不可寫入的,所謂不可寫入不是說改變了內存的物理性質,使內存寫不進去,而是說經過代碼段描述符訪問對應的內存區域時,處理器不容許向裏面寫數據或者更改數據。

可是,若是非要修改代碼段,有沒有辦法呢?有,那就是爲該代碼段創建一個新描述符,好比說可讀可寫的數據段描述符,這樣,經過這個數據段描述符,咱們就能夠冠冕堂皇地修改代碼段了。像這樣,當兩個或以上的描述符都指向同一個段時,把另外的那些描述符就成爲別名描述符。

3.棧操做時的保護

36         mov dword [ebx+0x20],0x7c00fffe
37         mov dword [ebx+0x24],0x00cf9600

第3六、37行安裝了棧段描述符。用咱們的小程序分析一下(參見數據段描述符和代碼段描述符(二)——《x86彙編語言:從實模式到保護模式》讀書筆記11),結果是:

-----------------------
seg_base = 0X7C00
seg_limit = 0XFFFFE
S = 1
DPL = 0
G = 1
D/B = 1
TYPE = 6
數據段: 向下擴展,可讀可寫
------------------------
得知,基地址是0x7c00,描述符中的界限值是0xFFFFE,G=1,是向下擴展的可讀寫數據段(通常做爲棧段)。

有效界限(effective limit)

段的有效界限取決於G標誌。

G=0:有效界限就是描述符中的界限值

G=1:有效界限 = 描述符中的段界限值* 0x1000 + 0xFFF

請牢記這個概念,由於咱們會屢次用到。

對於下擴(E=1)數據段,有效界限指定了段中最後一個不容許訪問的偏移地址。

B=0:偏移地址的有效範圍是 [有效界限+1,0xFFFF] ,爲了敘述方便,這裏用閉區間表示。

B=1:偏移地址的有效範圍是 [有效界限+1,0xFFFF_FFFF]

若是要想訪問向下擴展的棧段,那麼SP或者ESP的值必需要在偏移地址的有效範圍內。

結合本文的代碼,seg_base = 0X7C00,seg_limit = 0XFFFFE,G = 1,因而有效界限是

0xFFFFE * 0x1000 + 0xFFF = 0xFFFF_EFFF;

那麼偏移地址的有效範圍是 [ 0xFFFF_F000, 0xFFFF_FFFF]

假設ESP的初始值爲0,這時候執行 push eax, 請問合法嗎?

分析:ESP先減去4,等於0xFFFF_FFFC,而後(假如合法)EAX的值會被寫入 偏移爲 0xFFFF_FFFC~0xFFFF_FFFF的四個存儲單元,由於這些偏移值在有效範圍內,因此沒有問題。

假設ESP的初始值爲1,這時候執行push eax, 請問合法嗎?

分析:ESP先減去4,等於0xFFFF_FFFD,而後(假如合法)EAX的值會被寫入 偏移爲 0xFFFF_FFFD~0xFFFF_FFFF,0x0000_0000的四個存儲單元,由於偏移0不在有效範圍內,因此會引起異常。
在Bochs中模擬這種狀況,咱們發現CPU重啓了。

對於POP指令,也是這個道理。

假設ESP的初始值爲0xFFFF_FFFC,這時候執行 pop eax, 請問合法嗎?

分析:若是合法,那麼偏移爲 0xFFFF_FFFC~0xFFFF_FFFF的四個存儲單元中的內容會傳送到eax,以後ESP+4=0;顯然0xFFFF_FFFC~0xFFFF_FFFF是有效的偏移,因此容許執行。以下圖:

假設ESP的初始值爲0xFFFF_FFFD,這時候執行 pop eax, 請問合法嗎?

分析:若是合法,那麼偏移爲 0xFFFF_FFFD~0xFFFF_FFFF,0x0000_0000的四個存儲單元中的內容會傳送到eax,以後ESP+4=1;顯然其中0不是有效的偏移,因此不容許執行。以下圖:

再回到咱們的代碼,由於ESP僅提供偏移地址,真正的物理地址 = 偏移地址 + 段基地址;因此,對於本代碼中的棧,結合段基地址= 0x7c00,有效偏移地址= [ 0xFFFF_F000, 0xFFFF_FFFF],因此

最低端有效物理地址 = 0x7c00 + 0xFFFF_F000 = 0x6c00(進位被丟棄)

最高端有效物理地址 = 0x7c00 + 0xFFFF_FFFF = 0x7BFF (進位被丟棄)

也就是說,當前程序定義的棧空間介於物理地址0x6c00~0x7bff 之間。大小爲(0x7BFF- 0x6C00 + 0x01 =0x1000 )4KB;

4.修改段寄存器時的保護

54         ;如下進入保護模式... ...
55         jmp dword 0x0010:flush             ;16位的描述符選擇子:32位偏移
57         [bits 32]                          
58  flush:                                     
59         mov eax,0x0018                      
60         mov ds,eax
61      
62         mov eax,0x0008                     ;加載數據段(0..4GB)選擇子
63         mov es,eax
64         mov fs,eax
65         mov gs,eax
66      
67         mov eax,0x0020                     ;0000 0000 0010 0000
68         mov ss,eax
69         xor esp,esp                        ;ESP <- 0

 

第55行,這條指令會隱式地修改CS;一樣,會修改寄存器的指令還出如今58~68行(粗體部分)。

以上的指令涉及全部的段寄存器,當這些指令執行時,處理器把指令中給出的選擇子傳送到段寄存器的選擇器部分(就是16位可見部分)。可是,處理器的固件在完成傳送以前,會進行以下檢查:

(1)檢查索引號

要求:段選擇子中的描述符索引 * 8 + 7 <= GDT(或LDT)的界限值

若是不符合要求,則產生異常13,同時段寄存器中的原值不變。

(2)檢查描述符的類別

原書表12-1,我在這裏繪製一份。

Y:表示容許

N:表示不容許

舉例:SS只容許加載可讀寫的數據段。

另外,還須要注意:

  • 代碼段在任什麼時候候都是不可寫的
  • 對於DS,ES,FS,GS,能夠向其加載數值爲0的選擇子(可是訪問時會致使異常)
  • 對於CS和SS,不容許向其傳送數值爲0的選擇子

(3)檢查P位

若是P=0,表示描述符指向的段並不存在於物理內存中。此時,處理器停止處理,引起異常。

若是P=1,則處理器將段描述符加載到描述符高速緩存寄存器,同時置A位(僅限於當前討論的存儲器段描述符)

 

本博文的內容就到這裏。第12章餘下的內容,請參考存儲器的保護(二)——《x86彙編語言:從實模式到保護模式》讀書筆記19

相關文章
相關標籤/搜索