动态加载和弱符号分辨率

时间:2013-12-18 12:53:56

标签: 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),那么通常由{{1}导出的符号}}可用于满足bar.so中的弱符号链接。但是,如果foo.so还取决于b.so而不取决于foo.so,那么这些弱符号显然不会与bar.so相关联。好像bar.so墨迹似乎只查找来自foo.soa的符号及其所有依赖项。

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

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

1 个答案:

答案 0 :(得分:12)

不幸的是,权威文档是源代码。大多数Linux发行版都使用glibc或它的fork,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中的符号使用弱符号解析。一旦解决,这些符号就不再是未定义的。使用全局符号或弱符号来定义它们并不重要。 <{1}}加载的时间已经不再是未定义的。

ELF规范没有精确定义链接编辑器的内容或链接编辑器必须组合目标文件的时间。据推测,这是一个非问题,因为该文档在考虑动态链接。

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

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

POSIX合规性是另一个标准Linux标准库的一部分。 Linux发行版可能会也可能不会选择遵循这些标准,可能会或可能不会成为认证的麻烦。例如,据我所知,Open Group正式的Unix认证非常昂贵 - 因此拥有丰富的“类Unix”系统。

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

  

事实仍然是功能和对象之间的任何转换   指针必须被视为(固有的非便携式)   实现扩展,并没有直接的“正确”方式   转换存在,因为在这方面POSIX和ISO标准   相互矛盾。

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

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

在罗德里戈联系的帖子中表达了类似的情绪。

  

我做出这种改变的原因并不是更符合要求   (这很好,但没有理由,因为没有人抱怨旧的   行为)。

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

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

的main.c

dlopen()

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

编译并运行。

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

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