圖文并茂 | 例說ELF文件
掃描二維碼
隨時隨地手機看文章
ELF文件(Executable Linkable Format)是一種文件存儲格式。Linux下的目標(biāo)文件和可執(zhí)行文件都按照該格式進行存儲,有必要做個總結(jié)。
- 1. 鏈接舉例
- 2. ELF文件類型
- 2.1 可重定位目標(biāo)文件(.o文件)
- 2.2 可執(zhí)行目標(biāo)文件(a.out文件)
- 2.3 共享對象文件(.so文件)
- 3. ELF文件作用
- 4. ELF文件格式
- 4.1 從編譯和鏈接角度看ELF文件(可重定位目標(biāo)文件)
- 4.2 從程序執(zhí)行角度看ELF文件(可執(zhí)行文件)
- 5.總結(jié)
1. 鏈接舉例
??在介紹ELF文件之前,我們先看下,一個.c程序是如何變成可執(zhí)行目標(biāo)文件的。下面舉個例子。
??該程序由main.c和sum.c兩個模塊組成。sum.c接收數(shù)組和數(shù)組長度兩個參數(shù),最后將數(shù)組求和的結(jié)果返回。main.c調(diào)用sum函數(shù),并傳遞一個兩元素的int數(shù)組array,將計算結(jié)果保存在val中。
//main.c
int sum(int *a, int n);
int array[2] = {1, 2};
int main(int argc, char** argv)
{
int val = sum(array, 2);
return val;
}//sum.c
??讓我們來看看如果我們使用GCC編譯兩個模塊會發(fā)生什么?
int sum(int *a, int n)
{
int i, s = 0;
for (i = 0; i < n; i ) {
s = a[i];
}
return s;
}
??main.c和sum.c將分別通過翻譯器將源文件處理為可重定位的目標(biāo)文件main.o和sum.o。翻譯器處理的過程包括了預(yù)處理(ccp)、編譯(ccl)、匯編(as) 三個過程。最后,鏈接器(ld) 將可重定位的目標(biāo)文件main.o和sum.o以及一些必要的系統(tǒng)文件組合起來,創(chuàng)建一個可執(zhí)行目標(biāo)文件prog。具體過程如下圖所示。
鏈接過程??由上面的過程,我們可以看出在經(jīng)過匯編器后會輸出一個.o文件,這個叫做可重定位的目標(biāo)文件。將main.o和sum.o輸入鏈接器后,鏈接器輸出的prog文件叫做可執(zhí)行目標(biāo)文件。那這兩個目標(biāo)文件有什么樣的區(qū)別呢?
2. ELF文件類型
2.1 可重定位目標(biāo)文件(.o文件)
??包含二進制代碼和數(shù)據(jù),其形式可以和其他目標(biāo)文件進行合并,創(chuàng)建一個可執(zhí)行目標(biāo)文件。例如lib*.o文件。
2.2 可執(zhí)行目標(biāo)文件(a.out文件)
??包含二進制代碼和數(shù)據(jù),可直接被加載器加載執(zhí)行。例如編譯好的可執(zhí)行文件a.out。
2.3 共享對象文件(.so文件)
??用于和其他共享目標(biāo)文件或者可重定位文件一起生成ELF目標(biāo)文件或者和執(zhí)行文件一起創(chuàng)建進程映像,例如lib*.so文件。
3. ELF文件作用
??ELF文件參與程序的連接(建立一個程序)和程序的執(zhí)行(運行一個程序),所以可以從不同的角度來看待ELF格式的文件:
??1.如果用于編譯和鏈接(可重定位文件),則編譯器和鏈接器將把ELF文件看作是節(jié)頭表描述的節(jié)的集合,程序頭表可選。
??2.如果用于加載執(zhí)行(可執(zhí)行文件),則加載器則將把ELF文件看作是程序頭表描述的段的集合,一個段可能包含多個節(jié),節(jié)頭表可選。
4. ELF文件格式
4.1 從編譯和鏈接角度看ELF文件(可重定位目標(biāo)文件)
從編譯和鏈接角度看ELF文件ELF頭
??每個ELF文件都必須存在一個ELF_Header,這里存放了很多重要的信息用來描述整個文件的組織,如: 版本信息,入口信息,偏移信息等。程序執(zhí)行也必須依靠其提供的信息。
段頭表
??段頭表。存放的是所有不同段將在內(nèi)存中的位置。
.text section
??代碼段。存放已編譯程序的機器代碼,一般是只讀的。
.rodata section
??只讀數(shù)據(jù)段。此段的數(shù)據(jù)不可修改,存放常量。比如,printf中的格式化語句。
.data section
??數(shù)據(jù)段。存放已初始化的全局變量、常量。
.bss section
??bss段。未初始化全局變量,僅是占位符,不占據(jù)任何實際磁盤空間。目標(biāo)文件格式區(qū)分初始化和非初始化是為了空間效率。
從編譯和鏈接角度看ELF文件.symtab section
??符號表,它存放在程序中定義和引用的函數(shù)和全局變量的信息。
.rel.txt section
??.text節(jié)的重定位信息,用于重新修改代碼段的指令中的地址信息。
.rel.data section
??.data節(jié)的重定位信息,用于對被模塊使用或定義的全局變量進行重定位的信息。
.debug section
??調(diào)試用的符號表。
.strtab section
??包含 symtab和 debug節(jié)中符號及節(jié)名。
節(jié)頭部表
??每個節(jié)的節(jié)名、偏移和大小。
??以下是32位系統(tǒng)對應(yīng)的節(jié)頭表數(shù)據(jù)結(jié)構(gòu),說明了每個節(jié)的節(jié)名、在文件中的偏移、大小、訪問屬性、對齊方式等。
typedef struct {
??使用readelf命令命令查看節(jié)頭表內(nèi)容
Elf32_Word sh_name; //節(jié)名字符串在.strtab節(jié)(字符串表)中的偏移
Elf32_Word sh_type; //節(jié)類型:無效/代碼或數(shù)據(jù)/符號/字符串/...
Elf32_Word sh_flags; //節(jié)標(biāo)志:該節(jié)在虛擬空間中的訪問屬性
Elf32_Addr sh_addr; //虛擬地址:若可被加載,則對應(yīng)虛擬地址
Elf32_Off sh_offset; //在文件中的偏移地址,對.bss節(jié)而言則無意義
Elf32_Word sh_size; //節(jié)在文件中所占的長度
Elf32_Word sh_link; //sh_link和sh_info用于與鏈接相關(guān)的節(jié)(如 .rel.text節(jié)、.rel.data節(jié)、.symtab節(jié)等)
Elf32_Word sh_info;
Elf32_Word sh_addralign; //節(jié)的對齊要求
Elf32_Word sh_entsize; //節(jié)中每個表項的長度,0表示無固定長度表項
} Elf32_Shdr;
[ubuntu@localhost interpositioning]$ readelf -S main.o
??可重定位目標(biāo)文件中,每個可裝入節(jié)的起始地址總是0。
There are 13 section headers, starting at offset 0x3f8:
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
0000000000000071 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 000002d0
0000000000000090 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 000000b1
0000000000000049 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 000000b1
000000000000000c 0000000000000000 WA 0 0 1
[ 5] .rodata PROGBITS 0000000000000000 000000b1
0000000000000019 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 000000ca
0000000000000035 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 000000ff
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 00000100
0000000000000058 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 00000360
0000000000000030 0000000000000018 I 11 8 8
[10] .shstrtab STRTAB 0000000000000000 00000390
0000000000000061 0000000000000000 0 0 1
[11] .symtab SYMTAB 0000000000000000 00000158
0000000000000150 0000000000000018 12 9 8
[12] .strtab STRTAB 0000000000000000 000002a8
0000000000000023 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)
??.bss節(jié)應(yīng)占0x0c大小,但只有裝入內(nèi)存時才會分配。
4.2 從程序執(zhí)行角度看ELF文件(可執(zhí)行文件)
從程序執(zhí)行角度看ELF文件??與可重定位目標(biāo)文件不同:
??1.ELF頭中,字段 e_entry給出執(zhí)行程序時第一條指令的地址,而在可重定位文件中,此字段為0。
??2.多一個init節(jié),用于定義init函數(shù),該函數(shù)用來進行可執(zhí)行目標(biāo)文件開始執(zhí)行時的初始化工作。
??3.少兩個.rel節(jié)(無需重定位)。
??4.多一個程序頭表,也稱段頭表,是一個結(jié)構(gòu)數(shù)組。
??使用readelf命令查看ELF頭的內(nèi)容:
[ubuntu@localhost interpositioning]$readelf -h main.o
??裝入內(nèi)存時,ELF頭、程序頭表、.init節(jié)、.rodata節(jié)會被裝入只讀代碼段。.data節(jié)和.bss節(jié)會被裝入讀寫數(shù)據(jù)段。
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: 1064 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 32 (bytes) //程序頭表每項32B
Number of program headers: 8 //程序頭表共8項
Size of section headers: 64 (bytes)
Number of section headers: 13
Section header string table index: 10 //.strtab在節(jié)頭表中的索引
??段頭表能夠描述可執(zhí)行文件中的節(jié)與虛擬空間中的存儲段之間的映射關(guān)系。一個表項32B,說明虛擬地址空間中一個連續(xù)的片段或一個特殊的節(jié)。以下是32位系統(tǒng)對應(yīng)的段頭表數(shù)據(jù)結(jié)構(gòu):
typedef struct {
??使用readelf命令查看某可執(zhí)行目標(biāo)文件的程序頭表。
Elf32_Word p_type; //此數(shù)組元素描述的段的類型,或者如何解釋此數(shù)組元素的信息。
Elf32_Off p_offset; //此成員給出從文件頭到該段第一個字節(jié)的偏移
Elf32_Addr p_vaddr; //此成員給出段的第一個字節(jié)將被放到內(nèi)存中的虛擬地址
Elf32_Addr p_paddr; //此成員僅用于與物理地址相關(guān)的系統(tǒng)中。System V忽略所有應(yīng)用程序的物理地址信息。
Elf32_Word p_filesz; //此成員給出段在文件映像中所占的字節(jié)數(shù)??梢詾?。
Elf32_Word p_memsz; //此成員給出段在內(nèi)存映像中占用的字節(jié)數(shù)??梢詾?。
Elf32_Word p_flags; //此成員給出與段相關(guān)的標(biāo)志。
Elf32_Word p_align; //此成員給出段在文件中和內(nèi)存中如何對齊。
} Elf32_phdr;
[ubuntu@localhost interpositioning]$readelf -l main
??程序頭表信息有9個表項,其中兩個為可裝入段(即Type=LOAD):
Elf file type is EXEC (Executable file)
Entry point 0x400550
There are 9 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001f8 0x00000000000001f8 R E 8
INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238
0x000000000000001c 0x000000000000001c R 1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000008ac 0x00000000000008ac R E 200000
LOAD 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
0x0000000000000240 0x0000000000000248 RW 200000
DYNAMIC 0x0000000000000e28 0x0000000000600e28 0x0000000000600e28
0x00000000000001d0 0x00000000000001d0 RW 8
NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x0000000000000780 0x0000000000400780 0x0000000000400780
0x0000000000000034 0x0000000000000034 R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 10
GNU_RELRO 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
0x00000000000001f0 0x00000000000001f0 R 1
??第一可裝入段:第0x00000~0x0x8ab的長度為0x8ac字節(jié)的ELF頭、程序頭表、.init、.text和.rodata節(jié),映射到虛擬地址0x400000開始長度為0x8ac字節(jié)的區(qū)域 ,按0x200000=2MB對齊,具有只讀/執(zhí)行權(quán)限(Flg=RE),是只讀代碼段。
??第二可裝入段:第0xe10~0x104f的長度為0x240字節(jié)的.data節(jié)和磁盤中不占存儲空間的.bss節(jié),映射到虛擬地址0x600e10開始長度為0x248字節(jié)的存儲區(qū)域,在0x248=584B存儲區(qū)中,前0x240=576B用.data節(jié)內(nèi)容初始化,后面584-576=8B對應(yīng).bss節(jié),初始化為0 ,按0x200000=2MB對齊,具有可讀可寫權(quán)限(Flg=RW),是可讀寫數(shù)據(jù)段。
??由此看出.bss節(jié)在文件中不占用磁盤空間,但在存儲器中需要給它分配相應(yīng)大小的空間。
5.總結(jié)
??1.鏈接處理涉及到三種目標(biāo)文件格式:可重定位目標(biāo)文件、可執(zhí)行目標(biāo)文件和共享目標(biāo)文件。共享庫文件是一種特殊的可重定位目標(biāo)。
??2.ELF目標(biāo)文件格式可以從編譯鏈接角度和程序執(zhí)行角度兩個角度看,前者是可重定位目標(biāo)格式,后者是可執(zhí)行目標(biāo)格式。從編譯鏈接角度看,可重定位目標(biāo)文件中包含ELF頭、各個節(jié)以及節(jié)頭表??蓤?zhí)行目標(biāo)文件中包含ELF頭、程序頭表(段頭表)以及各種節(jié)組成的段。
??3.bss段在可執(zhí)行目標(biāo)文件中不會有它的空間,只有當(dāng)可執(zhí)行目標(biāo)文件裝載運行時,才會被分配內(nèi)存(并且位于data段內(nèi)存塊之后),并且初始化為0。
本文參考《深入理解計算機系統(tǒng)》