在创建共享库时,ld链接器会从静态库中移除一个目标文件。

3
我有一些静态库,我将它们链接在一起成为一个共享库。其中一个库,比如说libUsefulFunc.a包含了一个名为usefulFunc()的函数,它只被另一个静态库usingFunc()使用。usingFunc()存储在libUsingFunc.a中的usingFunc.c文件中。
问题是链接器会丢弃usefulFunc.o,并且我会得到"undefined reference"错误。我尝试过链接顺序的不同方式。
我已经用最简单的文件重新创建了这种情况:
a.h
extern int foo();

a.c

#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 archives --end-group选项。指定的存档将被重复搜索,直到不再创建新的未定义引用。
对于Android NDK库,只有第一和第二个选项似乎可用,因为NDK的makefile没有提供指定存档组的方法。
希望这对其他人也有用!

1
第一次调用ld不应该是这样的吗:ld -shared -o test.so ./b.a ./a.a - alk
对于那些也想知道是否真的需要使用extern关键字以及它是否会影响到这里正在发生的事情的人,这个答案:https://dev59.com/I3RA5IYBdhLWcg3wtwSe#856736 或许会有所帮助。 - alk
在Mac OS X 10.7.5上,使用GCC 4.7.1时,我不得不用gcc替换ld命令(ld命令显示“未知选项-shared”,但GCC知道该怎么做)。完成这个步骤后,假设第一个ld行引用了a.ab.a(而不是问题中写的.o文件),第一个共享对象为空——没有从任何存档库中复制任何内容。我不确定这是否是意外的行为;根据我的经验,共享库不会直接包含其他库(静态或共享)的材料,尽管它可能包含对它们的引用。 - Jonathan Leffler
3个回答

1

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

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

我在问题中已经写过 - 我知道这个选项,但在我的情况下,我找不到使用它的方法。该库是由Android工具链(ndk-build)构建的,我无法为单个库传递链接器选项。 - Moshe Kravchik
我找到了一种在Android工具链中强制使用--whole-archive的方法,为此需要在共享库的Android.mk中使用LOCAL_WHOLE_STATIC_LIBRARIES来为静态库设置。 - Moshe Kravchik

1

如果对读者有用的话,我发现了一个非常有用(而奇怪)的行为。

如果您在命令行上具有以下链接顺序:

-lsomething1 -lsomething2

如果libsomething1.a中包含的各个.o文件“相互引用”(彼此之间具有一些共享方法或类似内容),并且至少有一个.o文件具有程序“有用/使用”的依赖项,则所有相互链接的.o文件将在“链接该库时”被加载(基本上是当链接器执行-lsomething1命令时)。

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

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

b)如果“something1”中的.o文件之间没有相互依赖,则可能会在加载时“丢弃”该.o文件,因此不可用。

基本上,有时可以在 something1 中拥有对象,这些对象满足与 something2 的链接时间依赖关系,并且链接器很高兴,尽管它们按照“错误的顺序”指定(在这种情况下,正确顺序是相反的,满足依赖关系的内容出现在链接链中的后面)。太令人困惑了。
显然,如果它在加载时注意到它需要/想要一个。o文件,则会加载整个.o文件,而不仅仅是它知道自己需要/想要的符号。
因此,如果您意外删除了一个相互依赖项,则可能会突然遇到以前不存在的 undefined reference 失败(通常可以通过按照“正确”的顺序放置“-l”命令来抵消)。这也意味着您可以“意外地”满足前向依赖关系,这不是ld通常的工作方式。想想吧。您可以意外地满足前向依赖项[!]
您可以通过将 -Wl,-verbose 添加到链接命令行中来更精确地查看正在发生的情况(正在包含哪些.o文件或不包含哪些文件)。
您可以使用nm命令(例如:nm libmylib.a)或者交叉编译时使用i686-mingw-nm libmylib.a来查看您的 .a 文件中包含哪些 .o 文件(及其符号)。祝好运!

0

避免将静态对象链接到共享库中,因为共享库最好包含位置无关代码(以避免在动态链接时进行过多的重定位)。

实际上,使用gcc -fPIC -O -Wall -c foo.c -o foo.pic.o重新编译每个源文件,然后将它们全部链接起来形成您的共享库,例如gcc -shared *.pic.o -o libshared.so(您可以将所需的库链接到您的.so文件中)。


谢谢,我不明白为什么从静态库构建共享对象时不能使用PIC。 - Moshe Kravchik
通常的惯例是有两个静态库——第一个编译时不使用PIC,第二个使用了PIC。后者的名称带有“S”后缀(例如共享),例如libfoo.a,libfooS.a。 - Andy
这不是一个常见的约定。在Debian上,我不能按照它来命名一个软件包。 - Basile Starynkevitch
是的,在libtool世界中并不常见,但在某些平台上,例如QNX,我看到了数十个*S.a文件。 - Andy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接