Linux下ELF文件類型分爲以下幾種:
1、可重定位文件,例如SimpleSection.o;
2、可執行文件,例如/bin/bash;
3、共享目標文件,例如/lib/libc.so。
在Linux 可重定位文件 ELF結構一文中,我們已經分析了可重定位文件ELF結構。本文分析可執行文件的ELF結構。
首先附上源代碼:
SectionMapping.c
-
#include <stdlib.h>
-
-
int main()
-
{
-
while(1)
-
{
-
sleep(1000);
-
}
-
return 0;
-
}
使用命令gcc -static SectionMapping.c -o SectionMapping.elf,靜態鏈接爲可執行文件。
接着使用命令readelf -S SectionMapping.elf得到Section Table。如下:
-
There are 33 section headers, starting at offset 0xc3878:
-
-
Section Headers:
-
[Nr] Name Type Address Offset
-
Size EntSize Flags Link Info Align
-
[ 0] NULL 0000000000000000 00000000
-
0000000000000000 0000000000000000 0 0 0
-
[ 1] .note.ABI-tag NOTE 0000000000400190 00000190
-
0000000000000020 0000000000000000 A 0 0 4
-
[ 2] .note.gnu.build-i NOTE 00000000004001b0 000001b0
-
0000000000000024 0000000000000000 A 0 0 4
-
[ 3] .rela.plt RELA 00000000004001d8 000001d8
-
0000000000000120 0000000000000018 A 0 5 8
-
[ 4] .init PROGBITS 00000000004002f8 000002f8
-
0000000000000018 0000000000000000 AX 0 0 4
-
[ 5] .plt PROGBITS 0000000000400310 00000310
-
00000000000000c0 0000000000000000 AX 0 0 16
-
[ 6] .text PROGBITS 00000000004003d0 000003d0
-
0000000000094988 0000000000000000 AX 0 0 16
-
[ 7] __libc_thread_fre PROGBITS 0000000000494d60 00094d60
-
00000000000000a8 0000000000000000 AX 0 0 16
-
[ 8] __libc_freeres_fn PROGBITS 0000000000494e10 00094e10
-
000000000000181c 0000000000000000 AX 0 0 16
-
[ 9] .fini PROGBITS 000000000049662c 0009662c
-
000000000000000e 0000000000000000 AX 0 0 4
-
[10] .rodata PROGBITS 0000000000496640 00096640
-
000000000001d344 0000000000000000 A 0 0 32
-
[11] __libc_thread_sub PROGBITS 00000000004b3988 000b3988
-
0000000000000008 0000000000000000 A 0 0 8
-
[12] __libc_subfreeres PROGBITS 00000000004b3990 000b3990
-
0000000000000058 0000000000000000 A 0 0 8
-
[13] __libc_atexit PROGBITS 00000000004b39e8 000b39e8
-
0000000000000008 0000000000000000 A 0 0 8
-
[14] .eh_frame PROGBITS 00000000004b39f0 000b39f0
-
000000000000d4c4 0000000000000000 A 0 0 8
-
[15] .gcc_except_table PROGBITS 00000000004c0eb4 000c0eb4
-
0000000000000172 0000000000000000 A 0 0 1
-
[16] .tdata PROGBITS 00000000006c1ef0 000c1ef0
-
0000000000000020 0000000000000000 WAT 0 0 16
-
[17] .tbss NOBITS 00000000006c1f10 000c1f10
-
0000000000000038 0000000000000000 WAT 0 0 16
-
[18] .init_array INIT_ARRAY 00000000006c1f10 000c1f10
-
0000000000000008 0000000000000000 WA 0 0 8
-
[19] .fini_array FINI_ARRAY 00000000006c1f18 000c1f18
-
0000000000000008 0000000000000000 WA 0 0 8
-
[20] .ctors PROGBITS 00000000006c1f20 000c1f20
-
0000000000000010 0000000000000000 WA 0 0 8
-
[21] .dtors PROGBITS 00000000006c1f30 000c1f30
-
0000000000000010 0000000000000000 WA 0 0 8
-
[22] .jcr PROGBITS 00000000006c1f40 000c1f40
-
0000000000000008 0000000000000000 WA 0 0 8
-
[23] .data.rel.ro PROGBITS 00000000006c1f50 000c1f50
-
0000000000000080 0000000000000000 WA 0 0 16
-
[24] .got PROGBITS 00000000006c1fd0 000c1fd0
-
0000000000000010 0000000000000008 WA 0 0 8
-
[25] .got.plt PROGBITS 00000000006c1fe8 000c1fe8
-
0000000000000078 0000000000000008 WA 0 0 8
-
[26] .data PROGBITS 00000000006c2060 000c2060
-
0000000000001690 0000000000000000 WA 0 0 32
-
[27] .bss NOBITS 00000000006c3700 000c36f0
-
0000000000002ba8 0000000000000000 WA 0 0 32
-
[28] __libc_freeres_pt NOBITS 00000000006c62b0 000c36f0
-
0000000000000048 0000000000000000 WA 0 0 16
-
[29] .comment PROGBITS 0000000000000000 000c36f0
-
000000000000002a 0000000000000001 MS 0 0 1
-
[30] .shstrtab STRTAB 0000000000000000 000c371a
-
000000000000015b 0000000000000000 0 0 1
-
[31] .symtab SYMTAB 0000000000000000 000c40b8
-
000000000000c168 0000000000000018 32 870 8
-
[32] .strtab STRTAB 0000000000000000 000d0220
-
0000000000007a26 0000000000000000 0 0 1
表 1
這個可執行文件共有33個Section。
接着我們使用readelf -h SectionMapping.elf,讀取elf可執行文件頭部信息。如下圖:
![]()
圖 1
可以對比,Linux 可重定位文件 ELF結構,這裏多了program header。
Entry point address:程序的入口地址是0x401058,使用objdump -d SectionMapping.elf | less,可以查看到程序的入口地址是<_start>。如下圖:
![]()
圖 2
Start of program headers:program headers的偏移,由於頭文件大小爲64,所以program headers緊挨着頭文件存放。
Size of program headers:program headers的大小。爲56個字節。
Number of section headers:program headers的數量。爲6個。
在表1中,第一個section在文件中的偏移是0x190,頭文件大小爲64 + program header大小爲56 * program header數量6 = 400 = 0x190。
然後,我們使用命令readelf -l SectionMapping.elf,我們會得到program header部分。如下圖:
![]()
圖 3
從圖中可見,分爲6個Segment。注意表1中每個段叫Section。
Offset:這個Segment在文件中偏移。
VirtAddr:這個Segment在虛擬地址的偏移。
FileSiz:在ELF文件中所佔的長度。
MemSiz:在進程虛擬空間所佔的長度。
我們發現第二個Segment,MemSiz > FileSiz,表示在內存中分配的空間大小超過文件實際大小。超過的部分全部初始化爲0,作爲BSS段。因爲數據段和BSS段的唯一區別是,數據段從文件中初始化內容,BSS段內容全部初始化爲0。
我們主要關心前兩個Segment,第一個是代碼段,虛擬地址從0x00400000到0x004c1026。文件偏移從0x00000000到0x000c1026。
第二個是數據段,虛擬地址爲從0x006c1ef0到0x006c1ef0+0x4408=0x6c62f8。文件偏移從0x000c1ef0到0x000c1ef0+0x1800=0x000C36f0。
結合表1和兩個Segment的文件偏移,可以得出:
第一個Segment從第0個Section到第15個Section。(0x00000000-0x000c1026)
-
[Nr] Name Type Address Offset
-
Size EntSize Flags Link Info Align
-
[ 0] NULL 0000000000000000 00000000
-
0000000000000000 0000000000000000 0 0 0
-
[ 1] .note.ABI-tag NOTE 0000000000400190 00000190
-
0000000000000020 0000000000000000 A 0 0 4
-
[ 2] .note.gnu.build-i NOTE 00000000004001b0 000001b0
-
0000000000000024 0000000000000000 A 0 0 4
-
[ 3] .rela.plt RELA 00000000004001d8 000001d8
-
0000000000000120 0000000000000018 A 0 5 8
-
[ 4] .init PROGBITS 00000000004002f8 000002f8
-
0000000000000018 0000000000000000 AX 0 0 4
-
[ 5] .plt PROGBITS 0000000000400310 00000310
-
00000000000000c0 0000000000000000 AX 0 0 16
-
[ 6] .text PROGBITS 00000000004003d0 000003d0
-
0000000000094988 0000000000000000 AX 0 0 16
-
[ 7] __libc_thread_fre PROGBITS 0000000000494d60 00094d60
-
00000000000000a8 0000000000000000 AX 0 0 16
-
[ 8] __libc_freeres_fn PROGBITS 0000000000494e10 00094e10
-
000000000000181c 0000000000000000 AX 0 0 16
-
[ 9] .fini PROGBITS 000000000049662c 0009662c
-
000000000000000e 0000000000000000 AX 0 0 4
-
[10] .rodata PROGBITS 0000000000496640 00096640
-
000000000001d344 0000000000000000 A 0 0 32
-
[11] __libc_thread_sub PROGBITS 00000000004b3988 000b3988
-
0000000000000008 0000000000000000 A 0 0 8
-
[12] __libc_subfreeres PROGBITS 00000000004b3990 000b3990
-
0000000000000058 0000000000000000 A 0 0 8
-
[13] __libc_atexit PROGBITS 00000000004b39e8 000b39e8
-
0000000000000008 0000000000000000 A 0 0 8
-
[14] .eh_frame PROGBITS 00000000004b39f0 000b39f0
-
000000000000d4c4 0000000000000000 A 0 0 8
-
[15] .gcc_except_table PROGBITS 00000000004c0eb4 000c0eb4
-
0000000000000172 0000000000000000 A 0 0 1
第二個Segment從第16個Section到26個Section。(0x000c1ef0-0x000C36f0)
-
[16] .tdata PROGBITS 00000000006c1ef0 000c1ef0
-
0000000000000020 0000000000000000 WAT 0 0 16
-
[17] .tbss NOBITS 00000000006c1f10 000c1f10
-
0000000000000038 0000000000000000 WAT 0 0 16
-
[18] .init_array INIT_ARRAY 00000000006c1f10 000c1f10
-
0000000000000008 0000000000000000 WA 0 0 8
-
[19] .fini_array FINI_ARRAY 00000000006c1f18 000c1f18
-
0000000000000008 0000000000000000 WA 0 0 8
-
[20] .ctors PROGBITS 00000000006c1f20 000c1f20
-
0000000000000010 0000000000000000 WA 0 0 8
-
[21] .dtors PROGBITS 00000000006c1f30 000c1f30
-
0000000000000010 0000000000000000 WA 0 0 8
-
[22] .jcr PROGBITS 00000000006c1f40 000c1f40
-
0000000000000008 0000000000000000 WA 0 0 8
-
[23] .data.rel.ro PROGBITS 00000000006c1f50 000c1f50
-
0000000000000080 0000000000000000 WA 0 0 16
-
[24] .got PROGBITS 00000000006c1fd0 000c1fd0
-
0000000000000010 0000000000000008 WA 0 0 8
-
[25] .got.plt PROGBITS 00000000006c1fe8 000c1fe8
-
0000000000000078 0000000000000008 WA 0 0 8
-
[26] .data PROGBITS 00000000006c2060 000c2060
-
0000000000001690 0000000000000000 WA 0 0 32
以上分析的都是靜態狀態下的程序,下面我們看看動態下的進程的空間是怎麼分配的。
首先使用命令, ./SectionMapping.elf &,輸出如下:
![]()
然後使用命令:cat /proc/2184/maps,輸出如下:
![]()
圖 4
靜態時,我們計算出的兩個Segment的虛擬空間的偏移分別爲:
第一個是代碼段,虛擬地址從0x00400000到0x004c1026。在圖4中,因爲要頁面對齊,所以分配了0x400000到0x4c2000。
第二個是數據段,虛擬地址爲從0x006c1ef0到0x006c1ef0+0x4408=0x6c62f8。在圖4中,因爲要頁面對齊,所以分配了0x6c1000到0x6c4000。注意,0x6c62f8大於0x6c4000,具體原因以後再分析。
第三個緊接着是堆。用於動態分配內存。
第四個是棧。用於存放局部變量。
整體的結構如下圖:
![]()
程序運行的過程:建立虛擬空間(分配一個頁目錄)-> 建立虛擬空間與可執行文件映射(頁目錄項指向磁盤的程序) -> 跳到程序入口 -> 缺頁異常-> 在內存中尋找空閒頁,將對應的頁換入 -> 建立映射 -> 開始執行。