【问题标题】:ld linker removing an object file from a static library when creating a shared oneld 链接器在创建共享库时从静态库中删除目标文件
【发布时间】:2012-12-15 00:38:40
【问题描述】:

我有许多静态库,我将它们链接到一个共享库中。其中一个,比如 libUsefulFunc.a 包含一个对象文件 usefulFunc.o,它带有一个函数,有用的Func(),它只能从另一个静态库中使用,比如说 usingFunc(),驻留在 libUsingFunc.a 中的 usingFunc.c 中

问题是链接器丢弃了有用的Func.o,我得到错误“未定义的引用”。我尝试了两种链接顺序。

我用我能想到的最简单的文件重新创建了这种情况:

啊。

extern int foo();

交流

#include "a.h"
int foo()
{
    return 13;
}

b.c

#include "a.h"

extern int b()
{
  return print("a = %d\n", foo());
}

构建一切:

gcc -c a.c -o a.o
gcc -c b.c -o b.o
ar q b.a b.o
ar q a.a a.o
ld -shared -o test.so ./b.a ./a.a
nm ./test.so 
00001034 A __bss_start
00001034 A _edata
00001034 A _end

如果我提供目标文件而不是档案:

ld -shared -o test.so ./a.o ./b.o
nm ./test.so 
00001220 a _DYNAMIC
00000000 a _GLOBAL_OFFSET_TABLE_
00001298 A __bss_start
00001298 A _edata
00001298 A _end
000001a0 T b
00000194 T foo
         U print

有没有办法告诉链接器不要丢弃他认为未使用的目标文件而不必列出所有目标文件?我知道有一个 --whole-archive 选项,但我将该库构建为 Android NDK 项目的一部分,但没有找到一种方法为特定库传递此选项。

更新我已经完全理解了我原来的问题并找到了正确的解决方案。首先是我上面的示例:链接器从入口点开始并搜索它们使用的所有符号。这些在当前库中查找。一旦找到,它就会将他们使用的符号添加到其列表中,从而强制执行。这些库仅是一次进程,并且按照它们在命令行中出现的顺序。因此,如果第二个库使用第一个库中的符号 - 该符号将保持未定义,因为链接器不会返回。所以在我的例子中,我应该告诉他 b() 将被外部调用,我可以使用 --undefined=b:

ld -shared -o test.so --undefined=b ./b.a ./a.a

在最初的问题中,我遇到了两个静态库之间的循环引用。 就好像我在 b 存档中有一个文件 b1.c,它具有从 foo() 调用的函数 foo_b()。对于这种情况,我找到了 3 种可能的解决方案:

  1. 列出 b 两次:ld -shared -o test.so --undefined=b ./b.a ./a.a ./b.a
  2. 使用 --whole-archive
  3. 使用 --start-group 归档 --end-group 选项。重复搜索指定的档案,直到没有新的 未定义的引用被创建。

对于 Android NDK 库,似乎只有第一个和第二个选项可用,因为 NDK 的 makefile 不提供指定存档组的方法

希望这对其他人也有用!

【问题讨论】:

  • 第一次调用ld不应该读成ld -shared -o test.so ./b.a ./a.a吗?
  • 对于那些还想知道是否真的需要使用 extern 关键字和/或会影响这里发生的事情的人,这个答案:stackoverflow.com/a/856736/694576 可能很有趣。
  • 在带有 GCC 4.7.1 的 Mac OS X 10.7.5 上,我不得不用 gcc 替换 ld 命令(ld 命令说“未知选项 -shared,但是 GCC知道该怎么做)。完成后,假设第一个ld 行引用了a.ab.a(而不是问题中写的.o 文件),第一个共享对象是空的——没有从任何一个存档库复制.我不相信这是出乎意料的行为;根据我的经验,共享库不会被构建,因此它直接包含来自其他库(静态或共享)的材料,尽管它可能包含对它们的引用。

标签: c linux linker ld


【解决方案1】:

尝试使用 --whole-archive 选项:

ld -shared -o test.so --whole-archive ./a.a ./b.a

【讨论】:

  • 正如我在问题中所写 - 我知道这个选项,但就我而言,我找不到使用它的方法。该库是由 android 工具链 (ndk-build) 构建的,我无法为那里的各个库传递链接器选项。
  • 我找到了一种在 Android 工具链中为静态库强制执行 --whole-archive 的方法。为此,需要在共享库的 Android.mk 中为他的静态库使用 LOCAL_WHOLE_STATIC_LIBRARIES
【解决方案2】:

好吧,万一它对追随者有用,我发现了一个非常有用(和奇怪)的行为。

如果您在命令行上有此链接顺序:

-lsomething1 -lsomething2

如果 libsomething1.a 中包含的各种 .o 文件“相互引用”(它们之间有一些共享方法等)并且至少有一个 .o 文件具有“有用/被程序使用”,那么所有相互链接的 .o 文件都将在“当该库被链接时”加载(基本上,当链接器点击 -lsomething1 命令时)。

然后链接器继续并尝试加载库“something2”。 如果“something2”中存在对“something1”中的.o文件的依赖

a) 如果“something1”中的 .o 文件之间存在相互依赖关系,那么它将加载,即使该特定依赖关系之前未被使用。

b) 如果something1 中的 .o 文件之间没有相互依赖关系,那么 .o 文件可能正如 OP 所说的那样在加载时“被丢弃”,因此将不可用。

所以基本上它可能“有时”在something1 中有满足与something2 的链接时间依赖性的对象,并且链接器很高兴,即使它们以“错误的顺序”指定(先是先到后二,在在这种情况下,正确的顺序是相反的,满足依赖关系的东西在链接链的后面)。太混乱了。

显然,如果它在加载时注意到它需要/想要一个 .o 文件,它会加载整个 .o 文件,而不仅仅是它知道它需要/想要的符号。

因此,如果您不小心删除了一个相互依赖关系,您可能会突然遇到以前不存在的undefined reference 故障(您通常可以通过将“-l”命令放入“正确”顺序来应对)。这也意味着您可以“意外地”满足前向依赖关系,这不是 ld 通常的工作方式。去搞清楚。你可以偶然满足前向依赖[!]

通过将-Wl,-verbose 添加到链接命令行,您可以更准确地查看正在发生的事情(哪些 .o 文件被包含或不包含)。

您可以通过使用nm 命令(例如:nm libmylib.a)或交叉编译类似i686-mingw-nm libmylib.a 或类似的。 GL!

【讨论】:

    【解决方案3】:

    避免将静态对象链接到共享库,因为共享库最好包含position independent code(以避免在动态链接时重定位过多)。

    在实践中,使用gcc -fPIC -O -Wall -c foo.c -o foo.pic.o 重新编译每个源文件,例如foo.c,并将它们全部链接起来以创建您的共享库,例如gcc -shared *.pic.o -o libshared.so(您可以将所需的库链接到您的.so

    【讨论】:

    • 谢谢,我不明白为什么从静态库构建共享对象时我不能有 PIC。
    • 常见的约定是有两个静态库 - 第一个编译时不使用 PIC,第二个编译时使用 PIC。后者的名称带有后缀“S”(如共享),例如libfoo.a, libfooS.a.
    • 这不是一个常见的约定。在 Debian 上,我无法在它之后命名一个包
    • 是的,在 libtool 世界中并不常见,但在某些平台中,例如在 QNX 中,我看到了几十个 *S.a 文件。
    猜你喜欢
    • 1970-01-01
    • 2021-09-30
    • 2012-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多