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

时间:2014-05-28 11:02:20

标签: c++ c assembly masm

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

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

  2. 如果没有null,还有另一个调用函数指针的asm函数,这个函数指针也是一个C函数(如1中的函数所设置)。

  3. 以下是我迄今为止对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构建它。

2 个答案:

答案 0 :(得分:3)

首先,您通常需要按照推动它们的相反顺序弹出寄存器,即: push RBXpush RBP ... push R15 - > pop R15 ... pop RSIpop RBXret。这肯定会打破_asm_func的来电者。


接下来,您应该查看Windows x64 calling convention进行正确函数调用所需的一切。正确地满足所有要求是非常重要的,否则在其他一些代码中,事情可能会破裂甚至很晚,这不是最好的调试。

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

但是请注意这一部分:

  

在Microsoft x64调用约定中,调用者负责分配32个字节的"阴影空间"在调用函数之前就在堆栈上(不管实际使用的参数数量),并在调用后弹出堆栈。

因此,您应该在代码之前SUB ESP, 32,然后在ADD ESP, 32之前RET。{/ p>

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

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


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

否则我不会在那里看到任何问题。


最后,汇编程序文件中的行末不需要分号;

答案 1 :(得分:1)

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