我试图在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构建它。
答案 0 :(得分:3)
首先,您通常需要按照推动它们的相反顺序弹出寄存器,即:
push RBX
,push RBP
... push R15
- > pop R15
... pop RSI
,pop RBX
,ret
。这肯定会打破_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调用处理堆栈帧的方式彼此截然不同。