返回值的逆向

基本整数与指针类型 (int, char, short, long, 指针)

这是最常见的情况。无论是普通整数还是内存地址(指针),由于它们的长度通常不超过 CPU 寄存器的字长,因此直接通过通用寄存器返回。

  • x86 (32位) 架构: 统一通过 eax 寄存器返回。
    • 如果是 char (1字节),通常放在 al
    • 如果是 short (2字节),通常放在 ax
  • x64 (64位) 架构: 统一通过 rax 寄存器返回。
    • 如果是 32 位整数,放在 eax

C 代码示例:

int get_number() {
    return 100;
}

汇编特征:

mov eax, 64h  ; 100 的十六进制是 0x64
ret

64位大整数类型 (__int64, long long)

当数据类型的长度超过了当前架构的通用寄存器大小时,编译器会采取拼接的方式。

  • x86 (32位) 架构: 使用两个 32 位寄存器拼接返回。通常是 edx:eax
    • eax 存放低 32 位数据。
    • edx 存放高 32 位数据。
  • x64 (64位) 架构: 由于通用寄存器已经是 64 位,直接使用 rax 即可。

汇编特征 (32位下):

mov eax, [ebp-4] ; 低 32 位
mov edx, [ebp-8] ; 高 32 位
ret

浮点数类型 (float, double)

浮点数由于其特殊的内存结构(IEEE 754 标准),不使用常规的通用寄存器返回,而是使用专门的协处理器寄存器或 SIMD 寄存器。

  • x86 (32位) 架构: 默认使用 FPU (浮点运算单元) 的寄存器栈顶返回,即 ST(0)
  • x64 (64位) 架构: 现代编译器通常默认使用 SSE 寄存器,通过 xmm0 返回。

汇编特征 (x64下使用 SSE):

movss xmm0, dword ptr [rbp-4] ; 返回 float
; 或
movsd xmm0, qword ptr [rbp-8] ; 返回 double
ret

复杂数据类型 / 结构体 (struct)

当函数需要返回一个庞大的结构体对象(通常大于 8 字节)时,由于寄存器装不下,编译器会使用一种“隐式指针”的技巧。

底层逻辑步骤:

  1. 调用者 (Caller) 会在自己的栈帧中预先分配一块足够大的内存,用来接收这个结构体。
  2. 调用者将这块内存的指针作为隐藏的第一个参数,传递给被调用函数 (Callee)。
  3. 被调用者 (Callee) 在执行完毕前,将要返回的结构体数据拷贝到这个隐藏参数指向的内存中。
  4. 最后,被调用者通常会将这个隐藏指针再次放入 eax / rax 中返回。

汇编特征 (结构体返回): 你会发现函数明明在 C 源码中没有参数,但在汇编里却接受了一个指针参数(如 x86 stdcall 下通过 push 压栈,或 x64 下放入 rcx/rdi),并且在 ret 之前有大量的内存拷贝操作(如 rep movsd)。