Вызов метода с использованием встроенного ассемблера в gcc

поэтому, как я уже сказал, я пытаюсь вызвать метод, используя встроенный asm, используя gcc. Итак, я искал, как работает x86 и каковы соглашения о вызовах, затем я попробовал несколько простых вызовов, которые работали отлично. Затем я попытался внедрить v8, что было моей первоначальной целью, но это не сработало... Вот мой код:

v8::Handle<v8::Value> V8Method::staticInternalMethodCaller(const v8::Arguments& args, int argsize, void* object, void* method)
{
    int i = 0;
    char* native_args;

    // Move the ESP to the end of the array (argsize is the array size in byte)
    asm("subl %1, %%esp;"
        "movl %%esp, %0;"
        : "=r"(native_args)
        : "r"(argsize));


    // This for loop only converts V8 type to native type,
    // and puts them in the array:

    for (; i < args.Length(); ++i)
    {
        if (args[i]->IsInt32())
        {
            *(int*)(native_args) = args[i]->Int32Value();

            native_args += sizeof(int);
        }
        else if (args[i]->IsNumber())
        {
            *(float*)(native_args) = (float)(args[i]->NumberValue());

            native_args += sizeof(float);
        }
    }

    // Then call the method:

    asm("call *%1;" : : "c"(object), "r"(method));

    return v8::Null();
}

И вот сгенерированная сборка:

__ZN3srl8V8Method26staticInternalMethodCallerERKN2v89ArgumentsEiPvS5_:
LFB1178:
    .cfi_startproc
    .cfi_personality 0,___gxx_personality_v0
    .cfi_lsda 0,LLSDA1178
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    pushl   %ebx
    subl    $68, %esp
    .cfi_offset 3, -12
    movl    $0, -12(%ebp)
    movl    12(%ebp), %eax
/APP
 # 64 "method.cpp" 1
    subl %eax, %esp; movl %esp, %ebx; addl $4, %esp
 # 0 "" 2
/NO_APP
    movl    %ebx, -16(%ebp)
    jmp L74
L77:
    movl    -12(%ebp), %eax
    movl    %eax, (%esp)
    movl    8(%ebp), %ecx
LEHB25:
    call    __ZNK2v89ArgumentsixEi
LEHE25:
    subl    $4, %esp
    movl    %eax, -36(%ebp)
    leal    -36(%ebp), %eax
    movl    %eax, %ecx
    call    __ZNK2v86HandleINS_5ValueEEptEv
    movl    %eax, %ecx
LEHB26:
    call    __ZNK2v85Value7IsInt32Ev
LEHE26:
    testb   %al, %al
    je  L75
    movl    -12(%ebp), %eax
    movl    %eax, (%esp)
    movl    8(%ebp), %ecx
LEHB27:
    call    __ZNK2v89ArgumentsixEi
LEHE27:
    subl    $4, %esp
    movl    %eax, -32(%ebp)
    leal    -32(%ebp), %eax
    movl    %eax, %ecx
    call    __ZNK2v86HandleINS_5ValueEEptEv
    movl    %eax, %ecx
LEHB28:
    call    __ZNK2v85Value10Int32ValueEv
LEHE28:
    movl    %eax, %edx
    movl    -16(%ebp), %eax
    movl    %edx, (%eax)
    movl    -16(%ebp), %eax
    movl    (%eax), %ebx
    movl    $LC4, 4(%esp)
    movl    $__ZSt4cout, (%esp)
LEHB29:
    call    __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    movl    -16(%ebp), %edx
    movl    %edx, (%esp)
    movl    %eax, %ecx
    call    __ZNSolsEPKv
    subl    $4, %esp
    movl    $LC5, 4(%esp)
    movl    %eax, (%esp)
    call    __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    movl    %ebx, (%esp)
    movl    %eax, %ecx
    call    __ZNSolsEi
    subl    $4, %esp
    movl    $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, (%esp)
    movl    %eax, %ecx
    call    __ZNSolsEPFRSoS_E
    subl    $4, %esp
    addl    $4, -16(%ebp)
    jmp L76
L75:
    movl    -12(%ebp), %eax
    movl    %eax, (%esp)
    movl    8(%ebp), %ecx
    call    __ZNK2v89ArgumentsixEi
LEHE29:
    subl    $4, %esp
    movl    %eax, -28(%ebp)
    leal    -28(%ebp), %eax
    movl    %eax, %ecx
    call    __ZNK2v86HandleINS_5ValueEEptEv
    movl    %eax, %ecx
LEHB30:
    call    __ZNK2v85Value8IsNumberEv
LEHE30:
    testb   %al, %al
    je  L76
    movl    -12(%ebp), %eax
    movl    %eax, (%esp)
    movl    8(%ebp), %ecx
LEHB31:
    call    __ZNK2v89ArgumentsixEi
LEHE31:
    subl    $4, %esp
    movl    %eax, -24(%ebp)
    leal    -24(%ebp), %eax
    movl    %eax, %ecx
    call    __ZNK2v86HandleINS_5ValueEEptEv
    movl    %eax, %ecx
LEHB32:
    call    __ZNK2v85Value11NumberValueEv
LEHE32:
    fstps   -44(%ebp)
    flds    -44(%ebp)
    movl    -16(%ebp), %eax
    fstps   (%eax)
    movl    -16(%ebp), %eax
    movl    (%eax), %ebx
    movl    $LC4, 4(%esp)
    movl    $__ZSt4cout, (%esp)
LEHB33:
    call    __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    movl    -16(%ebp), %edx
    movl    %edx, (%esp)
    movl    %eax, %ecx
    call    __ZNSolsEPKv
    subl    $4, %esp
    movl    $LC5, 4(%esp)
    movl    %eax, (%esp)
    call    __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    movl    %ebx, (%esp)
    movl    %eax, %ecx
    call    __ZNSolsEf
    subl    $4, %esp
    movl    $__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, (%esp)
    movl    %eax, %ecx
    call    __ZNSolsEPFRSoS_E
    subl    $4, %esp
    addl    $4, -16(%ebp)
L76:
    incl    -12(%ebp)
L74:
    movl    8(%ebp), %ecx
    call    __ZNK2v89Arguments6LengthEv
    cmpl    -12(%ebp), %eax
    setg    %al
    testb   %al, %al
    jne L77
movl    16(%ebp), %eax
    movl    20(%ebp), %edx
    movl    %eax, %ecx
/APP
 # 69 "method.cpp" 1
    call *%edx;
 # 0 "" 2
/NO_APP
    call    __ZN2v84NullEv
    leal    -20(%ebp), %edx
    movl    %eax, (%esp)
    movl    %edx, %ecx
    call    __ZN2v86HandleINS_5ValueEEC1INS_9PrimitiveEEENS0_IT_EE
    subl    $4, %esp
    movl    -20(%ebp), %eax
jmp L87
L83:
    movl    %eax, (%esp)
    call    __Unwind_Resume
L84:
    movl    %eax, (%esp)
    call    __Unwind_Resume
L85:
    movl    %eax, (%esp)
    call    __Unwind_Resume
L86:
    movl    %eax, (%esp)
    call    __Unwind_Resume
LEHE33:
L87:
    movl    -4(%ebp), %ebx
    leave
    .cfi_restore 5
    .cfi_restore 3
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc

Итак, этот статический метод является обратным вызовом (до этого я выполняю некоторую проверку подписи), который должен вызывать конкретный метод, предоставляющий действительные собственные аргументы C++. Чтобы немного ускориться и избежать копирования аргументов, я пытаюсь загрузить все параметры в локальный массив, а затем изменить ESP, чтобы сделать этот массив аргументом.

Вызов метода работает хорошо, но я не получаю правильных аргументов... Я провел много исследований о вызове функции, соглашении о вызовах и множестве тестов (все они были успешными), но я не понимаю, что такое продолжается... Я что-то пропустил?

По сути, вызываемый должен получить свои аргументы в верхней части esp, в моем случае массив... (я уточняю, что массив действителен)

Я использую ГЦК.


person SRLKilling    schedule 04.10.2012    source источник
comment
Не сожалейте о том, что вы француз или не профессионал, но, пожалуйста, пожалуйста, очень сожалейте о том, что причинили нам этот беспорядок! Пожалуйста, отформатируйте свой код так, чтобы его могли прочитать обычные люди... (это включает в себя новые строки, пробелы, отступы, определения типов и т. д.) Merci!   -  person Kerrek SB    schedule 04.10.2012


Ответы (2)


Есть много проблем с тем, что вы пытаетесь сделать.

  • Вы не можете изменить %esp с помощью встроенной сборки, потому что компилятор, вероятно, использует %esp для ссылки на свои локальные переменные и аргументы. Это может работать, если вместо этого компилятор использует %ebp, но это не гарантируется.

  • Вы никогда не отменяете модификацию %esp перед возвратом.

  • В вашей встроенной сборке вам нужно объявить, что %esp имеет побочный эффект.

  • Вероятно, вам нужно передать object в качестве молчаливого первого аргумента. method - это метод экземпляра, а не статический метод?

  • все это зависит от того, какое соглашение о вызовах вы используете: cdecl, stdcall и т. д.

person Keith Randall    schedule 04.10.2012
comment
Компилятор использует %ebp, и я знаю, что нет никаких гарантий, но когда я смотрю на сгенерированный ASM, он использует %ebp. И да, я забыл отменить модификацию %esp, но это не меняет того факта, что аргументы недействительны внутри вызываемого объекта... Побочный эффект? В С++ существует только соглашение THISCALL, которое использует %ecx для передачи объекта ("c"(object)) и действует так же, как cdecl для аргументов... - person SRLKilling; 04.10.2012
comment
Дополнительные возможности: такие вызовы, как args[i]->Int32Value(), затирают ваш собственный массив аргументов? Может быть, float следует передать как double? Как именно выглядят неправильные аргументы? - person Keith Randall; 04.10.2012
comment
Кстати, thiscall в gcc не проходит this в %ecx. Он передается в стек. en.wikipedia.org/wiki/X86_calling_conventions#thiscall - person Keith Randall; 04.10.2012
comment
Я так не думаю, потому что, добавляя или подставляя значения, такие как 4 или 8, к %esp, аргументы становятся действительными (но если я меняю аргументы, например, даю double и int, мне пришлось изменить это значение, и тем не менее, они были случайными значениями ), и массив заполнен правильно. Неправильные аргументы выглядят, ну, как случайные аргументы, случайные целые числа, или случайные удвоения, или 0... Да, я знаю, и я делал так, но после быстрого просмотра сгенерированного ASM моя версия принимает this в %ecx , и снова вызов правильный (объект и указатель на метод), только аргументы неверны. - person SRLKilling; 04.10.2012
comment
Я добавил сгенерированную сборку для этой функции, но ее долго и сложно отлаживать... - person SRLKilling; 05.10.2012
comment
Я сразу вижу, что у вас есть вызовы, которые используют вершину стека для передачи аргументов, что забивает ваши аргументы: ищите movl %eax, (%esp) - person Keith Randall; 05.10.2012
comment
Я не уверен в этом ... Я имею в виду, что они должны обновить esp, предварительно подставив размер аргументов, иначе они касаются локальных переменных. По сути, то, что я делаю с asm("subl %1, %%esp;" "movl %%esp, %0;" : "=r"(native_args) : "r"(argsize));, похоже на объявление локальной переменной размера argsize, за исключением того, что я гарантирую, что она находится на вершине стека (в противном случае это не гарантируется). Эта переменная-подобная должна быть неприкасаемой, не так ли? - person SRLKilling; 06.10.2012
comment
@SRLKilling: вы делаете неверные предположения о компиляторе. Вы не можете разместить в стеке что-то, о чем компилятор не знает, и ожидать, что он не перезапишет это. - person Keith Randall; 06.10.2012
comment
Я бы разбил ваш код на две подпрограммы: подпрограмма C++, которая выполняет все ваши вызовы (например, Int32Value()) и генерирует простую структуру со всеми аргументами. Затем передайте это подпрограмме сборки, которая копирует аргументы на вершину стека и вызывает целевую подпрограмму. - person Keith Randall; 06.10.2012
comment
Да, это решение, о котором я думал... но оно подразумевает копирование аргументов, а это то, чего я пытаюсь избежать. Но ясно, это то, что я сделаю, если не найду никакого решения - person SRLKilling; 07.10.2012
comment
И я не сказал, что компилятор знает, что я делаю, я сказал, что компилятор использует %esp, вот и все. Поэтому он должен изменить %esp перед передачей аргументов, иначе он перезапишет текущий (%esp), который в моем случае является массивом аргументов, но может быть любой локальной переменной любой функции. То, что я делаю, похоже на объявление новой локальной переменной, и компилятору не обязательно об этом знать. Проблема, как вы сказали, больше связана с другим вызовом, который, не знаю почему, изменяет %esp - person SRLKilling; 07.10.2012
comment
@SRLKilling: компилятору не нужно изменять %esp перед передачей аргументов. Он может выделить локальную переменную область и область передачи аргументов одновременно в начале подпрограммы. - person Keith Randall; 07.10.2012

Я бы рекомендовал не пытаться сделать это самостоятельно, есть много раздражающих мелких деталей, которые нужно сделать правильно. Вместо этого я бы предложил использовать библиотеку FFCALL, в частности avcall набор методов для этого.

Я предполагаю, что что-то вроде этого будет делать то, что вы хотите:

v8::Handle<v8::Value> V8Method::staticInternalMethodCaller(const v8::Arguments& args, int argsize, void* object, void* method)
{
    // Set up the argument list with the function pointer, return type, and
    // pointer to value storing the return value (assuming int, change if
    // necessary)
    int return_value;
    av_alist alist;
    av_start_int(alist, method, &return_value);

    for(int i = args.Length() - 1; i >= 0; i--)
    {
        // Push the arguments onto the argument list
        if (args[i]->IsInt32())
        {
            av_int(alist, args[i]->Int32Value());
        }
        else if (args[i]->IsNumber())
        {
            av_double(alist, (float)(args[i]->NumberValue());
        }
    }

    av_call(alist);  // Call the function

    return v8::Null();
}
person Adam Rosenfield    schedule 04.10.2012
comment
Хм, эта библиотека выглядит отлично, но я делаю C++, а не C... Плюс, даже если я решу использовать библиотеку, я все равно хотел бы учиться, и знать, где мои ошибки, я не понимаю что не так в моем коде. - person SRLKilling; 04.10.2012