You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 22:39:03