計(jì)算機(jī)執(zhí)行機(jī)器代碼,用字節(jié)序列編碼低級(jí)的操作,包括處理數(shù)據(jù)、管理存儲(chǔ)器、讀寫(xiě)存儲(chǔ)設(shè)備上的數(shù)據(jù),以及利用網(wǎng)絡(luò)通信。編譯器基于變成語(yǔ)言的原則、目標(biāo)機(jī)器的指令集和操作系統(tǒng)遵循的規(guī)則,經(jīng)過(guò)一系列的階段產(chǎn)生機(jī)器代碼。GCC C語(yǔ)言編譯器以匯編代碼的形式產(chǎn)生輸出,匯編代碼是機(jī)器代碼的文本表示,給出程序中的每一條指令。然后GCC調(diào)用匯編器和鏈接器,從而根據(jù)匯編代碼生成可執(zhí)行的機(jī)器代碼。
Intel系列有好幾個(gè)名字,包括IA32,也就是“Intel 32 位體系結(jié)構(gòu)”(Intel Architecure 32-bit),以及后來(lái)的Intel64,即IA32的64位擴(kuò)展,也稱為x86-64,最常用的名字是“x86”,用它指代整個(gè)系列。
計(jì)算是機(jī)系統(tǒng)使用了多種不同形式的抽象,利用更簡(jiǎn)單的抽象模型來(lái)隱藏實(shí)現(xiàn)的細(xì)節(jié)。對(duì)于機(jī)器級(jí)編程來(lái)說(shuō),其中兩種抽象尤為重要。第一種是機(jī)器級(jí)程序的格式和行為,定義為指令集體系結(jié)構(gòu)(Instruction set architecture,ISA),它定義了處理器狀態(tài)、指令的格式,以及每條指令對(duì)狀態(tài)的影響。大多數(shù)ISA,將程序的行為描述成好像每條指令是按順序執(zhí)行的,一條指令結(jié)束后,下一條指令開(kāi)始。處理器的硬件遠(yuǎn)比描述的精細(xì)復(fù)雜,它們并發(fā)地執(zhí)行許多指令,但是可以采取措施保證整體行為與ISA指定的順序執(zhí)行完全一致。第二種抽象是,機(jī)器級(jí)程序使用的存儲(chǔ)器地址是虛擬地址,提供的存儲(chǔ)器模型看上去是一個(gè)非常大的字節(jié)數(shù)組。存儲(chǔ)器系統(tǒng)的實(shí)際實(shí)現(xiàn)是將多個(gè)硬件存儲(chǔ)器和操作系統(tǒng)軟件組合起來(lái)進(jìn)行操作的。
int accum=0;
int sum(int x, int y) {
int t = x + y;
accum++;
return t;
}
在命令行上使用“-S”選項(xiàng),就能得到C語(yǔ)言編譯器產(chǎn)生的匯編代碼,產(chǎn)生一個(gè)匯編文件sum.s。
$ gcc -O1 -S sum.c
編譯選項(xiàng)-O1告訴編譯器使用第一級(jí)優(yōu)化。通常,提高優(yōu)化級(jí)別會(huì)使最終程序運(yùn)行得更快,但是編譯時(shí)間可能會(huì)變長(zhǎng),用調(diào)試工具對(duì)代碼進(jìn)行調(diào)試會(huì)更困難。
sum.s文件內(nèi)容如下:
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 10, 15 sdk_version 10, 15
.globl _sum ## -- Begin function sum
.p2align 4, 0x90
_sum: ## @sum
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
## kill: def $esi killed $esi def $rsi
## kill: def $edi killed $edi def $rdi
leal (%rdi,%rsi), %eax
incl _accum(%rip)
popq %rbp
retq
.cfi_endproc
## -- End function
.globl _accum ## @accum
.zerofill __DATA,__common,_accum,4,2
.subsections_via_symbols
GCC產(chǎn)生的匯編代碼對(duì)我們來(lái)說(shuō)有點(diǎn)難懂。一方面,它包含一些我們不需要關(guān)心的信息;另一方面,它不提供任何程序的描述或它是如何工作的描述。所有以“.”開(kāi)頭的行都是匯編器和鏈接器的命令,我們?cè)诮庾x時(shí)通??梢院雎赃@些行,專注到機(jī)器指令上來(lái)。
再進(jìn)一步,在命令行上使用“-c”選項(xiàng),GCC會(huì)編譯并匯編該代碼:
$ gcc -O1 -c sum.c
這就會(huì)產(chǎn)生目標(biāo)代碼文件sum.o,它是二進(jìn)制格式,不能直接以文本形式查看。在Linux系統(tǒng)中,可以通過(guò)xxd來(lái)查看字節(jié)編碼。
$ xxd sum.o
00000000: cffa edfe 0700 0001 0300 0000 0100 0000 ................
00000010: 0400 0000 0802 0000 0020 0000 0000 0000 ......... ......
00000020: 1900 0000 8801 0000 0000 0000 0000 0000 ................
00000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000040: 7400 0000 0000 0000 2802 0000 0000 0000 t.......(.......
00000050: 7000 0000 0000 0000 0700 0000 0700 0000 p...............
00000060: 0400 0000 0000 0000 5f5f 7465 7874 0000 ........__text..
00000070: 0000 0000 0000 0000 5f5f 5445 5854 0000 ........__TEXT..
00000080: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000090: 0f00 0000 0000 0000 2802 0000 0400 0000 ........(.......
000000a0: 9802 0000 0100 0000 0004 0080 0000 0000 ................
000000b0: 0000 0000 0000 0000 5f5f 636f 6d6d 6f6e ........__common
000000c0: 0000 0000 0000 0000 5f5f 4441 5441 0000 ........__DATA..
000000d0: 0000 0000 0000 0000 7000 0000 0000 0000 ........p.......
000000e0: 0400 0000 0000 0000 0000 0000 0200 0000 ................
000000f0: 0000 0000 0000 0000 0100 0000 0000 0000 ................
00000100: 0000 0000 0000 0000 5f5f 636f 6d70 6163 ........__compac
00000110: 745f 756e 7769 6e64 5f5f 4c44 0000 0000 t_unwind__LD....
00000120: 0000 0000 0000 0000 1000 0000 0000 0000 ................
00000130: 2000 0000 0000 0000 3802 0000 0300 0000 .......8.......
00000140: a002 0000 0100 0000 0000 0002 0000 0000 ................
00000150: 0000 0000 0000 0000 5f5f 6568 5f66 7261 ........__eh_fra
00000160: 6d65 0000 0000 0000 5f5f 5445 5854 0000 me......__TEXT..
00000170: 0000 0000 0000 0000 3000 0000 0000 0000 ........0.......
00000180: 4000 0000 0000 0000 5802 0000 0300 0000 @.......X.......
00000190: 0000 0000 0000 0000 0b00 0068 0000 0000 ...........h....
000001a0: 0000 0000 0000 0000 3200 0000 1800 0000 ........2.......
000001b0: 0100 0000 000f 0a00 000f 0a00 0000 0000 ................
000001c0: 0200 0000 1800 0000 a802 0000 0200 0000 ................
000001d0: c802 0000 1000 0000 0b00 0000 5000 0000 ............P...
000001e0: 0000 0000 0000 0000 0000 0000 0200 0000 ................
000001f0: 0200 0000 0000 0000 0000 0000 0000 0000 ................
00000200: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000210: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000220: 0000 0000 0000 0000 5548 89e5 8d04 37ff ........UH....7.
00000230: 0500 0000 005d c300 0000 0000 0000 0000 .....]..........
00000240: 0f00 0000 0000 0001 0000 0000 0000 0000 ................
00000250: 0000 0000 0000 0000 1400 0000 0000 0000 ................
00000260: 017a 5200 0178 1001 100c 0708 9001 0000 .zR..x..........
00000270: 2400 0000 1c00 0000 b0ff ffff ffff ffff $...............
00000280: 0f00 0000 0000 0000 0041 0e10 8602 430d .........A....C.
00000290: 0600 0000 0000 0000 0900 0000 0000 001d ................
000002a0: 0000 0000 0100 0006 0600 0000 0f02 0000 ................
000002b0: 7000 0000 0000 0000 0100 0000 0f01 0000 p...............
000002c0: 0000 0000 0000 0000 005f 7375 6d00 5f61 ........._sum._a
000002d0: 6363 756d 0000 0000 ccum....
xxd默認(rèn)會(huì)以十六進(jìn)制的形式來(lái)進(jìn)行顯示,兩個(gè)字節(jié)為一組。可以通過(guò)指定 -b 選項(xiàng)來(lái)顯示二進(jìn)制,-g n 選項(xiàng)來(lái)指定每組顯示n個(gè)字節(jié)的編碼。
要查看目標(biāo)代碼文件的所代表的內(nèi)容,最有價(jià)值的是反匯編器(disassembler)。這些程序根據(jù)目標(biāo)代碼產(chǎn)生一種類(lèi)似于匯編代碼的格式。在Linux系統(tǒng)中,帶“-d”命令行標(biāo)志的程序objdump可以充當(dāng)這個(gè)角色。
$ objdump -d sum.o
sum.o: file format Mach-O 64-bit x86-64
Disassembly of section __TEXT,__text:
_sum:
0: 55 pushq %rbp
1: 48 89 e5 movq %rsp, %rbp
4: 8d 04 37 leal (%rdi,%rsi), %eax
7: ff 05 00 00 00 00 incl (%rip)
d: 5d popq %rbp
e: c3 retq
objdump -d 可以直接作用于可執(zhí)行文件,用于反匯編出文件代碼,比如hello的可執(zhí)行文件。
數(shù)據(jù)格式
由于是從16位體系結(jié)構(gòu)擴(kuò)展成32位的,Intel用術(shù)語(yǔ)“字”(word)表示16位數(shù)據(jù)類(lèi)型。因此,稱32位數(shù)為“雙字”(double words),稱64位數(shù)為“四字”(quad words)。
| C聲明 | Intel數(shù)據(jù)類(lèi)型 | 匯編代碼后綴 | 大小(字節(jié)) |
|---|---|---|---|
| char | 字節(jié) | b | 1 |
| short | 字 | w | 2 |
| int | 雙字 | l | 4 |
| long int | 雙字 | l | 4 |
| long long int | — | — | 4 |
| char * | 雙字 | l | 4 |
| float | 單精度 | s | 4 |
| double | 雙精度 | l | 8 |
| long double | 擴(kuò)展精度 | t | 10/12 |
上表是C語(yǔ)言數(shù)據(jù)類(lèi)型在IA32中的大小。IA32不支持64位整數(shù)運(yùn)算。編譯帶有 long long 數(shù)據(jù)的代碼,需要產(chǎn)生一些操作序列,以32位塊為單位執(zhí)行運(yùn)算。
大多數(shù)GCC生成的匯編代碼指令都有一個(gè)字符后綴,表明操作數(shù)的大小。例如,數(shù)據(jù)傳送指令有三個(gè)變種:movb(傳送字節(jié))、movew(傳送字)、movel(傳送雙字)。后綴 用來(lái)表示雙字,因?yàn)閷?2位數(shù)看成是“長(zhǎng)字”(long word),這是由于沿用了16位字為標(biāo)準(zhǔn)那個(gè)時(shí)代的習(xí)慣。匯編代碼也使用后綴
來(lái)表示4字節(jié)整數(shù)和8字節(jié)雙精度浮點(diǎn)數(shù)。這不會(huì)產(chǎn)生歧義,因?yàn)楦↑c(diǎn)數(shù)使用的是一組完全不同的指令和寄存器。
一個(gè)IA32中央處理單元(CPU)包含一組8個(gè)存儲(chǔ)32位值的寄存器。這些寄存器用來(lái)存儲(chǔ)整數(shù)數(shù)據(jù)和指針。這8個(gè)寄存器的名字都以 %e 開(kāi)頭,不過(guò)它們都另有特殊的名字。在大多數(shù)情況,前6個(gè)寄存器都可以看成通用寄存器,對(duì)它們的使用沒(méi)有限制。最后兩個(gè)寄存器(%ebp和%esp)保存著指向程序棧中重要位置的指針,只有根據(jù)棧管理的標(biāo)準(zhǔn)才能修改這兩個(gè)寄存器中的值。

所有8個(gè)寄存器都可以作為16位(字)或32位(雙字)來(lái)訪問(wèn)。字節(jié)操作指令可以獨(dú)立地讀或者寫(xiě)前4個(gè)寄存器的2個(gè)低位字節(jié)。當(dāng)一條字節(jié)指令更新這些單字節(jié)“寄存器元素”中的一個(gè)時(shí),該寄存器余下的3個(gè)字節(jié)不會(huì)改變。類(lèi)似地,字操作指令可以讀或者寫(xiě)每個(gè)寄存器的低16位。這個(gè)特性源自IA32從16位微處理器演化而來(lái)的這個(gè)傳統(tǒng),當(dāng)對(duì)大小指示符為short的整數(shù)運(yùn)算時(shí),也會(huì)用到這個(gè)特性。
大多數(shù)指令有一個(gè)或多個(gè)操作數(shù),指示出執(zhí)行一個(gè)操作中要引用的源數(shù)據(jù)值,以及放置結(jié)果的目標(biāo)位置。源數(shù)據(jù)值可以以常數(shù)形式給出,或是從寄存器或存儲(chǔ)器中讀出。結(jié)果可以存放在寄存器或存儲(chǔ)器中。各種不同的操作數(shù)的可能性被分為三種類(lèi)型:立即數(shù)、寄存器和存儲(chǔ)器引用。
立即數(shù)(immediate)也就是常數(shù)值。在ATT格式的匯編代碼中,立即數(shù)的書(shū)寫(xiě)方式是“$”后面跟一個(gè)用標(biāo)準(zhǔn)C表示法表示的整數(shù),比如$-577或$0x1F。任何能放進(jìn)一個(gè)32位的字里的數(shù)值都可以用作立即數(shù),不過(guò)匯編器在可能時(shí)會(huì)使用一個(gè)或兩個(gè)字節(jié)的編碼。
寄存器(register)表示某個(gè)寄存器的內(nèi)容。我們用符號(hào)來(lái)表示任意寄存器
,用引用
來(lái)表示它的值,這是將寄存器集合看成一個(gè)數(shù)組
,用寄存器標(biāo)識(shí)符作為索引。
存儲(chǔ)器(memory)引用會(huì)根據(jù)計(jì)算出來(lái)的有效地址訪問(wèn)某個(gè)存儲(chǔ)器位置。因?yàn)閷⒋鎯?chǔ)器看成一個(gè)很大的字節(jié)數(shù)組,我們用符號(hào)表示對(duì)存儲(chǔ)在存儲(chǔ)器中從地址
開(kāi)始的b個(gè)字節(jié)的引用。為了簡(jiǎn)便,我們通常省去下方的
。
有多種不同的尋址模式,允許不同形式的存儲(chǔ)器引用。表中底部用語(yǔ)法 表示的是最常用的形式。這樣的引用由四個(gè)部分組成:一個(gè)立即數(shù)偏移
,一個(gè)基址寄存器
,一個(gè)變址寄存器
和一個(gè)比例因子
,這里
必須是 1、2、4或者8。然后,有效地址被計(jì)算為
。引用數(shù)組元素時(shí),會(huì)用到這種通用形式。
| 類(lèi)型 | 格式 | 操作數(shù)值 | 名稱 |
|---|---|---|---|
| 立即數(shù) | $ |
立即數(shù)尋址 | |
| 寄存器 | 寄存器尋址 | ||
| 存儲(chǔ)器 | 絕對(duì)尋址 | ||
| 存儲(chǔ)器 | 間接尋址 | ||
| 存儲(chǔ)器 | (基址+偏移量)尋址 | ||
| 存儲(chǔ)器 | 變址尋址 | ||
| 存儲(chǔ)器 | 變址尋址 | ||
| 存儲(chǔ)器 | 比例變址尋址 | ||
| 存儲(chǔ)器 | 比例變址尋址 | ||
| 存儲(chǔ)器 | 比例變址尋址 | ||
| 存儲(chǔ)器 | $Imm(E_b,E_i,s) | 比例變址尋址 |
數(shù)據(jù)傳送指令
以下所有指令表中的S代表Source,即源數(shù)據(jù),D代表Destination,即結(jié)果數(shù)據(jù),或者單獨(dú)使用時(shí),僅僅是作為一個(gè)整數(shù)的代數(shù)符號(hào)。
| 指令 | 效果 | 描述 |
|---|---|---|
| MOV |
傳送 | |
| movb | 傳送字節(jié) | |
| movw | 傳送字 | |
| movl | 傳送雙字 | |
| MOVS |
傳送符號(hào)擴(kuò)展的字節(jié) | |
| movsbw | 將做了符號(hào)擴(kuò)展的字節(jié)傳送到字 | |
| movsbl | 將做了符號(hào)擴(kuò)展的字節(jié)傳送到雙字 | |
| movswl | 將做了符號(hào)擴(kuò)展的字傳送到雙字 | |
| MOVZ |
傳送零擴(kuò)展的字節(jié) | |
| movzbw | 將做了零擴(kuò)展的字節(jié)傳送到字 | |
| movzbl | 將做了零擴(kuò)展的字節(jié)傳送到雙字 | |
| movzwl | 將做了零擴(kuò)展的字傳送到雙字 | |
| pushl |
|
將雙字壓棧 |
| popl |
|
將雙字出棧 |
棧可以實(shí)現(xiàn)為一個(gè)數(shù)組,總是從數(shù)組的一端插入和刪除元素,這一端稱為棧頂。在IA32中,程序棧存放在存儲(chǔ)器中某個(gè)區(qū)域,并且棧是從高地址向低地址方向增長(zhǎng)的;所以壓棧是減小棧指針(寄存器%esp)的值,并將數(shù)據(jù)存放在存儲(chǔ)器中,而出棧是從存儲(chǔ)器中讀,并增加棧指針的值。
算術(shù)和邏輯操作
| 指令 | 效果 | 描述 |
|---|---|---|
| leal |
加載有效地址 | |
| INC |
加1 | |
| DEC |
減1 | |
| NEG |
取負(fù) | |
| NOT |
取補(bǔ) | |
| ADD |
加 | |
| SUB |
減 | |
| IMUL |
乘 | |
| XOR |
異或 | |
| OR |
或 | |
| AND |
與 | |
| SAL |
左移 | |
| SHL |
左移(等同于SAL) | |
| SAR |
算術(shù)右移 | |
| SHR |
邏輯右移 |
加載有效地址(load effective address)指令 leal 實(shí)際上是movl的變形,它的指令形式是從存儲(chǔ)器讀數(shù)據(jù)到寄存器,但實(shí)際上它根本就沒(méi)有引用存儲(chǔ)器。它的第一個(gè)操作數(shù)看上去是一個(gè)存儲(chǔ)器引用,但該指令并不是從指定的位置讀入數(shù)據(jù),而是將有效地址寫(xiě)入到目的操作數(shù)。這條指令可以為后面的存儲(chǔ)器引用產(chǎn)生指針。另外,它還可以簡(jiǎn)潔地描述普通的算術(shù)操作。例如,如果寄存器%edx的值為x,那么指令 leal 7(%edx,%edx,4),%eax 將設(shè)置寄存器%eax的值為5x+7。(采用尋址規(guī)則,)
一元操作只有一個(gè)操作數(shù),既是源又是目的。這個(gè)操作數(shù)可以是一個(gè)寄存器,也可以是一個(gè)存儲(chǔ)器位置。
二元操作的第二個(gè)操作數(shù)既是源又是目的。第一個(gè)操作數(shù)可以是立即數(shù)、寄存器或是存儲(chǔ)器位置,第二個(gè)操作數(shù)可以是寄存器或是存儲(chǔ)器位置。不過(guò),同movl指令一樣,兩個(gè)操作數(shù)不能同時(shí)是存儲(chǔ)器位置。
移位操作,先給出移位量,然后第二項(xiàng)給出的是要移位的位數(shù)。移位量是單個(gè)字節(jié)編碼,因?yàn)橹辉试S進(jìn)行0到31位的移位。移位量可以是一個(gè)立即數(shù),或者放在單字節(jié)寄存器元素%cl中。(這些指令很特別,因?yàn)橹辉试S以這個(gè)特定的寄存器作為操作數(shù)。)兩個(gè)左移指令的效果是一樣的,都是將右邊填上0。右移指令不同,算術(shù)右移是填上符號(hào)位,邏輯右移是填上0。移位操作的目的操作數(shù)可以是一個(gè)寄存器或是一個(gè)存儲(chǔ)器位置。
特殊的算術(shù)操作
| 指令 | 效果 | 描述 |
|---|---|---|
| imull |
有符號(hào)全64位乘法 | |
| mull |
無(wú)符號(hào)全64位乘法 | |
| cltd |
轉(zhuǎn)為4字 | |
| idivl |
|
有符號(hào)除法 |
| divl |
|
無(wú)符號(hào)除法 |
這些操作提供了有符號(hào)和無(wú)符號(hào)的全64位乘法和除法。一對(duì)寄存器 %edx 和 %eax 組成一個(gè)64位的四字。
imull和mull兩條指令都要求一個(gè)參數(shù)必須在寄存器%eax中,而另一個(gè)作為指令的源操作數(shù)給出。然后乘積存放在寄存器%edx(高32位)和%eax(低32位)中。
有符號(hào)除法指令idivl將寄存器%edx(高32位)和%eax(低32位)中的64位數(shù)作為被除數(shù),而除數(shù)作為指令的操作數(shù)給出。指令將商存儲(chǔ)在寄存器%eax中,將余數(shù)存儲(chǔ)在寄存器%edx中。無(wú)符號(hào)除法使用的是divl指令,通常會(huì)事先將寄存器%edx設(shè)置為0。
cltd指令將%eax符號(hào)擴(kuò)展到%edx,通常用來(lái)設(shè)置被除數(shù)。
條件碼
除了整數(shù)寄存器,CPU還維護(hù)著一組單個(gè)位的條件碼(condition code)寄存器,它們描述了最近的算術(shù)或邏輯操作的屬性??梢詸z測(cè)這些寄存器來(lái)執(zhí)行條件分支指令。最常用的條件碼有:
- CF:進(jìn)位標(biāo)志。最近的操作使最高位產(chǎn)生了進(jìn)位??梢杂脕?lái)檢查無(wú)符號(hào)操作數(shù)的溢出。
- ZF:零標(biāo)志。最近的操作得到的結(jié)果是0。
- SF:符號(hào)標(biāo)志。最近的操作得到的結(jié)果為負(fù)數(shù)。
- OF:溢出標(biāo)志。最近的操作導(dǎo)致一個(gè)補(bǔ)碼溢出(正溢出或負(fù)溢出)。
leal指令不改變?nèi)魏螚l件碼,因?yàn)樗怯脕?lái)進(jìn)行地址計(jì)算的。其它整數(shù)算術(shù)操作指令都會(huì)設(shè)置條件碼。對(duì)于邏輯操作,例如XOR,進(jìn)位標(biāo)志和溢出標(biāo)志會(huì)設(shè)置成0。對(duì)于移位操作,進(jìn)位標(biāo)志將設(shè)置為最后一個(gè)被移出的位,而溢出標(biāo)志設(shè)置為0。INC和DEC指令會(huì)設(shè)置溢出和零標(biāo)志,但是不會(huì)改變進(jìn)位標(biāo)志。
| 指令 | 基于 | 描述 |
|---|---|---|
| CMP |
比較 | |
| cmpb | 比較字節(jié) | |
| cmpw | 比較字 | |
| cmpl | 比較雙字 | |
| TEST |
測(cè)試 | |
| testb | 測(cè)試字節(jié) | |
| testw | 測(cè)試字 | |
| testl | 測(cè)試雙字 |
比較和測(cè)試指令只設(shè)置條件碼而不改變?nèi)魏纹渌拇嫫?。所以,除了不設(shè)置寄存器,CMP指令和SUB指令的行為是一樣的,TEST和ADD指令的行為也是一樣的。
訪問(wèn)條件碼
條件碼通常不會(huì)直接讀取。每條SET指令根據(jù)條件碼的某個(gè)組合,將一個(gè)字節(jié)設(shè)置為0或1。SET指令名字的不同后綴指明了它們所考慮的條件碼的組合,而不是操作數(shù)的大小,請(qǐng)注意與其他組指令在名字上的這點(diǎn)區(qū)別。
| 指令 | 同義名 | 效果 | 設(shè)置條件 |
|---|---|---|---|
| sete |
setz | 相等/零 | |
| setne |
setnz | 不等/非零 | |
| sets |
負(fù)數(shù) | ||
| setns |
非負(fù)數(shù) | ||
| setg |
setnle | 大于(有符號(hào) |
|
| setge |
setnl | 大于等于(有符號(hào) |
|
| setl |
setnge | 小于(有符號(hào) |
|
| setle |
setng | 大于等于(有符號(hào) |
|
| seta |
setnbe | 超過(guò)(無(wú)符號(hào) |
|
| setae |
setnb | 超過(guò)或相等(無(wú)符號(hào) |
|
| setb |
setnae | 低于(無(wú)符號(hào) |
|
| setbe |
setna | 低于或相等(無(wú)符號(hào) |
一條SET指令的目的操作數(shù)是8個(gè)單字節(jié)寄存器元素之一,或是存儲(chǔ)一個(gè)字節(jié)的存儲(chǔ)器位置,將這個(gè)字節(jié)設(shè)置成0或1。為了得到一個(gè)32位結(jié)果,我們必須對(duì)最高的24位清零。某些底層的機(jī)器指令可能有多個(gè)名字,我們稱為“同義名”(synonym)。比如,setg(設(shè)置大于)和setnle(設(shè)置不小于等于)指的就是同一條機(jī)器指令。
跳轉(zhuǎn)指令
正常執(zhí)行的情況下,指令按照它們出現(xiàn)的順序一條一條地執(zhí)行。跳轉(zhuǎn)(jump)指令會(huì)導(dǎo)致執(zhí)行切換到程序中一個(gè)全新的位置。在匯編代碼中,這些跳轉(zhuǎn)的目的地通常用一個(gè)標(biāo)號(hào)(label)指明。
| 指令 | 同義名 | 跳轉(zhuǎn)條件 | 描述 |
|---|---|---|---|
| jmp |
1 | 直接跳轉(zhuǎn) | |
| jmp |
1 | 間接跳轉(zhuǎn) | |
| je |
jz | 相等/零 | |
| jne |
jnz | 不等/非零 | |
| js |
負(fù)數(shù) | ||
| jns |
非負(fù)數(shù) | ||
| jg |
jnle | 大于(有符號(hào) |
|
| jge |
jnl | 大于等于(有符號(hào) |
|
| jl |
jnge | 小于(有符號(hào) |
|
| jle |
jng | 大于等于(有符號(hào) |
|
| ja |
jnbe | 超過(guò)(無(wú)符號(hào) |
|
| jae |
jnb | 超過(guò)或相等(無(wú)符號(hào) |
|
| jb |
jnae | 低于(無(wú)符號(hào) |
|
| jbe |
jna | 低于或相等(無(wú)符號(hào) |
jmp指令是無(wú)條件跳轉(zhuǎn),它可以是直接跳轉(zhuǎn),即跳轉(zhuǎn)目標(biāo)是作為指令的一部分編碼的;也可以是間接跳轉(zhuǎn),即跳轉(zhuǎn)目標(biāo)是從寄存器或存儲(chǔ)器位置中讀出的。
匯編語(yǔ)言中,直接跳轉(zhuǎn)是給出一個(gè)標(biāo)號(hào)作為跳轉(zhuǎn)目標(biāo)的。
jmp .L1
movl (%eax),%edx
.L1:
popl %edx
而間接跳轉(zhuǎn)的寫(xiě)法是“*”后面緊跟一個(gè)操作數(shù)指示符。
jmp *%eax // 用寄存器%eax中的值作為跳轉(zhuǎn)目標(biāo)
jmp *(%eax) // 以%eax中的值作為讀地址,從存儲(chǔ)器中讀出跳轉(zhuǎn)目標(biāo)
其它跳轉(zhuǎn)指令都是有條件跳轉(zhuǎn),它們根據(jù)條件碼的組合,或者跳轉(zhuǎn),或者繼續(xù)執(zhí)行代碼序列中的下一條指令。這些指令的名字和它們的跳轉(zhuǎn)條件與SET指令是相匹配的。
過(guò)程
一個(gè)過(guò)程調(diào)用包括將數(shù)據(jù)(以過(guò)程參數(shù)和返回值的形式)和控制從代碼的一部分傳遞到另一部分。另外,它還必須在進(jìn)入時(shí)為過(guò)程的局部變量分配空間,并在退出時(shí)釋放這些空間。大多數(shù)機(jī)器,只提供轉(zhuǎn)移控制到過(guò)程中和從過(guò)程中轉(zhuǎn)移出控制這種簡(jiǎn)單的命令。數(shù)據(jù)傳遞、局部變量的分配和釋放通過(guò)操縱程序棧來(lái)實(shí)現(xiàn)。

機(jī)器用棧來(lái)傳遞過(guò)程參數(shù)、存儲(chǔ)返回信息、保存寄存器用于以后恢復(fù),以及本地存儲(chǔ)。為單個(gè)過(guò)程分配的那部分棧稱為棧幀(stack frame)。棧幀的最頂端以兩個(gè)指針界定,寄存器%ebp為幀指針,而寄存器%esp為棧指針。當(dāng)程序執(zhí)行時(shí),棧指針可以移動(dòng),因此大多數(shù)信息的訪問(wèn)都是相對(duì)于幀指針的。
假設(shè)過(guò)程P(調(diào)用者)調(diào)用過(guò)程Q(被調(diào)用者),則Q的參數(shù)放在P的棧幀中。另外,當(dāng)P調(diào)用Q時(shí),P中的返回地址被壓入棧中,形成P的棧幀的末尾。返回地址就是當(dāng)程序從Q返回時(shí)應(yīng)該繼續(xù)執(zhí)行的地方。Q的棧幀從保存的幀指針的值(例如%ebp)開(kāi)始,后面是保存的其他寄存器的值。
轉(zhuǎn)移控制
| 指令 | 描述 |
|---|---|
| call |
過(guò)程調(diào)用 |
| call |
過(guò)程調(diào)用 |
| leave | 為返回準(zhǔn)備棧 |
| ret | 從過(guò)程調(diào)用中返回 |
call指令有一個(gè)目標(biāo),即指明被調(diào)用過(guò)程起始的指令地址。同跳轉(zhuǎn)一樣,調(diào)用可以直接的,也可以是間接的。call指令的效果是將返回地址入棧,并跳轉(zhuǎn)到被調(diào)用過(guò)程的起始處。返回地址是在程序中緊跟在call后面的那條指令的地址,這樣當(dāng)被調(diào)用過(guò)程返回時(shí),執(zhí)行會(huì)從此處繼續(xù)。
ret指令從棧中彈出地址,并跳轉(zhuǎn)到這個(gè)位置。leave指令可以使棧做好準(zhǔn)備,使棧指針指向前面call指令存儲(chǔ)返回地址的位置。
leave
# 等價(jià)于
movl %ebp,%esp
popl %ebp
程序寄存器組是唯一能被所有過(guò)程共享的資源。雖然在給定時(shí)刻只能有一個(gè)過(guò)程是活動(dòng)的,但是我們必須保證當(dāng)一個(gè)過(guò)程(調(diào)用者)調(diào)用另一個(gè)過(guò)程(被調(diào)用者)時(shí),被調(diào)用者不會(huì)覆蓋某個(gè)調(diào)用者稍后會(huì)使用的寄存器的值。為此,IA32采用了一組統(tǒng)一的寄存器使用慣例,所有的過(guò)程都必須遵守,包括程序庫(kù)中的過(guò)程。
根據(jù)慣例,寄存器%eax、%edx、%ecx被劃分為調(diào)用者保存寄存器。當(dāng)過(guò)程Q調(diào)用Q時(shí),Q可以覆蓋這些寄存器,而不會(huì)破壞任何P所需要的數(shù)據(jù)。另一方面,寄存器%ebx、%esi、%edi被劃分為被調(diào)用者保存寄存器。這意味著Q必須在覆蓋這些寄存器的值之前,先把它們保存到棧中,并在返回前恢復(fù)它們,因?yàn)镻(或某個(gè)更高層次的過(guò)程)可能會(huì)在后面的計(jì)算中需要這些值。此外,根據(jù)慣例,必須保存寄存器%ebp和%esp。
棧和鏈接慣例使得過(guò)程可以遞歸地調(diào)用它們自身。因?yàn)槊總€(gè)調(diào)用在棧中都有它自己的私有空間,多個(gè)未完成調(diào)用的局部變量還不會(huì)相互影響。此外,棧的原則很自然地提供了適當(dāng)?shù)牟呗裕?dāng)過(guò)程被調(diào)用時(shí)分配局部變量,當(dāng)返回時(shí)釋放存儲(chǔ)。
遞歸調(diào)用一個(gè)函數(shù)本身與調(diào)用其他函數(shù)是一樣的。棧規(guī)則提供了一種機(jī)制,每次函數(shù)調(diào)用都有它自己私有的狀態(tài)信息(保存的返回位置、棧指針和被調(diào)用者保存寄存器的值)存儲(chǔ)。如果需要,它還可以提供局部變量的存儲(chǔ)。分配和釋放的棧規(guī)則很自然地就與函數(shù)調(diào)用—返回的順序匹配。這種實(shí)現(xiàn)函數(shù)調(diào)用和返回的方法甚至對(duì)于更復(fù)雜的情況也適用,包括相互遞歸調(diào)用(例如,當(dāng)過(guò)程P調(diào)用Q,Q再調(diào)用P)。
將IA32擴(kuò)展到64位
以下是x86-64的標(biāo)準(zhǔn)數(shù)據(jù)類(lèi)型大小,與IA32比較,長(zhǎng)整數(shù)和指針需要8個(gè)字節(jié),而IA32只需要4個(gè)字節(jié)。
| C聲明 | Intel數(shù)據(jù)類(lèi)型 | 匯編代碼后綴 | x86-64大小(字節(jié)) | IA32大小 |
|---|---|---|---|---|
| char | 字節(jié) | b | 1 | 1 |
| short | 字 | w | 2 | 2 |
| int | 雙字 | l | 4 | 4 |
| long int | 四字 | q | 8 | 4 |
| long long int | 四字 | q | 8 | 8 |
| char * | 四字 | q | 8 | 4 |
| float | 單精度 | s | 4 | 4 |
| double | 雙精度 | l | 8 | 8 |
| long double | 擴(kuò)展精度 | t | 10/16 | 10/12 |
Intel進(jìn)入64位計(jì)算機(jī)領(lǐng)域打響的第一槍是Itanium處理器,它基于一種全新的指令集,稱為“IA64”。Intel的一貫的策略是每次引入新一代微處理器時(shí)還要維持后向兼容性,而這次不同,IA64是基于與Hewlett-Packard一起開(kāi)發(fā)的一種嶄新的方法。IA64的實(shí)現(xiàn)是很難的,因此第一批Itanium芯片直到2001年才出現(xiàn),而且在真實(shí)應(yīng)用上沒(méi)有達(dá)到預(yù)期的性能。大多數(shù)用戶寧愿使用比較便宜并且常常更快的基于IA32的系統(tǒng)。
同時(shí),Intel的主要競(jìng)爭(zhēng)對(duì)手,Advanced Micro Devices(AMD)看到一個(gè)機(jī)會(huì)去利用Intel在IA64上的失敗。2003年,AMD推出了基于“x86-64”指令集的64位微處理器。顧名思義,x86-64是Intel指令集到64位的一個(gè)演化。它保持了與IA32完全的后向兼容性,并且又增加了新的數(shù)據(jù)格式,以及其他一些特性,使得能力更強(qiáng),性能更高。通過(guò)x86-64,AMD獲得了以前屬于Intel的一些高端市場(chǎng)。AMD后來(lái)將這個(gè)指令集更名為AMD64,但是大家還是對(duì)“x86-64”這個(gè)名字更喜歡一些。
x86-64下的通用寄存器組,與IA32相比,有以下區(qū)別:
- 寄存器的數(shù)量翻倍至16個(gè)。
- 所有的寄存器都是64位長(zhǎng)。IA32寄存器的64位擴(kuò)展分別命名為%rax、%rcx、%rdx、%rbx、%rsi、%rdi、%rbp和%rsp,新增加的寄存器命名為%r8~%r15。
- 可以直接訪問(wèn)每個(gè)寄存器的低32位。這就給了我們IA32中熟悉的那些寄存器:%eax、%ecx、%edx、%ebx、%esi、%edi、%ebp和%esp,以及8個(gè)新32位寄存器:%r8d~%r15d。
- 可以直接訪問(wèn)每個(gè)寄存器的低16。新寄存器的字大小版本命名為 %r8w~%r15w。
- 可以直接訪問(wèn)每個(gè)寄存器的低8位,在IA32中只有對(duì)前4個(gè)寄存器(%al、%cl、%dl和%bl)才可以這樣。其他IA32寄存器的字節(jié)大小版本命名為%sil、%dil、%bpl和%spl。新寄存器的字節(jié)大小版本命名為%r8b~%r15b。
- 為了向后兼容,具有單字節(jié)操作數(shù)的指令可以直接訪問(wèn)%rax、%rcx、%rdx和%rbx的第二個(gè)字節(jié),即%ah、%ch、%dh和%bh。

同IA32一樣,大多數(shù)寄存器可以互換使用,但是有一些特殊情況。寄存器%rsp有特殊的狀態(tài),它會(huì)保存指向棧頂元素的指針。與IA32不同的是,沒(méi)有幀指針寄存器,可以將%rbp作為通用寄存器使用。
| 指令 | 類(lèi)型 | 描述 |
|---|---|---|
| movabsq |
傳送絕對(duì)四字 | |
| MOV |
傳送 | |
| movq | 傳送四字 | |
| MOVS |
符號(hào)擴(kuò)展傳遞 | |
| movsbq | 符號(hào)擴(kuò)展字節(jié)傳送四字 | |
| movswq | 符號(hào)擴(kuò)展字傳送四字 | |
| movslq | 符號(hào)擴(kuò)展雙字傳送四字 | |
| MOVZ |
零擴(kuò)展傳遞 | |
| movzbq | 零擴(kuò)展字節(jié)傳送四字 | |
| movzwq | 零擴(kuò)展字傳送四字 | |
| pushq |
|
將四字壓棧 |
| popq |
|
將四字出棧 |
這些指令是對(duì)IA32傳送指令的補(bǔ)充。movabsq只允許立即數(shù)(用表示)作為源值,其他指令允許立即數(shù)、寄存器或存儲(chǔ)器(用
表示)。有些指令要求目的是寄存器(用
表示),而其他的指令允許用寄存器或存儲(chǔ)器作為目的(用
表示)。
同樣地,算術(shù)運(yùn)算指令和控制指令也增加了對(duì)四字的擴(kuò)展支持。
| 指令 | 效果或根據(jù) | 描述 |
|---|---|---|
| imulq |
有符號(hào)全乘法 | |
| mulq |
無(wú)符號(hào)全乘法 | |
| cltq | 將 |
|
| cqto | 將 |
|
| idivq |
|
有符號(hào)除法 |
| divq |
|
無(wú)符號(hào)除法 |
| cmpq |
比較四字 | |
| testq |
測(cè)試四字 |
過(guò)程調(diào)用的x86-64實(shí)現(xiàn)與IA32實(shí)現(xiàn)有很大的不同,通過(guò)將寄存器組翻倍,程序不再需要依賴于棧來(lái)存儲(chǔ)和獲取過(guò)程信息。這極大地減少了過(guò)程調(diào)用和返回的開(kāi)銷(xiāo)。以下是x86-64實(shí)現(xiàn)過(guò)程的一些重點(diǎn):
- 參數(shù)(最多是前六個(gè))通過(guò)寄存器傳遞到過(guò)程,而不是在棧上。這消除了在棧上存儲(chǔ)和檢索值的開(kāi)銷(xiāo)。
- callq指令將一個(gè)64位返回地址存儲(chǔ)在棧上。
- 許多函數(shù)不需要棧幀。
- 函數(shù)最多可以訪問(wèn)超過(guò)當(dāng)前棧指針值128個(gè)字節(jié)的棧上存儲(chǔ)空間(地址低于當(dāng)前棧指針的值)。這允許一些函數(shù)將信息存儲(chǔ)在棧上而無(wú)需修改棧指針。
- 沒(méi)有幀指針。作為替代,對(duì)棧位置的引用相對(duì)于棧指針。大多數(shù)函數(shù)在調(diào)用開(kāi)始時(shí)分配所需要的整個(gè)棧存儲(chǔ),并保存棧指針指向固定位置。
- 同IA32一樣,有些寄存器被指定為被調(diào)用者保存寄存器。任何要修改這些寄存器的過(guò)程都必須保存并恢復(fù)它們。
函數(shù)需要棧幀的原因如下:
- 局部變量太多,不能都放在寄存器中。
- 有些局部變量是數(shù)組或者結(jié)構(gòu)。
- 函數(shù)用取地址操作符(&)來(lái)計(jì)算一個(gè)局部變量的地址。
- 函數(shù)必須將棧上的某些參數(shù)傳遞到另一個(gè)函數(shù)。
- 在修改一個(gè)被調(diào)用者保存寄存器之前,函數(shù)需要保存它的狀態(tài)。
當(dāng)上述條件有任何一條被滿足時(shí),函數(shù)編譯出來(lái)的代碼就會(huì)創(chuàng)建棧幀。