External function calls are achieved using procedure linkage table (PLT), which holds a stub for each external ref. The stub points to a location in
global offset table (GOT), which holds absolute address for external refs, both variables or functions (lazily). In the following example, we can see
that the function ref is resolved on first usage.
clang » gdb a.out ... (gdb) set environment LD_LIBRARY_PATH=. (gdb) break main Breakpoint 1 at 0x4006b6: file main.c, line 5. (gdb) r Breakpoint 1, main (argc=1, argv=0x7fffffffce98) at main.c:5 5 func(); (gdb) disassemble Dump of assembler code for function main: 0x00000000004006a0 <+0>: push %rbp 0x00000000004006a1 <+1>: mov %rsp,%rbp 0x00000000004006a4 <+4>: sub $0x10,%rsp 0x00000000004006a8 <+8>: movl $0x0,-0x4(%rbp) 0x00000000004006af <+15>: mov %edi,-0x8(%rbp) 0x00000000004006b2 <+18>: mov %rsi,-0x10(%rbp) => 0x00000000004006b6 <+22>: callq 0x400590 <func@plt> 0x00000000004006bb <+27>: callq 0x400590 <func@plt> 0x00000000004006c0 <+32>: xor %eax,%eax 0x00000000004006c2 <+34>: add $0x10,%rsp 0x00000000004006c6 <+38>: pop %rbp 0x00000000004006c7 <+39>: retq End of assembler dump. (gdb) si 0x0000000000400590 in func@plt () (gdb) disassemble Dump of assembler code for function func@plt: => 0x0000000000400590 <+0>: jmpq *0x200a92(%rip) # 0x601028 <func@got.plt> 0x0000000000400596 <+6>: pushq $0x2 0x000000000040059b <+11>: jmpq 0x400560 End of assembler dump. (gdb) p/x *0x601028 $1 = 0x400596 (gdb) s Single stepping until exit from function func@plt, which has no line number information. 0x00007ffff7bd8690 in func () from ./libfunc.so (gdb) s Single stepping until exit from function func, which has no line number information. main (argc=1, argv=0x7fffffffce98) at main.c:6 6 func(); (gdb) p/x *0x601028 $2 = 0xf7bd8690 (gdb) p/x &func $3 = 0x7ffff7bd8690 (gdb) q