如何在动态加载的.so共享库中获取目标进程及自身函数地址?
核心结论
你完全可以在动态加载的.so内部,拿到和主程序里&printNum完全等价的函数地址,根本不需要自己手动算.text段基址加函数偏移的流程。
标准实现方案
直接用动态链接器提供的dlsym接口查全局符号表就行,这是动态库场景下获取主程序函数地址的常规操作:
- 必要前置条件:编译主程序的时候要加
-rdynamic(对应的链接参数是-Wl,--export-dynamic),把printNum的符号导出到全局动态符号表。如果没加这个参数,主程序的函数符号不会暴露给动态链接器做运行时检索,dlsym根本找不到目标函数。 - 核心代码示例:
// 写在你加载的.so的代码里 void (*printNum_func)(void) = dlsym(RTLD_DEFAULT, "printNum"); // 校验返回值不为空后,printNum_func 存的地址和主程序里 &printNum 完全一致 if (printNum_func) { printNum_func(); // 直接调用就行,和主程序内部的调用效果没有任何区别 }
- 实现原理:传
RTLD_DEFAULT作为第一个参数时,dlsym会按照固定的搜索顺序,遍历主程序、所有带全局导出标记的已加载共享库的动态符号表,返回匹配到的符号在内存中的最终加载地址。这个地址已经由动态链接器完成了ASLR偏移调整、重定位修正,和主程序编译时生成的&printNum取址结果完全对齐,不存在计算偏差。
常见踩坑点
- 不推荐手动计算
.text段偏移:这种方案需要自己解析/proc/self/maps拿主程序的加载基址,还要处理段对齐、重定位修正等一堆细节,流程繁琐还容易因为PIE编译、ASLR随机化、段权限配置等问题算错地址,稳定性远不如直接用dlsym。 dlsym返回空的常见原因:- 主程序编译时没开
-rdynamic,目标符号没进入全局动态符号表 printNum是C++编译的函数,存在名称修饰规则,直接传字符串"printNum"匹配不到实际的修饰后符号名。这种情况要么用extern "C"把这个函数导出成C风格的无修饰符号,要么把修饰后的完整符号名传给dlsym做查询- 已经加载的其他共享库也导出了同名的
printNum符号,dlsym会返回搜索顺序中第一个匹配的地址,可能和你预期的主程序内的函数不一致
- 主程序编译时没开
无符号导出的兜底方案
如果主程序没法重新编译开符号导出,才需要考虑手动计算地址的方案:从主程序的可执行文件符号表中提取printNum相对于文件基址的偏移,结合/proc/self/maps里主程序的实际加载基址算出最终地址。这种方案只适用于改不动主程序的特殊场景,常规开发不推荐用。
内容的提问来源于stack exchange,提问作者ipolit__
相关产品推荐
相关产品推荐

