ELF文件头
ELF目标文件格式的最前部是ELF文件头,它包好了描述整个文件的基本属性,比如ELF文件版本,目标机器型号,程序的入口地址。紧接着是ELF文件的各个段。
readelf -h test.o
readelf 是一个工具。
elf.png
ELF文件头中定义了ELF魔数,文件机器字节长度,数据存储方式,版本,运行平台,ABI版本,ELF重定位类型,硬件平台,硬件平台版本,入口地址,程序头入口和长度,段表的位置和长度,段的数量。
ELF文件头结构及相关常数被定义在/usr/include/elf.h
里,ELF文件有32位版本和64位版本,文件头也有这两种结构,分别叫Elf32_Ehdr
和Elf64_Ehdr
。
typedef uint16_t Elf32_Half; /* 2字节 */
typedef uint32_t Elf32_Word; /* 4字节 */
typedef uint32_t Elf32_Off; /* 4字节 */
typedef uint32_t Elf32_Addr; /* 4字节 */
struct Elf32_Ehdr
{
unsigned char e_ident[16]; /* 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 */
} ;
-
unsigned char e_ident[16]
e_ident.png -
e_type
ELF 目标文件类型 取值 意义 ET_NONE 0 未知目标文件格式 ET_REL 1 可重定位文件 ET_EXEC 2 可执行文件 ET_DYN 3 动态共享目标文件 ET_CORE 4 core文件,程序崩溃时内存映像的转储格式 ET_LOPROC 0xff00 未知目标文件格式 ET_HIPROC 0xff00 未知目标文件格式 -
e_machine
e_machine 占 2 个字节,用来描述 ELF 目标文件的体系结构类型,也就是说该文件在哪种硬件平台上运行。 -
e_version
占用 4字节,表示版本信息。 -
e_entry
占用4字节,指明操作系统运行该程序时,将控制权转交到的虚拟地址,也就是程序的入口地址。可重定位文件一般没有入口地址,则这个为0。 -
e_phoff
占用 4字节,指明程序头表(program header table)在文件内的偏移量。 -
e_shoff
占用4字节,用来指明节头表(section header table)在文件内的字节偏移量。 -
e_flags
占用4字节。 -
e_ehsize
占用2字节,说明 ELF header 的字节大小。 -
e_phentsize
占用2字节,用来指明程序头表中每个条目的字节大小,即每个用来描述段信息的数据结构的大小,也就是后面要介绍的struct Elf32_Phdr。 -
e_phenum
占用2字节,用来指明程序头表中条目的数量,也就是段的个数。 -
e_shentsize
占用2字节,用来指明节头表中每个条目的字节大小,即每个用来描述节信息的数据结构的大小。 -
e_shnum
占用2字节,用来指明节头表中条目的数量,也就是节的个数。 -
e_shrtrndx
说明 string name table 在节头部表中的索引。
Section
objdump -h
命令只是把ELF文件中关键的节显示了出来,而省略了其他辅助性的节,可以使用readelf
工具来查看所有的节。
readelf -S test.o
image.png
readelf 输出的结果就是ELF文件节表的内容,节表的结构比较简单就是一个以Elf32_Shdr
结构体为元素的数组,数组元素个数等于段的个数。Elf32_Shdr
又被称为节描述符。
typedef uint16_t Elf32_Half; /* 2字节 */
typedef uint32_t Elf32_Word; /* 4字节 */
typedef uint32_t Elf32_Off; /* 4字节 */
typedef uint32_t Elf32_Addr; /* 4字节 */
struct Elf32_Shdr
{
Elf32_Word sh_name;
Elf32_Word sh_type;
};
- sh_name
记录section名,section名位于一个.shstrtab
的字符串表,sh_name是section名字符串在.shstrtab
中的偏移。 - sh_type
节的类型。常量 值 含义 SHT_NULL 0 无效 SHT_PROGBITS 1 程序节 SHT_SYMTAB 2 符号表
-
sh_flags
section 的标志位。该标志位表示该section在进程虚拟地址空间中的属性,比如是否可写,是否可执行。常量 值 含义 SHF_WRITE 1 表示该section在进程中可写 SHF_ALLOC 2 表示该section在进程空间中必须要分配空间 SHF_EXECINSTR 3 表示该section在进程空间中可执行 -
sh_addr
section 的虚拟地址. -
sh_offset
section 的偏移 -
sh_size
section 的长度 -
sh_link sh-info
重定位表
链接器在处理目标文件类型时,需要对目标文件中某些部位进行重定位,即代码段和数据段中那些对绝对地址的引用的位置。这些重定位信息都记录在ELF文件的重定位表里面。对于每一个需要重定位的代码段和数据段,都会有一个相应的重定位表。比如.rel.text
就是针对.text
段的重定位表。
字符串表
链接的接口-符号
在连接中,目标文件之间相互拼合实际上是目标文件之间对地址的引用,比如目标文件B用到了目标文件A中的函数foo
,则目标文件A定义了函数foo
,目标文件B引用了目标文件A中的函数foo
。在链接过程中,将函数和变量统称为符号。
每一个目标文件都会有一个相应的符号表,这个表里面记录了目标文件所用到的所有符号,每个定义的符号有一个对应的值,叫做符号值,对于变量和函数来说,符号值就是他们的地址。除了函数和变量外,还存在其他几种不常用的符号。
将符号进行分类,它们可能是:
-
定义在本目标文件的全局符号,可以被其他目标文件引用。
-
在本目标文件中引用的全局符号,却没有定义在本目标文件中,一般称为外部符号。
-
section名
-
局部符号,这类符号只在编译单元内部可见,比如
static_var
和static_var2
。 -
行号信息
对于链接来说,只关注全局符号,即上面分类中的第一类和第二类。其他局部符号,行号等,对于其他目标文件来说,是不可见的。
可以用nm
工具查看符号。
$ nm test.o
00000000 T func1
00000000 D global_init_var
00000004 C global_uninit_var
0000001b T main
U printf
00000004 d static_var.1701
00000000 b static_var2.1702
ELF 符号表结构
ELF文件中的符号表往往是一个文件中的一个段,段名一般叫.symtab
,符号表的结构也很简单,是一个Elf32_Sym
结构的数组,每个Elf32_Sym
结构对应一个符号。这个数组的第一个元素,也就是下标为0的元素为无效未定义符号。
typedef uint16_t Elf32_Half; /* 2字节 */
typedef uint32_t Elf32_Word; /* 4字节 */
typedef uint32_t Elf32_Off; /* 4字节 */
typedef uint32_t Elf32_Addr; /* 4字节 */
typedef struct {
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Half st_index;
} Elf32_Sym;
-
st_name
指定该符号名在字符串表中的下标。 -
st_value
符号相对应的值,这个值和符号有关,可能是一个绝对值,也可能是一个地址。 -
st_size
符号大小,对于包含数据的符号,这个值是该数据类型的大小。 -
st_info
该成员低4位标识符号的类型,高28位标识符号绑定信息。- 符号绑定信息
宏定义 值 含义 STB_LOCAL 0 局部符号,对于目标文件的外部不可见 STB_GLOBAL 1 全局符号,外部可见 STB_WEAK 2 弱引用,区分弱符号和强符号 - 符号类型
宏定义 值 含义 STT_NOTYPE 0 未知类型符号 STT_OBJECT 1 该符号是一个数据对象,比如变量,数组等 STT_FUNC 2 该符号是一个函数 STT_SECTION 3 该符号表示一个段,这种符号必须是 STB_LOCAL的 STT_FILE 4 该符号表示文件名,一般都是该目标文件所对应的源文件名,
- 符号绑定信息
-
st_other
暂未使用。 -
st_shndx
符号所在的段,如果符号定义在本目标文件中,那么这个成员表示符号所在的段在段表中的下标。如果符号不是定义在本目标文件中或者特殊符号,那么其值将如下表所示:宏定义 值 含义 SHN_ABS 0xfff1 表示该符号包含了一个绝对的值,比如表示文件名符号 SHN_COMMON 0xfff2 表示该符号是一个"COMMON块"类型符号,一般来说,未初始化的全局符号定义就是这种类型 SHN_UNDEF 0 表示该符号未定义,这个符号表示该符号在本目标文件被引用到,但是定义在其他目标文件中
$ readelf -s test.o
Symbol table '.symtab' contains 16 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS test.c
2: 00000000 0 SECTION LOCAL DEFAULT 1
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 SECTION LOCAL DEFAULT 4
5: 00000000 0 SECTION LOCAL DEFAULT 5
6: 00000004 4 OBJECT LOCAL DEFAULT 3 static_var.1701
7: 00000000 4 OBJECT LOCAL DEFAULT 4 static_var2.1702
8: 00000000 0 SECTION LOCAL DEFAULT 7
9: 00000000 0 SECTION LOCAL DEFAULT 8
10: 00000000 0 SECTION LOCAL DEFAULT 6
11: 00000000 4 OBJECT GLOBAL DEFAULT 3 global_init_var
12: 00000004 4 OBJECT GLOBAL DEFAULT COM global_uninit_var
13: 00000000 27 FUNC GLOBAL DEFAULT 1
14: 00000000 0 NOTYPE GLOBAL DEFAULT UND printf
15: 0000001b 56 FUNC GLOBAL DEFAULT 1 main
-
func
和main
函数他们在.text
,所以Ndx
为1;它们是函数,所以类型为STT_FUNC
;它绑定属性为STB_GLOBAL
,表示全局可见;Size
表示函数指令所占用的字节数;Value
表示函数相对于代码段起始位置的偏移量。 -
printf
在test.c中被引用,但是没有被定义,所以Ndx
是SHN_UNDEF
;类型是STT_NOTYPE
;绑定属性为STB_GLOBAL
,表示全局可见。 -
global_init_var
在已初始化的全局变量,它在.data
段,所以Ndx
为3;类型是STT_OBJECT
;绑定属性为STB_GLOBAL
,表示全局可见。
global_uninit_var
是未初始化的全局变量,它是一个SHN_COMMON
,其并不存在于.bss
段;类型是STT_OBJECT
;绑定属性为STB_GLOBAL
,表示全局可见。 -
static_var.1701
是已初始化的局部静态变量,它在.data
段,所以Ndx
为3;类型是STT_OBJECT
;绑定属性为STB_LOCAL
,即是只在编译单元内部可见。
static_var2.1702
是未初始化的局部静态变量,它在.bss
段,所以Ndx
为4;类型是STT_OBJECT
;绑定属性为STB_LOCAL
,即是只在编译单元内部可见。
网友评论