Linux ELF文件格式

本文深入解析ELF文件格式,包括ELF头、程序头、段表和符号表的结构及作用,阐述了segment与section的关系,以及如何通过readelf和objdump工具查看和反汇编ELF文件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. ELF header结构体


主要定义了文件的类型,程序头表位置,大小和个数,节头表位置,大小和个数
typedef struct {
               unsigned char e_ident[EI_NIDENT];/*ELF Majic*/
               uint16_t      e_type;/*文件类型(ET_REL,ET_EXEC,ET_DYN)*/
               uint16_t      e_machine;/*CPU体系(ARM ,X86,MIPS),表示程序在哪种cpu执行*/
               uint32_t      e_version;
               ElfN_Addr     e_entry;/* 程序的入口点*/
               ElfN_Off      e_phoff;/*程序头表的offset */
               ElfN_Off      e_shoff;/*节头表的offset */
               uint32_t      e_flags;/*目前未用到 */
               uint16_t      e_ehsize;/*文件头的大小 */
               uint16_t      e_phentsize;/*一个程序头的大小 */
               uint16_t      e_phnum;/*程序头个数*/
               uint16_t      e_shentsize;/*一个节表头的大小 */
               uint16_t      e_shnum; /*节表头的个数 */
               uint16_t      e_shstrndx;/*字符串节表在节表头table中的index */
           } ElfN_Ehdr;

常见的ELF程序类型
ET_REL :可重定向文件
ET_EXEC:可执行文件
ET_DYN : 共享文件

2.程序头(Program header)


可执行文件或者共享文件的program header table描述了系统执行一个程序所需要的段(segment)或者其它信息。
目标文件的一个段(segment)包含一个或者多个section。Program header只对可执行文件和共享目标文件有意义,
对于程序的链接没有任何意义。一个program header table包含多个program header,定义如下:

typedef struct {
               uint32_t   p_type;/*segment类型(PT_LOAD,PT_DYNAMIC,PT_GNU_STACK) */
               uint32_t   p_flags;/*segment 属性PF_X,PF_R,PF_W*/
               Elf64_Off  p_offset;/*这个segment相对文件的偏移 */
               Elf64_Addr p_vaddr;/*这个segment在运行时的虚拟地址 */
               Elf64_Addr p_paddr;/* 物理地址,暂时没用*/
               uint64_t   p_filesz;/*这个segment的大小 */
               uint64_t   p_memsz;/*这个segment加载到内存后的大小 */
               uint64_t   p_align;/*对齐相关*/
           } Elf64_Phdr;

segment类型和flags说明
PT_LOAD :这个段是可装载的(程序运行时,把这个segment加载到内存)
PT_DYNAMIC:动态链接相关的segment
PT_INTERP  :制定动态链接库的全路径
PF_X: segment具有执行权限
PF_R: segment是只读的
PF_W: segment是可写的.
代码段具有:PF_X, PF_W
数据段具有:PF_X,PF_W,PF_R

3.段表(section header table)


目标文件的section header table可以定位所有的section,段表实际上是一个数组

           typedef struct {
               uint32_t   sh_name;/* section name在string section 中的Index*/
               uint32_t   sh_type;/*section类型,常见类型SHT_PROGBITS,SHT_REL,SHT_SYMTAB,SHT_STRTAB,SHT_DYNSYM */
               uint64_t   sh_flags;/*section访问权限,SHF_WRITE,SHF_ALLOC,SHF_EXECINSTR */
               Elf64_Addr sh_addr;/*section在内存中的地址. */
               Elf64_Off  sh_offset;/*section在文件中的偏移 */
               uint64_t   sh_size;/*section的大小 */
               uint32_t   sh_link;/*section依赖其他section的index,比如.symtab依赖.strtab,sh_link的值就是.strtab的index */
               uint32_t   sh_info;/*额外信息,.symtab,重定向会用到 */
               uint64_t   sh_addralign;/*对齐相关 */
               uint64_t   sh_entsize;/*有些section包含额外的信息 */
           } Elf64_Shdr;

3.1 常见的section

可以用readelf -S binary_file查看所有的段
.bss :为初始化的全局变量
.text :代码段
.data :数据段
.rodata:只读数据段
.strtab :包含所有符号的名字
.shstrtab:包含所有section name
.symtab :包含文件所有的符号表(函数名,文件名,全局变量名等等)
.rel/.rela: 可重定向相关
.dymtab/.dymstr :动态链接相关

3.2 section 的sh_name解析

1.先根据file header找到section header table的位置
2.根据file header中的e_shstrndx,在段表中找到字符串section ".strtab"
3.根据sh_name的值从".strtab"找到对应的字符串.从而得出section name

3.3 symbol/section/segment的关系

symbol被定义在各种section中。而section最终又被包含到segment中.
一个segment可以包含多个section

4 symtab

sysmtab包含elf文件中的说有符号信息

 typedef struct {
               uint32_t      st_name;
               unsigned char st_info;
               unsigned char st_other;
               uint16_t      st_shndx;
               Elf64_Addr    st_value;
               uint64_t      st_size;
           } Elf64_Sym;

st_name:symbol名字在strtab中的offset

st_shndx: 表示符号的类型,比如模块调用内核的函数,编译模块后需要设置成SHN_UNDEF,

这样在加载模块是,内核会解析这个符号。

st_value:符号加载多内核的虚拟地址(elf静态文件是端内偏移)

5 重定向表

  typedef struct {
               Elf64_Addr r_offset;
               uint64_t   r_info;
               int64_t    r_addend;
           } Elf64_Rela;

重定向表描述了4个信息

1.需要重定向的地址(r_offset),相对section内

2.需要重定向的符号(r_info>>8)

3 重定向类型(r_info&(7))

129 int apply_relocate_add(Elf64_Shdr *sechdrs,
130                    const char *strtab,
131                    unsigned int symindex,
132                    unsigned int relsec,
133                    struct module *me)
134 {
135         unsigned int i;
            /* 重定向表项*/
136         Elf64_Rela *rel = (void *)sechdrs[relsec].sh_addr;
137         Elf64_Sym *sym; 
138         void *loc;
139         u64 val;
            /* 依次处理重定向表*/
140         for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) {
141                 Elf64_Sym kstack_sym;
143                 const char *symname;
144                 
145                 /* This is where to make the change */
                    /*sechdrs[relsec].sh_info:需要对哪个section重定向 */
                    /* loc是需要重定向的虚拟地址*/
146                 loc = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr
147                         + rel[i].r_offset;
148                         
149                 /* This is the symbol it is referring to.  Note that all
150                    undefined symbols have been resolved.  */
                    /*rel[i].r_info:需要重定向符号在symtab的index */
151                 sym = (Elf64_Sym *)sechdrs[symindex].sh_addr
152                         + ELF64_R_SYM(rel[i].r_info);
153                 symname = strtab + sym->st_name;
                    /* st_value:符号在内核中的虚拟地址*/
154                 val = sym->st_value + rel[i].r_addend;
155                 
156                 switch (ELF64_R_TYPE(rel[i].r_info)) {
157                 case R_X86_64_PC32:
158                 case R_X86_64_PLT32:
159                         if (*(u32 *)loc != 0)
160                                 goto invalid_relocation;
                            /*计算符号相对跳转指令的offset,并最终把offset写入loc */
161                         val -= (u64)loc;
162                         *(u32 *)loc = val;

 

6.readelf常用指令

readelf -h  binary_file /*显示文件头信息 */
readelf -l  binary_file /*显示程序头信息 */
readelf -S  binary_file /*显示段表头信息 */
readelf -s  binary_file /*显示.symtab内容 */
readelf -n  binary_file /*显示文件的Note信息 */
readelf -x  section_name /*以16进制显示段表内容 */
readelf -p  section_name /*以字符串显示段表内容 */

readelf -r /* 获取重定向表*/

objdump -d binary_file /*把代码段反汇编 */
objdump -d -Mintel binary_file /*以intel汇编格式来反汇编代码,默认是AT&T格式 */

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值