轉自:https://www.cnblogs.com/jiqingwu/p/elf_format_research_01.html
ELF 是Executable and Linking Format的縮寫,即可執行和可鏈接的格式,是Unix/Linux系統ABI (Application Binary Interface)規范的一部分。
Unix/Linux下的可執行二進制文件、目標代碼文件、共享庫文件和core dump文件都屬於ELF文件。
下面的圖來自於文檔 Executable and Linkable Format (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 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,表示可寫,需分配內存,這都是數據段的特征。
使用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加載到內存中,生成進程映像的。
歡迎繼續關注。