简体   繁体   English

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

[英]x64 asm how to set a function pointer to a _cdecl C function and call it?

I'm trying to do something pretty basic in x64 asm: 我正在尝试在x64 asm中做一些非常基本的事情:

  1. Have an asm function which take a function pointer and sets this in a variable. 有一个使用函数指针并将其设置在变量中的asm函数。 This function is called from C code. 此函数从C代码中调用。

  2. Have another asm function which calls the function pointer if not null, this function pointer is also a C function (as as set by the function in 1). 还有另一个asm函数,如果不为null,它将调用函数指针,该函数指针也是C函数(由1中的函数设置)。

Here is what I have so far for the C side of things: 到目前为止,这是我对C方面的了解:

extern "C" void _asm_set_func_ptr(void* ptr);

void _cdecl c_call_back()
{

}

void init()
{
    _asm_set_func_ptr(c_call_back);
}

And the asm side: 和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 

But it seems I damage the stack after calling _asm_set_func_ptr(), also I'm not sure if how I call g_pFuncPtr in _asm_func is correct? 但是似乎我在调用_asm_set_func_ptr()之后损坏了堆栈,也不确定在_asm_func中如何调用g_pFuncPtr是否正确? What is wrong with my code? 我的代码有什么问题? I'm building this with VS2013 MASM64. 我正在使用VS2013 MASM64构建它。

First, you generally need to pop the registers in the reverse order in which you push them, ie: 首先,通常需要按相反的顺序弹出寄存器,即:
push RBX , push RBP ... push R15 --> pop R15 ... pop RSI , pop RBX , ret . push RBXpush RBP ... push R15 > pop R15 ... pop RSIpop RBXret This will definitely break the caller of _asm_func . 这肯定会破坏_asm_func的调用者。


Next you should look at the Windows x64 calling convention what all is necessary to make proper function calls. 接下来,您应该查看Windows x64调用约定 ,进行正确的函数调用需要做些什么。 It is very important to get all the requirements right, otherwise things can break and even very late in some else's code, which is not the greatest thing to debug. 正确满足所有需求是非常重要的,否则在某些其他代码中事情可能会中断甚至到很晚,这并不是调试的最大事情。

For example, you don't need to save all registers. 例如,您不需要保存所有寄存器。 If the callback function destroys them, it will save and restore them itself. 如果回调函数销毁了它们,它将保存并恢复它们本身。 So no pushing and popping is necessary there, RAX can be invalidated anyway, no argument is being passed in it. 因此,在那里没有必要进行推送和弹出操作,无论如何, RAX都可以无效,也无需传递任何参数。

But then note this part: 但是请注意这一部分:

In the Microsoft x64 calling convention, it's the caller's responsibility to allocate 32 bytes of "shadow space" on the stack right before calling the function (regardless of the actual number of parameters used), and to pop the stack after the call. 在Microsoft x64调用约定中,调用者有责任在调用函数之前(无论使用的实际参数数如何)在堆栈上分配32个字节的“影子空间”,并在调用后弹出堆栈。

So you should do SUB ESP, 32 before your code, then ADD ESP, 32 before the RET . 因此,您应该在代码之前执行SUB ESP, 32 ,然后在RET之前执行ADD ESP, 32

There is also the requirement for " stack aligned on 16 bytes ", but you don't currently need to address that, because "8 bytes of return address + 32 bytes of shadow space + 8 bytes of next return address" is aligned on 16 bytes. 还要求“ 堆栈对齐16个字节 ”,但是您当前不需要解决这个问题,因为“ 8个返回地址+ 32个字节的影子空间+ 8个下一个返回地址”是按16个对齐的字节。

Additionally, the Windows x64 ABI has also strict requirements on exception handling and correct unwinding. 此外,Windows x64 ABI对异常处理和正确展开也有严格的要求。 As Raymond pointed out in the comment, because your function is not a leaf one (calls other functions), you need to provide a proper prologue and epilogue instead -- see here . 正如Raymond在评论中指出的那样,由于您的函数不是叶子函数(调用其他函数),因此您需要提供适当的序言和结尾-参见此处


The temporary saving of RCX at the beginning of _asm_set_func_ptr is unnecessary. 不需要在_asm_set_func_ptr的开头临时保存RCX

Otherwise I don't see any problems there, though. 否则,我在那里看不到任何问题。


Finally, semicolons ; 最后是分号; are not needed at end of lines in assembler files. 汇编文件中的行尾不需要。

You're pushing a lot of registers before checking g_pFuncPtr, but you're not popping them back off the stack if it's not been set. 在检查g_pFuncPtr之前,您要推入很多寄存器,但是,如果未设置它们,则不会将它们弹出堆栈。 If you push something onto the stack & then don't make the call and don't pop them back, your stack will fill up fast. 如果您将某些东西推入堆栈,然后不拨打电话也不将其弹出,则您的堆栈将很快填满。

You MUST pop registers in the opposite order of pushing them, or you'll get back the wrong registers. 您必须以与推送相反的顺序弹出寄存器,否则您将取回错误的寄存器。

Last, don't waste time & CPU cycles pushing them at all unless you have something to do with them: 最后,除非您与它们有任何关系,否则不要浪费时间和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

... and please - please... do read up on setting up stack frames and managing stack frames inside calls. ...而且请-请...请仔细阅读如何设置堆栈框架以及如何在调用中管理堆栈框架。 C calls and ASM calls handle stack frames very differently from each other. C调用和ASM调用对堆栈帧的处理彼此非常不同。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM