静态ELF下无需-rdynamic,实现类似dlsym的函数地址查找
当然可以!无需
-rdynamic,手动解析ELF就能定位函数地址 你完全可以跳过dlopen/dlsym和-rdynamic限制,通过手动解析当前进程的ELF内存镜像,从静态符号表(.symtab)里找到目标函数的内存地址——毕竟你已经用nm确认符号还存在于二进制中了,这就足够了。
核心原理
nm能看到符号,说明你的二进制是未stripped的,里面保留了静态符号表(.symtab)和对应的字符串表(.strtab)。程序加载到内存后,ELF的整个镜像会被映射到进程地址空间里,我们只需要:
- 找到ELF镜像在内存中的加载基址
- 解析ELF头部,定位到
.symtab和.strtab的位置 - 遍历符号表,匹配目标函数名,计算其实际内存地址
实现示例(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 = §ion_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
相关产品推荐
相关产品推荐

