Hi I've met a situation that would need to pass parameters into functions that is predefined like the following:
void dojmp(args...)
{
args[0]->func(args...);
}
Instead of writing multiple jumping functions to pass different parameters, I would like to use a naked jump, so that after jumping, the relative stack and register data were not changed. Like the following.
void __attribute__((naked)) dojmp(void)
{
__asm__ __volatile__ (
"jmp %%rax \n\t"
:
:"a"(args[0]->func)
:"memory"
);
}
And the gcc compiled result is:
<dojmp>:
mov %rdi,%rax
mov (%rax),%rax
jmpq *%rax
Now my question is that if it is safe for using rax
as jumping target (for gcc) with void
return function??
Since normally rax
would be storing returning value. But all of the function I'm calling is void....
Just remembering it here for passing the relative address into an assembly code, in case I messed up with my notes.
#include <stdio.h>
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
struct have_func{
int a;
void* func;
};
int main()
{
printf("hello world\n");
}
void __attribute__((naked)) dojmp(struct have_func * args)
{
asm volatile(
"jmpq *%P0(%%rdi)\n\t"
:
: "i"(offsetof(struct have_func, func))
);
}
You don't need inline asm for this. Calling this wrapper function should be exactly equivalent to casting a function pointer to the same prototype as you use for the wrapper. (In terms of arg-passing to the target function.)
Remember that the RAX return-value register isn't written by the function until after you've jumped to it, so yes of course it's fine to use RAX as a temporary before a tailcall, even to a function with a return value. That's not a problem. You mess with RAX, then the function runs, then it returns to your caller.
However, x86-64 System V does use AL for variadic functions to pass the count of XMM register arg. Stepping on that is problematic if your functions actually are variadic, using void foo(arg a, ...)
with a literal ...
in the actual C source. Then AL needs to be passed through (for efficiency in modern implementations, and for correctness in the general case including older GCC code-gen that would use AL for a computed-jump into a sequence of movdqa
stores.)
It's technically not safe or supported to use Extended Asm in a naked
function anyway (because handing the operands involves compiler-generated code). So your best bet for multiple reasons is to use a memory-indirect jump instead of asking the compiler to load for you:
asm( "jmpq *(%rdi)" );
That hard-codes offsetof(args, func)
as 0, so you can static_assert on that (or wherever in the struct you put it).
Using Extended Asm like "i"(offsetof(..))
or "m"(args[0]->func)
may work but is technically not supported in a naked
function. (Because it's Extended asm).
This attribute allows the compiler to construct the requisite function declaration, while allowing the body of the function to be assembly code. The specified function will not have prologue/epilogue sequences generated by the compiler. Only basic asm statements can safely be included in naked functions (see Basic Asm). While using extended asm or a mixture of basic asm and C code may appear to work, they cannot be depended upon to work reliably and are not supported.
Introducing this extra indirection (and defeating inlining) in the compiler-generated asm just to keep the compiler happy about function types kinda sucks. https://gcc.gnu.org/wiki/DontUseInlineAsm
Better if you can avoid inline asm entirely, although that might come at the cost of using a variadic function-pointer type but actually pointing it at non-variadic functions. x86-64 System V may handle this well enough, at least if none of the args end up default-promoted when you need them not to be.
args[0]->func(args, ...)
e.g.
void (*arg0func)(struct Arg, ...) = ( void (*)(struct Arg, ...) )args[0]->func;
arg0func(args, whatever you were going to pass);
You can wrap that with a CPP macro.
There might even be a way to write a variadic function that can inline and forward all its args on to another function.