一个进程生成的过程如下:
位置无关代码
在编译阶段,默认情况下函数及变量地址使用绝对地址表示。当目标文件中的函数需要被动态加载时,就需要添加 -fPIC
选项生成位置无关代码。所谓位置无关,就是函数和变量的调用使用相对地址。
两种情况下需要打开 -fPIC 编译选项:
|
静态链接
通过将编译后的目标文件进行打包,就形成了静态库文件。文件的格式为 ar 存档文件,可以使用 ar -t xxx.a
查看存档中的内容。
在链接过程中,如果连接器同时发现了静态库和动态库,则优先链接静态库,解决方法为:
指定库文件的全名或者绝对路径
在
-Wl,-Bstatic
链接选项后声明的库都会被视为静态库。例如:g++ -L. -o main main.cc -Wl,-Bstatic -ltest -Wl,-Bdynamic
使用
-static
链接选项要求使用全静态链接。
此外,对于一些系统常用库,gcc 提供了额外选项来将其静态链接:
链接选项 | 作用 |
---|---|
-static-libgcc | 静态链接 libc |
-static-libstdc++ | 静态链接 libstdc++ |
实际上由于程序常常依赖其它库,而其它库常常依赖 libc,所以此选项经常无效。
静态独立可执行文件
通过 -static-pie -fPIE
可以产生一个 静态独立可执行文件 。此文件的加载无需 dynamic linker 的参与。
全静态链接
全静态链接的最佳实践是使用 alpine + musl + -static 参数进行编译。
全静态编译的程序之依赖于系统内核版本。这是因为某些旧版本的内核可能不存在新的 syscall。
符号导出
Linux 下静态库默认导出所有符号,但当静态库被动态库使用时,只有被动态库使用的符号才会导出。因此,最佳实践是不要用静态库的方式划分动态库。
动态库
首先查看一个典型的动态库:
$ ldd /usr/bin/cat linux-vdso.so.1 (0x00007ffe165f1000) libc.so.6 => /lib64/libc.so.6 (0x00007fa34d688000) /lib64/ld-linux-x86-64.so.2 (0x00007fa34d886000)
三个动态库分别为:
vdso
使用相对路径链接的动态库
使用绝对路径链接的动态库()
vdso
vdso(Virtual Dynamic Shared Object)是内核的一部分。内核将一部分系统调用导出到用户空间以提高调用效率。
vdso 在程序加载时由系统 强制 进行映射,无论 ldd 中是否显示有 vdso,程序实际上都一定会加载此 so。这部分可以通过查看进程的 /proc/self/maps
文件得到结论。
处于安全的考虑,vdso 每次加载的地址都不固定。例如:
ldd /bin/cat | grep vdso linux-vdso.so.1 (0x00007ffc62be3000) ldd /bin/cat | grep vdso linux-vdso.so.1 (0x00007ffc12ff7000) ldd /bin/cat | grep vdso linux-vdso.so.1 (0x00007ffd3abec000)
Dynamic Linker
/lib64/ld-linux-x86-64.so.2 文件被称为 Dynamic Linker。当程序文件加载后,内核将控制权移交给动态连接器,然后由动态链接器完成动态库的加载。
使用 -Wl,-dynamic-linker
链接选项可以更换动态链接器的名字。