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文件格式解析_第1张图片

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

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

ELF文件格式解析_第2张图片

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

ELF文件格式解析_第3张图片

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

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

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

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

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


1、ELF头

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

typedef struct
{
  unsigned char    e_ident[EI_NIDENT];    /* Magic number and other info */
  Elf64_Half    e_type;            /* Object file type */
  Elf64_Half    e_machine;        /* Architecture */
  Elf64_Word    e_version;        /* Object file version */
  Elf64_Addr    e_entry;        /* Entry point virtual address */
  Elf64_Off    e_phoff;        /* Program header table file offset */
  Elf64_Off    e_shoff;        /* Section header table file offset */
  Elf64_Word    e_flags;        /* Processor-specific flags */
  Elf64_Half    e_ehsize;        /* ELF header size in bytes */
  Elf64_Half    e_phentsize;        /* Program header table entry size */
  Elf64_Half    e_phnum;        /* Program header table entry count */
  Elf64_Half    e_shentsize;        /* Section header table entry size */
  Elf64_Half    e_shnum;        /* Section header table entry count */
  Elf64_Half    e_shstrndx;        /* Section header string table index */
} Elf64_Ehdr;

e_ident:幻数

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

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

还有一些数据不一一描述

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

ELF文件格式解析_第4张图片

 2、程序表头

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

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

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

ELF文件格式解析_第5张图片

 3、节头表

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

typedef struct {
    Elf64_Word    sh_name;
    Elf64_Word    sh_type;
    Elf64_Xword    sh_flags;
    Elf64_Addr    sh_addr;
    Elf64_Off    sh_offset;
    Elf64_Xword    sh_size;
    Elf64_Word    sh_link;
    Elf64_Word    sh_info;
    Elf64_Xword    sh_addralign;
    Elf64_Xword    sh_entsize;
} 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信息


 分析

假设一段代码

#include 

unsigned char s_muse[]={0x12,0x34,0x56,0x78,0x90};

int main()
{
    int a = 10;
    printf("a=%d\n",a);
}

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

readelf -h 5.o

ELF文件格式解析_第6张图片

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

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

ELF文件格式解析_第7张图片

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

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

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

ELF文件格式解析_第8张图片

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

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

你可能感兴趣的:(linux,杂项,linux)