x64 asm如何将函数指针设置为_cdecl C函数并调用它?
Posted
技术标签:
【中文标题】x64 asm如何将函数指针设置为_cdecl C函数并调用它?【英文标题】:x64 asm how to set a function pointer to a _cdecl C function and call it? 【发布时间】:2014-05-28 11:02:20 【问题描述】:我正在尝试在 x64 asm 中做一些非常基本的事情:
有一个 asm 函数,它接受一个函数指针并将其设置在一个变量中。这个函数是从 C 代码中调用的。
有另一个 asm 函数调用函数指针,如果不为空,这个函数指针也是一个 C 函数(由 1 中的函数设置)。
到目前为止,我对 C 方面的看法如下:
extern "C" void _asm_set_func_ptr(void* ptr);
void _cdecl c_call_back()
void init()
_asm_set_func_ptr(c_call_back);
还有 asm 方面:
.DATA
g_pFuncPtr QWORD 0
.CODE ;Indicates the start of a code segment.
_asm_set_func_ptr PROC fPtr:QWORD
mov [rsp+qword ptr 8], rcx
mov rax, [rsp+qword ptr 8]
mov g_pFuncPtr, rax
ret
_asm_set_func_ptr ENDP
_asm_func PROC
push RBX
push RBP
push RDI
push RSI
push RSP
push R12
push R13
push R14
push R15
CMP g_pFuncPtr, 0
JE SkipCall
MOV RAX, [ g_pFuncPtr ];
CALL RAX;
SkipCall:
pop RBX
pop RBP
pop RDI
pop RSI
pop RSP
pop R12
pop R13
pop R14
pop R15
ret
_asm_func ENDP
但似乎我在调用_asm_set_func_ptr() 后损坏了堆栈,我也不确定在_asm_func 中如何调用g_pFuncPtr 是否正确?我的代码有什么问题?我正在使用 VS2013 MASM64 构建它。
【问题讨论】:
【参考方案1】:首先,您通常需要以与推送它们相反的顺序弹出寄存器,即:push RBX
, push RBP
... push R15
--> pop R15
... @ 987654327@,pop RBX
,ret
。这肯定会破坏_asm_func
的调用者。
接下来您应该查看Windows x64 calling convention 进行正确的函数调用所需的一切。正确满足所有需求非常重要,否则可能会出现问题,甚至在其他代码中很晚,这不是最适合调试的事情。
例如,您不需要保存所有寄存器。如果回调函数销毁它们,它会自己保存和恢复它们。所以那里不需要推送和弹出,RAX
无论如何都可以无效,没有参数被传递。
但是请注意这部分:
在 Microsoft x64 调用约定中,调用者有责任在调用函数之前在堆栈上分配 32 字节的“影子空间”(无论使用的实际参数数量如何),并在调用后弹出堆栈.
所以你应该在你的代码之前写SUB ESP, 32
,然后在RET
之前写ADD ESP, 32
。
还有“栈在 16 字节上对齐”的要求,但你目前不需要解决这个问题,因为“8 字节的返回地址 + 32 字节的影子空间 + 8下一个返回地址的字节”按 16 个字节对齐。
此外,Windows x64 ABI 对异常处理和正确展开也有严格要求。正如 Raymond 在评论中指出的那样,由于您的函数不是叶函数(调用其他函数),因此您需要提供适当的序言和结尾——请参阅 here。
不需要在_asm_set_func_ptr
开头临时保存RCX
。
不过,否则我看不出有任何问题。
最后,在汇编文件的行尾不需要分号;
。
【讨论】:
由于您的函数不是叶函数,因此您还需要声明序言和尾声代码,以便在发生异常时发生正确的事情。【参考方案2】:您在检查 g_pFuncPtr 之前推送了很多寄存器,但如果没有设置,您不会将它们从堆栈中弹出。如果您将某些东西压入堆栈,然后不进行调用也不将它们弹回,您的堆栈将很快填满。
您必须以与推送相反的顺序弹出寄存器,否则您将取回错误的寄存器。
最后,不要浪费时间和 CPU 周期来推动它们,除非你与它们有关:
CMP g_pFuncPtr, 0
JE SkipCall
PUSH RBX
PUSH RBP
PUSH RDI
PUSH RSI
PUSH RSP
PUSH R12
PUSH R13
PUSH R14
PUSH R15
MOV RAX, [ g_pFuncPtr ];
CALL RAX;
POP R15
POP R14
POP R13
POP R12
POP RSP
POP RSI
POP RDI
POP RBP
POP RBX
SkipCall:
ret
...请-请...阅读有关设置堆栈帧和管理调用内的堆栈帧的内容。 C 调用和 ASM 调用处理堆栈帧的方式非常不同。
【讨论】:
Mmmmmmm... 在阁楼上,从我第一次学习使用 DOS 的 C 和 Pascal 调用约定编写汇编语言 (MASM) TSR 时开始... 回到 1990 年代... 这个看起来(乍一看)是一个很好的起点:en.wikibooks.org/wiki/X86_Disassembly/… 这也是:csee.umbc.edu/~chang/cs313.s02/stack.shtml 但这似乎只与32位有关? 64bit 的 ABI 似乎完全不同 不同的寄存器名称,更大的寄存器,但堆栈帧处理技术完全相同。每个 CPU 上的堆栈向下增长,而堆向上增长,C/C++ 调用期望参数在堆栈上的顺序相同(但它们的位置会有所不同,因为寄存器更大),返回处理技术是相同的,所有 PUSHED(而不是被调用函数弹出)仍然需要以相反的顺序获得 POPPED,C 和 PASCAL 调用约定仍然彼此完全不同; ASM 通常使用 PASCAL 样式,因为它更快、更便宜。 但是在 x64 中只有一个调用约定?以上是关于x64 asm如何将函数指针设置为_cdecl C函数并调用它?的主要内容,如果未能解决你的问题,请参考以下文章