前文結(jié)尾說到編譯器編譯源代碼后生成的文件叫做目標(biāo)文件,而目標(biāo)文件經(jīng)過編譯器鏈接之后得到的就是可執(zhí)行文件。那么目標(biāo)文件到底是什么?它和可執(zhí)行文件又有什么區(qū)別?鏈接到底又做了什么呢?接下來,我們將探索一下目標(biāo)文件的本質(zhì)。
目標(biāo)文件的格式
目前,PC平臺(tái)流行的 可執(zhí)行文件格式(Executable) 主要包含如下兩種,它們都是 COFF(Common File Format) 格式的變種。
- Windows下的 PE(Portable Executable)
- Linux下的 ELF(Executable Linkable Format)
目標(biāo)文件就是源代碼經(jīng)過編譯后但未進(jìn)行連接的那些中間文件(Windows的.obj和Linux的.o),它與可執(zhí)行文件的格式非常相似,所以一般跟可執(zhí)行文件格式一起采用同一種格式存儲(chǔ)。在Windows下采用PE-COFF文件格式;Linux下采用ELF文件格式。
事實(shí)上,除了可執(zhí)行文件外,動(dòng)態(tài)鏈接庫(DDL,Dynamic Linking Library)、靜態(tài)鏈接庫(Static Linking Library) 均采用可執(zhí)行文件格式存儲(chǔ)。它們在Window下均按照PE-COFF格式存儲(chǔ);Linux下均按照ELF格式存儲(chǔ)。只是文件名后綴不同而已。
- 動(dòng)態(tài)鏈接庫:Windows的
.dll、Linux的.so - 靜態(tài)鏈接庫:Windows的
.lib、Linux的.a
下面,我們將以ELF文件為例進(jìn)行介紹。
ELF文件結(jié)構(gòu)

注意:段(Segment)與節(jié)(Section)的區(qū)別。很多地方對兩者有所混淆。段是程序執(zhí)行的必要組成,當(dāng)多個(gè)目標(biāo)文件鏈接成一個(gè)可執(zhí)行文件時(shí),會(huì)將相同權(quán)限的節(jié)合并到一個(gè)段中。相比而言,節(jié)的粒度更小。
如圖所示,為ELF文件的基本結(jié)構(gòu),其主要由四部分組成:
- ELF Header
- ELF Program Header Table (或稱Program Headers、程序頭)
- ELF Section Header Table (或稱Section Headers、節(jié)頭表)
- ELF Sections
從圖中,我們就能看出它們各自的數(shù)據(jù)結(jié)構(gòu)以及相互之間的索引關(guān)系。下面我們依次進(jìn)行介紹。
ELF Header
我們可以使用readelf工具來查看ELF Header。
$ 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: 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: 672 (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: 13
Section header string table index: 10
| 成員 | 含義 |
|---|---|
| e_ident | Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |
| Class: ELF32 | |
| Data: 2's complement, little end | |
| Version: 1(current) | |
| OS/ABI: UNIX - System V | |
| ABI Version: 0 | |
| e_type | Type: REL (Relocatable file) |
| ELF文件類型 | |
| e_machine | Machine: Advanced Micro Devices X86-64 |
| ELF文件的CPI平臺(tái)屬性 | |
| e_version | Version: 0x1 |
| ELF版本號(hào)。一般為常數(shù)1 | |
| e_entry | Entry point address: 0x0 |
| 入口地址,規(guī)定ELF程序的入口虛擬地址,操作系統(tǒng)在加載完該程序后從這個(gè)地址開始執(zhí)行進(jìn)程的指令??芍囟ㄎ恢噶钜话銢]有入口地址,則該值為0 | |
| e_phoff | Start of program headers: 0(bytes into file) |
| e_shoff | Start of section headers: 672 (bytes into file) |
| Section Header Table 在文件中的偏移 | |
| e_word | Flags: 0x0 |
| ELF標(biāo)志位,用來標(biāo)識(shí)一些ELF文件平臺(tái)相關(guān)的屬性。 | |
| e_ehsize | Size of this header: 64 (bytes) |
| ELF Header本身的大小 | |
| e_phentsize | Size of program headers: 0 (bytes) |
| e_phnum | Number of program headers: 0 |
| e_shentsize | Size of section headers: 64 (bytes) |
| 單個(gè)Section Header大小 | |
| e_shnum | Number of section headers: 13 |
| Section Header的數(shù)量 | |
| e_shstrndx | Section header string table index: 10 |
| Section Header字符串表在Section Header Table中的索引 |
ELF文件結(jié)構(gòu)示意圖中定義的Elf_Ehdr的各個(gè)成員的含義與readelf具有對應(yīng)關(guān)系。如下表所示:
| 成員 | 含義 |
|---|---|
| e_ident | Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |
| Class: ELF32 | |
| Data: 2's complement, little end | |
| Version: 1(current) | |
| OS/ABI: UNIX - System V | |
| ABI Version: 0 | |
| e_type | Type: REL (Relocatable file) |
| ELF文件類型 | |
| e_machine | Machine: Advanced Micro Devices X86-64 |
| ELF文件的CPI平臺(tái)屬性 | |
| e_version | Version: 0x1 |
| ELF版本號(hào)。一般為常數(shù)1 | |
| e_entry | Entry point address: 0x0 |
| 入口地址,規(guī)定ELF程序的入口虛擬地址,操作系統(tǒng)在加載完該程序后從這個(gè)地址開始執(zhí)行進(jìn)程的指令??芍囟ㄎ恢噶钜话銢]有入口地址,則該值為0 | |
| e_phoff | Start of program headers: 0(bytes into file) |
| e_shoff | Start of section headers: 672 (bytes into file) |
| Section Header Table 在文件中的偏移 | |
| e_word | Flags: 0x0 |
| ELF標(biāo)志位,用來標(biāo)識(shí)一些ELF文件平臺(tái)相關(guān)的屬性。 | |
| e_ehsize | Size of this header: 64 (bytes) |
| ELF Header本身的大小 | |
| e_phentsize | Size of program headers: 0 (bytes) |
| e_phnum | Number of program headers: 0 |
| e_shentsize | Size of section headers: 64 (bytes) |
| 單個(gè)Section Header大小 | |
| e_shnum | Number of section headers: 13 |
| Section Header的數(shù)量 | |
| e_shstrndx | Section header string table index: 10 |
| Section Header字符串表在Section Header Table中的索引 |
ELF魔數(shù)
每種可執(zhí)行文件的格式的開頭幾個(gè)字節(jié)都是很特殊的,特別是開頭4個(gè)字節(jié),通常被稱為魔數(shù)(Magic Number)。通過對魔數(shù)的判斷可以確定文件的格式和類型。如:ELF的可執(zhí)行文件格式的頭4個(gè)字節(jié)為0x7F、e、l、f;Java的可執(zhí)行文件格式的頭4個(gè)字節(jié)為c、a、f、e;如果被執(zhí)行的是Shell腳本或perl、python等解釋型語言的腳本,那么它的第一行往往是#!/bin/sh或#!/usr/bin/perl或#!/usr/bin/python,此時(shí)前兩個(gè)字節(jié)#和!就構(gòu)成了魔數(shù),系統(tǒng)一旦判斷到這兩個(gè)字節(jié),就對后面的字符串進(jìn)行解析,以確定具體的解釋程序路徑。
ELF文件類型
ELF文件主要有三種類型,可以通過ELF Header中的e_type成員進(jìn)行區(qū)分。
-
可重定位文件(Relocatable File):
ETL_REL。一般為.o文件??梢员绘溄映煽蓤?zhí)行文件或共享目標(biāo)文件。靜態(tài)鏈接庫屬于可重定位文件。 -
可執(zhí)行文件(Executable File):
ET_EXEC。可以直接執(zhí)行的程序。 -
共享目標(biāo)文件(Shared Object File):
ET_DYN。一般為.so文件。有兩種情況可以使用。- 鏈接器將其與其他可重定位文件、共享目標(biāo)文件鏈接成新的目標(biāo)文件;
- 動(dòng)態(tài)鏈接器將其與其他共享目標(biāo)文件、結(jié)合一個(gè)可執(zhí)行文件,創(chuàng)建進(jìn)程映像。

ELF Section Header Table
ELF 節(jié)頭表是一個(gè)節(jié)頭數(shù)組。每一個(gè)節(jié)頭都描述了其所對應(yīng)的節(jié)的信息,如節(jié)名、節(jié)大小、在文件中的偏移、讀寫權(quán)限等。編譯器、鏈接器、裝載器都是通過節(jié)頭表來定位和訪問各個(gè)節(jié)的屬性的。
我們可以使用readelf工具來查看節(jié)頭表。
$ readelf -S hello.o
There are 13 section headers, starting at offset 0x2a0:
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
0000000000000015 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 000001f0
0000000000000030 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 00000055
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 00000055
0000000000000000 0000000000000000 WA 0 0 1
[ 5] .rodata PROGBITS 0000000000000000 00000055
000000000000000d 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 00000062
0000000000000035 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 00000097
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 00000098
0000000000000038 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 00000220
0000000000000018 0000000000000018 I 11 8 8
[10] .shstrtab STRTAB 0000000000000000 00000238
0000000000000061 0000000000000000 0 0 1
[11] .symtab SYMTAB 0000000000000000 000000d0
0000000000000108 0000000000000018 12 9 8
[12] .strtab STRTAB 0000000000000000 000001d8
0000000000000013 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)
ELF文件結(jié)構(gòu)示意圖中定義的Elf_Shdr的各個(gè)成員的含義與readelf具有對應(yīng)關(guān)系。如下表所示:
| 成員 | 含義 |
|---|---|
| sh_name | 節(jié)名 |
節(jié)名是一個(gè)字符串,保存在一個(gè)名為.shstrtab的字符串表(可通過Section Header索引到)。sh_name的值實(shí)際上是其節(jié)名字符串在.shstrtab中的偏移值 |
|
| sh_type | 節(jié)類型 |
| sh_flags | 節(jié)標(biāo)志位 |
| sh_addr | 節(jié)地址:節(jié)的虛擬地址 |
| 如果該節(jié)可以被加載,則sh_addr為該節(jié)被加載后在進(jìn)程地址空間中的虛擬地址;否則sh_addr為0 | |
| sh_offset | 節(jié)偏移 |
| 如果該節(jié)存在于文件中,則表示該節(jié)在文件中的偏移;否則無意義,如sh_offset對于BSS 節(jié)來說是沒有意義的 | |
| sh_size | 節(jié)大小 |
| sh_link、sh_info | 節(jié)鏈接信息 |
| sh_addralign | 節(jié)地址對齊方式 |
| sh_entsize | 節(jié)項(xiàng)大小 |
| 有些節(jié)包含了一些固定大小的項(xiàng),如符號(hào)表,其包含的每個(gè)符號(hào)所在的大小都一樣的,對于這種節(jié),sh_entsize表示每個(gè)項(xiàng)的大小。如果為0,則表示該節(jié)不包含固定大小的項(xiàng)。 |
節(jié)類型(sh_type)
節(jié)名是一個(gè)字符串,只是在鏈接和編譯過程中有意義,但它并不能真正地表示節(jié)的類型。對于編譯器和鏈接器來說,主要決定節(jié)的屬性是節(jié)的類型(sh_type)和節(jié)的標(biāo)志位(sh_flags)。
節(jié)的類型相關(guān)常量以SHT_開頭,上述readelf -S命令執(zhí)行的結(jié)果省略了該前綴。常見的節(jié)類型如下表所示:
| 常量 | 值 | 含義 |
|---|---|---|
| SHT_NULL | 0 | 無效節(jié) |
| SHT_PROGBITS | 1 | 程序節(jié)。代碼節(jié)、數(shù)據(jù)節(jié)都是這種類型。 |
| SHT_SYMTAB | 2 | 符號(hào)表 |
| SHT_STRTAB | 3 | 字符串表 |
| SHT_RELA | 4 | 重定位表。該節(jié)包含了重定位信息。 |
| SHT_HASH | 5 | 符號(hào)表的哈希表 |
| SHT_DYNAMIC | 6 | 動(dòng)態(tài)鏈接信息 |
| SHT_NOTE | 7 | 提示性信息 |
| SHT_NOBITS | 8 | 表示該節(jié)在文件中沒有內(nèi)容。如.bss節(jié) |
| SHT_REL | 9 | 該節(jié)包含了重定位信息 |
| SHT_SHLIB | 10 | 保留 |
| SHT_DNYSYM | 11 | 動(dòng)態(tài)鏈接的符號(hào)表 |
節(jié)標(biāo)志位(sh_flag)
節(jié)標(biāo)志位表示該節(jié)在進(jìn)程虛擬地址空間中的屬性。如是否可寫、是否可執(zhí)行等。相關(guān)常量以SHF_開頭。常見的節(jié)標(biāo)志位如下表所示:
| 常量 | 值 | 含義 |
|---|---|---|
| SHF_WRITE | 1 | 表示該節(jié)在進(jìn)程空間中可寫 |
| SHF_ALLOC | 2 | 表示該節(jié)在進(jìn)程空間中需要分配空間。有些包含指示或控制信息的節(jié)不需要在進(jìn)程空間中分配空間,就不會(huì)有這個(gè)標(biāo)志。 |
| SHF_EXECINSTR | 4 | 表示該節(jié)在進(jìn)程空間中可以被執(zhí)行 |
節(jié)鏈接信息(sh_link、sh_info)
如果節(jié)的類型是與鏈接相關(guān)的(無論是動(dòng)態(tài)鏈接還是靜態(tài)鏈接),如重定位表、符號(hào)表、等,則sh_link、sh_info兩個(gè)成員所包含的意義如下所示。其他類型的節(jié),這兩個(gè)成員沒有意義。
| sh_type | sh_link | sh_info |
|---|---|---|
| SHT_DYNAMIC | 該節(jié)所使用的字符串表在節(jié)頭表中的下標(biāo) | 0 |
| SHT_HASH | 該節(jié)所使用的符號(hào)表在節(jié)頭表中的下標(biāo) | 0 |
| SHT_REL | 該節(jié)所使用的相應(yīng)符號(hào)表在節(jié)頭表中的下標(biāo) | 該重定位表所作用的節(jié)在節(jié)頭表中的下標(biāo) |
| SHT_RELA | 該節(jié)所使用的相應(yīng)符號(hào)表在節(jié)頭表中的下標(biāo) | 該重定位表所作用的節(jié)在節(jié)頭表中的下標(biāo) |
| SHT_SYMTAB | 操作系統(tǒng)相關(guān) | 操作系統(tǒng)相關(guān) |
| SHT_DYNSYM | 操作系統(tǒng)相關(guān) | 操作系統(tǒng)相關(guān) |
| other | SHN_UNDEF | 0 |
ELF Sections
節(jié)的分類
上述ELF Section Header Table部分已經(jīng)簡單介紹了節(jié)類型。接下來我們來介紹詳細(xì)一些比較重要的節(jié)。
.text節(jié)
.text節(jié)是保存了程序代碼指令的代碼節(jié)。一段可執(zhí)行程序,如果存在Phdr,則.text節(jié)就會(huì)存在于text段中。由于.text節(jié)保存了程序代碼,所以節(jié)類型為SHT_PROGBITS。
.rodata節(jié)
rodata節(jié)保存了只讀的數(shù)據(jù),如一行C語言代碼中的字符串。由于.rodata節(jié)是只讀的,所以只能存在于一個(gè)可執(zhí)行文件的只讀段中。因此,只能在text段(不是data段)中找到.rodata節(jié)。由于.rodata節(jié)是只讀的,所以節(jié)類型為SHT_PROGBITS。
.plt節(jié)(過程鏈接表)
.plt節(jié)也稱為過程鏈接表(Procedure Linkage Table),其包含了動(dòng)態(tài)鏈接器調(diào)用從共享庫導(dǎo)入的函數(shù)所必需的相關(guān)代碼。由于.plt節(jié)保存了代碼,所以節(jié)類型為SHT_PROGBITS。
.data節(jié)
.data節(jié)存在于data段中,其保存了初始化的全局變量等數(shù)據(jù)。由于.data節(jié)保存了程序的變量數(shù)據(jù),所以節(jié)類型為SHT_PROGBITS。
.bss節(jié)
.bss節(jié)存在于data段中,占用空間不超過4字節(jié),僅表示這個(gè)節(jié)本省的空間。.bss節(jié)保存了未進(jìn)行初始化的全局?jǐn)?shù)據(jù)。程序加載時(shí)數(shù)據(jù)被初始化為0,在程序執(zhí)行期間可以進(jìn)行賦值。由于.bss節(jié)未保存實(shí)際的數(shù)據(jù),所以節(jié)類型為SHT_NOBITS。
.got.plt節(jié)(全局偏移表-過程鏈接表)
.got節(jié)保存了全局偏移表。.got節(jié)和.plt節(jié)一起提供了對導(dǎo)入的共享庫函數(shù)的訪問入口,由動(dòng)態(tài)鏈接器在運(yùn)行時(shí)進(jìn)行修改。由于.got.plt節(jié)與程序執(zhí)行有關(guān),所以節(jié)類型為SHT_PROGBITS。
.dynsym節(jié)(動(dòng)態(tài)鏈接符號(hào)表)
.dynsym節(jié)保存在text段中。其保存了從共享庫導(dǎo)入的動(dòng)態(tài)符號(hào)表。節(jié)類型為SHT_DYNSYM。
.dynstr節(jié)(動(dòng)態(tài)鏈接字符串表)
.dynstr保存了動(dòng)態(tài)鏈接字符串表,表中存放了一系列字符串,這些字符串代表了符號(hào)名稱,以空字符作為終止符。
.rel.*節(jié)(重定位表)
重定位表保存了重定位相關(guān)的信息,這些信息描述了如何在鏈接或運(yùn)行時(shí),對ELF目標(biāo)文件的某部分或者進(jìn)程鏡像進(jìn)行補(bǔ)充或修改。由于重定位表保存了重定位相關(guān)的數(shù)據(jù),所以節(jié)類型為SHT_REL。
.hash節(jié)
.hash節(jié)也稱為.gnu.hash,其保存了一個(gè)用于查找符號(hào)的散列表。
.symtab節(jié)(符號(hào)表)
.symtab節(jié)是一個(gè)ElfN_Sym的數(shù)組,保存了符號(hào)信息。節(jié)類型為SHT_SYMTAB。
.strtab節(jié)(字符串表)
.strtab節(jié)保存的是符號(hào)字符串表,表中的內(nèi)容會(huì)被.symtab的ElfN_Sym結(jié)構(gòu)中的st_name引用。節(jié)類型為SHT_STRTAB。
.ctors節(jié)和.dtors節(jié)
.ctors(構(gòu)造器)節(jié)和.dtors(析構(gòu)器)節(jié)分別保存了指向構(gòu)造函數(shù)和析構(gòu)函數(shù)的函數(shù)指針,構(gòu)造函數(shù)是在main函數(shù)執(zhí)行之前需要執(zhí)行的代碼;析構(gòu)函數(shù)是在main函數(shù)之后需要執(zhí)行的代碼。
符號(hào)表
節(jié)的分類中我們介紹了.dynsym節(jié)和.symtab節(jié),兩者都是符號(hào)表。那么它們到底有什么區(qū)別呢?存在什么關(guān)系呢?
符號(hào)是對某些類型的數(shù)據(jù)或代碼(如全局變量或函數(shù))的符號(hào)引用,函數(shù)名或變量名就是符號(hào)名。例如,printf()函數(shù)會(huì)在動(dòng)態(tài)鏈接符號(hào)表.dynsym中存有一個(gè)指向該函數(shù)的符號(hào)項(xiàng)(以Elf_Sym數(shù)據(jù)結(jié)構(gòu)表示)。在大多數(shù)共享庫和動(dòng)態(tài)鏈接可執(zhí)行文件中,存在兩個(gè)符號(hào)表。即.dynsym和.symtab。
.dynsym保存了引用來自外部文件符號(hào)的全局符號(hào)。如printf庫函數(shù)。.dynsym保存的符號(hào)是.symtab所保存符合的子集,.symtab中還保存了可執(zhí)行文件的本地符號(hào)。如全局變量,代碼中定義的本地函數(shù)等。
既然.dynsym是.symtab的子集,那為何要同時(shí)存在兩個(gè)符號(hào)表呢?
通過readelf -S命令可以查看可執(zhí)行文件的輸出,一部分節(jié)標(biāo)志位(sh_flags)被標(biāo)記為了A(ALLOC)、WA(WRITE/ALLOC)、AX(ALLOC/EXEC)。其中,.dynsym被標(biāo)記為ALLOC,而.symtab則沒有標(biāo)記。
ALLOC表示有該標(biāo)記的節(jié)會(huì)在運(yùn)行時(shí)分配并裝載進(jìn)入內(nèi)存,而.symtab不是在運(yùn)行時(shí)必需的,因此不會(huì)被裝載到內(nèi)存中。.dynsym保存的符號(hào)只能在運(yùn)行時(shí)被解析,因此是運(yùn)行時(shí)動(dòng)態(tài)鏈接器所需的唯一符號(hào)。.dynsym對于動(dòng)態(tài)鏈接可執(zhí)行文件的執(zhí)行是必需的,而.symtab只是用來進(jìn)行調(diào)試和鏈接的。

上圖所示為通過符號(hào)表索引字符串表的示意圖。符號(hào)表中的每一項(xiàng)都是一個(gè)Elf_Sym結(jié)構(gòu),對應(yīng)可以在字符串表中索引得到一個(gè)字符串。該數(shù)據(jù)結(jié)構(gòu)中成員的含義如下表所示:
| 成員 | 含義 |
|---|---|
| st_name | 符號(hào)名。該值為該符號(hào)名在字符串表中的偏移地址。 |
| st_value | 符號(hào)對應(yīng)的值。存放符號(hào)的值(可能是地址或位置偏移量)。 |
| st_size | 符號(hào)的大小。 |
| st_other | 0 |
| st_shndx | 符號(hào)所在的節(jié) |
| st_info | 符號(hào)類型及綁定屬性 |
使用readelf工具我們也能夠看到符號(hào)表的相關(guān)信息。
$ readelf -s hello.o
Symbol table '.symtab' contains 11 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 7
7: 0000000000000000 0 SECTION LOCAL DEFAULT 8
8: 0000000000000000 0 SECTION LOCAL DEFAULT 6
9: 0000000000000000 21 FUNC GLOBAL DEFAULT 1 main
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts
字符串表
類似于符號(hào)表,在大多數(shù)共享庫和動(dòng)態(tài)鏈接可執(zhí)行文件中,也存在兩個(gè)字符串表。即.dynstr和.strtab,分別對應(yīng)于.dynsym和symtab。此外,還有一個(gè).shstrtab的節(jié)頭字符串表,用于保存節(jié)頭表中用到的字符串,可通過sh_name進(jìn)行索引。
ELF文件中所有字符表的結(jié)構(gòu)基本一致,如上圖所示。
重定位表
重定位就是將符號(hào)定義和符號(hào)引用進(jìn)行連接的過程??芍囟ㄎ晃募枰枋鋈绾涡薷墓?jié)內(nèi)容的相關(guān)信息,從而使可執(zhí)行文件和共享目標(biāo)文件能夠保存進(jìn)程的程序鏡像所需要的正確信息。
重定位表是進(jìn)行重定位的重要依據(jù)。我們可以使用objdump工具查看目標(biāo)文件的重定位表:
$ objdump -r hello.o
hello.o: file format elf64-x86-64
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
0000000000000005 R_X86_64_32 .rodata
000000000000000a R_X86_64_PC32 puts-0x0000000000000004
RELOCATION RECORDS FOR [.eh_frame]:
OFFSET TYPE VALUE
0000000000000020 R_X86_64_PC32 .text
重定位表是一個(gè)Elf_Rel類型的數(shù)組結(jié)構(gòu),每一項(xiàng)對應(yīng)一個(gè)需要進(jìn)行重定位的項(xiàng)。
其成員含義如下表所示:
| 成員 | 含義 |
|---|---|
| r_offset | 重定位入口的偏移。 |
| 對于可重定位文件來說,這個(gè)值是該重定位入口所要修正的位置的第一個(gè)字節(jié)相對于節(jié)起始的偏移 | |
| 對于可執(zhí)行文件或共享對象文件來說,這個(gè)值是該重定位入口所要修正的位置的第一個(gè)字節(jié)的虛擬地址 | |
| r_info | 重定位入口的類型和符號(hào) |
| 因?yàn)椴煌幚砥鞯闹噶钕到y(tǒng)不一樣,所以重定位所要修正的指令地址格式也不一樣。每種處理器都有自己的一套重定位入口的類型。 | |
| 對于可執(zhí)行文件和共享目標(biāo)文件來說,它們的重定位入口是動(dòng)態(tài)鏈接類型的。 |
重定位是目標(biāo)文件鏈接成為可執(zhí)行文件的關(guān)鍵。我們將在后面的進(jìn)行介紹。
參考
- Executable and Linkable Format (ELF)
- 《Linux 二進(jìn)制分析》
- 《程序員的自我修養(yǎng)——鏈接、裝載與庫》
- Executable and Linkable Format