ELF的PLT/GOT机制下,共享库导入函数地址获取及一致性的疑问
关于ELF中导入函数地址一致性的解析
你的核心困惑在于混淆了PLT的延迟绑定逻辑和直接获取函数地址时的立即解析行为,下面拆解背后的原理:
核心真相:你拿到的是GOT条目中的最终解析地址
当你通过函数指针获取导入函数地址时,返回的既不是PLT槽的地址,也不是未解析的占位地址——而是动态链接器已经解析完成的导出库中函数的真实内存地址,所有共享库的对应GOT条目最终都会被更新为这个地址,所以不同库中取到的地址自然相等。
为什么之前的理解有偏差?
- 你提到的“默认情况下导出地址只有调用后才解析”,指的是PLT触发的延迟绑定:当通过PLT调用函数时,第一次调用才会触发动态链接器去解析真实地址并写入GOT,后续调用直接跳转到GOT中的地址。
- 但直接获取函数地址的操作会绕过延迟绑定:动态链接器会立即执行重定位操作,直接将导出函数的真实地址写入当前模块的GOT条目,不需要等到第一次调用。
具体流程拆解
- 当程序加载时,每个共享库的GOT中对应导入函数的条目初始值是PLT槽的地址(用于延迟绑定的跳转入口)。
- 当你执行类似
void (*fp)() = &printf;这样的代码时,编译器会生成访问GOT条目的指令,动态链接器检测到这个访问(或在重定位阶段直接完成解析,取决于链接选项),立即去查找printf在libc.so中的真实地址。 - 动态链接器将找到的真实地址写入当前模块的GOT条目,同时其他共享库中对应的GOT条目也会被更新为同一个地址——因为全局符号解析的规则是:同一个符号在整个进程中只有一个唯一的定义。
- 所以无论从哪个共享库中获取该函数的地址,最终拿到的都是GOT条目中存储的真实地址,自然完全相等。
对应Windows IAT的情况
Windows的机制类似:
- 模块的IAT(导入地址表)初始存储的是导入函数的“桩函数”地址(类似ELF的PLT槽)。
- 当你直接获取导入函数地址时,系统会触发IAT的立即解析,将桩函数替换为DLL中导出函数的真实地址,所有模块的IAT条目最终指向同一个地址,因此不同模块中取到的函数地址一致。
内容的提问来源于stack exchange,提问作者Ofek Shilon
相关产品推荐
相关产品推荐

