在講述進程以前,先來了解一下二進制可執行文件和目標文件。咱們知道程序運行起來後就有了進程,因此瞭解程序的結構對於認識操做系統大有好處。先拋三個問題:編譯器編譯代碼後生成的文件是目標文件,目標文件裏面是什麼?可執行文件裏面又是什麼?可執行文件與目標文件的區別在哪呢?linux
這篇文件會使用以下一個簡單的 C 文件進行貫穿講解:程序員
#include <stdio.h> int main(){ printf("hello"); return 0; }
1.編譯過程
代碼要想成爲可執行文件就須要通過編譯。編譯過程分爲預處理、編譯、彙編、連接。windows
預處理:主要處理源碼中的預處理指令,在 C/C++ 中主要指的是「#include」、「#define」 等,它的處理方法是將這些指令所在的位置進行內容替換,好比 「#include」 就會把整個頭文件給引進來。centos
編譯:把上一步預處理過的文件進行詞法分析、語法分析、語義分析&優化一系列步驟後生成彙編代碼。這是整個編譯過程當中最難最複雜的 部分。在如今版本的 GCC 中,預處理和編譯這兩個過程已經合併爲一步。數組
彙編:把彙編代碼轉變爲機器能夠執行的指令,這是一個翻譯的過程,有個彙編指令與機器指令的對照表進行一一對應。架構
連接:把每一個獨立編譯的模塊進行組裝,這些模塊之間會有相互引用,連接就是讓這些獨立模塊可以相互正確引用,造成一個完整的可執行文件。連接分爲動態連接和靜態連接,這兩個會在下篇文章進行講解。函數
使用以上那個 C 文件進行整個編譯過程以下:優化
預處理:gcc -E hello.c -o hello.i 編譯:gcc -S hello.i -o hello.s 彙編:gcc -c hello.s -o hello.o 連接:ld -static /usr/lib64/crt1.o /usr/lib64/crti.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtbeginT.o -L /usr/lib/gcc/x86_64-redhat-linux/4.8.5 -L /usr/lib -L hello.o --start-group -lgcc -lgcc_eh -lc --end-group /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtend.o /usr/lib64/crtn.o
不想這麼麻煩的話,直接一行代碼搞定:ui
gcc hello.c -o hello
2.什麼是可執行文件
可執行文件指的是能夠由操做系統進行加載執行的文件。在不一樣的系統中,可執行文件是不一樣的。好比在 windows 中可執行文件是以 exe 後綴的文件,而在 linux 中可執行文件能夠是任何後綴的文件,只是須要給文件添加「可執行」權限。注意,在同一種操做系統中若是 CPU 的架構不一樣的話,可執行文件是不能通用的,好比:在 linux 中,ARM 架構上的可執行文件就不能直接在 X86 架構執行,由於可執行文件內一般含有二進制編碼的 CPU 指令,而每種 CPU 的指令集都是不同的。這種狀況下一般須要進行交叉編譯:在一個平臺上生成另外一個平臺的可執行文件,好比在 X86 平臺上生成 ARM 的可執行文件。編碼
3.可執行文件類型
在 windows 系統中可執行文件類型爲 PE(Portable Executable),在 Linux 系統中可執行文件類型爲 ELF(Executable Linkable Format)。
在 linux 下可使用 file 命令查看文件的文件格式。 普通的源碼文件顯示是:
[root@centos7-dev hello]# file hello.c hello.c: C source, ASCII text
目標文件是:
[root@centos7-dev hello]# file hello.o hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
可執行文件是:
[root@centos7-dev hello]# file hello hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=6115b831b9be5d023a87ce84ecd72d44cbfa1548, not stripped
不是隻有可執行文件能夠按照上述這兩種類型進行存儲,目標文件(.obj/.o)能夠,連接庫(.dll/.so)也能夠,甚至在 linux 下的 coredump 也是可執行文件格式。目標文件既然和可執行文件的存儲結構一致,那二者有什麼差別呢?一句話:目標文件是還沒有進行連接操做的可執行文件。
4.關於連接
爲何會有連接這個動做呢?
先把時間拉回到打孔條帶的時代,在計算機剛出來的時候,程序都是寫在條帶上。最開始的程序是一條紙帶,很簡單很完整。而後隨着時間的推移,程序愈來愈多了,條帶也是愈來愈多。在某個時刻有個程序員想偷懶,他正在寫的 A 程序想使用以前寫好的 B 程序的一個功能,因此他把 B 條帶上的那段代碼給剪出來而後拼接上了 A 程序。這是最先的連接,應該也是最先的靜態連接。
在現代的軟件開發過程當中,程序文件的數量是很是龐大的,每每一個工程就有上千個模塊和文件。這些模塊和文件是相互獨立和依賴的,相互調用的狀況是很常見的。你想一想,這樣一個龐大的項目要編譯出一個可執行文件應該怎麼作?先對每一個代碼文件進行單獨編譯獲得目標文件,而後把這些目標文件給捏到一塊去,造成一個可執行文件。而這個捏到一塊去的過程就是連接。
若是沒有這個連接過程,上千模塊的目標文件根本無法正常工做,由於它們沒法執行。那有人說了:整個項目工程能夠只寫一個文件呀,只對這個文件進行編譯就能夠不用連接了。沒錯,但那就無法比較好的進行多人協做開發了。
5.ELF 結構
因爲對 windows 系統的可執行文件結構不熟,因此這裏拿 linux 系統下的可執行文件結構 ELF 進行分析一下。ELF 裏面包含了編譯後的機器指令和數據、符號表、調試信息、字符串等,這些不一樣類型的信息都會單獨分開存放在某個模塊內,通常稱呼這些模塊爲」段「。
首先咱們得先了解 ELF 都有哪些段,請看以下資料:
.bss 構成程序的內存映像的未初始化數據。根據定義,系統在程序開始運行時會將數據初始化爲零。如節類型 SHT_NOBITS 所指明的那樣,此節不會佔用任何文件空間。 .comment 註釋信息,一般由編譯系統的組件提供。 .data、.data1 構成程序的內存映像的已初始化數據。 .dynamic 動態連接信息。 .dynstr 進行動態連接所需的字符串,一般是表示與符號表各項關聯的名稱的字符串。 .dynsym 動態連接符號表。 .eh_frame_hdr、.eh_frame 用於展開棧的調用幀信息。 .fini 可執行指令,用於構成包含此節的可執行文件或共享目標文件的單個終止函數。 .fini_array 函數指針數組,用於構成包含此節的可執行文件或共享目標文件的單個終止數組。 .got 全局偏移表。 .hash 符號散列表。 .init 可執行指令,用於構成包含此節的可執行文件或共享目標文件的單個初始化函數。 .init_array 函數指針數組,用於構成包含此節的可執行文件或共享目標文件的單個初始化數組。 .interp 程序的解釋程序的路徑名。 .lbss 特定於 x64 的未初始化的數據。此數據與 .bss 相似,但用於大小超過 2 GB 的節。 .ldata、.ldata1 特定於 x64 的已初始化數據。此數據與 .data 相似,但用於大小超過 2 GB 的節。 .lrodata、.lrodata1 特定於 x64 的只讀數據。此數據與 .rodata 相似,但用於大小超過 2 GB 的節。 .note 註釋節中說明了該格式的信息。 .plt 過程連接表。 .preinit_array 函數指針數組,用於構成包含此節的可執行文件或共享目標文件的單個預初始化數組。 .rela 不適用於特定節的重定位。此節的用途之一是用於寄存器重定位。 .relname、.relaname 重定位信息,如重定位節中所述。若是文件具備包括重定位的可裝入段,則此節的屬性將包括 SHF_ALLOC 位。不然,該位會處於禁用狀態。一般,name 由應用重定位的節提供。所以,.text 的重定位節的名稱一般爲 .rel.text 或 .rela.text。 .rodata、.rodata1 一般構成進程映像中的非可寫段的只讀數據。 .shstrtab 節名稱。 .strtab 字符串,一般是表示與符號表各項關聯的名稱的字符串。若是文件具備包括符號字符串表的可裝入段,則此節的屬性將包括 SHF_ALLOC 位。不然,該位會處於禁用狀態。 .symtab 符號表,如符號表節中所述。若是文件具備包括符號表的可裝入段,則此節的屬性將包括 SHF_ALLOC 位。不然,該位會處於禁用狀態。 .symtab_shndx 此節包含特殊符號表的節索引數組,如 .symtab 所述。若是關聯的符號表節包括 SHF_ALLOC 位,則此節的屬性也將包括該位。不然,該位會處於禁用狀態。 .tbss 此節包含構成程序的內存映像的未初始化線程局部數據。根據定義,爲每一個新執行流實例化數據時,系統都會將數據初始化爲零。如節類型 SHT_NOBITS 所指明的那樣,此節不會佔用任何文件空間。 .tdata、.tdata1 這些節包含已初始化的線程局部數據,這些數據構成程序的內存映像。對於每一個新執行流,系統會對其內容的副本進行實例化。 .text 程序的文本或可執行指令。
有了以上資料,咱們可使用 objdump 命令查看 hello 可執行文件中都有哪些模塊:
[root@centos7-dev hello]# objdump -h hello hello: file format elf64-x86-64 Sections: Idx Name Size VMA LMA File off Algn 0 .interp 0000001c 0000000000400238 0000000000400238 00000238 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 1 .note.ABI-tag 00000020 0000000000400254 0000000000400254 00000254 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 2 .note.gnu.build-id 00000024 0000000000400274 0000000000400274 00000274 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 3 .gnu.hash 0000001c 0000000000400298 0000000000400298 00000298 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 4 .dynsym 00000060 00000000004002b8 00000000004002b8 000002b8 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 5 .dynstr 0000003f 0000000000400318 0000000000400318 00000318 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 6 .gnu.version 00000008 0000000000400358 0000000000400358 00000358 2**1 CONTENTS, ALLOC, LOAD, READONLY, DATA 7 .gnu.version_r 00000020 0000000000400360 0000000000400360 00000360 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 8 .rela.dyn 00000018 0000000000400380 0000000000400380 00000380 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 9 .rela.plt 00000048 0000000000400398 0000000000400398 00000398 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 10 .init 0000001a 00000000004003e0 00000000004003e0 000003e0 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 11 .plt 00000040 0000000000400400 0000000000400400 00000400 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE 12 .text 00000182 0000000000400440 0000000000400440 00000440 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE 13 .fini 00000009 00000000004005c4 00000000004005c4 000005c4 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 14 .rodata 00000016 00000000004005d0 00000000004005d0 000005d0 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 15 .eh_frame_hdr 00000034 00000000004005e8 00000000004005e8 000005e8 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 16 .eh_frame 000000f4 0000000000400620 0000000000400620 00000620 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 17 .init_array 00000008 0000000000600e10 0000000000600e10 00000e10 2**3 CONTENTS, ALLOC, LOAD, DATA 18 .fini_array 00000008 0000000000600e18 0000000000600e18 00000e18 2**3 CONTENTS, ALLOC, LOAD, DATA 19 .jcr 00000008 0000000000600e20 0000000000600e20 00000e20 2**3 CONTENTS, ALLOC, LOAD, DATA 20 .dynamic 000001d0 0000000000600e28 0000000000600e28 00000e28 2**3 CONTENTS, ALLOC, LOAD, DATA 21 .got 00000008 0000000000600ff8 0000000000600ff8 00000ff8 2**3 CONTENTS, ALLOC, LOAD, DATA 22 .got.plt 00000030 0000000000601000 0000000000601000 00001000 2**3 CONTENTS, ALLOC, LOAD, DATA 23 .data 00000004 0000000000601030 0000000000601030 00001030 2**0 CONTENTS, ALLOC, LOAD, DATA 24 .bss 00000004 0000000000601034 0000000000601034 00001034 2**0 ALLOC 25 .comment 0000005a 0000000000000000 0000000000000000 00001034 2**0 CONTENTS, READONLY
而後再用一樣的命令來查看目標文件:
[root@centos7-dev hello]# objdump -h hello.o hello.o: file format elf64-x86-64 Sections: Idx Name Size VMA LMA File off Algn 0 .text 0000001a 0000000000000000 0000000000000000 00000040 2**0 CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE 1 .data 00000000 0000000000000000 0000000000000000 0000005a 2**0 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000000 0000000000000000 0000000000000000 0000005a 2**0 ALLOC 3 .rodata 00000006 0000000000000000 0000000000000000 0000005a 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 4 .comment 0000002e 0000000000000000 0000000000000000 00000060 2**0 CONTENTS, READONLY 5 .note.GNU-stack 00000000 0000000000000000 0000000000000000 0000008e 2**0 CONTENTS, READONLY 6 .eh_frame 00000038 0000000000000000 0000000000000000 00000090 2**3 CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
能夠發現目標文件內部的模塊信息比可執行文件少不少,這是由於在目標文件通過連接過程變成可執行文件的過程當中進行不少處理,好比:地址和空間分配、重定位、符號決議等。
咱們還能夠用 objdump 來查看每一個模塊的代碼信息:
objdump -s -d hello
查看符號表:
objdump -x hello
更多關於 objdump 命令的使用能夠 man objdump 查看。
歡迎關注個人公衆號:哈扣。