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

静态ELF下无需-rdynamic,实现类似dlsym的函数地址查找

当然可以!无需-rdynamic,手动解析ELF就能定位函数地址

你完全可以跳过dlopen/dlsym和-rdynamic限制,通过手动解析当前进程的ELF内存镜像,从静态符号表(.symtab)里找到目标函数的内存地址——毕竟你已经用nm确认符号还存在于二进制中了,这就足够了。

核心原理

nm能看到符号,说明你的二进制是未stripped的,里面保留了静态符号表(.symtab)和对应的字符串表(.strtab)。程序加载到内存后,ELF的整个镜像会被映射到进程地址空间里,我们只需要:

  1. 找到ELF镜像在内存中的加载基址
  2. 解析ELF头部,定位到.symtab和.strtab的位置
  3. 遍历符号表,匹配目标函数名,计算其实际内存地址

实现示例(64位Linux)

下面是一个直接在程序内部解析自身ELF的代码,功能和你要的dlopen+dlsym类似:

#include <stdio.h>
#include <elf.h>
#include <stdlib.h>
#include <string.h>

// GCC提供的全局变量,指向当前程序的ELF加载基址
extern char __executable_start[];

void *find_symbol(const char *target_name) {
    // 指向ELF文件头
    Elf64_Ehdr *elf_header = (Elf64_Ehdr *)__executable_start;
    // 指向段表(Section Header Table)
    Elf64_Shdr *section_headers = (Elf64_Shdr *)(__executable_start + elf_header->e_shoff);

    const char *string_table = NULL;
    Elf64_Shdr *symbol_table_header = NULL;

    // 遍历段表,找到字符串表和符号表
    for (int i = 0; i < elf_header->e_shnum; i++) {
        Elf64_Shdr *curr_shdr = &section_headers[i];
        // 定位字符串表(.strtab):存储符号名称的字符串
        if (curr_shdr->sh_type == SHT_STRTAB) {
            string_table = __executable_start + curr_shdr->sh_offset;
        }
        // 定位符号表(.symtab):存储符号的元数据(地址、类型等)
        if (curr_shdr->sh_type == SHT_SYMTAB) {
            symbol_table_header = curr_shdr;
        }
    }

    if (!string_table || !symbol_table_header) {
        fprintf(stderr, "Failed to locate symbol table or string table\n");
        return NULL;
    }

    // 遍历符号表项
    Elf64_Sym *symbols = (Elf64_Sym *)(__executable_start + symbol_table_header->sh_offset);
    int symbol_count = symbol_table_header->sh_size / sizeof(Elf64_Sym);

    for (int i = 0; i < symbol_count; i++) {
        Elf64_Sym *curr_sym = &symbols[i];
        // 只找函数类型的符号(STT_FUNC)
        if ((curr_sym->st_info & 0xF) == STT_FUNC) {
            const char *sym_name = string_table + curr_sym->st_name;
            if (strcmp(sym_name, target_name) == 0) {
                // 计算实际内存地址:加载基址 + 符号的虚拟地址偏移
                // 兼容PIE和非PIE程序
                return (void *)(__executable_start + curr_sym->st_value);
            }
        }
    }

    fprintf(stderr, "Symbol '%s' not found\n", target_name);
    return NULL;
}

// 测试用的目标函数
void myfunc() {
    printf("Successfully called myfunc from resolved address!\n");
}

int main() {
    void (*func_ptr)() = find_symbol("myfunc");
    if (func_ptr) {
        func_ptr();
    }
    return 0;
}

关键注意事项

  • 仅适用于未stripped的二进制:如果用strip命令去掉了符号表,.symtab会被删除,这个方法就失效了——这也是你能用nm看到符号的前提。
  • 兼容PIE和非PIE程序:__executable_start会自动指向程序加载后的实际基址,不管是否开启地址随机化,所以计算出来的地址是正确的。
  • 无需-rdynamic:我们直接读取静态符号表,不需要依赖动态链接器导出符号,所以完全不需要这个编译选项。
  • 32位程序适配:如果是32位系统,只需要把代码中的Elf64_*替换成Elf32_*,其他逻辑一致。

为什么nm能看到符号?

nm默认读取的是静态符号表(.symtab),这个表在编译时默认生成(除非手动strip)。而-rdynamic的作用是把符号添加到动态符号表(.dynsym),供动态链接器(比如ld-linux.so)使用,和静态符号表是两个独立的结构——我们的方法就是绕开动态链接器,直接用静态符号表实现符号查找。

内容的提问来源于stack exchange,提问作者hnh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:50:31