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

Linux 下用 /proc/self/maps 解析 SO 基地址最可靠
Linux 没有标准 API 直接枚举已加载的共享库基地址,/proc/self/maps 是唯一稳定、无需额外权限、且内核保证一致性的来源。它按内存映射顺序列出所有段,其中类型为 r-xp 且路径含 .so 的行,其首列十六进制地址就是该 SO 的加载基址。
实操注意点:
- 必须逐行解析,不能只找
.so字符串——有些库路径可能不含点(如libc),或带版本号后缀(libstdc++.so.6.0.30) - 同一 SO 可能出现多次(如
.text和.data分属不同映射),只取r-xp(可执行)那行的起始地址 - 路径字段可能为空(如 vdso、vvar),跳过无路径的行
- 用
std::ifstream读取时建议加std::ios::binary避免换行符处理异常
示例关键逻辑:
// 伪代码:读取 maps 并提取基址
std::ifstream maps("/proc/self/maps");
std::string line;
while (std::getline(maps, line)) {
unsigned long start, end;
char perms[5], path[256];
if (sscanf(line.c_str(), "%lx-%lx %4s %*x %*x:%*x %*d %255s",
&start, &end, perms, path) == 4 &&
std::string(perms) == "r-xp" &&
std::string(path).find(".so") != std::string::npos) {
// start 即为该 SO 基地址
}
}
Windows 下靠 EnumProcessModules + GetModuleInformation
Windows 没有类似 /proc 的文件接口,必须调用 PSAPI。核心是先用 EnumProcessModules 获取所有模块句柄列表,再对每个句柄调 GetModuleInformation 得到 lpBaseOfDll 字段——这就是 DLL 加载基址。
立即学习“C++免费学习笔记(深入)”;
容易踩的坑:
- 必须链接
psapi.lib,且在 Windows 7+ 运行时需确保启用SE_DEBUG_NAME权限(仅调试器进程需要;普通进程调用默认可用) -
EnumProcessModules返回的模块顺序不固定,且包含主 EXE(GetModuleHandle(NULL)对应的那个),需用GetModuleFileName判断是否为 DLL - 某些注入 DLL 或延迟加载模块可能不在初始列表中,需定期轮询或结合
LoadLibrary钩子捕获 -
MODULEINFO中的SizeOfImage是整个镜像大小,不是 .text 段长度
跨平台封装要注意符号可见性与运行时开销
如果写通用工具函数,别试图用宏统一两套逻辑。Linux 版本应直接读 /proc/self/maps,Windows 版本走 PSAPI,二者实现完全隔离。强行抽象成一个 get_loaded_modules() 接口会掩盖底层差异。
性能与安全提醒:
-
/proc/self/maps每次读取都是内核态拷贝,频繁调用(如每帧)会导致明显开销;建议缓存并配合inotify监听/proc/self/maps变更(但实际极少变动) - Windows 下
EnumProcessModules在多线程环境下非原子,若其他线程正LoadLibrary/FreeLibrary,可能返回部分结果或ERROR_PARTIAL_COPY,需重试 - 返回的基地址是虚拟地址,不能直接用于跨进程操作;如需符号解析,得配合
dladdr(Linux)或SymFromAddr(Windows)
不要依赖 dlopen 管理的 handle 列表
dlopen 返回的 void* handle 是内部管理结构指针,并非模块基地址,也不能反向查出已加载的所有 handle。glibc 内部用哈希表维护,但无公开 API 导出该表。试图通过 RTLD_NOLOAD | RTLD_GLOBAL 重复打开来“探测”已加载库,既不可靠(可能触发初始化副作用),又无法获取基址。
常见误判场景:
- 用
dladdr查某个函数地址反推所在 SO 基址——这只能查到该符号所在的那个模块,漏掉没导出符号的库(如静态链接进来的libgcc) - 认为
_DYNAMIC符号或link_map链表可遍历——虽然 glibc 确实维护该链表,但它属于 ABI 内部细节,不同 GLIBC 版本结构偏移不同,且 Android Bionic 完全不提供 - Windows 下尝试解析 PE 头里的 ImageBase 字段——那是编译时建议地址,实际加载地址由 ASLR 决定,必须运行时查询
真正稳定的方案只有两个:Linux 读 /proc/self/maps,Windows 调 PSAPI。其它任何绕过它们的尝试,迟早会在某个发行版或系统更新后失效。


















