iT邦幫忙

2021 iThome 鐵人賽

DAY 2
0
Arm Platforms

30天從0開始探索嵌入式世界系列 第 2

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

平常我們很少關注編譯和鏈結的過程,因為開發環境都集成開發的環境,比如Visual Studio、Eclipse,這樣的IDE一般都將編譯和鏈結的過程一步完成,因此我們必須深入了解這些被隱藏的過程。


https://ithelp.ithome.com.tw/upload/images/20210915/201418904waQVDB5n8.png


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

給定一個普通的輸出 Hello World 的程式
通常我們可以使用GCC 進行編譯
$ gcc -o hello hello.c 會輸出 ELF 格式的執行檔
$ ./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
程式設計師的自我修養


上一篇
Day1.準備好踏入嵌入式的第一步
下一篇
Day3.編譯器運作流程介紹
系列文
30天從0開始探索嵌入式世界15

尚未有邦友留言

立即登入留言