常见汇编代码及其指定
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 %ebpmovl %esp, %ebpsubl $8, %espcalll L0$pb
L0$pb:popl %eaxleal L_str-L0$pb(%eax), %eaxmovl %eax, (%esp)calll _putsmovl $0, (%esp)calll _exitsubl $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 %ebpmovl %esp, %ebpsubl $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 %eaxleal L_str-L0$pb(%eax), %eax
calll L0$pb
:调用下一条指令L0$pb
(实质是获取当前指令地址)。
-
call
会将下一条指令地址(即L0$pb
的地址)压入栈。
popl %eax
:弹出返回地址(即L0$pb
的地址)到eax
寄存器。leal L_str-L0$pb(%eax), %eax
:
-
- 计算
L_str
相对于L0$pb
的偏移量,加上eax
(即L0$pb
的实际地址),得到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 _exitsubl $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
- 汇编器指令:告知链接器通过符号生成子节(用于优化可执行文件体积)。