linux - 动态加载和弱符号解析

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

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

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

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

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

最佳答案

不幸的是,权威文档是源代码。大多数 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 规范中最相关的部分。

When the link editor searches archive libraries, it extracts archive members that contain definitions of undefined global symbols. The member’s definition may be either a global or a weak symbol.

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

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

POSIX 描述了一些 dlopen() 功能,但留给了很多实现,包括你的问题的实质。 POSIX 一般不提及 ELF 格式或弱符号。对于实现 dlopen() 的系统,甚至不需要任何弱符号的概念。

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

POSIX 合规性是另一个标准 Linux Standard Base 的一部分。 Linux 发行版可能会或可能不会选择遵循这些标准,并且可能会或可能不会麻烦地通过认证。例如,据我了解,由 Open Group 进行的正式 Unix 认证非常昂贵——因此出现了大量“类 Unix”系统。

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

The fact remains that any conversion between function and object pointers has to be regarded as an (inherently non-portable) implementation extension, and that no "correct" way for a direct conversion exists, since in this regard the POSIX and ISO standards contradict each other.

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

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

rodrigo 链接的帖子表达了类似的观点。

The reason I've made this change is not really to be more conformant (it's nice but no reason since nobody complained about the old behaviour).

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

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

主.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;
}

动态加载.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。

关于linux - 动态加载和弱符号解析,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20658809/

相关文章:

linux - 尝试使用 Openssl 解密 S/MIME 文件

module - Racket 中的模块路径索引是什么

linux - Linux内核如何确定ld.so的加载地址?

linux - shell 脚本 : Is there a difference between "local foo" and "local foo="?

linux - 访问 Linux VM 内的 Azure 公共(public) IP

linux - gitlab dosent 恢复备份

c++ - arm-linux libstdc++ 调试和发布的区别

c - Linux 何时加载 .so 文件?

c++ - 没有外部的动态加载 "C"

dynamic-linking - 是否可以通过名称唯一标识动态导入的函数?