linux下解析动态库基地址最可靠的方式是读取/proc/self/maps,筛选r-xp权限且路径含.so的行,首列十六进制值即为基址;windows需用enumprocessmodules配合getmodulefilenameex;macos则依赖_dyld_image_count与_dyld_get_image_header。

Linux 下用 /proc/self/maps 解析动态库基地址最可靠
Linux 没有标准 C++ API 能直接列出已加载的动态库及其基址,/proc/self/maps 是唯一稳定、无需额外权限、且内核保证一致性的来源。它按内存映射顺序输出每段区域的起始地址、权限、偏移、设备号、inode 和映射文件路径。
关键点在于:只取 r-xp 权限、非匿名(即路径非 [anon] 或 [stack])、且路径以 .so 结尾的行;首列就是该库在当前进程地址空间的加载基址(十六进制)。
示例解析逻辑:
while (std::getline(file, line)) {
std::istringstream iss(line);
std::string start, perms, offset, dev, inode, path;
iss >> start >> perms >> offset >> dev >> inode >> path;
if (perms.substr(0, 4) == "r-xp" && !path.empty() && path.find(".so") != std::string::npos) {
void* base = reinterpret_cast<void>(std::stoul(start.substr(0, start.find('-')), nullptr, 16));
// base 即为该 .so 的加载基地址
}
}
</void>
Windows 下必须用 EnumProcessModules + GetModuleFileNameEx
Windows 不提供类似 /proc 的文本接口,必须调用 PSAPI 函数。注意:EnumProcessModules 返回的是模块句柄(即基地址),但需配合 GetModuleFileNameEx 获取路径来过滤出真正的动态库(DLL),因为该函数也会返回 EXE 主模块和系统 DLL(如 ntdll.dll)。
常见错误是忽略 GetModuleInformation 验证模块大小,或未检查 GetModuleFileNameEx 返回值是否为 0(失败时返回空路径)。
实操要点:
-
EnumProcessModules第一次调用需传入NULL缓冲区获取所需大小,否则容易读越界 - 只保留路径中含
.dll且不等于主程序名的模块 - 模块句柄本身就是基地址,无需再调用
GetModuleBaseAddress类似函数
macOS 上靠 _dyld_image_count 和 _dyld_get_image_header
macOS 使用 Mach-O 加载器,公开 API 明确支持枚举所有已加载镜像。_dyld_image_count() 返回总数,_dyld_get_image_header(i) 返回第 i 个镜像的 mach_header* 指针——这个指针值就是该镜像在进程内的实际加载基地址。
但要注意:_dyld_get_image_name(i) 才返回路径字符串,需手动判断是否为动态库(路径含 .dylib 或不含扩展名但位于 /usr/lib/ 等典型位置);仅靠 _dyld_get_image_header 无法区分是可执行文件还是 dylib。
性能影响小,但该系列函数仅在主可执行文件或 dylib 中可用,不能在静态链接的二进制中使用(链接时会报 undefined symbol)。
跨平台封装要避开 dlopen/LoadLibrary 的陷阱
有人试图用 dlopen(nullptr, RTLD_NOW)(Linux)或 GetModuleHandle(nullptr)(Windows)获取“主模块”,但这只能拿到当前模块或主程序基址,完全无法枚举其他已加载的动态库。更危险的是:在 Windows 上调用 LoadLibrary 后不配对 FreeLibrary 会导致引用计数泄漏;在 macOS 上反复调用 dlopen 同一路径可能触发重复加载。
真正跨平台的方案只能是条件编译 + 各系统原生机制:
- Linux:坚持解析
/proc/self/maps - Windows:强制链接
psapi.lib,并检查GetModuleFileNameEx是否成功 - macOS:确保
CoreFoundation.framework或至少dlfcn.h可用,依赖_dyld_*系列函数
别试图用 backtrace 或符号表反推——那些地址属于代码段偏移,不是模块基址,且无调试信息时不可靠。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











