Linux如何在地址空间中加载共享库?何时加载?

51
地址共享对象的地址是在程序中什么时候指定的?是在链接期间还是加载期间?如果我想在程序内部找到 libc 中的 system 命令的内存地址,我可以在 gdb 中轻松找到它,但是如果我不想将程序带入调试器怎么办? 这个地址会在每次运行时改变吗?是否有其他静态分析工具可以让我查看库或函数在运行时加载到此程序的内存空间的位置? 我想在程序外部获取这些信息(例如使用类似于 objdump 的实用程序来收集信息)。

然后还有prelink,它会显著改变顺序。 - Ben Voigt
5个回答

83

库被ld.so(动态链接器或运行时链接器,Linux中的ld-linux.so.2ld-linux.so.*;glibc的一部分)加载。所有动态链接的ELF二进制文件都将其声明为“解释器”(INTERP;.interp节)。因此,当您启动程序时,Linux将启动一个ld.so(加载到内存并跳转到其入口点),然后ld.so将程序加载到内存中,准备并运行它。您也可以使用以下命令启动动态程序

 /lib/ld-linux.so.2 ./your_program your_prog_params

ld.so会实际打开和映射所有所需的ELF文件,包括您的程序和所有所需库的ELF文件。此外,它还填充GOT和PLT表并进行重定位解析(在许多情况下使用间接调用将库中函数的地址写入调用站点)。

通过ldd实用程序,可以获得某个库的典型加载地址。它实际上是一个bash脚本,该脚本设置了ld.so的调试环境变量(实际上是glibc的rtld的LD_TRACE_LOADED_OBJECTS=1),然后启动程序。您甚至可以自己完成此操作,无需使用脚本,例如使用bash轻松更改单个运行的环境变量:

 LD_TRACE_LOADED_OBJECTS=1 /bin/echo

ld.so将看到这个变量,并解析所有需要的库并打印它们的加载地址。但是,如果设置了此变量,ld.so实际上不会启动程序(对于程序或库的静态构造函数不确定)。如果ASLR特性被禁用,则大多数时候加载地址将相同。现代Linux通常启用ASLR,因此要禁用它,请使用echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

您可以使用binutils中的nm实用程序在libc.so内找到system函数的偏移量。我认为,您应该使用nm -D /lib/libc.soobjdump -T /lib/libc.so并搜索输出。


2
非常棒的信息,谢谢。您知道有哪些好的文章可以解释这个过程是如何工作的(生成GOT / PLT表),或者通过谷歌搜索是否足够? - Ryan
也许,阅读关于链接器的书籍会有所帮助?http://www.iecc.com/linker/linker10.html 此外,还有 gold 链接器和 binutils ld 链接器 ELF 部分作者的博客 www.airs.com/blog/archives/41 - osgx
1
这非常有启发性!我之前对于ld.so和ldd并不了解那么多! - Matt Joiner
4
这个答案有误,因为ld.so并不会加载主程序,而是由内核来加载。内核也不会查看任何节(这些节可以完全被剥离),它会在PT_INTERP段中找到解释器。 - Employed Russian
@EmployedRussian,您能发表更多正确的答案吗?我不是专家,而您是。 - osgx
显示剩余3条评论

13

"直接找到消息源头问马..."

Drepper - 如何编写共享库

对于Linux库的编写者来说,这是必读的文档。它详细地解释了加载机制。


10

如果您只想获取函数的地址而不是硬编码名称,可以使用dlopen()加载主程序:

void *self = dlopen(NULL, RTLD_NOW);
dlsym(self, "system"); // returns the pointer to the system() function

如果您只想在编译时知道函数的名称并获取其地址,可以简单地使用void *addr = &system;

请注意,此处保留了HTML标记。

请参见OP中的编辑,但一定要保留此答案,因为它对于另一种可能含糊的标题变体是有帮助的。 - Ryan
1
可能两者都不能满足OP的需求,因为“system”可能会被解析为主程序映像中的PLT条目,该条目执行实际跳转到共享库。 - R.. GitHub STOP HELPING ICE

10

nm 命令用于 libc.so,可以显示 libc.sosystem 符号的位置。然而,如果启用了 ASLR,每次运行程序时,libc.so 加载的地址以及最终的 system 地址都会随机变化。即使没有启用 ASLR,您也需要确定 libc.so 加载的地址并将 system 的地址偏移该数量。


这基本上就是我想要的 - 我假设确定libc.so中系统偏移量的最佳方法是再次使用带有调试符号安装的nm?还是有更简单/更强大的方法来做到这一点。 - Ryan
@Ryan,nm不需要调试符号,它可以直接读取符号表(ld.so使用的)。 - osgx
@osgx那么,我的原始问题减去调试符号是正确的吗? - Ryan

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接