【问题标题】:x64 asm how to set a function pointer to a _cdecl C function and call it?x64 asm如何将函数指针设置为_cdecl C函数并调用它?
【发布时间】:2014-05-28 11:02:20
【问题描述】:

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

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

  2. 有另一个 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 构建它。

【问题讨论】:

    标签: c++ c assembly masm


    【解决方案1】:

    首先,您通常需要以与推送它们相反的顺序弹出寄存器,即:
    push RBX, push RBP ... push R15 --> pop R15 ... @ 987654327@,pop RBXret。这肯定会破坏_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 中只有一个调用约定?
    猜你喜欢
    • 2013-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多