登录
首页 >  Golang >  Go问答

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

来源:stackoverflow

时间:2024-03-31 17:54:36 442浏览 收藏

学习Golang要努力,但是不要急!今天的这篇文章《使用内联汇编用 C 调用 golang 函数时,“mov”的内存引用过多》将会介绍到等等知识点,如果你想深入学习Golang,可以关注我!我会持续更新相关文章的,希望对大家都能有所帮助!

问题内容

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


解决方案


"g" 约束允许编译器选择内存或寄存器,因此显然如果发生这种情况,您最终会得到 mov mem,memmov 最多可以有 1 个内存操作数。 (与所有 x86 指令一样,最多可以使用一个显式内存操作数。)

对将移动到内存目标的输入使用 "ri" 约束,以允许寄存器或立即数,但不允许内存。

此外,您正在修改 rsp,因此您无法安全地使用内存源操作数。编译器将假设它可以使用 [rsp+16][rsp-4] 等寻址模式。因此您不能使用 push 代替 mov

您还需要在所有调用破坏寄存器上声明破坏者,因为函数调用将执行此操作。 (或者更好的是,可能会要求这些调用破坏寄存器中的输入,这样编译器就不必通过像 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 上声明破坏错误。 (我想。或者也许我正在考虑 32 位 pic 代码中的 ebx。)

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

原始发帖人将此解决方案添加为对其问题的编辑:

如果有人发现这个,当您尝试使用内联汇编调用 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;
}

今天关于《使用内联汇编用 C 调用 golang 函数时,“mov”的内存引用过多》的内容介绍就到此结束,如果有什么疑问或者建议,可以在golang学习网公众号下多多回复交流;文中若有不正之处,也希望回复留言以告知!

声明:本文转载于:stackoverflow 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>