cfile清空文件内容_编译-链接-加载 :ELF文件格式解析
摘要:對于C++的初學者,經常在程序的編譯或者加載過程中遇到很多錯誤,類似undefined reference to ... 和 GLIBCXX_3.4.20 not found 等。這些錯誤都涉及到編譯器、連接器、加載器的相關知識。本系列文章,將通過一個實例程序,詳細介紹一個程序的編譯、鏈接、加載的過程。為了弄清這個過程,本文會簡要介紹文本代碼到可執行二進制文件的大致過程,同時介紹x86平臺上linux系統下ELF文件格式,方便后續詳細探討編譯-鏈接-加載的詳細過程。
1. 程序的編譯與鏈接過程
對于編譯型的程序,代碼需要經過編譯-鏈接的過程才會生成可執行程序,具體過程如下
=====> 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)- 詞法分析
- 語法分析
- 語義分析
- 源代碼優化:循環優化、無用代碼刪除等
- 代碼生成
3. 鏈接:符號解析、重定位等。注意連接器和加載器的功能區分并不是那么清晰,對于loader而言,也會處理一些鏈接的工作。
后文用到的main.cpp內容如下,其他代碼都在這里 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文件格式
與編譯-鏈接-加載相關的ELF文件主要有兩種格式:可重定位目標文件(后綴名為.o) 與 可執行目標文件。(另外還有兩種是共享庫文件 和 coredump文件。)
分析數據結構之前,我們秉承一個基本原則:結構決定功能;反過來說也成立,設計ELF文件結構,是為了滿足特定的功能。這里我們先簡要梳理一下,ELF文件應該提供哪些功能?簡單來說,ELF文件需要滿足可鏈接、可加載、可執行三大類基本功能,具體來說,包含以下詳細功能。
- 從可執行的角度講,程序需要包含指令與數據,也就是說ELF文件中需要存儲程序對應的指令和數據
- 從可鏈接的角度講,需要處理不同編譯單元之間的引用問題,所以需要符號解析與重定位相關信息
- 從內容組織的角度講,ELF文件中包含代碼、數據、重定位信息等多個section,同時包含這些數據的元數據信息(每個section在文件的起始地址是什么,有多大)。另外,ELF文件格式和其他的任何二進制文件一樣,還應該包含一個header,作為所有ELF文件中信息的元數據
- 從可加載的角度講,ELF文件需要指定將那些代碼、數據映射到虛擬內存的什么位置
綜上,ELF的文件大致格式如圖所示
注意:Section Headers并不在ELF文件的末尾;Program Header table并不存在于每一種ELF文件格式之中。下面我們用linux下的兩個命令工具readelf和objdump來詳細分析ELF文件中的各個部分。
2.1 ELF文件頭
ELF文件的相關定義在/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文件的文件頭,得到的結果如下
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通過以上的內容,我們不難分析,header的主要作用是標識ELF文件中section headers和program headers的位置與大小。header中各個其他字段的解釋,我們主要關注以下幾點
- Type表示這個ELF文件屬于上文說到的哪種(可重定位還是可執行)ELF文件
- 程序入口地址Entry point address這一項對于可執行文件才有意義
- 因為loader只會加載可執行文件,將文件中的代碼和數據映射到虛擬MM,所以只有可執行文件的program headers相關信息才有意義。
2.2 ELF文件section
ELF文件中的section主要包括:代碼段、數據段、重定位段等信息,section對應的數據結構如下
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;下面,讓我們來分別查看可重定位目標文件與可執行目標文件的section信息
####可重定位目標文件的信息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: 程序內容,包含代碼、數據、調試相關信息
- NOBITS:和PROGBITS類似,唯一不同的是在文件中不占空間,對應的進行內存空間是加載的時候申請的
- SYSTAM/DYNSYM: SYSTAM 表用于普通鏈接;DYNSYM用于動態鏈接
- STRTAB:string table,用于section名稱、普通的符號名稱、動態鏈接的符號名稱。 據此,我們繪制出main.o文件的布局如下:
可執行文件的信息比較繁瑣,我們大致給出,后續再分析具體每個section的含義與作用。
####可執行文件的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)-數據段-只讀數據段
代碼段的信息,我們可以用objdump -s -d main.o具體查看代碼段的信息,此處不展開討論。
數據段信息如下:
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 .查看符號表信息如下 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從這里我們可以看出,我們只有依賴符號表,才能知道某個變量存放的具體數值信息。
3.其他
需要指出的是,ELF文件格式之所以是現在這種結構,是由體系結構和操作系統來決定的。在一些其他的系統上(例如MS-DOS或者IBM system V),編譯-鏈接的中間文件具有完全不同的結構。總體來說,這些二進制文件主要需要滿足可鏈接、可加載、可執行。這里,我們簡要列出了另外兩種編譯-鏈接-加載相關的文件結構:
- COM(component object model)文件:MS-DOS系統上的可執行文件。只有二進制代碼,不包含其他任何信息,代碼會自動load到0x100,只支持一個代碼段。
- a.out 文件:unix上可執行文件的一種,包含header、代碼段、數據段、其他段。程序執行的過程主要是“讀取文件頭; map代碼段;map私有數據段; 創建進行棧; 設置寄存器然后跳轉到程序開頭”
ELF文件是目前linux平臺上最通用的一種可鏈接-加載-執行的文件結構,對于不同的語言,例如C/C++,他們對應的ELF文件格式略微有所不同:C++相對于C編譯而成的ELF文件格式有自己獨特的section。了解ELF文件格式有利于我們后續詳細理解程序的鏈接-加載-執行過程。
最后放一下blog地址,歡迎來玩
編譯-鏈接-加載:ELF文件格式解析 | 優孚?www.uufool.com參考:
總結
以上是生活随笔為你收集整理的cfile清空文件内容_编译-链接-加载 :ELF文件格式解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 域电脑不能显示桌面_学会这些电脑操作,工
- 下一篇: cygwin编译生成hello worl