零. 課程要點:
- IA-32的體系結構
- IA-32的常用指令類型
這章的內容如果想深入學習,更適合專門找一本《匯編語言》或《微機原理》來研讀,這里只需能夠了解IA-32的體系結構,以及能查表看懂匯編語句即可。主要目的是為了弄懂C語言的一條語句在底層是如何實現的,在這種體系結構和操作下,可能會存在哪些需要注意的地方。而匯編語言是能夠看懂,又能夠比較清晰展示底層操作的語言,和機器指令一一對應。
一. 指令集體系結構(ISA)
什么是指令集體系結構?先來看這張圖:
指令集體系結構(Instruction Set Architecture, ISA),位于計算機系統層次的軟件和硬件分界處,起到一個溝通橋梁的作用。ISA是一種規約,規定了如何使用“硬件”,如:
- 可執行的指令的集合,包括指令格式、操作種類以及每種操作對應的操作數的相應規定;
- 指令可以接受的操作數的類型;
- 操作數所能存放的寄存器組的結構,包括每個寄存器的名稱、編號、長度和用途;
- 操作數所能存放的存儲空間的大小和編址方式;
- 操作數在存儲空間存放時按照大端還是小端方式存放;
- 指令獲取操作數的方式,即尋址方式;
- 指令執行過程的控制方式,包括程序計數器、條件碼定義等;
沒有ISA,軟件就無法使用計算機硬件。換言之,可以有不同的指令集體系結構(確實如此,如IA-32,MIPS,ARM等),甚至同一種ISA也可以有不同的計算機組成,只要相應的計算機組成能夠實現ISA規定的功能。這里主要介紹IA-32體系結構。
二. IA-32體系結構
現代的馮·諾依曼計算機結構大致如下:
其中:CPU-中央處理器,PC-程序計數器,MAR-存儲器地址寄存器,ALU-算術邏輯部件,IR-指令寄存器,MDR-存儲器數據寄存器,GPRs-通用寄存器組。
首先我們得知道計算機是如何工作的呢?
- 程序在執行前
數據和指令事先存放在存儲器中,每條指令和每個數據都有地址,指令按序存放,指令由OP、ADDR字段組成,程序起始地址置PC - 開始執行程序
第一步:根據PC取指令
第二步:指令譯碼
第三步:取操作數
第四步:指令執行
第五步:回寫結果
第六步:修改PC的值
繼續執行下一條指令
那么可以想想作為一個指令集體系結構,它的體系結構是怎樣的?有哪些需要確定?如:寄存器個數及各自功能?寄存器寬度?存儲空間大小?編址單位?指令格式?指令條數?指令操作功能?尋址方式?數據類型?小端/大端?標志寄存器各位含義?PC位數?I/O端口編址方式?……
IA-32是典型的CISC(復雜指令集計算機,與之對應的是精簡指令集-RISC)風格ISA,包括:
-
8個通用寄存器(8位、16位、32位)
2個專用寄存器:EIP(PC)、標志寄存器EFLAGS
6個段寄存器(間接給出段基址)
OF,SF,ZF,CF之前介紹過了,AF表示輔助進位標志(BCD碼運算時有用),PF表示奇偶標志。另外還有三個控制標志:DF表示方向標志(自動變址方式是增還是減),IF表示中斷允許標志(僅對外部可屏蔽中斷有用),TF表示陷阱標志(是否允許單步跟蹤狀態)。
- 支持的數據類型及格式
IA-32架構由16位架構發展而來,因此,雖然字長為32位或更大,但一個字為16位,長度后綴為 w;32位為雙字,長度后綴為 long double實際長度為80位,但分配96位=12B(按4B對齊)。
- 存儲器地址空間為4GB,按字節編址,小端方式
1個字節(1Byte = 8bits)一個地址,32位機器可表示232個地址,即4GB,這就是為什么32位機器最多支持4GB內存。
那什么是小端方式和大端方式?
如果以字節排列,有一個int變量,共32位數據位,4個字節,如0xFF FF 00 01,那么FF即為最高有效字節MSB,01即為最低有效字節LSB。
這個變量共需占4個地址,假設為100,101,102,103,那么這個變量的地址是100還是103?答案是按最小地址表示,即我們說這個變量的地址是100,或者說變量存放在100-103單元。
那么大端方式就是MSB所在的地址是數的地址,小端方式就LSB所在的地址是數的地址,即“大大小小”:
IBM,Motorola 68k,MIPS等是大端方式,Intel 80x86,DEC VAX等是小端方式,因此不同機器之間程序移值或者數據通信的時候要考慮字節順序和字節交換問題。
- 尋址方式
什么是尋址方式?即如何根據指令得到操作數或操作地址。
那么指令長什么樣?
機器指令是由一個0/1序列組成,如100010000100100111111011表示:
機器指令太難讀懂了,所以一般用匯編指令來表示,匯編指令也可以有不同的格式,如Intel格式和AT&T格式,這門課主要用后者:
根據操作數所在的位置不同,IA-32的尋址方式可分為:立即尋址,寄存器尋址,存儲單元尋址,和相對尋址。而尋址方式與微處理器的工作模式有關,工作模式可分為:實地址模式和保護模式,這里主要討論保護模式下的尋址方式(這方面內容跟后面分頁和內存管理關系較大,深入細節較多,不詳細討論,這里主要能看懂分清各種尋址方式的指令即可)。
舉個例子:movw 8(%ebp, %edx, 4), %ax 表示R[ax] ← M[R[ebp]+R[edx]x4+8],傳送字。
- 變長指令字、變長操作碼
三. IA-32的常用指令類型
再次說明一下,學習匯編指令是為了我們能通過反編譯出來的匯編語句,看懂高級程序語句的執行過程細節,更深刻的理解計算機工作原理。不需要特別記憶,只要用到某條指令的時候,會查手冊并理解手冊中所描述的內容即可。
& 1. 傳送指令
- 通用數據傳送指令
MOV:一般傳送,包括movb、movw和movl等
MOVS:符號擴展傳送,如movsbw、movswl等
MOVZ:零擴展傳送,如movzwl、movzbl等
XCHG:數據交換
PUSH/POP:入棧/出棧,如pushl,pushw,popl,popw等 - 地址傳送指令
LEA:加載有效地址,如leal (%edx,%eax), %eax”的功能為R[eax]←R[edx]+R[eax],執行前,若R[edx]=i,R[eax]=j,則指令執行后,R[eax]=i+j - 輸入輸出指令
IN和OUT:I/O端口與寄存器之間的交換 - 標志傳送指令
PUSHF、POPF:將EFLAG壓棧,或將棧頂內容送EFLAG
“棧”是什么?棧(Stack)是一種采用“先進后出”方式進行訪問的一塊存儲區,從高地址向低地址增長。執行pushw %ax表示把16位寄存器里的“字”入棧:先將當前棧頂位置減2,然后把AX內容存入棧中,按小端方式存儲。反過來出棧popw %ax就是相反的操作。
& 2. 定點算術運算指令
- 加 / 減運算(影響標志、不區分無/帶符號)
ADD:加,包括addb、addw、addl等
SUB:減,包括subb、subw、subl等 - 增1 / 減1運算(影響除CF以外的標志、不區分無/帶符號)
INC:加,包括incb、incw、incl等
DEC:減,包括decb、decw、decl等 - 取負運算(影響標志、若對0取負,則結果為0且CF清0,否則CF置1)
NEG:取負,包括negb、negw、negl等 - 比較運算(做減法得到標志、不區分無/帶符號)
CMP:比較,包括cmpb、cmpw、cmpl等 - 乘 / 除運算(不影響標志、區分無/帶符號)
MUL / IMUL:無符號乘 / 帶符號乘
DIV/ IDIV:帶無符號除 / 帶符號除
其中,乘法指令:可給出一個、兩個或三個操作數
- 若給出一個操作數SRC,則另一個源操作數隱含在AL/AX/EAX中,將SRC和累加器內容相乘,結果存放在AX(16位)或DX-AX(32位)或EDX-EAX(64位)中。DX-AX表示32位乘積的高、低16位分別在DX和AX中。 n位× n位=2n位
- 若指令中給出兩個操作數DST和SRC,則將DST和SRC相乘,結果在DST中。n位× n位=n位
- 若指令中給出三個操作數REG、SRC和IMM,則將SRC和立即數IMM相乘,結果在REG中。n位× n位=n位
例:mulb %bl 表示R[ax] ← R[al] x R[bl];imull -16, (%eax,%ebx,4), %eax 表示R[eax] ← (-16) × M[R[eax]+R[ebx]×4]
除法指令:只明顯指出除數,用EDX-EAX中內容除以指定的除數
- 若為8位,則16位被除數在AX寄存器中,商送回AL,余數在AH
- 若為16位,則32位被除數在DX-AX寄存器中,商送回AX,余數在DX
- 若為32位,則被除數在EDX-EAX寄存器中,商送EAX,余數在EDX
& 3. 按位運算指令
- 邏輯運算
NOT:非,包括 notb、notw、notl等
AND:與,包括 andb、andw、andl等
OR:或,包括 orb、orw、orl等
XOR:異或,包括 xorb、xorw、xorl等
TEST:做“與”操作測試,僅影響標志
僅NOT不影響標志,其他指令OF=CF=0,而ZF和SF則根據結果設置:若全0,則ZF=1;若最高位為1,則SF=1。
- 移位運算(左/右移時,最高/最低位送CF)
SHL/SHR: 邏輯左/右移,包括 shlb、shrw、shrl等
SAL/SAR: 算術左/右移,左移判溢出,右移高位補符(移位前、后符號位發生變化,則OF=1 )包括 salb、sarw、sarl等
ROL/ROR: 循環左/右移,包括 rolb、rorw、roll等
RCL/RCR: 帶進位循環左/右移,即:將CF作為操作數的一部分循環移位,包括 rclb、rcrw、rcll等
& 4. 控制轉移指令
指令執行可按順序 或 跳轉到轉移目標指令處執行
- 無條件轉移指令
JMP DST:無條件轉移到目標指令DST處執行 - 條件轉移
Jcc DST:cc為條件碼,根據標志(條件碼)判斷是否滿足條件,若滿足,則轉移到目標指令DST處執行,否則按順序執行 - 條件設置
SETcc DST:按條件碼cc判斷的結果保存到DST(是一個8位寄存器 ) - 調用和返回指令 (用于過程調用)
CALL DST:返回地址RA入棧,轉DST處執行
RET:從棧中取出返回地址RA,轉到RA處執行 - 中斷指令
& 5. x87浮點處理指令
IA-32的浮點處理架構有兩種:
- x87FPU指令集(gcc默認)
- SSE指令集(x86-64架構所用)
早期的浮點處理器是作為CPU的外置協處理器出現的,x87 FPU 特指與x86處理器配套的浮點協處理器架構。
- 浮點寄存器采用棧結構
? 深度為8,寬度為80位,即8個80位寄存器
? 名稱為 ST(0) ~ ST(7),棧頂為ST(0),編號分別為 0~7 - 所有浮點運算都按80位擴展精度進行
- 浮點數在浮點寄存器和內存之間傳送
? float、double、long double型變量在內存分別用IEEE 754單精度、雙精度和擴展精度表示,分別占32位(4B)、64位(8B)和96位(12B,其中高16位無意義)
? float、double、long double類型變量在浮點寄存器中都用80位擴展精度表示
? 從浮點寄存器到內存:80位擴展精度格式轉換為32位或64位(那樣就存在精度問題)
? 從內存到浮點寄存器: 32位或64位格式轉換為80位擴展精度格式
數據傳送類
(1) 裝入 (轉換為80位擴展精度)
FLD:將數據從存儲單元裝入浮點寄存器棧頂 ST(0)
FILD:將數據從int型轉換為浮點格式后,裝入浮點寄存器棧頂
(2) 存儲(轉換為IEEE 754單精度或雙精度)
FSTx:x為s/l時,將棧頂ST(0)轉換為單/雙精度格式,然后存入存儲單元
FSTPx:彈出棧頂元素,并完成與FSTx相同的功能
FISTx:將棧頂數據從浮點格式轉換為int型后,存入存儲單元
FISTP:彈出棧頂元素,并完成與FISTx相同的功能
帶P結尾指令表示操作數會出棧,也即ST(1)將變成ST(0)
(3) 交換
FXCH:交換棧頂和次棧頂兩元素
(4) 常數裝載到棧頂
FLD1 :裝入常數1.0
FLDZ :裝入常數0.0
FLDPI :裝入常數pi (=3.1415926...)
FLDL2E :裝入常數log(2)e
FLDL2T :裝入常數log(2)10
FLDLG2 :裝入常數log(10)2
FLDLN2 :裝入常數Log(e)2算術運算類
(1) 加法
FADD/FADDP: 相加/相加后彈出棧
FIADD:按int型轉換后相加
(2) 減法
FSUB/FSUBP : 相減/相減后彈出棧
FSUBR/FSUBRP:調換次序相減/相減后彈出棧
FISUB:按int型轉換后相減
FISUBR:按int型轉換并調換次序相減
若指令未帶操作數,則默認操作數為ST(0)、ST(1)
帶R后綴指令是指操作數順序變反,例如:fsub執行的是x-y,fsubr執行的就是y-x
(3) 乘法
FMUL/FMULP: 相乘/相乘后彈出棧
FIMUL:按int型轉換后相乘
(4) 除法
FDIV/FDIVP : 相除/相除后彈出棧
FIDIV:按int型轉換后相除
FDIVR/FDIVRP:調換次序相除/相減后彈出棧
FIDIVR:按int型轉換并調換次序相除
& 6. MMX及SSE指令
(暫略)
注:文中圖片來源于mooc官網