слишком много ссылок на память для `mov 'при вызове функции golang с C с использованием встроенной сборки - PullRequest
0 голосов
/ 13 ноября 2018

Я пытаюсь вызвать функцию golang из моего C-кода. Голанг не использует стандартное соглашение о вызовах 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 голосов
/ 22 ноября 2018

Автор оригинала добавил это решение в качестве правки к своему вопросу:

Если кто-то когда-либо обнаружит это, принятый ответ не поможет вам, когда вы попытаетесь вызвать код golang с помощью встроенного asm! Принятый ответ только помогает с моей первоначальной проблемой, которая помогла мне исправить 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;
}
0 голосов
/ 13 ноября 2018
Ограничение

A "g" позволяет компилятору выбирать память или регистр, поэтому, очевидно, вы получите mov mem,mem, если это произойдет.mov может иметь максимум 1 операнд памяти.(Как и во всех инструкциях x86, возможен не более одного явного операнда памяти.)

Используйте ограничения "ri" для входов, которые будут перемещены в место назначения памяти, чтобы разрешить регистр или немедленный, но не память.

Кроме того, вы модифицируете RSP, поэтому вы не можете безопасно использовать операнды источника памяти.Компилятор предполагает, что он может использовать режимы адресации, такие как [rsp+16] или [rsp-4].Таким образом, вы не можете использовать push вместо mov.


Вам также необходимо объявить clobbers во всех регистрах call-clobbered, потому что вызов функции сделает это.(Или лучше, возможно, запросить входные данные в этих регистрах, перекрывающих вызовы, чтобы компилятору не приходилось сбрасывать их через сохраненные вызовы регистры, такие как 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 допустил ошибку, объявляя клоббер в RBP при компиляции без -fomit-frame-pointer.(Я думаю. Или, может быть, я думаю о EBX в 32-битном коде PIC.)


Кроме того, если вы используете ABI System V x86-64, будьте осторожны, что встроенный asm не должензабить красную зону.Компилятор предполагает, что этого не происходит, и нет способа объявить клоббер в красной зоне или даже установить -mno-redzone для каждой функции.Так что вам, вероятно, нужно sub rsp, 128 + 0xe0.Или 0xe0 уже содержит достаточно места, чтобы пропустить красную зону, если это не является частью аргументов вызываемого абонента.

...