Search code examples
cassemblycalling-convention

x86 vs x86_64 calling convention


We've got two architectures and two different years: x86 in 2007 and x86_64 in 2017. In a book published in 2007 basing on x86, there's an example listed for a compiled file:

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

void usage(char *program_name) {

    printf("Usage: %s <message> <# of times to repeat>\n", program_name);
    exit(1);
}

int main(int argc, char *argv[]) {

    int i, count;

    if(argc < 3)            // If fewer than 3 arguments are used, 
        usage(argv[0]);     // display usage message and exit.

    count = atoi(argv[2]);  // Convert the 2nd arg into an integer.
    printf("Repeating %d times..\n", count);

    for(i = 0; i < count; i++)
        printf("%3d - %s\n", i, argv[1]);   // Printf the 1st arg.

    return 0;
}

When compiling via gcc -g -o file.o file.c and disassembling the file in gdb or simply objdumping the file on x86_64: it looks like this:

0000000000000000 <usage>:
 0: 55                      push   rbp
 1: 48 89 e5                mov    rbp,rsp
 4: 48 83 ec 10             sub    rsp,0x10
 8: 48 89 7d f8             mov    QWORD PTR [rbp-0x8],rdi
 c: 48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
10: 48 89 c6                mov    rsi,rax
13: 48 8d 3d 00 00 00 00    lea    rdi,[rip+0x0]  # 1a <usage+0x1a>
1a: b8 00 00 00 00          mov    eax,0x0
1f: e8 00 00 00 00          call   24 <usage+0x24>
24: bf 01 00 00 00          mov    edi,0x1
29: e8 00 00 00 00          call   2e <main>

000000000000002e <main>:
2e: 55                      push   rbp
2f: 48 89 e5                mov    rbp,rsp
32: 48 83 ec 20             sub    rsp,0x20
36: 89 7d ec                mov    DWORD PTR [rbp-0x14],edi
39: 48 89 75 e0             mov    QWORD PTR [rbp-0x20],rsi
3d: 83 7d ec 02             cmp    DWORD PTR [rbp-0x14],0x2
41: 7f 0f                   jg     52 <main+0x24>
43: 48 8b 45 e0             mov    rax,QWORD PTR [rbp-0x20]
47: 48 8b 00                mov    rax,QWORD PTR [rax]
4a: 48 89 c7                mov    rdi,rax
4d: e8 00 00 00 00          call   52 <main+0x24>
52: 48 8b 45 e0             mov    rax,QWORD PTR [rbp-0x20]
56: 48 83 c0 10             add    rax,0x10
5a: 48 8b 00                mov    rax,QWORD PTR [rax]
5d: 48 89 c7                mov    rdi,rax
60: e8 00 00 00 00          call   65 <main+0x37>
65: 89 45 f8                mov    DWORD PTR [rbp-0x8],eax
68: 8b 45 f8                mov    eax,DWORD PTR [rbp-0x8]
6b: 89 c6                   mov    esi,eax
6d: 48 8d 3d 00 00 00 00    lea    rdi,[rip+0x0]  # 74 <main+0x46>
74: b8 00 00 00 00          mov    eax,0x0
79: e8 00 00 00 00          call   7e <main+0x50>
7e: c7 45 fc 00 00 00 00    mov    DWORD PTR [rbp-0x4],0x0
85: eb 25                   jmp    ac <main+0x7e>
87: 48 8b 45 e0             mov    rax,QWORD PTR [rbp-0x20]
8b: 48 83 c0 08             add    rax,0x8
8f: 48 8b 10                mov    rdx,QWORD PTR [rax]
92: 8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
95: 89 c6                   mov    esi,eax
97: 48 8d 3d 00 00 00 00    lea    rdi,[rip+0x0]  # 9e <main+0x70>
9e: b8 00 00 00 00          mov    eax,0x0
a3: e8 00 00 00 00          call   a8 <main+0x7a>
a8: 83 45 fc 01             add    DWORD PTR [rbp-0x4],0x1
ac: 8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
af: 3b 45 f8                cmp    eax,DWORD PTR [rbp-0x8]
b2: 7c d3                   jl     87 <main+0x59>
b4: b8 00 00 00 00          mov    eax,0x0
b9: c9                      leave  
ba: c3                      ret    

However, from what I've learned in assembler on x86 is that before calling a function, the usual calling convention (pushing rbp, moving rsp to rbp, basically building a new stack frame plus all arguments) sets in. But in the above objdump output of main, there is no line like

call    <addr_of_function_usage>    # <usage+0x0>

like in x86 generally or in the book. So my question is, where in this objdump does the call to the usage function take place?

Thanks in advance.


Solution

  • In an object file, the addresses of functions aren't filled in yet. For this reason, the disassembler has trouble seeing what functions you try to call. If you look at the disassembly, you see a bunch of call instructions, one of these probably calls usage. It seems to be the call at address 0x42.