如何获取运行中ELF二进制进程自身的堆段与栈段起始地址
错误原因分析
pidfd_open() 返回的是进程管理类文件描述符,仅用于进程信号发送、状态等待等进程控制操作,它并不指向磁盘上的ELF可执行文件。而libelf的elf_begin()接口要求传入的是符合ELF格式规范的普通文件描述符,二者类型完全不匹配,因此会触发无效描述符报错。
运行时堆、栈基地址获取方案
Linux环境下最可靠、兼容性最好的实现方式是解析/proc/self/maps文件,该文件由内核动态生成,记录了当前进程所有内存映射段的地址范围、权限、归属路径等信息,堆段标记为[heap],主线程栈段标记为[stack]。
示例实现代码如下:
#include <stdio.h> #include <stdint.h> #include <string.h> #include <errno.h> /** * 获取当前进程堆、主线程栈的地址边界 * @param heap_start 输出参数,堆段起始地址 * @param heap_end 输出参数,堆段结束地址 * @param stack_start 输出参数,栈段起始地址(低地址) * @param stack_end 输出参数,栈段结束地址(高地址,即栈基地址) * @return 0成功,-1失败 */ int get_runtime_segment_bounds(uintptr_t *heap_start, uintptr_t *heap_end, uintptr_t *stack_start, uintptr_t *stack_end) { if (!heap_start || !heap_end || !stack_start || !stack_end) { errno = EINVAL; return -1; } *heap_start = *heap_end = *stack_start = *stack_end = 0; FILE *maps_fp = fopen("/proc/self/maps", "r"); if (!maps_fp) { return -1; } char line_buf[512], perm_buf[32], path_buf[256]; uintptr_t seg_start, seg_end; while (fgets(line_buf, sizeof(line_buf), maps_fp)) { if (sscanf(line_buf, "%lx-%lx %s %*x %*x:%*x %*u %255s", &seg_start, &seg_end, perm_buf, path_buf) < 4) { continue; } if (strcmp(path_buf, "[heap]") == 0) { *heap_start = seg_start; *heap_end = seg_end; } else if (strcmp(path_buf, "[stack]") == 0) { *stack_start = seg_start; *stack_end = seg_end; } } fclose(maps_fp); return 0; }
注意事项
- Linux栈是从高地址向低地址增长的,因此栈的基地址是上述代码输出的
stack_end,而非stack_start。 - 多线程场景下,
[stack]仅对应主线程的栈,子线程的栈由pthread库通过mmap分配,无固定标记,需要在线程创建时主动记录地址。 - 如果进程从未申请过堆内存,
[heap]段可能不存在,对应输出参数会保持为0,符合实际运行状态。
替代方案说明
堆地址获取
你尝试的sbrk(0)方案也可以使用,该接口返回当前堆的program break(堆顶边界)。如果要获取堆的初始基地址,只需在程序启动后、第一次调用malloc前执行sbrk(0)保存结果即可。该方案的局限是仅对glibc默认malloc实现有效,如果使用了自定义分配器、大内存mmap分配等场景,堆可能存在多个不连续映射,sbrk返回结果会不准确。
栈地址获取
也可以在程序入口处取局部变量的地址,再按内存页大小对齐得到近似的栈使用边界,但该方法无法获取完整的栈段边界,仅可做测试参考,不建议正式环境使用。
内容的提问来源于stack exchange,提问作者clive
相关产品推荐
相关产品推荐

