用libelf读取ELF符号表须先打开文件、调用elf_begin()、定位.symtab或.dynsym节区;修改符号需同步更新符号表、字符串表及节头,并严格按序调用gelf_update_sym()和elf_update()。

用 libelf 读取 ELF 符号表必须先正确初始化文件和节区
直接调用 elf_getscn() 或 elf_getsym() 前,libelf 要求你完成三步:打开 ELF 文件、调用 elf_begin() 获取句柄、定位到 .symtab(或 .dynsym)节区。漏掉任何一步都会返回 NULL,但不会报错,容易误判为“没有符号表”。
-
elf_begin()必须传入ELF_C_READ(读)或ELF_C_RDWR(读写),后者才能后续修改;传ELF_C_READ后再试图写会静默失败 - 节区名不是字符串比较安全的——要用
elf_strptr(elf, ehdr->e_shstrndx, shdr->sh_name)解析,不能直接strcmp(shdr_name, ".symtab") -
.symtab可能不存在(strip 过的二进制),此时需退回到.dynsym,但后者只含动态链接符号,数量少、无局部符号
修改符号表前必须重分配节区空间并更新头部字段
ELF 符号表是固定大小的数组,每个条目由 Elf64_Sym 结构定义(通常 24 字节)。想增删符号,不能只改内存里的符号数组,必须:
- 用
gelf_update_sym()写回单个符号时,仅影响已加载的内存副本;真正写入文件要调用elf_update(elf, ELF_C_WRITE) - 新增符号必须确保
.symtab节区有足够的空间——需调用gelf_getshdr()获取当前节头,手动增大sh_size,再用gelf_update_shdr()回写节头 - 如果新增符号导致节区变大,还要检查是否需要调整程序头(
PT_LOAD段)的p_filesz/p_memsz,否则ld或loader可能拒绝加载
gelf_getsym() 和 gelf_update_sym() 的索引是节区内偏移,不是全局符号序号
符号表节区(.symtab)里存的是连续的 Elf64_Sym 数组,gelf_getsym(scn, idx, &sym) 的 idx 是该节区内的数组下标(从 0 开始),不是整个 ELF 文件的符号总序号。常见错误是把 idx 当成全局 ID 用,结果越界或跳过符号。
- 获取符号总数:先用
gelf_getshdr(scn, &shdr),再算shdr.sh_size / sizeof(Elf64_Sym) - 遍历时
idx范围是[0, count),其中count是上一步算出的值,不是shdr.sh_info(那是第一个非本地符号索引) - 修改符号名时,
st_name字段只是字符串表中的偏移,要改名字必须同时操作.strtab节区——先用elf_getdata()读出字符串表数据,追加新字符串,再更新st_name指向新偏移
实际修改符号名需同步更新 .strtab 并处理对齐与重定位
改一个符号的名字,本质是两件事:在字符串表里写新字符串,在符号结构里更新 st_name。但字符串表(.strtab)通常按 1 字节对齐,而节区本身可能要求 8 字节对齐,直接追加会导致后续节区偏移错乱。
立即学习“C++免费学习笔记(深入)”;
- 用
elf_getdata(strtab_scn, NULL)获取字符串表原始Elf_Data*,其d_buf是可写的;追加字符串后必须调用data->d_size += len + 1并更新data->d_off(若需移动) - 追加后调用
elf_update(elf, ELF_C_NULL)触发内部重计算,否则elf_update(elf, ELF_C_WRITE)可能写入旧长度 - 如果目标符号被重定位引用(比如
.rela.dyn里有对应项),改名不影响运行时行为,但调试器(gdb)或readelf -s显示的就是新名字——这点常被忽略,以为“改了没生效”
真正麻烦的不是读,而是写:ELF 格式要求所有头部、节区、字符串表、符号表之间严格自洽。哪怕只改一个符号名,也得动至少三个地方(符号结构、字符串表内容、字符串表大小),且顺序不能错。多数人卡在 elf_update() 返回 -1 却不报具体原因——这时候该检查 elf_errno(),而不是重试。



















