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

我正在尝试从我的C代码中调用golang函数。 Golang不使用标准的x86_64调用约定,因此我不得不自己实现过渡。 由于gcc不想将cdecl与x86_64约定混合使用,
我正尝试使用内联汇编调用该函数:</ p>

  void go_func(struct go_String filename,void * key  ,int error){
void * f_address =(void *)SAVEECDSA;
asm volatile(“ sub rsp,0xe0; \ t
\
mov [rsp + 0xe0],rbp; \ t
\ n mov [rsp],%0; \ t
\
mov [rsp + 0x8],%1; \ t
\
mov [rsp + 0x18],%2; \ t
\
调用%3; \ t
\
mov rbp,[rsp + 0xe0]; \ t
\
添加rsp,0xe0;“

:” g“(filename.str),” g “(filename.len),” g“(密钥),” g“(f_address)
:);
返回;
}
</ code> </ pre>

可悲 编译器总是向我抛出一个我不明白的错误:</ p>

  ./ code.c:241:错误:`mo的内存引用过多 v'
</ code> </ pre>

这对应于以下行: mov [rsp + 0x18],%2; \ t
\ </ code>如果删除它,则编译有效。 我不明白我的错误是什么... </ p>

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

展开原文

原文

I'm trying to call a golang function from my C code. Golang does not use the standard x86_64 calling convention, so I have to resort to implementing the transition myself. As gcc does not want to mix cdecl with the x86_64 convention, I'm trying to call the function using inline assembly:

void go_func(struct go_String filename, void* key, int error){
    void* f_address = (void*)SAVEECDSA;
    asm volatile("  sub     rsp, 0xe0;           \t
\
                    mov     [rsp+0xe0], rbp;   \t
\
                    mov     [rsp], %0;            \t
\
                    mov     [rsp+0x8], %1;       \t
\
                    mov    [rsp+0x18], %2;       \t
\
                    call    %3;                     \t
\
                    mov     rbp, [rsp+0xe0];   \t
\
                    add     rsp, 0xe0;"          
                    :
                    : "g"(filename.str), "g"(filename.len), "g"(key), "g"(f_address)
                    : );
    return;
}

Sadly the compiler always throws an error at me that I dont understand:

./code.c:241: Error: too many memory references for `mov'

This corresponds to this line: mov [rsp+0x18], %2; \t \ If I delete it, the compilation works. I don't understand what my mistake is...

I'm compiling with the -masm=intel flag so I use Intel syntax. Can someone please help me?

c
doudai8783
doudai8783 将您的答案发布为答案,而不是对该问题进行编辑。顺便说一句,如果您只是简单地更改从RSP分配的数字以解决问题,则可以使用push。您的“答案”有多个错误,我在答案中警告过:您不会告诉编译器调用阻塞的寄存器已被破坏,而您踩在红色区域是因为您没有足够的堆栈空间过去了。
一年多之前 回复

2个回答

A "g" constraint allows the compiler to pick memory or register, so obviously you'll end up with mov mem,mem if that happens. mov can have at most 1 memory operand. (Like all x86 instructions, at most one explicit memory operand is possible.)

Use "ri" constraints for the inputs that will be moved to a memory destination, to allow register or immediate but not memory.

Also, you're modifying RSP so you can't safely use memory source operands. The compiler is going to assume it can use addressing modes like [rsp+16] or [rsp-4]. So you can't use push instead of mov.


You also need to declare clobbers on all the call-clobbered registers, because the function call will do that. (Or better, maybe ask for the inputs in those call-clobbered registers so the compiler doesn't have to bounce them through call-preserved regs like RBX. But you need to make those operands read/write or declare separate output operands for the same registers to let the compiler know they'll be modified.)

So probably your best bet for efficiency is something like

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
\t"    // adjust as necessary to align the stack before a call
    // "push args in reverse order"
    "push  %[fn_len] 
\t"
    "push  %[fn_str] 
\t"
    "call 
\t"
    "add   rsp, 0xe0 + 3*8 
\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
    );

Notice that the output are not early-clobber, so the compiler can (at its option) use those registers for inputs, but we're not forcing it so the compiler is still free to use some other register or an immediate.

Upon further discussion, Go functions don't clobber RBP, so there's no reason to save/restore it manually. The only reason you might have wanted to is that locals might use RBP-relative addressing modes, and older GCC made it an error to declare a clobber on RBP when compiling without -fomit-frame-pointer. (I think. Or maybe I'm thinking of EBX in 32-bit PIC code.)


Also, if you're using the x86-64 System V ABI, beware that inline asm must not clobber the red-zone. The compiler assumes that doesn't happen and there's no way to declare a clobber on the red zone or even set -mno-redzone on a per-function basis. So you probably need to sub rsp, 128 + 0xe0. Or 0xe0 already includes enough space to skip the red-zone if that's not part of the callee's args.

doukongpao0903
doukongpao0903 我编辑了问题,以包含调用golang所需的最终代码。 我必须使用mov,因为它不会移动rsp。 尽管如此:谢谢您的支持:)
一年多之前 回复
douzhang1926
douzhang1926 是的...我非常迷恋于模仿golang函数调用,方法是将另一个函数调用的反汇编看成是一个模板,我没想到它实际上是x86-64 cc的一部分。 谢谢!
一年多之前 回复
dsjzmrz78089
dsjzmrz78089 这样就没有理由在内联汇编中保存/还原rbp。 就编译器而言,它就像RBX或R15。
一年多之前 回复
dongyouzhui1969
dongyouzhui1969 对不起,我不清楚。 我的意思是暂时添加其堆栈框架。 Golang实际上并没有棍棒rbp,它会在返回呼叫者之前将其还原。
一年多之前 回复
dtr53557
dtr53557 您确定Go不会在返回之前恢复呼叫者的RBP吗? 制作传统堆栈帧的一部分是保存调用者的RBP,以​​便您可以通过Leave或pop rbp恢复它。
一年多之前 回复
dongzang7182
dongzang7182 我不知道Go将哪些寄存器视为呼叫密集型寄存器,但是我只是用整套呼叫密集型x86-64 System V寄存器集更新了答案。
一年多之前 回复
dongwo5940
dongwo5940 感谢您的更新,现在我的代码崩溃了golang对象的分配(在子例程中),所以我想我正在以某种方式杀死我调用golang函数时上下文所需的其他代码中的重要寄存器。 我必须找出这里到底出了什么问题。 从某种意义上讲,在创建新的堆栈帧并将其位置添加到rbp时,请使用rbp。 运行时始终具有所有被调用函数的列表,其堆栈帧以rbp为单位。 现在,我无法顺利通过通话指令。 之后,我将弄清楚我必须设置哪些俱乐部。
一年多之前 回复
dtqqq24248
dtqqq24248 更新了Clobbers +寄存器输入示例,该示例应允许编译器选择将输入包含在调用密集寄存器中。
一年多之前 回复
douqie3391
douqie3391 杜德,你是救命稻草! 我从您的回应中学到了很多。 再次感谢! 编辑:我明天测试一次后就会接受。 不知道我的函数调用方式是否是最好的方法,但是我正在尝试模拟Golang在调用函数时的功能。
一年多之前 回复



原始张贴者将此解决方案添加为对他们的问题的修改:</ p>

如果有人找到 这样,当您尝试使用嵌入式asm调用golang代码时,可接受的答案对您没有帮助! 接受的答案仅有助于解决我的最初问题,这有助于我修复golangcall。 使用类似这样的东西:** </ p>

  void * cdecl go_call(void * func,int64 p1,__int64 p2,__int64 p3,__int64 p4){
void * ret; \ n asm volatile(“ sub rsp,0x28; \ t
\
mov [rsp],%[p1]; \ t
\
mov [rsp + 0x8],%[p2]; \ t
\
mov [rsp + 0x10],%[p3]; \ t
\
mov [rsp + 0x18],%[p4]; \ t
\
呼叫%[func_addr]; \ t \ n \
添加rsp,0x28;“

:[p1]” ri“(p1),[p2]” ri“(p2),
[p3]” ri“(p3),[p4 ]“ ri”(p4),[func_addr]“ ri”(func)
:);
返回ret;
}
</ code> </ pre>
</ div>

展开原文

原文

The original poster added this solution as an edit to their question:

If someone ever finds this, the accepted answer does not help you when you try to call golang code with inline asm! The accepted answer only helps with my initial problem, which helped me to fix the golangcall. Use something like this:**

void* __cdecl go_call(void* func, __int64 p1, __int64 p2, __int64 p3, __int64 p4){
    void* ret;
    asm volatile("  sub     rsp, 0x28;             \t
\
                    mov     [rsp], %[p1];                      \t
\
                    mov     [rsp+0x8], %[p2];                      \t
\
                    mov     [rsp+0x10], %[p3];                      \t
\
                    mov     [rsp+0x18], %[p4];                      \t
\      
                    call    %[func_addr];               \t
\
                    add     rsp, 0x28; "     
                    :
                    : [p1] "ri"(p1), [p2] "ri"(p2), 
                    [p3] "ri"(p3), [p4] "ri"(p4), [func_addr] "ri"(func)
                    : );
    return ret;
}

dongqiang2358
dongqiang2358 编写在一个测试中起作用的内联汇编程序是很常见的,但实际上已损坏,并且在更改一些不相关的代码后会导致难以调试的问题。 或仅使用新的编译器版本和/或构建选项(尤其是链接时优化可启用跨源文件的内联)。
一年多之前 回复
duanfu1873
duanfu1873 如果忽略红色区域,则它的运动需要一个子rsp偏移量。 通过推,您可以开始免费推动RSP。 但是由于您需要跳过红色区域,因此两种方式都需要一个子和一个添加。 mov的唯一优点是,您可以显式地看到要存储的偏移量,而不必按相反的顺序推送args。 (最右边的arg在前,因此它在最高的地址)。
一年多之前 回复
duanke2012
duanke2012 好的,我明白了。 我更喜欢mov方式,因为它不会强迫我进行其他子调用。 但是我想使用push更清楚。 关于Redzone和Clubbered Register的观点:我知道这一点,只是我没有遇到任何错误(尚未),所以现在可以正常使用。 您的答案仍然是更好,更充实的答案!
一年多之前 回复
dqppv86022
dqppv86022 :明白了。 对于其他读者-我发布这篇文章的目的是捕获OP的后期编辑,对此我不承担任何技术责任;-)。 它被标记为社区Wiki,因为我认为将他的更新更新为答案可能是合理的,无论它是好是坏。 我忘了要做的是从问题中删除他的解决方案。
一年多之前 回复
dqk42179
dqk42179 这个“答案”有多个错误,我在我的警告中警告过:您没有告诉编译器调用调用寄存器,而是踩到了红色区域,因为您没有足够的堆栈空间来超过它 。 sub rsp,0x80 /​​ 4x推送/调用/添加rsp,0x80 + 4 * 8应该可以解决问题,还有我的答案中所示的clobbers。 无需使用mov将args放在堆栈上,push可以很好地工作。 (@michael:我知道这会给您带来便利,但我发布此邮件是为了将来的读者受益。)
一年多之前 回复
Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问