当前位置: 首页 > news >正文

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

相关文章:

  • NVIDIA cuOpt:GPU加速优化AI微服务详解
  • 红宝书第十八讲:详解JavaScript的async/await与错误处理
  • 浅谈数据结构
  • 蓝桥杯 数三角
  • 阿里OSS使用指南!
  • 论文阅读笔记——ST-4DGS,WideRange4D
  • 【day24】逻辑分析与流程梳理:电子门票核销成功率巡检
  • 【数据分享】2000—2024年我国乡镇的逐年归一化植被指数(NDVI)数据(年最大值/Shp/Excel格式)
  • FFmpeg —— 实时绘制音频波形图(附源码)
  • 服务器与客户端通讯测试
  • 2025年- G32-Lc106-133. 克隆图--java版(很抽象,没有很能理解)
  • Linux SSH远程登录
  • HCIA-Datacom高阶:基础的单区域 OSPF 与多区域 OSPF的配置
  • 关于IP免实名的那些事
  • 语音机器人与智能体结合
  • SpringAI与JBoltAI深度对比:从工具集到企业级AI开发范式的跃迁
  • (一)初始化窗口
  • 记录Jmeter 利用BeanShell 脚本解析JSON字符串
  • MAC安装docker 后提示com.docker.vmnetd”将对您的电脑造成伤害
  • MySQL 语句解析json字符串
  • 多地再发网约车从业及投资风险提示:避免盲目花费大笔资金“购车”入行
  • 印巴战火LIVE丨“快速接近战争状态”?印度袭击巴军事基地,巴启动反制军事行动
  • 印巴战火LIVE丨印巴互相发动无人机袭击,巴官员称两国已在国安层面接触
  • 央视315晚会曝光“保水虾仁”后,湛江4家涉事企业被罚超800万元
  • 郑州一街道被指摊贩混乱经营,12345热线:已整治并加强巡查
  • 五一假期上海楼市延续向好态势,成交量同比增加36%