动态库 "forwarding"

Dynamic library "forwarding"

[编辑:简而言之,问题是:当我 link 针对一个动态库 link 针对另一个动态库时,我是否必须 明确地 link 也反对吗?]

我在一个软件中看到了这样的东西。它不起作用,现在我想知道它是否应该起作用。我可以 link 一个库 "bar" 动态地针对另一个库 "foo" 然后 link 针对该库访问来自 "foo" 的符号(因为 "bar"应该 linked 到 "foo")? (我正在使用 linux 和 gcc 4.8.2 以防万一。)

具体假设我有以下三个文件。现在我做

gcc -c -Wall -Werror -fpic foo.c
gcc -shared -olibfoo.so foo.o

我通常会在什么时候做

gcc -o program main.c -L. -lfoo

获取工作程序。现在我做

gcc -shared -olibbar.so -L. -lfoo
gcc -o program main.c -L. -lbar

这行不通:

/tmp/cciNSTyI.o: In function `main':
main.c:(.text+0xf): undefined reference to `foo'
collect2: error: ld returned 1 exit status

应该吗?

foo.h

#ifndef foo_h__
#define foo_h__

extern void foo(void);

#endif

foo.c

#include <stdio.h>

void foo(void)
{
  puts("foo");
}

main.c

#include <stdio.h>
#include "foo.h"

int main(void)
{
  puts("Library test...");
  foo();
  return 0;
}

编辑:我写了一个关于我对下面发生的事情的理解的答案。

我仍然不太清楚的一件事是参数的顺序:如果(使用该答案中的文件 bar.c)我 link 带有行的栏(注意位置"bar.o")

gcc -o program main.c -L. -lbar
gcc -shared -olibbar.so -L. -lfoo bar.o

那么"bar"不依赖"foo":

> readelf -d libbar.so
Dynamic section at offset 0xe18 contains 24 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000c (INIT)               0x5a8
 [...]

不,不可能 "forward" 动态 linked 库。

当您 link 针对任何静态或动态库时,您实际上启用了主执行程序 table 来调用 functions/symbols 在 linked 库中定义的 functions/symbols .

在您的情况下,库 bar 中没有定义函数 foo()。所以当bar.so被创建时,生成的符号被注册在你的主executable-program的符号table中。因为,bar 库中的符号不​​包含任何名为 foo() 的函数,它没有在 program 的符号 table 中注册。因此,当 foo() 在一段时间内被调用时,加载程序会尝试在您 link 在编译 program 期间编辑的所有库中找到将在其中定义 foo() 的 .so。因此 运行 时间错误。它没有显示编译时错误,因为您已经将 foo.h 头文件包含在其中。

您需要显式link您要在正在编译的代码中引用哪些符号(函数、变量、常量等)的所有库。

when I link against a dynamic library that is linked against another dynamic library, do I have to explicitly link against that as well?

没有。

另一个库必须针对它需要的共享库进行 link 编辑,否则它将是一个巨大的 fuster cluck(你用 .a 文件得到的那个)。

想象一下其他人将共享库依赖项添加到您使用的共享库中。这会导致您的应用程序在 link 时间(最多)或 运行 时间失败。这就是共享库带有自己的依赖项的原因。

您可以使用 readelf 实用程序来检查共享库依赖性,例如:

$ readelf -d /usr/lib64/libboost_wave-mt.so

Dynamic section at offset 0x12fd58 contains 30 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libpthread.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [librt.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libboost_filesystem-mt.so.5]
 0x0000000000000001 (NEEDED)             Shared library: [libboost_thread-mt.so.5]
 0x0000000000000001 (NEEDED)             Shared library: [libboost_date_time-mt.so.5]
 0x0000000000000001 (NEEDED)             Shared library: [libboost_system-mt.so.5]
 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libm.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000e (SONAME)             Library soname: [libboost_wave-mt.so.5]
 0x000000000000000c (INIT)               0xb49f0
 0x000000000000000d (FINI)               0x10c018
 0x000000006ffffef5 (GNU_HASH)           0x1b8
 0x0000000000000005 (STRTAB)             0xbe08
 0x0000000000000006 (SYMTAB)             0x2cb8
 0x000000000000000a (STRSZ)              637705 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000003 (PLTGOT)             0x3308a8
 0x0000000000000002 (PLTRELSZ)           7584 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0xb2c50
 0x0000000000000007 (RELA)               0xa8600
 0x0000000000000008 (RELASZ)             42576 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x000000006ffffffe (VERNEED)            0xa8530
 0x000000006fffffff (VERNEEDNUM)         4
 0x000000006ffffff0 (VERSYM)             0xa7912
 0x000000006ffffff9 (RELACOUNT)          405
 0x0000000000000000 (NULL)               0x0

注意 NEEDED 属性 - 这些是在您加载此共享库时自动加载的共享库。


gcc -shared -olibbar.so -L. -lfoo

您从共享库生成共享库。在这种情况下,您需要使用 --relocatable linker 选项 partial linking

gcc -shared -Wl,--relocatable -olibbar.so -L. -lfoo

这是对 Maxim Egorushkin 的回答的跟进(特别是使用 readelf 很有启发性)。

首先,似乎只有库被标记为 "NEEDED" 如果它们确实在任何地方都需要,即如果完全使用它们定义的某些符号。在问题的例子中当然不是这样。

其次,即使通过这些依赖项 "foo" 最终被 link 编辑,它的符号仍然无法用于 main.c 除非它被 link 编辑明确地。

要验证此行为,请考虑文件

bar.h

#ifndef bar_h__
#define bar_h__

extern void bar(void);

#endif

bar.c

#include <stdio.h>
#include "foo.h"

void bar(void)
{
  foo();
  puts("bar");
}

通过

编译和 linked
gcc -o program main.c -L. -lbar
gcc -shared -olibbar.so -L. -lfoo bar.o

然后"bar"取决于"foo"(与答案中的不同):

> readelf -d libbar.so
Dynamic section at offset 0xe08 contains 25 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libfoo.so]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000c (INIT)               0x5b0

如果将上面文件 main.c 中的每个 "foo" 替换为 "bar",就可以编译并 link 将所有内容编译成一个工作程序

gcc -o program main.c -L. -lbar -Wl,-rpath-link .

如果保留 "foo" 行,linking 会失败,因为无法解析 foo 符号(程序甚至 linked 不会针对 "bar").

有趣的情况是同时调用 "foo" 和 "bar" 的情况。现在 main.c 被 linked 反对 "bar" 是 linked 反对 "foo",但是 "foo" 中的符号仍然不能用于 main.c.

这种行为实际上是有道理的,因为 main.c 可能依赖于定义函数 foo() 的不同库,然后它不想知道 "bar" 使用的那个。如果需要这种行为,那么优化 not "NEEDing" 不需要的库也是有效的。