使用共享库编译可执行文件时仅链接所需的符号

Link only needed symbols when compiling an executable with a Shared Library

本文关键字:链接 符号 共享 编译 可执行文件      更新时间:2023-10-16

我正在做一个繁重的项目,该项目有很多相互依赖的静态库。此外,某些符号在某些库之间是冗余的,具有不同的实现。我的目标是使项目与共享库一起工作。

我尝试使用我的一个共享库编译可执行文件,但我的可执行文件未使用的函数出现未定义的符号错误。经过一些研究,我了解到动态链接器的工作方式与静态链接器非常不同。如果我理解正确,在链接共享库时,需要解析所有符号,因为整个库都加载到内存中。

一个简单的解决方法是在编译可执行文件时添加我的库的所有依赖项。但是它们充满了依赖关系,有时这意味着在命令行中添加 10+ 个库,这将适用于一百个可执行文件。

到目前为止,我尝试使用-Wl,--as-needed-Wl,--unresolved-symbols=ignore-in-shared-libs并使用dlopen打开共享对象以获得我想要的dlsym函数。但所有这些方法都在某一点或另一点失败。

我的问题是:当将动态库与可执行文件链接时,您是否被迫解析动态库的每个未定义符号?

动态链接的详细信息以及所涉及的对象类型因环境和工具链而异。 在你说的Linux上,在Solaris和其他几个UNIX-y平台上,你正在研究ELF对象和语义。

到目前为止,我尝试使用-Wl,--as-needed-Wl,--unresolved-symbols=ignore-in-shared-libs

这两者都在(静态)链接时具有全部效果。 第一个告诉链接器,只有在命令行上遵循它的库解析至少一个尚未定义的符号时,才应链接它们。 后者告诉链接器不要担心解析链接中包含的共享库中的符号。 这与运行程序时动态链接器的行为无关。

并用dlopen打开共享对象以获得我想要的dlsym功能.

dlopen指示动态链接器在运行时链接未在二进制文件中指定为必需共享库的共享对象。 它此时的行为可以通过传递给dlopen的标志来调制,但可用的选项不能超过链接时可以指定的选项。 当您在链接时实际上知道需要哪些库时,几乎没有理由使用dlopen

您是否被迫解析动态库的每个未定义符号 当将其链接到可执行文件时?

专注于ELF和GNU工具链,没有。-Wl,--unresolved-symbols=ignore-in-shared-libs正是为了避免这种情况的目的。 但正如您所发现的,这伴随着警告。

首先,在每个共享对象中,引用数据的每个符号都需要在运行时由动态链接器解析,无论您如何链接各种共享对象,包括主程序。 这主要是一个操作考虑因素 - 动态链接器无法延迟解析引用对象的符号,因为它没有很好的方法来捕获访问它们的尝试。

另一方面,可以将引用函数的符号的解析推迟到首次使用。 事实上,这是 GNU 链接器的默认值,但是您可以通过在链接时将-Wl,-z,lazy传递给gcc来重申这一点。 但是,请注意,这会设置要链接的对象的属性,因此您应该确保每个共享对象都是使用该链接选项构建的(但通常是因为它们是,因为,再次,这是默认值)。

此外,还应注意动态链接器的行为可能会受到环境变量的影响。 特别是,如果动态链接器在运行时环境中找到设置为非空字符串LD_BIND_NOW则将禁用延迟绑定。

一个简单的解决方法是添加我的 编译可执行文件时的库。但他们是如此充满 依赖关系,这有时意味着将 10+ 库添加到 命令行,这将是大约一百个 可执行。

这到底有什么大不了的? 当然,您有一个(或几个)结构良好的Makefile来帮助您,因此确保所有库都链接起来应该没什么大不了的。 右?

但是你也应该考虑重构你的库,特别是如果"相互依赖"意味着依赖关系图中存在循环。 正如您所发现的,动态链接与静态链接不同,差异有时比您目前正在努力解决的差异更微妙。 虽然这不是硬性规定,但我敦促您避免造成一个进程使用的共享对象包含同一外部符号的多个定义的情况,尤其是在实际使用该符号的情况下。


更新

上面的讨论侧重于将共享库链接到可执行文件,但还有另一个重要的考虑因素:库本身是如何链接的。 每个 ELF 对象,无论是可执行文件库还是共享库,都带有自己的所需共享库列表。 动态链接器将以递归方式将所有这些包含在要在程序启动时(立即)加载的共享库列表中,尽管它的行为与引用函数的符号的延迟绑定有关。

因此,如果您希望可执行文件不需要给定的共享库 X,那么不仅该可执行文件本身,而且它所依赖的每个共享库都必须避免表达对 X 的依赖关系。 如果某些共享库在与其他程序结合使用时需要 X,那么在构建这些程序时,您将有责任链接所有需要的库(否则,您可以安排仅链接直接依赖项)。 你可以告诉 GNU 链接器通过传递--allow-shlib-undefined标志来构建共享库。

这是一个完整的概念证明:

主.c

int mul(int, int);
int main(void) {
return mul(2, 3);
}

int add(int, int);
int mul(int x, int y) {
return x * y;
}
int mul2(int x, int y) {
return add(x, y) * add(x, -y);
}

生成文件

CC = gcc
LD = gcc
CFLAGS = -g -O2 -fPIC -DPIC
LDFLAGS = -Wl,--unresolved-symbols=ignore-in-shared-libs
SHLIB_LDFLAGS = -shared -Wl,--allow-shlib-undefined
all: main
main: main.o libmul.so
$(LD) $(CFLAGS) $(LDFLAGS) -o $@ $^
libmul.so: mul.o
$(LD) $(CFLAGS) $(SHLIB_LDFLAGS) -o $@ $^
clean:
rm -f main main.o libmul.so mul.o

演示

$ make
gcc -g -O2 -fPIC -DPIC   -c -o main.o main.c
gcc -g -O2 -fPIC -DPIC   -c -o mul.o mul.c
gcc -g -O2 -fPIC -DPIC -shared -Wl,--allow-shlib-undefined -o libmul.so mul.o
gcc -g -O2 -fPIC -DPIC -Wl,--unresolved-symbols=ignore-in-shared-libs -o main main.o libmul.so
$ LD_LIBRARY_PATH=$(pwd) ./main
$ echo $?
6
$

请注意,省略注释中讨论的-zlazy链接器选项,因为它是默认值。