How does argument passing work? - c

I want to know how passing arguments to functions in C works. Where are the values being stored and how and they retrieved? How does variadic argument passing work? Also since it's related: what about return values?
I have a basic understanding of CPU registers and assembler, but not enough that I thoroughly understand the ASM that GCC spits back at me. Some simple annotated examples would be much appreciated.

Considering this code:
int foo (int a, int b) {
return a + b;
}
int main (void) {
foo(3, 5);
return 0;
}
Compiling it with gcc foo.c -S gives the assembly output:
foo:
pushl %ebp
movl %esp, %ebp
movl 12(%ebp), %eax
movl 8(%ebp), %edx
leal (%edx,%eax), %eax
popl %ebp
ret
main:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
movl $5, 4(%esp)
movl $3, (%esp)
call foo
movl $0, %eax
leave
ret
So basically the caller (in this case main) first allocates 8 bytes on the stack to accomodate the two arguments, then puts the two arguments on the stack at the corresponding offsets (4 and 0), and then the call instruction is issued which transfers the control to the foo routine. The foo routine reads its arguments from the corresponding offsets at the stack, restores it, and puts its return value in the eax register so it's available to the caller.

That is platform specific and part of the "ABI". In fact, some compilers even allow you to choose between different conventions.
Microsoft's Visual Studio, for example, offers the __fastcall calling convention, which uses registers. Other platforms or calling conventions use the stack exclusively.
Variadic arguments work in a very similar way - they are passed via registers or stack. In case of registers, they are usually in ascending order, based on type. If you have something like (int a, int b, float c, int d), a PowerPC ABI might put a in r3, b in r4, d in r5, and c in fp1 (I forgot where float registers start, but you get the idea).
Return values, again, work the same way.
Unfortunately, I don't have many examples, most of my assembly is in PowerPC, and all you see in the assembly is the code going straight for r3, r4, r5, and placing the return value in r3 as well.

Your questions are more than anybody could reasonably try to answer in a SO post, not to mention that it's implementation defined as well.
However, if you're interested in the x86 answer might I suggest you watch this Stanford CS107 Lecture titled Programming Paradigms where all the answers to the questions you posed will be explained in great detail (and quite eloquently) in the first 6-8 lectures.

It depends on your compiler, the target architecture and OS you’re compiling for, and whether your compiler supports non-standard extensions that change the calling convention. But there are some commonalities.
The C calling convention is usually established by the vendor of the operating system, because they need to decide what convention the system libraries use.
More recent CPUs (such as ARM or PowerPC) tend to have their calling conventions defined by the CPU vendor and compatible across different operating systems. x86 is an exception to this: different systems use different calling conventions. There used to be a lot more calling conventions for the 16-bit 8086 and 32-bit 80386 than there are for x86_64 (although even that is not down to one). 32-bit x86 Windows programs sometimes use multiple calling conventions within the same program.
Some observations:
An example of an operating system that supports several different ABIs with different calling conventions simultaneously, some of which follow the same conventions as other OSes for the same architecture, is Linux for x86_64. This can host three different major ABIs (i386, x32 and x86_64), two of which are the same as other operating systems for the same CPU, and several variants.
An exception to the rule that there's one system calling convention used for everything is 16- and 32-bit versions of MS Windows, which inherited some of the proliferation of calling conventions from MS-DOS. The Windows C API uses a different calling convention (STDCALL, originally FAR PASCAL) than the “C” calling convention for the same platform, and also supports FORTRAN and FASTCALL conventions. All four come in NEAR and FAR variants on 16-bit OSes. Nearly all Windows programs therefore use at least two different conventions in the same program.
Architectures with a lot of registers, including classic RISC and nearly all modern ISAs, use several of those registers to pass and return function arguments.
Architectures with few or no general-purpose registers often pass arguments on the stack, pointed to by a stack pointer. CISC architectures often have instructions to call and return which store the return address on the stack. (RISC architectures typically store the return address in a "link register", which the callee can save/restore manually if it's not a leaf function.)
A common variant is for tail calls, functions whose return value is also the return value of the caller, to jump to the next function (so it returns to our parent function) instead of calling it and then returning after it returns. Placing args in the right places has to account for the return address already being on the stack, where a call instruction would place it.
This is especially true of tail-recursive calls, which have exactly the same stack frame on each invocation. A tail-recursive call is typically equivalent to a loop: update a few registers that changed, then jump back to the entry point. They do not need to create a new stack frame, or have their own return address: you can simply update the caller’s stack frame and use its return address as the tail call’s. i.e. tail-recursion easily optimizes into a loop.
Some architectures with only a few registers nevertheless defined an alternative calling convention that could pass one or two arguments in registers. This was FASTCALL on MS-DOS and Windows.
A few older ISAs, such as SPARC, had a special bank of “windowed” registers, so that every function has its own bank of input and output registers, and when it made a function call, the caller’s outputs became the callee’s inputs, and the reverse when it came time to return a value. Modern superscalar designs consider this more trouble than it’s worth.
A few very old architectures used self-modifying code in their calling convention, and the first edition of The Art of Computer Programming followed this model for its abstract language. It no longer works on most modern CPUs, which have instruction caches.
A few other very old architectures had no stack and generally could not call the same function again, re-entering it, until it returned.
A function with a lot of arguments almost always puts most of them onto the stack.
C functions that put arguments on the stack almost have to push them in reverse order and have the caller clean up the stack. The called function might not even know exactly how many arguments are on the stack! That is, if you call printf("%d\n", x); the compiler will push x, then the format string, then the return address, onto the stack. This guarantees that the first argument is at a known offset from the stack pointer and <varargs.h> has the information it needs to work.
Most other languages, and therefore some operating systems that C compilers support, do it the other way around: arguments are pushed from left to right. The function being called usually cleans up its own stack frame. This used to be called the PASCAL convention on MS-DOS, and survives as the STDCALL convention on Windows. It cannot support variadic functions. (https://en.wikibooks.org/wiki/X86_Disassembly/Calling_Conventions)
Fortran and a few other language historically passed all arguments by reference, which translates to C as pointer arguments. Compilers that might need to interface with these other languages often support these foreign calling conventions.
Because a major source of bugs was “smashing the stack,” many compilers now have a way to add canary values (which, like a canary in a coal mine, warn you that something dangerous is going on if anything happens to them) and other means of detecting when code tampers with the stack frame.
Another form of variation across different platforms is whether the stack frame will contain all the information it needs for a debugger or exception-handler to backtrace, or whether that info will be in separate metadata (or not present at all) allowing simplification of function prologue/epilogue (-fomit-frame-pointer).
You can get cross-compilers to emit code using different calling conventions, and compare them, with switches such as -S -target (on clang).

Basically, C passes arguments by pushing them on the stack. For pointer types, the pointer is pushed on the stack.
One things about C is that the caller restores the stack rather the function being called. This way, the number of arguments can vary and the called function doesn't need to know ahead of time how many arguments will be passed.
Return values are returned in the AX register, or variations thereof.

Related

GCC C and ARM Assembly Stack Cleanup

If I call an ARM assembly function from C, sometimes I need to pass in many arguments. If they do not fit in registers r0, r1, r2, r3 it is generally expected that 5-th, 6-th ... x-th arguments are pushed onto stack so that ARM assembly can read them from it.
So in the ARM function I receive some arguments that are on the stack. After finishing the assembly function I can either remove these arguments from stack or leave them there and expect that the C program will deal with them later.
If we are talking about GCC C and ARM assembly who is usually responsible for cleaning up the stack?
The function that made the call (A)
Or the function that was called (B)
I understand that when developing we could agree on either convention. But what is generally used as the default in this particular case (ARM assembly and GCC C)?
And how would generally a low level piece of code describe which behavior it implements? It seems that there should be some kind of standard description for this. If there isn't one it seems that you pretty much just have to try them both and look at which one does not crash.
If someone is interested in how the code could look like:
arm_function:
stmfd sp, {r4-r12, lr} # Save registers that are not the first three registers, SP->PASSED ARGUMENTS
ldmfd sp, {r4-r6} # Load 3 arguments that were passed through the stack, SP->PASSED ARGUMENTS
sub sp, sp, #40 # Adjust the stack pointer so it points to saved registers, STACK POINTER->SAVED REGISTERS->PASSED ARGUMENTS
#The main function body.
ldmfd sp!, {r4-r12, lr}, # Load saved registers STACK POINTER->PASSED ARGUMENTS
add sp, sp, #12 # Increment stack pointer to remove passed arguments, SP->NOTHING
# If the last code line would not be there, the caller would need to remove the arguments from stack.
UPDATE:
It seems that for C/C++ choice A. is pretty standard. Compilers usually use calling conventions like cdecl that work pretty similar to code in the answers below. More information can be found in this link about calling conventions. Changing C/C++ calling convention for a function does not seem to be so common/easy. With older C standard I could not manage to change it, so it looks like using A should be a decent default choice.
The current ARM procedure call standard is AAPCS.
The language-specific ABI can be found here. Relevant will be the document about C, but others should be similar (why reinvent the wheel?).
A good start for reading might be page 14 in the AAPCS.
It basically requires the caller to clean up the stack, as this is the most simple way: push additional arguments onto the stack, call the function and after return simply adjust the stack pointer by adding an offset (the number of bytes pushed on the stack; this is always a multiple of 4 (the "natural 32bit ARM word size).
But if you use gcc, you can just avoid handling the stack yourself by using inline assembler. This provides features to pass C variables (etc.) to the assembler code. This will also automatically load a parameter into a register if required. Just have a look at the gcc documentation. It is a bit hard to figure out in detail, but I prefer this to having raw assember stubs somewhere.
Ok, i added this as there might be problems understanding the principle:
caller:
...
push r5 // argument which does not fit into r0..r3 anymore
bl callee
add sp,4 // adjust SP
callee:
push r5-r7,lr // temp, variables, return address
sub sp,8 // local variables
// processing
add sp, 8 // restore previous stack frame
pop r5-r7,pc // restore temp. variables and return (replaces bx)
You can verify this by just disassmbling some sample C functions. Note that the pre- and postamble may vary if no temp registers are used or the function does not call another function (no need to stack lr for this).
Also, the caller might have to stack r0..r3 before the call. But that is a matter of compiler optimizations.
Disassembly can be done with gdb and objdump for example.
I use -mabi=aapcs for gcc invocation; not sure if gcc would otherwise use a different standard. Note that all object files have to use the same standard.
Edit:
Just had a peek in the AAPCS and that states that the SP need only 4 byte alignment. I might have confused this with the Cortex-M interrupt handling system which (for whatever reason, possibly for M7 which has 64 bit busses) aligns the SP to 8 bytes by default (software-config option).
However, SP must be 8 byte aligned at a public interface. Ok, the standard actually is more complicated than I remembered. That's why I prefer gcc caring about this stuff.
If some spaces allocated on the stack by caller function (argument passing), stack clearance done within the caller function. And how it happens you may ask. In ARM #Olaf has completely cleared, and in x86 it is usually like this:
sub esp, 8 ; make some room
... ; move arguments on stack
call func
add esp, 8 ; clean the stack
or
push eax ; push the arguments
push ebx ; or pusha, then after call, popa
call func
add esp, 8 ; assuming registers are 4 bytes each
Also how the interaction between caller and callee in a system takes places is explained in ABI (Application Binary Interface) You may find it useful.

assembly stack management through %esp

I don't really understand why gcc has subtract 12 to esp before calling the function.
pushl %ebp
movl %esp,%ebp
sub $12,%esp
socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
movl $AF_INET,(%esp)
The current* x86 ABI requires the stack pointer to be aligned mod 16 at the time of function call. This is the typical reason for otherwise-unexplained adjustments of the stack pointer.
* I say current because GCC actually unilaterally changed the ABI and introduced this requirement somewhere back in the 3.x series. I don't have the references handy but maybe someone else can provide them. The change was intended to optimize for use of SIMD instructions, but isn't actually needed for that purpose, and ended up breaking ABI compatibility with old code when the old code calls back to new code that assumes alignment. The whole story is a big mess.
Firstly you are pushing values of base pointer which decrements the values of stack pointer. Since push operation virtually take sp upwars essentially decrenting the address. Then the stack frame of c program consists of code seg above which there are arguments to function above which sits sp. Now when you want to access the 1st arg passed to function you need to add 12bytes since 3 words eventually 12 bytes needs to be popped to get that argument.
http://www.cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/stack.html
I found this resource very helpful

How to know the count of arguments when implementing backtrace in C

I am implementing a backtrace function in C, which can output caller's info. like this
ebp:0x00007b28 eip:0x00100869 args:0x00000000 0x00640000 0x00007b58 0x00100082
But how can I know the count of arguments of the caller?
Thank you very much
You can deduce the numbers of arguments a function uses in 32bit x86 code under some circumstances.
If the code has been compiled to use framepointers, then a given function's stackframe extends between (highest address) EBP and (lowest address / stack top) ESP. Immediately above the stack end at EBP you find the return address, and again above that you'll have, if your code is using the C calling convention (cdecl), consecutively, arg[0...].
That means: arg[0] at [EBP + 4], arg[1] at [EBP + 8 ], and so on.
When you disassemble function, look for instructions referencing [EBP + ...] and you know they access function arguments. The highest offset value used tells you how many there are.
This is of course somewhat simplified; arguments with sizes different from 32bits, code that doesn't use cdecl but e.g. fastcall, code where the framepointer has been optimized makes the method trip, at least partially.
Another option, again for cdecl functions, is to look at the return address (location of the call into the func you're interested in), and disassemble around there; you will, in many cases, find a sequence push argN; push ...; push arg0; call yourFunc and you can deduce how many arguments were passed in this instance. That's in fact the only way (from the code alone) to test how many arguments were passed to functions like printf() in a particular instance.
Again, not perfect - these days, compilers often preallocate stackspace and then use mov to write arguments instead of pushing them (on some CPUs, this is better since sequences of push instructions have dependencies on each other due to each modifying the stackpointers).
Since all these methods are heuristic this requires quite a bit of coding to automate. If compiler-generated debugging information is available, use that - it's faster.
Edit: There's another useful heuristic that can be done; Compiler-generated code for function calling often looks like this:
...
[ code that either does "push arg" or "mov [ESP ...], arg" ]
...
call function
add ESP, ...
The add instruction is there to clean up stackspace used for arguments. From the size of the immediate operand, you know how much space the args this code gave to function has used, and by implication (assuming they're all 32bit, for example), you know how many there were.
This is particularly simple given you already have the address of said add instruction if you have working backtrace code - the instruction at the return address is this add. So you can often get away with simply trying to disassemble the (single) instruction at the return address, and see if it's an add ESP, ... (sometimes it's a sub ESP, -...) and if so, calculate the number of arguments passed from the immediate operand. The code for that is much simpler than having to pull in a full disassembly library.
You can't. The number of arguments isn't saved anywhere, as you can see in this simple disassembly:
f(5);
002B144E push 5
002B1450 call f (2B11CCh)
002B1455 add esp,4
g(1, "foo");
002B1458 push offset string "foo" (2B5740h)
002B145D push 1
002B145F call g (2B11C7h)
002B1464 add esp,8
h("bar", 'd', 8);
002B1467 push 8
002B1469 push 64h
002B146B push offset string "bar" (2B573Ch)
002B1470 call h (2B11D1h)
002B1475 add esp,0Ch
Basically, only the called function knows how many arguments it has.
As Yahia commented, there's no general way.
You'll probably need to parse the debug information placed by the debugger (assuming you have compiled with gcc -g).
Glibc implements a backtrace function. It unwind backtrace, arg by arg.
You can see how they've done it in sysdeps/$ARCH/backtrace.c. Beware that it's quite hard to read.

Recognizing stack frames in a stack using saved EBP values

I would like to divide a stack to stack-frames by looking on the raw data on the stack. I thought to do so by finding a "linked list" of saved EBP pointers.
Can I assume that a (standard and commonly used) C compiler (e.g. gcc) will always update and save EBP on a function call in the function prologue?
pushl %ebp
movl %esp, %ebp
Or are there cases where some compilers might skip that part for functions that don't get any parameters and don't have local variables?
The x86 calling conventions and the Wiki article on function prologue don't help much with that.
Is there any better method to divide a stack to stack frames just by looking on its raw data?
Thanks!
Some versions of gcc have a -fomit-frame-pointer optimization option. If memory serves, it can be used even with parameters/local variables (they index directly off of ESP instead of using EBP). Unless I'm badly mistaken, MS VC++ can do roughly the same.
Offhand, I'm not sure of a way that's anywhere close to universally applicable. If you have code with debug info, it's usually pretty easy -- otherwise though...
Even with the framepointer optimized out, stackframes are often distinguishable by looking through stack memory for saved return addresses instead. Remember that a function call sequence in x86 always consists of:
call someFunc ; pushes return address (instr. following `call`)
...
someFunc:
push EBP ; if framepointer is used
mov EBP, ESP ; if framepointer is used
push <nonvolatile regs>
...
so your stack will always - even if the framepointers are missing - have return addresses in there.
How do you recognize a return address ?
to start with, on x86, instruction have different lengths. That means return addresses - unlike other pointers (!) - tend to be misaligned values. Statistically 3/4 of them end not at a multiple of four.
Any misaligned pointer is a good candidate for a return address.
then, remember that call instructions on x86 have specific opcode formats; read a few bytes before the return address and check if you find a call opcode there (99% most of the time, it's five bytes back for a direct call, and three bytes back for a call through a register). If so, you've found a return address.
This is also a way to distinguish C++ vtables from return addresses by the way - vtable entrypoints you'll find on the stack, but looking "back" from those addresses you don't find call instructions.
With that method, you can get candidates for the call sequence out of the stack even without having symbols, framesize debugging information or anything.
The details of how to piece the actual call sequence together from those candidates are less straightforward though, you need a disassembler and some heuristics to trace potential call flows from the lowest-found return address all the way up to the last known program location. Maybe one day I'll blog about it ;-) though at this point I'd rather say that the margin of a stackoverflow posting is too small to contain this ...

Why does the C standard leave use of indeterminate variables undefined?

Where are the garbage value stored, and for what purpose?
C chooses to not initialize variables to some automatic value for efficiency reasons. In order to initialize this data, instructions must be added. Here's an example:
int main(int argc, const char *argv[])
{
int x;
return x;
}
generates:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
movl -4(%ebp), %eax
leave
ret
While this code:
int main(int argc, const char *argv[])
{
int x=1;
return x;
}
generates:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
movl $1, -4(%ebp)
movl -4(%ebp), %eax
leave
ret
As you can see, a full extra instruction is used to move 1 into x. This used to matter, and still does on embedded systems.
Garbage values are not really stored anywhere. In fact, garbage values do not really exist, as far as the abstract language is concerned.
You see, in order to generate the most efficient code it is not sufficient for the compiler to operate in terms of lifetimes of objects (variables). In order to generate the most efficient code, the compiler must operate at much finer level: it must "think" in terms of lifetimes of values. This is absolutely necessary in order to perform efficient scheduling of the CPU registers, for one example.
The abstract language has no such concept as "lifetime of value". However, the language authors recognize the importance of that concept to the optimizing compilers. In order to give the compilers enough freedom to perform efficient optimizations, the language is intentionally specified so that it doesn't interfere with important optimizations. This is where the "garbage values" come into picture. The language does not state that garbage values are stored anywhere, the language does not guarantee that the garbage values are stable (i.e. repeated attempts to read the same uninitialized variable might easily result in different "garbage values"). This is done specifically to allow optimizing compilers to implement the vital concept of "lifetime of value" and thus perform more efficient variable manipulation than would be dictated by the language concept of "object lifetime".
IIRC, Thompson or Richie did an interview some years ago where they said the language definition purposely left things vague in some places so the implementers on specific platforms had leeway to do things that made sense (cycles, memory, etc) on that platform. Sorry I don't have a reference to link to.
Why does the C standard leave use of indeterminate variables undefined?
It does not :-) :
for local variables, it says undefined behavior, which means that anything (e.g. segfault, erasing your hard disk) is legal: (Why) is using an uninitialized variable undefined behavior?
for global variables, it zeros them: What happens to a declared, uninitialized variable in C? Does it have a value?
C was designed to be a relatively low-level language so that it could be used for writing, well, low-level stuff like operating systems. (in fact, it was designed so that UNIX could be written in C) You can simply think of it as assembly code with readable syntax and higher-level constructs. For this reason, C (minus optimizations) does exactly what you ask it to do, nothing more, nothing less.
When you write int x;, the compiler simply allocates memory for the integer. You never asked it to store anything there, so whatever was in that location when your program started stays as such. Most often, it turns out that the pre-existing value is "garbage".
Sometimes, an external program (for eg. a device driver) may write into some of your variables, so it is unnecessary to add another instruction to initialize such variables.

Resources