彙編
儲存程式計算機
圖靈機
馮諾依曼機
運算器、控制器、儲存器、輸入和輸出裝置
CPU由運算器(算術邏輯單元ALU),控制器和暫存器組成
暫存器程式計數器PC,在IA32(x86-32)中是EIP,指示要執行的下一條指令在儲存器中的地址
x86-32彙編
- 通用暫存器
4個資料暫存器EAX,EBX,ECX,EDX
2個指標暫存器ESP,EBP
2個變址和指標暫存器ESI,EDI - 控制暫存器
1個指令指標暫存器EIP
1個標誌暫存器EFlags - 段暫存器
6個段暫存器CS,ES,SS,DS,FS,GS
32位資料暫存器更具通用性,可以暫存資料,如ALU的運算結果,還可以作為指標暫存器
程式碼段暫存器CS,指令儲存在程式碼段中,使用CS:EIP指明指令地址
堆疊段暫存器SS,每個程序都有核心態堆疊和使用者態堆疊
定址方式和彙編指令
彙編指令包括操作碼和運算元
操作碼,即彙編指令
movb, movw, movl, movq
運算元
- 立即數
即常數,如$8
- 暫存器數
如%eax
- 儲存器引用
定址方式
- 暫存器定址register
操作的是暫存器,不與記憶體互動,如movl %eax, %edx
- 立即定址immediate
$
符號後跟一個數值,如movl $0x123, %edx
- 直接定址direct
直接用數值表示記憶體地址,如movl 0x123, %edx
- 間接定址indirect
暫存器加個小括號,表示暫存器中儲存的是記憶體地址,如move (%ebx), %edx
- 變址定址displaced
在原地址上加一個立即數,如movl 4(%ebx), %edx
AT&T格式彙編,也是Linux核心使用的彙編格式
一般全是大寫字母的為Intel彙編,全是小寫字母的是AT&T彙編
pushl/popl
call/ret
pushl %eax
將EAX暫存器值壓棧,實際上做了兩個動作
subl $4, %esp
movl %eax, (%esp)
堆疊是向低地址增長的,所以用subl
指令
popl %eax
從堆疊棧頂取一個儲存單元放入EAX暫存器,實際上做了兩個動作
movl (%esp), %eax
addl $4, %esp
call 0x12345
實際上做了兩個動作,將EIP壓棧,並將地址0x12345傳入EIP暫存器,由硬體一次性完成
ret
將堆疊棧頂的一個儲存單元放入EIP暫存器,該動作由硬體一次性完成
儲存程式
函式呼叫堆疊
中斷
ESP堆疊暫存器
EBP基址暫存器
x86體系結構,堆疊空間是從高地址向低地址增長的,即向上增長
堆疊操作push/pop
在函式傳遞返回值時,透過EAX暫存器來儲存返回值,若有多個返回值,EAX返回記憶體地址
堆疊提供區域性變數的空間,編譯器會預留足夠的棧空間以儲存區域性變數,但是早期編譯器並沒有這麼做,而是要求區域性變數宣告在函式體前面
透過堆疊傳遞引數的方法是從右向左以此壓棧,如形參的壓棧順序
C語言內嵌彙編
__asm__ __volatile__(
彙編語句模板:
輸出部分:
輸入部分:
破壞描述部分:
);
示例程式碼
#include<stdio.h>
int main(){
/* val1 + val2 = val3 */
unsigned int val1= 1;
unsigned int val2= 2;
unsigned int val3= 0;
printf("val1:%d, val2:%d, val3:%d\n", val1, val2, val3);
asm volatile(
"movl $0, %%eax\n\t" // clear %eax
"addl %1, %%eax\n\t" // %eax+= val1
"addl %2, %%eax\n\t" // %eax+= val2
"movl %%eax, %0\n\t" // val2= %eax
: "=m" (val3) // =m mean only write output memory variable
: "c" (val1), "d" (val2) // input c or d mean %ecx/%edx
);
printf("val1:%d + val2:%d = val3:%d\n", val1, val2, val3);
return 0;
}
__asm__
是gcc關鍵字asm
的宏定義,是內嵌彙編關鍵字
__volatile__
是gcc關鍵字volatile
的宏定義,告訴編譯器不要最佳化程式碼
內嵌彙編的暫存器前有兩個%
,一個%
加一個數字表示第二部分輸出,第三部分輸入或第四部分破壞描述
%1
指的是val1
,用c
修飾,即存入ECX暫存器
%2
指的是val2
,用d
修飾,即存入EDX暫存器
%0
指的是val3
,用=m
修飾,即寫入記憶體變數
內嵌彙編語法
內嵌彙編常用的修飾限定符
通用暫存器
限定符 | 說明 |
---|---|
a |
EAX暫存器 |
b |
EBX暫存器 |
c |
ECX暫存器 |
d |
EDX暫存器 |
s |
ESI暫存器 |
D |
EDI暫存器 |
q |
將輸入變數放入eax,ebx,ecx,edx中的一個 |
r |
將輸入變數放入eax,ebx,ecx,esi,edi中的一個 |
A |
將eax和edx合成為一個64位暫存器 |
記憶體
限定符 | 說明 |
---|---|
m |
記憶體變數 |
o |
運算元為記憶體變數,定址方式是偏移量定址 |
v |
運算元為記憶體變數,定址方式不是偏移量定址 |
. |
運算元為記憶體變數,定址方式是自動增量 |
p |
運算元是一個合法的記憶體地址 |
運算元型別
| =
| 運算元在指令中是隻寫的 |
| +
| 運算元在指令中是讀寫的 |
暫存器或記憶體
限定符 | 說明 |
---|---|
g |
將輸入變數放入eax,ebx,ecx,edx中的一個或作為記憶體變數 |
x |
運算元可以是任何型別 |
立即數
限定符 | 說明 |
---|---|
i |
0~31之間的立即數,用於32位移位指令 |
J |
0~63之間的立即數,用於64位移位指令 |
N |
0~255之間的立即數,用於out 指令 |
l |
立即數 |
n |
立即數,有些系統不支援除字以外的立即數,這些系統應該使用n |
浮點數
限定符 | 說明 |
---|---|
f |
浮點數 |
t |
第一個浮點暫存器 |
u |
第二個浮點暫存器 |
G |
標準的80387浮點常數 |
% |
該運算元可以和下一個運算元交換位置 |
示例
int main(){
int input, output, temp;
intput= 1;
__asm__ __volatile__(
"movl $0, %%eax;\n\t"
"movl %%eax, %1;\n\t"
"movl %2, %%eax;\n\t"
"movl %%eax, %0;\n\t"
: "=m" (output), "=m" (temp)
: "r" (input)
: "eax"
);
printf("%d, %d\n", temp, output);
return 0;
}
破壞描述部分"eax"
,表示EAX暫存器會被改動