1. 常用的X86 汇编指令
x86 汇编指令集包含通用数据操作、控制转移、内存访问、系统调用等类别,以下是最常用的核心指令,按功能分类整理,兼顾 16 位 / 32 位 / 64 位架构的通用场景:
一、数据传输指令
用于寄存器、内存、立即数之间的数据搬运,是最基础的指令类型。
| 指令 | 功能说明 | 示例 |
|---|---|---|
MOV |
数据传送(寄存器→寄存器、立即数→寄存器、内存→寄存器等) | MOV EAX, EBX(EBX→EAX)MOV [ESI], 100(立即数 100 存入 ESI 指向的内存) |
PUSH |
将数据压入栈顶 | PUSH EBP(EBP 入栈) |
POP |
从栈顶弹出数据到寄存器 / 内存 | POP ESP(栈顶数据→ESP) |
LEA |
加载有效地址(计算内存地址并送入寄存器,不访问内存) | LEA EAX, [EBX+ECX*4](计算 EBX+4×ECX 地址→EAX) |
XCHG |
交换两个操作数的数据 | XCHG EAX, EDX(EAX 与 EDX 交换) |
MOVSX/MOVZX |
带符号 / 零扩展传送(如 8 位→32 位,保持符号位 / 补零) | MOVSX EAX, BL(BL(8 位)带符号扩展→EAX(32 位)) |
二、算术运算指令
处理加减乘除、比较等数值计算,影响标志寄存器(CF、ZF、SF 等)。
| 指令 | 功能说明 | 示例 |
|---|---|---|
ADD |
加法运算 | ADD EAX, 5(EAX = EAX + 5) |
SUB |
减法运算 | SUB EBX, ECX(EBX = EBX - ECX) |
INC/DEC |
自增 1 / 自减 1 | INC EDX(EDX += 1)DEC [ESI](ESI 指向的内存值 - 1) |
MUL/IMUL |
无符号乘法 / 有符号乘法(8/16/32 位,结果存 AX/DX:AX/EDX:EAX) | MUL BL(AX = AL × BL,无符号)IMUL ECX, EDX, 2(ECX = EDX × 2,有符号) |
DIV/IDIV |
无符号除法 / 有符号除法(被除数存 AX/DX:AX/EDX:EAX,商 / 余数存 AX/DX 等) | DIV CX(DX:AX ÷ CX,商 AX,余数 DX,无符号) |
CMP |
比较运算(SUB 但不保存结果,仅影响标志位) | CMP EAX, 0(EAX 与 0 比较,设置 ZF 等标志) |
NEG |
取负(按位取反 + 1) | NEG EBX(EBX = -EBX) |
三、逻辑运算与位操作指令
处理按位运算、移位、逻辑判断,常用于位掩码、条件判断。
| 指令 | 功能说明 | 示例 |
|---|---|---|
AND/OR/XOR |
按位与 / 或 / 异或 | AND EAX, 0xFF(EAX 与 0xFF 按位与)XOR EBX, EBX(EBX 清零) |
NOT |
按位取反 | NOT ECX(ECX 按位取反) |
SHL/SHR |
逻辑左移 / 逻辑右移(右侧补 0) | SHL EDX, 2(EDX 左移 2 位,乘 4) |
SAR |
算术右移(左侧补符号位) | SAR EAX, 1(EAX 带符号右移 1 位,除 2) |
TEST |
按位与测试(同 AND 但不保存结果,仅设标志位) | TEST EAX, 1(判断 EAX 奇偶,结果影响 ZF) |
四、控制转移指令
改变程序执行流程(分支、循环、调用),依赖标志寄存器或直接跳转。
| 指令 | 功能说明 | 示例 |
|---|---|---|
JMP |
无条件跳转 | JMP label(跳转到 label 标签) |
JE/JZ |
相等 / 零标志置位时跳转(CMP 结果为 0) | JE exit(若相等则跳 exit) |
JNE/JNZ |
不相等 / 零标志未置位时跳转 | JNE loop(若不等则跳 loop) |
JG/JGE |
有符号数大于 / 大于等于时跳转 | JG greater(EAX>EBX 则跳 greater) |
JL/JLE |
有符号数小于 / 小于等于时跳转 | JLE less_eq(EAX≤EBX 则跳 less_eq) |
JA/JAE |
无符号数大于 / 大于等于时跳转 | JA above(EAX>EBX 无符号则跳 above) |
JB/JBE |
无符号数小于 / 小于等于时跳转 | JB below(EAX<EBX 无符号则跳 below) |
CALL |
调用子程序(函数),将返回地址压栈后跳转 | CALL func(调用 func 函数) |
RET |
子程序返回,弹出栈顶返回地址跳转 | RET(返回到 CALL 的下一条指令) |
LOOP |
循环指令(ECX 减 1,非零则跳转) | LOOP again(ECX--,若≠0 则跳 again) |
五、栈与帧操作指令
用于函数调用时的栈帧建立与销毁,32 位架构中尤为常用。
| 指令 | 功能说明 | 示例 |
|---|---|---|
ENTER |
建立栈帧(替代 PUSH EBP; MOV EBP, ESP 等操作) | ENTER 8, 0(分配 8 字节局部栈空间) |
LEAVE |
销毁栈帧(替代 MOV ESP, EBP; POP EBP) | LEAVE(恢复 ESP 和 EBP) |
PUSHFD/POPFD |
压入 / 弹出 EFLAGS 寄存器(32 位) | PUSHFD(保存标志位) |
六、字符串操作指令
批量处理内存中的字符串 / 数组,需配合 ESI/EDI/ECX 等寄存器。
| 指令 | 功能说明 | 示例 |
|---|---|---|
MOVSB/MOVSW/MOVSD |
字符串传送(ESI→EDI,每次 1/2/4 字节,DF 标志控制方向) | REP MOVSD(重复 ECX 次,ESI→EDI 传送 4 字节) |
CMPSB/CMPSD |
字符串比较(ESI 与 EDI 指向数据比较) | REPE CMPSB(相等则重复比较) |
SCASB/SCASD |
字符串扫描(AL/EAX 与 EDI 指向数据比较) | REPNE SCASB(不等则重复扫描) |
STOSB/STOSD |
存储字符串(AL/EAX→EDI 指向内存) | REP STOSD(重复 ECX 次,EAX 存入 EDI) |
七、系统调用与特权指令
64 位 Linux/Windows 下的系统调用或特权级操作(如 x86-64 的SYSCALL)。
| 指令 | 功能说明 | 适用场景 |
|---|---|---|
INT |
软中断(如 32 位 Linux 用INT 0x80触发系统调用,Windows 用INT 0x2E) |
MOV EAX, 1; INT 0x80(Linux 32 位 exit 系统调用) |
SYSCALL/SYSRET |
64 位快速系统调用(Linux 用,Windows 用SYSCALL+SYSRET) |
MOV RAX, 60; SYSCALL(Linux 64 位 exit) |
IRET |
中断返回(从异常 / 中断处理程序返回) | 内核态中断处理 |
2. 选择语句的机器表示
选择语句(如if-else、switch-case)的机器表示本质是通过条件判断指令和跳转指令改变程序执行流,不同选择结构对应不同的汇编实现逻辑,核心依赖处理器的标志寄存器(如 ZF、CF、SF)和跳转指令(如 JE、JMP)。以下是具体实现方式:
一、if-else语句的机器表示
if-else的核心是 "条件判断 + 分支跳转",编译器会将条件表达式转换为比较 / 测试指令,再根据标志位结果跳转到对应代码块。
int a = 5, b = 3, result;
if (a > b) {
result = 1;
} else {
result = 0;
}
对应的 x86 汇编(AT&T 格式):
movl $5, %eax # a=5 → EAX
movl $3, %ebx # b=3 → EBX
cmpl %ebx, %eax # 比较EAX(a)和EBX(b),设置标志位(a-b)
jle .Lelse # 若a ≤ b(JLE:小于等于则跳转),跳转到else块
# if块:a > b
movl $1, %ecx # result=1 → ECX
jmp .Lend # 跳过else块,直接到结束
.Lelse:
movl $0, %ecx # result=0 → ECX
.Lend:
# 后续代码
关键逻辑:
- 条件判断 :用
CMP(比较)或TEST(测试)指令计算条件表达式,改变标志寄存器(如 ZF = 零标志、SF = 符号标志、CF = 进位标志)。 - 条件跳转 :根据标志位选择跳转指令(如
JG(大于)、JLE(小于等于)、JE(等于)),跳转到对应代码块。 - 无条件跳转 :
if块执行完后用JMP跳过else块,避免执行冗余代码。
3. 循环语句的机器表示
for循环本质是while循环的语法糖,汇编层面会拆解为 "初始化→条件检查→循环体→更新变量" 的结构,与while循环几乎一致。
示例代码(C 语言):
int sum = 0;
for (int i = 0; i < 5; i++) {
sum += i;
}
对应的 x86 汇编(AT&T 格式):
movl $0, %ebx # sum=0 → EBX
movl $0, %eax # 初始化i=0 → EAX(for的初始化部分)
.Lloop_start:
cmpl $5, %eax # 条件检查:i < 5?
jge .Lloop_end # 不满足则退出循环
# 循环体:sum += i
addl %eax, %ebx # sum = sum + i
# 更新变量:i++
incl %eax # i = i + 1
jmp .Lloop_start # 回跳检查条件
.Lloop_end:
- 依赖跳转指令 :通过条件跳转(如
JGE、JL)控制循环是否继续,无条件跳转(JMP)实现回跳。 - 标志寄存器的作用 :条件跳转的依据是
CMP/TEST指令设置的标志位(如 ZF、SF、CF)。 - 结构等价性 :
for/while/do-while在机器码层面最终都会转化为 "条件检查 + 回跳" 的结构,仅执行顺序略有差异。 - 优化方向:编译器可能会对循环进行优化(如循环展开、寄存器重命名),减少跳转次数以提升性能。
总结
循环语句的机器级表示本质是 **"条件判断 + 回跳" 的组合 **:通过比较指令设置标志位,跳转指令控制执行流是否重复,不同循环语句(for/while/do-while)仅在条件检查和循环体的执行顺序上略有差异,最终都转化为高效的汇编跳转结构。
4. 函数调用的机器级表示
一、核心概念:栈帧(Stack Frame)
栈帧是函数调用时在栈上分配的独立内存区域,用于存储:
- 函数参数、返回地址(调用者的下一条指令地址);
- 函数的局部变量、寄存器上下文(如基址寄存器
EBP/RBP);每个函数的栈帧由栈基址指针(EBP/RBP) 和栈指针(ESP/RSP) 界定。
二、x86 32 位架构的函数调用流程(CDECL 调用约定)
以经典的 32 位 x86 为例(C 语言默认调用约定),函数调用分为调用前准备→执行调用→函数执行→返回恢复四步。
示例代码(C 语言):
// 调用者函数
int caller() {
int a = 1, b = 2;
int res = add(a, b); // 调用add函数
return res;
}
// 被调用函数
int add(int x, int y) {
int temp = x + y;
return temp;
}
1. 调用前准备(caller 函数)
- 参数压栈 :按 CDECL 约定,参数从右到左压栈(先
y,再x); - 保存返回地址 :执行
CALL指令时,自动将下一条指令地址压栈。
对应的汇编(AT&T 格式):
caller:
pushl %ebp ; 保存旧EBP(调用者的栈基址)
movl %esp, %ebp ; 建立caller的栈帧(EBP=ESP)
subl $8, %esp ; 分配局部变量空间(a和b,各4字节)
movl $1, -4(%ebp) ; a=1 → EBP-4(局部变量)
movl $2, -8(%ebp) ; b=2 → EBP-8(局部变量)
; 准备add函数的参数:从右到左压栈(先b,再a)
pushl -8(%ebp) ; 参数y = b(2)压栈
pushl -4(%ebp) ; 参数x = a(1)压栈
call add ; 调用add函数:自动将下一条指令地址压栈,跳转到add
2. 函数执行(add 函数)
- 建立栈帧 :保存调用者的
EBP,设置自身的EBP; - 分配局部变量空间 :通过
ESP偏移分配栈空间; - 执行函数逻辑 :计算
x+y,结果存入返回寄存器EAX。
对应的汇编:
add:
pushl %ebp ; 保存caller的EBP(栈顶现在是返回地址→caller的EBP)
movl %esp, %ebp ; 建立add的栈帧(EBP=ESP)
subl $4, %esp ; 分配局部变量temp的空间(4字节)
movl 8(%ebp), %eax ; 取参数x → EAX(EBP+8是第一个参数,EBP+4是返回地址)
addl 12(%ebp), %eax ; x + y(EBP+12是第二个参数y)→ EAX
movl %eax, -4(%ebp) ; temp = x+y → EBP-4
movl -4(%ebp), %eax ; 返回值存入EAX(约定用EAX传递返回值)
leave ; 等价于movl %ebp, %esp + popl %ebp(销毁add的栈帧)
ret ; 弹出返回地址,跳回caller的下一条指令
3. 返回恢复(caller 函数)
- 清理栈参数:按 CDECL 约定,调用者负责清理压栈的参数;
- 恢复栈帧:销毁自身栈帧,返回结果。
对应的汇编:
; call add后的指令
addl $8, %esp ; 清理add的参数(2个参数×4字节=8字节)
movl %eax, -12(%ebp) ; res = add返回值(EAX)→ EBP-12
movl -12(%ebp), %eax ; caller的返回值存入EAX
leave ; 销毁caller的栈帧
ret ; 返回caller的调用者
三、栈帧布局(关键!)
以add函数执行时的栈为例,栈从高地址向低地址增长(ESP 指向栈顶),布局如下:
高地址 → [caller的EBP] ; EBP(add的栈基址)指向这里
[返回地址] ; EBP+4:caller中call add的下一条指令地址
[参数x (a=1)] ; EBP+8:第一个参数(x)
[参数y (b=2)] ; EBP+12:第二个参数(y)
[局部变量temp] ; EBP-4:add的局部变量
低地址 → [ESP] ; 栈指针指向栈顶
四、x86 64 位架构的差异(System V AMD64 调用约定)
64 位架构不再完全依赖栈传递参数,而是用寄存器 + 栈结合:
- 参数传递 :前 6 个整数参数用寄存器
RDI/RSI/RDX/RCX/R8/R9传递,超过 6 个的参数压栈; - 返回值 :用
RAX传递(64 位); - 栈帧 :仍用
RBP作为基址指针(可选,部分编译器优化后会省略),栈需 16 字节对齐。
示例(add 函数的 64 位汇编):
add:
pushq %rbp
movq %rsp, %rbp
addq %rsi, %rdi ; x(RDI) + y(RSI) → RDI
movq %rdi, %rax ; 返回值存入RAX
popq %rbp
ret
五、关键指令与约定
CALL指令 :- 功能:① 将下一条指令地址压栈(返回地址);② 跳转到被调用函数入口;
RET指令 :- 功能:① 弹出栈顶的返回地址;② 跳转到该地址(回到调用者);
LEAVE指令 :- 等价于
movl %ebp, %esp(恢复 ESP) +popl %ebp(恢复旧 EBP),快速销毁栈帧;
- 等价于
- 调用约定(Calling Convention) :
- 规定参数传递方式、栈清理责任、寄存器保存规则,常见的有:
- CDECL :参数右到左压栈,调用者清理栈,支持可变参数(如
printf); - STDCALL:参数右到左压栈,被调用者清理栈(Windows API 常用);
- System V AMD64:64 位 Linux/macOS 的默认约定,寄存器 + 栈传参。
- CDECL :参数右到左压栈,调用者清理栈,支持可变参数(如
- 规定参数传递方式、栈清理责任、寄存器保存规则,常见的有:
六、函数调用的核心逻辑总结
- 栈帧隔离:每个函数有独立栈帧,避免局部变量冲突;
- 参数传递:32 位用栈,64 位用寄存器 + 栈;
- 返回机制 :通过
CALL/RET指令和返回地址实现执行流跳转; - 寄存器约定 :部分寄存器(如
EBP/ESI)需保存,部分(如EAX/EDX)可随意修改(由调用约定规定)。
本质上,函数调用的机器级实现是栈的 "push/pop" 操作 + 指令跳转的结合,通过严格的栈帧管理保证调用的正确性。