使用内联汇编程序用C调用golang函数时,对于'mov'的内存引用过多

时间:2018-11-13 18:15:18

标签: c go inline-assembly calling-convention

我正在尝试从我的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语法。有人可以帮我吗?

2 个答案:

答案 0 :(得分:2)

"g"约束允许编译器选择内存或寄存器,因此,如果发生这种情况,显然您将以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
    );

请注意,输出不是早期版本的 ,因此编译器可以(可以选择)使用这些寄存器作为输入,但是我们不是强制使用它,这样编译器仍然可以自由使用其他寄存器或立即数。

在进一步讨论中,Go函数不会破坏RBP,因此没有理由手动保存/恢复它。您可能想要的唯一原因是,本地人可能会使用相对于RBP的寻址模式,而较早的GCC在没有-fomit-frame-pointer的情况下进行编译时,在RBP上声明Clobber会出错。 (我认为。或者也许我在考虑32位PIC代码中的EBX。)


此外,如果您使用的是x86-64 System V ABI,请注意,内联asm一定不能掩盖红色区域。编译器假定不会发生这种情况,并且无法在红色区域上声明破坏对象,甚至无法针对每个函数设置-mno-redzone。因此,您可能需要sub rsp, 128 + 0xe0。或0xe0已经包含足够的空间来跳过红色区域(如果这不是被调用者的参数的一部分)。

答案 1 :(得分:0)

原始张贴者将此解决方案添加为对其问题的修改:

如果有人找到了它,那么当您尝试使用内联asm调用golang代码时,可接受的答案对您没有帮助!接受的答案仅有助于解决我的最初问题,这有助于我修复golangcall。使用类似这样的内容:**

x