2

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?

  • @Alex : As a new contributor do not forget to mark the best answer as the right answer. It helps a lot the website statistics among the Stack-Exchange websites. – perror Nov 29 '18 at 8:50
  • @perror I haven't got an answer yet. – Alex Nov 30 '18 at 0:30
3

If you use -dr instead of plain -d, objdump will list relocation info next to the instructions. E.g. here's a random sample I just compiled

00000000 <main>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   83 e4 f0                and    $0xfffffff0,%esp
   6:   83 ec 20                sub    $0x20,%esp
   9:   c7 44 24 18 a9 03 00    movl   $0x3a9,0x18(%esp)
  10:   00
  11:   c7 44 24 04 00 00 00    movl   $0x0,0x4(%esp)
  18:   00
                        15: R_386_32    m1
  19:   c7 04 24 30 00 00 00    movl   $0x30,(%esp)
                        1c: R_386_32    .rodata
  20:   e8 fc ff ff ff          call   21 <main+0x21>
                        21: R_386_PC32  wprintf
  25:   89 44 24 1c             mov    %eax,0x1c(%esp)
  29:   b8 00 00 00 00          mov    $0x0,%eax
  2e:   c9                      leave
  2f:   c3                      ret

You can see that call at 20 has a relocation info to wprintf (at 21, right at the bytes that correspond to the offset to the destination of the call). linker (ld) uses this information at link time to patch the instructions with the final resolved address of the function.

It seems Hopper used relocation info symbol's name for more user-friendly disassembly.

  • Thanks. You don't have e8 00 00 00 00. Did you build without gcc -c ? I want to understand how calls work without relocation info. – Alex Nov 29 '18 at 0:39

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.