Hello World Odyssey - 邂逅 ELF 本篇文章將介紹 ELF 檔案格式,以及引入一些符號的概念,對於逆向工程、linker 的概念都會有所助益,也有助於我們理解 Hello world 的底層原理。
一、為何要了解 ELF 檔案格式 ? 1.1 ELF 介紹 - 這不是我認識的 hello world 在 ELF 中文維基百科 (opens new window) 中,僅以一小段話簡介
可執行與可鏈結格式 (英語:Executable and Linkable Format,縮寫為ELF),常被稱為ELF格式,在電腦科學中,是一種用於執行檔、目的檔、共享庫和核心轉儲(core dump)的標準檔案格式。
在 Unix 體系下,可執行檔案(Executable file)跟目的檔案(Object file)都是依循著 ELF 檔案格式,一般原始程式碼變成可執行檔案的流程如下。
原本的 C 語言檔案,經過 compiler 編譯及 assembler 組譯後,得到二進位機器碼的目的檔案(Object file),之後我們會將所有引用到的函式庫以 linker 連結再一起,並且建立彼此的符號關係及進行重定位。以 hello world 檔案為例子,注意在此以靜態連結為例子。
# include <stdio.h>
int main ( )
{
printf ( "Hello world!\n" ) ;
}
1 2 3 4 5
1 2 3 4 5
Preprocessor
: 首先, stdio 會經由前處理器把整段 stdio.h 的內容貼上去 compiler / assembler
: 利用這兩個工具編譯成機器碼ELF格式的目的檔 linker
: 在 hello world 的 C 語言程式,我們引用到了 printf 的函數,我們以為函數在stdio.h 內就有實現,然而當我們查 stdio.h 的檔案時,卻發現一件意外的事情。 . . .
extern int printf ( const char * __restrict __format, . . . ) ;
. . .
1 2 3
1 2 3
什麼 ? 在 stdio.h 內居然只有 extern 外部引用的宣告而已,實作並不在此檔案 。也就是說,我們需要讓 linker 幫我們鏈接 libc.a
的靜態函式庫,因為 printf 的實作就躲在這個函式庫裡的 printf.o 檔案中。
倘若我們不鏈接,我們直接執行,程式沒有實作只有定義宛如沒有靈魂的空殼,無法順利執行。
這裡補充一下,xxx.a
檔案是靜態函式庫的檔案,可以看成是將所有 object file 的檔案合併在一個壓縮檔。那我們怎麼知道 libc.a 有 printf 的目的檔 ? 根據 這篇stackoverflow 的問答 (opens new window) ,輸入指令
ar -t /usr/lib/x86_64-linux-gnu/libc.a | grep ^printf.o
1
1
ar
是一個壓縮(archieve)指令,通常我們以這個指令來打包所有的目的檔成為一個靜態函式庫,-t
由 man ar
所述,是列出所有打包的內容檔案的選項。
...
t Display a table listing the contents of archive, or those of the files listed in member... that are
present in the archive. Normally only the member name is shown; if you also want to see the modes
(permissions), timestamp, owner, group, and size, you can request that by also specifying the v modifier.
...
1 2 3 4 5
1 2 3 4 5
由此指令,我們還真的可以發現 printf 的實作躲在這個函式庫,因此在靜態連結(static link)
時,直接將 hello.o 跟 printf.o 連結再一起即可。所以我們常講的ld linker 其實是叫 link editor,最後編譯的一道手續 ld 把該有的資訊寫進可執行檔。如果是靜態連結(static link)那麼就會去找出libxxx.a的函式庫檔,如同方才所述,把想要的程式碼片段拷貝一份進可執行檔並且做重定位後, 把參照的相互引用關係寫進可執行檔,這個檔就可以執行。
在陳鍾誠老師的網站 (opens new window) 也寫到,
ELF可用來記錄目的檔 (object file)、執行檔 (executable file)、動態連結檔 (share object)、與核心傾印 (core dump) 檔等格式,並且支援較先進的動態連結與載入等功能。因此,ELF 格式在 UNIX/Linux 的設計上具有相當關鍵性的地位。
為了支援連結與執行等兩種時期的不同用途,ELF 格式可以分為兩種不同觀點,而目標文件的格式其實與最終的可執行文件的格式是一致的,只是沒有經過符號的鏈接過程,這點要注意。
View 工具 檔案 位置 結構 連結時期觀點 (Linking View) linker 目的檔(Object file) 在硬碟時的樣子 以分段 (Section) 為主的結構 執行時期觀點 (Execution View) loader 執行檔 (Executable File) 在記憶體時的樣子 以分區 (Segment) 為主的結構,其中,一個區通常是數個分段的組合體,像是與程式有關的段落,包含程式段、程式重定位等,在執行時期會被組合為一個分區。
執行檔中也有很多檔案格式,例如 windows 的 COFF 檔案格式,Unix 體系的 ELF 檔案格式等等,在最後一道編譯過程中,linker ld 會偷偷放一堆資訊進去可執行檔。例如尤其是當我們有好多個compile後等待連結的.o 這種可重定位(relocatable) 檔,既然這些檔裡面變數或函式名的相對位置參照自己檔案的相對位置就有一些資訊是要告訴 link editor (就是 ld) 怎樣修改section的內容去做重定位(relocate)也就是做位址的重新參照以便合成一個新的可執行檔。之後在執行時,變數、函數的位置才可以相互正確引用。
最後,ELF 檔案在 linux 世界中執行兩項神聖的任務
告訴 kernel 從 disk 上的 ELF 文件,那些內容要擺放在哪個位置,並提供動態載入程序的功能 ELF 的定義的資料結構,提供理解檔案的有用訊息 1.2 學習 ELF 檔案格式的理由 由上一個小節,是不是開始覺得跟我們所想像的 hello world 開始不太一樣了😀,
即使我們脫去 IDE 的外殼,只用純粹的指令跟 makefile 來編譯執行程式碼,粒度仍然
太大,什麼意思 ? 別忘了 gcc 是 GNU Compiler Collection
的縮寫,gcc 是一個編譯的 toolchain,幫我們一條龍包裝編譯器、組譯器、鏈接器。直接幫我們做好成品 - 也就是我們的執行檔,隱藏中間發生的事情,封裝流程的代價就是我們容易忽略一些
細節,造成之後出現一些符號引用錯誤、鏈接錯誤等,需要多花很多功夫來學習,見招拆
招,但沒有一個系統性的學習很容易就忘記,下次遇到一樣的錯誤就要重新求救 google 搜尋引擎🙄。
功利導向 - 增加除錯效率,早點下班
學會 ELF 檔案格式,是了解符號連結跟鏈接器的敲門磚,對於符號處理、連結相關的問題也能快速排障,減少犯錯跟求助的時間,加速工作效率。
求知慾本能導向 - 增加內功
事實上,如同最前段所述,在一切都封裝好,一切都很便利的時代,我們往往忽略了細節,像是我們我們使用馬桶,卻不知馬桶的運作原理一樣。認識 ELF 檔案格式,就是我們開始增加內功的第一步,是對 linker 機制的尋幽訪勝,可以理解 linker, loader 的工作原理,同時學到更多符號的知識,在一切看似理所當然的世界,仍能增加一絲探索的樂趣。
這是必經之路,也是必要之惡😅。
1.3 分析 ELF 檔案格式兩大分析工具 在本篇介紹中,會交叉查看 readelf 跟 hexdump 的結果,是不是有種會逆向工程的資安駭客的味道(四叉貓式滾動)。
1.4 ELF 美圖秀秀 這裡列出幾張 ELF 的圖片,先讓你們對 ELF 格式有點印象,先放寬心,欣賞幾幅美麗的圖吧,不得不說,中國那裡科技文章真的都蠻會畫圖的🤣。
不知道各位有沒有聽過很有名的 3 的法則 ,使用 3 的法則可以大幅提高跟聚焦人的專注力,蘋果 CEO 賈伯斯在報告簡報時,也很常使用這種技巧。那我們 ELF 檔的格式,最主要也是由三個區塊拼湊出來的
ELF File 3 Blocks ELF Header ELF Section Header Table Sections
聚焦在這三個區塊,在往後的理解可以更加迅速。在下一章節,也會聚焦在這三大點上。
二、ELF 檔案格式 首先,我們先準備一個檔案,我這裡命名為 elf.c
,本章會在著重在這個檔案的分析。
unsigned long long data1 = 0xdddddddd11111111 ;
unsigned long long data2 = 0xdddddddd22222222 ;
void func1 ( ) { }
void func2 ( ) { }
1 2 3 4 5
1 2 3 4 5
之後下 gcc 指令將之編譯 但不連結 ,輸出 object file 的二進位檔案,接下來會不斷探討這個簡單的程式碼。
那我們先用 hexdump 來幫我們看一下內容吧 😀
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 01 00 3e 00 01 00 00 00 00 00 00 00 00 00 00 00 |..>.............|
00000020 00 00 00 00 00 00 00 00 b0 02 00 00 00 00 00 00 |................|
00000030 00 00 00 00 40 00 00 00 00 00 40 00 0b 00 0a 00 |....@.....@.....|
00000040 55 48 89 e5 90 5d c3 55 48 89 e5 90 5d c3 00 00 |UH...].UH...]...|
00000050 11 11 11 11 dd dd dd dd 22 22 22 22 dd dd dd dd |........""""....|
00000060 00 47 43 43 3a 20 28 55 62 75 6e 74 75 20 37 2e |.GCC: (Ubuntu 7.|
00000070 34 2e 30 2d 31 75 62 75 6e 74 75 31 7e 31 38 2e |4.0-1ubuntu1~18.|
00000080 30 34 2e 31 29 20 37 2e 34 2e 30 00 00 00 00 00 |04.1) 7.4.0.....|
00000090 14 00 00 00 00 00 00 00 01 7a 52 00 01 78 10 01 |.........zR..x..|
000000a0 1b 0c 07 08 90 01 00 00 1c 00 00 00 1c 00 00 00 |................|
000000b0 00 00 00 00 07 00 00 00 00 41 0e 10 86 02 43 0d |.........A....C.|
000000c0 06 42 0c 07 08 00 00 00 1c 00 00 00 3c 00 00 00 |.B..........<...|
000000d0 00 00 00 00 07 00 00 00 00 41 0e 10 86 02 43 0d |.........A....C.|
000000e0 06 42 0c 07 08 00 00 00 00 00 00 00 00 00 00 00 |.B..............|
000000f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000100 01 00 00 00 04 00 f1 ff 00 00 00 00 00 00 00 00 |................|
00000110 00 00 00 00 00 00 00 00 00 00 00 00 03 00 01 00 |................|
00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000130 00 00 00 00 03 00 02 00 00 00 00 00 00 00 00 00 |................|
00000140 00 00 00 00 00 00 00 00 00 00 00 00 03 00 03 00 |................|
00000150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000160 00 00 00 00 03 00 05 00 00 00 00 00 00 00 00 00 |................|
00000170 00 00 00 00 00 00 00 00 00 00 00 00 03 00 06 00 |................|
00000180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000190 00 00 00 00 03 00 04 00 00 00 00 00 00 00 00 00 |................|
000001a0 00 00 00 00 00 00 00 00 07 00 00 00 11 00 02 00 |................|
000001b0 00 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00 |................|
000001c0 0d 00 00 00 11 00 02 00 08 00 00 00 00 00 00 00 |................|
000001d0 08 00 00 00 00 00 00 00 13 00 00 00 12 00 01 00 |................|
000001e0 00 00 00 00 00 00 00 00 07 00 00 00 00 00 00 00 |................|
000001f0 19 00 00 00 12 00 01 00 07 00 00 00 00 00 00 00 |................|
00000200 07 00 00 00 00 00 00 00 00 65 6c 66 2e 63 00 64 |.........elf.c.d|
00000210 61 74 61 31 00 64 61 74 61 32 00 66 75 6e 63 31 |ata1.data2.func1|
00000220 00 66 75 6e 63 32 00 00 20 00 00 00 00 00 00 00 |.func2.. .......|
00000230 02 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 |................|
00000240 40 00 00 00 00 00 00 00 02 00 00 00 02 00 00 00 |@...............|
00000250 07 00 00 00 00 00 00 00 00 2e 73 79 6d 74 61 62 |..........symtab|
00000260 00 2e 73 74 72 74 61 62 00 2e 73 68 73 74 72 74 |..strtab..shstrt|
00000270 61 62 00 2e 74 65 78 74 00 2e 64 61 74 61 00 2e |ab..text..data..|
00000280 62 73 73 00 2e 63 6f 6d 6d 65 6e 74 00 2e 6e 6f |bss..comment..no|
00000290 74 65 2e 47 4e 55 2d 73 74 61 63 6b 00 2e 72 65 |te.GNU-stack..re|
000002a0 6c 61 2e 65 68 5f 66 72 61 6d 65 00 00 00 00 00 |la.eh_frame.....|
000002b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000002f0 1b 00 00 00 01 00 00 00 06 00 00 00 00 00 00 00 |................|
00000300 00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 |........@.......|
00000310 0e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000320 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000330 21 00 00 00 01 00 00 00 03 00 00 00 00 00 00 00 |!...............|
00000340 00 00 00 00 00 00 00 00 50 00 00 00 00 00 00 00 |........P.......|
00000350 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000360 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000370 27 00 00 00 08 00 00 00 03 00 00 00 00 00 00 00 |'...............|
00000380 00 00 00 00 00 00 00 00 60 00 00 00 00 00 00 00 |........`.......|
00000390 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000003a0 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000003b0 2c 00 00 00 01 00 00 00 30 00 00 00 00 00 00 00 |,.......0.......|
000003c0 00 00 00 00 00 00 00 00 60 00 00 00 00 00 00 00 |........`.......|
000003d0 2c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |,...............|
000003e0 01 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................|
000003f0 35 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 |5...............|
00000400 00 00 00 00 00 00 00 00 8c 00 00 00 00 00 00 00 |................|
00000410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000420 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000430 4a 00 00 00 01 00 00 00 02 00 00 00 00 00 00 00 |J...............|
00000440 00 00 00 00 00 00 00 00 90 00 00 00 00 00 00 00 |................|
00000450 58 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |X...............|
00000460 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000470 45 00 00 00 04 00 00 00 40 00 00 00 00 00 00 00 |E.......@.......|
00000480 00 00 00 00 00 00 00 00 28 02 00 00 00 00 00 00 |........(.......|
00000490 30 00 00 00 00 00 00 00 08 00 00 00 06 00 00 00 |0...............|
000004a0 08 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00 |................|
000004b0 01 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 |................|
000004c0 00 00 00 00 00 00 00 00 e8 00 00 00 00 00 00 00 |................|
000004d0 20 01 00 00 00 00 00 00 09 00 00 00 08 00 00 00 | ...............|
000004e0 08 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00 |................|
000004f0 09 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 |................|
00000500 00 00 00 00 00 00 00 00 08 02 00 00 00 00 00 00 |................|
00000510 1f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000520 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000530 11 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 |................|
00000540 00 00 00 00 00 00 00 00 58 02 00 00 00 00 00 00 |........X.......|
00000550 54 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |T...............|
00000560 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000570
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
沒想到短短四行程式碼,居然生出這麼巨大的內容,那是因為 在產生目的檔的過程中放入很多額外資訊,方便未來可執行、可鏈接 ,接下來就正式介紹 ELF 檔案格式吧。
接下來的文章,會大量引用到 elf.h
的程式碼,內有定義各種 ELF 使用到的資料結構
做為整個檔案的入口,ELF header 幫我們指出 Program table 和 Section Header table 的位置,內容包含了
魔數(magic number) 檔案類型和大小 靜態鏈接或是動態鏈接 程式進入點地址(如果是可執行檔案的話) ... 我們可以先去 elf.h
的檔案標頭檔,看看怎麼定義 elf 的資料結構,順便使用 readelf
幫我們解析 elf.o
的 ELF header,我們就可以得到 ELF 表頭完整的資訊。
typedef struct
{
unsigned char e_ident[ EI_NIDENT] ;
Elf64_Half e_type;
Elf64_Half e_machine;
Elf64_Word e_version;
Elf64_Addr e_entry;
Elf64_Off e_phoff;
Elf64_Off e_shoff;
Elf64_Word e_flags;
Elf64_Half e_ehsize;
Elf64_Half e_phentsize;
Elf64_Half e_phnum;
Elf64_Half e_shentsize;
Elf64_Half e_shnum;
Elf64_Half e_shstrndx;
} Elf64_Ehdr;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
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: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 688 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 11
Section header string table index: 10
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
你可以發現,其實標頭檔的資訊並沒有很多,粗略可以說,標頭檔是用來告訴我們這個檔案的相關訊息,接下來就要介紹這些欄位的用處了。
Data Type Bytes member name description unsigned char 16 e_ident[EI_NIDENT] Magic number and other info Elf64_Half 2 e_type Object file type Elf64_Half 2 e_machine Architecture Elf64_Word 4 e_version Object file version Elf64_Addr 8 e_entry Entry point virtual address Elf64_Off 8 e_phoff Program header table file offset Elf64_Off 8 e_shoff Section header table file offset Elf64_Word 4 e_flags Processor-specific flags Elf64_Half 2 e_ehsize ELF header size in bytes Elf64_Half 2 e_phentsize Program header table entry size Elf64_Half 2 e_phnum Program header table entry count Elf64_Half 2 e_shentsize Section header table entry size Elf64_Half 2 e_shnum Section header table entry count Elf64_Half 2 e_shstrndx Section header string table index
這裡挑幾個重點說明
e_ident
: 前四個bytes 被稱作 ELF的 Magic Number。後面的 bytes 描述了ELF 內容如何解碼等信息。
e_type
: 描述了ELF 的類型
type value Description ET_NONE 0 No file type ET_REL 1 Relocatable file(可重定位檔案,通常是文件名以.o结尾,目標檔案) ET_EXEC 2 Executable file (可執行檔案) ET_DYN 3 Shared object file (動態庫檔案,你用gcc編譯出的二進位往往也屬於這種類型😀) ET_CORE 4 core 檔案 ET_NUM 5 表示已經定義了5種檔案類型 ET_LOPROC 0XFF00 Processor-specific ET_HIPROC 0XFFFF Processor-specific
e_entry
: 執行入口點,如果檔案沒有入口點(不是可執行檔),這個區域保持0。
再來以下五個欄位,和我們後面的理解、計算地址偏移有很大的關係,可以稍微注意一下。
e_shoff
: section header table 的 offset e_ehsize
: ELF header 的大小 e_shentsize
: section header table 的 entry size e_shnum
: section header table 的 entry 數目 e_shstrndx
:section header string table index 那接下來,我們來交叉比對 readelf
跟 hexdump
的結果,來更加明白
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: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 688 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 11
Section header string table index: 10
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
我們由 readelf 知道,整個 header 大小是 64 bytes,所以只讓 hexdump 顯示前 64 bytes。
hexdump -n 64 -C elf.o
1
1
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 01 00 3e 00 01 00 00 00 00 00 00 00 00 00 00 00 |..>.............|
00000020 00 00 00 00 00 00 00 00 b0 02 00 00 00 00 00 00 |................|
00000030 00 00 00 00 40 00 00 00 00 00 40 00 0b 00 0a 00 |....@.....@.....|
1 2 3 4
1 2 3 4
在閱讀 hexdump 時,要注意是 little-endian 或是 big-endian,例如最後兩個 byte 是 Section header string table index,
hexdump 的結果是 0a 00
,因為是 little-endian 請把它轉換為 00 0a,得到 Section header string table index = 10。
介紹完 header 的資料結構後,承接 section header table 的三劍客是 e_shoff
, e_shentsize
, e_shnum
,我們 由 e_shoff 可以幫我們找到 section header table 的起始地址 。另外兩個只是告訴你該表格的欄位大小跟個數而已。
再來就是劇透時間😂
e_shoff
可以讓我們直接得到 section header table 的起始地址,另外我們可以想像 Section header table 是一個一維陣列,那一維陣列的容量怎麼算? 是不是大小 x 個數, 因此 e_shentsize
x e_shnum
就是我們整張 Section header table 的大小,又因為 Section header table 是放在ELF 檔案最後面的地方,因此我們可以得出
f i l e s i z e = e _ s h o f f + e _ s h e n t s i z e × e _ s h n u m filesize = e\_shoff + e\_shentsize \times e\_shnum
f i l es i ze = e _ s h o ff + e _ s h e n t s i ze × e _ s hn u m
就可以計算出整個ELF檔案的大小了,很簡單吧,ELF 其實沒有你想像中的難,接下來就正式進入 section header table 的介紹。
Section Header table 主要作用于鏈接過程,包含靜態鏈接與動態鏈接兩種鏈接方式,記錄了各 Section 的基本資訊,包含 Section 起始位址等。因此可以透過此 table 讀取各 Section,所以在 Section header table 內的每一個欄位元素,一一對應各個 Section,可以說 Section header table就是一個索引表來記錄各個section的索引。
那我們先來簡單看一下 Section header table 的資訊吧
There are 11 section headers, starting at offset 0x2b0:
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
000000000000000e 0000000000000000 AX 0 0 1
[ 2] .data PROGBITS 0000000000000000 00000050
0000000000000010 0000000000000000 WA 0 0 8
[ 3] .bss NOBITS 0000000000000000 00000060
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .comment PROGBITS 0000000000000000 00000060
000000000000002c 0000000000000001 MS 0 0 1
[ 5] .note.GNU-stack PROGBITS 0000000000000000 0000008c
0000000000000000 0000000000000000 0 0 1
[ 6] .eh_frame PROGBITS 0000000000000000 00000090
0000000000000058 0000000000000000 A 0 0 8
[ 7] .rela.eh_frame RELA 0000000000000000 00000228
0000000000000030 0000000000000018 I 8 6 8
[ 8] .symtab SYMTAB 0000000000000000 000000e8
0000000000000120 0000000000000018 9 8 8
[ 9] .strtab STRTAB 0000000000000000 00000208
000000000000001f 0000000000000000 0 0 1
[10] .shstrtab STRTAB 0000000000000000 00000258
0000000000000054 0000000000000000 0 0 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
備註,這裡我有故意隔開,讓讀者比較有感覺,我們由 ELF header 知道,起始地址是 0x2b0 (688)
hexdump -s 688 -C elf.o
1
1
[0]
000002b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
[1]
000002f0 1b 00 00 00 01 00 00 00 06 00 00 00 00 00 00 00 |................|
00000300 00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 |........@.......|
00000310 0e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000320 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
[2]
00000330 21 00 00 00 01 00 00 00 03 00 00 00 00 00 00 00 |!...............|
00000340 00 00 00 00 00 00 00 00 50 00 00 00 00 00 00 00 |........P.......|
00000350 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000360 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
[3]
00000370 27 00 00 00 08 00 00 00 03 00 00 00 00 00 00 00 |'...............|
00000380 00 00 00 00 00 00 00 00 60 00 00 00 00 00 00 00 |........`.......|
00000390 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000003a0 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
[4]
000003b0 2c 00 00 00 01 00 00 00 30 00 00 00 00 00 00 00 |,.......0.......|
000003c0 00 00 00 00 00 00 00 00 60 00 00 00 00 00 00 00 |........`.......|
000003d0 2c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |,...............|
000003e0 01 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................|
[5]
000003f0 35 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 |5...............|
00000400 00 00 00 00 00 00 00 00 8c 00 00 00 00 00 00 00 |................|
00000410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000420 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
[6]
00000430 4a 00 00 00 01 00 00 00 02 00 00 00 00 00 00 00 |J...............|
00000440 00 00 00 00 00 00 00 00 90 00 00 00 00 00 00 00 |................|
00000450 58 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |X...............|
00000460 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
[7]
00000470 45 00 00 00 04 00 00 00 40 00 00 00 00 00 00 00 |E.......@.......|
00000480 00 00 00 00 00 00 00 00 28 02 00 00 00 00 00 00 |........(,.......|
00000490 30 00 00 00 00 00 00 00 08 00 00 00 06 00 00 00 |0...............|
000004a0 08 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00 |................|
[8]
000004b0 01 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 |................|
000004c0 00 00 00 00 00 00 00 00 e8 00 00 00 00 00 00 00 |................|
000004d0 20 01 00 00 00 00 00 00 09 00 00 00 08 00 00 00 | ...............|
000004e0 08 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00 |................|
[9]
000004f0 09 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 |................|
00000500 00 00 00 00 00 00 00 00 08 02 00 00 00 00 00 00 |................|
00000510 1f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000520 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
[10]
00000530 11 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 |................|
00000540 00 00 00 00 00 00 00 00 58 02 00 00 00 00 00 00 |........X.......|
00000550 54 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |T...............|
00000560 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000570
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
我們可以看到,各個 seciton 對應的 index 是什麼,最開始第 0 項的 section 全部都是 0,佔了 40 bytes,section header table 的第 1 項是 .text
section,其他依此類推。所以我們的 section header table 總共有 11 項, 在先前 readelf -h elf.o
的結果中,Number of section headers 也跟我們說了總共 11 項。
這個 readelf 格式其實可讀性不是很好,因為早期的螢幕較小,所以要這樣換行呈現,所以要委屈一下你我的眼睛。
再來看原始資料結構的定義吧
typedef struct
{
Elf64_Word sh_name;
Elf64_Word sh_type;
Elf64_Word sh_flags;
Elf64_Addr sh_addr;
Elf64_Off sh_offset;
Elf64_Word sh_size;
Elf64_Word sh_link;
Elf64_Word sh_info;
Elf64_Word sh_addralign;
Elf64_Word sh_entsize;
} Elf64_Shdr;
1 2 3 4 5 6 7 8 9 10 11 12 13
1 2 3 4 5 6 7 8 9 10 11 12 13
直接拿 Section header table 第一個元素來比照,並且解釋於本篇較重要的成員。
[1]
000002f0 1b 00 00 00 01 00 00 00 06 00 00 00 00 00 00 00 |................|
00000300 00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 |........@.......|
00000310 0e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000320 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
1 2 3 4 5
1 2 3 4 5
Elf64_Word sh_name = 0000001 b
Elf64_Word sh_type = 00000001 == PROG_BITS
Elf64_Word sh_flags = 00000006 == 02 + 04 = SHF_ALLOC | SHF_EXECINSTR
Elf64_Addr sh_addr = 0000000000000000
Elf64_Off sh_offset = 0000000000000040 = 0x40 = 64
Elf64_Word sh_size = 0000000 e = 14
Elf64_Word sh_link = 00000000
Elf64_Word sh_info = 00000000
Elf64_Word sh_addralign = 00000000
Elf64_Word sh_entsize = 00000001
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
這裡比較多好玩的東西可以說 ~
sh_name
: 是該 Section 名字的偏移量,利用此偏移量在 shstrtab section 找到這個section 的名字 ,而我們所有 section 的名字,集中在 .shstrtab 這個 section 內,這裡先劇透一下,shstrtab 跟 strtab 都是所有字串 concat 再一起,換言之,就是一個超大字串,所以我們必須要用偏移量的方式去尋找我們的名字,這樣的設計我認為是出於 section header 資料結構比較好設計、統一。
首先我們同樣比對 readelf 跟 hexdump 的結果,來看這個 sh_name 跟 shstrtab section的關係 ,請依照 tab 順序觀看。
There are 11 section headers, starting at offset 0x2b0:
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
000000000000000e 0000000000000000 AX 0 0 1
[ 2] .data PROGBITS 0000000000000000 00000050
0000000000000010 0000000000000000 WA 0 0 8
[ 3] .bss NOBITS 0000000000000000 00000060
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .comment PROGBITS 0000000000000000 00000060
000000000000002c 0000000000000001 MS 0 0 1
[ 5] .note.GNU-stack PROGBITS 0000000000000000 0000008c
0000000000000000 0000000000000000 0 0 1
[ 6] .eh_frame PROGBITS 0000000000000000 00000090
0000000000000058 0000000000000000 A 0 0 8
[ 7] .rela.eh_frame RELA 0000000000000000 00000228
0000000000000030 0000000000000018 I 8 6 8
[ 8] .symtab SYMTAB 0000000000000000 000000e8
0000000000000120 0000000000000018 9 8 8
[ 9] .strtab STRTAB 0000000000000000 00000208
000000000000001f 0000000000000000 0 0 1
[10] .shstrtab STRTAB 0000000000000000 00000258
0000000000000054 0000000000000000 0 0 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
我們可以發現 shstrtab section 在第 10 個索引,起始位置是 0x258,大小是 0x54 = 84,再來我們以 hexdump 來看 shstrtab 的內容吧😀
由 readelf 的結果,我們知道 shstrtab section 的兩個重要資料
起始位置是 0x258 大小是 0x54 = 84
接下來我們以這兩個值用 hexdump 來看內容吧 hexdump -s 0x258 -n 84 -c elf.o
1
1
0000258 \0 . s y m t a b \0 . s t r t a b
0000268 \0 . s h s t r t a b \0 . t e x t
0000278 \0 . d a t a \0 . b s s \0 . c o m
0000288 m e n t \0 . n o t e . G N U - s
0000298 t a c k \0 . r e l a . e h _ f r
00002a8 a m e \0
00002ac
1 2 3 4 5 6 7
1 2 3 4 5 6 7
由沒有覺得這個粉熟悉 ? 沒錯這就是我們所有 section 的名字, hexdump 將所有的十六進制用 ASCII code 轉成原本的字元,可以發現如同方才所述這是 所有字串concat在一起的大字串,並以null-terminater為分界 ( 也是因為C的字串規定要\0結尾 ) ,因此,假設我們是 .text
section,如同剛剛的sh_name例子是 1b,那我們 就知道是 0x258 + 0x1b = 0x273,總共 5 個字元
hexdump -s 0x273 -n 5 -c elf.o
1
1
就得到我們的名字了,真的藏了很多巧思。另外,你也可以試試看用 hexedit
這個工具,去修改把 1b 改為 1e,.text 的節就會變成 xt,因為我們頑皮地把偏移量改掉了。你可以看看 readelf 的輸出資訊,可以印證我說的話。
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] xt PROGBITS 0000000000000000 00000040
000000000000000e 0000000000000000 AX 0 0 1
1 2 3 4 5 6
1 2 3 4 5 6
sh_flags
: 這裡利用到一個特性,任一正整數都可以唯一表示成一種二進制,因此將此數 拆解 成不同的二進位相加就可以知道設哪些 flag 。
其實這裡也有一點線性代數的味道, 如上面的 00000006(十六進位),是由 00000010 | 00000100 (二進位)合成
8個 bits 有 8 個彼此獨立的正交向量 (00000001, 00000010, 00000100, 00001000, 00010000, 00100000, 01000000, 100000000)
因此這裡的 6 就是 00000010 | 00000100 得到的線性組合。
sh_size
: 此 section 的大小
sh_offset
: 這個可以推得該 section 起始位置,如下圖
因此,該 section 的範圍是 [ sh_offset, sh_offset + sh_size ] (閉區間)
sh_entsize
: 如果這個 section 是表格,例如 symtab section ,這是個該section 每個 entry 的大小。
截至目前為止,我們大概將ELF linking view 各自的引用關係,都講完了。
2.3 ELF 引用關係總結 事實上,每個 section 的意義我要留到下一篇再講,因為要跟 symbol table 做連結、一次性的探討較完整部分散。那本篇 ELF linking view 引用流程總結如下。請看我畫的美圖🤣
由 ELF header,知道這個 ELF 檔的描述資訊,其中重要的三劍客 e_shoff
, e_shnum
, e_shentsize
由 ELF Header 得到的三劍客
e_shoff
讓我們得到 section header table 的起始位置 e_shnum
, e_shentsize
讓我們得到 section header table 每個元素大小跟個數,可以聯想到一維陣列的 大小 x 個數
又由於 section header table 在 ELF 檔案的最後,我們也可以推得檔案大小 f i l e s i z e = e _ s h o f f + e _ s h e n t s i z e × e _ s h n u m filesize = e\_shoff + e\_shentsize \times e\_shnum
f i l es i ze = e _ s h o ff + e _ s h e n t s i ze × e _ s hn u m
section header table 每一個元素或索引對應一個 section,從section header table 選定一個元素,我們可以得知另一對三劍客 sh_name
, sh_size
, sh_offset
得到 setion header table 某一元素後,我們可以求得
sh_offset
得到 section 的起始位址 sh_size
得到 section 的大小 sh_name
得到 section 名字的偏移量,我們要去 shstrtab section 利用此偏移量從這個大字串中找 section 的名字 這就整體的流程,現在對於 ELF 的相互引用,是不是有些概念了呢 ? 下一篇我們將講述 symtab section 跟 symbol 的相關知識。
三、參考連結 特別感謝 yaaangmin (opens new window) ,讓我受益良多的影片。 詳解ELF (opens new window) ProgrammerSought (opens new window) Introduction to the ELF Format : The ELF Header (Part I) (opens new window) 陳鍾誠老師的網站 (opens new window) ELF格式文件 (opens new window) gcc與Obj檔,動態連結與ELF檔 (opens new window)