本節內容 - 通用可執行文件結構(COFF)(readelf -h) - COFF用段(section)存儲不同類型數據(readelf -S) - 常用段 - 演示:使用readelf、xxd、objdump、gdb查看可執行文件結構信息 - 演示:objcopy -add-section;st... ...
03 可執行文件結構
本節內容
- 通用可執行文件結構(COFF)(readelf -h)
- COFF用段(section)存儲不同類型數據(readelf -S)
- 常用段
- 演示:使用readelf、xxd、objdump、gdb查看可執行文件結構信息[付費閱讀]
- 演示:objcopy -add-section;strip -remove-section;readelf -p[付費閱讀]
編譯完成了,鏈接完成了,我們現在得到可執行文件了,接下來問題是可執行文件是什麼樣子?
通用可執行文件結構(COFF)(readelf -h)
$ cat hello.c
這是很簡單的c語言代碼,有兩個引入標準庫的頭,三個全局變數,x有初始化值,y沒有初始化值,s是個字元串。
#include <stdio.h>
#include <stdlib.h>
int x = 0x1234;
int y;
char *s = "x = %x, y = %x\n";
int main()
{
printf(s, x, y);
return 0;
}
編譯成可執行文件,包含調試信息,禁止優化
$ gcc -g -O0 -o test hello.c
$ ./test
這個可執行文件到底什麼樣子?先看可執行文件是怎麼描述自己的,使用readelf -h查看可執行文件頭部信息。
$ readelf -h test #輸出可執行文件頭部信息
輸出
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x400430
Start of program headers: 64 (bytes into file)
Start of section headers: 7512 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 36
Section header string table index: 33
Magic標識用於判斷是否是標準ELF文件。
Class代表是64位可執行文件。
Entry point address代表第一條執行指令地址,正好指向.text段。
下麵描述可執行文件結構的,一共有多少節,各自尺寸,從哪開始。
我們把這種看上去像資料庫這種格式的可執行文件通常稱之為COFF,COFF是通用可執行文件結構,不同的公司對它做具體的一些定製,比如說Linux把它的改進版本稱之為ELF,Windows的改進版本稱之為PE,其實它們都屬於COFF範疇,只不過每家公司對它細節處理不一樣,但是它格式基本上類似的。
COFF用段(section)存儲不同類型數據(readelf -S)
$ readelf -S test #輸出可執行文件段信息
輸出
There are 36 section headers, starting at offset 0x1d58:
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 0000000000400238 00000238
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000400254 00000254
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 0000000000400274 00000274
0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400298 00000298
000000000000001c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000004002b8 000002b8
0000000000000060 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000400318 00000318
000000000000003f 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000400358 00000358
0000000000000008 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000400360 00000360
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000400380 00000380
0000000000000018 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000400398 00000398
0000000000000030 0000000000000018 AI 5 24 8
[11] .init PROGBITS 00000000004003c8 000003c8
000000000000001a 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 00000000004003f0 000003f0
0000000000000030 0000000000000010 AX 0 0 16
[13] .plt.got PROGBITS 0000000000400420 00000420
0000000000000008 0000000000000000 AX 0 0 8
[14] .text PROGBITS 0000000000400430 00000430
00000000000001a2 0000000000000000 AX 0 0 16
[15] .fini PROGBITS 00000000004005d4 000005d4
0000000000000009 0000000000000000 AX 0 0 4
[16] .rodata PROGBITS 00000000004005e0 000005e0
0000000000000014 0000000000000000 A 0 0 4
[17] .eh_frame_hdr PROGBITS 00000000004005f4 000005f4
0000000000000034 0000000000000000 A 0 0 4
[18] .eh_frame PROGBITS 0000000000400628 00000628
00000000000000f4 0000000000000000 A 0 0 8
[19] .init_array INIT_ARRAY 0000000000600e10 00000e10
0000000000000008 0000000000000000 WA 0 0 8
[20] .fini_array FINI_ARRAY 0000000000600e18 00000e18
0000000000000008 0000000000000000 WA 0 0 8
[21] .jcr PROGBITS 0000000000600e20 00000e20
0000000000000008 0000000000000000 WA 0 0 8
[22] .dynamic DYNAMIC 0000000000600e28 00000e28
00000000000001d0 0000000000000010 WA 6 0 8
[23] .got PROGBITS 0000000000600ff8 00000ff8
0000000000000008 0000000000000008 WA 0 0 8
[24] .got.plt PROGBITS 0000000000601000 00001000
0000000000000028 0000000000000008 WA 0 0 8
[25] .data PROGBITS 0000000000601028 00001028
0000000000000020 0000000000000000 WA 0 0 8
[26] .bss NOBITS 0000000000601048 00001048
0000000000000008 0000000000000000 WA 0 0 4
[27] .comment PROGBITS 0000000000000000 00001048
0000000000000034 0000000000000001 MS 0 0 1
[28] .debug_aranges PROGBITS 0000000000000000 0000107c
0000000000000030 0000000000000000 0 0 1
[29] .debug_info PROGBITS 0000000000000000 000010ac
00000000000000de 0000000000000000 0 0 1
[30] .debug_abbrev PROGBITS 0000000000000000 0000118a
000000000000005c 0000000000000000 0 0 1
[31] .debug_line PROGBITS 0000000000000000 000011e6
000000000000003e 0000000000000000 0 0 1
[32] .debug_str PROGBITS 0000000000000000 00001224
00000000000000c4 0000000000000001 MS 0 0 1
[33] .shstrtab STRTAB 0000000000000000 00001c06
000000000000014c 0000000000000000 0 0 1
[34] .symtab SYMTAB 0000000000000000 000012e8
0000000000000708 0000000000000018 35 52 8
[35] .strtab STRTAB 0000000000000000 000019f0
0000000000000216 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
假如可執行文件是個資料庫的話,這裡面一大堆的表,這些表看上去有點暈,不知道裡面到底什麼東西。
我們知道所謂的段就是這些,每個段存儲不同類型的數據,這裡面有些信息可以看到,每一節的具體描述:Name名稱、Type類型、Address起始地址、Offset相對於當前可執行文件的偏移量、Size長度、Flags許可權開關。這些數據表裡面有幾個東西需要註意的,這些數據信息的數量和名字不是固定的,每種編譯器都有自己的規則,這個信息對於編譯器、反彙編有用,CPU不關心這些,CPU只關心地址從哪開始讀多長數據到哪結束。至於你這東西屬於哪個數據表叫什麼名字跟CPU一點關係也沒有,這些東西實際上是給編譯器用的,但是這些名字雖然每種編譯器有各自的習慣,也不太一樣,但是有幾個是約定俗成的。
常用段
有幾個名字是約定俗成的
- .text存儲的全部是機器碼。
- .data存儲的是有初始化值的全局變數,靜態局部變數。
- .bss存儲沒有初始化值的全局變數。
- .rodata存儲的是只讀數據,比如字面量。
$ readelf -x .text test #查看.text段機器指令內容
輸出
Hex dump of section '.text':
0x00400430 31ed4989 d15e4889 e24883e4 f0505449 1.I..^H..H...PTI
0x00400440 c7c0d005 400048c7 c1600540 0048c7c7 [email protected]..`[email protected]..
0x00400450 26054000 e8b7ffff fff4660f 1f440000 &[email protected]..
0x00400460 b84f1060 0055482d 48106000 4883f80e .O.`.UH-H.`.H...
0x00400470 4889e576 1bb80000 00004885 c074115d H..v......H..t.]
0x00400480 bf481060 00ffe066 0f1f8400 00000000 .H.`...f........
0x00400490 5dc30f1f 4000662e 0f1f8400 00000000 ][email protected].........
0x004004a0 be481060 00554881 ee481060 0048c1fe .H.`.UH..H.`.H..
0x004004b0 034889e5 4889f048 c1e83f48 01c648d1 .H..H..H..?H..H.
0x004004c0 fe7415b8 00000000 4885c074 0b5dbf48 .t......H..t.].H
0x004004d0 106000ff e00f1f00 5dc3660f 1f440000 .`......].f..D..
0x004004e0 803d610b 20000075 11554889 e5e86eff .=a. ..u.UH...n.
0x004004f0 ffff5dc6 054e0b20 0001f3c3 0f1f4000 ..]..N. ......@.
0x00400500 bf200e60 0048833f 007505eb 930f1f00 . .`.H.?.u......
0x00400510 b8000000 004885c0 74f15548 89e5ffd0 .....H..t.UH....
0x00400520 5de97aff ffff5548 89e58b15 1c0b2000 ].z...UH...... .
0x00400530 8b0d020b 2000488b 05030b20 0089ce48 .... .H.... ...H
0x00400540 89c7b800 000000e8 b4feffff b8000000 ................
0x00400550 005dc366 2e0f1f84 00000000 000f1f00 .].f............
0x00400560 41574156 4189ff41 5541544c 8d259e08 AWAVA..AUATL.%..
0x00400570 20005548 8d2d9e08 20005349 89f64989 .UH.-.. .SI..I.
0x00400580 d54c29e5 4883ec08 48c1fd03 e837feff .L).H...H....7..
0x00400590 ff4885ed 742031db 0f1f8400 00000000 .H..t 1.........
0x004005a0 4c89ea4c 89f64489 ff41ff14 dc4883c3 L..L..D..A...H..
0x004005b0 014839eb 75ea4883 c4085b5d 415c415d .H9.u.H...[]A\A]
0x004005c0 415e415f c390662e 0f1f8400 00000000 A^A_..f.........
0x004005d0 f3c3 ..
.bss在文件內沒有存儲,可查看offset確認
為何全局變數需要分開存儲?全局變數實際上是要寫到可執行文件里,.data和.bss,如果這個全局變數本身有初始化值是不是要找地方存起來,要不然程式執行時候根本不知道初始化值是多少,.data用來存儲這些初始化具體的值,.bss雖然也是全局變數,但沒有初始化值,沒有初始化值沒必要寫到可執行文件裡面,因為你根本沒有數據,只有在運行期才初始化,你在編譯時候根本沒有值幹嘛去寫,所以它只是保留了.bss表用於映射地址信息,但是不存儲裡面任何數據,你會註意到它相對文件的偏移量與下個段節點的偏移量一樣,就說明這裡面根本沒有數據,否則兩個偏移量不可能相等。因為像int y;
的值只有在運行期才被初始化或者給它一個莫名其妙隨機值。既然沒有初始化值我編譯時候根本不需要類似1234這樣的值保存起來,你不要忘了在沒有執行之前這些有初始化值的數據必須保存起來,你保存在哪,肯定在可執行文件裡面有個表存這些數據,否則數據執行時候數據從哪裡來。
go區分有指針和沒指針data(data、.noptrdata)、bss,便於GC操作[付費閱讀]
演示:使用readelf、xxd、objdump、gdb查看可執行文件結構信息[付費閱讀]
演示:objcopy -add-section;strip -remove-section;readelf -p[付費閱讀]
這個系列的每篇文章有大半篇幅內容屬於付費閱讀。提供微信支付或支付寶支付打賞50元備註留言手動提供付費文章訪問密碼。