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

时间:2018-07-10 12:49:10

标签: c++ c linker shared-libraries

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

我尝试使用共享库之一编译可执行文件,并且我的可执行文件未使用的功能出现未定义的符号错误。经过一些研究,我了解到动态链接器的工作方式与静态链接器的工作方式大不相同。如果我理解正确,则在链接共享库时,由于整个库都已加载到内存中,因此需要解析所有符号。

一个简单的解决方法是在编译可执行文件时添加我的库的所有依赖项。但是它们是如此依赖,以至于有时这意味着要在命令行中添加10个以上的库,这将是针对一百个可执行文件的。

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

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

1 个答案:

答案 0 :(得分:0)

动态链接的详细信息以及所涉及的对象的种类在环境和工具链中会有所不同。在您所说的Linux上,在Solaris和其他UNIX-y平台上,您正在查看ELF对象和语义。

  

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

它们都在(静态)链接时发挥全部作用。第一个告诉链接器,仅当它们解析至少一个尚未定义的符号时,才应链接命令行上跟随它的库。后者告诉链接器不必担心解析链接中包含的共享库中的符号。这与运行程序时 dynamic 链接程序的行为无关。

  

,然后使用dlopen打开共享对象,以使用dlsym获得我想要的功能。

dlopen指示动态链接器在运行时链接共享库中未指定为必需的共享库的共享库。可以通过传递给dlopen的标志来调制该点的行为,但是可用选项的数量最多不能超过链接时指定的选项。当您实际上在链接时知道所需的库时,几乎没有理由使用dlopen

  

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

关注ELF和GNU工具链,没有。 -Wl,--unresolved-symbols=ignore-in-shared-libs正是为了避免这种情况。但是,正如您所发现的那样,有一些警告。

首先,在动态共享程序中,每个链接对象在运行时都需要解析每个引用 data 的符号,无论您如何链​​接各种共享对象(包括主程序) 。这主要是一个操作上的考虑-动态链接器无法推迟引用对象的解析符号,因为它无法捕获试图访问它们的好方法。

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

此外,您应该意识到动态链接器的行为可能会受到环境变量的影响。特别是,如果动态链接程序在运行时环境中发现LD_BIND_NOW设置为非空字符串,则将禁用惰性绑定。

  

一个简单的解决方法是添加我的所有依赖项   可执行文件时编译库。但是他们充满了   依赖关系,这有时意味着要向其中添加10个以上的库   命令行,这将是大约一百   可执行文件。

那真的有什么大不了的?当然,您有一个结构合理的Makefile(或几个)可以为您提供帮助,因此确保所有库都链接起来并不重要。对吧?

但是您还应该考虑重构您的库,尤其是在“相互依赖”意味着依赖关系图中存在循环的情况下。正如您所发现的那样,动态链接不同于静态链接,并且有时差异比您目前正在努力解决的差异更微妙。尽管这不是硬性规定,但我敦促您避免出现一种情况,即一个进程使用的共享库在其中包含同一外部符号的多个定义,尤其是在实际使用该符号的情况下。


更新

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

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

这里是概念的完整证明:

main.c

int mul(int, int);

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

mul.c

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);
}

Makefile

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链接器选项被省略,因为它是默认选项。