Understanding C disassembled call

user2290802 picture user2290802 · Apr 18, 2013 · Viewed 7.2k times · Source

I want to learn about C calling convention. To do this I wrote the following code:

#include <stdio.h>
#include <stdlib.h>

struct tstStruct
{
    void *sp;
    int k; 
};

void my_func(struct tstStruct*);

typedef struct tstStruct strc;

int main()
{
    char a;
    a = 'b';
    strc* t1 = (strc*) malloc(sizeof(strc));
    t1 -> sp = &a;
    t1 -> k = 40; 
    my_func(t1);
    return 0;   
}

void my_func(strc* s1)
{
        void* n = s1 -> sp + 121;
        int d = s1 -> k + 323;
}

Then I used GCC with the following command:

gcc -S test3.c

and came up with its assembly. I won't show the whole code I got but rather paste the code for the function my_func. It is this:

my_func:
.LFB1:
.cfi_startproc
pushq   %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq    %rsp, %rbp
.cfi_def_cfa_register 6
movq    %rdi, -24(%rbp)
movq    -24(%rbp), %rax
movq    (%rax), %rax
addq    $121, %rax
movq    %rax, -16(%rbp)
movq    -24(%rbp), %rax
movl    8(%rax), %eax
addl    $323, %eax
movl    %eax, -4(%rbp)
popq    %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc

As far as I understood, this is what happens: First the callers base pointer is pushed into stack and its stack pointer is made the new base pointer to set up the stack for the new function. But then the rest I don't understand. As far as I know, the arguments (or the pointer to the argument) is stored in the stack. If so what is the purpose of the second instruction,

movq        -24(%rbp), %rax

Here, the content of the %rax register is moved to the address 24 bytes away from the address in the register %rbp. But what is in %rax???? Nothing is initially stored there??? I think I'm confused. Please help to understand how this function works. Thanks in advance!

Answer

nrz picture nrz · Apr 18, 2013

You confuse AT&T syntax with Intel syntax.

movq -24(%rbp), %rax

In Intel syntax it would be

mov rax,[rbp-24]

So it moves data addressed by rbp to rax, and not vice versa. The order of operands is src, dest in AT&T syntax, whereas in Intel syntax it is dest, src.

Then, to get rid of GAS directives to make the disassembly easier to read, I assembled the code with gcc simply with gcc test3.c and disassembled it with ndisasm -b 64 a.out. Note the disassembly of my_func function produced by NDISASM below is in Intel syntax:

000005EF  55                push rbp
000005F0  4889E5            mov rbp,rsp        ; create the stack frame.
000005F3  48897DE8          mov [rbp-0x18],rdi ; s1 into a local variable.
000005F7  488B45E8          mov rax,[rbp-0x18] ; rax = s1 (it's a pointer)
000005FB  488B00            mov rax,[rax]      ; dereference rax, store into rax.
000005FE  4883C079          add rax,byte +0x79 ; rax = rax + 121
00000602  488945F8          mov [rbp-0x8],rax  ; void* n = s1 -> sp + 121
00000606  488B45E8          mov rax,[rbp-0x18] ; rax = pointer to s1
0000060A  8B4008            mov eax,[rax+0x8]  ; dereference rax+8, store into eax.
0000060D  0543010000        add eax,0x143      ; eax = eax + 323
00000612  8945F4            mov [rbp-0xc],eax  ; int d = s1 -> k + 323
00000615  5D                pop rbp
00000616  C3                ret

For information on Linux x86-64 calling convention (System V ABI), see answers to What are the calling conventions for UNIX & Linux system calls on x86-64 .