我正在尝试在x64 asm:中做一些非常基本的事情
-
有一个asm函数,它接受一个函数指针并将其设置在一个变量中。这个函数是从C代码中调用的。
-
如果函数指针不为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 RBX
、push RBP
。。。push R15
->pop R15
。。。pop RSI
、pop RBX
、ret
。这肯定会打断_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调用处理堆栈帧的方式非常不同。