猿问

使用内联汇编用 C 调用 golang 函数时,“mov”的内存引用过多

我正在尝试从我的 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\如果我删除它,编译工作。我不明白我的错误是什么......


我正在使用 -masm=intel 标志进行编译,因此我使用了 Intel 语法。有人可以帮帮我吗?


互换的青春
浏览 210回答 2
2回答

手掌心

约束允许编译器选择内存或寄存器,所以很明显,如果发生这种情况, "g"你最终会得到。最多可以有 1 个内存操作数。(与所有 x86 指令一样,最多可以有一个显式内存操作数。)mov mem,memmov"ri"对将被移动到内存目标的输入使用约束,以允许寄存器或立即但不是内存。此外,您正在修改 RSP,因此您无法安全地使用内存源操作数。编译器将假设它可以使用[rsp+16]or之类的寻址模式[rsp-4]。所以你不能使用pushinstead of mov。您还需要在所有被调用破坏的寄存器上声明破坏者,因为函数调用会这样做。(或者更好的是,也许在那些被调用破坏的寄存器中请求输入,这样编译器就不必通过像 RBX 这样的调用保留寄存器来反弹它们。但是你需要让这些操作数读/写或声明单独的输出操作数相同的寄存器让编译器知道它们将被修改。)因此,提高效率的最佳选择可能是int ecx, edx, edi, esi; // dummy outputs as clobbersregister int r8 asm("r8d");  // for all the call-clobbered regs in the calling conventionregister 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. (我想。或者也许我正在考虑 32 位 PIC 代码中的 EBX。)此外,如果您使用的是 x86-64 System V ABI,请注意内联 asm 不得破坏红色区域。编译器假设这不会发生,并且没有办法在红色区域声明一个 clobber,甚至无法-mno-redzone在每个函数的基础上设置。所以你可能需要sub rsp, 128 +  0xe0。或者0xe0已经包含足够的空间来跳过红色区域,如果它不是被调用者参数的一部分的话。

精慕HU

原始发布者将此解决方案添加为对他们问题的编辑:如果有人发现这个,当您尝试使用内联 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;}
随时随地看视频慕课网APP

相关分类

Go
我要回答