2024年3月19日 星期二

*******程式運行的基本概念(預處理、編譯、組譯、鏈結)

 

編譯過程分解

#include <stdio.h>
int main(void)
{
  printf("Hello, world!\n");
  return 0;
}

給定一個普通的輸出 Hello World 的程式
通常我們可以使用GCC 進行編譯
gcc -o hello hello.c 會輸出ELF 格式的執行檔

我們可以使用file hello 命令用於辨識文件類型
$ ./hello 即可執行

事實上,上述的過程可分解為四個步驟,分別是預處理、編譯、組譯和鏈結

預處理

C預處理器參照標頭檔stdio.h的內容,展開macro和驗證prototype,並輸出成一個.i文件。輸出的結果就不會再見到 "#"開頭字樣,預編譯的過程的命令用 -E 表示:
$ gcc -E hello.c -o hello.i

預編譯完成後,替換完標頭檔和macro之後的樣子如下

extern int printf (const char *__restrict __format, ...);
........

int main(void)
{
  printf("Hello, world!\n");
  return 0;
}

上面程式碼擷取了標頭檔文件中相關的部分,省略了stdio.h的其他部分,在這一步驟註釋也會被移除。


※不同的原碼文件,可能會引用一個標頭檔(比如stdio.h),編譯的時候,標頭檔也必須一起編譯,而編譯器會先編譯標頭檔,這是為了確保標頭檔只需編譯一次,不必每次用到的時候都重新編譯。


編譯

編譯過程就是把預處理完的文件進行一系列字彙分析、語法分析、語意分析、最佳化後生成對映的組合語言(hello.s),編譯過程的命令如下:
$gcc -S hello.i -o hello.s

組譯

組譯就是一個將組合語言轉換成機器可以執行的指令,組譯過程我們可以使用組譯器 as 來完成:
as hello.s -o hello.o 或 gcc -c hello.s -o hello.o
會發現其實出來的 hello.o 並無法執行,因為缺少鏈結的過程。

鏈結

在編譯階段並不知道printf的位址,所以暫時會以printf符號名稱代替

main
LDR R1, [R2 + l2]
BAL printf

而在組譯器輸出的對應的組合語言,仍然不知道printf的位址,因此暫時不填入

main:
    EC 00 00 12
    F0 ?? ?? ??

printf 實作於libc.a(C語言標準含市庫的靜態版本),
其地址為0x1000 Linker重新配置(relocate)

0x2000 <main>:
    EC 00 00 12
    F0 00 10 00

鏈結通常是一個比較費解的過程,有靜態鏈結、和動態鏈結,下次會更詳細的分析此過程

參考資料

From Source to Binary: How A Compiler Works: GNU Toolchain
程式設計師的自我修養

https://ithelp.ithome.com.tw/m/articles/10261793

沒有留言:

張貼留言

*******【WSL教學】在Windows上執行Linux + VSCode

安裝WSL: 以管理員身份運行PowerShell來開啟此功能。要做到這一點,您可以在Windows搜索欄中輸入“PowerShell”,然後右鍵單擊Windows PowerShell,選擇“以管理員身份運行”。 在PowerShell中,輸入以下指令以安裝WSL。 wsl -...