共享库符号冲突和静态链接(Linux)

Shared library symbol conflicts and static linking (on Linux)

我遇到了一篇很好的文章 Shared Library Symbol Conflicts (on Linux) 中详细阐述的问题。问题是当执行和.so定义了同名函数时,如果.so调用这个函数名,它会调用执行中的那个函数而不是.so本身中的这个。

本文来说说案例。我了解 layer.o 中的 DoLayer() 函数在编译 layer.o.

时具有 DoThing() 的外部函数依赖性

但是在编译libconflict.so时,不应该就地解析外部函数依赖并静态替换为conflict.o/DoThing()的地址吗?

为什么 layer.o/DoLayer() 仍然使用动态链接来查找 DoThing()?这是设计好的行为吗?

是的,这是设计好的行为。当您将程序 link 转换为二进制文件时,所有对命名外部(非静态)函数的引用都将解析为指向二进制文件的符号 table。 link 编辑的任何共享库都指定为 DT_NEEDED 个条目。

然后,当您 运行 二进制文件时,动态 linker 将每个所需的共享库加载到一个 suitable 地址并将每个符号解析为一个地址。有时这是懒惰地完成的,有时是在第一次启动时完成一次。如果有多个名称相同的符号,linker 将选择其中一个,您的程序可能会崩溃,因为您最终可能不会找到正确的符号。

请注意,这是 Linux 上的行为,它将所有符号作为平面命名空间。 Windows 使用树形拓扑以不同方式解析符号,它既有优点(冲突更少)也有缺点(无法在一个库中分配内存并在另一个库中释放它)。

如果您希望 LD_PRELOAD 之类的东西起作用,Linux 行为非常重要。这允许您使用像 Electric Fence 这样的调试工具和像 Google 性能工具这样的 CPU 分析工具,或者在 运行 时替换内存分配器。 None 如果符号优先解析为它们的二进制或共享库,这些东西将起作用。

GNU dynamic linker 确实支持符号版本,因此可以将多个版本的共享库加载到同一个程序中。通常,像 Debian 这样的发行版会使用他们希望经常更改的库来执行此操作,例如 OpenSSL。如果程序使用使用 OpenSSL 1.0 的 liba 和使用 OpenSSL 1.1 的 libb,那么程序在这种情况下应该仍然可以运行,因为 OpenSSL 具有版本化符号,并且每个库将使用相关符号的适当版本。

Is this a designed behavior?

是的。

在 UNIX 上引入共享库时,目标是假装它们像代码在常规(存档)库中一样工作。

假设您在 libfoolibbar 中都定义了 foo(),并且 libbar 中的 bar() 调用了 foo().

设计目标是,无论 libfoolibbar 是存档还是共享库,cc main.c -lfoo -lbar 都能正常工作。实现此目的的唯一方法是让 libbar.so 使用 动态链接 来解析从 bar()foo() 的调用,尽管有一个本地版本的 foo().

这种设计使得不可能创建一个自包含的 libbar.so -- 它的行为(它最终调用哪些函数)取决于链接到进程中的其他函数。这也与 Windows DLL 的工作方式相反。

当时并没有考虑创建独立的 DSO,因为 UNIX 实际上是开源的。

您可以更改带有特殊链接器标志的规则,例如 -Bsymbolic。但是规则很快就会变得复杂,并且(因为这不是默认设置)您可能会在链接器或运行时加载器中遇到错误。