1

I'm learning (and re-learning) C and assembly, and I came across a difference between what I've been taught and the actual result I have.

Some code:

int test(int a, int b){
  return a + b;
}

int main(){
  test(1,2);
}

As you can see, it's a really simple example in which I try to understand how values are passed around function.

I've been taught that to pass variable to functions, the values must be pushed to the stack, in reverse order, to be accessible then with the base pointer, something like (simplified writing):

-- main
...
mov   [esp+8] 0x02
mov   [esp] 0x01
call  0x...  <test>
...

And then you can get back those values directly from the stack using:

-- test
...
mov   esi [esp+8]
add   esi [esp]
mov   eax esi
...

I'm perfectly OK with this (although I may not have understood everything), but what is strange to me is the practical result I get when playing with it in gdb:

$ gcc -g stack.c
$ gdb a.out

(gdb) disass main
Dump of assembler code for function main:
   0x0000000100000f70 <+0>:     push   rbp
   0x0000000100000f71 <+1>:     mov    rbp,rsp
   0x0000000100000f74 <+4>:     sub    rsp,0x10
-> 0x0000000100000f78 <+8>:     mov    edi,0x1
-> 0x0000000100000f7d <+13>:    mov    esi,0x2
   0x0000000100000f82 <+18>:    call   0x100000f50 <test>
   0x0000000100000f87 <+23>:    xor    esi,esi
   0x0000000100000f89 <+25>:    mov    DWORD PTR [rbp-0x4],eax
   0x0000000100000f8c <+28>:    mov    eax,esi
   0x0000000100000f8e <+30>:    add    rsp,0x10
   0x0000000100000f92 <+34>:    pop    rbp
   0x0000000100000f93 <+35>:    ret
End of assembler dump.
(gdb) disass test
Dump of assembler code for function test:
   0x0000000100000f50 <+0>:     push   rbp
   0x0000000100000f51 <+1>:     mov    rbp,rsp
-> 0x0000000100000f54 <+4>:     mov    DWORD PTR [rbp-0x4],edi
-> 0x0000000100000f57 <+7>:     mov    DWORD PTR [rbp-0x8],esi
   0x0000000100000f5a <+10>:    mov    esi,DWORD PTR [rbp-0x4]
   0x0000000100000f5d <+13>:    add    esi,DWORD PTR [rbp-0x8]
   0x0000000100000f60 <+16>:    mov    eax,esi
   0x0000000100000f62 <+18>:    pop    rbp
   0x0000000100000f63 <+19>:    ret
End of assembler dump.

Here, instead of using the stack directly, the two values I'm passing to the function test are stored in the registers esi and edi (corresponding lines marked with ->).

Here's my setup:

$ gcc -v
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 7.0.2 (clang-700.1.81)
Target: x86_64-apple-darwin14.5.0
Thread model: posix


$ gdb --version
GNU gdb (GDB) 7.11.1
...
This GDB was configured as "x86_64-apple-darwin14.5.0".
...

My two questions are:

  • Is this behavior related to my setup?
  • Is it documented anywhere?
2

This behavior is totally normal. The way functions are handled is usually described in what's called an ABI (Application Binary Interface). It defines calling conventions which detail how a call is made in assembly code and how parameters are passed to a function using specific registers. I would recommend Agner Fog's C++ Optimization Manual. It contains extremely helpful information about Linux, Windows, and MacOS ABIs.

| improve this answer | |
  • Great, thanks for the answer and the links. Are you aware of a way (if possible) to force the calling convention? Or is it fixed for an OS? – nobe4 Dec 14 '16 at 9:22
  • 2
    64 bit uses fastcall calling covention where first 4 arguments are passed via registers and the rest are passed via stack so yes it is fixed for 64 bit architecture – blabb Dec 14 '16 at 9:36
  • 1
    There's no way you can change the calling convention for a specific function unless you write your code in assembly or modify the compiler. The latter will impact all functions processed by the compiler. – yaspr Dec 14 '16 at 9:57
  • 1
    Older 32 bit compilers used to have different calling conventions, and several C compilers had additional keywords to force a specific one. For example, the original Windows API used the Pascal calling convention, and the Microsoft compiler had a pascal keyword to force this (which was #defined to WINAPI somewhere in windows.h). Luckily, 64 bit compilers did away with this old stuff. – Guntram Blohm supports Monica Dec 14 '16 at 17:05
  • @GuntramBlohm I remember this oddity, it was one of those dirty tricks that made me hate Windows. – yaspr Dec 14 '16 at 19:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.