mtvec
mtvec

Reputation: 18316

Direct C function call using GCC's inline assembly

If you want to call a C/C++ function from inline assembly, you can do something like this:

void callee() {}
void caller()
{
    asm("call *%0" : : "r"(callee));
}

GCC will then emit code which looks like this:

movl $callee, %eax
call *%eax

This can be problematic since the indirect call will destroy the pipeline on older CPUs.

Since the address of callee is eventually a constant, one can imagine that it would be possible to use the i constraint. Quoting from the GCC online docs:

`i'

An immediate integer operand (one with constant value) is allowed. This includes symbolic constants whose values will be known only at assembly time or later.

If I try to use it like this:

asm("call %0" : : "i"(callee));

I get the following error from the assembler:

Error: suffix or operands invalid for `call'

This is because GCC emits the code

call $callee

Instead of

call callee

So my question is whether it is possible to make GCC output the correct call.

Upvotes: 16

Views: 19982

Answers (4)

mtvec
mtvec

Reputation: 18316

I got the answer from GCC's mailing list:

asm("call %P0" : : "i"(callee));  // FIXME: missing clobbers

Now I just need to find out what %P0 actually means because it seems to be an undocumented feature...

Edit: After looking at the GCC source code, it's not exactly clear what the code P in front of a constraint means. But, among other things, it prevents GCC from putting a $ in front of constant values. Which is exactly what I need in this case.


For this to be safe, you need to tell the compiler about all registers that the function call might modify, e.g. : "eax", "ecx", "edx", "xmm0", "xmm1", ..., "st(0)", "st(1)", ....

See Calling printf in extended inline ASM for a full x86-64 example of correctly and safely making a function call from inline asm.

Upvotes: 18

markpapadakis
markpapadakis

Reputation: 21

Maybe I am missing something here, but

extern "C" void callee(void) 
{

}

void caller(void)
{
  asm("call callee\n");
}

should work fine. You need extern "C" so that the name won't be decorated based on C++ naming mangling rules.

Upvotes: 2

user5000736
user5000736

Reputation:

If you're generating 32-bit code (e.g. -m32 gcc option), the following asm inline emits a direct call:

asm ("call %0" :: "m" (callee));

Upvotes: 1

nategoose
nategoose

Reputation: 12382

The trick is string literal concatenation. Before GCC starts trying to get any real meaning from your code it will concatenate adjacent string literals, so even though assembly strings aren't the same as other strings you use in your program they should be concatenated if you do:

#define ASM_CALL(X) asm("\t call  " X "\n")


int main(void) {
    ASM_CALL( "my_function" );
    return 0;
}

Since you are using GCC you could also do

#define ASM_CALL(X) asm("\t call  " #X "\n")

int main(void) {
   ASM_CALL(my_function);
   return 0;
}

If you don't already know you should be aware that calling things from inline assembly is very tricky. When the compiler generates its own calls to other functions it includes code to set up and restore things before and after the call. It doesn't know that it should be doing any of this for your call, though. You will have to either include that yourself (very tricky to get right and may break with a compiler upgrade or compilation flags) or ensure that your function is written in such a way that it does not appear to have changed any registers or condition of the stack (or variable on it).

edit this will only work for C function names -- not C++ as they are mangled.

Upvotes: -1

Related Questions