我们通过《程序员的自我修养》学习笔记(二)————目标文件(1):格式大致了解了ELF文件的轮廓,接着就来看看ELF文件的结构格式。
![](https://i-blog.csdnimg.cn/blog_migrate/70cb0af974f0331ba0ede0bf8c3ab4a9.png)
ELF目标文件格式的最前部是ELF文件头(ELF Header),它包含了描述整个文件的基本信息,比如ELF的文件版本、目标机器型号、程序入口地址等。紧接着是ELF文件各个段。其中ELF文件中与段有关的重要结构就是段表(Section Header Table),该表描述了ELF文件包含的所有段的信息,比如每个段的段名、长度、在文件中的偏移、读写权限等。
1. 文件头
使用readelf查看ELF文件,如图2所示:
![](https://i-blog.csdnimg.cn/blog_migrate/d7429faf18ff650440e8790488e18101.png)
从上面输出的结构可以看到:ELF文件头定义了ELF魔数、文件机器字节长度、数据存储方式、版本、运行平台等。
ELF文件头结构及相关常数被定义在“/usr/include/elf.h”,因为ELF文件在各种平台下都通用,ELF文件有32位版本和64位版本的ELF文件的文件头内容是一样的,只不过有些成员的大小不一样。它的文件图也有两种版本:分别叫 “Elf32_Ehdr” 和 “Elf64_Ehdr”。
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;
查看ELF文件头结构和前面图2的信息,可以看出基本一致。但,“Elf64_Ehdr”中的e_ident对应了readelf输出结果中的“Class”、“Data”、“Version”、“OS/ABI”和“ABI Version”这5个参数。其他的,可以一一对应。
成员 | readelf 输出结果与含义 |
e_ident | Magic:7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX-System V ABI Version: 0 |
e_type | Type: REL (Relocatable file) ELF文件的类型 |
e_machine | Machine: Advanced Micro Devices X86-64 ELF文件的CPU平台属性,相关常量以EM_开头 |
e_version | Version:0x1 ELF版本号 |
e_entry | Entry point address: 0x0 入口地址,规定ELF程序的入口虚拟地址,操作系统在加载完程序后从这个地址开始执行进程的指令。可重定位文件一般没有入口地址,则这个值为0 |
e_phoff | Start of program headers: 0 (bytes into file) |
e_shoff | Start of section headers: 392 (bytes into file) 段表在文件中的偏移。 |
e_word | Flags: 0x0 标志位 |
e_ehsize | Size of this header: 64 (bytes) 即ELF文件头本身的大小 |
e_phentsize | Size of program headers: 0 (bytes) |
e_phnum | Number of program headers: 0 |
e_shentsize | Size of section headers: 64 (bytes) 段表描述符的大小,一般等于sizeof(Elf64_Ehdr) |
e_shnum | Number of section headers:13 段表描述符数量 |
e_shstrndx | Section header string table index: 10 段表字符串表所在的段在段表中的下标。 |
ELF魔数
![](https://i-blog.csdnimg.cn/blog_migrate/bd53bf76f18c59af1c50d98712203a7f.png)
文件类型
e_type表示ELF的文件类型
![](https://i-blog.csdnimg.cn/blog_migrate/b3174a9bc85b252816c93c758aefa798.png)
机器类型
e_machine表示该ELF平台的属性
![](https://i-blog.csdnimg.cn/blog_migrate/58c5fd64f0fec910517982378b49e3a1.png)
2 段表
ELF文件有很多段,这个段表(Section Header Table)就是保存这些段的基本属性的结构。ELF文件的段结构就是由段表来决定的,编译器、链接器和装载器都是依靠段表来定位和访问各个段的属性的。段表在ELF文件中的位置由ELF头文件的“e_shoff”成员决定。
![](https://i-blog.csdnimg.cn/blog_migrate/0fa2eab56d79587631346edf85430ef6.png)
段表的结构比较简单,是以“Elf64_Shdr”结构体的元素的数组。ELF段表的这个数组的第一个元素是无效的,其类型为“NULL”。
typedef struct
{
Elf64_Word sh_name; /* Section name (string tbl index) */
Elf64_Word sh_type; /* Section type */
Elf64_Xword sh_flags; /* Section flags */
Elf64_Addr sh_addr; /* Section virtual addr at execution */
Elf64_Off sh_offset; /* Section file offset */
Elf64_Xword sh_size; /* Section size in bytes */
Elf64_Word sh_link; /* Link to another section */
Elf64_Word sh_info; /* Additional section information */
Elf64_Xword sh_addralign; /* Section alignment */
Elf64_Xword sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;
![](https://i-blog.csdnimg.cn/blog_migrate/9b8e68ffa97fc629d89e8d91c398c4bd.png)
段的类型
对编译器和链接器来说,主要决定段的属性的是段的类型(sh_type)和段的标志位(sh_flag)。
![](https://i-blog.csdnimg.cn/blog_migrate/a09ed2455c4b510e47ac3c7dac103d96.png)
段的标志位
段的标志位(sh_flag)表示该段在进程虚拟地址空间中的属性。
![](https://i-blog.csdnimg.cn/blog_migrate/ccd8c89390f630cffa718b9b9c552872.png)
段的链接信息
如果段的类型是与链接有关的,比如重定位表、符号表等,那么sh_link、sh_info的含义如下表所示:
![](https://i-blog.csdnimg.cn/blog_migrate/0f66ec7f76402ec1a6ae761c687d8e25.png)
3 重定位表
从图6中可以看到,有一个段叫做“.rel.text”,它的类型为“SHT_RELA”,也就是重定位表。“.rel.text”针对的是“.text”段的重定位表。它的“sh_link”表示符号表的下标,它的“sh_info”表示它作用于哪个段。比如“.rel.text”作用于“.text”段,而“.text”段的下标为“1”,所以“sh_info”为“1”。
4 字符串表
ELF文件中用到很多字符串,比如段名、变量名等。因为字符串的长度往往不固定,所以用固定的结构来表示比较困难。一种常见的做法是把字符串集中起来存放到一个表,然后使用字符串在表中的偏移来引用字符串。
![](https://i-blog.csdnimg.cn/blog_migrate/66b48599d83662066be0c575d821ec85.png)
![](https://i-blog.csdnimg.cn/blog_migrate/60258ba987532ea178cff540bf636c62.png)
通过这种方法,在ELF文件中引用字符串只需要给出一个数字下标即可,而不考虑字符串长度的问题。这也是段表结构“Elf64_Shdr”成员sh_name类型为Elf64_Word(uint32_t)的原因。一般字符串表在ELF文件中也已段的形式保存,常见段名为“.strtab”或“.shstrtab”,即字符串表和段表字符串表。字符串表用来保存普通的字符串,段表字符串表用来保存段表中用到的字符串,最常见的就是段名(sh_name)。
ELF头文件结构Elf64_Ehdr中的“e_shstrndx”表示“.shstrtab”在段表中的下标,即段表字符串表在段表中的下标。
5 符号
在链接中,我们将函数和变量统称为符号,函数名和变量名就是符号名。链接过程中很关键的一部分就是符号的管理,每一个目标文件都会有一个相应的符号表(Symbol Table),这个表里面记录了目标文件中所用到的所有符号。每个定义的符号有一个对应值,叫做符号值(Symbol Value),对于变量和函数来说,符号值就是它们的地址。我们可以使用readelf、nm等命令查看符号。
![](https://i-blog.csdnimg.cn/blog_migrate/9c59030de73ad4daad09381befe4bb09.png)
ELF文件中符号表也是一个段,段名叫“.symtab”。表的结构是“Elf64_Sym”的数组。
typedef struct
{
Elf64_Word st_name; /* Symbol name (string tbl index) */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf64_Section st_shndx; /* Section index */
Elf64_Addr st_value; /* Symbol value */
Elf64_Xword st_size; /* Symbol size */
} Elf64_Sym;
![](https://i-blog.csdnimg.cn/blog_migrate/b24dab78ed8192fe00efe90d99574688.png)
符号类型和绑定信息(st_info)
该成员低4位表示符号的类型(Symbol Type),高28位表示符号绑定信息(Symbol Binding),如图15、图16所示。
![](https://i-blog.csdnimg.cn/blog_migrate/d200d6fdbdd5806d26b0fe90655e9696.png)
![](https://i-blog.csdnimg.cn/blog_migrate/2f9207cd5b2efd0a2a4e217a1ee1e67b.png)
符号所在段(sh_shndx)
如果符号定义在本目标文件中,那么这个成员表示符号所在的段在段表中的下标;但是如果符号不是定义在本目标文件中,或者对于有些特殊符号,sh_shndx的值有些特殊,如图17所示。
![](https://i-blog.csdnimg.cn/blog_migrate/7047e0193e9d3ad02a29f714921e15f7.png)
符号值(st_value)
如果这个符号是一个函数或者变量的定义,那么符号的值就是这个函数或者变量的地址。
参考文件:
https://www.jianshu.com/p/10b2e410aed0
https://www.cnblogs.com/linhaostudy/p/8855238.html
https://download.csdn.net/download/mikeiii/747643
《程序员的自我修养——链接、装载与库》