cfile清空文件内容_编译-链接-加载 :ELF文件格式解析
摘要:對(duì)于C++的初學(xué)者,經(jīng)常在程序的編譯或者加載過(guò)程中遇到很多錯(cuò)誤,類似undefined reference to ... 和 GLIBCXX_3.4.20 not found 等。這些錯(cuò)誤都涉及到編譯器、連接器、加載器的相關(guān)知識(shí)。本系列文章,將通過(guò)一個(gè)實(shí)例程序,詳細(xì)介紹一個(gè)程序的編譯、鏈接、加載的過(guò)程。為了弄清這個(gè)過(guò)程,本文會(huì)簡(jiǎn)要介紹文本代碼到可執(zhí)行二進(jìn)制文件的大致過(guò)程,同時(shí)介紹x86平臺(tái)上linux系統(tǒng)下ELF文件格式,方便后續(xù)詳細(xì)探討編譯-鏈接-加載的詳細(xì)過(guò)程。
1. 程序的編譯與鏈接過(guò)程
對(duì)于編譯型的程序,代碼需要經(jīng)過(guò)編譯-鏈接的過(guò)程才會(huì)生成可執(zhí)行程序,具體過(guò)程如下
=====> COMPILATION PROCESS <======||----> Input is Source file(.c)|V+=================+| || C Preprocessor || |+=================+|| ---> Pure C file ( comd:cc -E <file.name> )|V+=================+| || Lexical Analyzer|| |+-----------------+| || Syntax Analyzer || |+-----------------+| || Semantic Analyze|| |+-----------------+| || Pre Optimization|| |+-----------------+| || Code generation || |+-----------------+| || Post Optimize || |+=================+||---> Assembly code (comd: cc -S <file.name> )|V+=================+| || Assembler || |+=================+||---> Object file (.obj) (comd: cc -c <file.name>)|V+=================+| Linker || and || loader |+=================+||---> Executable (.Exe/a.out) (com:cc <file.name> ) |VExecutable file(a.out)- 詞法分析
- 語(yǔ)法分析
- 語(yǔ)義分析
- 源代碼優(yōu)化:循環(huán)優(yōu)化、無(wú)用代碼刪除等
- 代碼生成
3. 鏈接:符號(hào)解析、重定位等。注意連接器和加載器的功能區(qū)分并不是那么清晰,對(duì)于loader而言,也會(huì)處理一些鏈接的工作。
后文用到的main.cpp內(nèi)容如下,其他代碼都在這里 https://github.com/yukun89/draft/tree/master/hello_world/chapter1
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include "func.h" int global_b = 1; const int global_c = 1; int global_d[10]; static int global_e[10];int main(){static char *p = "Begin printf ";int *ip = (int *)malloc(4);*ip = 1;global_b = func(*ip);printf("%s the value of func is %dn", p, func(1));return global_b + global_c; }2.ELF文件格式
與編譯-鏈接-加載相關(guān)的ELF文件主要有兩種格式:可重定位目標(biāo)文件(后綴名為.o) 與 可執(zhí)行目標(biāo)文件。(另外還有兩種是共享庫(kù)文件 和 coredump文件。)
分析數(shù)據(jù)結(jié)構(gòu)之前,我們秉承一個(gè)基本原則:結(jié)構(gòu)決定功能;反過(guò)來(lái)說(shuō)也成立,設(shè)計(jì)ELF文件結(jié)構(gòu),是為了滿足特定的功能。這里我們先簡(jiǎn)要梳理一下,ELF文件應(yīng)該提供哪些功能?簡(jiǎn)單來(lái)說(shuō),ELF文件需要滿足可鏈接、可加載、可執(zhí)行三大類基本功能,具體來(lái)說(shuō),包含以下詳細(xì)功能。
- 從可執(zhí)行的角度講,程序需要包含指令與數(shù)據(jù),也就是說(shuō)ELF文件中需要存儲(chǔ)程序?qū)?yīng)的指令和數(shù)據(jù)
- 從可鏈接的角度講,需要處理不同編譯單元之間的引用問(wèn)題,所以需要符號(hào)解析與重定位相關(guān)信息
- 從內(nèi)容組織的角度講,ELF文件中包含代碼、數(shù)據(jù)、重定位信息等多個(gè)section,同時(shí)包含這些數(shù)據(jù)的元數(shù)據(jù)信息(每個(gè)section在文件的起始地址是什么,有多大)。另外,ELF文件格式和其他的任何二進(jìn)制文件一樣,還應(yīng)該包含一個(gè)header,作為所有ELF文件中信息的元數(shù)據(jù)
- 從可加載的角度講,ELF文件需要指定將那些代碼、數(shù)據(jù)映射到虛擬內(nèi)存的什么位置
綜上,ELF的文件大致格式如圖所示
注意:Section Headers并不在ELF文件的末尾;Program Header table并不存在于每一種ELF文件格式之中。下面我們用linux下的兩個(gè)命令工具readelf和objdump來(lái)詳細(xì)分析ELF文件中的各個(gè)部分。
2.1 ELF文件頭
ELF文件的相關(guān)定義在/usr/include/elf.h文件之中,具體ELF文件頭的信息如下
typedef struct {unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */Elf64_Half e_type; /* Object file type */Elf64_Half e_machine; /* Architecture */Elf64_Word e_version; /* Object file version */Elf64_Addr e_entry; /* Entry point virtual address */Elf64_Off e_phoff; /* Program header table file offset */Elf64_Off e_shoff; /* Section header table file offset */Elf64_Word e_flags; /* Processor-specific flags */Elf64_Half e_ehsize; /* ELF header size in bytes */Elf64_Half e_phentsize; /* Program header table entry size */Elf64_Half e_phnum; /* Program header table entry count */Elf64_Half e_shentsize; /* Section header table entry size */Elf64_Half e_shnum; /* Section header table entry count */Elf64_Half e_shstrndx; /* Section header string table index */ } Elf64_Ehdr;這里,我們用readelf -h分別查看main.o和main兩種不同格式ELF文件的文件頭,得到的結(jié)果如下
ykhuang@0062a6cb7e5e: readelf -h main.o ELF Header:Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00Class: ELF64Data: 2's complement, little endianVersion: 1 (current)OS/ABI: UNIX - System VABI Version: 0Type: REL (Relocatable file)Machine: Advanced Micro Devices X86-64Version: 0x1Entry point address: 0x0Start of program headers: 0 (bytes into file)Start of section headers: 1112 (bytes into file)Flags: 0x0Size of this header: 64 (bytes)Size of program headers: 0 (bytes)Number of program headers: 0Size of section headers: 64 (bytes)Number of section headers: 14Section header string table index: 1ykhuang@0062a6cb7e5e : readelf -h main ELF Header:Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00Class: ELF64Data: 2's complement, little endianVersion: 1 (current)OS/ABI: UNIX - System VABI Version: 0Type: EXEC (Executable file)Machine: Advanced Micro Devices X86-64Version: 0x1Entry point address: 0x400550Start of program headers: 64 (bytes into file)Start of section headers: 4536 (bytes into file)Flags: 0x0Size of this header: 64 (bytes)Size of program headers: 56 (bytes)Number of program headers: 9Size of section headers: 64 (bytes)Number of section headers: 30Section header string table index: 27通過(guò)以上的內(nèi)容,我們不難分析,header的主要作用是標(biāo)識(shí)ELF文件中section headers和program headers的位置與大小。header中各個(gè)其他字段的解釋,我們主要關(guān)注以下幾點(diǎn)
- Type表示這個(gè)ELF文件屬于上文說(shuō)到的哪種(可重定位還是可執(zhí)行)ELF文件
- 程序入口地址Entry point address這一項(xiàng)對(duì)于可執(zhí)行文件才有意義
- 因?yàn)閘oader只會(huì)加載可執(zhí)行文件,將文件中的代碼和數(shù)據(jù)映射到虛擬MM,所以只有可執(zhí)行文件的program headers相關(guān)信息才有意義。
2.2 ELF文件section
ELF文件中的section主要包括:代碼段、數(shù)據(jù)段、重定位段等信息,section對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)如下
typedef struct {Elf64_Word sh_name; /* Section name (string tbl index) */Elf64_Word sh_type; /* Section type */Elf64_Xword sh_flags; /* Section flags */Elf64_Addr sh_addr; /* Section virtual addr at execution */Elf64_Off sh_offset; /* Section file offset */Elf64_Xword sh_size; /* Section size in bytes */Elf64_Word sh_link; /* Link to another section */Elf64_Word sh_info; /* Additional section information */Elf64_Xword sh_addralign; /* Section alignment */Elf64_Xword sh_entsize; /* Entry size if section holds table */ } Elf64_Shdr;下面,讓我們來(lái)分別查看可重定位目標(biāo)文件與可執(zhí)行目標(biāo)文件的section信息
####可重定位目標(biāo)文件的信息ykhuang@0062a6cb7e5e ~/project/draft/hello_world/chapter1 master ● readelf -S -W main.o There are 14 section headers, starting at offset 0x1c0:Section Headers:[Nr] Name Type Address Off Size ES Flg Lk Inf Al[ 0] NULL 0000000000000000 000000 000000 00 0 0 0[ 1] .text PROGBITS 0000000000000000 000040 000063 00 AX 0 0 4[ 2] .rela.text RELA 0000000000000000 000750 0000c0 18 12 1 8[ 3] .data PROGBITS 0000000000000000 0000a8 000010 00 WA 0 0 8[ 4] .rela.data RELA 0000000000000000 000810 000018 18 12 3 8[ 5] .bss NOBITS 0000000000000000 0000c0 000068 00 WA 0 0 32[ 6] .rodata PROGBITS 0000000000000000 0000c0 00002e 00 A 0 0 4[ 7] .comment PROGBITS 0000000000000000 0000ee 00002d 01 MS 0 0 1[ 8] .note.GNU-stack PROGBITS 0000000000000000 00011b 000000 00 0 0 1[ 9] .eh_frame PROGBITS 0000000000000000 000120 000038 00 A 0 0 8[10] .rela.eh_frame RELA 0000000000000000 000828 000018 18 12 9 8[11] .shstrtab STRTAB 0000000000000000 000158 000066 00 0 0 1[12] .symtab SYMTAB 0000000000000000 000540 0001b0 18 13 11 8[13] .strtab STRTAB 0000000000000000 0006f0 00005a 00 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)其中type字段的含義如下:
- PROGBITS: 程序內(nèi)容,包含代碼、數(shù)據(jù)、調(diào)試相關(guān)信息
- NOBITS:和PROGBITS類似,唯一不同的是在文件中不占空間,對(duì)應(yīng)的進(jìn)行內(nèi)存空間是加載的時(shí)候申請(qǐng)的
- SYSTAM/DYNSYM: SYSTAM 表用于普通鏈接;DYNSYM用于動(dòng)態(tài)鏈接
- STRTAB:string table,用于section名稱、普通的符號(hào)名稱、動(dòng)態(tài)鏈接的符號(hào)名稱。 據(jù)此,我們繪制出main.o文件的布局如下:
可執(zhí)行文件的信息比較繁瑣,我們大致給出,后續(xù)再分析具體每個(gè)section的含義與作用。
####可執(zhí)行文件的section信息ykhuang@0062a6cb7e5e ~/project/draft/hello_world/chapter1 master ● readelf -S -W main There are 30 section headers, starting at offset 0x11b8:Section Headers:[Nr] Name Type Address Off Size ES Flg Lk Inf Al[ 0] NULL 0000000000000000 000000 000000 00 0 0 0[ 1] .interp PROGBITS 0000000000400238 000238 00001c 00 A 0 0 1[ 2] .note.ABI-tag NOTE 0000000000400254 000254 000020 00 A 0 0 4[ 3] .note.gnu.build-id NOTE 0000000000400274 000274 000024 00 A 0 0 4[ 4] .gnu.hash GNU_HASH 0000000000400298 000298 00001c 00 A 5 0 8[ 5] .dynsym DYNSYM 00000000004002b8 0002b8 0000c0 18 A 6 1 8[ 6] .dynstr STRTAB 0000000000400378 000378 0000b7 00 A 0 0 1[ 7] .gnu.version VERSYM 0000000000400430 000430 000010 02 A 5 0 2[ 8] .gnu.version_r VERNEED 0000000000400440 000440 000020 00 A 6 1 8[ 9] .rela.dyn RELA 0000000000400460 000460 000018 18 A 5 0 8[10] .rela.plt RELA 0000000000400478 000478 000060 18 A 5 12 8[11] .init PROGBITS 00000000004004d8 0004d8 00001a 00 AX 0 0 4[12] .plt PROGBITS 0000000000400500 000500 000050 10 AX 0 0 16[13] .text PROGBITS 0000000000400550 000550 000224 00 AX 0 0 16[14] .fini PROGBITS 0000000000400774 000774 000009 00 AX 0 0 4[15] .rodata PROGBITS 0000000000400780 000780 00003e 00 A 0 0 8[16] .eh_frame_hdr PROGBITS 00000000004007c0 0007c0 000044 00 A 0 0 4[17] .eh_frame PROGBITS 0000000000400808 000808 000134 00 A 0 0 8[18] .init_array INIT_ARRAY 0000000000600de0 000de0 000008 00 WA 0 0 8[19] .fini_array FINI_ARRAY 0000000000600de8 000de8 000008 00 WA 0 0 8[20] .jcr PROGBITS 0000000000600df0 000df0 000008 00 WA 0 0 8[21] .dynamic DYNAMIC 0000000000600df8 000df8 000200 10 WA 6 0 8[22] .got PROGBITS 0000000000600ff8 000ff8 000008 08 WA 0 0 8[23] .got.plt PROGBITS 0000000000601000 001000 000038 08 WA 0 0 8[24] .data PROGBITS 0000000000601038 001038 000018 00 WA 0 0 8[25] .bss NOBITS 0000000000601050 001050 000038 00 WA 0 0 16[26] .comment PROGBITS 0000000000000000 001050 000060 01 MS 0 0 1[27] .shstrtab STRTAB 0000000000000000 0010b0 000108 00 0 0 1[28] .symtab SYMTAB 0000000000000000 001938 0006d8 18 29 47 8[29] .strtab STRTAB 0000000000000000 002010 00028f 00 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)2.2.1 代碼段(txt)-數(shù)據(jù)段-只讀數(shù)據(jù)段
代碼段的信息,我們可以用objdump -s -d main.o具體查看代碼段的信息,此處不展開討論。
數(shù)據(jù)段信息如下:
ykhuang@0062a6cb7e5e ~/project/draft/hello_world/chapter1 master ● objdump -s -d main.omain.o: file format elf64-x86-64Contents of section .text:0000 554889e5 4883ec10 bf040000 00e80000 UH..H...........0010 00004889 45f8488b 45f8c700 01000000 ..H.E.H.E.......0020 488b45f8 8b0089c7 e8000000 00890500 H.E.............0030 000000bf 01000000 e8000000 0089c248 ...............H0040 8b050000 00004889 c6bf0000 0000b800 ......H.........0050 000000e8 00000000 8b050000 000083c0 ................0060 01c9c3 ... Contents of section .data:0000 01000000 00000000 00000000 00000000 ................ Contents of section .rodata:0000 01000000 25732074 68652076 616c7565 ....%s the value0010 206f6620 66756e63 20697320 25640a00 of func is %d..0020 42656769 6e207072 696e7466 2000 Begin printf .查看符號(hào)表信息如下 objdump -x main.o
SYMBOL TABLE: 0000000000000000 g O .data 0000000000000004 global_b 0000000000000000 g O .rodata 0000000000000004 global_c 0000000000000000 g O .bss 0000000000000028 global_d 0000000000000000 g F .text 0000000000000063 main從這里我們可以看出,我們只有依賴符號(hào)表,才能知道某個(gè)變量存放的具體數(shù)值信息。
3.其他
需要指出的是,ELF文件格式之所以是現(xiàn)在這種結(jié)構(gòu),是由體系結(jié)構(gòu)和操作系統(tǒng)來(lái)決定的。在一些其他的系統(tǒng)上(例如MS-DOS或者IBM system V),編譯-鏈接的中間文件具有完全不同的結(jié)構(gòu)。總體來(lái)說(shuō),這些二進(jìn)制文件主要需要滿足可鏈接、可加載、可執(zhí)行。這里,我們簡(jiǎn)要列出了另外兩種編譯-鏈接-加載相關(guān)的文件結(jié)構(gòu):
- COM(component object model)文件:MS-DOS系統(tǒng)上的可執(zhí)行文件。只有二進(jìn)制代碼,不包含其他任何信息,代碼會(huì)自動(dòng)load到0x100,只支持一個(gè)代碼段。
- a.out 文件:unix上可執(zhí)行文件的一種,包含header、代碼段、數(shù)據(jù)段、其他段。程序執(zhí)行的過(guò)程主要是“讀取文件頭; map代碼段;map私有數(shù)據(jù)段; 創(chuàng)建進(jìn)行棧; 設(shè)置寄存器然后跳轉(zhuǎn)到程序開頭”
ELF文件是目前l(fā)inux平臺(tái)上最通用的一種可鏈接-加載-執(zhí)行的文件結(jié)構(gòu),對(duì)于不同的語(yǔ)言,例如C/C++,他們對(duì)應(yīng)的ELF文件格式略微有所不同:C++相對(duì)于C編譯而成的ELF文件格式有自己獨(dú)特的section。了解ELF文件格式有利于我們后續(xù)詳細(xì)理解程序的鏈接-加載-執(zhí)行過(guò)程。
最后放一下blog地址,歡迎來(lái)玩
編譯-鏈接-加載:ELF文件格式解析 | 優(yōu)孚?www.uufool.com參考:
總結(jié)
以上是生活随笔為你收集整理的cfile清空文件内容_编译-链接-加载 :ELF文件格式解析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 域电脑不能显示桌面_学会这些电脑操作,工
- 下一篇: cygwin编译生成hello worl