在說正題之前,我們先看2個概念。
### 1.指令集架構(ISA)
ISA 的全稱是 instruction set architecture,中文就是指令集架構,是指對程序員實際“可見”的指令集,包含了程序員編寫一個能正確運行的二進制機器語言程序的所有信息,涉及到指令、 I/O 設備等。例如 Intel 的 IA-32、Intel 64、ARM 的 ARMv7、ARMv8 等等。
### 2.微架構
微架構(Microarchitecture)又稱為微體系結構/微處理器體系結構。是將一種給定的指令集架構在處理器中執行的方法。一種給定的指令集可以在不同的微架構中執行。 [1]
?
需要說明的是:
微架構與指令集是兩個概念:指令集是CPU選擇的語言,而微架構是具體的實現.[2]
ARM公司將自己研發的指令集叫做ARM指令集,同時它還研發具體的微架構(如Cortex系列)并對外授權。但是,一款CPU使用了ARM指令集不等于它就使用了ARM研發的微架構。Intel、高通、蘋果、Nvidia等廠商都自行開發了兼容ARM指令集的微架構,同時還有許多廠商使用ARM開發的微架構來制造CPU。通常,業界認為只有具備獨立的微架構研發能力的企業才算具備了CPU研發能力,而是否使用自行研發的指令集則無關緊要。廠商研發CPU時并不需要獲得指令集授權就可以獲得指令集的相關資料與規范,指令集本身的技術含量并不是很高。獲得授權主要是為了避免法律問題。然而微架構的設計細節是各家廠商絕對保密的,而且由于其技術復雜,即便獲得相應文檔也難以山寨。[2]
如前所述,僅僅從ARM購買微架構來組裝芯片的廠商是不能被稱作CPU研發企業的,這些芯片也不能被稱為“xx廠商研發的CPU”.典型如華為的海思920、三星Exynos 5430,只能說是“使用ARM Cortex-A15核心的芯片”。但是如果一款兼容ARM指令集的芯片使用了廠商自主研發的微架構,情況就不同了。高通驍龍800、蘋果A7就是這樣的例子——它們分別使用了高通、蘋果自主研發的CPU.[2]
### 3. 32位寄存器
(1)通用寄存器
在16位處理器內,有8個通用寄存器,分別是AX,BX,CX,DX,SI,DI,BP,SP. 其中,前4個還可以拆分成2個獨立的寄存器來用。32處理器在16位處理器的基礎上,擴展了這8個通用寄存器的長度,使之達到32位。它們的名字分別是EAX,EBX,ECX,EDX,ESI,EDI,ESP,EBP.
注意:
- 在使用這些寄存器的時候,指令的源操作數和目的操作數必須具有相同的長度(個別特殊用途的指令除外)。如果目的操作數是32位寄存器,源操作數是立即數,那么立即數被視為是32位的。
- 32位通用寄存器的高16位是不能獨立使用的,但是低16位保持同16位處理器的兼容性(可以拆成8位的來用)。
- 可以在32位的處理器上運行16位處理器上的軟件。
(2)指令指針寄存器
在32位模式下,為了生成32位物理地址,處理器需要使用32位的指令指針寄存器,也就是說之前的16位的IP擴展成為32位的EIP。當處理器工作在16位模式下時,依然使用16位的IP;工作在32位模式下時,使用32位的EIP。
注意:和之前一樣,EIP也只能由處理器自己使用,程序員無法直接訪問。
對IP和EIP的修改通常是通過某些隱式的指令進行的,比如JMP,CALL,RET,IRET等等。
(3)標志寄存器
在32位處理器中,標志寄存器由之前16位的FLAGS擴展為32位的EFLAGS,低16位的每個字段和原先保持一致。
下圖摘自《 The Intel Architecture Software Developer’s Manual——Volume 3 》
[](http://img.blog.csdn.net/20160109003529851)
(4)段寄存器
在32位模式下,對內存的訪問從理論上來說不需要分段,因為有32根地址線,可以直接尋址4G的內存。但是,IA-32結構的處理器是基于分段模型的,因此,32位處理器依然需要以段為單位訪問內存,即使它工作在32位模式下。
不過,可以采取一個變通的方案,即只分一個段。也就是說段的基地址是0x0000_0000,段的長度是4GB。在這種情況下,相當于不分段,即平坦模型(Flat Mode)。
在32位模式下,處理器要求在加載程序時,先定義該程序擁有的段,然后才可以使用這些段。定義段時,除了起始地址外,還附加了段界限、特權級別、類型等屬性。當程序訪問一個段時,處理器將通過固件進行各種檢查工作,以防止對內存的違規訪問。
在32位模式下,傳統的段寄存器,如CS,SS,DS,ES保存的不再是16位的段基地址,而是段選擇子(到底什么是段選擇子,我們以后再說)。另外,32位處理器還增加了兩個額外的段寄存器,分別是FS和GS。
### 4.實模式與保護模式
8086是16位的處理器,可以通過分段來訪問1M的內存,段的最大長度是64KB。8086只有一種工作模式,就是我們現在所說的“實模式”。1985年,Intel公司推出了80386,獲得了極大的成功。80386以及后續的處理器,都向前兼容,可以運行實模式下的8086程序。而且,在加電時,這些處理器都自動處于實模式下。只有在一番設置之后,才能運行在保護模式下。
### 5.邏輯地址和線性地址
在8086,我們把“段地址:段內偏移地址”稱為邏輯地址。8086CPU內部有一個地址加法器,用來把邏輯地址轉換成物理地址。轉換規則是:物理地址=段地址×10H+段內偏移量
但是在80386中,情況就不同了。80386的邏輯地址(也叫虛擬地址)構成是“段選擇子:段內偏移量”。邏輯地址經過80386CPU內部的分段部件轉換后成為線性地址。線性地址再經過分頁部件轉換就成為物理地址。如果禁用分頁機制,那么線性地址就是物理地址。
### 6.處理器的尋址方式
在16位處理器上,內存尋址方式為:
[](http://img.blog.csdn.net/20160109003532770)
?
在32位處理器上,內存尋址方式為:
[](http://img.blog.csdn.net/20160109003534734)
也是就是說,在指定有效地址的時候,可以使用所有的32位通用寄存器作為基址寄存器。同時,還可以再加上一個除ESP之外的32位通用寄存器作為變址寄存器。另外,變址寄存器還允許乘以一個比例因子(1或2或4或8)。最后,還可以加上一個8位或者32位的偏移量。
舉例:
add eax,[0x2008]
sub eax,[eax+0x04]
mov ecx,[edx+esi*8+0x02]
### 7.匯編器指令 BITS
相同的機器指令,在16位模式和32位模式下的解釋和執行效果是不同的。舉例來說:
8B5022,這條機器碼,在16位模式下,對應的匯編指令是:mov dx,[bx+si+0x02]; 但是,在32位模式下,對應的匯編指令卻是 mov edx,[eax+0x02];
NASM匯編器中有一個偽指令——BITS.
'BITS'指令指定 NASM 產生的代碼是被設計運行在 16 位模式的處理器上還是運行在32位模式的處理器上。語法是'BITS 16'或'BITS 32'. NASM以.bin格式輸出時,默認是16位模式。如果我們需要編譯成32位的,則需要加上[bits 32](方括號可以有,也可以沒有。)
### 8.一般指令的擴展
(1)loop指令
在16位處理器上,loop指令的循環次數在寄存器CX中。在32位處理器上,如果當前模式是16位的,那么loop指令執行時,仍然使用CX寄存器;如果運行在32位模式下,則使用ECX;
(2)mul指令
在16位處理器上,無符號數乘法指令mul的格式為
mul r/m8???? ; AX <- AL * r/m8
mul r/m16? ; DX:AX <- AX * r/m16
說明:這里的r/m8表示8位的通用寄存器或內存單元, r/m16表示16位的通用寄存器或內存單元,下面的r/m32表示32位的通用寄存器或內存單元
在32位處理器上,除了依然支持上面的的操作,另外還支持以下擴展格式:
mul r/m32? ;EDX:EAX <- EAX * r/m32
有符號數乘法指令imul與此相同。
(3)div指令
在16位處理器上,無符號除法指令div的格式為:
div r/m8? ; AX? ÷? r/m8?? = AL …… AH
div r/m16 ; DX:AX ÷ r/m16? = AX ……DX
在32位處理器上,除了依然支持上面的操作,還支持以下擴展格式:
div r/m32 ; EDX:EAX ÷ r/m32 = EAX……EDX
有符號數除法指令idiv于此相同;
(4)push和pop指令
操作數是立即數的情況
32處理器的棧操作指令push和pop也有所擴展,允許壓入雙字操作數。特別是,它支持立即數的壓棧操作。指令格式為(imm8/16/32表示8位或16位或32位立即數):
push imm8? ;操作碼為6A
push imm16 ; 操作碼為68
push imm32; 操作碼也為68
還是舉書上的例子吧:
- 例1:壓入一個字節
push byte 0x55;?
這里的關鍵字byte是給編譯器看的,告訴它壓入的是字節(畢竟0x55可以解釋為字0x0055或者雙字0x0000_0055).這條指令的16位形式(用bits 16 編譯)和32位形式(用bits 32 編譯)是一樣的,機器碼都是
6A 55
但是,執行的時候就不同了。注意,無論什么時候,處理器都不會壓入一個字節。它要么壓入字,要么壓入雙字。
在16位模式下,默認的操作數是16位。于是處理器將0x55作符號擴展,擴展成16位的0x0055,然后壓入棧。(壓棧時用sp寄存器,且先將sp減去2);
在32位模式下,處理器將0x55擴展成32位的0x0000_0055,然后壓入。(壓棧時用esp寄存器,且先將esp減去4)
- 例2:壓入一個字
壓入一個字,必須用word關鍵字來修飾,如
push word 0xfffb
在16位模式下,默認的操作數是16位的。處理器直接壓入該字(壓棧時用sp寄存器,且先將sp減去2);
在32位模式下,處理器將0xfffb擴展成32位的0xffff_fffb,然后壓入。(壓棧時用esp寄存器,且先將esp減去4)
- 例3:壓入一個雙字
如果壓入雙字,則必須用關鍵字dword來修飾。如
push dword 0xfb
在16位模式下,壓入的是0x0000_00fb(壓棧時用sp寄存器,且先將sp減去4);
在32位模式下,壓入的也是0x0000_00fb(壓棧時用esp寄存器,且先將esp減去4).
操作數位于通用寄存器或者內存單元的情況
對于操作數位于通用寄存器或者內存單元的情況,只能壓入字或者雙字。指令格式為:
push r/m16
push r/m32
比如:
push ax
push edx
如果操作數位于內存單元中,則必須用關鍵字word或者dword修飾,如:
push word [0x2000]
push dword [ecx+esi*2+0x02]
無論操作數位于寄存器還是內存單元,
在16位模式下,壓入字的時候,將sp的內容減去2;壓入雙字的時候,將sp的內容減去4;
在32位模式下,壓入字的時候,將esp的內容減去2;壓入雙字的時候,將esp的內容減去4.
操作數是段寄存器的情況
指令格式為:
push cs/ds/es/fs/gs/ss
在16位模式下,將sp的內容減去2,然后直接壓入段寄存器的內容;
在32位模式下,先將段寄存器的內容擴展為32位(高16位全為0),然后將esp的內容減去4,再壓入擴展后的32位的值。
?
今天就說到這里,下次我們開啟保護模式之旅
?
?
### 參考資料:
[1]百度百科:[http://baike.baidu.com/link?url=I1wsXUAGa541Pn8h1XVgSnR6GmUsfWK8VOjpALlzmE7vOccJVOpxkQfKjYYHODUe2BxqOw2q5KAB6pS4ZQjD9K](http://baike.baidu.com/link?url=I1wsXUAGa541Pn8h1XVgSnR6GmUsfWK8VOjpALlzmE7vOccJVOpxkQfKjYYHODUe2BxqOw2q5KAB6pS4ZQjD9K "http://baike.baidu.com/link?url=I1wsXUAGa541Pn8h1XVgSnR6GmUsfWK8VOjpALlzmE7vOccJVOpxkQfKjYYHODUe2BxqOw2q5KAB6pS4ZQjD9K")
[2]知乎:王強,[http://zhuanlan.zhihu.com/xpenrynidea/19893066](http://zhuanlan.zhihu.com/xpenrynidea/19893066)