Linux下如何获取dlopen加载的ELF共享库的基地址
实现方案
1. 从dlopen句柄获取ELF共享库基地址
dlopen()返回的void*指针本质就是struct link_map结构体的地址,该结构体定义在<link.h>头文件中,其中l_addr成员就是共享库的加载基地址。
示例代码:
#include <stdio.h> #include <dlfcn.h> #include <link.h> #include <elf.h> #include <string.h> #include <sys/mman.h> #include <unistd.h> #include <stdint.h> #include <stdlib.h> int main() { // 加载目标共享库 void* handle = dlopen("./libgame_headless.so", RTLD_NOW); if (!handle) { fprintf(stderr, "dlopen failed: %s\n", dlerror()); return 1; } // 转换为link_map结构体指针获取基地址 struct link_map* lib_map = (struct link_map*)handle; ElfW(Addr) lib_base = lib_map->l_addr;
2. 解析ELF结构定位.data和.bss段
共享库加载到内存后,基地址起始位置就是ELF文件头,我们可以通过遍历ELF段表找到目标段的虚拟地址和大小:
// 解析ELF头 ElfW(Ehdr)* ehdr = (ElfW(Ehdr)*)lib_base; // 段表地址 ElfW(Shdr)* shdr_table = (ElfW(Shdr)*)(lib_base + ehdr->e_shoff); // 段名字符串表地址 const char* sh_strtab = (const char*)(lib_base + shdr_table[ehdr->e_shstrndx].sh_offset); // 遍历段表找.data和.bss void *data_addr = NULL, *bss_addr = NULL; size_t data_size = 0, bss_size = 0; for (int i = 0; i < ehdr->e_shnum; i++) { const char* sec_name = sh_strtab + shdr_table[i].sh_name; if (strcmp(sec_name, ".data") == 0) { data_addr = (void*)(lib_base + shdr_table[i].sh_addr); data_size = shdr_table[i].sh_size; } else if (strcmp(sec_name, ".bss") == 0) { bss_addr = (void*)(lib_base + shdr_table[i].sh_addr); bss_size = shdr_table[i].sh_size; } } if (!data_addr || !bss_addr) { fprintf(stderr, "failed to find .data or .bss section\n"); dlclose(handle); return 1; }
3. 状态保存与恢复
你可以预先申请足够大的缓冲区存储段数据,恢复时直接写回即可。如果写入时遇到段错误,需要先调用mprotect修改对应内存页的读写权限:
// 示例:保存状态 void* data_backup = malloc(data_size); void* bss_backup = malloc(bss_size); memcpy(data_backup, data_addr, data_size); memcpy(bss_backup, bss_addr, bss_size); // 示例:恢复状态,先获取内存页对齐的地址改权限 long page_size = sysconf(_SC_PAGESIZE); void* data_page = (void*)((uintptr_t)data_addr & ~(page_size - 1)); void* bss_page = (void*)((uintptr_t)bss_addr & ~(page_size - 1)); // 改读写权限 mprotect(data_page, data_size + ((uintptr_t)data_addr - (uintptr_t)data_page), PROT_READ | PROT_WRITE); mprotect(bss_page, bss_size + ((uintptr_t)bss_addr - (uintptr_t)bss_page), PROT_READ | PROT_WRITE); // 写回数据 memcpy(data_addr, data_backup, data_size); memcpy(bss_addr, bss_backup, bss_size); // 可选:恢复原有权限,这里省略原有权限获取逻辑 // 释放资源 free(data_backup); free(bss_backup); dlclose(handle); return 0; }
编译注意事项
编译代码时需要链接dl库,编译命令示例:gcc your_code.c -o state_tool -ldl
额外注意点
- 该方案仅能保存静态分配在
.data和.bss段内的变量值,如果共享库内的静态变量是指针,指向堆、栈或其他动态申请的内存,对应指向的内容不会被本方案保存。 - 不要在共享库的构造/析构函数执行期间执行读写段的操作,避免出现数据不一致问题。
内容的提问来源于stack exchange,提问作者itzjackyscode
相关产品推荐
相关产品推荐

