1. 数据传输指令
1.1. mov
-
作用:将数据从源操作数复制到目标操作数。
-
语法 :mov dest, src
mov eax, 10 ; 将立即数 10 存入 eax 寄存器
mov ebx, eax ; 将 eax 的值复制到 ebx
mov [ecx], eax ; 将 eax 的值写入 ecx 指向的内存地址
1.2. lea
-
作用:计算内存地址的偏移量并存入寄存器(不访问内存)。
-
语法 :lea dest, [address]
lea eax, [ebx + 8] ; 将 ebx + 8 的地址存入 eax(不读取内存内容)
2. 栈操作指令
2.1. push
- 作用 :将操作数压入栈顶,栈指针(esp/rsp)减小。
- 语法 :push src
底层行为:
sub esp, 4 ; x86: 栈指针下移 4 字节(32位)
mov [esp], src ; 将数据写入新的栈顶
示例:
push eax ; 将 eax 的值压入栈
push 0xdeadbeef ; 将立即数压入栈
2.2. pop
- 作用 :将栈顶数据弹出到目标操作数,栈指针(esp/rsp)增大。
- 语法 :pop dest
底层行为:
mov dest, [esp] ; 从栈顶读取数据
add esp, 4 ; x86: 栈指针上移 4 字节(32位)
示例:
pop ebx ; 弹出栈顶数据到 ebx
3. 算数运行指令
3.1. add
- 作用:将目标操作数与源操作数相加,结果存入目标。
- 语法 :add dest, src
示例:
add eax, ebx ; eax = eax + ebx
add dword [ecx], 5; 将 ecx 指向的内存值加 5
3.2. sub
- 作用:目标操作数减去源操作数,结果存入目标。
- 语法 :sub dest, src
示例:
sub eax, 10 ; eax = eax - 10
3.3. inc/dec
- 作用 :对操作数加 1(inc)或减 1(dec)。
示例:
inc ecx ; ecx += 1
dec dword [edx] ; 将 edx 指向的内存值减 1
4. 控制流指令
4.1. jmp
- 作用:无条件跳转到指定地址。
- 语法 :jmp target
示例:
jmp 0x8048000 ; 跳转到绝对地址 0x8048000
jmp eax ; 跳转到 eax 寄存器中的地址
4.2. call
作用:调用函数(保存返回地址并跳转)。
底层行为:
push eip + 5 ; 将下一条指令地址压入栈(返回地址)
jmp target ; 跳转到目标函数
示例:
call 0x8048123 ; 调用位于 0x8048123 的函数
4.3. ret
- 作用:从函数返回(弹出返回地址并跳转)。
底层行为:
pop eip ; 将栈顶的返回地址存入 eip(程序计数器)
示例:
ret ; 返回到调用者
5. 比较与条件跳转
5.1. cmp
- 作用 :比较两个操作数(目标 - 源),结果影响标志寄存器(EFLAGS)。
- 语法 :cmp dest, src
示例:
cmp eax, ebx ; 比较 eax 和 ebx 的值
5.2. test
- 作用:对两个操作数进行按位与(AND)操作,结果影响标志寄存器。
- 语法 :test dest, src
示例:
test eax, eax ; 检查 eax 是否为 0
5.3. 条件跳转指令
- 作用:根据标志寄存器状态跳转(如相等、大于等)。
- 常见指令:
-
- je / jz:等于/零时跳转。
- jne / jnz:不等于/非零时跳转。
- jg:有符号数大于时跳转。
- jl:有符号数小于时跳转。
- ja:无符号数大于时跳转。
示例:
cmp eax, 5
je label ; 若 eax == 5,跳转到 label
6. 函数调用与栈帧
6.1. 函数序言(Prologue)
- 作用:保存调用者的栈帧并建立新栈帧。
典型代码:
push ebp ; 保存旧的基址指针
mov ebp, esp ; 设置新的基址指针(当前栈顶)
sub esp, 0x20 ; 为局部变量分配栈空间
6.2. 函数尾声(Epilogue)
- 作用:恢复调用者的栈帧并返回。
典型代码:
mov esp, ebp ; 释放局部变量空间
pop ebp ; 恢复旧的基址指针
ret ; 返回到调用者
7. 实际应用场景
请看以下代码,简单的输出"Hello word"。
#include <stdio.h>
#include <stdlib.h>
int main() {
puts("hello world");
exit(0);
}
对其生成的的汇编代码如下,详细介绍如下:
.section __TEXT,__text,regular,pure_instructions
.macosx_version_min 10, 12
.globl _main
.p2align 4, 0x90
_main: ## @main
## BB#0:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
calll L0$pb
L0$pb:
popl %eax
leal L_str-L0$pb(%eax), %eax
movl %eax, (%esp)
calll _puts
movl $0, (%esp)
calll _exit
subl $4, %esp
.section __TEXT,__cstring,cstring_literals
L_str: ## @str
.asciz "hello world"
.subsections_via_symbols
段定义与平台声明:
.section __TEXT,__text,regular,pure_instructions
.macosx_version_min 10, 12
- .section __TEXT,__text:定义代码段(__TEXT 段中的 __text 节),用于存放可执行代码。
- .macosx_version_min 10,12:声明目标 macOS 最低版本为 10.12。
- regular,pure_instructions:指定节属性(常规代码,纯指令)。
主函数入口:
.globl _main
.p2align 4, 0x90
_main:
## BB#0:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
- .globl _main:声明 _main 为全局符号(程序入口)。
- .p2align 4, 0x90:对齐指令(16字节对齐,填充 0x90(NOP))。
- 函数序言:
-
- pushl %ebp:保存旧的基址指针(栈帧基址)。
- movl %esp, %ebp:建立新的栈帧(ebp 指向当前栈顶)。
- subl $8, %esp:在栈上分配 8 字节空间(可能用于后续函数调用参数)。
获取字符串地址:
calll L0$pb
L0$pb:
popl %eax
leal L_str-L0$pb(%eax), %eax
- calll L0pb**:调用下一条指令 **L0pb(实质是获取当前指令地址)。
-
- call 会将下一条指令地址(即 L0$pb 的地址)压入栈。
- popl %eax:弹出返回地址(即 L0$pb 的地址)到 eax 寄存器。
- leal L_str-L0$pb(%eax), %eax:
-
- 计算 L_str 相对于 L0pb** 的偏移量,加上 **eax**(即 **L0pb 的实际地址),得到 L_str 的绝对地址。
- 这是 macOS 中实现 位置无关代码(PIC) 的常见手法,用于获取数据地址。
调用puts输出字符串:
movl %eax, (%esp)
calll _puts
- movl %eax, (%esp):将字符串地址(eax)作为参数传递给 puts。
- calll _puts:调用标准库函数 puts,输出字符串 "hello word"。
调用exit终止程序:
movl $0, (%esp)
calll _exit
subl $4, %esp
- movl $0, (%esp):将退出码 0 作为参数传递给 exit。
- calll _exit:调用 exit 终止程序(而非 return 0,直接退出不返回到调用者)。
- subl $4, %esp:可能是调整栈指针的冗余操作(实际无意义,可能是编译器生成的冗余指令)。
字符串常量定义:
.section __TEXT,__cstring,cstring_literals
L_str:
.asciz "hello world"
- .section __TEXT,__cstring:定义只读字符串段。
- L_str:标签指向字符串 "hello world"
- .asciz:定义以 \0 结尾的 ASCII 字符串。
子节生成控制器:
.subsections_via_symbols
- 汇编器指令:告知链接器通过符号生成子节(用于优化可执行文件体积)。