最可靠方式是读取/proc/self/maps解析.so基址:逐行匹配库名,提取r-xp权限段的十六进制起始地址;Windows则用EnumProcessModules+GetModuleFileName获取HMODULE即基址。

Linux 下用 /proc/self/maps 解析 .so 基址最可靠
Linux 没有标准 API 直接返回已加载共享库的基址,dlopen 返回的句柄也不是基址,而是内部结构指针。真正稳定的方式是读取 /proc/self/maps,它由内核实时维护,每行包含内存段起始地址、权限、偏移、设备号、inode 和映射文件路径。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 逐行读取
/proc/self/maps,匹配目标库名(如"libfoo.so"),注意路径可能为绝对路径或带(deleted)后缀 - 取每行首字段(形如
7f8b2c000000-7f8b2c001000),用sscanf解析十六进制起始地址(%lx) - 优先匹配
r-xp权限段(代码段),避免误取数据段或 debug 符号段 - 注意:ASLR 开启时基址每次不同;若库被
dlmopen加载到独立命名空间,则不会出现在默认 maps 中
Windows 下用 EnumProcessModules + GetModuleFileName
Windows 提供了明确的模块枚举接口,但需注意:默认只枚举当前进程的模块,且 HMODULE 本身即为模块基址(PE 头所在地址),无需额外计算。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 先调用
GetCurrentProcess()获取句柄,再用EnumProcessModules获取所有模块句柄数组 - 对每个
HMODULE调用GetModuleFileName,比对文件名(注意大小写和路径格式,推荐用_stricmp) -
HMODULE就是基址,可直接转为uintptr_t使用;不要误用GetModuleHandle——它只查已知名称,不保证返回当前加载实例 - 若目标 DLL 是延迟加载或由第三方 loader 注入,可能不在初始枚举结果中,需在关键时机重复枚举
C++ 跨平台封装要注意符号可见性与解析时机
想写个通用函数?别直接封装成“一键获取”,因为 Linux 和 Windows 的语义差异太大:前者返回的是 mmap 起始地址(即 ELF 加载基址),后者返回的是 PE 映像基址(含 DOS/PE 头)。两者都不能直接当“代码入口”用。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 避免在
main()之前调用——Linux 下/proc/self/maps可能尚未完整生成;Windows 下某些 DLL 可能还未完成重定位 - 若需获取某个符号的实际地址(比如函数指针),应先用
dlsym/GetProcAddress,而不是靠基址加偏移手动算——重定位、PLT/GOT、IAT 都会让偏移不可靠 - 静态链接的
.a或.lib不会出现在模块列表里,这类库的符号地址只能通过&symbol取得,不能混为一谈 - 调试版二进制常含调试符号段,可能让同一库在 maps 中出现多次(不同权限段),务必按文件路径 + r-xp 组合去重
常见错误:把 dladdr 当作基址获取工具
dladdr 看起来很诱人——传入任意函数指针就能返回 Dl_info 结构,其中 dli_fbase 字段常被误认为“库基址”。但它实际返回的是该符号所在加载单元的基址,而一个 .so 可能被 mmap 多次(比如 RTLD_DEEPBIND 场景),dli_fbase 只对应那个符号被解析时所在的映射实例。
典型翻车场景:
- 主程序调用
libA.so中函数,dladdr返回 A 的基址;但 A 内部又dlopen("libB.so", RTLD_GLOBAL),此时 B 的基址无法用 A 的dladdr推导 - 同一个 .so 被
dlopen两次(不同 flag),dladdr对两个句柄里的同名函数可能返回不同dli_fbase - 使用
LD_PRELOAD注入的库,其符号经 PLT 调用后,dladdr返回的可能是 PLT 项地址而非真实库基址
真要靠 dladdr,至少得配合 dli_fname 校验路径,并接受它只反映“当前符号解析路径”而非全局加载视图。



















