一个进程生成的过程如下:

Diagram

位置无关代码

在编译阶段,默认情况下函数及变量地址使用绝对地址表示。当目标文件中的函数需要被动态加载时,就需要添加 -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 链接选项可以更换动态链接器的名字。

Last moify: 2025-05-06 09:49:50
Build time:2025-07-18 09:41:42
Powered By asphinx