ELF格式檔案符號表全解析及readelf命令使用方法
readelf 命令引數
以 hello.c 程式為例
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a=100;
printf("hello world!\n");
return 0;
}
1. 讀取ELF檔案頭:$ readelf -h hello.o
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)(.so檔案DYN(Shared object file)、.o檔案REL(Relocatable file)、Core dump檔案(CORE)) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x400510 Start of program headers: 64 (bytes into file) Start of section headers: 3072 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 8 Size of section headers: 64 (bytes) Number of section headers: 31 Section header string table index: 28 在 readelf 的輸出中: 第 1 行,ELF Header: 指名 ELF 檔案頭開始。 第 2 行,Magic 魔數,用來指名該檔案是一個 ELF 目標檔案。第一個位元組 7F 是個固定的數;後面的 3 個位元組正是 E, L, F 三個字母的 ASCII 形式。 第 3 行,CLASS 表示檔案型別,這裡是 64位的 ELF 格式。 第 4 行,Data 表示檔案中的資料是按照什麼格式組織(大端或小端)的,不同處理器平臺數據組織格式可能就不同,如x86平臺為小端儲存格式。 第 5 行,當前 ELF 檔案頭版本號,這裡版本號為 1 。 第 6 行,OS/ABI ,指出作業系統型別,ABI 是 Application Binary Interface 的縮寫。 第 7 行,ABI 版本號,當前為 0 。 第 8 行,Type 表示檔案型別。ELF 檔案有 3 種類型,一種是如上所示的 Relocatable file 可重定位目標檔案,一種是可執行檔案(Executable),另外一種是共享庫(Shared Library) 。 第 9 行,機器平臺型別。 第 10 行,當前目標檔案的版本號。 第 11 行,程式的虛擬地址入口點,因為這還不是可執行的程式,故而這裡為零。 第 12 行,與 11 行同理,這個目標檔案沒有 Program Headers。 第 13 行,sections 頭開始處,這裡 208 是十進位制,表示從地址偏移 0xD0 處開始。 第 14 行,是一個與處理器相關聯的標誌,x86 平臺上該處為 0 。 第 15 行,ELF 檔案頭的位元組數。 第 16 行,因為這個不是可執行程式,故此處大小為 0。 第 17 行,同理於第 16 行。 第 18 行,sections header 的大小,這裡每個 section 頭大小為 40 個位元組。 第 19 行,一共有多少個 section 頭,這裡是 8 個。 第 20 行,section 頭字串表索引號,從 Section Headers 輸出部分可以看到其內容的偏移在 0xa0 處,從此處開始到0xcf 結束儲存著各個 sections 的名字,如 .data,.text,.bss等。
2. 顯示程式頭表(目標檔案沒有該表):
上述各段組成了最終在記憶體中執行的程式,其還提供了各段在虛擬地址空間和實體地址空間中的大小、位置、標誌、訪問授權和對齊方面的資訊。各段語義如下:
PHDR儲存程式頭表
INTERP指定程式從可行性檔案對映到記憶體之後,必須呼叫的直譯器,它是通過連結其他庫來滿足未解析的引用,用於在虛擬地址空間中插入程式執行所需的動態庫。
LOAD表示一個需要從二進位制檔案對映到虛擬地址空間的段,其中儲存了常量資料(如字串),程式目的碼等。
DYNAMIC段儲存了由動態聯結器(即INTERP段中指定的直譯器)使用的資訊。
3. 讀取節頭表:
讀取 .o 檔案
- .text:已編譯程式的機器程式碼。
- .rodata:只讀資料,比如printf語句中的格式串和開關(switch)語句的跳轉表。
- .data:已初始化的全域性C變數。區域性C變數在執行時被儲存在棧中,既不出現在.data中,也不出現在.bss節中。
- .bss:未初始化的全域性C變數。在目標檔案中這個節不佔據實際的空間,它僅僅是一個佔位符。目標檔案格式區分初始化和未初始化變數是為了空間效率在:在目標檔案中,未初始化變數不需要佔據任何實際的磁碟空間。
- .symtab:一個符號表(symbol table),它存放在程式中被定義和引用的函式和全域性變數的資訊。一些程式設計師錯誤地認為必須通過-g選項來編譯一個程式,得到符號表資訊。實際上,每個可重定位目標檔案在.symtab中都有一張符號表。然而,和編譯器中的符號表不同,.symtab符號表不包含區域性變數的表目。
- .rel.text:當連結噐把這個目標檔案和其他檔案結合時,.text節中的許多位置都需要修改。一般而言,任何呼叫外部函式或者引用全域性變數的指令都需要修改。另一方面呼叫本地函式的指令則不需要修改。注意,可執行目標檔案中並不需要重定位資訊,因此通常省略,除非使用者顯式地指示連結器包含這些資訊。
- .rel.data:被模組定義或引用的任何全域性變數的資訊。一般而言,任何已初始化全域性變數的初始值是全域性變數或者外部定義函式的地址都需要被修改。
- .debug:一個除錯符號表,其有些表目是程式中定義的區域性變數和型別定義,有些表目是程式中定義和引用的全域性變數,有些是原始的C原始檔。只有以-g選項呼叫編譯驅動程式時,才會得到這張表。
- .line:原始C源程式中的行號和.text節中機器指令之間的對映。只有以-g選項呼叫編譯驅動程式時,才會得到這張表。
- .strtab:一個字串表,其內容包括.symtab和.debug節中的符號表,以及節頭部中的節名字。字串表就是以null結尾的字串序列。
讀取可執行檔案
PROGBITS(程式必須解釋的資訊,如二進位制程式碼),STRTAB用於儲存與ELF格式有關的字串,但與程式沒有直接關聯,如各個節的名稱(.text, .comment)
.data儲存初始化過的資料,這是普通程式資料的一部分,可以在程式執行期間修改。
.rodata儲存了只讀資料,可以讀取但不能修改,例如printf語句中的所有靜態字串封裝到該節。
.init和.fini儲存了程序初始化和結束所用的程式碼,這通常是由編譯器自動新增的。
.hash是一個散列表,允許在不對全表元素進行線性搜尋的情況下,快速訪問所有符號表項。
4. 符號表機制(readelf -s)
符號表儲存了程式實現或使用的所有全域性變數和函式,如果程式引用一個自身程式碼未定義的符號,則稱之為未定義符號,這類引用必須在靜態連結期間用其他目標模組或庫解決,或在載入時通過動態連結解決。
實現:
.symtab確定符號的名稱與其值之間的關聯,其中名稱不是直接以字串形式出現的,而是表示為某一字串陣列(.strtab)的索引。
.strtab儲存了字串陣列(.shstrtab包含了節名稱字串表)。
.hash儲存了一個散列表,以幫助快速查詢符號。
typedef struct elf64_sym {
Elf64_Word st_name; // 符號名稱,字串表中的索引
// STT_OBJECT表示符號關聯到一個數據物件,如變數、陣列或指標;
// STT_FUNC表示符號關聯到一個函式;
// STT_NOTYPE表示符號型別未指定,用於未定義引用
unsigned char st_info; // 型別和繫結屬性:STB_LOCAL/STB_GLOBAL/STB_WEAK;
unsigned char st_other; // 語義未定義,0
Elf64_Half st_shndx; // 相關節的索引,符號將繫結到該節,此外SHN_ABS指定符號是絕對值,不因重定位而改變,SHN_UNDEF標識未定義符號。
Elf64_Addr st_value; // 符號的值
Elf64_Xword st_size; // 符號的長度,如一個指標的長度或struct物件中包含的位元組數。
} Elf64_Sym;
例項:
readelf 用來顯示 ELF 格式檔案資訊,該命令選項很多,其中 -a 選項可以用來顯示 ELF 檔案的所有資訊。
下面對 -a 選項的輸出內容進行分析。原始碼如下:
進行gcc編譯,等操作:
例如我想解析出我在test.c檔案中寫的全域性變數的內容。可以檢視 符號表(Symbol table '.dynsym')部分
其中欄位中有OBJECT 和 GLOBAL 的即為全域性變數,我們在test.c中定義的全域性變數會出現在裡面,如上圖中 244和252行。
以及一些函式等內容都可以在這段區域內找到相應的地址和大小資訊等。。。