Direct C function call using GCC's inline assembly
Asked Answered
R

4

16

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.

Recife answered 12/8, 2010 at 11:36 Comment(6)
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!Mantis
@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).Recife
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.Mantis
@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.Recife
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??Mantis
@R..: Yes that's probably the best option although I don't like having to hard-code the mangled name... And yes, I really am using C++:-) It's just a hobby kernel, though.Recife
R
18

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.

Recife answered 13/8, 2010 at 9:56 Comment(8)
See also stackoverflow.com/questions/37639993/… for the dangers of using call from inline asm. It's basically impossible to do reliably for x86-64 System V (Linux), because you can't tell the compiler you want to clobber the red-zone. It's generally a bad idea. See stackoverflow.com/questions/7984209/….Obannon
@PeterCordes this seems to be the most upvoted question for "how to do function call from inline assembly", I recommend that we centralize the pitfall answer here.Imprudent
@CiroSantilli新疆改造中心996ICU六四事件: Maybe an edit to this answer to include a big fat warning and links to more details would be appropriate. And to include a "memory" clobber unless the function you're calling is known not to have any side-effects visible to this function.Obannon
@PeterCordes that would help. My favorite answer so far is Michael's concrete attempt at it: stackoverflow.com/questions/37502841/…Imprudent
And BTW, update: %P is documented now, to print symbol names without decoration. Or to print foo@plt if needed/appropriate. gcc.gnu.org/onlinedocs/gcc/…Obannon
@PeterCordes, any idea on why this doesn't work with -fpic (gcc.godbolt.org/z/9qd8Y9Ge7)?Emulous
@PSkocik - Possibly because a symbol address isn't an absolute constant in position-independent / relocatable code? No, they work with -fpie, and the only(?) difference between PIC and PIE is supporting symbol-interposition. (And that libraries should be using the main executable's definition of global vars, not their own, if the visibility isn't "hidden", but that's not relevant here. And I think that's pretty much a special case of symbol interposition.) IDK why it doesn't just reference the PLT entry since you didn't use -fno-plt.Obannon
@PSkocik: Ah, using "g"(callee), we can see what GCC wanted to use as the operand: gcc.godbolt.org/z/33zos3nvv call QWORD PTR callee@GOTPCREL[rip] for -fpic / -fpie. It insists on take the function address this way, instead of just printing the label and letting the linker replace it. This is normally a good thing when compiling code that sets function pointers to library functions; they bypass the PLT. I think older GCC didn't used to do that, at least when the documentation was written.Obannon
A
2

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.

Antipus answered 13/1, 2016 at 13:13 Comment(1)
This is extremely unsafe. Don't use GNU C basic asm inside a function (unless it's __attribute__((naked))). Your caller can inline into other functions, but its asm statement doesn't declare any clobbers even though it trashes all of the call-clobbered registers (including ZMM0-31, k0-7, mm0-7, st0-7), and the red-zone. It's a huge pain to safely make a function call from inside an asm statement: Calling printf in extended inline ASMObannon
S
1

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));
Suzettesuzi answered 11/6, 2015 at 20:6 Comment(1)
missing clobbers on all the call-clobbered registers, like ZMM0-7, k0-7, etc.Obannon
C
-1

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.

Canella answered 12/8, 2010 at 15:39 Comment(3)
No, this doesn't work at all. First, the '$' is totally wrong for call, second he does not want "my_function" in the asm string, but the mangled name that C++ generates.Halverson
I did not see that this was for C++ too. In that case it is likely that this won't work if the function name is overloaded without lots of hoops. I just copied the "$" from another post since I didn't remember off hand the x86 asm syntax. Fixing that...Canella
It's also GNU C Basic asm, missing clobber declarations on all the call-clobbered registers. So you can expect this to break if used anywhere except a trivial function that doesn't inline, and even then it still might. gcc.gnu.org/wiki/ConvertBasicAsmToExtended Only an __attribute__((naked)) function would be safe (also from the clobbers, because they you're on your own.)Obannon

© 2022 - 2024 — McMap. All rights reserved.