符号解析与动态链接相关技术问题咨询
嘿,很高兴你在钻研动态链接的细节——这部分确实有点绕,但理解清楚后对底层运行机制会豁然开朗。我来逐个解答你的问题,顺便纠正一些可能的小误解:
问题1:共享对象何时被加载到内存?
你提到的动态链接器“将共享对象的内存段添加到进程镜像”,这里要区分虚拟内存映射和物理内存加载两个不同的步骤:
- 虚拟内存映射阶段:当
LD_BIND_NOW非空时,动态链接器会递归解析所有依赖的共享对象(包括可执行文件直接依赖的,以及这些共享对象自身依赖的库),并为每个共享对象的段(比如.text、.data等)在进程的虚拟地址空间中建立映射。这一步就是“添加到进程镜像”的含义——此时这些共享对象的虚拟地址已经被分配,但物理内存并没有被实际加载。 - 物理内存加载阶段:默认情况下,只有当进程访问到某个虚拟页时,操作系统会触发页错误,才会把对应的物理页从磁盘加载到内存中。不过如果设置了
LD_BIND_NOW,动态链接器在做预重定位的时候,可能会触发部分页加载,但核心逻辑还是按需加载物理内存。
简单说:LD_BIND_NOW会让动态链接器提前完成所有共享对象的虚拟地址映射和重定位计算,但物理内存依然是按需加载的;而且递归依赖的库会被全部解析并映射,不会留到后续再处理。
问题2:动态链接器修补GOT条目的具体流程
你的PLT/GOT跳转流程理解基本是对的,我来补充动态链接器被调用后的详细操作:
当动态链接器通过PLT存根被调用时,它会从栈中获取两个关键信息:
- 对应符号的
Elf_Rel结构的偏移量:这个偏移量指向当前共享对象重定位表中的一个条目,里面记录了需要修补的GOT地址、符号索引等信息。 - 指向
link_map结构的指针:link_map是动态链接器用来管理每个加载的共享对象的元数据结构,包含了该对象的符号表、重定位表、虚拟地址基址等关键信息。
动态链接器的具体操作步骤:
- 第一步:通过
link_map找到当前共享对象的重定位表,用栈中的偏移量定位到对应的Elf_Rel条目。 - 第二步:从
Elf_Rel条目中取出符号索引,然后在该共享对象的符号表(.dynsym)中找到对应的符号信息。 - 第三步:按照动态链接器的符号搜索顺序,查找该符号的真实内存地址:
- 搜索顺序优先级大致为:可执行文件自身的符号 → 已加载的共享对象(按依赖加载顺序)→ 可执行文件的
DT_RPATH(若未被DT_RUNPATH覆盖)→ 环境变量LD_LIBRARY_PATH→ 可执行文件的DT_RUNPATH→ 系统缓存/etc/ld.so.cache→ 系统默认库目录(如/lib、/usr/lib)。另外还遵循“全局符号介入”规则——如果多个库有同名符号,先加载的库的符号会被优先使用。
- 搜索顺序优先级大致为:可执行文件自身的符号 → 已加载的共享对象(按依赖加载顺序)→ 可执行文件的
- 第四步:将找到的符号真实地址,写入
Elf_Rel条目指定的GOT位置。 - 第五步:返回PLT,此时PLT会直接跳转到GOT中刚写入的真实地址,后续调用就不需要再触发动态链接了。
学习资源推荐
- 《程序员的自我修养——链接、装载与库》:这本书把链接、装载、动态链接的底层机制讲得非常透彻,适合从入门到进阶学习。
- Linux手册页:
man ld.so(动态链接器的详细行为说明)、man ldd(查看可执行文件的依赖链)、man readelf(分析ELF文件的段、符号表等结构)、man objdump(反汇编查看PLT/GOT的汇编代码)。 - GNU Binutils文档:通过
info ld可以查看关于链接和动态链接的权威细节,涵盖了很多手册页没提到的底层逻辑。 - 动手实践:用
readelf -d <executable>查看可执行文件的依赖信息,objdump -R <executable>查看重定位条目,objdump -d <executable>拆解PLT和GOT的汇编逻辑,能帮你快速把理论和实际结构对应起来。
内容的提问来源于stack exchange,提问作者ray an
相关产品推荐
相关产品推荐

