0

so as I said, I'm trying to call a method using inline asm using gcc. So, I searched how x86 works, and what are the calling convention, then I tried some easy call witch worked perfectly. Then I tried to embed v8, which was my original goal, but it didn't work so well... Here's my code :

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();
}

And here is the generated assembly :

__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

So, this static method is a callback (I do some signature checking before) witch is supposed to call the specific method providing valid C++ native args. In order to speed up a little bit and avoid copies of args, I'm trying to load all param in an local array, and then modify the ESP to make this array an argument.

The method call works well, but I don't get correct arguments... I've done lots of research about function call, calling convention, and lots of test (which were all successful), but I don't understand what is going on... Is there something I missed ?

Basically, the callee is supposed to get its arguments at the top of the esp, in my case, the array... (I precise that the array is valid)

I use GCC.

SRLKilling
  • 42
  • 7
  • 2
    Don't be sorry for being French or not a pro, but please *do* feel very sorry for inflicting this mess on us! Please format your code so that ordinary people can read it... (this involves new lines, whitespace, indentation, typedefs, etc.) Merci! – Kerrek SB Oct 04 '12 at 17:25

2 Answers2

2

There are many problems with what you are attempting.

  • You cannot modify %esp using inline assembly, because the compiler is probably using %esp to reference its local variables and arguments. This may work if the compiler uses %ebp instead, but there is no guarantee.

  • You never undo the %esp modification before returning.

  • In your inline assembly, you need to declare that %esp is side-effected.

  • You probably need to pass object as a silent first argument. method is an instance method, not a static method?

  • all of this depends on what calling convention you're using: cdecl, stdcall, etc.

Keith Randall
  • 22,985
  • 2
  • 35
  • 54
  • The compiler is using `%ebp` and I know there is no guarantee but, when I look at the generated ASM, it uses `%ebp`. And yep, I forget to undo `%esp` modification but it's doesn't change the fact that args aren't valid inside the callee... Side-effected ? In c++, there is only the THISCALL convention which uses `%ecx` to pass object (`"c"(object)`), and acts just like cdecl for arguments... – SRLKilling Oct 04 '12 at 18:06
  • More possibilities: calls like `args[i]->Int32Value()` are clobbering your native argument array? Maybe `float` should be passed as `double`? What exactly do the wrong arguments look like? – Keith Randall Oct 04 '12 at 18:57
  • By the way, `thiscall` on gcc doesn't pass `this` in `%ecx`. It gets passed on the stack. http://en.wikipedia.org/wiki/X86_calling_conventions#thiscall – Keith Randall Oct 04 '12 at 19:09
  • I don't think so because by adding or subbing values like 4 or 8 to the `%esp`, args where valid (but if I change args, like giving double and int, I had to change that value, and still, they were random values), and the array is correctly filled. Wrong arguments looks, well, like random arguments,random ints, or random doubles, or 0... Yes, I know, and i was doing this way, but after a quick look into the generated ASM, my version takes `this` in `%ecx`, and again, the call is valid (object, and method pointer), only arguments are wrong. – SRLKilling Oct 04 '12 at 19:37
  • I added the generated assembly for that function, but it's long and hard to debug... – SRLKilling Oct 04 '12 at 20:24
  • I can see immediately that you have calls that are using the top of stack to pass args, which will clobber your args : look for `movl %eax, (%esp)` – Keith Randall Oct 04 '12 at 21:04
  • I'm not sure about that... I mean, they must update the esp by subbing the args size before, otherwise they're touching the local variables. Basically, what I'm doing with `asm("subl %1, %%esp;" "movl %%esp, %0;" : "=r"(native_args) : "r"(argsize));` is just like declaring a local variable of size `argsize`, except that I'm ensuring it is located on the top of the stack (which is not guaranteed otherwise). This variable-like is supposed to be untouchable, isn't it ? – SRLKilling Oct 06 '12 at 10:53
  • @SRLKilling: you're making assumptions about the compiler that just aren't valid. You can't allocate something on the stack that the compiler doesn't know about and expect it not to overwrite it. – Keith Randall Oct 06 '12 at 17:14
  • I would break your code into two routines: a C++ routine which does all of your calls (like `Int32Value()`) and generates a simple struct with all of the arguments. Then pass that to an assembly routine that copies the args to top-of-stack and calls the target routine. – Keith Randall Oct 06 '12 at 17:16
  • Yep, this is a solution I've been thinking of... but it implies copying arguments, and this is what I'm trying to avoid. But cleary, this is what I'll do if I don't find any solution – SRLKilling Oct 06 '12 at 20:35
  • And I didn't say the compiler knows what I'm doing, I said that the compiler uses `%esp`, that's all. So it must modify `%esp` before passing args, otherwise it will overwrite the current `(%esp)`, which in my case is the argument array, but can be any local variable of any function. What I'm doing is exactly like declaring a new local variable and the compiler doesn't have to know it. The problem is, like you said, more about other call, which, dunno why, modify the `%esp` – SRLKilling Oct 06 '12 at 20:46
  • @SRLKilling: the compiler *doesn't* need to modify `%esp` before passing args. It can allocate a local variable region and an arg passing region all at once at the start of the routine. – Keith Randall Oct 07 '12 at 00:32
0

I'd recommend not trying to do this yourself, there are a lot of annoying little details that have to be gotten exactly right. I'd suggest instead using the FFCALL library, specifically the avcall set of methods, to do this.

I imagine that something like this would do what you want:

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();
}
Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
  • Hum, this library looks great, but I'm doing C++ not C... Plus, even if I decide to use a library, I'd still like to learn, and to know where are my mistakes, i don't understand what fails in my code. – SRLKilling Oct 04 '12 at 18:11