日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

存储器的保护(一)——《x86汇编语言:从实模式到保护模式》读书笔记18

發(fā)布時間:2025/3/15 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 存储器的保护(一)——《x86汇编语言:从实模式到保护模式》读书笔记18 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

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

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

第12章的代碼如下。

1 ;代碼清單12-1 2 ;文件名:c12_mbr.asm 3 ;文件說明:硬盤主引導(dǎo)扇區(qū)代碼 4 ;創(chuàng)建日期:2011-10-27 22:52 5 6 ;設(shè)置堆棧段和棧指針 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 ;段內(nèi)起始偏移地址 19 20 ;創(chuàng)建0#描述符,它是空描述符,這是處理器的要求 21 mov dword [ebx+0x00],0x00000000 22 mov dword [ebx+0x04],0x00000000 23 24 ;創(chuàng)建1#描述符,這是一個數(shù)據(jù)段,對應(yīng)0~4GB的線性地址空間 25 mov dword [ebx+0x08],0x0000ffff ;基地址為0,段界限為0xfffff 26 mov dword [ebx+0x0c],0x00cf9200 ;粒度為4KB,存儲器段描述符 27 28 ;創(chuàng)建保護模式下初始代碼段描述符 29 mov dword [ebx+0x10],0x7c0001ff ;基地址為0x00007c00,512字節(jié) 30 mov dword [ebx+0x14],0x00409800 ;粒度為1個字節(jié),代碼段描述符 31 32 ;創(chuàng)建以上代碼段的別名描述符 33 mov dword [ebx+0x18],0x7c0001ff ;基地址為0x00007c00,512字節(jié) 34 mov dword [ebx+0x1c],0x00409200 ;粒度為1個字節(jié),數(shù)據(jù)段描述符 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 ;南橋芯片內(nèi)的端口 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 ;設(shè)置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 ;加載數(shù)據(jù)段(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 ;遍歷次數(shù)=串長度-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中存放的是源字的高字節(jié) 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.設(shè)置堆棧段和棧指針

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

第7、8兩行,你可能覺得有點怪異,但是這么寫是可以的。關(guān)于原因,作者已經(jīng)在書中說明了。

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

以上代碼每一行的注釋是指令編譯后產(chǎn)生的機器碼。

對于某些老式的編譯器,在編譯“mov ds,ax”這條指令時,16位和32位的編譯結(jié)果是不同的:在32位模式下,會添加前綴0x66(因為編譯器認為源操作數(shù)AX是16位的,所以要添加0x66以反轉(zhuǎn)默認操作數(shù)的大小)。

但是,如果添加了0x66,處理器在執(zhí)行時就會多花去一個時鐘周期,這樣的指令又用得很頻繁,所以不管是16位還是32位模式,它們被設(shè)計為相同的機器指令,都是8ED8,不需要指令前綴。可是某些編譯器太固執(zhí)了,它們依然會加上指令前綴0x66. 好吧,為了照顧它們,程序員想出了一個辦法,就是用這樣的形式:

mov ds,eax

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

說到這里,NASM編譯器還是非常優(yōu)秀的,至少他不會那么固執(zhí)。不管處理器模式怎么變化,也不管指令形式如何,以下代碼編譯后都是一個結(jié)果:

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

說了這么多,其實我就是把作者講的內(nèi)容又講了一遍。不管你理解了沒有,反正我是有點糊涂了。

因為剛開始的這段代碼,是在16位模式下執(zhí)行的,編譯也是按照16位來編譯的,所以按照16位的寫法就可以了。以下這樣寫,簡單明了。

7 mov ax,cs 8 mov ss,ax

反匯編后,生成的機器碼如下:

可是,如果按照配書程序,那么反匯編后成了:

看到了嗎,第一行多了前綴0x66,執(zhí)行時會多用掉一個指令周期。

我個人認為,寫代碼用通俗的寫法就好,能讓人看懂的代碼才是好代碼。OK,這個問題就到這里,我們繼續(xù)。

2.創(chuàng)建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 ;段內(nèi)起始偏移地址 106;------------------------------------------------------------------------------- 107 pgdt dw 0 108 dd 0x00007e00 ;GDT的物理地址 109;------------------------------------------------------------------------------- 第12行,就是把GDT的物理地址0x7e00傳送到EAX,至于為什么給標號pgdt加上(0x7c00+0x02),相信你已經(jīng)明白了,如果不明白,看看我的圖。

第13行到15行,其實是做除法運算,把物理地址分解為段地址和偏移地址:?? EDX:EAX / 16 = EAX(得到段地址) …EDX(得到偏移地址)

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

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

第20~30行分別創(chuàng)建了3個描述符,相信大家都很熟悉了。需要說明的是33~34行,創(chuàng)建了一個代碼段的別名描述符。這樣做用意何在呢?

在保護模式下,代碼段是不可寫入的,所謂不可寫入不是說改變了內(nèi)存的物理性質(zhì),使內(nèi)存寫不進去,而是說通過代碼段描述符訪問對應(yīng)的內(nèi)存區(qū)域時,處理器不允許向里面寫數(shù)據(jù)或者更改數(shù)據(jù)。

但是,如果非要修改代碼段,有沒有辦法呢?有,那就是為該代碼段建立一個新描述符,比如說可讀可寫的數(shù)據(jù)段描述符,這樣,通過這個數(shù)據(jù)段描述符,我們就可以堂而皇之地修改代碼段了。像這樣,當兩個或以上的描述符都指向同一個段時,把另外的那些描述符就成為別名描述符。

3.棧操作時的保護

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

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

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

有效界限(effective limit)

段的有效界限取決于G標志。

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

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

請牢記這個概念,因為我們會多次用到。

對于下擴(E=1)數(shù)據(jù)段,有效界限指定了段中最后一個不允許訪問的偏移地址。

B=0:偏移地址的有效范圍是 [有效界限+1,0xFFFF] ,為了敘述方便,這里用閉區(qū)間表示。

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

如果要想訪問向下擴展的棧段,那么SP或者ESP的值必須要在偏移地址的有效范圍內(nèi)。

結(jié)合本文的代碼,seg_base = 0X7C00,seg_limit = 0XFFFFE,G = 1,于是有效界限是

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

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

假設(shè)ESP的初始值為0,這時候執(zhí)行 push eax, 請問合法嗎?

分析:ESP先減去4,等于0xFFFF_FFFC,然后(假如合法)EAX的值會被寫入 偏移為 0xFFFF_FFFC~0xFFFF_FFFF的四個存儲單元,因為這些偏移值在有效范圍內(nèi),所以沒有問題。

假設(shè)ESP的初始值為1,這時候執(zhí)行push eax, 請問合法嗎?

分析:ESP先減去4,等于0xFFFF_FFFD,然后(假如合法)EAX的值會被寫入 偏移為 0xFFFF_FFFD~0xFFFF_FFFF,0x0000_0000的四個存儲單元,因為偏移0不在有效范圍內(nèi),所以會引發(fā)異常。
在Bochs中模擬這種情況,我們發(fā)現(xiàn)CPU重啟了。

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

假設(shè)ESP的初始值為0xFFFF_FFFC,這時候執(zhí)行 pop eax, 請問合法嗎?

分析:如果合法,那么偏移為 0xFFFF_FFFC~0xFFFF_FFFF的四個存儲單元中的內(nèi)容會傳送到eax,之后ESP+4=0;顯然0xFFFF_FFFC~0xFFFF_FFFF是有效的偏移,所以允許執(zhí)行。如下圖:

假設(shè)ESP的初始值為0xFFFF_FFFD,這時候執(zhí)行 pop eax, 請問合法嗎?

分析:如果合法,那么偏移為 0xFFFF_FFFD~0xFFFF_FFFF,0x0000_0000的四個存儲單元中的內(nèi)容會傳送到eax,之后ESP+4=1;顯然其中0不是有效的偏移,所以不允許執(zhí)行。如下圖:

再回到我們的代碼,因為ESP僅提供偏移地址,真正的物理地址 = 偏移地址 + 段基地址;所以,對于本代碼中的棧,結(jié)合段基地址= 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 ;加載數(shù)據(jù)段(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;同樣,會修改寄存器的指令還出現(xiàn)在58~68行(粗體部分)。

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

(1)檢查索引號

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

如果不符合要求,則產(chǎn)生異常13,同時段寄存器中的原值不變。

(2)檢查描述符的類別

原書表12-1,我在這里繪制一份。

Y:表示允許

N:表示不允許

舉例:SS只允許加載可讀寫的數(shù)據(jù)段。

另外,還需要注意:

  • 代碼段在任何時候都是不可寫的
  • 對于DS,ES,FS,GS,可以向其加載數(shù)值為0的選擇子(但是訪問時會導(dǎo)致異常)
  • 對于CS和SS,不允許向其傳送數(shù)值為0的選擇子

(3)檢查P位

如果P=0,表示描述符指向的段并不存在于物理內(nèi)存中。此時,處理器中止處理,引發(fā)異常。

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

?

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

總結(jié)

以上是生活随笔為你收集整理的存储器的保护(一)——《x86汇编语言:从实模式到保护模式》读书笔记18的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。