[英]too many memory references for `mov' when calling a golang function with C by using inline assembly
我正在尝试从我的 C 代码中调用一个 golang 函数。 Golang 不使用标准的 x86_64 调用约定,所以我不得不求助于自己实现转换。 由于 gcc 不想将 cdecl 与 x86_64 约定混合使用,我正在尝试使用内联汇编调用该函数:
void go_func(struct go_String filename, void* key, int error){
void* f_address = (void*)SAVEECDSA;
asm volatile(" sub rsp, 0xe0; \t\n\
mov [rsp+0xe0], rbp; \t\n\
mov [rsp], %0; \t\n\
mov [rsp+0x8], %1; \t\n\
mov [rsp+0x18], %2; \t\n\
call %3; \t\n\
mov rbp, [rsp+0xe0]; \t\n\
add rsp, 0xe0;"
:
: "g"(filename.str), "g"(filename.len), "g"(key), "g"(f_address)
: );
return;
}
可悲的是,编译器总是向我抛出一个我不明白的错误:
./code.c:241: Error: too many memory references for `mov'
这对应于这一行: mov [rsp+0x18], %2; \\t\\n\\
mov [rsp+0x18], %2; \\t\\n\\
如果我删除它,则编译工作。 我不明白我的错误是什么......
我正在使用 -masm=intel 标志进行编译,因此我使用 Intel 语法。 有人可以帮帮我吗?
"g"
约束允许编译器选择内存或寄存器,所以很明显mov mem,mem
如果发生这种情况mov mem,mem
你最终会得到mov mem,mem
。 mov
最多可以有 1 个内存操作数。 (与所有 x86 指令一样,最多可以有一个显式内存操作数。)
对将移动到内存目标的输入使用"ri"
约束,以允许寄存器或立即但不允许内存。
此外,您正在修改 RSP,因此您无法安全地使用内存源操作数。 编译器将假定它可以使用[rsp+16]
或[rsp-4]
等寻址模式。 所以你不能使用push
而不是mov
。
您还需要在所有调用破坏的寄存器上声明 clobbers,因为函数调用会这样做。 (或者更好,也许要求在那些调用破坏的寄存器中输入输入,这样编译器就不必通过像 RBX 这样的调用保留的寄存器来反弹它们。但是你需要使这些操作数读/写或声明单独的输出操作数相同的寄存器,让编译器知道它们将被修改。)
所以可能你对效率的最佳选择是这样的
int ecx, edx, edi, esi; // dummy outputs as clobbers
register int r8 asm("r8d"); // for all the call-clobbered regs in the calling convention
register int r9 asm("r9d");
register int r10 asm("r10d");
register int r11 asm("r11d");
// These are the regs for x86-64 System V.
// **I don't know what Go actually clobbers.**
asm("sub rsp, 0xe0\n\t" // adjust as necessary to align the stack before a call
// "push args in reverse order"
"push %[fn_len] \n\t"
"push %[fn_str] \n\t"
"call \n\t"
"add rsp, 0xe0 + 3*8 \n\t" // pop red-zone skip space + pushed args
// real output in RAX, and dummy outputs in call-clobbered regs
: "=a"(retval), "=c"(ecx), "=d"(edx), "=D"(edi), "=S"(esi), "=r"(r8), "=r"(r9), "=r"(r10), "=r"(r11)
: [fn_str] "ri" (filename.str), [fn_len] "ri" (filename.len), etc. // inputs can use the same regs as dummy outputs
: "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", // All vector regs are call-clobbered
"xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15",
"memory" // if you're passing any pointers (even read-only), or the function accesses any globals,
// best to make this a compiler memory barrier
);
请注意,输出不是early-clobber,因此编译器可以(根据其选择)使用这些寄存器作为输入,但我们不会强制这样做,因此编译器仍然可以自由使用其他寄存器或立即数。
经过进一步讨论,Go 函数不会破坏 RBP,因此没有理由手动保存/恢复它。 您可能想要的唯一原因是本地人可能使用 RBP 相对寻址模式,而较旧的 GCC 在没有-fomit-frame-pointer
情况下-fomit-frame-pointer
编译时,会导致在 RBP 上声明破坏者是一个错误。 (我想。或者也许我正在考虑 32 位 PIC 代码中的 EBX。)
此外,如果您使用的是 x86-64 System V ABI,请注意内联 asm 不得破坏红色区域。 编译器假定这不会发生,并且无法在红色区域上声明一个 clobber,甚至无法在每个函数的基础上设置-mno-redzone
。 所以你可能需要sub rsp, 128 + 0xe0
。 或者0xe0
已经包含足够的空间来跳过红色区域,如果那不是被调用者参数的一部分。
原始海报将此解决方案添加为对他们问题的编辑:
如果有人发现了这一点,那么当您尝试使用内联 asm 调用 golang 代码时,接受的答案对您没有帮助! 接受的答案仅对我最初的问题有所帮助,这有助于我修复 golangcall。 使用这样的东西:**
void* __cdecl go_call(void* func, __int64 p1, __int64 p2, __int64 p3, __int64 p4){
void* ret;
asm volatile(" sub rsp, 0x28; \t\n\
mov [rsp], %[p1]; \t\n\
mov [rsp+0x8], %[p2]; \t\n\
mov [rsp+0x10], %[p3]; \t\n\
mov [rsp+0x18], %[p4]; \t\n\
call %[func_addr]; \t\n\
add rsp, 0x28; "
:
: [p1] "ri"(p1), [p2] "ri"(p2),
[p3] "ri"(p3), [p4] "ri"(p4), [func_addr] "ri"(func)
: );
return ret;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.