当前位置:   article > 正文

ELF文件格式解析_elf文件格式详解

elf文件格式详解

介绍

ELF(Executable and Linkable Format)文件是一种用于二进制文件、可执行文件、目标代码、共享库和core转存的格式文件,是UNIX系统实验室作为应用程序二进制接口(ABI)而开发和发布的,也是Linux的主要可执行文件格式。

  • 可执行文件(.out): 包含代码和数据,可以直接运行,其代码和数据都有固定的地址或基地址偏移。系统可以根据这些地址加载程序

  • 重定位文件(.o): 包含基础代码和数据,但它们没有指定绝对地址,所以需要和其他目标文件链接来创建.o或者.so

  • 共享目标文件(.so):包含代码和数据,这些数据是在链接时被链接器(ld)和运行时动态链接器(ld.so.1、libc.so.1、ld-linux.so.1)使用

  • 内核转储(core dump):存放当前进程的执行上下文

 这个ELF文件是属于上面类型,存在ELF头部信息中

ELF文件格式提供了2种视图:链接视图和执行视图

顾名思义,链接视图就是在链接时用到的视图,而执行视图则是在执行时用到的视图,链接视图以节(section)为单位,执行视图以段(segment)为单位。

 本文主要分析链接视图,继续细分一下

 如上图,ELF可分为4大部分:

  • ELF头(ELF Header)——定义全局属性信息,比如幻数、目标体系结构、节头表地址偏移等;

  • 程序头表(Program Header Table)——举了所有有效的段(segments)和它们的属性、程序表头需要加载器将文件中的节接在到虚拟内存中,对于可链接文件来说,程序表头可能为空;

  • 节区(Section Table)——ELF文件中的数据和代码以节区的形式存储,不同类型的节区包含了不同的信息,如代码区、数据区、符号表区等;

  • 节头表(Section Header Table)——包含对节(section)的描述,记录了ELF文件中各个节的起始偏移、大小、标志等信息;


1、ELF头

描述ELF文件的主要特性(/usr/include/elf.h)

  1. typedef struct
  2. {
  3. unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
  4. Elf64_Half e_type; /* Object file type */
  5. Elf64_Half e_machine; /* Architecture */
  6. Elf64_Word e_version; /* Object file version */
  7. Elf64_Addr e_entry; /* Entry point virtual address */
  8. Elf64_Off e_phoff; /* Program header table file offset */
  9. Elf64_Off e_shoff; /* Section header table file offset */
  10. Elf64_Word e_flags; /* Processor-specific flags */
  11. Elf64_Half e_ehsize; /* ELF header size in bytes */
  12. Elf64_Half e_phentsize; /* Program header table entry size */
  13. Elf64_Half e_phnum; /* Program header table entry count */
  14. Elf64_Half e_shentsize; /* Section header table entry size */
  15. Elf64_Half e_shnum; /* Section header table entry count */
  16. Elf64_Half e_shstrndx; /* Section header string table index */
  17. } Elf64_Ehdr;

e_ident:幻数

e_type:目标文件类型(上面第一张图)

e_machine:体系结构类型(比如183是AARCH64架构)

还有一些数据不一一描述

下面的图是32位系统的,只有占用字节数不同

 2、程序表头

是一个结构体数组,每一个元素类型都是Elf64_Phdr(64位编译器)、Elf32_Phdr(32位编译器)描述了一个段或者其他系统在准备程序执行时所需要的信息,其中 ELF 头中的 e_phentsize 和 e_phnum 指定了该数组每个元素的大小以及元素个数,一个目标文件的段包含一个或者多个节。可以说程序表头就是专门为ELF文件运行时中的段所准备的

  1. typedef struct
  2. {
  3. Elf64_Word p_type; /* Segment type */ 该字段为段的类型
  4. Elf64_Word p_flags; /* Segment flags 该字段给出了与段相关的标记*/
  5. Elf64_Off p_offset; /* Segment file offset 该字段给出了从文件开始到该段开头的第一个字节的偏移*/
  6. Elf64_Addr p_vaddr; /* Segment virtual address 该字段给出了该段第一个字节在内存中的虚拟地址*/
  7. Elf64_Addr p_paddr; /* Segment physical address 该字段仅用于物理地址寻址相关的系统中*/
  8. Elf64_Xword p_filesz; /* Segment size in file 该字段给出了文件镜像中该段的大小,可能为 0*/
  9. Elf64_Xword p_memsz; /* Segment size in memory 该字段给出了内存镜像中该段的大小,可能为 0*/
  10. Elf64_Xword p_align; /* Segment alignment 可加载的程序的段的 p_vaddr 以及 p_offset 的大小必须是 page 的整数倍*/
  11. } Elf64_Phdr;

一个段可以包含一个或多个节,但是不同的段可能会有重合,即一个节在不同段里

 3、节头表

这个数据结构位于ELF文件的尾部,具体位置在ELF头中的e_shoff项给出了偏移,e_shnum告诉我们节头表中包含的节数,e_shentsize给出了每一节的字节大小(比如64位系统就是64字节)

  1. typedef struct {
  2. Elf64_Word sh_name;
  3. Elf64_Word sh_type;
  4. Elf64_Xword sh_flags;
  5. Elf64_Addr sh_addr;
  6. Elf64_Off sh_offset;
  7. Elf64_Xword sh_size;
  8. Elf64_Word sh_link;
  9. Elf64_Word sh_info;
  10. Elf64_Xword sh_addralign;
  11. Elf64_Xword sh_entsize;
  12. } Elf64_Shdr;

成员

说明

sh_name

节名称,是节区头字符串表节区中(Section Header String Table Section)的索引,因此该字段实际是一个数值。在字符串表中的具体内容是以 NULL 结尾的字符串。

sh_type

根据节的内容和语义进行分类,具体的类型下面会介绍

sh_flags

每一比特代表不同的标志,描述节是否可写,可执行,需要分配内存等属性。

sh_addr

如果节区将出现在进程的内存映像中,此成员给出节区的第一个字节应该在进程镜像中的位置。否则,此字段为 0

sh_offset

给出节区的第一个字节与文件开始处之间的偏移。SHT_NOBITS 类型的节区不占用文件的空间,因此其 sh_offset 成员给出的是概念性的偏移。

sh_size

此成员给出节区的字节大小。除非节区的类型是 SHT_NOBITS ,否则该节占用文件中的 sh_size 字节。类型为 SHT_NOBITS 的节区长度可能非零,不过却不占用文件中的空间。

sh_link

此成员给出节区头部表索引链接,其具体的解释依赖于节区类型

sh_info

此成员给出附加信息,其解释依赖于节区类型。

sh_addralign

某些节区的地址需要对齐。例如,如果一个节区有一个 doubleword 类型的变量,那么系统必须保证整个节区按双字对齐。也就是说sh_addr%sh_addralign = 0。 目前它仅允许为 0,以及 2 的正整数幂数。 0 和 1 表示没有对齐约束

sh_entsize

某些节区中存在具有固定大小的表项的表,如符号表。对于这类节区,该成员给出每个表项的字节大小。反之,此成员取值为 0

4、节区

 一些系统预定的固定section

sh_name

sh_type

说明

.text

SHT_PROGBITS

代码段,包含程序的可执行指令

.data

SHT_PROGBITS

包含初始化了的数据,将出现在程序的内存映像中

.bss

SHT_NOBITS

未初始化数据,因为只有符号所以

.rodata

SHT_PROGBITS

包含只读数据

.comment

SHT_PROGBITS

包含版本控制信息

.dynsym

SHT_DYNSYM

此节区包含了动态链接符号表

.shstrtab

SHT_STRTAB

存放section名,字符串表。Section

Header String Table

.strtab

SHT_STRTAB

字符串表

.symtab

SHT_SYMTAB

符号表

.text代码段可以通过objdump -d反汇编查看ELF文件代码段内容 

ELF文件中有很多字符串,比如section name、变量名等,ELF会集中放到一起(.strtab、.shstrtab),每一个字符串以'\0'分割,然后保存首字符偏移进行引用字符串,可以使用readelf -S xxx.o查看所有section信息


 分析

假设一段代码

  1. #include <stdio.h>
  2. unsigned char s_muse[]={0x12,0x34,0x56,0x78,0x90};
  3. int main()
  4. {
  5. int a = 10;
  6. printf("a=%d\n",a);
  7. }

aarch64-xxx-gcc -c 5.c -o 5.o

readelf -h 5.o

 可以知道架构是aarch64、elf头64字节、节头每个节64字节、elf有13个section、secton name在第12节中,

节头表偏移在848(0x350)这个位置上,我们可以从0x350开始解析所有节,从0x350到结尾都是节头表,总共64*13=832字节,可以通过readelf -S 5.o或许section信息

 上图的所有信息其实都是在节头表每64个字节的节头表结构体中解析出来的,从节信息中又可以得到对应节的起始偏移、占用大小、一些flag等。

比如代码中定义了一个全局数组(0x12,0x34,0x56,0x78,0x90),它是保存在.data节中

可以看到起始位置是0x70、占用5个字节,首地址8字节对齐,使用16进制格式打开5.o

 可以看到0x70开始的5个字节刚好就是数组数据了

ELF所有信息都可以通过偏移和结构体获得

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/319402
推荐阅读
相关标签
  

闽ICP备14008679号