ELF 是Executable and Linking Format的缩写,即可执⾏和可链接的格式,是Unix/Linux系统ABI (Application Binary Interface)规范的⼀部分。
Unix/Linux下的可执⾏⼆进制⽂件、⽬标代码⽂件、共享库⽂件和core dump⽂件都属于ELF⽂件。下⾯的图来⾃于⽂档 ,描述了ELF⽂件的⼤致布局。
左边是ELF的链接视图,可以理解为是⽬标代码⽂件的内容布局。右边是ELF的执⾏视图,可以理解为可执⾏⽂件的内容布局。注意⽬标代码⽂件的内容是由section组成的,⽽可执⾏⽂件的内容是由segment组成的。
要注意区分段(segment)和节(section)的概念,这两个概念在后⾯会经常提到。
我们写汇编程序时,⽤.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。
⼀个好⽤的解析ELF⽂件的⼯具是readelf。对我本机上的⼀个⽬标代码⽂件sleep.o执⾏readelf -S sleep.o,输出如下:
There are 12 section headers, starting at offset 0x270:
Section Headers:
[Nr] Name Type Address Offset Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .text PROGBITS 0000000000000000 00000040 0000000000000015 0000000000000000 AX 0 0 1 [ 2] .rela.text RELA 0000000000000000 000001e0 0000000000000018 0000000000000018 I 9 1 8
[ 3] .data PROGBITS 0000000000000000 00000055 0000000000000000 0000000000000000 WA 0 0 1 [ 4] .bss NOBITS 0000000000000000 00000055 0000000000000000 0000000000000000 WA 0 0 1 ... ... ... ...
[11] .shstrtab STRTAB 0000000000000000 00000210 0000000000000059 0000000000000000 0 0 1Key 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信息,sleep.o中共有12个section, 我们省略了其中⼀些Section的信息。
可以看到,除了我们熟悉的.text, .data, .bss,还有其它Section,这等我们以后展开讲Section的时候还会专门讲到。
看每个Section的Flags我们也可以得到⼀些信息,⽐如.text section的Flags是AX,表⽰要分配内存,并且是可执⾏的,这⼀节是代码⽆疑了。
.data 和 .bss的Flags的Flags都是WA,表⽰可写,需分配内存,这都是数据段的特征。
使⽤readelf -l可以显⽰⽂件的program header信息。我们对sleep.o执⾏readelf -l sleep.o。会输出There are no program headers in this file.。program header和⽂件中的segment⼀⼀对应,因为⽬标代码⽂件中没有segment,program header也就没有必要了。可执⾏⽂件的内容组织成segment,因此program header table是必须的。
section header不是必须的,但没有strip过的⼆进制⽂件中都含有此信息。对本地可执⾏⽂件sleep执⾏readelf -l sleep,输出如下:
Elf file type is DYN (Shared object file)Entry point 0x1040
There are 11 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040 0x0000000000000268 0x0000000000000268 R 0x8
INTERP 0x00000000000002a8 0x00000000000002a8 0x00000000000002a8 0x000000000000001c 0x000000000000001c R 0x1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000560 0x0000000000000560 R 0x1000
LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000 0x00000000000001d5 0x00000000000001d5 R E 0x1000
LOAD 0x0000000000002000 0x0000000000002000 0x0000000000002000 0x0000000000000110 0x0000000000000110 R 0x1000
LOAD 0x0000000000002de8 0x0000000000003de8 0x0000000000003de8 0x0000000000000248 0x0000000000000250 RW 0x1000
DYNAMIC 0x0000000000002df8 0x0000000000003df8 0x0000000000003df8 0x00000000000001e0 0x00000000000001e0 RW 0x8
NOTE 0x00000000000002c4 0x00000000000002c4 0x00000000000002c4 0x0000000000000044 0x0000000000000044 R 0x4
GNU_EH_FRAME 0x0000000000002004 0x0000000000002004 0x0000000000002004 0x0000000000000034 0x0000000000000034 R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x0000000000002de8 0x0000000000003de8 0x0000000000003de8 0x0000000000000218 0x0000000000000218 R 0x1
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 03 .init .plt .text .fini
04 .rodata .eh_frame_hdr .eh_frame
05 .init_array .fini_array .dynamic .got .got.plt .data .bss 06 .dynamic
07 .note.ABI-tag .note.gnu.build-id 08 .eh_frame_hdr 09
10 .init_array .fini_array .dynamic .got
如输出所⽰,⽂件中共有11个segment。只有类型为LOAD的段是运⾏时真正需要的。
除了段信息,还输出了每个段包含了哪些section。⽐如第⼆个LOAD段标志为R(只读)E(可执⾏)的,它的编号是03,表⽰它包含哪些section的那⼀⾏内容为:03 .init .plt .text .fini。
可以发现.text包含在其中,这⼀段就是代码段。
再⽐如第三个LOAD段,索引是04,标志为R(只读),但没有可执⾏的属性,它包含的section有.rodata .eh_frame_hdr .eh_frame,其中rodata表⽰只读的数据,也就是程序中⽤到的字符串常量等。
最后⼀个LOAD段,索引05,标志RW(可读写),它包含的节是.init_array .fini_array .dynamic .got .got.plt .data .bss,可以看到.data和.bss都包含其中,这段是数据段⽆疑。
今天先讲到这⾥,后⾯的内容这样组织:
⾸先讲⼀下Elf⽂件的header,因为⽂件⼀开始⼏⼗个字节就是Elf header的数据,这个数据结构包含了很多信息,还能告诉我们program header table, section header table在⽂件中什么位置。
接下来会讲⼀下如何解读section header table,以及section的数据如何组织的。
然后会讲program header table,以及segment的数据组织。section是如何组织成段的,这⼀点我们也要弄请求。最后我们会讲程序如果被loader加载到内存中,⽣成进程映像的。欢迎继续关注。
因篇幅问题不能全部显示,请点此查看更多更全内容