ELF 是Executable and Linking Format的缩写,便可执行和可连接的格式,是Unix/Linux系统ABI (Application Binary Interface)规范的一部分。linux
Unix/Linux下的可执行二进制文件、目标代码文件、共享库文件和core dump文件都属于ELF文件。编程
下面的图来自于文档 Executable and Linkable Format (ELF),描述了ELF文件的大体布局。数据结构
左边是ELF的连接视图,能够理解为是目标代码文件的内容布局。右边是ELF的执行视图,能够理解为可执行文件的内容布局。
注意目标代码文件的内容是由section组成的,而可执行文件的内容是由segment组成的。app
要注意区分段(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
,输出以下:ui
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 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信息,sleep.o中共有12个section, 咱们省略了其中一些Section的信息。
能够看到,除了咱们熟悉的.text, .data, .bss,还有其它Section,这等咱们之后展开讲Section的时候还会专门讲到。
看每一个Section的Flags咱们也能够获得一些信息,好比.text section的Flags是AX,表示要分配内存,而且是可执行的,这一节是代码无疑了。
.data 和 .bss的Flags的Flags都是WA,表示可写,需分配内存,这都是数据段的特征。this
使用readelf -l
能够显示文件的program header信息。咱们对sleep.o执行readelf -l sleep.o
。会输出There are no program headers in this file.
。
program header和文件中的segment一一对应,由于目标代码文件中没有segment,program header也就没有必要了。spa
可执行文件的内容组织成segment,所以program header table是必须的。
section header不是必须的,但没有strip过的二进制文件中都含有此信息。
对本地可执行文件sleep
执行readelf -l sleep
,输出以下:3d
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都包含其中,这段是数据段无疑。
今天先讲到这里,后面的内容这样组织:
欢迎继续关注。