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.
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
.