ELF文件解析一

ELF 是Executable and Linking Format的缩写,即可执行和可链接的格式,是Unix/Linux系统ABI (Application Binary Interface)规范的一部分。
Unix/Linux下的可执行二进制文件、目标代码文件、共享库文件和core dump文件都属于ELF文件。


左边是ELF的链接视图,可以理解为是目标代码文件的内容布局。右边是ELF的执行视图,可以理解为可执行文件的内容布局。
注意目标代码文件的内容是由section组成的,而可执行文件的内容是由segment组成的。

我们写汇编程序时,用.text,.bss,.data这些指示,都指的是section,比如.text,告诉汇编器后面的代码放入.text section中。
目标代码文件中的section和section header table中的条目是一一对应的。section的信息用于链接器对代码重定位。
而文件载入内存执行时,是以segment组织的,每个segment对应ELF文件中program header table中的一个条目,用来建立可执行文件的进程映像。
比如我们通常说的,代码段、数据段是segment,目标代码中的section会被链接器组织到可执行文件的各个segment中。
.text section的内容会组装到代码段中,.data, .bss等节的内容会包含在数据段中。

在目标文件中,program header不是必须的,我们用gcc生成的目标文件也不包含program header。

readelf的一些命令

解析ELF文件的常用工具是readelf。命令是readelf -S 文件名,输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
There are 28 section headers, starting at offset 0xe88:

Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400200 00000200
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 000000000040021c 0000021c
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 000000000040023c 0000023c
0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400260 00000260
0000000000000028 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 0000000000400288 00000288
0000000000000138 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 00000000004003c0 000003c0
00000000000000a4 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000400464 00000464
000000000000001a 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000400480 00000480
0000000000000040 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 00000000004004c0 000004c0
0000000000000048 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000400508 00000508
00000000000000f0 0000000000000018 A 5 12 8
[11] .init PROGBITS 00000000004005f8 000005f8
000000000000001a 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 0000000000400620 00000620
00000000000000b0 0000000000000010 AX 0 0 16
[13] .text PROGBITS 00000000004006d0 000006d0
0000000000000252 0000000000000000 AX 0 0 16
[14] .fini PROGBITS 0000000000400924 00000924
0000000000000009 0000000000000000 AX 0 0 4
[15] .rodata PROGBITS 0000000000400930 00000930
0000000000000068 0000000000000000 A 0 0 8
[16] .eh_frame_hdr PROGBITS 0000000000400998 00000998
000000000000002c 0000000000000000 A 0 0 4
[17] .eh_frame PROGBITS 00000000004009c8 000009c8
00000000000000d4 0000000000000000 A 0 0 8
[18] .init_array INIT_ARRAY 0000000000600aa0 00000aa0
0000000000000008 0000000000000000 WA 0 0 8
[19] .fini_array FINI_ARRAY 0000000000600aa8 00000aa8
0000000000000008 0000000000000000 WA 0 0 8
[20] .jcr PROGBITS 0000000000600ab0 00000ab0
0000000000000008 0000000000000000 WA 0 0 8
[21] .dynamic DYNAMIC 0000000000600ab8 00000ab8
00000000000001d0 0000000000000010 WA 6 0 8
[22] .got PROGBITS 0000000000600c88 00000c88
0000000000000008 0000000000000008 WA 0 0 8
[23] .got.plt PROGBITS 0000000000600c90 00000c90
0000000000000068 0000000000000008 WA 0 0 8
[24] .data PROGBITS 0000000000600d00 00000d00
0000000000000040 0000000000000000 WA 0 0 32
[25] .bss NOBITS 0000000000600d40 00000d40
0000000000000018 0000000000000000 WA 0 0 16
[26] .comment PROGBITS 0000000000000000 00000d40
000000000000004d 0000000000000001 MS 0 0 1
[27] .shstrtab STRTAB 0000000000000000 00000d8d
00000000000000f8 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)

readelf -S是显示文件中的Section信息,
可以看到,除了我们熟悉的.text, .data, .bss,还有其它Section,这等我们以后展开讲Section的时候还会专门讲到。
看每个Section的Flags我们也可以得到一些信息,比如.text section的Flags是AX,表示要分配内存,并且是可执行的,这一节是代码无疑了。
.data 和 .bss的Flags的Flags都是WA,表示可写,需分配内存,这都是数据段的特征。
使用readelf -l可以显示文件的program header信息。还是这个文件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Elf file type is EXEC (Executable file)
Entry point 0x4006ee
There are 8 program headers, starting at offset 64

Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001c0 0x00000000000001c0 R E 0x8
INTERP 0x0000000000000200 0x0000000000400200 0x0000000000400200
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x0000000000000a9c 0x0000000000000a9c R E 0x200000
LOAD 0x0000000000000aa0 0x0000000000600aa0 0x0000000000600aa0
0x00000000000002a0 0x00000000000002b8 RW 0x200000
DYNAMIC 0x0000000000000ab8 0x0000000000600ab8 0x0000000000600ab8
0x00000000000001d0 0x00000000000001d0 RW 0x8
NOTE 0x000000000000021c 0x000000000040021c 0x000000000040021c
0x0000000000000044 0x0000000000000044 R 0x4
GNU_EH_FRAME 0x0000000000000998 0x0000000000400998 0x0000000000400998
0x000000000000002c 0x000000000000002c R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10

Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07

如输出所示,文件中共有8个segment。只有类型为LOAD的段是运行时真正需要的。
除了段信息,还输出了每个段包含了哪些section。比如第一个LOAD段标志为R(只读)E(可执行)的,它的编号是02,表示它包含哪些section的那一行内容为:

1
02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame

然后再根据对应的section去分辨他们的属性.