0

I'm writing a piece of code to get a function's arguments without using stdarg. The arguments will always be integers. The platform is Linux x86_64. Therefore the calling convention should be : first 6 arguments in the registers %rdi, %rsi, %rdx, %rcx, %r8 and %r9, then the following arguments on the stack. With this in mind, I ended up with the following code, which uses inline assembly to get the first 6 arguments and then uses a pointer to the stack to parse the remaining ones.

#define CFI_DEF_CFA_OFFSET 16ull

void get_args (int arg1, ...)
{
    register int rdi __asm__ ("rdi"); // 1st arg
    register int rsi __asm__ ("rsi");
    register int rdx __asm__ ("rdx");
    register int rcx __asm__ ("rcx");
    register int r8  __asm__ ("r8" );
    register int r9  __asm__ ("r9" ); // 6th arg

    printf("%d %d %d %d %d %d\n", rdi, rsi, rdx, rcx, r8, r9);

    uint64_t frame_pointer = (uint64_t)__builtin_frame_address(0) + CFI_DEF_CFA_OFFSET;
    printf("%d\n", *((int*)frame_pointer)); // 1st stack argument
    frame_pointer += 8ull; // going to the next
    printf("%d\n", *((int*)frame_pointer)); // and so on ...
}

int main (void)
{
    get_args(666, 42, 64, 555, 1111, 8888, 7777, 4444);
}

This works fine with GCC, but the inline assembly part does not work with Clang (It compiles but the values seem to be random garbage). Due to my limited knowledge of assembly and possible misinterpretation of the comments on similar questions, I have not understood if it is possible to read these particular registers in a similar fashion with Clang, and if yes, with what syntax.

Thanks for your help !

2 Answers 2

4

At least two showstopper problems, plus other problems:

The function can inline, in which case there's no reason to expect the args to be in any particular registers, or to exist at all since at a C level they're unused. Won't happen at -O0, but unusable for anything other than debug-mode toy experiments, unless you use __attribute__((noinline)) or put it in a different file from the caller and are careful not to use link-time optimization. This is a showstopper for GCC and clang.

Even more fundamental, the only documented (and thus guaranteed) effect of GNU C register-asm local variables is to make sure an "r" constraint picks that register for an Extended asm() statement. The behaviour you're depending on is explicitly not guaranteed by the docs, and thus not officially supported by GCC. It could break in any future GCC version.
It used to be documented, and GCC itself still happens to go beyond that so usually a read of an uninitialized C variable will get whatever was originally in that register, but clang does not. It's just like reading any other uninitialized variable. Look at the compiler-generated asm to see how your code compiled (e.g. on https://godbolt.org/)

Also problematic: any compiler-generated code might use registers before those variables come into scope. Probably unlikely at the top of a function.


To do what you want, declare the function as taking 6 integer/pointer args, then variadic. So the register args all have actual valid C names and you don't need the asm keyword anywhere. Or write get_args by hand in asm.

If you want to pass fewer args, lie to the compiler when you call it, e.g. by providing a prototype with fewer args.

Maybe use __attribute__ ((weak, alias (get_args))) to declare a prototype for a variadic function that you can call with any number of args, but whose asm symbol name is the same as the function you did declare. (This may block inlining, which isn't actually necessary if it's properly valid C.)

I haven't experimented with this because it's basically pointless. If you want to do weird stuff that relies on the calling convention instead of the C abstract machine, write it in asm. C is not a portable assembly language, and modern C is very far from it, even with inline asm to try to beat it into submission.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for your answer. I understand the issues with my approach. I realized that maybe it wasn't clear in my original post that my main interest is to weakly emulate stdarg, not doing so with inline assembly. This would be assuming that I know the number (i.e. through the 1st parameter) and type of arguments (always integer-like) at runtime. I saw another of your answers where you seemingly manage to read a register with (extended ?) asm and compiling in Clang. Could that syntax be suitable for my problem ? Thanks a lot
@talentless: Yes possibly one asm statement with multiple specific-register outputs at the top of a function could work. But I don't think that's better than simply having 6 C args of type unsigned long and maybe a weak-alias hack. For actual real-world production use, I wouldn't recommend an asm calling-convention hack.
I finally found the proper syntax somewhere. Even if I barely understand it, I think that's the idea you were describing. Thanks for your help anyway :)
0

It seems there are several ways of doing this.

  • I can declare the function as taking 6 arguments, then variadic arguments.
void get_args (int argc, int a1, int a2, int a3, int a4, int a5, ...);

Here I can get the first 6 arguments by their name, and then use a pointer to the stack to get the remaining ones To call the function with less than 6 parameters, I can lie to the compiler by declaring another prototype. e.g. :

void get_args (int argc, ...);
  • I can use this assembly syntax to read registers into a variable :
void get_args(int argc, ...)
{
    int rdi, rsi, rdx, rcx, r8, r9;
    // 32-bit alias (why ? ...)   ↓
    __asm__ __volatile__("movl %%r8d, %%eax" : "=a"(r8) :: "rdi", "rsi", "rdx", "rcx", "r8", "r9");
    // repeat this for the 5 other registers         ↑
} 

And similarly getting the >6 args by grabbing the stack address (for example with __builtin_frame_address(0)).

3 Comments

If you're going to use inline asm, use one asm statement that uses "=D" (rdi), "=S"(rsi), "=d"(rdx), etc. outputs with no mov instructions. That's possibly more reliable than 6 separate asm statements. Also, IDK why you're using 32-bit int when args are possibly 64-bit.
Well I barely understand how this syntax works to start with (for instance, why it's using the 32-bit names of the registers). I'm going to try to make the 6 calls into one. Also yes, longs make more sense, it was just guaranteed that parameters would be ints in my original problem.
You're the one writing mov instructions (and choosing to use 32-bit operand size to match the int you picked for your variables). My answer on Reading a register value into a C variable doesn't do that.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.