隨便說說:
保護模式下的內存訪問模式、方法,而這個保護模式下的內存訪問模式在實模式與保護模式已經介紹過是什麼,這次來具體點看。
訪問內存
1.首先確定好哪些是屬於訪問內存?
1.1最容易確定莫過於直接對內存空間進行讀寫的指令
例如:
[bits 32]
mov [ds:esi],123 ;write
mov eax,[ds:esi] ;read
add [ds:esi],123 ;read/write
sub [ds:esi],123 ;read/write
shr [ds:esi],8 ;read/write
...
1.2代碼執行類的指令(jmp,call…)
不僅僅是數據讀寫,包括代碼的執行實際上也是對內存的讀取,因爲jmp、call這類跳轉指令,終歸就是讀取跳轉目的地的內容,再將其作爲指令執行。
2.讀寫內存過程
在實模式與保護模式(裏面講到描述符)提到過會先判斷訪問是否合法,合法在進行讀寫。
先來看這兩個表:
對於數據段分別爲(X,E,W,A)
X | E | W | A | 描述符類別 | 含義 |
---|---|---|---|---|---|
0 | 0 | 0 | x | 數據 | 只讀 |
0 | 0 | 1 | x | 數據 | 讀、寫 |
0 | 1 | 0 | x | 數據 | 只讀,向下擴展 |
0 | 1 | 1 | x | 數據 | 讀、寫,向下擴展 |
對於代碼段分別爲(X,C,R,A)
X | C | R | A | 描述符類別 | 含義 |
---|---|---|---|---|---|
1 | 0 | 0 | x | 代碼 | 只執行 |
1 | 0 | 1 | x | 代碼 | 執行、讀 |
1 | 1 | 0 | x | 代碼 | 只執行,依從的代碼段 |
1 | 1 | 1 | x | 代碼 | 執行、讀,依從的代碼段 |
合法簡單來說就是:
數據段:
1.只讀數據段只能讀,不能寫
2.讀寫的位置不能超過描述符中的界限
3.特權級符合要求
代碼段:
1.代碼段均不能寫
2.只執行的代碼不能讀
3.特權級符合要求
P.S.關於特權級的內容可以看特權級保護,其實特權級和其它屬性的作用也相差無幾
實踐:
合法的話當然按照預想中一樣成功進行,先來我們來看看不合法情況下的結果(其實這個纔是這文章的關鍵。。。)
錯誤代碼
;片1
data_selector equ 0x8
code_selector equ 0x10
mov es,ax
mov esi,[cs:gdt_address + 0x7c00 + 2]
;data descriptor 0~4GB
add esi,8
mov dword [es:esi],0x0000ffff
mov dword [es:esi + 4],1100_0000_10010010_00000000B
;code descriptor
add esi,8
mov dword [es:esi],0x7c0001ff
mov dword [es:esi + 4],0100_0000_10011010_00000000B
;set the size of GDT
mov word [cs:gdt_address + 0x7c00],23
lgdt [cs:gdt_address + 0x7c00]
in al,0x92 ;南橋芯片內的端口
or al,0000_0010B
out 0x92,al ;打開A20
cli ;中斷機制尚未工作
mov eax,cr0
or eax,1
mov cr0,eax ;設置PE位
;以下進入保護模式... ...
jmp dword 0x0010:flush ;16位的描述符選擇子:32位偏移
;清流水線並串行化處理器
;片2
;======================================
[bits 32]
;======================================
flush:
mov eax,data_selector
mov ds,ax
@a: mov eax,[cs:message]
@b: mov [ds:0x40000],eax
@c: mov ebx,[ds:0x40000]
@d: mov dword [cs:message],0x123456
;片3
message: dd 0xabcdef
gdt_address dw 0
dd 0x7e00
;---------------------------
;主引導扇區要求
times 510 - ($ -$$) db 0
db 0x55
db 0xaa
分析:
片1:
裏面創建了2個全局描述符:
1.數據段描述符:可讀寫,基址:0x0,界限:4GB,也就是可以訪問整個內存空間
2.代碼段描述符:可讀、可執行,基址:0x7c00,界限:512B
其中描述符表從0x7e00開始
片2:
有如下幾個操作:
1.讀代碼段->@a
2.寫數據段->@b:
3.讀數據段->@c:
4.寫代碼段->@d:
安裝完2個描述符後:
從圖中可看到有3個描述符,第一個是空描述符(處理器要求…)
第二個就是我們安裝的數據段描述符,從中可以看到其特性它是一個基址爲0x0,界限4GB,可讀寫的數據段
第三個是我們安裝的代碼段描述符,基址爲0x7c00,界限爲512B,可執行、可讀的32位代碼
上面顯示的信息與我們代碼中的信息一致,描述符安裝成功
圖1:
圖2:
jmp dword 0x0010:flush
上面的圖1是執行上面行代碼之前的,下圖是執行了這行代碼之後,關鍵是看圖中紅框部分,從實模式與保護模式中知道,改行代碼中的0x0010就是段選擇子,jmp指令,使得cs保存新的段,也就是正式進入了保護模式。
接下來看代碼中讀寫內存的操作:
1.讀代碼段->@a
成功讀取
2.寫數據段->@b:
成功寫入
3.讀數據段->@c:
成功讀取
4.寫代碼段->@d:
因爲不合法,寫失敗,這時會自動跳到0xf000:e05b的位置(我也不知道爲什麼,不過當執行不合法的訪問時都會自動跳轉到該處)
3.總結
容易出現內存訪問不合法地方:
1.沒有初始化段選擇器
特別是剛進入32位模式時,幾個重要的段選擇器必須初始化好,如cs,ds,ss,還有它們的偏移地址,特別是sp
2.使用jmp、call之前沒有給正確的段選擇子
這裏簡單提提jmp的幾個用法:
1.直接訪問
code_selector equ 0x8
...
section code vstart=0
start:
...
jmp code_selector:start
;or
jmp 0x8:start
start爲地址(偏移地址)
2.間接訪問
section data vstart=0
entry dd offset
dw selector
...
mov eax,data_selector
mov ds,ax
jmp far [ds:entry]
上面的jmp,先讀取該地址起始的6個字節,其中前4個字節作爲偏移地址,後2個字節爲段選擇子(由於是段間跳轉,所以用jmp far)
3.代碼段描述符初始化爲只執行,但後期出現對代碼段的讀寫
4.在保護模式中調用實模式的代碼
wrong:
...
ret
[bits 32]
call wrong
就如上面代碼中的用法,會出現和你預想完全不同的內存訪問(偏移地址的完全不同),因此哪裏使用call、jmp就調用哪裏的方法。
5.段間的call,一定要配合上retf使用
段間的遠距離call,是要壓入選擇子以及偏移地址,就是總共壓入8個字節,而普通的ret最後只會彈回4個字節。。。
+
+
+
+
+
+
+
+
+
P.S.這5個看上去也不是很大的問題,不過加上彙編,一旦出現了,還真要調試一大輪才找出問題。。。而且,彙編的調試真的真的超級麻煩。。。。。