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