返回值的逆向
基本整数与指针类型 (int, char, short, long, 指针)
这是最常见的情况。无论是普通整数还是内存地址(指针),由于它们的长度通常不超过 CPU 寄存器的字长,因此直接通过通用寄存器返回。
- x86 (32位) 架构: 统一通过
eax寄存器返回。- 如果是
char(1字节),通常放在al。 - 如果是
short(2字节),通常放在ax。
- 如果是
- x64 (64位) 架构: 统一通过
rax寄存器返回。- 如果是 32 位整数,放在
eax。
- 如果是 32 位整数,放在
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 字节)时,由于寄存器装不下,编译器会使用一种“隐式指针”的技巧。
底层逻辑步骤:
- 调用者 (Caller) 会在自己的栈帧中预先分配一块足够大的内存,用来接收这个结构体。
- 调用者将这块内存的指针作为隐藏的第一个参数,传递给被调用函数 (Callee)。
- 被调用者 (Callee) 在执行完毕前,将要返回的结构体数据拷贝到这个隐藏参数指向的内存中。
- 最后,被调用者通常会将这个隐藏指针再次放入
eax/rax中返回。
汇编特征 (结构体返回): 你会发现函数明明在 C 源码中没有参数,但在汇编里却接受了一个指针参数(如 x86 stdcall 下通过 push 压栈,或 x64 下放入 rcx/rdi),并且在 ret 之前有大量的内存拷贝操作(如 rep movsd)。