汇编传参和调用约定

基础知识

原码、反码、补码

原码、反码、补码是表示带符号整数的不同表示方法。

  • 原码:直接使用最高位表示数值的正负

    5: 00000101

    -5:10000101

  • 反码

    对原码的数值部分(符号位除外)取反表示负数。

    +5:00000101,正数反码与原码相同,不取反。

    -5:10000101,取反得到11111010

  • 补码

    正数的补码与原码相同。

    -5:10000101,先取反11111010,然后加1得到补码11111011

十六进制

位数 术语 说明 常见数据类型
8位 BYTE 基本数据单位,1字节 char
16位 WORD 2字节(通常是处理器的基本数据单位) short
32位 DWORD 4字节(双字) intlong
64位 QWORD 8字节(四字) long long
128位 (Octa Word) 16字节(较少见,通常用于SIMD) SIMD 寄存器
256位 (YMM) 32字节,通常用于SIMD(如AVX2) SIMD 寄存器
512位 (ZMM) 64字节,通常用于SIMD(如AVX - 512) SIMD 寄存器

x86架构

寄存器分类

通用寄存器32位

  • EAX:累加寄存器,用于算术运算、I/O操作。Windows代码中通常用于存放返回值。
  • EBX:基址寄存器
  • ECX:计数寄存器,用于循环寄存器,移位操作。
  • EDX:数据寄存器,存储乘法、除法结果
  • ESI:源索引寄存器,指向源操作数,尤其是字符串操作。
  • EDI:目的索引寄存器,指向目标操作数,尤其是字符串操作。
  • EBP:基指针寄存器
  • ESP:堆栈指针寄存器,指向栈顶。

al、ah、bl、bh、cl、ch、dl、dh

指针寄存器

  • EIP:指令指针寄存器,指向CPU正在执行的下一条指令的内存地址。在64位中,叫做RIP

    EIP存在于CPU中,没有汇编指令可以直接修改它。

  • VT技术可以修改EIP

标志寄存器

缩写 全称 中文名称 功能说明
CF Carry Flag 进位标志 如果运算的结果的最高位产生了一个进位或者借位,那么值为1,否则为0
ZF Zero Flag 零标志 表示运算结果是否为零
SF Sign Flag 符号标志 表示运算结果的符号位(可辅助判断正负,结合结果最高位,0 正、1 负等场景)
OF Overflow Flag 溢出标志 表示加法或减法是否发生溢出(超出数据类型表示范围)
PF Parity Flag 偶校验标志 用于检查运算结果二进制中 1 的个数是否为偶数
DF Direction Flag 方向标志 控制字符串操作方向(如增量/减量,决定指针移动方式)
AF Auxiliary Carry Flag 辅助进位标志 常用于 BCD(二进制编码的十进制)运算,辅助判断低 4 位进位情况
  • 溢出
    正+正=负,有溢出
    负+负=正,有溢出
    正+负,无溢出

浮点寄存器

  • ST-0到ST-7
  • FPU:用于执行浮点数运算

调试寄存器

寄存器 用途说明
DR0 ~ DR3 存储硬件断点地址,调试器可在此设断点,监视内存访问/指令执行
DR4, DR5 已保留,现代处理器中作保留字段,不再使用
DR6 调试状态寄存器,存储断点触发状态信息,用于判断哪个断点被触发
DR7 调试控制寄存器,设置硬件断点条件(读/写/执行等)及权限(全局/本地断点)

FS和TEB寄存器

  • fs[0]:默认值是TEB的基地址

x64寄存器参数顺序

rdi:第 1 个参数

rsi:第 2 个参数

rdx:第 3 个参数

rcx:第 4 个参数

r8:第 5 个参数

r9:第 6 个参数

内存分布

指令

操作符

算术操作符

操作符 功能说明 示例代码
add 加法 add eax, ebx
sub 减法 sub eax, ecx
mul 无符号乘法 mul ecx
imul 有符号乘法 imul eax, ecx
div 无符号除法 div ecx
idiv 有符号除法 idiv ecx
inc 自增(increase) inc eax
dec 自减(decrease) dec eax
除法
asm86 复制代码
div ax,r/m8    ;ax除以r/m8,al是商,ah是余数。
div eax,r/m32  ;eax是商,edx是余数。

位操作符

操作符 功能 示例 说明
AND 按位与
OR 按位或
XOR 按位异或
SHL 逻辑左移 shl eax,1 所有位向左移动一位,空出位置填0,最左边丢弃
SHR 逻辑右移
sal 有符号左移
sar 有符号右移
rol 循环左移 rol eax,1 所有位向左移动一位,最左边移动到末尾
ror 循环右移 ror eax,1

数据传输指令

操作符 功能描述 示例代码
mov 将数据从源传输到目的位置 mov eax, ebx
push 将数据压入栈 push eax
pop 从栈中弹出数据到目的位置 pop eax
xchg 交换两个操作数的值 xchg eax, ebx
lea 取地址 lea eax, [ebx+4]

逻辑比较指令

操作符 功能描述 示例代码
cmp 比较,减法 cmp eax, ebx
test (按位与,不存结果,仅设置标志) test eax, eax
bash 复制代码
CPM eax,ebx    // eax-ebx
               //如果eax > ebx,通过JG跳转。
               
CF:(Carry Flag)
设置条件:发生借位,第一个操作数小于第二个。
清楚条件:未发生借位。

ZF:(Zero Flag)
设置条件:两个操作数相等(即结果为0)
清除条件:两个操作数不相等。

SF:(Sign Flag)
设置条件:结果为负(即最高有效位为1)
清除条件:结果为正或零

OF:(Overflow Flag)
设置条件:有符号比较时发生溢出(即正数减负数得到负数,或负数减正数得到正数)
清除条件:未发生溢出

PF:(Parity Flag)
设置条件:结果的最低字节中,1的位数是偶数。
清除条件:结果的最低字节中,1的位数是奇数。

AF:(Auxiliary Carry Flag)
设置条件:在低4位操作中发生借位。
清除条件:未发生借位。
bash 复制代码
test eax,eax    //若eax为0,JZ跳转
test eax,0x01    //若eax最低为为1,JZ跳转

控制转移指令

操作符 功能说明 依赖标志位及条件 示例代码
call 调用子程序(执行完返回原位置) 无(直接调用,自动压栈返回地址) call 0x00401000
ret 返回调用点(配合 call) 无(从栈弹出返回地址) ret
jmp 无条件跳转(不依赖标志位) 无(直接跳转) jmp 0x00401000
jb CF==1
jbe CF1 or ZF1
je/jz 相等/零标志置位时跳转 ZF= 1(结果为 0/相等) je 0x00401020
jne/jnz 不等/零标志未置位时跳转 ZF= 0(结果非 0/不等) jne 0x00401020
jle/jng ZF==1 or SF!=OF
jg 大于时跳转(有符号比较) SF= OF or ZF=0 jg 0x00401020
jl 小于时跳转(有符号比较) SF≠ OF jl 0x00401020
jge/jnl 大于等于时跳转(有符号比较) SF= OF or ZF=1 jge 0x00401020
jle 小于等于时跳转(有符号比较) SF≠ OF or ZF=1 jle 0x00401020
loop 循环跳转(配合计数器) 计数寄存器(ECX)减 1 后 ≠ 0 loop 0x00401020
int 调用中断(触发系统/硬件中断) 无(主动触发中断) int 0x80
iret 从中断返回(恢复状态) 无(恢复 EFLAGS/CS/EIP 等) iret

栈操作指令

指令 功能描述 ESP 变化(32位环境) 补充说明 & 场景
PUSH 将寄存器、立即数、内存值压入栈顶(如 PUSH EAX PUSH 0x123 ESP -= 4 常用于保存临时数据、函数调用传参
POP 从栈顶弹出数据,存入寄存器/内存(如 POP EBX ESP += 4 配合 PUSH 恢复数据,需注意栈平衡
PUSHAD 按顺序压入通用寄存器:EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI ESP -= 32(8个寄存器×4字节) 快速保存全部通用寄存器(调试/上下文切换)
POPAD 按顺序弹出恢复通用寄存器:EDI, ESI, EBP, ESP(跳过), EBX, EDX, ECX, EAX ESP += 32 PUSHAD 配对,注意 ESP 会被跳过
PUSHFD 将32位标志寄存器(EFLAGS)压入栈 ESP -= 4 保存标志位状态(如调试、异常处理)
POPFD 从栈弹出值恢复32位EFLAGS寄存器 ESP += 4 PUSHFD 配对,恢复标志位
CALL 地址 调用子程序:压入返回地址(EIP)→ 跳转到目标地址 ESP -= 4(压入返回地址) 函数调用核心指令,返回地址用于 RET
RET 从栈弹出返回地址 → 跳转到该地址(可选带立即数平衡栈,如 RET 4 ESP += 4(或更多) 函数返回,RET N 用于跳过函数调用前压入的参数
ENTER 设置栈帧:压入旧 EBP → 新 EBP = ESP → 分配局部变量空间(如 ENTER 8,0 ESP -= 4 + 局部变量大小 替代 MOV EBP, ESP + SUB ESP, N,较少用
LEAVE 恢复栈帧:ESP = EBP → 弹出旧 EBP ESP += 局部变量大小 + 4 ENTER 配对,快速清理栈帧
INT imm8 软中断:压入 EFLAGS、CS、EIP → 跳转到中断向量表 ESP -= 12(32位环境) 触发系统调用(如 INT 0x80 是Linux传统系统调用)

字符串操作指令

mov

nasm 复制代码
mov eax,offset str_hello;    加载"hello"字符串的地址到EAX
mov [edi],eax           ;    把这个地址存入到EDI指向的位置

movs

movs是一组字符串操作指令,表示"移动字符串数据"。

nasm 复制代码
movsb:移动一个字节
movsw:移动一个字
movsd:移动一个双字
movsq:(64位模式下使用)移动一个四字

repmovsb

rep movsb:批量复制字符串数据。

nasm 复制代码
mov ecx,length     ;设置要复制的字节数
mov esi,source     ;源字符串地址
mov edi,destination;目标字符串地址
rep movsb          ;批量复制字符串

rep系列指令

nasm 复制代码
rep:重复执行后续指令,直到ECX或CX寄存器的值为0
repe/repz:(Repeat While Equal/Repeat While Zero)两者等价在ZF为1,且ECX不为0时执行。
repne/repnz:(Repeat While Not Equal/Repeat While Not Zero)两者等价,当ZF不为0,且ECX不为0时执行。

cmp指令

nasm 复制代码
mov esi,offset str_userinput ; 用户输入字符串
mov edi,offset str_password  ; 目标字符串
mov ecx,length               ; 设置比较的字节数
cld                          ; 清除方向标志
repe cmpsb                   ; 比较两段内存的字符串
je match                     ; 如果相等跳转

scas指令

扫描字符串中的特定字符

扫描方向由DF确定,cld清除DF从低地址扫描,std设置DF从高地址扫描。

nasm 复制代码
mov edi,offset str_buffer ; EDI指向要扫描的字符串
mov al,'A'                ; 查找指定字符
mov ecx,length_of_string  ; ECX设置为字符串长度

repne scasb               ; 扫描直到找到'A'或结束
jnz not_found             ; 未找到执行
xxxx                      ; 找到执行

stos指令

向目标地址填充数据,通常用于清空字符串或填充特定值。

nasm 复制代码
mov edi,offset buffer ; 目标地址
mov al,0              ; 填充为0
mov ecx,size          ; 填充大小
cld                   ; 设置方向标志递增
req stosb             ; 填充字符串

loads指令

nasm 复制代码
lodsb:加载一个字节到AL,并将ESI自增1。
lodsw:加载一个字到AX,并将ESI自增2.
lodsd:加载一个双字到EAX,并将ESI自增4.

堆栈中的字符串操作

nasm 复制代码
push offset str_hello ;将"hello"的地址压栈。
call printf           ;调用函数
add  esp,4           ;平衡堆栈

浮点运算指令

FPU

FPU(浮点运算单元)

  • 定义:FPU是处理器中一个硬件模块,专门负责浮点运算。
  • 功能:处理浮点数的加减乘除,平方根,三角函数等复杂操作。
  • 实现:现在FPU集成到CPU内核中。

FPU组成

  • 寄存器堆栈:8个80位宽的浮点寄存器(ST0-ST7),以堆栈形式组织。
  • 通过push和pop操作

x87指令集

  • 定义:x87是专门位FPU涉及的一套指令集,早期专用于处理浮点运算。
  • 特点:
    • 操作对象是FPU的堆栈寄存器
    • 指令风格是Fxxx开头。
    • 支持扩展精度(80位),比SSE的单精度和双精度浮点数高。
加载与存储指令
指令 功能描述
FLD 从内存加载浮点数到 FPU 栈顶(支持单精度、双精度、长双精度)
FST 将 FPU 栈顶浮点数复制到内存(栈顶数据保留)
FSTP 将 FPU 栈顶浮点数存储并弹出(栈顶数据移除,栈指针 +1)
FLDENV 从内存加载 FPU 环境(控制字、状态字、标记字等)
FSTENV 将 FPU 环境(控制字、状态字等)保存到内存
FLDCW 从内存加载 FPU 控制字(设置舍入模式、精度等)
FSTCW 将 FPU 控制字保存到内存
算术运算指令
指令 功能描述
FADD 栈顶两数相加(FADD st(0), st(1) 或简写 FADD,结果存栈顶)
FSUB 栈顶两数相减(FSUB st(0), st(1)st(0)-st(1),结果存栈顶)
FSUBR 反向相减(FSUBR st(0), st(1)st(1)-st(0),结果存栈顶)
FMUL 栈顶两数相乘(结果存栈顶)
FDIV 栈顶两数相除(FDIV st(0), st(1)st(0)/st(1),结果存栈顶)
FDIVR 反向相除(FDIVR st(0), st(1)st(1)/st(0),结果存栈顶)
FABS 取栈顶数的绝对值
FNEG 取栈顶数的相反数
FSQRT 计算栈顶数的平方根
FSCALE 栈顶数乘以 2 的(次顶数)次方(st(0) *= 2^st(1),然后弹出次顶数)
转换指令
指令 功能描述
FIST 将栈顶浮点数复制为整数存入内存(按当前 FPU 控制字舍入)
FISTP 将栈顶浮点数转换为整数并存出,然后弹出栈顶(清理栈空间)
FCVT 浮点数类型转换(如单精度→双精度,或反向)
FILD 从内存加载整数到 FPU 栈顶(自动转换为浮点数)
FIST 将栈顶浮点数转换为整数并存入内存(同 FIST,需区分操作数大小)
控制与比较指令
指令 功能描述
FCOM 比较栈顶两数(st(0) vs st(1)),设置 FPU 状态位(不修改栈)
FCOMI 比较栈顶两数,结果写入 CPU 通用寄存器 EFLAGS(支持 JE/JL 等指令)
FUCOM FCOM,但处理非规范数(NaN、无穷大)
FUCOMI FCOMI,但处理非规范数(NaN、无穷大)
FTST 比较栈顶数与 0(判断正负、是否为 0)
FBSTP 将栈顶浮点数转换为 BCD 码(十进制)并存入内存,然后弹出栈
FINCSTP 递增 FPU 栈指针(手动调整栈顶,风险高)
FDECSTP 递减 FPU 栈指针(手动调整栈顶,风险高)
特殊指令
指令 功能描述
FNINIT 初始化 FPU(重置控制字、状态字等)
FNOP 空操作(无实际功能,用于填充指令周期)
FWAIT 等待 FPU 完成当前指令(旧版兼容,现代 CPU 多自动同步)
FXSAVE 保存 SSE/FPU 状态到内存(支持 SIMD 扩展)
FXRSTOR 从内存恢复 SSE/FPU 状态(支持 SIMD 扩展)

SSE

  • 定义:SSE是Intel在x86处理器上引用的一套SIMD(单指令多数据)扩展指令集。
  • 特点:
    • 采用xmm寄存器(每个128位宽)。
    • 支持并行处理多个单精度(32位)或双精度(64位)浮点数。
    • 指令风格是 xxxPs(处理单精度矢量)或xxxSD(处理双精度标量)。

数据扩展与传送指令

指令 全称 功能描述 指令格式示例 适用场景
movsx Move with Sign Extension 将窄宽度有符号数扩展为宽宽度有符号数(高位填充符号位) movsx 存储位置, 源操作数 有符号整数类型转换(如char→int
movzx Move with Zero Extension 将窄宽度无符号数扩展为宽宽度数(高位填充0) movzx reg_dest, reg_src/mem_src 无符号整数类型转换(如unsigned char→unsigned int
cbw Convert Byte to Word 将8位寄存器al中的有符号数扩展为16位axal符号位扩展到ah cbw(无操作数,固定使用alax int8_t→int16_t转换
cwd Convert Word to Doubleword 将16位寄存器ax中的有符号数扩展为32位dx:axax符号位扩展到dx cwd(无操作数,固定使用axdx:ax int16_t→int32_t转换
cdq Convert Doubleword to Quadword 将32位寄存器eax中的有符号数扩展为64位edx:eaxeax符号位扩展到edx cdq(无操作数,固定使用eaxedx:eax int32_t→int64_t转换
cqo Convert Quadword to Octword 将64位寄存器rax中的有符号数扩展为128位rdx:raxrax符号位扩展到rdx cqo(无操作数,固定使用raxrdx:rax int64_t→int128_t转换

函数

函数特征代码

nasm 复制代码
;函数头部
push ebp    ;保存上一函数的栈基指针。
mov ebp,esp ;设置当前栈帧基址。
sub esp,0x10;为局部变量分配空间

;函数退出
mov esp,ebp  ;
pop ebp      ;
ret          ;

函数参数和局部变量

nasm 复制代码
push 3    ;第二个参数
push 2    ;第一个参数
call add  ;如果发现call上面有push,那些就是函数调用用到的参数。

当在内层函数看到ebp + xxx是函数的参数;ebp - xxx是函数的局部变量。

局部变量在栈上的位置(下图)

  • x64
nasm 复制代码
lea     rdx, [rbp+input_flag]//参数
lea     rax, [rbp+var_90]    //参数
mov     rsi, rdx
mov     rdi, rax
call    function

调用约定

cdecl

又叫做c调用约定,在Windows下都是外平栈。

特点:

  • 参数传递:参数从右到左依次压栈
  • 栈清理:由调用者(Caller)清理栈(add esp,x)
  • 返回值:eax寄存器
  • 常见场景:c语言默认约定
nasm 复制代码
push 3    ;第三个参数
push 2    ;
push 1    ;第一个参数
call my_function
add esp,0xC    ;调用者清理栈(3个参数 x 4个字节 = 12个字节)

my_function:
push ebp    ;保存旧的栈帧基址
mov ebp,esp ;建立新栈帧
sub esp,8   ;分配局部变量空间
mov dword ptr [ebp - 4],10    ;局部变量a = 10
mov dword ptr [ebp - 8],20    ;局部变量b = 20
; 访问参数
mov eax,[ebp + 8]    ;访问第一个参数
mov eax,[ebp + 12]   ;访问第二个参数
mov eax,[ebp + 16]   ;访问第三个参数
;尾部
mov esp,ebp    ;恢复栈指针
pop ebp        ;恢复旧的栈帧基址
ret            ;返回(无栈清理)

stdcall

叫做标准调用约定,windows下内平栈

特点:

  • 参数传递:参数从右到左压栈
  • 栈清理:由被调用者清理栈
  • 返回值:eax寄存器
  • 常见场景:Windows API
nasm 复制代码
push 3    ;第三个参数
push 2    ;
push 1    ;第一个参数
call my_function
;无需清理栈

my_function:
push ebp    ;保存旧的栈帧基址
mov ebp,esp ;建立新栈帧
sub esp,8   ;分配局部变量空间
mov dword ptr [ebp - 4],10    ;局部变量a = 10
mov dword ptr [ebp - 8],20    ;局部变量b = 20
; 访问参数
mov eax,[ebp + 8]    ;访问第一个参数
mov eax,[ebp + 12]   ;访问第二个参数
mov eax,[ebp + 16]   ;访问第三个参数
;尾部
mov esp,ebp    ;恢复栈指针
pop ebp        ;恢复旧的栈帧基址
ret 0xC        ;被调用者清理栈

fastcall

windows下内平栈

特点:

  • 参数传递:前两个参数通过寄存器(ecx和edx),其余参数从右到左压栈
  • 栈清理:由被调用者清理栈(同stdcall)
  • 返回值:eax寄存器
  • 常见场景:性能敏感的函数
nasm 复制代码
mov ecx,1        ;第一个参数,放入ecx
mov edx,2        ;第二个参数,放入edx
push 3           ;第三个参数(最右侧)
call my_function ;

my_function:
push ebp    ;保存旧的栈帧基址
mov ebp,esp ;建立新栈帧
sub esp,4   ;分配局部变量空间

; 访问参数
mov eax,ecx    ;访问第一个参数
mov eax,edx   ;访问第二个参数
mov eax,[ebp + 8]   ;访问第三个参数
;尾部
mov esp,ebp    ;恢复栈指针
pop ebp        ;恢复旧的栈帧基址
ret 4        ;被调用者清理栈

thiscall

特点:

  • 参数传递:this指针通过ecx传递,其余参数从右到左入栈
  • 栈清理:由调用者或者编译器确定
  • 返回值:eax寄存器
  • 常见场景:C++类成员函数
nasm 复制代码
mov ecx,obj_ptr    ;this指针通过ecx传递,其余参数从右到左压栈
push 2             ;第二个参数
push 1             ;第一个参数
call obj.method    ;调用成员函数
add esp,8          ;调用者清栈