I just started learning reverse engineering. The near call instruction (e8) is very confusing to me. It took me a while to figure out how address calculation works for local functions.
Now I'm looking at the output of
// gcc -c test.c
void test() {
puts("Hello from puts");
printf("Hello from printf");
}
And it's e8 00 00 00 00
for both calls. Somehow my disassembler can figure out which call's which.
Here's objdump output:
0000000000000000 <_test>:
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp
4: 48 83 ec 10 sub rsp,0x10
8: 48 8d 3d 1f 00 00 00 lea rdi,[rip+0x1f] # 2e <_test+0x2e>
f: e8 00 00 00 00 call 14 <_test+0x14>
14: 48 8d 3d 23 00 00 00 lea rdi,[rip+0x23] # 3e <_test+0x3e>
1b: 89 45 fc mov DWORD PTR [rbp-0x4],eax
1e: b0 00 mov al,0x0
20: e8 00 00 00 00 call 25 <_test+0x25>
25: 89 45 f8 mov DWORD PTR [rbp-0x8],eax
28: 48 83 c4 10 add rsp,0x10
2c: 5d pop rbp
2d: c3 ret
Hopper's output:
_test:
0000000000000000 push rbp
0000000000000001 mov rbp, rsp
0000000000000004 sub rsp, 0x10
0000000000000008 lea rdi, qword [0x2e] ; argument "s" for _puts
000000000000000f call _puts
0000000000000014 lea rdi, qword [0x3e] ; argument "format" for _printf
000000000000001b mov dword [rbp+var_4], eax
000000000000001e mov al, 0x0
0000000000000020 call _printf
0000000000000025 mov dword [rbp+var_8], eax
0000000000000028 add rsp, 0x10
000000000000002c pop rbp
000000000000002d ret ; endp
Edit
I found the answer to my initial question:
If you're disassembling .o object files that haven't been linked yet, the call address will just be a placeholder to be filled in by the linker.
So how does the linker know which function is getting called?