天天看点

ELF文件格式入门

什么是ELF

ELF(Executable and Linking Format),即“可执行可连接格式”,最初由 UNIX系统实验室做为应用程序二进制接口(ABI)的一部分而制定和发布。简单说就是一种文件格式。

ELF文件类型

(1)可重定位文件, 一般就是源文件编译生成的".o"文件,这些文件用于与其它目标文件进行链接生成可执行文件或动态链接库。

(2)共享目标文件, 即动态链接库文件, 就是".so"文件。

(3)可执行文件, 经过链接的,可以执行的程序文件。

ELF文件格式

ELF文件格式入门

因为ELF格式的文件有些是用来连接的,有些是可以执行的。所以上面中从“连接”和“运行”两个视角来看ELF文件的格式。

ELF文件主要由四部分组成:

(1)ELF文件头

(2)程序头表

(3)节(Section)或段

(4)节头表或段头表

1、ELF文件头

描述文件的主要特性:类型,CPU架构,入口地址,现有部分的大小和偏移等。

文件头数据结构如下:

#define EI_NIDENT  16

typedef struct elf64_hdr {
  unsigned char  e_ident[EI_NIDENT]; //魔术字,用来确认文件是否是一个ELF文件
  Elf64_Half e_type;  //文件类型,比如可重定位文件,可执行文件等
  Elf64_Half e_machine; //处理器架构
  Elf64_Word e_version; //ELF文件格式的版本
  Elf64_Addr e_entry;   //程序入口的虚拟地址
  Elf64_Off e_phoff;    //程序头表开始处在文件中的偏移量
  Elf64_Off e_shoff;   //节头表开始处在文件中的偏移量
  Elf64_Word e_flags;  //处理器特定的标志位
  Elf64_Half e_ehsize; //ELF文件头的大小
  Elf64_Half e_phentsize; //在程序头表中每一个表项的大小
  Elf64_Half e_phnum; //程序头表中总共有多少个表项
  Elf64_Half e_shentsize; //节头表中每一个表项的大小
  Elf64_Half e_shnum;  //节头表中每一个表项的大小
  Elf64_Half e_shstrndx; //节头表中与节名字表相对应的表项的索引
} Elf64_Ehdr;      

2、程序头表

程序头表就是一个数组,里面的每个元素都是一个程序头。每一个程序头描述了一个“段(segment)”。一个“段(segment)”包含一个或者多个“节(section)”。

程序头的数据结构如下:

typedef struct elf64_phdr {
  Elf64_Word p_type;   //段的类型
  Elf64_Word p_flags;  //段属性,处理器特定的标志位
  Elf64_Off p_offset;  //段内容在文件中的位置
  Elf64_Addr p_vaddr;  //段内容的开始位置在进程空间中的虚拟地址
  Elf64_Addr p_paddr;  //段内容的开始位置在进程空间中的物理地址
  Elf64_Xword p_filesz;//段内容在文件中的大小
  Elf64_Xword p_memsz; //段内容在内存中的大小
  Elf64_Xword p_align; //段对齐
} Elf64_Phdr;      

*注意: 程序头只对可执行文件或共享目标文件有意义,对于其它类型的目标文件,该信息可以忽略。

3、节头表

节头表也是一个数组。节头表的每一个表项描述了节的信息,通过每一个表项可以定位到对应的节。

节头的数据结构如下:

typedef struct elf64_shdr {
  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;      

4.节(Section)

节里面就是代码和数据。比如".text", “.data” , ".rodata"等。如下图:

ELF文件格式入门
  • .text : 就是存放代码的节
  • .rodata: 存放只读数据的节
  • .data: 存放读写数据的节

在 ELF文件中有一些特定的节是预定义好的,其内容是指令代码或者控制信息。比如:“.text”, “.bss”, ".data"等都是预定义的节。

应用程序也可以构造自己的段,但最好不要取与上述系统已定义的节相同的名字,也不要取以点号开头的名字,以避免潜在的冲突。

更多定义可参考下面文档:

​​https://paper.seebug.org/papers/Archive/refs/elf/Understanding_ELF.pdf​​

实例分析

我们自己写一个应用程序,然后通过readelf命令来分析elf文件。

(1)读取可执行文件的ELF文件头

readelf -h timer      
ELF文件格式入门

这些解析完的参数就是对应上面的ELF文件头的结构体。

上面的文件类型就是可执行文件。

(2)读取.o文件的ELF文件头

readelf -h timer.o      
ELF文件格式入门

上面的文件类型为可重定位文件。

(3)读取可执行文件的程序头

readelf -l timer      
ELF文件格式入门

从上面可以看到总共有9个程序头,也就有9个段。这9个段和节之间的映射关系如下:

ELF文件格式入门

总结

看完这些是不是觉得没什么用,好像开发中也很少用到。但如果有一天你的项目需要你写链接脚本时,可能会从中找到一些有用的东西!

继续阅读