x64 asm如何设置指向_cdecl C函数的函数指针并调用它



我正在尝试在x64 asm:中做一些非常基本的事情

  1. 有一个asm函数,它接受一个函数指针并将其设置在一个变量中。这个函数是从C代码中调用的。

  2. 如果函数指针不为null,请使用另一个调用函数指针的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构建这个。

首先,您通常需要按照推送寄存器的相反顺序弹出寄存器,即:
push RBXpush RBP。。。push R15->pop R15。。。pop RSIpop RBXret。这肯定会打断_asm_func的调用者。


接下来,你应该看看Windows x64调用约定,进行正确的函数调用需要什么。正确处理所有的需求是非常重要的,否则事情可能会中断,甚至在其他代码的很晚的时候发生,这不是最好的调试方法。

例如,您不需要保存所有寄存器。如果回调函数破坏了它们,它将自己保存和恢复它们。因此,这里不需要推送和弹出,RAX无论如何都可以无效,其中没有传递任何参数

但请注意这一部分:

在Microsoft x64调用约定中,调用方负责在调用函数之前在堆栈上分配32字节的"阴影空间"(无论实际使用的参数数量如何(,并在调用后弹出堆栈。

因此,您应该在代码之前执行SUB ESP, 32,然后在RET之前执行ADD ESP, 32

还有一个要求是">堆栈在16字节上对齐",但您目前不需要解决这个问题,因为"8字节的返回地址+32字节的阴影空间+8字节的下一个返回地址"是在16字节对齐的。

此外,Windows x64 ABI对异常处理和正确展开也有严格的要求。正如Raymond在评论中指出的那样,因为您的函数不是叶函数(调用其他函数(,所以您需要提供一个适当的序言和尾声——请参阅此处。


_asm_set_func_ptr开始时临时保存RCX是不必要的。

除此之外,我看不出有任何问题。


最后,在汇编文件的行尾不需要分号;

在检查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调用处理堆栈帧的方式非常不同。

最新更新