如何获取ELF中全局变量的存储地址范围(dlopen场景)
问题分析与解决方案
核心原因
你遇到的问题和-fPIC确实有关,但并非“未禁用重定位”的问题——dlopen加载的共享模块必须以-fPIC编译,否则无法在不同地址空间完成重定位。而__data_start和_end仅对应ELF的.data段起止,无法覆盖所有全局变量的存储段:
- 只读全局变量(如
const修饰的变量)会被放在.data.rel.ro段 - 未初始化/零初始化的全局变量会被放在
.bss段 - 线程局部存储变量会被放在
.tbss/.tdata段
这些段的地址都不在__data_start到_end的范围内,所以你会看到部分全局变量(比如数组)超出这个区间。
获取所有全局变量地址范围的方法
方法1:遍历ELF程序段表(最可靠)
通过dlopen的句柄获取模块的ELF头,遍历程序段表(Program Header Table),筛选出所有数据类的加载段,计算每个段的真实地址范围:
#include <dlfcn.h> #include <elf.h> #include <stdio.h> #include <stdint.h> void print_global_var_ranges(void *module_handle) { Dl_info dl_info; if (!dladdr(module_handle, &dl_info)) { fprintf(stderr, "dladdr error: %s\n", dlerror()); return; } // 指向ELF文件头 Elf64_Ehdr *elf_hdr = (Elf64_Ehdr *)dl_info.dli_fbase; // 指向程序段表起始 Elf64_Phdr *prog_hdr = (Elf64_Phdr *)((char *)elf_hdr + elf_hdr->e_phoff); for (int i = 0; i < elf_hdr->e_phnum; i++) { Elf64_Phdr *ph = &prog_hdr[i]; // 筛选加载类型的段,且是数据段(可读/可写) if (ph->p_type == PT_LOAD && (ph->p_flags & (PF_R | PF_W))) { uintptr_t seg_start = (uintptr_t)dl_info.dli_fbase + ph->p_vaddr; uintptr_t seg_end = seg_start + ph->p_memsz; printf("Data segment range: 0x%lx - 0x%lx\n", seg_start, seg_end); } } }
调用时传入dlopen返回的句柄即可,这个方法能覆盖所有存储全局变量的段。
方法2:自定义段(精准控制)
如果只需要追踪特定全局变量,可以把它们放到自定义段中,通过编译器生成的起止符号获取范围:
// 把变量放到自定义段 __attribute__((section("my_globals"))) int global_var1 = 10; __attribute__((section("my_globals"))) char global_arr[1024] = {0}; // 编译器会自动生成这两个符号 extern char __start_my_globals[]; extern char __stop_my_globals[]; void print_custom_segment_range() { uintptr_t start = (uintptr_t)__start_my_globals; uintptr_t end = (uintptr_t)__stop_my_globals; printf("Custom global range: 0x%lx - 0x%lx\n", start, end); }
编译时不需要额外参数,编译器会自动处理自定义段的起止符号。
方法3:结合nm工具预分析
先用nm -D <your_module.so>列出所有全局变量的符号和偏移,再在代码中通过dlsym获取变量的真实地址,对比偏移判断是否在目标范围内。但这个方法需要提前知道符号名,灵活性较差。
关键注意事项
- 不要尝试禁用
-fPIC:dlopen加载的共享库必须是位置无关代码,否则在不同进程中加载时会触发重定位错误。 - 线程局部存储(TLS)变量需要单独处理:TLS段的地址在每个线程中不同,需要用
pthread_getspecific或__thread相关的API获取。
内容的提问来源于stack exchange,提问作者Deep Fried Camembert
相关产品推荐
相关产品推荐

