Skip to content

返回值传递

在 32 位(x86)逆向工程中,理解返回值的传递机制是分析函数功能的关键。虽然不同的调用约定(Calling Convention)在参数压栈顺序上有所不同,但在如何返回结果这一点上,通常遵循非常一致的硬件规范。

1. 核心通道:寄存器传递

对于大多数基本数据类型,返回值并不通过栈传递,而是直接存储在特定的寄存器中。

  • 整数与指针(32位):这是最常见的情况。函数执行完毕后,返回值会存储在 EAX 寄存器中。

  • 64 位整数(long long):由于 32 位寄存器放不下 64 位数据,编译器会联合使用两个寄存器:EDX:EAX。其中 EDX 存储高 32 位,EAX 存储低 32 位。

  • 浮点数:通常通过浮点寄存器栈的栈顶 ST(0) 传递。


2. 特殊情况:大对象(结构体)的返回

如果函数返回一个很大的结构体(超过 8 字节),寄存器放不下,传递机制会发生显著变化:

  1. 调用者预留空间:调用者(Caller)会在自己的栈帧上预先开辟一块足够大的临时空间。

  2. 隐藏参数:调用者会将这块空间的地址作为一个“隐藏参数”压入栈中。

  3. 被调用者填充:被调用者(Callee)执行逻辑后,将结果拷贝到该地址指向的内存。

  4. 返回地址:最后,EAX 依然会被使用,但它存储的是指向那块内存的地址,而不是具体数值。


3. 逆向中的实战观察

当你使用 x32dbg 调试你编写的程序时,可以按照以下步骤观察返回值:

  • 观察 CALL 之后:在调试器中,单步执行(F8)跳过一个函数调用后,立刻观察右侧寄存器窗口的 EAX。此时 EAX 的值就是该函数的 return 结果。 (依据:用户使用 x32dbg 进行逆向练习)

  • 识别布尔值:如果一个函数返回布尔值(bool),你会发现执行完后 EAX 通常是 1 (true) 或 0 (false)。

  • 函数指针回溯:有时你会看到 mov eax, [ebp-4] 紧跟着 ret,这说明函数将局部变量的值移动到了 EAX 中准备返回。 (依据:用户对局部变量压栈顺序的疑问)


4. 总结对比表

返回值类型 存放位置 逆向关注点
int / char / 指针 EAX 执行 ret 前最后一次对 EAX 的赋值
long long (64位) EDX:EAX 同时观察这两个寄存器的组合
大型结构体 (struct) 内存 + EAX (地址) 寻找调用前压入的“隐藏”指针参数
void (无返回) 无 (EAX 保持原样) EAX 的值通常是函数内最后一次运算的残余