文章目录
- 前言
- 一、获取符号信息
- 1.1 相关结构体
- 1.2 代码示例1
- 1.3 代码示例2
- 二、Elf64_Shdr的字段sh_link
- 2.1 简介
- 2.2 代码示例 - sh_link用于符号表
- 2.3 代码示例 - sh_link用于重定位节
- 三、获取导出符号
前言
关于ELF文件的信息请参考:Linux C语言读取elf文件的代码段,重定位段
一、获取符号信息
1.1 相关结构体
节头表:
typedef struct
{Elf64_Word sh_name; /* Section name (string tbl index) */Elf64_Word sh_type; /* Section type */Elf64_Xword sh_flags; /* Section flags */Elf64_Addr sh_addr; /* Section virtual addr at execution */Elf64_Off sh_offset; /* Section file offset */Elf64_Xword sh_size; /* Section size in bytes */Elf64_Word sh_link; /* Link to another section */Elf64_Word sh_info; /* Additional section information */Elf64_Xword sh_addralign; /* Section alignment */Elf64_Xword sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;
符号表:
typedef struct
{Elf64_Word st_name; /* Symbol name (string tbl index) */unsigned char st_info; /* Symbol type and binding */unsigned char st_other; /* Symbol visibility */Elf64_Section st_shndx; /* Section index */Elf64_Addr st_value; /* Symbol value */Elf64_Xword st_size; /* Symbol size */
} Elf64_Sym;
1.2 代码示例1
下面是获取符号值的相关代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <elf.h>// 检查ELF头有效性
int check_elf_header(Elf64_Ehdr *ehdr) {if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0) {fprintf(stderr, "Not an ELF file\n");return 0;}if (ehdr->e_ident[EI_CLASS] != ELFCLASS64) {fprintf(stderr, "Not a 64-bit ELF file\n");return 0;}return 1;
}// 获取符号绑定类型字符串
const char* get_symbol_bind(Elf64_Sym *sym) {switch(ELF64_ST_BIND(sym->st_info)) {case STB_LOCAL: return "LOCAL";case STB_GLOBAL: return "GLOBAL";case STB_WEAK: return "WEAK";default: return "UNKNOWN";}
}// 获取符号类型字符串
const char* get_symbol_type(Elf64_Sym *sym) {switch(ELF64_ST_TYPE(sym->st_info)) {case STT_NOTYPE: return "NOTYPE";case STT_OBJECT: return "OBJECT";case STT_FUNC: return "FUNC";case STT_SECTION: return "SECTION";case STT_FILE: return "FILE";case STT_COMMON: return "COMMON";case STT_TLS: return "TLS";default: return "UNKNOWN";}
}// 获取符号可见性字符串
const char* get_symbol_visibility(Elf64_Sym *sym) {switch(ELF64_ST_VISIBILITY(sym->st_other)) {case STV_DEFAULT: return "DEFAULT";case STV_INTERNAL: return "INTERNAL";case STV_HIDDEN: return "HIDDEN";case STV_PROTECTED: return "PROTECTED";default: return "UNKNOWN";}
}// 处理符号表
void process_symbol_table(Elf64_Shdr *shdr, Elf64_Shdr *symtab_shdr, Elf64_Shdr *strtab_shdr, void *file_data) {Elf64_Sym *syms = (Elf64_Sym *)(file_data + symtab_shdr->sh_offset);int num_syms = symtab_shdr->sh_size / sizeof(Elf64_Sym);const char *strtab = (const char *)(file_data + strtab_shdr->sh_offset);printf("Found %d symbols in symbol table\n", num_syms);printf("%-30s %-8s %-8s %-8s %-12s %-8s %s\n", "Name", "Value", "Size", "Bind", "Type", "Vis", "Section");for (int i = 0; i < num_syms; i++) {Elf64_Sym *sym = &syms[i];const char *name = "(unnamed)";if (sym->st_name != 0) {name = strtab + sym->st_name;}printf("%-30s 0x%06lx %-8lu %-8s %-8s %-8s %d\n",name,sym->st_value,sym->st_size,get_symbol_bind(sym),get_symbol_type(sym),get_symbol_visibility(sym),sym->st_shndx);}
}int main(int argc, char *argv[]) {if (argc < 2) {fprintf(stderr, "Usage: %s <kernel_module.ko>\n", argv[0]);return 1;}// 打开并映射.ko文件int fd = open(argv[1], O_RDONLY);if (fd == -1) {perror("open");return 1;}struct stat st;if (fstat(fd, &st) == -1) {perror("fstat");close(fd);return 1;}void *file_data = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);if (file_data == MAP_FAILED) {perror("mmap");close(fd);return 1;}// 检查ELF头Elf64_Ehdr *ehdr = (Elf64_Ehdr *)file_data;if (!check_elf_header(ehdr)) {munmap(file_data, st.st_size);close(fd);return 1;}// 获取节头表和节字符串表Elf64_Shdr *shdrs = (Elf64_Shdr *)(file_data + ehdr->e_shoff);char *shstrtab = (char *)(file_data + shdrs[ehdr->e_shstrndx].sh_offset);// 查找符号表和字符串表Elf64_Shdr *symtab_shdr = NULL, *strtab_shdr = NULL;for (int i = 0; i < ehdr->e_shnum; i++) {const char *name = shstrtab + shdrs[i].sh_name;if (!symtab_shdr && (strcmp(name, ".symtab") == 0)) {symtab_shdr = &shdrs[i];} else if (!strtab_shdr && (strcmp(name, ".strtab") == 0)) {strtab_shdr = &shdrs[i];}}if (!symtab_shdr || !strtab_shdr) {fprintf(stderr, "Symbol table or string table not found\n");munmap(file_data, st.st_size);close(fd);return 1;}printf("Symbol table found at section %ld\n", symtab_shdr - shdrs);printf("String table found at section %ld\n", strtab_shdr - shdrs);// 处理符号表process_symbol_table(shdrs, symtab_shdr, strtab_shdr, file_data);// 清理munmap(file_data, st.st_size);close(fd);return 0;
}
对于符号的类型:
#define STB_LOCAL 0 /* Local symbol */
#define STB_GLOBAL 1 /* Global symbol */
#define STB_WEAK 2 /* Weak symbol */
STB_GLOBAL 是全局符号,是需要重定位的符号。指向全局符号的未定义引用,将在重定位期间确定相关符号的位置。
1.3 代码示例2
要获取符号在 .ko 文件 中的实际物理偏移量(即该符号在文件中的具体位置),计算公式如下:
符号在文件中的物理偏移量 =
Symbol File Offset = sh_offset(所属节的物理偏移) + st_value(符号在节内的偏移)
Symbol File Offset = Elf64_Shdr->sh_offset + Elf64_Sym->st_value
st_value是符号相对于其所属节起始位置的偏移量。
sh_offset(节头表字段)表示该节在ELF文件中的物理偏移量(即从文件开头到节数据的字节偏移)。用于定位节数据在文件中的实际存储位置。
具体实现步骤
(1)找到符号所属的节
符号的 st_shndx 字段表示它在哪个节(Elf64_Shdr 数组的索引)。
如果 st_shndx == SHN_UNDEF,表示该符号未定义(如外部引用的函数)。
(2)获取该节的 sh_offset
从节头表(Elf64_Shdr)中获取该节的 sh_offset,即该节在文件中的起始偏移。
(3)计算符号的文件偏移
File Offset=sh_offset+st_value
代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <elf.h>// 获取符号类型名称
const char* get_symbol_type(Elf64_Sym *sym) {switch (ELF64_ST_TYPE(sym->st_info)) {case STT_NOTYPE: return "NOTYPE";case STT_OBJECT: return "OBJECT";case STT_FUNC: return "FUNC";case STT_SECTION: return "SECTION";case STT_FILE: return "FILE";default: return "UNKNOWN";}
}// 打印符号的文件偏移
void print_symbol_file_offset(Elf64_Shdr *shdrs, Elf64_Sym *sym, const char *name, const char *shstrtab) {if (sym->st_shndx == SHN_UNDEF) {printf("Symbol: %-30s (Undefined, no file offset)\n", name);return;}// 获取所属节的名称const char *section_name = shstrtab + shdrs[sym->st_shndx].sh_name;// 计算符号在文件中的物理偏移// 指针也是数组,sym->st_shndx是该符号坐在节数组的序号,shdrs[sym->st_shndx]找到该节结构体的起始地址Elf64_Off file_offset = shdrs[sym->st_shndx].sh_offset + sym->st_value;printf("Symbol: %-30s Type: %-8s Section: %-10s File Offset: 0x%lx\n",name, get_symbol_type(sym), section_name, file_offset);
}int main(int argc, char *argv[]) {if (argc < 2) {fprintf(stderr, "Usage: %s <kernel_module.ko>\n", argv[0]);return 1;}// 打开并映射 .ko 文件int fd = open(argv[1], O_RDONLY);if (fd == -1) {perror("open");return 1;}struct stat st;if (fstat(fd, &st) == -1) {perror("fstat");close(fd);return 1;}void *file_data = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);if (file_data == MAP_FAILED) {perror("mmap");close(fd);return 1;}// 检查 ELF 头Elf64_Ehdr *ehdr = (Elf64_Ehdr *)file_data;if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0 || ehdr->e_ident[EI_CLASS] != ELFCLASS64) {fprintf(stderr, "Not a valid 64-bit ELF file\n");munmap(file_data, st.st_size);close(fd);return 1;}// 获取节头表和节字符串表Elf64_Shdr *shdrs = (Elf64_Shdr *)(file_data + ehdr->e_shoff);char *shstrtab = (char *)(file_data + shdrs[ehdr->e_shstrndx].sh_offset);// 查找符号表(.symtab)和字符串表(.strtab)Elf64_Shdr *symtab_shdr = NULL, *strtab_shdr = NULL;for (int i = 0; i < ehdr->e_shnum; i++) {const char *name = shstrtab + shdrs[i].sh_name;if (!symtab_shdr && strcmp(name, ".symtab") == 0) {symtab_shdr = &shdrs[i];} else if (!strtab_shdr && strcmp(name, ".strtab") == 0) {strtab_shdr = &shdrs[i];}}if (!symtab_shdr || !strtab_shdr) {fprintf(stderr, "Symbol table or string table not found\n");munmap(file_data, st.st_size);close(fd);return 1;}// 遍历符号表Elf64_Sym *syms = (Elf64_Sym *)(file_data + symtab_shdr->sh_offset);int num_syms = symtab_shdr->sh_size / sizeof(Elf64_Sym);const char *sym_names = (const char *)(file_data + strtab_shdr->sh_offset);printf("Symbols in %s:\n", argv[1]);for (int i = 0; i < num_syms; i++) {Elf64_Sym *sym = &syms[i];const char *name = sym_names + sym->st_name;print_symbol_file_offset(shdrs, sym, name, shstrtab);}munmap(file_data, st.st_size);close(fd);return 0;
}
// 计算符号在文件中的物理偏移// 指针也是数组,sym->st_shndx是该符号坐在节数组的序号,shdrs[sym->st_shndx]找到该节结构体的起始地址Elf64_Off file_offset = shdrs[sym->st_shndx].sh_offset + sym->st_value;
节头表通过数组实现的,每个数组项包含一节的信息。获取对应数组项:
shdrs[sym->st_shndx]
比如符号perf_trace_xfs_attr_list_class,计算出:
Symbol: perf_trace_xfs_attr_list_class Type: FUNC Section: .text File Offset: 0xb400
那么我们通过readelf命令查看:
$ readelf -s xfs.koSymbol table '.symtab' contains 8605 entries:Num: Value Size Type Bind Vis Ndx Name......24: 000000000000b360 294 FUNC LOCAL DEFAULT 3 perf_trace_xfs_attr_list_class
$ readelf -S xfs.ko | more
There are 66 section headers, starting at offset 0x33f8c0:Section Headers:[Nr] Name Type Address OffsetSize EntSize Flags Link Info Align......[ 3] .text PROGBITS 0000000000000000 000000a000000000000c1b6c 0000000000000000 AX 0 0 16
0xa0 + 0xb360 = 0xb400
二、Elf64_Shdr的字段sh_link
2.1 简介
在ELF (Executable and Linkable Format) 文件格式中,Elf64_Shdr(64位ELF节头结构体)的sh_link字段是一个非常重要的字段,它的具体作用取决于当前节的类型。以下是详细解释:
sh_link 是一个索引值,指向与该节相关联的其他节。具体含义由当前节的类型(sh_type)决定:
|
节类型 (sh_type) | sh_link 的含义 |
---|---|
SHT_SYMTAB /SHT_DYNSYM | 指向关联的字符串表(通常是 .strtab 或 .dynstr 节),存储符号名称。 |
SHT_REL / SHT_RELA | 指向符号表(.symtab 或 .dynsym 节),用于解析重定位项中的符号索引。 |
SHT_HASH | 指向符号表(.dynsym 节),用于动态链接的哈希表。 |
SHT_DYNAMIC | 指向字符串表(.dynstr 节),存储动态链接所需的符号名称。 |
SHT_GROUP | 指向符号表(.symtab 节),标识节组的签名符号。 |
其他类型 | 通常为 0(SHN_UNDEF),表示无关联节。 |
关键用途示例:
(1)符号表 (SHT_SYMTAB)
sh_link 指向字符串表(如 .strtab),因为符号名称存储在字符串表中。
例如,以下代码通过 sh_link 找到符号名称:
Elf64_Shdr *symtab = &shdrs[symtab_idx];
const char *strtab = (char *)file_data + shdrs[symtab->sh_link].sh_offset;
const char *sym_name = strtab + sym->st_name; // 获取符号名
(2)重定位节 (SHT_REL/SHT_RELA)
sh_link 指向符号表(如 .symtab),用于解析重定位项中的符号索引。
例如:
Elf64_Shdr *reloc_sec = &shdrs[reloc_idx];
Elf64_Sym *symtab = (Elf64_Sym *)(file_data + shdrs[reloc_sec->sh_link].sh_offset);
Elf64_Sym *sym = &symtab[ELF64_R_SYM(rel->r_info)]; // 获取关联的符号
ELF 文件通过 sh_link 实现节之间的关联,避免将数据直接嵌入节中,从而保持灵活性。例如:
符号表无需直接存储字符串,而是通过 sh_link 引用独立的字符串表。
重定位节通过 sh_link 找到符号表,而不是硬编码位置。
实际代码中的使用
在解析ELF文件时,通常需要结合 sh_type 和 sh_link 来正确解析数据。例如:
Elf64_Shdr *shdr = &shdrs[i];
if (shdr->sh_type == SHT_SYMTAB) {// sh_link 指向字符串表const char *strtab = (char *)file_data + shdrs[shdr->sh_link].sh_offset;// 处理符号表...
} else if (shdr->sh_type == SHT_RELA) {// sh_link 指向符号表Elf64_Sym *symtab = (Elf64_Sym *)(file_data + shdrs[shdr->sh_link].sh_offset);// 处理重定位...
}
sh_link 是 节头中的关联字段,指向当前节依赖的其他节。
具体含义由 sh_type 决定,常见于符号表、重定位节和动态节。
2.2 代码示例 - sh_link用于符号表
关键用途示例:
符号表 (SHT_SYMTAB)
sh_link 指向字符串表(如 .strtab),因为符号名称存储在字符串表中。
例如,以下代码通过 sh_link 找到符号名称:
Elf64_Shdr *symtab = &shdrs[symtab_idx];
const char *strtab = (char *)file_data + shdrs[symtab->sh_link].sh_offset;
const char *sym_name = strtab + sym->st_name; // 获取符号名
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <elf.h>// 获取符号绑定类型
const char* get_symbol_bind(Elf64_Sym *sym) {switch (ELF64_ST_BIND(sym->st_info)) {case STB_LOCAL: return "LOCAL";case STB_GLOBAL: return "GLOBAL";case STB_WEAK: return "WEAK";default: return "UNKNOWN";}
}// 获取符号类型
const char* get_symbol_type(Elf64_Sym *sym) {switch (ELF64_ST_TYPE(sym->st_info)) {case STT_NOTYPE: return "NOTYPE";case STT_OBJECT: return "OBJECT";case STT_FUNC: return "FUNC";case STT_SECTION: return "SECTION";case STT_FILE: return "FILE";default: return "UNKNOWN";}
}// 打印符号信息
void print_symbol_info(Elf64_Sym *sym, const char *name, const char *shstrtab, Elf64_Shdr *shdrs) {printf("Symbol: %-30s ", name);printf("Type: %-8s ", get_symbol_type(sym));printf("Bind: %-8s ", get_symbol_bind(sym));printf("Value: 0x%lx ", sym->st_value);printf("Size: %-6lu ", sym->st_size);// 打印所属节名称(如果有)if (sym->st_shndx != SHN_UNDEF) {const char *section_name = shstrtab + shdrs[sym->st_shndx].sh_name;printf("Section: %-10s", section_name);} else {printf("Section: UNDEF ");}// 计算文件偏移(仅对已定义符号有效)if (sym->st_shndx != SHN_UNDEF && sym->st_shndx != SHN_ABS) {Elf64_Off file_offset = shdrs[sym->st_shndx].sh_offset + sym->st_value;printf("File Offset: 0x%lx", file_offset);}printf("\n");
}int main(int argc, char *argv[]) {if (argc < 2) {fprintf(stderr, "Usage: %s <kernel_module.ko>\n", argv[0]);return 1;}// 打开并映射 .ko 文件int fd = open(argv[1], O_RDONLY);if (fd == -1) {perror("open");return 1;}struct stat st;if (fstat(fd, &st) == -1) {perror("fstat");close(fd);return 1;}void *file_data = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);if (file_data == MAP_FAILED) {perror("mmap");close(fd);return 1;}// 检查 ELF 头Elf64_Ehdr *ehdr = (Elf64_Ehdr *)file_data;if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0 || ehdr->e_ident[EI_CLASS] != ELFCLASS64) {fprintf(stderr, "Not a valid 64-bit ELF file\n");munmap(file_data, st.st_size);close(fd);return 1;}// 获取节头表和节字符串表Elf64_Shdr *shdrs = (Elf64_Shdr *)(file_data + ehdr->e_shoff);char *shstrtab = (char *)(file_data + shdrs[ehdr->e_shstrndx].sh_offset);// 查找符号表(.symtab)和字符串表(.strtab)Elf64_Shdr *symtab_shdr = NULL;for (int i = 0; i < ehdr->e_shnum; i++) {const char *name = shstrtab + shdrs[i].sh_name;if (strcmp(name, ".symtab") == 0) {symtab_shdr = &shdrs[i];break;}}if (!symtab_shdr) {fprintf(stderr, "Symbol table (.symtab) not found\n");munmap(file_data, st.st_size);close(fd);return 1;}// 通过 sh_link 获取字符串表Elf64_Shdr *strtab_shdr = &shdrs[symtab_shdr->sh_link];const char *strtab = (const char *)(file_data + strtab_shdr->sh_offset);// 遍历符号表Elf64_Sym *syms = (Elf64_Sym *)(file_data + symtab_shdr->sh_offset);int num_syms = symtab_shdr->sh_size / sizeof(Elf64_Sym);printf("Symbols in %s:\n", argv[1]);for (int i = 0; i < num_syms; i++) {Elf64_Sym *sym = &syms[i];const char *name = strtab + sym->st_name;print_symbol_info(sym, name, shstrtab, shdrs);}munmap(file_data, st.st_size);close(fd);return 0;
}
// 通过 sh_link 获取字符串表Elf64_Shdr *strtab_shdr = &shdrs[symtab_shdr->sh_link];const char *strtab = (const char *)(file_data + strtab_shdr->sh_offset);
2.3 代码示例 - sh_link用于重定位节
重定位节 (SHT_REL/SHT_RELA)
sh_link 指向符号表(如 .symtab),用于解析重定位项中的符号索引。
例如:
Elf64_Shdr *reloc_sec = &shdrs[reloc_idx];
Elf64_Sym *symtab = (Elf64_Sym *)(file_data + shdrs[reloc_sec->sh_link].sh_offset);
Elf64_Sym *sym = &symtab[ELF64_R_SYM(rel->r_info)]; // 获取关联的符号```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <elf.h>// 获取重定位类型名称(x86_64架构示例)
const char* get_reloc_type(Elf64_Word type) {switch(type) {case R_X86_64_NONE: return "R_X86_64_NONE";case R_X86_64_64: return "R_X86_64_64";case R_X86_64_PC32: return "R_X86_64_PC32";case R_X86_64_GOT32: return "R_X86_64_GOT32";case R_X86_64_PLT32: return "R_X86_64_PLT32";case R_X86_64_RELATIVE: return "R_X86_64_RELATIVE";case R_X86_64_GOTPCREL: return "R_X86_64_GOTPCREL";case R_X86_64_32: return "R_X86_64_32";case R_X86_64_32S: return "R_X86_64_32S";default: return "UNKNOWN";}
}// 解析重定位节
void process_relocation_section(Elf64_Shdr *reloc_shdr, Elf64_Shdr *symtab_shdr,Elf64_Shdr *strtab_shdr,void *file_data,const char *shstrtab) {// 获取重定位节的名称(如.rela.text)const char *reloc_name = shstrtab + reloc_shdr->sh_name;printf("\nProcessing relocation section: %s\n", reloc_name);// 确定重定位条目是REL还是RELAint is_rela = (reloc_shdr->sh_type == SHT_RELA);size_t entry_size = is_rela ? sizeof(Elf64_Rela) : sizeof(Elf64_Rel);int num_entries = reloc_shdr->sh_size / entry_size;// 获取重定位表数据void *reloc_data = file_data + reloc_shdr->sh_offset;// 通过sh_link获取符号表Elf64_Sym *symtab = (Elf64_Sym *)(file_data + symtab_shdr->sh_offset);const char *strtab = (const char *)(file_data + strtab_shdr->sh_offset);// 遍历所有重定位项for (int i = 0; i < num_entries; i++) {Elf64_Addr r_offset;Elf64_Xword r_info;Elf64_Sxword r_addend = 0;if (is_rela) {Elf64_Rela *rela = (Elf64_Rela *)reloc_data + i;r_offset = rela->r_offset;r_info = rela->r_info;r_addend = rela->r_addend;} else {Elf64_Rel *rel = (Elf64_Rel *)reloc_data + i;r_offset = rel->r_offset;r_info = rel->r_info;}// 从r_info中提取符号索引和重定位类型Elf64_Word sym_index = ELF64_R_SYM(r_info);Elf64_Word reloc_type = ELF64_R_TYPE(r_info);// 获取符号信息Elf64_Sym *sym = &symtab[sym_index];const char *sym_name = "(unknown)";if (sym->st_name != 0) {sym_name = strtab + sym->st_name;}// 打印重定位项详情printf(" Relocation %d:\n", i);printf(" r_offset: 0x%lx (address to modify)\n", r_offset);printf(" r_info: 0x%lx (sym_index=%u, type=%s)\n", r_info, sym_index, get_reloc_type(reloc_type));if (is_rela) {printf(" r_addend: %ld\n", r_addend);}printf(" Symbol: %s (value=0x%lx, size=%lu, bind=%s, type=%s)\n",sym_name, sym->st_value, sym->st_size,(ELF64_ST_BIND(sym->st_info) == STB_GLOBAL) ? "GLOBAL" : "LOCAL",(ELF64_ST_TYPE(sym->st_info) == STT_FUNC) ? "FUNC" : "OBJECT");}
}int main(int argc, char *argv[]) {if (argc < 2) {fprintf(stderr, "Usage: %s <kernel_module.ko>\n", argv[0]);return 1;}// 打开并映射ELF文件int fd = open(argv[1], O_RDONLY);if (fd == -1) {perror("open");return 1;}struct stat st;if (fstat(fd, &st) == -1) {perror("fstat");close(fd);return 1;}void *file_data = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);if (file_data == MAP_FAILED) {perror("mmap");close(fd);return 1;}// 检查ELF头Elf64_Ehdr *ehdr = (Elf64_Ehdr *)file_data;if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0 || ehdr->e_ident[EI_CLASS] != ELFCLASS64) {fprintf(stderr, "Not a valid 64-bit ELF file\n");munmap(file_data, st.st_size);close(fd);return 1;}// 获取节头表和节字符串表Elf64_Shdr *shdrs = (Elf64_Shdr *)(file_data + ehdr->e_shoff);char *shstrtab = (char *)(file_data + shdrs[ehdr->e_shstrndx].sh_offset);// 查找符号表和字符串表(用于解析符号名)Elf64_Shdr *symtab_shdr = NULL, *strtab_shdr = NULL;for (int i = 0; i < ehdr->e_shnum; i++) {const char *name = shstrtab + shdrs[i].sh_name;if (!symtab_shdr && strcmp(name, ".symtab") == 0) {symtab_shdr = &shdrs[i];} else if (!strtab_shdr && strcmp(name, ".strtab") == 0) {strtab_shdr = &shdrs[i];}}if (!symtab_shdr || !strtab_shdr) {fprintf(stderr, "Symbol table or string table not found\n");munmap(file_data, st.st_size);close(fd);return 1;}// 遍历所有节,查找重定位节(SHT_REL/SHT_RELA)for (int i = 0; i < ehdr->e_shnum; i++) {if (shdrs[i].sh_type == SHT_REL || shdrs[i].sh_type == SHT_RELA) {// 重定位节的sh_link指向符号表Elf64_Shdr *linked_symtab = &shdrs[shdrs[i].sh_link];process_relocation_section(&shdrs[i], linked_symtab, strtab_shdr, file_data, shstrtab);}}munmap(file_data, st.st_size);close(fd);return 0;
}
// 重定位节的sh_link指向符号表Elf64_Shdr *linked_symtab = &shdrs[shdrs[i].sh_link];process_relocation_section(&shdrs[i], linked_symtab, strtab_shdr, file_data, shstrtab);// 通过sh_link获取符号表Elf64_Sym *symtab = (Elf64_Sym *)(file_data + symtab_shdr->sh_offset);
三、获取导出符号
关于导出符号请参考:Linux EXPORT_SYMBOL宏详解
对于3.10.0:
// linux/v3.10/source/include/linux/export.hstruct kernel_symbol
{unsigned long value;const char *name;
};
对于5.15.0
// linux/v5.15/source/include/linux/export.h#ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS
#include <linux/compiler.h>
/** Emit the ksymtab entry as a pair of relative references: this reduces* the size by half on 64-bit architectures, and eliminates the need for* absolute relocations that require runtime processing on relocatable* kernels.*/
#define __KSYMTAB_ENTRY(sym, sec) \__ADDRESSABLE(sym) \asm(" .section \"___ksymtab" sec "+" #sym "\", \"a\" \n" \" .balign 4 \n" \"__ksymtab_" #sym ": \n" \" .long " #sym "- . \n" \" .long __kstrtab_" #sym "- . \n" \" .long __kstrtabns_" #sym "- . \n" \" .previous \n")struct kernel_symbol {int value_offset;int name_offset;int namespace_offset;
};
这个struct kernel_symbol结构体在高版本有改动。
我们通过其他方式来获取导出的函数:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <elf.h>// 检查ELF头有效性
int check_elf_header(Elf64_Ehdr *ehdr) {if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0) {fprintf(stderr, "Not an ELF file\n");return 0;}if (ehdr->e_ident[EI_CLASS] != ELFCLASS64) {fprintf(stderr, "Not a 64-bit ELF file\n");return 0;}return 1;
}// 获取符号类型名称
const char* get_symbol_type(Elf64_Sym *sym) {switch(ELF64_ST_TYPE(sym->st_info)) {case STT_NOTYPE: return "NOTYPE";case STT_OBJECT: return "OBJECT";case STT_FUNC: return "FUNC";case STT_SECTION: return "SECTION";case STT_FILE: return "FILE";default: return "UNKNOWN";}
}// 获取符号绑定类型
const char* get_symbol_bind(Elf64_Sym *sym) {switch(ELF64_ST_BIND(sym->st_info)) {case STB_LOCAL: return "LOCAL";case STB_GLOBAL: return "GLOBAL";case STB_WEAK: return "WEAK";default: return "UNKNOWN";}
}int main(int argc, char *argv[]) {if (argc < 2) {fprintf(stderr, "Usage: %s <kernel_module.ko>\n", argv[0]);return 1;}// 打开并映射ELF文件int fd = open(argv[1], O_RDONLY);if (fd == -1) {perror("open");return 1;}struct stat st;if (fstat(fd, &st) == -1) {perror("fstat");close(fd);return 1;}void *file_data = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);if (file_data == MAP_FAILED) {perror("mmap");close(fd);return 1;}// 检查ELF头Elf64_Ehdr *ehdr = (Elf64_Ehdr *)file_data;if (!check_elf_header(ehdr)) {munmap(file_data, st.st_size);close(fd);return 1;}// 获取节头表和节字符串表Elf64_Shdr *shdrs = (Elf64_Shdr *)(file_data + ehdr->e_shoff);char *shstrtab = (char *)(file_data + shdrs[ehdr->e_shstrndx].sh_offset);// 查找.symtab和.strtab节Elf64_Shdr *symtab_shdr = NULL, *strtab_shdr = NULL;for (int i = 0; i < ehdr->e_shnum; i++) {const char *name = shstrtab + shdrs[i].sh_name;if (strcmp(name, ".symtab") == 0) {symtab_shdr = &shdrs[i];} else if (strcmp(name, ".strtab") == 0) {strtab_shdr = &shdrs[i];}}if (!symtab_shdr || !strtab_shdr) {fprintf(stderr, ".symtab or .strtab section not found\n");munmap(file_data, st.st_size);close(fd);return 1;}// 获取符号表和字符串表数据Elf64_Sym *symtab = (Elf64_Sym *)(file_data + symtab_shdr->sh_offset);int num_syms = symtab_shdr->sh_size / sizeof(Elf64_Sym);const char *strtab = (const char *)(file_data + strtab_shdr->sh_offset);printf("Kernel exported symbols (__ksymtab_*):\n");printf("%-40s %-8s %-8s %-12s %-10s %s\n", "Name", "Type", "Bind", "Value", "Size", "Section");// 遍历所有符号,查找以"__ksymtab_"开头的for (int i = 0; i < num_syms; i++) {Elf64_Sym *sym = &symtab[i];const char *name = strtab + sym->st_name;// 检查符号名是否以"__ksymtab_"开头if (strncmp(name, "__ksymtab_", 10) == 0) {const char *exported_name = name + 10; // 去掉"__ksymtab_"前缀const char *section_name = (sym->st_shndx != SHN_UNDEF) ? shstrtab + shdrs[sym->st_shndx].sh_name : "UNDEF";printf("%-40s %-8s %-8s 0x%08lx %-10lu %s\n",exported_name,get_symbol_type(sym),get_symbol_bind(sym),sym->st_value,sym->st_size,section_name);}}munmap(file_data, st.st_size);close(fd);return 0;
}
结果显示:
$ ./a.out snd-sof-intel-hda-common.ko
Kernel exported symbols (__ksymtab_*):
Name Type Bind Value Size Section
hda_pci_intel_probe NOTYPE LOCAL 0x00000030 0 __ksymtab
sof_apl_ops NOTYPE LOCAL 0x00000054 0 __ksymtab
apl_chip_info NOTYPE LOCAL 0x0000000c 0 __ksymtab
sof_cnl_ops NOTYPE LOCAL 0x00000060 0 __ksymtab
cnl_chip_info NOTYPE LOCAL 0x00000018 0 __ksymtab
jsl_chip_info NOTYPE LOCAL 0x00000048 0 __ksymtab
sof_tgl_ops NOTYPE LOCAL 0x00000078 0 __ksymtab
tgl_chip_info NOTYPE LOCAL 0x00000084 0 __ksymtab
tglh_chip_info NOTYPE LOCAL 0x00000090 0 __ksymtab
ehl_chip_info NOTYPE LOCAL 0x00000024 0 __ksymtab
adls_chip_info NOTYPE LOCAL 0x00000000 0 __ksymtab
sof_icl_ops NOTYPE LOCAL 0x0000006c 0 __ksymtab
icl_chip_info NOTYPE LOCAL 0x0000003c 0 __ksymtab
使用readelf查看:
$ nm snd-sof-intel-hda-common.ko | grep __ksymtab
0000000000000000 r __ksymtab_adls_chip_info
000000000000000c r __ksymtab_apl_chip_info
0000000000000018 r __ksymtab_cnl_chip_info
0000000000000024 r __ksymtab_ehl_chip_info
0000000000000030 r __ksymtab_hda_pci_intel_probe
000000000000003c r __ksymtab_icl_chip_info
0000000000000048 r __ksymtab_jsl_chip_info
0000000000000054 r __ksymtab_sof_apl_ops
0000000000000060 r __ksymtab_sof_cnl_ops
000000000000006c r __ksymtab_sof_icl_ops
0000000000000078 r __ksymtab_sof_tgl_ops
0000000000000084 r __ksymtab_tgl_chip_info
0000000000000090 r __ksymtab_tglh_chip_info