一、对象文件格式(Object file format)
ELF 文件标准里把系统中采用ELF 格式的文件归类为四种:
可重定位文件,Relocatable File ,这类文件包含代码和数据,可用来连接成可执行文件或共享目标文件,静态链接库归为此类,对应于Linux 中的.o ;Windows 的 .obj.
可执行文件,Executable File ,这类文件包含了可以直接执行的程序,它的代表就是ELF 可执行文件。Linux下,他们一般没有扩展名,比如/bin/bash ;Windows 下的 .exe
共享目标文件,Shared Object File ,这种文件包含代码和数据,链接器可以使用这种文件跟其他可重定位文件的共享目标文件链接,产生新的目标文件。另外是动态链接器可以将几个这种共享目标文件与可执行文件结合,作为进程映像来运行。对应于Linux 中的 .so,Windows 中的 DLL
核心转储文件,Core Dump File,当进程意外终止,系统可以将该进程地址空间的内容及终止时的一些信息转存到核心转储文件。 对应 Linux 下的core dump。
现在PC平台流行的可执行文件格式(Executable File Format),主要是Windows下的PE(Portable Executable)和Linux的ELF(Executable and Linking Format,可执行和链接格式)。他们都是COFF(Common Object File Format)的变种。ARM架构采用的也是ELF文件格式。
COFF是在Unix System V Release 3时由 UNIX 系统实验室(UNIX System Laboratories, USL)首先提出并且使用的文件规范,后来微软公司基于COFF格式,制定了PE格式标准,并将其用于当时的Windows NT系统。在System V Release 4时,UNIX系统实验室(USL)在COFF的基础上,开发和发布了ELF格式,作为应用程序二进制接口 (Application Binary Interface(ABI)。
此后,工具接口标准委员会(TIS)选择了正在发展中的ELF标准作为工作在32位INTEL体系上不同操作系统之间可移植的二进制文件格式。
由于ELF文件具有通用性强的优点,现在流行的开发模式是:先通过编译工具生成ELF文件格式的可执行文件,在使用外部工具,抽离出ELF文件中的相应部分,生成BIN文件。著名的GNU、bootloader、U-Boot就采用了这种做法。例如,编译器工具集GCC的BIN生成工具是elf2bin。ARM公司虽然使用的是自家的armcc编译器,但是也提供了fromelf工具来实现上面的方式。
二、ARM ELF文件格式
ARM的ELF文件总体来说和标准ELF并没有多少差别。符合ELF的两种组织方式:连接视图和运行视图。
与标准的ELF文件相比,ARM ELF的某些值比较特殊,下面以实际文件来说明一下每个部分。编译工具如下图:
图1 工具
编译后,会在对应目录下生成.o文件和axf文件,为了分析ELF文件,我们将使用readelf工具。在详细解析之前,先用Winhex直接打开生成的.o文件,可以看到文件开头有ELF字样。表明它是一个ELF文件。如下:
图2 ELF文件
注意:.o 不是ARM的可执行文件!axf为可执行文件。以下用两种程序作对比。
(1)ELF Header
ELF Header描述了体系结构和操作系统等基本信息,并指出Section Header Table和Program Header Table在文件中的什么位置。
Program Header Table在汇编和链接过程中没有用到, 所以在重定位文件中可以没有; Section Header Table中保存了所有Section的描述信息,Section Header Table在加载过程中没有用到, 对于可执行文件,可以没有该部分。
当然,对于某些类型的文件来说,可以同时拥有Program header table和Section Header Table,这样load完后还可以relocate。(例如:shared objects)
图3 .o文件ELF Header
图4 .axf文件ELF Header
ELF Header可以使用如下数据结构表示(对应关系见注释):
#define EI_NIDENT 16
typedef struct {
unsigned char e_ident[EI_NIDENT]; // Magic
Elf32_Half e_type; // Type
Elf32_Half e_machine; // Machine
Elf32_Word e_version; // Version
Elf32_Addr e_entry; // Entry point address
Elf32_Off e_phoff; // Start of program headers
Elf32_Off e_shoff; // Start of section headers
Elf32_Word e_flags; // Flags
Elf32_Half e_ehsize; // Size of this header
Elf32_Half e_phentsize; // Size of program headers
Elf32_Half e_phnum; // Number of program headers
Elf32_Half e_shentsize; // Size of section headers
Elf32_Half e_shnum; // Number of section headers
Elf32_Half e_shstrndx; // Section header string table index
} Elf32_Ehdr;
下面对readelf输出内容做一下详细介绍:
第 1 行ELF Header:指名 ELF 文件头开始。
第 2 行Magic:用来指名该文件是一个 ELF 目标文件,是一个数组,使用以下宏值进行索引:
名称 |
取值 |
意义 |
EI_MAG0 |
0 |
文件标识 |
EI_MAG1 |
1 |
文件标识 |
EI_MAG2 |
2 |
文件标识 |
EI_MAG3 |
3 |
文件标识 |
EI_CLASS |
4 |
文件类 |
EI_DATA |
5 |
数据编码 |
EI_VERSION |
6 |
文件版本 |
EI_PAD |
7 |
补齐字节开始处 |
EI_NIDENT |
16 |
e_ident[]大小 |
e_ident[EI_MAG0]~e_ident[EI_MAG3]:包含了ELF文件的魔数,依次是0x7f和'E'、'L'、'F'的ASCII。
e_ident[EI_CLASS]:取值如下
名称 |
取值 |
意义 |
ELFCLASSNONE |
0 |
非法类别 |
ELFCLASS32 |
1 |
32 位目标 |
ELFCLASS64 |
2 |
64 位目标 |
e_ident[EI_DATA]:
名称 |
取值 |
意义 |
ELFDATANONE |
0 |
非法数据编码 |
ELFDATA2LSB |
1 |
高位在前 |
ELFDATA2MSB |
2 |
低位在前 |
选择将由执行环境中的默认数据顺序控制。 在以BE8模式运行的Architecture v6处理器上,所有的指令均为小端格式。 适合在此模式下操作的可执行映像将在e_flags字段中设置EF_ARM_BE8。
e_ident[EI_VERSION]:指定 ELF头部的版本,当前必须为1。
e_ident[7]~e_ident[15]:是填充符,通常是0
第 3 行Class:该值就是e_ident[EI_CLASS]。
第 4 行Data:该值就是e_ident[EI_DATA]。
第 5 行Version:该值就是e_ident[EI_VERSION]
第 6 行OS/ABI:该值应该是e_ident的扩展部分。操作系统类型,ABI 是 Application Binary Interface 的缩写。除非文件使用具有OS特定含义的标志(例如,使用SHN_LOOS通过SHN_HIOS的段索引),否则该字段应为零。 目前,该字段有一个特定于处理器的值,如下。
取值 |
意义 |
ELFOSABI_ARM_AEABI (64) |
该对象包含符号版本控制扩展,如§3.1.1符号版本控制中所述。 |
第 7 行ABI Version: 该值应该是e_ident的扩展部分。版本号,当前为 0 。
第 8 行Type:表示该目标文件类型。(上图中的类型省略了ET_)
名称 |
取值 |
意义 |
说明 |
ET_NONE |
0 |
未知目标文件格式 |
|
ET_REL |
1 |
可重定位文件 |
|
ET_EXEC |
2 |
可执行文件 |
|
ET_DYN |
3 |
共享目标文件 |
|
ET_CORE |
4 |
Core 文件(转储格式) |
|
ET_LOPROC |
0xff00 |
特定处理器文件 |
ET_LOPROC 和 ET_HIPROC 之间的取值用来标识与处理器相关的文件格式。 |
ET_HIPROC |
0xffff |
特定处理器文件 |
第 9 行Machine:机器平台类型。ARM架构为EM_ARM
Name |
Value |
Meaning |
EM_NONE |
0 |
No machine |
EM_M32 |
1 |
AT&T WE 32100 |
EM_SPARC |
2 |
SPARC |
EM_386 |
3 |
Intel Architecture |
EM_68K |
4 |
Motorola 68000 |
EM_88K |
5 |
Motorola 88000 |
EM_860 |
7 |
Intel 80860 |
EM_MIPS |
8 |
MIPS RS3000 Big-Endian |
EM_MIPS_RS4_BE |
10 |
MIPS RS4000 Big-Endian |
…… | ||
EM_ARM |
40 |
ARM/Thumb Architecture |
第 10 行Version:当前目标文件的版本号。
名称 |
取值 |
意义 |
说明 |
EV_NONE |
0 |
Invalid version |
|
EV_CURRENT |
1 |
Current version |
该项的取值可根据需要改变 |
第 11 行Entry point address:程序的虚拟地址入口点。在ARM中:
- 在可执行ELF文件中,e_entry是映像唯一入口点的虚拟地址,如果映像没有唯一入口点,则为0。
- 在可重定位ELF文件中,e_entry是被SHF_ENTRYSECT所标记的段的入口点的偏移量,若没有入口点,则为0。
- Bit[0] = 1,表示Thumb指令;Bit[0:1] = 00,表示ARM指令;Bit[0:1] = 10,保留;
第 12 行Start of program headers:程序头的起始地址,.o文件没有 Program Headers 。
第 13 行Start of section headers:节头的起始地址。图4的 486388 是十进制,即:表示节头是从地址偏移0x76BF4处开始。
第 14 行Flags:是一个与处理器相关联的标志。
名称 |
意义 |
EF_ARM_ABIMASK (0xFF000000) (current version is 0x05000000) |
此ELF文件符合的ARM EABI的版本,该值为一个8比特的掩码。 当前EABI是版本5。0表示未知符合。 |
EF_ARM_BE8 (0x00800000) |
ELF文件包含适合在ARM Architecture v6处理器上执行的BE-8代码。 该标志只能在可执行文件上设置。 |
EF_ARM_GCCMASK (0x00400FFF) |
gcc-arm-xxx生成的旧版代码(ABI版本4及更早版本)可能会使用这些位。 |
EF_ARM_ABI_FLOAT_HARD (0x00000400) (ABI version 5 and later) |
设置可执行文件头(e_type = ET_EXEC或ET_DYN)以标注可执行文件的构建是为了符合硬件浮点过程调用标准。 与旧版(预版本5)兼容,gcc用作EF_ARM_VFP_FLOAT |
EF_ARM_ABI_FLOAT_SOFT (0x00000200) (ABI version 5 and later) |
设置在可执行文件头(e_type = ET_EXEC或ET_DYN)中明确标注可执行文件的构建符合软件浮点过程调用标准(基准标准)。 如果EF_ARM_ABI_FLOAT_XXXX位都清零,则默认符合基本过程调用标准。 与旧版(预版本5)兼容,gcc用作EF_ARM_SOFT_FLOAT。 |
注意:以上部分与ARM早期文档是有区别的,很多值已经不同
第 15 行Size of this header:ELF 文件头的字节数。
第 16 行Size of program headers:Program Headers 大小。.o文件大小为 0。
第 17 行Number of program headers:Program Headers 的数量(可以有多个)。
第 18 行Size of section headers:sections header的大小
第 19 行Number of section headers:sections header的数量。
第 20 行Section header string table index:节区头部表格中与节区名称字符串表相关的表项的索引。如果文件没有节 区名称字符串表,此参数可以为 SHN_UNDEF。
注意:实际文件中,每一部分的位置顺序并不一定完全相同,只有ELF Header位置是绝对的,且只能在最开始。
(2)Section Header(节区头)
节区中包含目标文件中的所有信息,除了:ELF 头部、程序头部表格、节区头部 表格。节区满足以下条件:
(1) 目标文件中的每个节区都有对应的节区头部描述它,反过来,有节区头部不意 味着有节区。
(2) 每个节区占用文件中一个连续字节区域(这个区域可能长度为 0)。
(3) 文件中的节区不能重叠,不允许一个字节存在于两个节区中的情况发生。
(4) 目标文件中可能包含非活动空间(INACTIVE SPACE)。这些区域不属于任何 头部和节区,其内容未指定。
ELF 头部中,e_shoff成员给出从文件头到节区头部表格的偏移字节数;e_shnum给出表格中条目数目;e_shentsize 给出每个项目的字节数。从这些信息中可以确切地定位节区的具体位置、长度。节区头部表格中比较特殊的几个下标如下:
名称 |
取值 |
说明 |
SHN_UNDEF |
0 |
标记未定义的、缺失的、不相关的,或者没有含义的节区引用 |
SHN_LORESERVE |
0xFF00 |
保留索引的下界 |
SHN_LOPROC |
0xFF00 |
SHN_HIPROC 0XFF1F 保留给处理器特殊的语义 |
SHN_ABS |
1 |
包含对应引用量的绝对取值。这些值不会被重定位所 影响 |
SHN_COMMON |
2 |
相对于此节区定义的符号是公共符号。如 FORTRAN 中 COMMON 或者未分配的 C 外部变量。 |
SHN_HIRESERVE |
0XFFFF |
保留索引的上界 |
介于 SHN_LORESERVE 和 SHN_HIRESERVE 之间的表项不会出现在节区头部表中。
图5 .o文件Section Header(部分)
图6 .axf文件Section Header
上图中的表头可以用如下数据结构描述(对应关系见注释):
typedef struct{
Elf32_Word sh_name; // name
Elf32_Word sh_type; // Type
Elf32_Word sh_flags; // Flg
Elf32_Addr sh_addr; // Addr
Elf32_Off sh_offset; // Off
Elf32_Word sh_size; // Size
Elf32_Word sh_link; // Lk
Elf32_Word sh_info; // Inf
Elf32_Word sh_addralign; // Al
Elf32_Word sh_entsize; // ES
}Elf32_Shdr;
sh_name:给出节区名称。是节区头部字符串表节区(Section Header String Table Section)的索引。名字是一个 NULL 结尾的字符串。ELF文件规定一些标准节区的名字,例如.text、.data、.bss。此外,如上图中,许多节区名字都是ARM自己扩展的。
sh_type:为节区的内容和语义进行分类。ARM ELF只使用了其中的一部分。参见下表(部分)。
SHT_NULL |
0 |
此值标志节区头部是非活动的,没有对应的节区。此节区头部中的其他成员取值无意义。 |
SHT_PROGBITS |
1 |
此节区包含程序定义的信息,其格式和含义都由程序来解释释。 |
SHT_SYMTAB |
2 |
此节区包含一个符号表。目前目标文件对每种类型的节区都只能包含一个,不过这个限制将来可能发生变化。一般,SHT_SYMTAB 节区提供用于链接编辑(指 ld而言) 的符号,尽管也可用来实现动态链接。 |
SHT_STRTAB |
3 |
此节区包含字符串表。目标文件可能包含多个字符串表节区。 |
SHT_RELA |
4 |
此节区包含重定位表项,其中可能会有补齐内容(addend),例如 32 位目标文件中的 Elf32_Rela 类型。目标文件可能拥有多个重定位节区。 |
SHT_HASH |
5 |
此节区包含符号哈希表。所有参与动态链接的目标都必须包含一个符号哈希表。目前,一个目标文件只能包含一个哈希表, 不过此限制将来可能会解除。 |
SHT_DYNAMIC |
6 |
此节区包含动态链接的信息。目前一个目标文件中只能包含一个动态节区,将来可能会取消这一限制。 |
SHT_NOTE |
7 |
此节区包含以某种方式来标记文件的信息。 |
SHT_NOBITS |
8 |
这种类型的节区不占用文件中的空间,其他方面和SHT_PROGBITS 相似。尽管此节区不包含任何字节,成员sh_offset 中还是会包含概念性的文件偏移 |
SHT_REL |
9 |
此节区包含重定位表项,其中没有补齐(addends),例如 32 位目标文件中的 Elf32_rel 类型。目标文件中可以拥有多个重定位节区。 |
除了以上标准节区类型外,ARM架构下,还有以下特殊的类型:
SHT_ARM_EXIDX |
0x70000001 |
异常索引表 |
SHT_ARM_PREEMPTMAP |
0x70000002 |
BPABI DLL动态链接抢占地图 |
SHT_ARM_ATTRIBUTES |
0x70000003 |
对象文件兼容性属性 |
SHT_ARM_DEBUGOVERLAY |
0x70000004 |
|
SHT_ARM_OVERLAYSECTION |
0x70000005 |
sh_flags:字段定义了一个节区中包含的内容是否可以修改、是否可以执行等信息。如果一个标志比特位被设置,则该位取值为1。未定义的各位都设置为0。
SHF_WRITE |
0x1 |
节区包含进程执行过程中将可写的数据 |
SHF_ALLOC |
0x2 |
此节区在进程执行过程中占用内存。某些控制节区并不出现于目标 文件的内存映像中,对于那些节区,此位应设置为 0 |
SHF_EXECINSTR |
0x4 |
节区包含可执行的机器指令 |
SHF_MASKPROC |
0xF0000000 |
所有包含于此掩码中的四位都用于处理器专用的语义 |
ARM 中的特殊取值如下:
Name |
Value |
Purpose |
SHF_ARM_NOREAD |
0x20000000 |
本节的内容不应由程序执行者读取 |
sh_addr:如果节区将出现在进程的内存映像中,此成员给出节区的第一个字节应处的位置。否则,此字段为 0。
sh_link和sh_info:根据节区类型的不同,sh_link 和 sh_info 的具体含义也有所不同。ARM取值如下:
sh_link |
sh_info |
|
SHT_SYMTAB, SHT_DYNSYM |
相关联的字符串表的节区头部索引 |
最后一个局部符号(绑 定 STB_LOCAL)的符 号表索引值加一 |
SHT_DYNAMIC |
此节区中条目所用到的字符串表格 的节区头部索引 |
0 |
SHT_HASH |
此哈希表所适用的符号表的节区头 部索引 |
0 |
SHT_REL SHT_RELA |
相关符号表的节区头部索引 |
重定位所适用的节区的 节区头部索引 |
其它 |
SHN_UNDEF |
0 |
sh_addralign:节区没有最小对齐要求。 但是,包含thumb代码的部分必须至少为16位对齐,并且包含ARM代码的部分必须至少为32位对齐。具有SHF_ALLOC属性的任何节必须满足sh_addralign >= 4。其他节可根据需要对齐。 例如,调试表通常没有对齐要求。并且输入到静态链接器的数据段可以自然对齐。
平台标准可能会限制他们可以保证的最大对齐(通常是页面大小)。
sh_entsize:某些节区中包含固定大小的项目,如符号表。对于这类节区,此成员给出每个表项的长度字节数。如果节区中并不包含固定长度表项的表格,此成员取值为 0。
sh_size:此成员给出本节区的长度(字节数)。除非节区的类型是SHT_NOBITS,否则节区占用文件中的 sh_size 字节。类型为SHT_NOBITS 的节区长度可能非零,不过却不占用文件中的空间。
sh_offset:此成员的取值给出节区的第一个字节与文件头之间的偏移。不过,SHT_NOBITS 类型的节区不占用文件的空间,因此其 sh_offset 成员给出的是其概念性的偏移。
注意:
(1)保留给处理器体系结构的节区名称一般构成为:处理器体系结构名称简写 + 节区名称。且处理器名称应该与 e_machine 中使用的名称相同。例如:图5最后的 .ARM.attributes
(2)目标文件中也可以包含多个名字相同的节区。
(3)上图节区名ER_IROM1、RW_IRAM1、RW_IRAM是由连接器的分散加载文件指定的名称。可以根据需要自行修改。
ARM节名称是以下面列出的具有预定义含义的标准前缀之一开始的名称,或者是包含美元($)字符的名称。 在ARM EABI下没有其他具有特殊意义的段名称。
节区前缀名 |
节区类型 |
节区属性 |
解释 |
.bss |
SHT_NOBITS |
SHF_ALLOC+SHF_WRITE |
本节保存有助于程序内存映像的未初始化数据。 根据定义,当程序开始运行时,系统将使用零初始化数据。 该部分不占用文件空间,如段类型SHT_NOBITS所示。 |
.comment |
SHT_PROGBITS |
None |
本节包含版本控制信息 |
.data |
SHT_PROGBITS |
SHF_ALLOC+SHF_WRITE |
这些部分保存有助于程序内存映像的已初始化数据 |
.data1 |
SHT_PROGBITS |
SHF_ALLOC+SHF_WRITE |
|
.debug… |
SHT_PROGBITS |
None |
本节保存符号调试信息。 内容未指定。 具有前缀.debug的所有段名保留供将来使用 |
.dynamic |
SHT_DYNAMIC |
SHF_ALLOC [+SHF_WRITE] |
本节保存动态链接信息,并具有SHF_ALLOC和SHF_WRITE等属性。 操作系统和处理器确定SHF_WRITE位是否被置位 |
.hash |
SHT_HASH |
[SHF_ALLOC] |
本节包含一个符号哈希表。 |
.line |
SHT_PROGBITS |
None |
本节保存符号调试的行号信息,其中描述了源程序和机器代码之间的对应关系。 内容未指定 |
.rodata |
SHT_PROGBITS |
SHF_ALLOC |
这些部分保存通常有助于过程映像中的不可写段的只读数据 |
.rodata1 |
SHT_PROGBITS |
SHF_ALLOC |
|
.rel name .rela name |
SHT_REL SHT_RELA |
[SHF_ALLOC] |
这些节区中包含了重定位信息。如果文件中 包含可加载的段,段中有重定位内容,节区 的属性将包含 SHF_ALLOC 位,否则该位 置 0。传统上 name 根据重定位所适用的节 区给定。例如 .text 节区的重定位节区名字,将是:.rel.text 或者 .rela.text。 |
.shstrtab |
SHT_STRTAB |
None |
本节保存节区名称。 |
.strtab |
SHT_STRTAB |
[SHF_ALLOC] |
此节区包含字符串,通常是代表与符号表项 相关的名称。如果文件拥有一个可加载的 段,段中包含符号串表,节区的属性将包含 SHF_ALLOC 位,否则该位为 0。 |
.symtab |
SHT_SYMTAB |
[SHF_ALLOC] |
此节区包含一个符号表。如果文件中包含一 个可加载的段,并且该段中包含符号表,那 么节区的属性中包含SHF_ALLOC 位,否则 该位置为 0。 |
.text |
SHT_PROGBITS |
SHF_ALLOC+ SHF_EXECINSTR |
本节包含程序的文本或可执行指令 |
除了以上标准节区外,ARM架构下,还有以下特殊的节区:
节区前缀名 |
节区类型 |
节区属性 |
|
.ARM.exidx* |
SHT_ARM_EXIDX |
SHF_ALLOC + SHF_LINK_ORDER |
以.ARM.exidx开头的节区包含部分展开的索引条目。 |
.ARM.extab* |
SHT_PROGBITS |
SHF_ALLOC |
以.ARM.extab开头的节区包含异常展开信息的名称部分。 |
.ARM.preemptmap |
SHT_ARM_PREEMPTMAP |
SHF_ALLOC |
以.ARM.preemptmap开头的节区包含一个BPABI DLL动态链接优先地图。 |
.ARM.attributes |
SHT_ARM_ATTRIBUTES |
none |
包含构建属性 |
.ARM.debug_overlay |
SHT_ARM_DEBUGOVERLAY |
none |
|
.ARM.overlay_table |
SHT_ARM_OVERLAYSECTION |
See DBGOVL for details |
这里需要注意一下Debug Sections。Debug Sections仅在调试时使用,稍微复杂一些。ARM可执行ELF文件的调试节区中包含多种类型的调试信息,ELF可执行文件的使用者(如armlink)可以通过检查可执行文件的节表来区分这些种类型的调试信息。
ARM 系列的开发工具在不同的发展时期,采用的调试信息是有区别的,后来统一采用DWARP。目前采用的应该是3.0 版本。具体如下:
- ASD debugging tables
These provide backwards compatibility with ARM's Symbolic Debugger. ASD debugging information is stored in a single Section in the executable named .asd.
- DWARP version 1.0
When DWARF 1.0 debugging information is included by the linker in the ELF executable, the file contains the following ELF Sections, each of which has a Section Header Table entry:
Section name |
Contents |
.debug |
debugging entries |
.line |
fileinfo entries |
.debug_pubnames |
table for accelerated access to debug items |
.debug_aranges |
address ranges for compilation units |
- DWARF version 2.0
When DWARF 2.0 debugging information is included by the linker in the ELF executable, the file contains the following ELF sections, each of which has a Section Header Table entry:
Section name |
Contents |
.debug_info |
debugging entries |
.debug_line |
fileinfo statement program |
.debug_pubnames |
table for accelerated access to debug items |
.debug_aranges |
address ranges for compilation units |
.debug_macinfo |
macro information (#define / #undef) |
.debug_frame |
call frame information |
.debugj_abbrev |
abbreviation table |
.debug_str |
debug string table |
关于DWARF调试标准详见:http://www.dwarfstd.org/。目前最新版本是The DWARF Debugging Standard Version 5
(3)Program Headers(程序头)
可执行文件或者共享目标文件的程序头部是一个结构数组,每个结构描述了一个段或者系统准备程序执行所必需的其它信息。目标文件的"段"包含一个或者多个"节区",也就是"段内容(Segment Contents)"。程序头部仅对于可执行文件和共享目标文件有意义。
图7 Program Header
程序头可以使用如下数据结构来表示(对应关系见注释):
typedef struct {
Elf32_Word p_type; // Type
Elf32_Off p_offset; // Offset
Elf32_Addr p_vaddr; // VirtAddr
Elf32_Addr p_paddr; // PhyAddr
Elf32_Word p_filesz; // FileSiz
Elf32_Word p_memsz; // MemSiz
Elf32_Word p_flags; // Flg
Elf32_Word p_align; // Align
} Elf32_Phdr;
p_type:这个成员告诉这个数组元素描述什么样的段,或者如何解释数组元素的信息。 类型值及其含义如下图所示。
名称 |
取值 |
意义 |
PT_NULL |
0 |
数组元素未使用; 其他成员的值是未定义的。 此类型使程序头表已忽略条目。 |
PT_LOAD |
1 |
数组元素指定由p_filesz和p_memsz描述的可加载段。 |
PT_DYNAMIC |
2 |
数组元素指定动态链接信息。 |
PT_INTERP |
3 |
数组元素指定要作为解释器调用的以空值结尾的路径名的位置和大小。 |
PT_NOTE |
4 |
数组元素指定辅助信息的位置和大小。 |
PT_SHLIB |
5 |
该段类型是保留的,但具有未指定的语义。 |
PT_PHDR |
6 |
数组元素(如果存在)指定程序头表本身的位置和大小。 |
PT_ARM_ARCHEXT |
0x70000000 |
|
PT_ARM_EXIDX PT_ARM_UNWIND |
0x70000001 |
p_offset:此成员给出从文件头到该段第一个字节的偏移
p_vaddr:此成员给出段的第一个字节将被放到内存中的虚拟地址。
p_paddr:此成员仅用于与物理地址相关的系统中。因为 System V 忽略所有应用程序的物理地址信息,此字段对与可执行文件和共享目标文件而言,具体内容是未指定的。
p_filesz:此成员给出段在文件映像中所占的字节数。可以为 0。
p_memsz: 此成员给出段在内存映像中占用的字节数。可以为 0。
p_flags:此成员给出与段相关的标志。
名称 |
取值 |
意义 |
PF_X |
1 |
可执行的段 |
PF_W |
2 |
可写的段 |
PF_R |
4 |
可读的段 |
PF_MASKPROC |
0xf0000000 |
保留 |
p_align:可加载的进程段的 p_vaddr 和 p_offset 取值必须合适,相对于对页面大小的取模而言。此成员给出段在文件中和内存中如何 对齐。数值 0 和 1 表示不需要对齐。否则 p_align 应该是个 正整数,并且是 2 的幂次数,p_vaddr 和 p_offset 对 p_align 取模后应该相等。
(4)Symbol table(符号表)
一个对象文件的符号表保存了定位和重定位所在程序的符号定义和引用所需的信息。符号表以数组的下标进行索引。0指定表中的第一个条目,并用作未定义的符号索引。
ARM结构中,符号表与标准的ELF文件没有任何区别。
图12 .o文件Symbol table(部分)
在C语言中,符号表保存了程序实现或使用的所有全局变量和函数,如果程序引用一个自身代码未定义的符号,则称之为未定义符号,这类引用必须在静态链接期间用其他目标模块或库解决,或在加载时通过动态链接解决。
符号表可以使用以下数据结构表示:
typedef struct {
Elf32_Word st_name; // Name
Elf32_Addr st_value; // Value
Elf32_Word st_size; // Size
unsigned char st_info; //
unsigned char st_other;
Elf32_Half st_shndx; // Ndx
} Elf32_Sym;
st_name:该成员将目标文件的符号字符串表中的索引保存在符号名称的字符表示中
st_value:该成员给出相关联的符号的值。 根据上下文,这可能是绝对值,地址等等; 细节见第3.5.1节"符号值"。
st_size:许多符号具有相关尺寸。 例如,数据对象的大小是对象中包含的字节数。 如果符号没有大小或未知的大小,该成员将保持0。
st_info:该成员指定符号的类型和绑定属性。 值和值的列表如下图3-16和图3-17所示。 以下代码显示了如何操作这些值。
#define ELF32_ST_BIND(i) ((i)>>4)
#define ELF32_ST_TYPE(i) ((i)&0xf)
#define ELF32_ST_INFO(b,t) (((b)<<4)+((t)&0xf))
A symbol's binding determines the linkage visibility and behavior.
Figure 3-16, Symbol Binding, ELF32_ST_BIND
Name |
Value |
意义 |
STB_LOCAL |
0 |
Local symbols are not visible outside the object file containing their definition. Local symbols of the same name may exist in multiple files without interfering with each other |
STB_GLOBAL |
1 |
Global symbols are visible to all object files being combined. One file's definition of a global symbol will satisfy another file's undefined reference to the same global symbol |
STB_WEAK |
2 |
Weak symbols resemble global symbols, but their definitions have lower precedence. Undefined weak symbols (weak references) may have processor- or OS-specific semantics (see, for example, section 4.4.3,Weak symbols). |
STB_LOPROC |
13 |
Values in this inclusive range are reserved for processor-specific semantics. |
STB_HIPROC |
15 |
In each symbol table, all symbols with STB_LOCAL binding precede the weak and global symbols. A symbol's type provides a general classification for the associated entity.
Figure 3-17, Symbol Types, ELF32_ST_TYPE
Name |
Value |
Meaning |
STT_NOTYPE |
0 |
The symbol's type is not specified. |
STT_OBJECT |
1 |
The symbol is associated with a data object, such as a variable, an array, and so on. |
STT_FUNC |
2 |
The symbol is associated with a function or other executable code. |
STT_SECTION |
3 |
The symbol is associated with a section. Symbol table entries of this type exist primarily for relocation and normally have STB_LOCAL binding. |
STT_FILE |
4 |
A file symbol has STB_LOCAL binding, its section index is SHN_A BS, and it precedes the other STB_LOCAL symbols for the file, if it is present. |
STT_LOPROC |
13 |
Values in this inclusive range are reserved for processor-specific semantics. If a symbol's value refers to a specific location within a section, its section index member, st_shndx, holds an index into the section header table. As the section moves during relocation, the symbol's value changes as well, and references to the symbol continue to point to the same location in the program. Some special section index values give other semantics. |
STT_HIPROC |
15 |
st_other:该成员目前只有0,没有定义。
st_shndx:每个符号表条目与某些部分有关"定义"; 该成员保存相关部分标题表索引。 如上图3-7和3.3.1节所述,一些段索引表示特殊含义。
The symbols in ELF object files convey specific information to the linker and loader. See section 4, ARM- and Thumb-Specific Definitions, for a description of the actual linking model used in the system.
SHN_ABS:The symbol has an absolute value that will not change because of relocation.
SHN_COMMON:The symbol labels a common block that has not yet been allocated. The symbol's value gives alignment constraints, similar to a section's sh_addralign member. That is, the link editor will allocate the storage for the symbol at an address that is a multiple of st_value. The symbol's size tells how many bytes are required.
SHN_UNDEF:This section table index means the symbol is undefined. When the link editor combines this object file with another that defines the indicated symbol, this file's references to the symbol will be linked to the actual definition.
As mentioned above, the symbol table entry for index 0 (STN_UNDEF) is reserved. It is shown in Figure 3-18.
Figure 3-18, Symbol Table Entry: Index 0
Name |
Value |
Note |
st_name |
0 |
No name |
st_value |
0 |
Zero value |
st_size |
0 |
No size |
st_info |
0 |
No type, local binding |
st_other |
0 |
|
st_shndx |
SHN_UNDEF |
No section |
(5)String table(字符串表)
字符串表节区包含以 NULL(ASCII 码 0)结尾的字符序列,通常称为字符串。ELF 目标文件通常使用字符串来表示符号和节区名称。对字符串的引用通常以字符串在字符 串表中的下标给出。
ARM结构中,字符串表与标准的ELF文件没有任何区别。
三、ARM映像文件
ARM中的各种源文件(包括汇编文件,C语言程序及C++程序等)经过ARM编译器编译后生成ELF格式的目标文件(.o文件)。这些目标文件和相应的C/C++运行时用到的库经过ARM连接器处理后,生成ELF格式的镜像文件(image),这种ELF格式的映像文件是一种可执行文件,可被写入嵌入式设备的ROM中。前面说过,实际文件中,只有ELF Header位置是绝对的,且只能在最开始,其他部分部分的位置顺序并不一定完全相同。ARM的镜像文件一般如下图所示。
对象文件参与程序链接(构建程序)和程序执行(运行程序)。为了方便和高效,目标文件格式提供文件内容的并行视图,反映了这些活动的不同需求。 这样就出来三个概念:节、区、段。在ARM中,ARM又将段进行了归类,这就是ARM中RO、RW、ZI。下面具体进行说明。
An ARM ELF image contains sections, regions, and segments, and each link stage has a different view of the image.
The structure of an image is defined by the:
- Number of its constituent regions and output sections.
- Positions in memory of these regions and sections when the image is loaded.
- Positions in memory of these regions and sections when the image executes.
Each link stage has a different view of the image. The image views are:
ELF object file view (linker input)
The ELF object file view comprises input sections. The ELF object file can be:
- A relocatable file that holds code and data suitable for linking with other object files to create an executable or a shared object file.
- A shared object file that holds code and data.
Linker view
The linker has two views for the address space of a program that become distinct in the presence of overlaid, position-independent, and relocatable program fragments (code or data):
- The load address of a program fragment is the target address that the linker expects an external agent such as a program loader, dynamic linker, or debugger to copy the fragment from the ELF file. This might not be the address at which the fragment executes.
- The execution address of a program fragment is the target address where the linker expects the fragment to reside whenever it participates in the execution of the program.
If a fragment is position-independent or relocatable, its execution address can vary during execution.
ELF image file view (linker output)
The ELF image file view comprises program segments and output sections:
- A load region corresponds to a program segment.
-
An execution region contains one or more of the following output sections:
- RO section.
- RW section.
- XO section.
- ZI section.
One or more execution regions make up a load region.
When describing a memory view:
- The term root region means a region that has the same load and execution addresses.
- Load regions are equivalent to ELF segments.
The following figure shows the relationship between the views at each link stage:
Input sections, output sections, regions, and program segments
An object or image file is constructed from a hierarchy of input sections, output sections, regions, and program segments.
Input section
An input section is an individual section from an input object file. It contains code, initialized data, or describes a fragment of memory that is not initialized or that must be set to zero before the image can execute. These properties are represented by attributes such as RO, RW, XO, and ZI. These attributes are used by armlink to group input sections into bigger building blocks called output sections and regions.
Output section
An output section is a group of input sections that have the same RO, RW, XO, or ZI attribute, and that are placed contiguously in memory by the linker. An output section has the same attributes as its constituent input sections. Within an output section, the input sections are sorted according to the section placement rules.
Region
A region contains up to four output sections depending on the contents and the number of sections with different attributes. By default, the output sections in a region are sorted according to their attributes. Any XO output section is first, followed by the RO output section, then the RW output section, and finally the ZI output section. A region typically maps onto a physical memory device, such as ROM, RAM, or peripheral. You can change the order of output sections using scatter-loading.
Program segment
A program segment corresponds to a load region and contains execution regions. Program segments hold information such as text and data.
Note
With armlink, the maximum size of a program segment is 2GB.
Considerations when execute-only sections are present
Be aware of the following when execute-only (XO) sections are present:
- You can mix XO and non-XO sections in the same execution region. However, this results in the output of a RO section.
- If an input file has one or more XO sections then the linker generates a separate XO ELF segment. In the final image, the XO segment immediately precedes the RO segment, unless otherwise specified by a scatter file or the --xo-base option.
Load view and execution view of an image
Image regions are placed in the system memory map at load time. The location of the regions in memory might change during execution.
Before you can execute the image, you might have to move some of its regions to their execution addresses and create the ZI output sections. For example, initialized RW data might have to be copied from its load address in ROM to its execution address in RAM.
The memory map of an image has the following distinct views:
Load view
Describes each image region and section in terms of the address where it is located when the image is loaded into memory, that is, the location before image execution starts.
Execution view
Describes each image region and section in terms of the address where it is located during image execution.
The following figure shows these views for an image without an execute-only (XO) section:
The following figure shows load and execution views for an image with an XO section:
axf文件
axf文件是ARM的调试文件,其格式符合上一节讲的目标文件格式(ELF)。其中除了包含了完整的bin文件外,还附加了其他的调试信息。在调试的时候,这些调试信息是不必下到RAM中去的,真正下到RAM中的信息仅仅是可执行代码。下图为axf文件的头部。
通过直接查看完整的axf文件可以看出,axf中绝大多数都是和调试相关的内容。真正的Bin只是其中的一小部分。Bin的结尾处在axf文件中也很容易找到,再次就不在赘述。
既然前面我们说了,axf文件就是ELF文件格式,那么我们可以使用readelf工具,具体查看一下axf文件。下图是一个axf文件的节区表
Bin文件
bin文件是ARM的可执行文件,是最纯粹的二进制机器代码。与HEX文件包括地址信息的不同,BIN文件格式只包括了数据本身。在烧写或下载HEX文件的时候,一般都不需要用户指定地址,因为HEX文件内部的信息已经包括了地址。而烧写BIN文件的时候,用户是一定需要指定地址信息的。
ARM的Bin文件就是axf的精华部分(掐掉ELF头,去掉 .symtab、.debug和.symtab区里的信息)。下图是笔者使用Winhex截取的ARM的Bin文件的开头和结尾的示意图。
hex文件
首先,hex文件最初由Intel提出。在Intel HEX文件中,每一行是一个HEX记录,由十六进制数组成的机器码或者数据常量,Intel HEX文件经常被用于将程序或数据传输存储到ROM、EPROM,大多数编程器和模拟器使用Intel HEX文件。
hex文件全部由可打印的ASCII字符组成。如下图就是ARM-MDK5.22生成的一个hex文件(部分)
从上图不难看出,hex文件就是一个个的十六进制的字符串。实际上,一个Intel HEX文件可以包含任意多的十六进制记录,每条记录有五个域,每条记录都由一个冒号":"打头。一个数据记录以一个回车和一个换行结束。其格式如下:
:CCAAAARR[DD...]ZZ
其中:
CC:本条记录中数据(dd)的字节数目
AAAA:本条记录中的数据在存储区中的起始地址
RR:记录类型:
00 数据记录 (data record)
01 文件结束记录 (end record)
02 扩展段地址记录 (paragraph record)
03 扩展线性地址记录 (transfer address record)
DD... :数据域。表示一个字节的数据,一个记录可能有多个数据字节,字节数目可以
查看ll域的说明
ZZ:效验和域,表示记录的效验和,计算方法是将本条记录冒号开始的所有字母对
所表示的十六进制数字都加起来然后模除256得到的余数最后求出余数的补码即是本效验字节cc。
例如:
:10400000781A00203D420008034C0008D14B0008FC
10:长度16
4000:起始地址
00:表示数据记录
从78开始到08 :数据
FC:校验和