什么是ELF
ELF(Executable and Linking Format),即“可执行可连接格式”,最初由 UNIX系统实验室做为应用程序二进制接口(ABI)的一部分而制定和发布。简单说就是一种文件格式。
ELF文件类型
(1)可重定位文件, 一般就是源文件编译生成的".o"文件,这些文件用于与其它目标文件进行链接生成可执行文件或动态链接库。
(2)共享目标文件, 即动态链接库文件, 就是".so"文件。
(3)可执行文件, 经过链接的,可以执行的程序文件。
ELF文件格式
因为ELF格式的文件有些是用来连接的,有些是可以执行的。所以上面中从“连接”和“运行”两个视角来看ELF文件的格式。
ELF文件主要由四部分组成:
(1)ELF文件头
(2)程序头表
(3)节(Section)或段
(4)节头表或段头表
1、ELF文件头
描述文件的主要特性:类型,CPU架构,入口地址,现有部分的大小和偏移等。
文件头数据结构如下:
#define EI_NIDENT 16typedef 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"等。如下图:
-
.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文件头的结构体。
上面的文件类型就是可执行文件。
(2)读取.o文件的ELF文件头
readelf -h timer.o
上面的文件类型为可重定位文件。
(3)读取可执行文件的程序头
readelf -l timer
从上面可以看到总共有9个程序头,也就有9个段。这9个段和节之间的映射关系如下:
总结
看完这些是不是觉得没什么用,好像开发中也很少用到。但如果有一天你的项目需要你写链接脚本时,可能会从中找到一些有用的东西!
往期推荐:
【通信协议】一文搞懂SPI
嵌入式到底应该选择驱动开发,还是应用开发?
【通信协议】一文搞懂I2C
推荐几款串口监控软件
一文搞懂UART