欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

浅析elf格式的二进制文件

程序员文章站 2022-07-15 22:13:09
...

elf格式的二进制文件

elf指的是executable and linkable format,可执行链接格式。最初由UNIX系统实验室作为应用程序二进制接口(ABI)而开发的。后来发展成了二进制文件格式标准,Linux操作系统下的可执行程序使用的就是该标准。

elf格式文件的产生

在Linux下代码的编译链接后的生成文件都要符合elf文件格式。将c代码编译链接后生成的二进制可执行文件,该文件可以直接运行。只经过编译过的文件称为目标文件或者待重定位文件(relocatable file)。

#以gcc编译器为例:现有hello.c文件
#生成待重定位文件
gcc -c hello.c -o hello.o   #hello.o文件就是待重定位文件
#生成可执行文件
gcc hello.c -o hello   #hello文件就是可执行文件

elf目标文件有待重定位文件,可共享目标文件,可执行文件。这里重点描述可执行文件。

节、段

节(section)是指在汇编源码中由section或segment修饰,的指令区域或者数据区域。不同的汇编器关键字可能不一样。汇编器将这两个关键字修饰的区域在目标文件中编译成节,所以说节是在待重定位文件目标文件时产生的。
段(segment)是链接器根据待重定位文件中属性相同的多个section合并成section集合,这个集合就是段(segment)。链接器最后生成的是可执行文件,所以段是在可执行文件中生成的。平时所说的数据段,代码段就是这里说的segment。

节头表、程序头表、节头、程序头

一个程序中,节和段的大小和数量是不固定的,所以就需要一个专门的表来描述它们,这个表就是所说的节头表(section header table)和程序头表(program header table)。节头表中描述的信息是多个节头(section header),段头表中描述的信息是多个段头(program header)。
在表中,每一个成员称为条目,及entry,一个条目代表一个段或者一个节的头信息。

elf header

因为程序中的段和节的大小和数量不确定,所以程序头表和节头表的大小就是不确定的,表在程序文件中的存储顺序也是由先后的,所以这两个表的位置也是不确定的。这时就必须在用一个固定的结构来描述这些不确定的信息,这个结构就是elf header,它位于文件最开始的部分。这里就想感叹一下计算机中的分层结构。

elf文件格式作用是在文件链接和运行时方便进行程序的重定位。

elf结构

这里给出elf header结构体,然后通过实例来分析结构体中的内容。其他结构可查看/usr/include/elf.h获得。

这里分析的程序是32位的,64位的程序在分析后也很好理解。
现有程序hello.c,编译链接后的hello
使用xxd命令查看二进制文件,使用readelf查看elf文件信息,以此对照着看

gcc -m32 hello.c -o hello
xxd hello
readelf -h hello

这里先声明要用到的数据类型

数据类型名称 字节大小 对齐 意义 声明
ELF32_Half 2 2 无符号中等大小的整数 typedef uint16_t Elf32_Half;
ELF32_Word 4 4 无符号大整数 typedef uint32_t Elf32_Word;
ELF32_Addr 4 4 无符号程序运行地址 typedef uint32_t Elf32_Addr;
ELF32_Off 4 4 无符号的文件偏移量 typedef uint32_t Elf32_Off;
//elf header结构
typedef struct
{
  unsigned char	e_ident[EI_NIDENT];	/* Magic number and other info */ //elf文件的魔数
  Elf32_Half	e_type;			//elf文件的格式
  Elf32_Half	e_machine;	 //描述elf文件的体系结构类型
  Elf32_Word	e_version;		 //版本信息
  Elf32_Addr	e_entry;		 //操作系统运行程序时,将控制权交到的虚拟地址
  Elf32_Off	e_phoff;		//程序头表在文件内的偏移量
  Elf32_Off	e_shoff;		//节头表在文件内的偏移量
  Elf32_Word	e_flags;		//与处理器相关的标志
  Elf32_Half	e_ehsize;		 //elf header的大小
  Elf32_Half	e_phentsize;		 //程序头表中每个条目的大小
  Elf32_Half	e_phnum;		 //程序头表中条目的数量
  Elf32_Half	e_shentsize;		//节头表中每个条目的大小
  Elf32_Half	e_shnum;		 //节头表中条目的数量
  Elf32_Half	e_shstrndx;		//string name table在节头表中的索引
} Elf32_Ehdr;
# xxd hello的结果,只截取了elf header相关的部分
00000000: 7f45 4c46 0101 0100 0000 0000 0000 0000   .ELF............
00000010: 0300 0300 0100 0000 e003 0000 3400 0000  ............4...
00000020: a817 0000 0000 0000 3400 2000 0900 2800  ........4. ...(.
00000030: 1d00 1c00 0600 0000 3400 0000 3400 0000  ........4...4...

#readelf -h hello的结果
ELF 头:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  类别:                              ELF32
  数据:                              2 补码,小端序 (little endian)
  版本:                              1 (current)
  OS/ABI:                            UNIX - System V
  ABI 版本:                          0
  类型:                              DYN (共享目标文件)
  系统架构:                          Intel 80386
  版本:                              0x1
  入口点地址:               0x3e0
  程序头起点:          52 (bytes into file)
  Start of section headers:          6056 (bytes into file)
  标志:             0x0
  本头的大小:       52 (字节)
  程序头大小:       32 (字节)
  Number of program headers:         9
  节头大小:         40 (字节)
  节头数量:         29
  字符串表索引节头: 28

文件起始的十六字节对应的是elf结构体中的魔数部分,在readelf中可以看到是相符的。往后可以按照xxd的结果和readelf的结果对照,都是一一对应的。根据数据类型的字节大小很好分析,这里就不在赘述。