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.

1
2
3
4
5
6
7
8
9
// main.c
void func(void);

int main(int argc, char *argv[])
{
func();
func();
return 0;
}
1
2
// func.c
void func(void) { }
1
2
$ clang -shared -fpic func.c -o libfunc.so && clang -g main.c -L . -lfunc
$ objdump -D a.out > main.s
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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

References