程式的編譯與執行過程
本文以C程式為例。
構建C程式需要4個步驟,分別使用4個工具完成: preprocessor, compiler, assembler, and linker.四步完成後生成一個可執行檔案。
第一步,預處理. 這一步處理 標頭檔案、條件編譯指令和巨集定義。
第二步,編譯. 將第一步產生的檔案連同其他原始檔一起編譯成彙編程式碼。
第三步,彙編。將第二步產生的彙編原始碼轉換為 object file.
第四步,連結. 將第三步產生的一些object file 連結成一個可執行的檔案。
檔案字尾 | 對於編譯來說 |
---|---|
file_name.c | 需要預處理的C原檔案 |
file_name.i | 預處理C檔案後產生的檔案 |
file_name.ii | 預處理C++檔案後產生的檔案 |
file_name.cc file_name.cpp | 需要預處理的C++原檔案 |
file_name.s | 產生的彙編程式碼 |
file_name.o | 產生的 object file |
可執行檔案
原始碼經過彙編產生物件檔案(Object files),然後經過連結生成可執行檔案(executable files)。物件檔案和可執行檔案的格式可以是ELF
(Executable and Linking Format) 或者 COFF
ELF
格式用於Linux系統,COFF
用於windows系統。 ELF格式來源於System V Release 4 unix。ELF檔案由幾個區組成,每個區前面都包含一個頭部。區總數有限制。區內可以包含可執行檔案,資料,動態連結資訊,除錯資料,符號表,重定位資訊,註釋,字串表和備註等資訊。其中,一些區等內容直接載入到程序映象中,一些區為構建程序映象過程中提供輔助資訊,還有一些區只在連結物件檔案過程中使用到。
下面幾個區是所有可執行檔案通用部分。
section | 描述 |
---|---|
.text | 這部分內容為可執行指令碼,被執行該檔案所產生到程序之間所共享 |
.bss | BSS 代表 ‘Block Started by Symbol’. 這裡記錄未初始化的全域性和靜態變數。由於 BSS 只會記錄還未賦值到變數,所以它不會實際儲存變數映象。 |
.data | 包含始化的全域性和靜態變數。這裡是可執行檔案的最大一部分。 |
.rdata | 只讀資料區,這裡包含常量和字串。 |
Symbol table | symbol 就是一些名字和地址。Symbol table 儲存著程式的symbolic definitions 和symbolic references 定位資訊。 |
Relocation records | 比如,當程式呼叫一個函式,對應的呼叫指令必須出讓控制到目的地址去執行。簡單來說,relocation records就是一些用來給linker用來調整區塊內容的參考資訊。 |
Because the various object files will include references to each others code and/or data, so various locations, these shall need to be combined during the link time.
For example in Figure w.2, the object file that has main() includes calls to functions funct() and printf().
重定位資訊(RELOCATION RECORDS)
由於不同的物件檔案之間儲存了彼此之間的引用,所以在連結期間需要整合這些定位。下圖是RELOCATION RECORDS
資訊的使用過程。
符號表(SYMBOL TABLE)
由於從彙編到機器碼過程中需要移除labels
,所以,物件檔案必須另找地方儲存對labels
的跟蹤資訊。符號表裡儲存著名字以及和名字對應的data
和text
區的偏移量。符號表就是用來記錄這些資訊的。
連結
共享物件檔案(shared object)
像printf(), malloc(), strcpy()
這樣的標準庫函式,一般在系統中以共享物件檔案存在,一遍程式連結時候取用。共享物件檔案根據連結方式不同,以兩種形態出現,一種是.a
字尾檔案,以便靜態連結使用,如libc.a
;一種是.so
字尾檔案,以便動態連結使用。比如libc.so
。
靜態連結
靜態連結將共享物件檔案.a
一同連結至可執行檔案內,比如:
gcc –static filename.c –o filename
這麼幹的缺點是可執行檔案大,程式執行耗費記憶體。
動態連結
動態連結不會將共享物件檔案連結至可執行檔案內,而是將.so
檔案的依賴資訊寫入可執行檔案中,當程式載入時,讓執行時連結器去找.so
檔案。動態連結的好處是,1)可執行檔案小,2)程式執行耗記憶體少,因為不同程序會共用同一份虛擬記憶體中的.so
載入,而不是每個程序單獨載入一份.so
至記憶體。3)庫升級不用重新連結程式。
程式如何利用共享物件
ELF區塊
.init - Startup
.text - String
.fini - Shutdown
.rodata - Read Only
.data - Initialized Data
.tdata - Initialized Thread Data
.tbss - Uninitialized Thread Data
.ctors - Constructors
.dtors - Destructors
.got - Global Offset Table
.bss - Uninitialized Data
在Linux系統上,可以使用readelf或者objdump工具產看ELF檔案。
程序載入
Linux程序載入自檔案系統裡的ELF檔案(使用execve()
或者spawn()
系統呼叫),如果檔案系統是塊裝置比如硬碟,則將其讀取到記憶體中,如果裝置已經記憶體對映比如flash盤,那麼無需載入至RAM,直接本地執行。
將可執行檔案載入至記憶體需要用到載入器(loader),載入器是作業系統通用必備的。載入器的作用:
1. Memory and access validation記憶體與訪問校驗。OS核心讀取可執行檔案頭部資訊確定可讀性以及記憶體評估,確定對其指令的執行能力。
2. 建立程序:1)為程式分配主存,2)copy可執行檔案的必要資料至主存,3)初始化暫存器比如esp等,4)跳進主程main()
程式載入後,在記憶體中的佈局: