16位模式/32位模式下PUSH指令探究——《x86彙編語言:從實模式到保護模式》讀書筆記16

1、Intel 32 位處理器的工做模式

New0004ia-32工做模式

如上圖所示,Intel 32 位處理器有3種工做模式。編程

(1)實模式:工做方式至關於一個8086oop

(2)保護模式:提供支持多任務環境的工做方式,創建保護機制測試

(3)虛擬8086模式:這種方式可使用戶在保護模式下運行8086程序(好比cmd打開的console窗口,就是工做在虛擬8086模式)spa

有幾點須要特別說明:.net

(1)保護模式可分爲16位和32位的,由段描述符中的D標誌指明。對於32位代碼段和數據段,這個標誌老是設爲1;對於16位代碼和數據段,這個標誌被設置爲0.指針

D=1:默認使用32位地址和32位或8位的操做數。調試

D=0:默認使用16位地址和16位或8位的操做數。(主要是爲了可以在32位處理器上運行16位保護模式的程序)code

指令前綴0x66用來選擇非默認值得操做數大小,0x67用來選擇非默認值的地址大小。blog

(2)在實模式下,也可使用32位的寄存器,好比內存

mov eax,ecx
mov ebx,0x12345678

(3)在書中,把實模式和16位的保護模式統稱爲「16位模式」;把32位保護模式稱爲「32位模式」。個人博文也沿用這種叫法。

(4)32位處理器能夠執行16位的程序,包括實模式和16位保護模式。

(5)當處理器在16位模式下運行時,可使用32位的寄存器,執行32位運算。

(6)在16位模式下,數據的大小是8位或者16位的;控制轉移和內存訪問時,偏移量也是16位的。

(7)32位保護模式兼容80286的16位保護模式。

(8)在16位模式下,處理器把全部指令都當作是16位的。

結合(5)和(8),咱們發現一個問題:當處理器運行16位模式下,既然把全部指令都當作16位的,那麼怎麼使用32位的寄存器,執行32位的運算呢?答案是利用指令前綴0x66和0x67.前面已經說過,指令前綴0x66用來選擇非默認值的操做數大小,0x67用來選擇非默認值的地址大小。

好比說,指令碼0x40在16位模式下對應的指令是

inc ax

若是加上前綴0x66,也就是指令碼66 40,當處理器在16位模式下運行,66 40對應的指令是

inc eax

同理,若是處理器運行在32位模式下,處理器認爲指令是32位的,若是加了0x66,那麼就表示指令的操做數是16位的。

在編寫程序的時候,咱們應該考慮指令的運行環境。爲了指令默認的運行環境,NASM提供了僞指令bits,用於指明其後的指令是被編譯成16位的仍是32位的。好比:

[bits 16] 
mov cx,dx      ;89 D1 
mov eax,ebx    ;66 89 D8 
 
[bits 32] 
mov cx,dx      ;66 89 D1 
mov eax,ebx    ;89 D8

注意,[bits 16]和[bits 32]的方括號是能夠省略的。

2、PUSH指令探究

因爲32位的處理器都擁有32位的寄存器和算術邏輯部件,並且同內存芯片之間的數據通路至少是32位的,所以,全部以寄存器或者內存單元爲操做數的指令都被擴充,以適應32位的算術邏輯操做。並且,這些擴展的操做即便是在16位模式下(實模式和16位保護模式)也是可用的。

我在博文 32位x86處理器編程導入——《x86彙編語言:從實模式到保護模式》讀書筆記08 中已經總結了通常指令的擴展,在這裏,我僅對PUSH指令進行實驗和總結。

實驗目的就是測試在3種模式下,PUSH指令的工做行爲(好比SP或ESP到底怎麼變化,壓入的數究竟是多少)。因此,我列了一個單子,把全部能想到的形式都列出來了,其中有的我也不肯定(或許這樣寫編譯都會報錯)。無論那麼多,先寫出來,而後讓編譯器篩選吧眨眼

1    ;測試各類push
2
3    ;操做數是當即數,分爲一字節、兩字節、四字節
4        push 0x80
5        push byte 0x80 
6
7        push 0x8000
8        push word 0x8000
9        
10       push 0x87654321
11       push dword 0x87654321
12
13    ;操做數是寄存器,分爲16位寄存器和32位寄存器    
14       mov eax,0x86421357
15       push ax
16       push eax
17
18    ;操做數是內存單元,分爲一字節、兩字節、四字節         
19       push [data]
20       push byte [data]
21       push word [data]
22       push dword [data]

是否是有的寫法明顯就不對呢?

首先,第20行,確定不對。由於若是是內存操做數的話,不能用byte修飾。剩下來的錯誤,我會在後文揭曉答案。

1.在實模式下的實驗

(1)實驗代碼

1             ;PUSH 指令實驗
2             
3             jmp near start
4        
5     data db 0x12,0x34,0x56,0x78
6     message db 'Hello,PUSH!'
7            
8     start:
9             mov ax,0x7c0           ;設置數據段的段基地址 
10             mov ds,ax
11    
12             mov ax,0xb800          ;設置附加段基址到顯示緩衝區
13             mov es,ax
14    
15             ;如下顯示字符串 
16             mov si,message          
17             mov di,0
18             mov cx,start-message
19         @g:
20             mov al,[si]
21             mov [es:di],al
22             inc di
23             mov byte [es:di],0x02
24             inc di
25             inc si
26             loop @g
27    
28             ;測試各類push
29             push 0x80
30             push byte 0x80
31             
32             push 0x8000
33             push word 0x8000
34             
35             push 0x87654321
36             push dword 0x87654321
37             
38             mov eax,0x86421357
39             push ax
40             push eax
41             
42             ;push [data]
43             push word [data]
44             push dword [data]
45             
46             push ds
47             push gs
48           
49             jmp near $ 
50           
51    
52    times 510-($-$$) db 0
53                     db 0x55,0xaa

這段代碼不是用的配書代碼,是我本身寫的。

第5行,定義了4字節的數據,這是爲了後面驗證「push + 內存操做數」這一狀況。

第6行,定義了一個字符串,要把它顯示在屏幕上。這樣作是爲了調試方便,讓咱們知道咱們的程序已經RUN了。

第29行到47行,測試各類push,我會利用Bochs的調試功能,跟蹤每條Push的執行狀況,把結果總結出來。

好的,咱們開始編譯吧。

對於30行,有個警告:

push byte 0x80 ;warning: signed byte value exceeds bounds
既然是警告,那麼30行沒必要去掉。相反咱們更加好奇了,看看執行時會發生什麼。

對於35行,仍是一個警告:

push 0x87654321 ;warning: word data exceeds bounds

對於42行,呵呵,就是一個錯誤了。

push [data]  ; error: operation size not specified

好吧,看來這樣不指定操做數的大小是不行的,因此咱們把42行註釋掉。

而後再編譯,好的,能夠了。

調試的過程就是不斷用n命令,反覆用print-stack命令,還有reg命令等,仔細觀察棧的變化和SP的變化。(此處省略2000字)

(2)實驗報告

小二,上實驗報告!

New0005實模式_push

經過上面的實驗,咱們能夠知道,若是CPU運行在實模式,若是用NASM編譯,push指令能夠這麼用:

New0006PUSH_指令用法總結(實模式)

2.在16位保護模式下的實驗

(1)關於16位保護模式

請參考個人博文 關於80286——《x86彙編語言:從實模式到保護模式》讀書筆記15

(2)實驗代碼

實驗代碼由配書代碼(代碼清單11-1 (文件名:c11_mbr.asm))修改而成。目的就是咱們要從實模式進入16位的保護模式,而後測試16位保護模式下PUSH指令的行爲。

1         ;test push (16位保護模式下)
2
3         ;設置堆棧段和棧指針 
4         mov ax,cs      
5         mov ss,ax
6         mov sp,0x7c00
7      
8         ;計算GDT所在的邏輯段地址 
9          mov ax,[cs:gdt_base+0x7c00]        ;低16位 
10         mov dx,[cs:gdt_base+0x7c00+0x02]   ;高16位 
11         mov bx,16        
12         div bx            
13         mov ds,ax                          ;令DS指向該段以進行操做
14         mov bx,dx                          ;段內起始偏移地址 
15      
16         ;建立0#描述符,它是空描述符,這是處理器的要求
17         mov dword [bx+0x00],0x00
18         mov dword [bx+0x04],0x00  
19
20         ;建立#1描述符,保護模式下的代碼段描述符
21         mov dword [bx+0x08],0x7c0001ff     
22         mov dword [bx+0x0c],0x00009800     
23
24         ;建立#2描述符,保護模式下的數據段描述符(文本模式下的顯示緩衝區) 
25         mov dword [bx+0x10],0x8000ffff     
26         mov dword [bx+0x14],0x0000920b     
27
28         ;建立#3描述符,保護模式下的堆棧段描述符
29         mov dword [bx+0x18],0x00007a00
30         mov dword [bx+0x1c],0x00009600
31
32         ;初始化描述符表寄存器GDTR
33         mov word [cs: gdt_size+0x7c00],31  ;描述符表的界限(總字節數減一)   
34                                             
35         lgdt [cs: gdt_size+0x7c00]
36      
37         in al,0x92                         ;南橋芯片內的端口 
38         or al,0000_0010B
39         out 0x92,al                        ;打開A20
40
41         cli                                ;保護模式下中斷機制還沒有創建,應 
42                                            ;禁止中斷 
43         mov eax,cr0
44         or eax,1
45         mov cr0,eax                        ;設置PE位
46      
47         ;如下進入保護模式... ...
48         jmp  0x0008:flush                  ;描述符選擇子:16位偏移
49                                            ;清流水線並串行化處理器 
50          
51
52    flush:
53         mov cx,00000000000_10_000B         ;加載數據段選擇子(0x10)
54         mov ds,cx
55
56         ;如下在屏幕上顯示"ABCDEFGHIJK" 
57         mov byte [0x00],'A'  
58         mov byte [0x02],'B'
59         mov byte [0x04],'C'
60         mov byte [0x06],'D'
61         mov byte [0x08],'E'
62         mov byte [0x0a],'F'
63         mov byte [0x0c],'G'
64         mov byte [0x0e],'H'
65         mov byte [0x10],'I'
66         mov byte [0x12],'J'
67         mov byte [0x14],'K'
68         
69
70         ;測試push
71         mov cx,00000000000_11_000B         ;加載堆棧段選擇子
72         mov ss,cx
73         mov sp,0x7c00
74
75         
76           push 0x80
77           push byte 0x80  ; warning: signed byte value exceeds bounds
78           
79           push 0x8000
80           push word 0x8000
81           
82           push 0x87654321
83           ;warning: word data exceeds bounds
84           push dword 0x87654321
85         
86           
87           mov eax,0x86421357
88           push ax
89           push eax
90           
91           ;push [0x00]error: operation size not specified
92           push byte [0x00]
93           push word [0x00]
94         
95           push dword [0x00]
96           
97           push ds
98           push gs
99           push es
100          push cs
101      
102  ghalt:     
103         hlt                                ;已經禁止中斷,將不會被喚醒 
104
105;-------------------------------------------------------------------------------
106     
107         gdt_size         dw 0
108         gdt_base         dd 0x00007e00     ;GDT的物理地址 
109                             
110         times 510-($-$$) db 0
111                          db 0x55,0xaa

對比32位保護模式的代碼,就會發現16位保護模式的代碼略有不一樣。

首先,好比說22行,段描述符的定義是

22         mov dword [bx+0x0c],0x00009800

由於80286中,段描述符的格式是

New000780286段寄存器

因此,高4字節的16~32位所有爲0.

其次,

47         ;如下進入保護模式... ...
48         jmp  0x0008:flush                  ;描述符選擇子:16位偏移
49                                            ;清流水線並串行化處理器

這裏,沒有加僞指令[bits 32],並且,偏移flush沒有用dword修飾。由於操做數和偏移是16位的。

好了,代碼就說到這裏,咱們看實驗報告吧。

(3)實驗報告

New000816位保護模式下push

經過和實模式的對比,能夠發現,除了九、10兩行中的指令碼的偏移不同(這和數據存放的位置有關係,和PUSH沒有關係),PUSH指令的行爲是驚人的相同。因此咱們能夠得出結論,16位保護模式下,PUSH的用法和實模式是同樣的。我想,這也是在原書中,做者把實模式和16位的保護模式統稱爲「16位模式」,把32位保護模式稱爲「32位模式」的緣由吧。

3.在32位保護模式下的實驗

(1)實驗代碼

實驗代碼由配書代碼(代碼清單11-1 (文件名:c11_mbr.asm))修改而成。

1         ;test push (32位保護模式)
2
3         ;設置堆棧段和棧指針 
4         mov ax,cs      
5         mov ss,ax
6         mov sp,0x7c00
7      
8         ;計算GDT所在的邏輯段地址 
9          mov ax,[cs:gdt_base+0x7c00]        ;低16位 
10         mov dx,[cs:gdt_base+0x7c00+0x02]   ;高16位 
11         mov bx,16        
12         div bx            
13         mov ds,ax                          ;令DS指向該段以進行操做
14         mov bx,dx                          ;段內起始偏移地址 
15      
16         ;建立0#描述符,它是空描述符,這是處理器的要求
17         mov dword [bx+0x00],0x00
18         mov dword [bx+0x04],0x00  
19
20         ;建立#1描述符,保護模式下的代碼段描述符
21         mov dword [bx+0x08],0x7c0001ff     
22         mov dword [bx+0x0c],0x00409800     
23
24         ;建立#2描述符,保護模式下的數據段描述符(文本模式下的顯示緩衝區) 
25         mov dword [bx+0x10],0x8000ffff     
26         mov dword [bx+0x14],0x0040920b     
27
28         ;建立#3描述符,保護模式下的堆棧段描述符
29         mov dword [bx+0x18],0x00007a00
30         mov dword [bx+0x1c],0x00409600
31
32         ;初始化描述符表寄存器GDTR
33         mov word [cs: gdt_size+0x7c00],31  ;描述符表的界限(總字節數減一)   
34                                             
35         lgdt [cs: gdt_size+0x7c00]
36      
37         in al,0x92                         ;南橋芯片內的端口 
38         or al,0000_0010B
39         out 0x92,al                        ;打開A20
40
41         cli                                ;保護模式下中斷機制還沒有創建,應 
42                                            ;禁止中斷 
43         mov eax,cr0
44         or eax,1
45         mov cr0,eax                        ;設置PE位
46      
47         ;如下進入保護模式... ...
48         jmp dword 0x0008:flush             ;16位的描述符選擇子:32位偏移
49                                            ;清流水線並串行化處理器 
50         [bits 32] 
51
52    flush:
53         mov cx,00000000000_10_000B         ;加載數據段選擇子(0x10)
54         mov ds,cx
55
56         ;如下在屏幕上顯示"ABCDEFGHIJK" 
57         mov byte [0x00],'A'  
58         mov byte [0x02],'B'
59         mov byte [0x04],'C'
60         mov byte [0x06],'D'
61         mov byte [0x08],'E'
62         mov byte [0x0a],'F'
63         mov byte [0x0c],'G'
64         mov byte [0x0e],'H'
65         mov byte [0x10],'I'
66         mov byte [0x12],'J'
67         mov byte [0x14],'K'
68         
69
70         ;測試push
71         mov cx,00000000000_11_000B         ;加載堆棧段選擇子
72         mov ss,cx
73         mov esp,0x7c00
74
75         
76           push 0x80
77           push byte 0x80 ;warning: signed byte value exceeds bounds
78           
79           push 0x8000
80           push word 0x8000
81           
82           push 0x87654321
83           push dword 0x87654321
84           
85           mov eax,0x86421357
86           push ax
87           push eax
88           
89           
90           push word [0x00]
91           push dword [0x00]
92           
93           push ds
94           push gs
95           push es
96           push cs
97      
98  ghalt:     
99         hlt                                ;已經禁止中斷,將不會被喚醒 
100
101;-------------------------------------------------------------------------------
102     
103         gdt_size         dw 0
104         gdt_base         dd 0x00007e00     ;GDT的物理地址 
105                             
106         times 510-($-$$) db 0
107                          db 0x55,0xaa

若是對上面的代碼不熟悉的話,能夠參考個人博文 進入保護模式(一)——《x86彙編語言:從實模式到保護模式》讀書筆記12 等文章。

(2)實驗報告

New001032_push

根據測試報告,咱們能夠概括出32位保護模式下,針對NASM編譯器的push指令用法:

push用法總結(32)

 

(完)

相關文章
相關標籤/搜索