0

I'd like to create a variadic function that sums its inputs. I've managed to write inline assembly for capturing arguments 2-6, but I'm struggling to retrieve the other arguments from the stack frame.

I'm trying to solve a coding exercise that teaches you to understand the stack frame and how arguments are accessed in assembly, which is why I'm not using va.

I've been following the Linux x86-64 calling convention with information from this site. This has led to my current attempt:

int n_sum(int n, ...)
{
  int sum = 0;
  
  if (n > 0)
    __asm__("add %%rsi, %0": "+m"(sum));
  
  if (n > 1)
    __asm__("add %%rdx, %0": "+m"(sum));
  
  if (n > 2)
    __asm__("add %%rcx, %0": "+m"(sum));
  
  if (n > 3)
    __asm__("add %%r8, %0": "+m"(sum));
  
  if (n > 4)
    __asm__("add %%r9, %0": "+m"(sum));
  
  printf("%d\n", sum);
  
  if (n > 5)
  {
    void *extra_args;
    __asm__("mov %%rbp, %0" : "=r"(extra_args));
    printf("%p\n", extra_args);
    extra_args += 16;
    printf("%d\n", extra_args);
  }
  
  return sum;
}

int main()
{
  n_sum(10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
  return 0;
}

My expectation is that the command: __asm__("mov %%rbp, %0" : "=r"(extra_args)); has moved the %rbp's value to my extra_args pointer. Therefore adding 16 to this value should move me to the 7th argument, stored in the stack frame. But I get the incorrect value when I try to print this as an integer.

I presume I'm not accessing the stack arguments correctly, but I don't know why! How can I fix this?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Connor
  • 867
  • 7
  • 18
  • 1
    You can't do it with inline asm this way, you'd have to use an `__attribute__((naked))` function that you write entirely in asm. Even your existing code for accessing registers is totally unsafe undefined behaviour that only happens to work, and will often break, especially with optimization enabled (and function inlining). If you want to do it in a non-`naked` function, you need to do use `va_start` and the usual stuff to get a C object holding the value you want, then `asm("add %1, %0" : "+m"(sum) : "ri"(arg6))` – Peter Cordes Apr 16 '23 at 09:18
  • 1
    The amount of stack space reserved by the compiler is unknown; GCC might reserve an extra 16 bytes beyond what it needs, or not. If you look at the generate asm for your version of GCC with your source with your optimization options, you could figure out a way to reference the stack slot above your return address that would happen to work with the same optimization options. If the function sets up RBP as a frame pointer, it's easy, `16(%rbp)`. But GCC only does that at `-O0`. And doing it this was in *inline* asm is a terrible way to learn anything. Just read the compiler-generated asm. – Peter Cordes Apr 16 '23 at 09:24
  • @PeterCordes Thank you, interesting! So what is an attribute naked function, and why is it better than inline asm? Also why is what I'm doing unsafe with the registers? – Connor Apr 16 '23 at 09:33
  • @PeterCordes Unfortunately, I can't test on my local machine because the code is sent off to a server and tested, so I can't look at the exact assembly after compilation! – Connor Apr 16 '23 at 09:34
  • 1
    `__attribute__((naked))` means no compiler-generated code other than your inline asm, so you just write the whole function in asm, inside a big `asm("")` statement. It's essentially the same as writing a separate `.s` file, except for stuff like getting the compiler to do C++ name mangling for you. If you'd actually want to know about `__attribute__((naked))`, google can found info for you... The GCC manual entry is https://gcc.gnu.org/onlinedocs/gcc/x86-Function-Attributes.html#index-naked-function-attribute_002c-x86 – Peter Cordes Apr 16 '23 at 09:38
  • @PeterCordes Thank you! Why is what I'm presently doing unsafe? – Connor Apr 16 '23 at 09:39
  • *Unfortunately, I can't test on my local machine because the code is sent off to a server and tested, so I can't look at the exact assembly after compilation!* Then this exercise is ridiculous, stop wasting our time with this nonsense. Set up a usable dev environment on your own machine if you want to learn more about asm and stack frames. Or write some debug-prints in your code to dump registers like RBP and RSP. In theory you could write a program that would hexdump machine code starting at a function address, so you could disassemble it on another machine. – Peter Cordes Apr 16 '23 at 09:42
  • But likely a bit of trial and error with a similar GCC version on https://godbolt.org/ and matching options (like `-mstack-protector-strong -fpie`) will get you asm that's the same as what GCC makes on whatever server you're talking about. – Peter Cordes Apr 16 '23 at 09:42
  • 1
    It's unsafe because your code assumes no compiler-generated code will use those RSI through R9 for anything before your last `if() asm()`. Or that the args will even be there after this function inlines into a caller. GCC can see that this function doesn't access any of its variadic args so will optimize them away. Both these assumptions are probably fine with optimization disabled (the default, where `-fno-omit-frame-pointer` is also the default, otherwise RBP is unrelated to accessing args.) – Peter Cordes Apr 16 '23 at 09:46
  • 1
    Generally it's not safe to assume anything about a register value in an `asm` statment unless you used an input constraint like `"r"(foo)` to tell the compiler you want some value somewhere. Or `"S"(foo)` to ask for something in ESI/RSI specifically. See https://stackoverflow.com/tags/inline-assembly/info and see also [Why can't local variable be used in GNU C basic inline asm statements?](https://stackoverflow.com/q/60227941) for how to *safely* access C objects. Also [Code blocks Ver.16.01 crashing during run cycle of programme](https://stackoverflow.com/a/41117655) – Peter Cordes Apr 16 '23 at 09:48
  • 1
    If I had to use some ridiculous hack, I'd probably declare a function with 7 explicit args (not `...`), and take the address of the 7th. Assuming the compiler didn't copy it somewhere, it'll be in its arg-passing stack slot. Accessing at an offset from it should access other stack args. ofc you'd have to use `__attribute((noinline))` and maybe `noipa`. [How Get arguments value using inline assembly in C without Glibc?](//stackoverflow.com/a/50283880) shows some major hackery based on knowing the ABI (for `_start` initial environment) to get GCC to make the desired asm w. no `asm` statement. – Peter Cordes Apr 16 '23 at 09:53
  • 1
    See also [How to remove "noise" from GCC/clang assembly output?](https://stackoverflow.com/q/38552116) for more about looking at GCC output. – Peter Cordes Apr 16 '23 at 09:54

0 Answers0