Linux C语言获取elf文件符号信息
文章目录
- 前言
- 一、获取符号信息
- 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.ko
Symbol 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 Offset
Size EntSize Flags Link Info Align
......
[ 3] .text PROGBITS 0000000000000000 000000a0
00000000000c1b6c 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还是RELA
int 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.h
struct 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