0

I want to retrieve the parameters from the program stack by their consecutive relative position in memory, but it fails, any idea on the following snippet of code?

#include <stdio.h>

int __cdecl sum(int num, ...) {
  int* p = &num + 1;
  int ret = 0;
  while(num--) {
    ret += *p++; 
  }
  return ret;
}
int __cdecl main(int argc, char** argv) {
  printf("%d\n", sum(3, 1, 2, 3));  // wrong result!
  return 0;
}

Compiling by the below command:

clang sum.c -g -O0 -o sum
Jason Yu
  • 1,886
  • 16
  • 26
  • 2
    The parameters passing is depending on the ABI. Which one you are assuming? – Eugene Sh. Jun 24 '20 at 17:04
  • 1
    here is more info https://stackoverflow.com/questions/40216160/variadic-arguements-and-x64 – Eugene Sh. Jun 24 '20 at 17:08
  • 1
    This is *hugely* dependent on the architecture, I've worked on systems that used a combination of stack and registers to pass parameters, as well as one system where the stack grew *up*. – Steve Friedl Jun 24 '20 at 17:17
  • 2
    Use `stdarg.h` and do the job cleanly. – Jonathan Leffler Jun 24 '20 at 17:26
  • It's true, you can't be certain how the parameters are being passed because it will be architecture dependent, and if they are even using the stack. It's quite possible for some of the parameters to be passed via registers, so this clearly doesn't work in that situation. Perhaps write the code in assembly for more consistent behavior. Additionally, providing your compiler commands will help give further insight. – h0r53 Jun 24 '20 at 17:27
  • I declare __cdecl as a suffix of each function, it should follow the cdecl calling convention, meaning the parameters will be passed into the stack from right to left, and also the caller has responsibility to clean the stack afterward. – Jason Yu Jun 24 '20 at 21:18
  • That's not what cdecl means, and many implementations don't work that way. – Steve Friedl Jun 24 '20 at 21:34

2 Answers2

3

This depends so much on the architecture that no one answer can tell you how to do this, and the advice to use <stdarg.h> is overwhelmingly the proper way to solve the problem.

Even on a given machine, compiler options themselves can change the layout of the stack frame (not to mention the difference between 32-bit and 64-bit code generation).

However, if you're doing this simply because you're curious and are trying to investigate the architecture - a fine goal in my book - then the best way to noodle around with it is to write some code in C, compiler it to assembler, and look at the asm code to see how the parameters are passed.

// stack.c
#include <stdio.h>

int sum(int count, ...)
{
    int sum = 0;
    // stuff
    return sum;
}

int main()
{
    printf("%d\n", sum(1, 1));
    printf("%d\n", sum(2, 1, 2));
    printf("%d\n", sum(3, 1, 2, 3));
    printf("%d\n", sum(4, 1, 2, 3, 4));
    printf("%d\n", sum(10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10));

    return 0;
}

On Linux, you can use either clang -S stack.c or gcc -S stack.c to produce a readable stack.s assembler output file, and with Visual C on Windows, cl /Fa stack.c will produce stack.asm

Now you can look at the different flavors of how the data is passed; when I try this on my Windows system, the 32-bit compiler does a routine push/push/push prior to the call (and might plausibly be negotiated on your own), while the 64-bit compiler does all kinds of things with registers that you won't manage yourself.

So, unless you're just curious about assembly language and architectures, I urge you to use <stdarg.h> facilities to get this done.

EDIT responding to the OP's comment:

I declare __cdecl as a suffix of each function, it should follow the cdecl calling convention, meaning the parameters will be passed into the stack from right to left, and also the caller has responsibility to clean the stack afterward.

This is a common implementation, but this is not what cdecl means; it means "use the calling convention compatible with how C does things", and though passing arguments on the stack is one of them, it's not the only way.

  1. There is no requirement that arguments be passed left or right.
  2. There is no requirement that the stack grows down.
  3. There is no requirement that arguments be passed on the stack at all.

On my 64-bit system, the assembly language for the 10-parameter call looks like:

; Line 20
    mov DWORD PTR [rsp+80], 10
    mov DWORD PTR [rsp+72], 9
    mov DWORD PTR [rsp+64], 8
    mov DWORD PTR [rsp+56], 7
    mov DWORD PTR [rsp+48], 6
    mov DWORD PTR [rsp+40], 5
    mov DWORD PTR [rsp+32], 4
    mov r9d, 3
    mov r8d, 2
    mov edx, 1
    mov ecx, 10
    call    sum
    mov edx, eax
    lea rcx, OFFSET FLAT:$SG8911
    call    printf

The first handful of parameters are passed right-to-left on the stack, but the rest are passed in registers.

What cdecl really means is the caller has to clean up the parameters (however passed) after the function returns rather than the callee doing so. Callee-cleanup is a bit more efficient, but that does not support variable-length parameters.

Steve Friedl
  • 3,929
  • 1
  • 23
  • 30
  • As you mentioned the "architecture" here, I think the compiler will use cdecl calling convention by default even I have also specified this by the suffix to each of the functions. So, from the memory layout perspective of view, I think it should be able to retrieve parameters like the way I did, right? – Jason Yu Jun 24 '20 at 21:26
  • See my edited response; your approach only works on some platforms. Can you let us know why you don't want to use the `` mechanism? – Steve Friedl Jun 24 '20 at 21:34
0

This is platform dependant.

But there are some standard library functions for C that will help you.

Have a look in #include <stdarg.h>

There you will find a couple of macros to help in decoding:

  • va_start Create the va_list

  • va_end Tides up the va_list

  • va_arg Moves to the next param in va_list

  • va_list Keeps track of the current spot.

    int __cdecl sum(int num, ...)
    {
        int loop;
        va_list vl;
        va_start(vl, num);    // start at num
    
        va_arg(vl,int);       // Move to the first extra argument
        for (loop=1; loop < num; ++loop)
        {
            int val = va_arg(vl, int); // Get the current argument and move on.
    
        }
        va_end(vl);          // Tidy up
    }
    
Martin York
  • 257,169
  • 86
  • 333
  • 562