Linux下获取可执行文件基地址最可靠方式是解析/proc/self/maps:逐行读取,找到首字段为r-xp权限且路径非匿名(不含[)的行,取其地址范围起始值(-前十六进制数)即为ASLR后的实际加载基址。

Linux 下用 /proc/self/maps 解析可执行文件基地址
Linux 没有标准 API 直接返回当前进程的可执行文件加载基址,但 /proc/self/maps 是最可靠、无需特权的途径。它列出所有内存映射段,其中第一行通常对应可执行文件的代码段(r-xp 权限),起始地址就是基地址。
常见错误是直接读取 /proc/self/exe 的符号链接——它只给出磁盘路径,不反映 ASLR 实际加载位置;也有人试图用 &main 减去编译时的 main 地址,但该偏移在 PIE 可执行文件中不可靠,且无法处理动态链接器插入的间隙。
- 逐行读取
/proc/self/maps,匹配以r-xp结尾且不含[(即非匿名映射或内核映射)的行 - 提取每行首字段(形如
555555554000-555555558000),取-前的十六进制值,转为uintptr_t - 注意:某些构建方式(如非 PIE)可能使基地址固定为
0x400000,但不能硬编码——必须运行时解析
Windows 上用 GetModuleHandle(NULL) 获取基址
Windows 提供明确的 API:GetModuleHandle(NULL) 返回当前进程主模块(即可执行文件)的 HMODULE,其值就是加载基地址。这是唯一推荐方式,稳定且兼容所有 Windows 版本(包括 WoW64)。
容易踩的坑是误用 GetModuleHandleA("") 或传入空字符串——行为未定义;也有人尝试用 GetModuleInformation,但它需要额外 handle 且不比直接 cast 更安全。
立即学习“C++免费学习笔记(深入)”;
-
HMODULE base = GetModuleHandle(NULL);返回值可直接转为uintptr_t或void* - 若需验证是否有效,检查返回值是否非 NULL(失败仅发生在极特殊异常状态)
- 注意:此地址是 PE 映像的 ImageBase,与实际加载地址一致(即使启用 ASLR,系统已修正重定位)
macOS 需结合 _dyld_get_image_header 和镜像遍历
macOS 没有等价于 GetModuleHandle(NULL) 的单点 API,必须遍历已加载的 dyld 镜像,找到主可执行文件对应的镜像头,再提取其加载地址。核心函数是 _dyld_get_image_header 和 _dyld_get_image_count。
常见错误是假设索引 0 总是主程序——虽然多数情况下成立,但 dyld 初始化顺序不保证;更稳妥的方式是用 _dyld_get_image_name(i) 匹配可执行文件名或判断 LC_MAIN 加载命令是否存在。
- 循环
i从0到_dyld_get_image_count()-1,调用_dyld_get_image_header(i) - 对每个 header,用
mach_header_t->filetype == MH_EXECUTE筛选主可执行镜像 - 基地址 =
(uintptr_t)_dyld_get_image_header(i)(Mach-O header 就位于加载起始处)
跨平台封装要注意符号可见性与链接模型
如果写通用函数,不能简单 #ifdef 堆砌逻辑——关键在于避免 ODR 违规和链接器干扰。例如在 Linux 下引用 /proc 文件需确保不被 strip 掉;Windows 下调用 GetModuleHandle 需链接 kernel32.lib(隐式默认);macOS 下 _dyld_* 函数声明在 <mach-o/dyld.h> 中,但需加 __attribute__((weak_import)) 防止旧系统链接失败。
最容易被忽略的是 PIE 与非 PIE 构建差异:Linux 下非 PIE 可执行文件基地址恒为 0x400000,但程序无法提前知道自身是否 PIE,所以运行时解析仍是唯一健壮方案。macOS 所有可执行文件默认 PIE,Windows 自 Vista 起也强制 ASLR,因此硬编码任何地址都不可靠。


















