分析这个问题后,我发现了有关dlopen在Linux 上动态加载()上下文中弱符号解析行为的一些信息。现在,我正在寻找管理该规范的规范。
dlopen
让我们举个例子。假设有一个程序a可以按此顺序动态加载库b.so和c.so。如果c.so依赖于其他两个库foo.so(实际上libgcc.so在该示例中)和bar.so(实际上libpthread.so),则通常bar.so使用导出的符号可以满足中的弱符号链接foo.so。但是如果b.so还依赖foo.so但不依赖bar.so,那么这些弱符号显然不会被联系起来bar.so。它好像foo.soinkages只能看从符号a和b.so及其所有的依赖关系。
a
b.so
c.so
foo.so
libgcc.so
bar.so
libpthread.so
在某种程度上,这是有道理的,因为否则加载c.so可能会foo.so在b.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规范和POSIX标准中获得什么技术规范。ELF规范使弱符号变得有意义。POSIX是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”系统。
关于dlopen()的标准合规性的有趣观点在Wikipedia文章上进行了动态加载。根据POSIX的要求,dlopen()返回void ,但是根据ISO的要求,C表示void 是指向对象的指针,并且这种指针不一定与函数指针兼容。
事实仍然是,函数指针和对象指针之间的任何转换都必须被视为(本质上不可移植的)实现扩展,并且不存在直接转换的“正确”方式,因为在这方面POSIX和ISO标准彼此矛盾。其他。
确实存在的标准相互矛盾,并且其中包含哪些标准文件可能并不是特别有意义。这是乌尔里希·德雷珀(Ulrich Drepper)撰写的关于他对Open Group的鄙视及其“规范”的文章。
http://udrepper.livejournal.com/8511.html
罗德里格(Rodrigo)链接的帖子也表达了类似的观点。
我做出此更改的原因并不是真正符合要求(很好,但没有理由,因为没有人抱怨过时的行为)。
经过仔细研究,我相信您所提问题的正确答案是,dlopen()在这方面没有对或错的行为。可以说,一旦搜索已解析符号,它就不再是未定义的,并且在后续搜索中,动态加载程序将不会尝试解析已经定义的符号。
dlopen()
最后,正如您在评论中指出的那样,您在原始帖子中描述的内容是不正确的。动态加载的共享库可用于解析以前动态加载的共享库中的未定义符号。实际上,这不仅限于动态加载的代码中的未定义符号。这是一个示例,其中可执行文件本身具有未定义的符号,该符号可通过动态加载来解析。
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。