Static Link && Dynamic Link
Outline:
- static link
- static load
- dynamic link && load
Link & Load
静态库: .a
动态库( aka 共享对象 ): .so
static link
- 需求: 允许引用其他文件(C标准称为编译单元
compilation unit)里定义的符号- C不阻止你随便声明符号的类型
- 但类型不匹配是
undefined behavior
- 使用
-Wl,--verbose可以将--verbose传递给ld- 可以看到
ld script
- 可以看到
1 | //a.c |
1 | //b.c |
1 |
|
1 | //Makefile |
- 刚编译完程序的时候,
main.o不能运行,因为其外部符号都“留空”objdump -d a.o- (符号地址还没有解析,暂时是全零)
- 链接后
objdump -d a.out | grep main可以看到其外部符号已经被正确赋值
static load
静态ELF加载器:加载a.out时执行
ELF文件中有若干个ELF program header, 描述了文件到内存的映射。
静态ELF加载器根据ELF program header,将文件中指定部分移动到内存
- 遍历ELF中的各个
program header,然后read/mmap
- 遍历ELF中的各个
OS在
execve时执行:OS在kernel mode调用
mmap(进程还未准备好时,由内核直接执行系统调用)
映射好
a.out代码、数据、堆、栈、vvar、vdso、vsyscall
加载完成后,静态链接的程序就从ELF entry开始执行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22readelf -h a.out
ELF Header:
Magic: 7f 45 4c 46 02 01 01 03 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - GNU
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x401670 //ELF entry 0x401670
Start of program headers: 64 (bytes into file)
Start of section headers: 795088 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 10
Size of section headers: 64 (bytes)
Number of section headers: 32
Section header string table index: 311
2
3
4
5
6
7
8gdb a.out
...
(gdb) starti
Starting program: /home/lyk/Documents/Hexo/LYK-love.github.io/source/_drafts/Test/a.out
Program stopped.
0x0000000000401670 in _start () //可以看到,确实是0x401670查看该进程的地址空间:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15(gdb) info inferiors
Num Description Connection Executable
* 1 process 15776 1 (native) /home/lyk/Documents/Hexo/LYK-love.github.io/source/_drafts/Test/a.out
(gdb) !cat /proc/15776/maps
00400000-00401000 r--p 00000000 103:08 3802167 .../LYK-love.github.io/source/_drafts/Test/a.out
00401000-00482000 r-xp 00001000 103:08 3802167 .../LYK-love.github.io/source/_drafts/Test/a.out
00482000-004a9000 r--p 00082000 103:08 3802167 .../LYK-love.github.io/source/_drafts/Test/a.out
004aa000-004b1000 rw-p 000a9000 103:08 3802167 .../LYK-love.github.io/source/_drafts/Test/a.out
004b1000-004b2000 rw-p 00000000 00:00 0 [heap]
7ffff7ff9000-7ffff7ffd000 r--p 00000000 00:00 0 [vvar]
7ffff7ffd000-7ffff7fff000 r-xp 00000000 00:00 0 [vdso]
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
(gdb)可看到的确有上述内容
静态链接缺点:导致复用性降低,磁盘过多被占用
dynamic link & load
需求: 节约内存
Linux系统会共享动态链接库的一个副本(可以用
size查看内存占用)实现动态加载(代码):
- 编译成
Position Independent Code( PIC )- 引用代码全部使用PC相对寻址
- x86已经是这样了
- 把代码
mmap进进程的地址空间
- 编译成
实现动态加载(代码+数据+允许访问其他动态链接库导出的符号):
- 编译成
Position Independent Code( PIC )- 引用代码全部使用PC相对寻址
- x86已经是这样了
- 对于其他动态链接库导出的符号,可以在数据区维护一张表,每次引用该符号时就查表。在运行时给相应的表项赋值
- 把代码
mmap到进程的地址空间
- 编译成
ELF文件都有
Global Offset TableGOT, 即上述的“表”- Lazy Symbol Resolution: 不一次性加载GOT的所有符号
Example
编写两个简单的程序(fred.c, bill.c),将其编译为目标文件,并分别生成静态库和动态库。再编写程序调用之,说明库的使用。
- 生成静态链接库
gcc -c h.c -o h.oar cqs libh.a h.o:ar是生成库的命令,cqs是参数,libh.a是生成的静态链接库须以lib开头,h是库名,a表示是静态链接库,h.o是刚生成的目标文件
- 生成动态链接库
gcc -c h.c -o h.ogcc -shared -WI -o libh.so h.o:生成动态链接库使用gcc来完成,-shared -WI是参数,libh.so是刚生成的静态链接库,必须以lib开头,h是库名,so表示动态链接库,h.o是刚生成目标文件。
- 将生成的libh.a,libh.so拷贝到/usr/lib或/lib下
- 编译带静态链接库的程序
gcc -c test.c -o test.ogcc test.o -o test -WI -Bstatic -lh:-WI -Bstatic表示链接静态库,-lh中-l表示链接,h是库名即/usr/lib下的libh.a
- 编译带动态链接库的程序
gcc -c test.c -o test.ogcc test.o -o test -WI -Bdynamic -lh:-WI -Bdynamic表示链接动态库,-lh中-l表示链接,h是库名即/usr/lib下的libh.so
- 运行./test得到结果