🎨 颜色标记说明

  • 粉色:特别重要、主线必掌握、优先复盘的内容。
  • 蓝色:核心概念、术语、分类名。
  • 橙色:需要关注的边界、易错点、注意事项。
  • 绿色:解释类内容、帮助理解知识点。

返回:全书总览 · 上一章:流程控制 · 下一章:变量与内存

CALL 与 RET

CALL 保存下一条指令地址并转移控制流;RET 从栈中取得返回地址。

1
2
CALL ≈ push 返回地址 + jump 目标
RET ≈ 从栈顶恢复下一条执行地址

直接相对调用常见 E8 rel32,间接调用常见 FF /2,后者经常对应函数指针、IAT 或虚函数。

经典 x86 栈帧

1
2
3
push ebp
mov ebp, esp
sub esp, local_size

典型布局:

位置 常见含义
[ebp] 保存的上一层 EBP
[ebp+4] 返回地址
[ebp+8] 第一个栈参数
[ebp-4] 局部变量或临时空间

函数尾声常见:

1
2
3
mov esp, ebp
pop ebp
ret

leave; ret

参数和局部变量的偏移只是传统未优化栈帧的常见形式,不是所有函数都必须使用 EBP。

x86 调用方式

调用约定 参数 清栈 典型线索
__cdecl 从右到左入栈 调用者 calladd esp, n
__stdcall 从右到左入栈 被调用者 ret n
__fastcall 部分参数用 ECX、EDX 通常被调用者 调用前写 ECX、EDX
__thiscall ECX 常保存 this 依编译器 成员访问基于 ECX

可变参数函数通常需要调用者清栈,因为只有调用者知道实际参数数量。

参数恢复

恢复函数原型时同时检查:

  • 调用前准备了多少数据。
  • 函数内部实际读取了哪些参数。
  • 参数按值使用还是作为地址解引用。
  • 所有调用点是否保持一致。

未被读取的压栈数据可能是对齐、寄存器保存或其他调用的准备,不应仅凭一处 push 计数。

返回值

常见规则:

  • x86 整数和指针:EAX
  • x86 宽整数:可能使用 EDX:EAX
  • Windows x64 整数和指针:RAX
  • 浮点数:常见 XMM0,旧 x86 代码可能使用 x87。
  • 大对象:可能通过隐藏返回缓冲区指针完成。

Windows x64 调用约定

前四个整数或指针参数使用:

1
RCX, RDX, R8, R9

调用者还需要:

  • 预留 32 Byte 影子空间。
  • 在调用点满足 16 Byte 栈对齐要求。
  • 把更多参数放到栈中。

非易失寄存器在函数返回前必须恢复,易失寄存器可被被调用函数破坏。

帧指针省略与内联

优化构建中可能不使用 EBP/RBP 作为帧指针,局部变量直接基于 ESP/RSP 寻址。小函数还可能完全内联,不再存在独立 CALL

函数边界、参数数量和调用约定必须结合调用点、返回点、栈变化与展开信息共同判断。

函数分析模板

1
2
3
4
5
6
7
8
9
函数地址:
调用者:
调用方式:
参数列表:
返回值:
保存的寄存器:
局部变量:
读取/修改的外部数据:
副作用:

学习检查清单

检查项 状态
能说明 CALL 与 RET 对栈的影响 待复盘
能画出经典 x86 栈帧 待复盘
能区分 cdecl、stdcall、fastcall 和 thiscall 待复盘
能说明 Windows x64 前四个整数参数的位置 待复盘
能根据调用点验证候选函数原型 待复盘