【问题标题】:Dynamic loading and weak symbol resolution动态加载和弱符号分辨率
【发布时间】:2014-01-06 16:05:34
【问题描述】:

分析this question 我发现了一些关于在Linux 上动态加载(dlopen) 上下文中弱符号解析的行为。现在我正在寻找管理这个的规范。

让我们以an example 为例。假设有一个程序a 按此顺序动态加载库b.soc.so。如果c.so 依赖于另外两个库foo.so(在该示例中实际上是libgcc.so)和bar.so(实际上是libpthread.so),那么通常bar.so 导出的符号可以用于满足@ 中的弱符号链接987654337@。但是如果b.so 也依赖于foo.so 而不是bar.so,那么这些弱符号显然不会与bar.so 相关联。似乎foo.soinkages 只查找来自ab.so 及其所有依赖项的符号。

这在某种程度上是有道理的,因为否则加载c.so 可能会在b.so 已经使用该库的某个时刻改变foo.so 的行为。另一方面,在让我开始的问题中,这引起了很多麻烦,所以我想知道是否有办法解决这个问题。为了找到解决方法,我首先需要很好地了解在这些情况下如何指定符号解析的确切细节。

在这些场景中定义正确行为的规范或其他技术文档是什么?

【问题讨论】:

  • 你看过this PDF吗?很多有趣的数据,但不确定它是否包含您要查找的内容。
  • @rodrigo:不确定是这个还是类似的东西,但到目前为止,我发现的所有 ELF 文档都只描述了执行二进制文件之前的动态链接,而不是动态加载对象中涉及的链接。这是一个很长的文档,我可能看错了地方,但到目前为止,它似乎不是我要找的。​​span>
  • 那么这个Drepper post 和它或多或少的related doc (见1.5.2 节)呢?正如我所解释的那样,弱符号仅用于静态链接。所以dlopen() 不会区分弱符号和强符号。
  • @rodrigo:抱歉,我花了这么长时间才尝试这个。看来您是对的,与我的看法相反,加载共享对象时未解决的符号不会导致加载失败。我认为弱是需要的,但事实并非如此。无论如何,您链接的文档的第 1.5.4 节是迄今为止我读过的最有用的部分,因为它非常详细地说明了库的处理顺序以及控制它的各种设置。如果您(或其他人)可以总结这一点,我很乐意为此奖励我的赏金。我会自己做,但在那种情况下,赏金会丢失,所以我不会。

标签: linux dynamic-linking dynamic-loading symbol-table weak-linking


【解决方案1】:

不幸的是,权威文档是源代码。大多数 Linux 发行版都使用 glibc 或其分支 eglibc。在两者的源代码中,应该记录 dlopen() 的文件如下所示:

手册/libdl.texi

@c FIXME these are undocumented:
@c dladdr
@c dladdr1
@c dlclose
@c dlerror
@c dlinfo
@c dlmopen
@c dlopen
@c dlsym
@c dlvsym

有什么技术规范可以从ELF specification和POSIX标准中得出。 ELF 规范使弱符号变得有意义。 POSIX 是实际的 specification for dlopen() 本身。

这是我发现 ELF 规范中最相关的部分。

当链接编辑器搜索归档库时,它会提取归档 包含未定义全局符号定义的成员。这 成员的定义可以是全局符号或弱符号。

ELF 规范没有提到动态加载,所以本段的其余部分是我自己的解释。我发现上述相关的原因是解析符号发生在单个“何时”。在您给出的示例中,当程序 a 动态加载 b.so 时,动态加载器会尝试解析未定义的符号。最终可能会使用全局符号或弱符号。当程序随后动态加载c.so 时,动态加载器再次尝试解析未定义的符号。在您描述的场景中,b.so 中的符号是用弱符号解析的。一旦解决,这些符号就不再是未定义的。使用全局符号还是弱符号来定义它们并不重要。在加载 c.so 时,它们已经不再是未定义的了。

ELF 规范没有给出链接编辑器是什么或链接编辑器何时必须合并目标文件的精确定义。大概这不是问题,因为文档考虑到了动态链接。

POSIX 描述了一些 dlopen() 功能,但很大程度上取决于实现,包括您问题的实质。 POSIX 通常不引用 ELF 格式或弱符号。对于实现 dlopen() 的系统,甚至不需要任何弱符号的概念。

http://pubs.opengroup.org/onlinepubs/9699919799/functions/dlopen.html

POSIX 合规性是另一个标准 Linux 标准库的一部分。 Linux 发行版可能会或可能不会选择遵循这些标准,并且可能会或可能不会遇到认证的麻烦。例如,我知道 Open Group 的正式 Unix 认证非常昂贵——因此有大量的“类 Unix”系统。

Wikipedia article for dynamic loading 上提出了一个关于 dlopen() 标准合规性的有趣观点。 POSIX 规定的 dlopen() 返回一个 void*,但 ISO 规定的 C 表示 void* 是指向对象的指针,这样的指针不一定与函数指针兼容。

事实上,函数和对象之间的任何转换 指针必须被视为(本质上不可移植) 实施扩展,并且没有直接的“正确”方式 存在转换,因为在这方面 POSIX 和 ISO 标准 互相矛盾。

确实存在的标准相互矛盾,并且存在哪些标准文件可能无论如何都不是特别有意义。这是 Ulrich Drepper 写的关于他对 Open Group 及其“规范”的蔑视。

http://udrepper.livejournal.com/8511.html

rodrigo 链接的帖子中表达了类似的情绪。

我进行此更改的原因并不是为了更符合要求 (很好,但没有理由,因为没有人抱怨旧的 行为)。

在调查之后,我相信您所问问题的正确答案是dlopen() 在这方面没有正确或错误的行为。可以说,一旦搜索解析了一个符号,它就不再是未定义的,并且在随后的搜索中,动态加载器将不会尝试解析已经定义的符号。

最后,正如您在 cmets 中所说,您在原始帖子中描述的内容不正确。动态加载的共享库可用于解析以前动态加载的共享库中的未定义符号。事实上,这不仅限于动态加载的代码中未定义的符号。这是一个示例,其中可执行文件本身具有通过动态加载解析的未定义符号。

main.c

#include <dlfcn.h>

void say_hi(void);

int main(void) {
    void* symbols_b = dlopen("./dyload.so", RTLD_NOW | RTLD_GLOBAL);
    /* uh-oh, forgot to define this function */
    /* better remember to define it in dyload.so */
    say_hi();
    return 0;
}

dyload.c

#include <stdio.h>
void say_hi(void) {
    puts("dyload.so: hi");
}

编译并运行。

gcc-4.8 main -fpic -ldl -Wl,--unresolved-symbols=ignore-all -o main
gcc-4.8 dyload.c -shared -fpic -o dyload.so
$ ./main
dyload.so: hi

请注意,主可执行文件本身已编译为 PIC。

【讨论】:

  • 这就是我所说的优秀答案!
  • Glibc 链接器会忽略符号的弱点,除非指定了LD_DYNAMIC_WEAK(参见manpage)。
猜你喜欢
  • 2011-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-26
  • 1970-01-01
  • 2015-03-12
  • 2014-08-24
相关资源
最近更新 更多