$ readelf -h sign
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等。
在 Section Headers 這裏,可以看到 .bss 和 .shstrtab 的偏移都爲 0xa0 。這是因爲,沒有被初始化的全局變量,會在加載階段被用
0 來初始化,這時候它和 .data 段一樣可讀可寫。但在編譯階段,.data 段會被分配一部分空間已存放數據(這裏從偏移 0x6c 開始),而 .bss 則沒有,.bss 僅有的是 section headers 。
鏈接器從 .rel.text 就可以知道哪些地方需要進行重定位(relocate) 。
.symtab 是符號表。
Ndx 是符號表所在的 section 的 section header 編號。如 .data 段的 section header 編號是 3,而string1,string2,lenght 都是在 .data 段的。
2. 顯示程序頭表(目標文件沒有該表):
$ readelf -l sign
Elf file type is EXEC (Executable file)
Entry point 0x400510
There are 8 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040 0x00000000000001c0 0x00000000000001c0 R E 8
INTERP 0x0000000000000200 0x0000000000400200 0x0000000000400200 0x000000000000001c 0x000000000000001c R 1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x00000000000008a4 0x00000000000008a4 R E 200000
LOAD 0x00000000000008a8 0x00000000006008a8 0x00000000006008a8 0x0000000000000220 0x0000000000000230 RW 200000
DYNAMIC 0x00000000000008d0 0x00000000006008d0 0x00000000006008d0 0x00000000000001a0 0x00000000000001a0 RW 8
NOTE 0x000000000000021c 0x000000000040021c 0x000000000040021c 0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x00000000000007d8 0x00000000004007d8 0x00000000004007d8 0x000000000000002c 0x000000000000002c R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 8
Section to Segment mapping: (段到節的映射)
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .hash .gnu.hash .dynsym .dynstr
.gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
上述各段組成了最終在內存中執行的程序,其還提供了各段在虛擬地址空間和物理地址空間中的大小、位置、標誌、訪問授權和對齊方面的信息。各段語義如下:
PHDR保存程序頭表
INTERP指定程序從可行性文件映射到內存之後,必須調用的解釋器,它是通過鏈接其他庫來滿足未解析的引用,用於在虛擬地址空間中插入程序運行所需的動態庫。
LOAD表示一個需要從二進制文件映射到虛擬地址空間的段,其中保存了常量數據(如字符串),程序目標代碼等。
DYNAMIC段保存了由動態連接器(即INTERP段中指定的解釋器)使用的信息。
3. 讀取節頭表:
$ readelf -S sign.o
There are 13 section headers, starting at offset 0x210:(這裏指定的offset是相對於二進制文件)
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 00000000000000e5 0000000000000000 AX 0 0 4
[ 2] .rela.text RELA 0000000000000000 000006f8 00000000000000a8 0000000000000018 11 1 8
[ 3] .data PROGBITS 0000000000000000 00000128 0000000000000000 0000000000000000 WA 0 0 4
[ 4] .bss NOBITS 0000000000000000 00000128 0000000000000000 0000000000000000 WA 0 0 4
[ 5] .rodata PROGBITS 0000000000000000 00000128 000000000000000b 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 00000133 000000000000001d 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 00000150 0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 00000150 0000000000000058 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 000007a0 0000000000000030 0000000000000018 11 8 8
[10] .shstrtab STRTAB 0000000000000000 000001a8 0000000000000061 0000000000000000 0 0 1
[11] .symtab SYMTAB 0000000000000000 00000550 0000000000000168 0000000000000018 12 9 8
[12] .strtab STRTAB 0000000000000000 000006b8 0000000000000039 0000000000000000 0 0 1
- .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結尾的字符串序列。
$ readelf -S sign
There are 31 section headers, starting at offset 0xc00
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 0000000000400200 00000200 000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 000000000040021c 0000021c 0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-id NOTE 000000000040023c 0000023c 0000000000000024 0000000000000000 A 0 0 4
[ 4] .hash HASH 0000000000400260 00000260 0000000000000030 0000000000000004 A 6 0 8
[ 5] .gnu.hash GNU_HASH 0000000000400290 00000290 000000000000001c 0000000000000000 A 6 0 8
[ 6] .dynsym DYNSYM 00000000004002b0 000002b0 00000000000000a8 0000000000000018 A 7 1 8
[ 7] .dynstr STRTAB 0000000000400358 00000358 0000000000000065 0000000000000000 A 0 0 1
[ 8] .gnu.version VERSYM 00000000004003be 000003be 000000000000000e 0000000000000002 A 6 0 2
[ 9] .gnu.version_r VERNEED 00000000004003d0 000003d0 0000000000000030 0000000000000000 A 7 1 8
[10] .rela.dyn RELA 0000000000400400 00000400 0000000000000018 0000000000000018 A 6 0 8
[11] .rela.plt RELA 0000000000400418 00000418 0000000000000078 0000000000000018 A 6 13 8
[12] .init PROGBITS 0000000000400490 00000490 0000000000000018 0000000000000000 AX 0 0 4
[13] .plt PROGBITS 00000000004004a8 000004a8 0000000000000060 0000000000000010 AX 0 0 4
[14] .text PROGBITS 0000000000400510 00000510 00000000000002a8 0000000000000000 AX 0 0 16
[15] .fini PROGBITS 00000000004007b8 000007b8 000000000000000e 0000000000000000 AX 0 0 4
[16] .rodata PROGBITS 00000000004007c8 000007c8 000000000000000f 0000000000000000 A 0 0 4
[17] .eh_frame_hdr PROGBITS 00000000004007d8 000007d8 000000000000002c 0000000000000000 A 0 0 4
[18] .eh_frame PROGBITS 0000000000400808 00000808 000000000000009c 0000000000000000 A 0 0 8
[19] .ctors PROGBITS 00000000006008a8 000008a8 0000000000000010 0000000000000000 WA 0 0 8
[20] .dtors PROGBITS 00000000006008b8 000008b8 0000000000000010 0000000000000000 WA 0 0 8
[21] .jcr PROGBITS 00000000006008c8 000008c8 0000000000000008 0000000000000000 WA 0 0 8
[22] .dynamic DYNAMIC 00000000006008d0 000008d0 00000000000001a0 0000000000000010 WA 7 0 8
[23] .got PROGBITS 0000000000600a70 00000a70 0000000000000008 0000000000000008 WA 0 0 8
[24] .got.plt PROGBITS 0000000000600a78 00000a78 0000000000000040 0000000000000008 WA 0 0 8
[25] .data PROGBITS 0000000000600ab8 00000ab8 0000000000000010 0000000000000000 WA 0 0 8
[26] .bss NOBITS 0000000000600ac8 00000ac8 0000000000000010 0000000000000000 WA 0 0 8
[27] .comment PROGBITS 0000000000000000 00000ac8 0000000000000038 0000000000000001 MS 0 0 1
[28] .shstrtab STRTAB 0000000000000000 00000b00 00000000000000fe 0000000000000000 0 0 1
[29] .symtab SYMTAB 0000000000000000 000013c0 0000000000000678 0000000000000018 30 47 8
[30] .strtab STRTAB 0000000000000000 00001a38 000000000000023c 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)
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編譯,等操作:
a.info即爲a.out 的ELF文件。
打開可見上面介紹的各個部分的內容。
以上就是-a選項所有符號表的內容。。。
下面是一個應用,例如我想解析出我在a.c文件中寫的全局變量的內容。
我們可以看162-233行的內容,其中字段中有OBJECT 和 GLOBAL 的即爲全局變量,我們在a.c中定義的全局變量會出現在裏面,221和225行。
以及一些函數等內容都可以在這段區域內找到相應的地址和大小信息等。。。
具體的參數及用法如下圖: