Hi,我是小杜。SoC驗證中會經(jīng)常使用C語言,所以需要知道C語言生成可執(zhí)行二進制文件的具體過程以及如何從生成的中間文件讀取有用信息。因為小杜是轉行做數(shù)字IC驗證,SoC的知識需要重頭開始學,如果錯誤,還請批評指正。
C語言源碼到生成可執(zhí)行文件的過程通常包括預處理(Preprocessing)、編譯(Compilation)、匯編(Assembly)、鏈接(Linking)等多個步驟,每個步驟都有其特定的任務和產(chǎn)物。下面,小杜通過一個具體的例子詳細講述這個過程,以及如何通過反匯編(Disassembly)來查看匯編、鏈接產(chǎn)生的不可讀二進制目標文件。
C語言源碼準備
首先編寫3個文件,一個是主程序 main.c,另一個是功能函數(shù) func.c,外加一個頭文件 func.h。具體路徑為:
project/ │ ├──main.c ├── func.c └── func.h
代碼如下:
// main.c #include#include int main() { int result = add(5, 3); printf("Result: %d ", result); return 0; }
// func.c #include "func.h" int add(int a, int b) { return a + b; }
// func.h #ifndef FUNC_H #define FUNC_H int add(int a, int b); #endif
分步生成可執(zhí)行文件
1. 預處理(Preprocessing)
預處理器處理 #include、#define 等指令,并生成一個預處理后的文件??梢允褂?-E 選項運行預處理器。
gcc-Emain.c-omain.i-I . gcc-Efunc.c-ofunc.i-I .
main.i 和 func.i 是預處理后的文件,包含展開后的宏和頭文件內(nèi)容。以main.i為例,預處理后的文件可能會非常長,因為所有的宏、頭文件都被展開。預處理器在預處理文件中插入了很多#開頭的行,提供了文件和行號信息用于調(diào)試和錯誤報告。下面截圖展示了main.i文件中的主要部分:
源碼中所有的注釋都會在預處理階段被去除。如果源碼中有條件編譯指令,比如`ifdef,`ifndef,`if 等,預處理器會根據(jù)條件和結果保留或刪除相應的代碼。
預處理文件的用途:
調(diào)試:通過查看預處理后的文件,可以檢查宏和頭文件是否正確展開,從而幫助調(diào)試編譯問題。
了解代碼結構:預處理文件展示了代碼的最終形態(tài),包括所有的頭文件和宏定義,這對理解代碼的整體結構很有幫助。
性能優(yōu)化:分析預處理后的代碼,有助于識別和優(yōu)化編譯時間和代碼冗余問題。
2.編譯(Compilation)
編譯器將預處理后的C代碼轉換為匯編代碼。可以使用 -S 選項生成匯編代碼。
gcc -s main.i -o main.s -I . gcc-sfunc.i -o func.s -I .
從生成的匯編代碼中我們可以看到文件和段定義、函數(shù)入口和棧幀設置、調(diào)用add函數(shù)、調(diào)用printf函數(shù)以及函數(shù)退出。
匯編代碼可能是調(diào)試過程中接觸的最底層部分,SoC驗證過程中如果CPU卡死,通過記錄的寄存器內(nèi)容、程序計數(shù)器(PC)和堆棧指針(SP),找到對應的匯編指令就可以知道CPU卡死的具體位置,這對調(diào)試和找出代碼bug十分重要。從匯編代碼中我們可以得到如下信息:
函數(shù)調(diào)用和參數(shù)傳遞
通過匯編代碼可以看到函數(shù)是如何調(diào)用的,以及參數(shù)是如何傳遞的。例如,在x86-64架構中,前六個整數(shù)參數(shù)通過寄存器(如EDI、ESI、EDX等)傳遞。
棧幀管理
匯編代碼展示了棧幀是如何創(chuàng)建和銷毀的,尤其是通過 pushq %rbp、movq %rsp, %rbp 和 leave 指令。這有助于理解函數(shù)調(diào)用過程中棧的變化。
變量和寄存器操作
匯編代碼展示了如何使用寄存器和內(nèi)存操作來實現(xiàn)程序邏輯。例如,通過 movl 指令可以看到如何在寄存器和內(nèi)存之間傳遞數(shù)據(jù)。
調(diào)試和優(yōu)化
通過分析匯編代碼,可以發(fā)現(xiàn)編譯器生成的代碼是否高效。例如,可以識別不必要的指令或冗余操作,進而優(yōu)化代碼。
3. 匯編(Assembly)與反匯編(Disassembly)
匯編器將匯編代碼轉換為機器碼,生成目標文件。可以使用 -c 選項生成目標文件。這一步開始生成的二進制目標文件已經(jīng)不能看了,不過我們還是可以通過反匯編來獲取有用信息。
gcc -c main.s -o main.o -I . gcc-cfunc.s-ofunc.o -I .
main.o 和func.o 是目標文件,包含機器代碼、符號表和調(diào)試信息,但它們是二進制格式,不像匯編代碼那樣直接可讀。我們可以使用工具來查看分析目標文件,以獲取有用的信息,比如使用objdump。
反匯編
通過反匯編可以查看目標文件的機器碼、段信息以及符號表。
objdump-dmain.o#反匯編目標文件,顯示機器碼 objdump-hmain.o#顯示目標文件的段信息 objdump -t main.o # 顯示符號表
比如反匯編產(chǎn)生機器碼(工作中一般不會接觸這么深,這里只是舉例):
4. 鏈接(Linking)
鏈接器將多個目標文件和庫文件鏈接在一起,生成最終的可執(zhí)行文件。
gccmain.ofunc.o-omyprogram
myprogram 是最終生成的可執(zhí)行文件。二進制可執(zhí)行文件不可讀,但同樣可以使用objdump來查看可執(zhí)行文件的有用信息,比如:
objdump -f myprogram # 查看可執(zhí)行文件功能
該輸出展示了可執(zhí)行文件的基本信息,包括文件格式、架構、標志和程序入口信息。
一步到位的編譯和鏈接
通常我們會直接使用 gcc 命令來一步到位完成整個過程,而不需要手動執(zhí)行每個步驟:
gccmain.cfunc.-omyprogram
這個命令會自動處理上述所有步驟,并生成最終的可執(zhí)行文件 myprogram。
總結 讓我們來總結一下C語言源碼到最終的可執(zhí)行二進制文件的4個過程分別干了哪些事:
預處理:處理頭文件包含和宏定義,生成一個單一的C源文件。
編譯:將C源文件轉換為匯編代碼,這一步會進行語法檢查和優(yōu)化。
匯編:將匯編代碼轉換為目標文件,目標文件是二進制格式的機器碼,但還不是完整的可執(zhí)行程序。
鏈接:將多個目標文件和庫文件鏈接在一起,解決符號引用問題(如函數(shù)和變量的定義和聲明),生成最終的可執(zhí)行文件。
感謝你看到這里。項目工作中,其實工具鏈建立好之后一般也就不會去關注這些過程了,寫完C代碼走腳本流程即可,但如果出現(xiàn)bug,了解這一過程對解決bug就很重要了!
-
二進制
+關注
關注
2文章
778瀏覽量
41557 -
C語言
+關注
關注
180文章
7591瀏覽量
135788 -
函數(shù)
+關注
關注
3文章
4276瀏覽量
62313 -
預處理
+關注
關注
0文章
33瀏覽量
10467
原文標題:【SoC】C code如何生成二進制可執(zhí)行文件?實例詳解
文章出處:【微信號:小杜的芯片驗證日記,微信公眾號:小杜的芯片驗證日記】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論