ELF文件
ELF目标文件格式的最前部是ELF文件头(ELF Header),它包含了描述整个文件的基本属性,比如ELF文件版本、目标机器型号、程序入口地址等。紧接着是ELF文件各个段。其中ELF文件中与段有关的重要结构就是段表(Section Header Table),该表描述了ELF文件包含的所有段的信息,比如每个段的段名、段的长度、在文件中的偏移、读写权限及段的其他属性。接着将详细分析ELF文件头、段表等ELF关键的结构。
下面对ELF结构做介绍:
(1)文件头
$readelf –h SimpleSection.o
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 280 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 40 (bytes)
Number of section headers: 11
Section header string table index: 8
从上面输出的结果可以看到,ELF的文件头中定义了ELF魔数、文件机器字节长度、数据存储方式、版本、运行平台、ABI版本、ELF重定位类型、硬件平台、硬件平台版本、入口地址、程序头入口和长度、段表的位置和长度及段的数量等。
(2)段表
$ readelf -S SimpleSection.o
There are 11 section headers, starting at offset 0x118:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 00005b 00 AX 0 0 4
[ 2] .rel.text REL 00000000 000428 000028 08 9 1 4
[ 3] .data PROGBITS 00000000 000090 000008 00 WA 0 0 4
[ 4] .bss NOBITS 00000000 000098 000004 00 WA 0 0 4
[ 5] .rodata PROGBITS 00000000 000098 000004 00 A 0 0 1
[ 6] .comment PROGBITS 00000000 00009c 00002a 00 0 0 1
[ 7] .note.GNU-stack PROGBITS 00000000 0000c6 000000 00 0 0 1
[ 8] .shstrtab STRTAB 00000000 0000c6 000051 00 0 0 1
[ 9] .symtab SYMTAB 00000000 0002d0 0000f0 10 10 10 4
[10] .strtab STRTAB 00000000 0003c0 000066 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific
段表是ELF文件中除了文件头以外最重要的结构,它描述了ELF的各个段
的信息,比如每个段的段名、段的长度、在文件中的偏移、读写权限及段的其他属性。也就是说,ELF文件的段结构就是由段表决定的,编译器、链接器和装载器都是依靠段表来定位和访问各个段的属性的。
(3)重定位表
我们注意到,SimpleSection.o中有一个叫做“.rel.text”的段,它的类型(sh_type)为“SHT_REL”,也就是说它是一个重定位表(Relocation Table)。重定位表主要是告诉链接器哪条指令要进行调整,以及如何进行调整。
$ objdump -r a.o
a.o: file format elf32-i386
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
0000001c R_386_32 shared
00000027 R_386_PC32 swap
可以看出来要进行重定位的是shared和swap,以及重定位的类型。R_386_32是绝对寻址,R_386_PC32是相对寻址。
(4)符号表
ELF文件中的符号表往往是文件中的一个段,段名一般叫“.symtab”。
$ readelf –s SimpleSection.o
Symbol table '.symtab' contains 15 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS SimpleSection.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: 00000000 4 OBJECT LOCAL DEFAULT 4 static_var2.1534
7: 00000004 4 OBJECT LOCAL DEFAULT 3 static_var.1533
8: 00000000 0 SECTION LOCAL DEFAULT 7
9: 00000000 0 SECTION LOCAL DEFAULT 6
10: 00000000 4 OBJECT GLOBAL DEFAULT 3 global_init_var
11: 00000000 27 FUNC GLOBAL DEFAULT 1 func1
12: 00000000 0 NOTYPE GLOBAL DEFAULT UND printf
13: 0000001b 64 FUNC GLOBAL DEFAULT 1 main
14: 00000004 4 OBJECT GLOBAL DEFAULT COM global_uninit_var
第一列Num表示符号表数组的下标,从0开始,共15个符号;第二列Value就是符号值,即st_value;第三列Size为符号大小,即st_size;第四列和第五列分别为符号类型和绑定信息,即对应st_info的低4位和高28位;第六列Vis目前在C/C++语言中未使用,我们可以暂时忽略它;第七列Ndx即st_shndx,表示该符号所属的段;当然最后一列也最明显,即符号名称。
符号表记录了符号的地址,在进行重定位的时候,要根据符号的地址进行重定位。
(5)字符串表
ELF文件中用到了很多字符串,比如段名、变量名等。因为字符串的长度往往是不定的,所以用固定的结构来表示它比较困难。一种很常见的做法是把字符串集中起来存放到一个表,然后使用字符串在表中的偏移来引用字符串。比如表3-12这个字符串表。
通过这种方法,在ELF文件中引用字符串只须给出一个数字下标即可,不用考虑字符串长度的问题。
(6)调试信息
标文件里面还有可能保存的是调试信息。几乎所有现代的编译器都支持源代码级别的调试,比如我们可以在函数里面设置断点,可以监视变量变化,可以单步行进等,前提是编译器必须提前将源代码与目标代码之间的关系等,比如目标代码中的地址对应源代码中的哪一行、函数和变量的类型、结构体的定义、字符串保存到目标文件里面。甚至有些高级的编译器和调试器支持查看STL容器的内容,即程序员在调试过程中可以直接观察STL容器中的成员的值。如果我们在GCC编译时加上“-g”参数,编译器就会在产生的目标文件里面加上调试信息,我们通过readelf等工具可以看到,目标文件里多了很多“debug”相关的段:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
...
[ 4] .debug_abbrev PROGBITS 00000000 000040 000034 00 0 0 1
[ 5] .debug_info PROGBITS 00000000 000074 0000af 00 0 0 1
[ 6] .rel.debug_info REL 00000000 000738 000038 08 9 5 4
[ 7] .debug_line PROGBITS 00000000 000123 000037 00 0 0 1
[ 8] .rel.debug_line REL 00000000 000770 000008 08 19 7 4
[ 9] .debug_frame PROGBITS 00000000 00015c 000034 00 0 0 4
[10] .rel.debug_frame REL 00000000 000778 000010 08 19 9 4
[11] .debug_loc PROGBITS 00000000 000190 00002c 00 0 0 1
[12] .debug_pubnames PROGBITS 00000000 0001bc 00001a 00 0 0 1
[13] .rel.debug_pubnam REL 00000000 000788 000008 08 19 12 4
[14] .debug_aranges PROGBITS 00000000 0001d6 000020 00 0 0 1
[15] .rel.debug_arange REL 00000000 000790 000010 08 19 14 4
...