11
votes

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.

4
Are you sure the indirect call destroys the pipeline? Have you benchmarked? My understanding was that in the old days on x86 (pre-i686), indirect calls were very bad (I recall them being a good 10-100 times slower on my K6), but nowadays cpus are smarter and can deal with them just fine. So do some testing before you jump to conclusions!R.. GitHub STOP HELPING ICE
@R..: You're right: if I benchmark this on a real CPU, it doesn't make any difference. I'm running my code in qemu, however, and it seems to make a difference there (around 20% more cycles/call).mtvec
Then I would just stick with the way you're doing it, with the indirect call. This will allow gcc to generate the correct code for PIC/PIE libraries/executables without you having to insert special hacks to handle these things.R.. GitHub STOP HELPING ICE
@R..: Yes that would probably be the best idea. Although I don't have to worry about PIC/PIE (this is kernel code) so I'm still very much interested in finding a good solution for this problem.mtvec
Well if it's kernel code, just hard-code the call and put __attribute__((used)) on the function so it doesn't get optimized out. You don't have to worry about portability if you have a single target OS and cpu architecture. By the way, are you really using C++ in kernel code??R.. GitHub STOP HELPING ICE

4 Answers

11
votes

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.

2
votes

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.

0
votes

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.

0
votes

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