機(jī)器級(jí)代碼
兩種抽像:
- (Instruction Set Architecture,ISA) 定義機(jī)器級(jí)田可以存入格式和行為,定義了處理器狀態(tài),指令的格式,以及每條指令對(duì)狀態(tài)的影響。
- 機(jī)器機(jī)程序使用的內(nèi)存地址是虛擬地址,提供的內(nèi)存模型看上去是一個(gè)非常大的按字節(jié)尋址的數(shù)組。
一些C 語(yǔ)言中隱藏的處理器狀態(tài):
-
程序計(jì)數(shù)器 (PC,
%rip) 給出將要執(zhí)行的下一條指令在內(nèi)存中的地址。 - 整數(shù)寄存器(包含16個(gè)命名位置),分別存儲(chǔ)64位的值,用來(lái)存儲(chǔ) 地址,整數(shù)數(shù)據(jù), 記錄狀態(tài), 臨時(shí)數(shù)據(jù), 局部變量.
-
條件碼寄存器 保存最近執(zhí)行的算術(shù)或邏輯指令的狀態(tài)信息。用來(lái)實(shí)現(xiàn)控制或數(shù)據(jù)流中的條件變化,比如說(shuō)用來(lái)實(shí)現(xiàn)
if或switch語(yǔ)句。 - 一組向量寄存器 用來(lái)存放一個(gè)或多個(gè)整數(shù)或浮點(diǎn)數(shù)值。
目前一般 x86-64的虛擬地址是 由64位的字來(lái)表示,在目前的實(shí)現(xiàn)中這些地址的高16位必須設(shè)置為0,所以實(shí)現(xiàn)目前一般最高可以指定 64TB 內(nèi)存地址空間。
代碼示例
long mult2(long,long);
void multstore(long x, long y, long *dest){
long t = mult2(x,y);
*dest = t;
}
使用 gcc -Og -S mstore.c 編譯之后得到的文件的主要內(nèi)容如下:
.global _multstore
_multstore:
pushq %rbp
movq %rsp, %rbp
pushq %rbx
pushq %rax
movq %rdx, %rbx
callq _mult2
movq %rax, (%rbx)
addq $8, %rsp
popq %rbx
popq %rbp
retq
上面編譯選項(xiàng)使用的是 -S 如果使用 -c 則可以得到匯編的目標(biāo)文件。
objdump 工具可以用來(lái)反匯編目標(biāo)文件。
? ch3 objdump -d mstore.o
mstore.o: file format Mach-O 64-bit x86-64
Disassembly of section __TEXT,__text:
_multstore:
0: 55 pushq %rbp
1: 48 89 e5 movq %rsp, %rbp
4: 53 pushq %rbx
5: 50 pushq %rax
6: 48 89 d3 movq %rdx, %rbx
9: e8 00 00 00 00 callq 0 <_multstore+0xE>
e: 48 89 03 movq %rax, (%rbx)
11: 48 83 c4 08 addq $8, %rsp
15: 5b popq %rbx
16: 5d popq %rbp
17: c3 retq
將上面的輸出整理成下表:
| Offset | Bytes | Equivalent assembly language |
|---|---|---|
| 0: | 55 | pushq %rbp |
| 1: | 48 89 e5 | movq %rsp, %rbp |
| 4: | 53 | pushq %rbx |
| 5: | 50 | pushq %rax |
| 6: | 48 89 d3 | movq %rdx, %rbx |
| 9: | e8 00 00 00 00 | callq 0 <_multstore+0xE> |
| e: | 48 89 03 | movq %rax, (%rbx) |
| 11: | 48 83 c4 08 | addq $8, %rsp |
| 15: | 5b | popq %rbx |
| 16: | 5d | popq %rbp |
| 17: | c3 | retq |
上面一共 24個(gè)字節(jié)分成了 11 組,代表了11條指令。 右邊是等價(jià)的匯編語(yǔ)言。
比如 c3 字節(jié)表示的機(jī)器碼對(duì)應(yīng)了匯編指令就是 retq。
AT&T 語(yǔ)法與 Intel 語(yǔ)法
gcc 與 objdump 等工具默認(rèn)使用 AT&T 語(yǔ)法。也可以使用如下指令生成 Intel 語(yǔ)法格式的匯編。 gcc -Og -S -masm=intel mstore.c
對(duì)應(yīng)的輸出的代碼如下:
.intel_syntax noprefix
global _multstore
_multstore:
push rbp
mov rbp, rsp
push rbx
push rax
mov rbx,rdx
call _mult2
mov qword ptr [rbx], rax
add rsp, 8
pop rbx
pop rbp
ret
這兩種語(yǔ)法的對(duì)比如下:
| 語(yǔ)法 | AT&T | Intel | 說(shuō)明 |
|---|---|---|---|
| 指令名稱 | pushq | push | Intel 語(yǔ)法省略了指示大小的后綴 |
| 寄存器引用 | %rbp | rbp | Intel 語(yǔ)法省略了寄存器前面的 % 符號(hào) |
| 地址引用 | (%rbp) | qword ptr [rbp] | 內(nèi)存位置 |
| 字面量 | $8 | 8 | Intel 語(yǔ)法省略了字面量前面的 $ 符號(hào) |
| 多操作數(shù)順序 | addq $8, %rsp | add rsp, 8 | 兩種語(yǔ)法的操作數(shù)順序相反 |
關(guān)于格式的注解
gcc -Og -S mstore.c 實(shí)際的完整如下:
.section __TEXT,__text,regular,pure_instructions
.macosx_version_min 10, 13
.globl _multstore ## -- Begin function multstore
.p2align 4, 0x90
_multstore: ## @multstore
.cfi_startproc
## BB#0:
pushq %rbp
Lcfi0:
.cfi_def_cfa_offset 16
Lcfi1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Lcfi2:
.cfi_def_cfa_register %rbp
pushq %rbx
pushq %rax
Lcfi3:
.cfi_offset %rbx, -24
movq %rdx, %rbx
callq _mult2
movq %rax, (%rbx)
addq $8, %rsp
popq %rbx
popq %rbp
retq
.cfi_endproc
## -- End function
.subsections_via_symbols
其中以 . 開(kāi)頭的行都是指導(dǎo)匯編器和鏈接器工作的偽指令。
匯編代碼沒(méi)有注釋是很難看的。
基本要求是對(duì)于指令在右給出注釋或?qū)?yīng)的可能 C 語(yǔ)言方法。
函數(shù)的參數(shù)需要給出對(duì)應(yīng) C 語(yǔ)言的簽名,參數(shù)的說(shuō)明。
把 C 程序與匯編代碼結(jié)合起來(lái)
- 用匯編編寫(xiě)完整的匯編代碼(主要是函數(shù)),放進(jìn)一個(gè)獨(dú)立的匯編文件中,讓匯編器和鏈接器反它和用 C 語(yǔ)言書(shū)寫(xiě)的代碼合并起來(lái)。
- 用 GCC 內(nèi)聯(lián)匯編的特性,用
__asm__偽指令可以在 C 程序中包含簡(jiǎn)短的匯編代碼。
C 代碼調(diào)用外部匯編函數(shù)代碼
編寫(xiě) add.s 文件,內(nèi)容如下:
# 兩整數(shù)想加,返回和
# @signature: int (int a,int b)
# @body: return a + b
# @param a - %edi
# @param b - %esi
# @return 返回兩數(shù)想加和 - %eax
.global _add
_add:
pushq %rbp # 保存老的基址指針(即將 rbp 寄存器的值壓入當(dāng)前棧頂)
movq %rsp, %rbp # 將當(dāng)前棧指針作為新的基址指針
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl -4(%rbp), %esi
addl -8(%rbp), %esi
movl %esi, %eax
popq %rbp # 將棧頂保存的老的基址指針恢復(fù)到 rbp 寄存器
retq
編寫(xiě) add_main.c 內(nèi)容如下:
#include<stdio.h>
extern int add(int a,int b); // 在 add.s 中實(shí)現(xiàn)
int main(int argc, char const *argv[]) {
int sum = add(3,4);
printf("3 + 4 = %d\n", sum);
return 0;
}
編譯與運(yùn)行:
? ch3 cc -o add_main add_main.c add.s
? ch3 ./add_main
3 + 4 = 7
? ch3