一文读懂ELF文件格式(elf文件格式详解pdf)

2023-03-22 22:56:18

ELF的英文全称是The Executable and Linking Format,最初是由UNIX系统实验室开发、发布的ABI(Application Binary Interface)接口的一部分,也是Linux的主要可执行文件格式。

引言我们来回顾一下,一个用C语言编写的高级语言程序是从编写到打包、再到编译执行的基本过程。

在Unix系统中,从源文件到可执行目标文件是由编译驱动程序完成的,如大名鼎鼎的gcc,翻译过程包括图中的四个阶段;Ø 预处理阶段预处理器(cpp)根据以字符#开头的命令修改原始的C程序,结果得到另一个C程序,通常以.i作为文件扩展名。

主要是进行文本替换、宏展开、删除注释这类简单工作对应的命令:linux> gcc -E hello.c hello.iØ 编译阶段编译器将文本文件hello.i翻译成hello.s,包含相应的汇编语言程序。

对应的命令:linux> gcc -S hello.c hello.sØ 汇编阶段将.s文件翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件.o中(把汇编语言翻译成机器语言的过程)。

把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成主要是进行词法分析和语法分析,又称为源程序分析,分析过程中发现有语法错误,给出提示信息。

对应的命令:linux> gcc -c hello.c hello.oØ 链接阶段此时hello程序调用了printf函数 printf函数存在于一个名为printf.o的单独的预编译目标文件中 链接器(ld)就负责处理把这个文件并入到hello.o程序中,结果得到hello文件,一个可执行文件。

最后可执行文件加载到储存器后由系统负责执行, 函数库一般分为静态库和动态库两种静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了其后缀名一般为.a。

动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销动态库一般后缀名为.so,gcc在编译时默认使用动态库从使用上来说,主要的ELF文件的种类主要有三类:

可执行文件(.out):Executable File,包含代码和数据,是可以直接运行的程序其代码和数据都有固定的地址 (或相对于基地址的偏移 ),系统可根据这些地址信息把程序加载到内存执行可重定位文件(.o文件):Relocatable File。

,包含基础代码和数据,但它的代码及数据都没有指定绝对地址,因此它适合于与其他目标文件链接来创建可执行文件或者共享目标文件共享目标文件(.so):Shared Object File,也称动态库文件,包含了代码和数据,这些数据是在链接时被链接器(ld)和运行时动态链接器(ld.so.l、libc.so.l、ld-linux.so.l)使用的。

ELF文件的基本格式

从程序执行视角来说,这就是Linux加载器加载的各种Segment的集合比如只读代码段、数据的读写段、符号段等等而从链接的视角上来看,elf又分为各种的sections注意Section Header Table和Program Header Table并不是一定要位于文件开头和结尾的,其位置由ELF Header指出,上图这么画只是为了清晰。

ELF文件的头部信息对于elf头部文件信息,首先可以可以查看一下内存的布局情况:

根据readelf可以得到该文件的头部信息的情况。

根据定义,elf32的结构体定义,在Linux上可以在/usr/include/elf.h中找到#define EI_NIDENT (16)typedefstruct {unsignedchar e_ident[EI_NIDENT];

/* Magic number and other info */ Elf32_Half e_type; /* Object file type */ Elf32_Half e_machine;

/* Architecture */ Elf32_Word e_version; /* Object file version */ Elf32_Addr e_entry;

/* Entry point virtual address */ Elf32_Off e_phoff; /* Program header table file offset */

Elf32_Off e_shoff; /* Section header table file offset */ Elf32_Word e_flags;

/* Processor-specific flags */ Elf32_Half e_ehsize; /* ELF header size in bytes */

Elf32_Half e_phentsize; /* Program header table entry size */ Elf32_Half e_phnum;

/* Program header table entry count */ Elf32_Half e_shentsize; /* Section header table entry size */

Elf32_Half e_shnum; /* Section header table entry count */ Elf32_Half e_shstrndx;

/* Section header string table index */ } Elf32_Ehdr;有了这些数据结构的信息,然后对应具体的数据细节如下:e_ident[EI_NIDENT]文件的标识以及标识描述了elf如何编码等信息。

Magic:7f454c46010101000000000000000000关于该结构体的索引可以看下面的表格:名称取值目的EI_MAG00文件标识(0x7f)EI_MAG11文件标识(E)EI_MAG2

2文件标识(L)EI_MAG33文件标识(F)EI_CLASS4文件类EI_DATA5数据编码EI_VERSION6文件版本EI_PAD7补齐字节开始处EI_NIDENT16e_ident[]大小EI_CLASS的内容,当取值为0时,是非法类别,1是32位的目标,2是64位的目标。

这里是1所以程序是32位的目标EI_DATA表示数据的编码,当为0时,表示非法数据编码,1表示高位在前,2表示低位在前EL_VERSION表示了elf的头部版本号码前面四个基本上确定的,内容第一个字符为7f,后面用ELF字符串表示该文件为ELF格式。

e_type该数据类型是uint16_t数据类型的,占两个字节通过字段查看,可以看到这个值为00 02表格定义如下:名称取值含义ET_NONE0x0000未知目标文件格式ET_ERL0x0001可重定位文件。

ET_EXEC0x0002可执行文件ET_DYN0x0003共享目标文件ET_CORE0x0004Core文件(转储格式)ET_LOPROC0xff00特定处理器文件ET_HIPROC0xffff特定处理器文件

对应表格内容,可以看到类型为EXEC即可执行文件类型e_machine由字段可以看到为00 28,关于这个字段的解析,基本上就是表示该elf文件是针对哪个处理器架构的下面只列出几个常见的架构的序号名称取值

含义EM_NONE0No machineEM_SPARC2SPARCEM_3863Intel 80386EM_MIPS8MIPS I ArchitectureEM_PPC0x14PowerPCEM_ARM

0x28Advanced RISC Machines ARM通过上述的表格,可以看到该架构是ARM处理器上运行的程序e_version该字段占四个字节,表示当前文件版本的信息现在取值为00 00 00 01。

从取值上来看名称取值含义EV_NONE0非法版本EV_CURRENT1当前版本e_entry这里表示程序的入口地址,目前为四字节,所以通过字段解析到的内容为00 00 80 00得到可执行程序的入口地址为。

0x8000e_phoff该字段表示程序表头偏移占四个字节,根据字段解析,可以查看当前的偏移量为00 00 00 34也就是实际的偏移量为52个字节这52个字节其实就是头部的信息数据结构体的大小e_shoff

该区域比较重要,记录了section的偏移地址。为四字节,解析出来的字段为0x00 04 24 5c。所以得到地址为0x4245c。

根据这个偏移得到section的内容:

通过readelf -t也可以得到类似的结果。

关于节区如何解析后面再进行描述e_flags特定处理器格式的标志,这里的字段解析为05 00 02 00与特定的处理器相关e_ehsizeelf文件的头部大小该取值与头文件结构体的大小相关,目前为52字节,即。

00 34。e_phentsize程序头部表项大小,当前取值为00 20,为32个字节,这里表示

关于程序表项的解析,后面再进行具体分析e_phnum目前取值为00 01,这里表示程序头的个数当前只有一个程序头,如果有多个程序头表,那么会在elf头文件之后,也就是52个字节之后,依次向下排列因为这里是1,所以只有1个程序头。

e_shentsize表示节区头部表格大小,解析字段为00 28,也就是第一个节区的大小为40个字节的偏移处。根据e_shoff可以知道。

将从e_shoff的区域向后面偏移40个字节,得到第一个节区的内容e_shnum节区的数量,由字段解析得到数据为00 11此时得到节区的数量为17个通过readelf -t也可以解析到节区的数量为17个。

bigmagic@bigmagic:~/work/python_elf/elf$ readelf -t rtthread.elf There are 17 section headers, starting at offset

0x4245c:e_shstrndx标记字符串节区的索引。当前的解析为00 0e。也就是14个节区为字符节区。

到这里,头部信息的相关字段就解析完成了4.elf文件的节区(Section)elf文件中的节是从编译器链接角度来看文件的组成的从链接器的角度上来看,包括指令、数据、符号以及重定位表等等4.1 节区的作用。

在可从定位的可执行文件中,节区描述了文件的组成,节的位置等信息。通过readelf -s可以查看信息。

这些节信息通过特定的地址偏移组成了一个elf文件的整体4.2 节区的组成关于理解ELF中的Section首先需要知道程序的链接视图,在编译器将一个一个.o文件链接成一个可以执行的elf文件的过程中,同时也生成了一个表。

这个表记录了各个Section所处的区域在程序中,程序的section header有多个,但是大小是一样拿elf32文件来说typedefstruct { Elf32_Word sh_name; 。

/* Section name (string tbl index) */ Elf32_Word sh_type; /* Section type */ Elf32_Word sh_flags;

/* Section flags */ Elf32_Addr sh_addr; /* Section virtual addr at execution */ Elf32_Off sh_offset;

/* Section file offset */ Elf32_Word sh_size; /* Section size in bytes */ Elf32_Word sh_link; /* Link to another section */

Elf32_Word sh_info; /* Additional section information */ Elf32_Word sh_addralign; /* Section alignment */

Elf32_Word sh_entsize; /* Entry size if section holds table */ } Elf32_Shdr;根据e_shoff可以找到section的地址,根据

e_shentsize可以找到具体的第一个section的内容。

如果要找到每个段的具体细节,首先可以根据e_shstrndx找到节的字段由于e_shstrndx=14而且每个为40字节那么一共是560字节的偏移从e_shoff的地址0x4245c开始,首先偏移了e_shentsize

也就是40个字节。然后向下得到40x14个Section表项。最后可以得到e_shstrndx对应的节区。

为什么首先需要得到这个字符串节区,通过这个就可以得到节区的名字了。然后通过计算,节区字符串存在的区域:

每个字符串以\0结尾。大小为0000ab也就是171个字节。接下来我们来举个具体的例子来解析Section。比如要读取.text的段。那么首先看一下细节。

首先从字段结构体上进行分析:sh_name表示从e_shstrndx的偏移地址开始,得到的字符字符串信息为该段的名字。目前解析到的为0x1b。最后算出得到实际的名称为.text。

sh_type字段的类型为01,关于sh_type的类型,解析如下:/* Legal values for sh_type (section type). */#define SHT_NULL 0

/* Section header table entry unused */#define SHT_PROGBITS 1 /* Program data */#define

SHT_SYMTAB 2 /* Symbol table */#define SHT_STRTAB 3 /* String table */

#define SHT_RELA 4 /* Relocation entries with addends */#define SHT_HASH 5

/* Symbol hash table */#define SHT_DYNAMIC 6 /* Dynamic linking information */#define SHT_NOTE 7

/* Notes */#define SHT_NOBITS 8 /* Program space with no data (bss) */#define SHT_REL 9

/* Relocation entries, no addends */#define SHT_SHLIB 10 /* Reserved */#define SHT_DYNSYM 11

/* Dynamic linker symbol table */#define SHT_INIT_ARRAY 14 /* Array of constructors */#define

SHT_FINI_ARRAY 15 /* Array of destructors */#define SHT_PREINIT_ARRAY 16 /* Array of pre-constructors */

#define SHT_GROUP 17 /* Section group */#define SHT_SYMTAB_SHNDX 18 /* Extended section indeces */

#define SHT_NUM 19 /* Number of defined types. */#define SHT_LOOS 0x60000000

/* Start OS-specific. */#define SHT_GNU_ATTRIBUTES 0x6ffffff5 /* Object attributes. */#define SHT_GNU_HASH 0x6ffffff6

/* GNU-style hash table. */#define SHT_GNU_LIBLIST 0x6ffffff7 /* Prelink library list */#define

SHT_CHECKSUM 0x6ffffff8 /* Checksum for DSO content. */#define SHT_LOSUNW 0x6ffffffa

/* Sun-specific low bound. */#define SHT_SUNW_move 0x6ffffffa#define SHT_SUNW_COMDAT 0x6ffffffb

#define SHT_SUNW_syminfo 0x6ffffffc#define SHT_GNU_verdef 0x6ffffffd /* Version definition section. */

#define SHT_GNU_verneed 0x6ffffffe /* Version needs section. */#define SHT_GNU_versym 0x6fffffff

/* Version symbol table. */#define SHT_HISUNW 0x6fffffff /* Sun-specific high bound. */#

define SHT_HIOS 0x6fffffff /* End OS-specific type */#define SHT_LOPROC 0x70000000

/* Start of processor-specific */#define SHT_HIPROC 0x7fffffff /* End of processor-specific */

#define SHT_LOUSER 0x80000000 /* Start of application-specific */#define SHT_HIUSER 0x8fffffff

/* End of application-specific */当前为1,所以得到数据为程序数据比如.text .data .rodata等等sh_flags表示段的标志,A表示分配的内存、AX表示分配可执行、WA表示分配内存并且可以修改。

sh_addr加载后程序段的虚拟地址sh_offset表示段在文件中的偏移sh_size段的长度sh_addralign段对齐sh_entsize每项固定的大小5.elf文件的段(Segment)关于Linking View

与Execution View的具体含义,可以查看http://www.skyfree.org/linux/references/ELF_Format.pdf这里有一张图值得研究一下:

对于链接视图,也就是我们前面分析的Section,可以理解目标代码文件的内容布局而右边的ELF的执行视图,则可以理解为可执行的文件内容布局链接视图由sections组成,而可执行的文件的内容由segment组成。

两者是有一些区别的,我们平时在进行程序构建的时候理解的.text、.bss、.data段,这些都是section,也就节区的概念这些段通过section header table进行组织与重定位但是对于segment来说,程序代码段、数据段是Segment。

代码段又可以分为.text,数据段又分为.data、.bss等通过readelf -l可以查看具体的可执行文件的细节。

这里的信息和程序的加载直接相关。具体的elf文件加载过程这篇文章不会多说,后面会写文章专门叙述。本文的目的是elf文件格式的解析过程。

 


以上就是关于《一文读懂ELF文件格式(elf文件格式详解pdf)》的全部内容,本文网址:https://www.7ca.cn/baike/6872.shtml,如对您有帮助可以分享给好友,谢谢。
标签: ELF 文件格式
声明