筆記:from Source to Binary: How GNU Toolchain Works
筆記
微言大意:一個單純的描述背後隱含大量的細節和操作
- printf放在哪裡,怎麼被系統呼叫?
- main()函數怎麼被呼叫?
- #include到底做了什麼事?
傳統程式碼從Compile到執行 階段每段的最佳化都和上下階段無關,上面的資訊下面的無法存取
- Compiler 知道原始碼的結構,但是沒有變數和函數怎麼放置的資訊
- Linker 和Compiler相反;知道變數和函數怎麼放置,但是沒有原始碼的結構的資訊
- 一個source code從編譯到產生binary使用到的GNU toolchain
- cpp: 處理巨集產生*.i檔案
- cc1: 產生*.s (組合語言)檔案
- as: 產生*.o 檔案
- collect2: 沒放在PATH內,Ubuntu 12.04 放在/lib/gcc/x86_64-linux-gnu/4.6/collect2。Wrap linker and generate constructor code if needed.
- ld: 吃link script,library, C runtime以及.o,產生binary
- gcc
- GNU compiler collection
- compiler driver
- compile時幫使用者呼叫cpp, cc1, as ....等程式並處理對應的參數
- Intermediate Representation在gcc也有不同的leve
- High Level : GENERIC (Syntax Tree Style IR)
- Middle Level : Gimple (Tree Style IR, SSA form)
- Low Level : RTL (List Style IR, Register Based)
- SSA: Static Single Assignment
- 每次的assign expression將變數加上版本號碼
- a = b + 1; a++ ; b++; 變成
- a1 = b1 + 1; a2++; b2++;
- 如果變數assign expression是條件式的結果,使用Φ函數從集合中選擇
- if (cond) { a = 1} else { a = 2 }變成
- if (cond1) { a1 = 1} else { a2 = 2 } => a3 = Φ (a1, a2)
- SSA的優點 (pass,每個應該是大哉問)
- 每次的assign expression將變數加上版本號碼
- GCC最佳化
- 將程式碼轉成語法樹
- 把語法樹使用binary operation方式呈現,接著轉成SSA並針對SSA最佳化
- 將SSA轉成RTL,使用和平臺相關的最佳化如pipeline最佳化、針對平臺最佳化過的函數、找出可以簡化的機械碼片段取換等
- Peephole optimization
- Other Built-in Functions Provided by GCC
- Instruction scheduling
- as的任務
- 產生obj 檔案
- 讓linker resolve symbol: ex: test.c 呼叫了printf("hello\n");,printf在哪裡?
- 產生symbol table
- 產生relocation table
- symbol,用來協助linker找到或連結變數或是函數正確的位址
- 先標記檔案內的變數或函數是internal的還是external的
- binary分類
- obj file
- 不需要處理symbol和relocation問題
- 執行檔: linker要把需要的function或變數從*.o檔案和在一起,或是動態使用symbol。
- static link
- 要處理symbol和relocation問題
- dynamic link
- 要處理symbol和relocation問題
- 要處理動態呼叫library的問題
- static link
- shared library * 要處理symbol和relocation問題
- 要處理動態呼叫library的問題
- 使用Position independent code
- 載入時才確認address
- global variable使用base + offset方式
- 會有overhead
- obj file
- dynamic linker
- 優點
- 不同process可共用dynamic library 可共用的部份如程式碼(應該要thread safe?),想像不同process呼叫printf,而printf程式碼只需要一份放在shared library-> 節省記憶體
- 系統只放一份shared library -> 節省空間
- 更新library不需要重邊執行檔
- Load on demand
- 執行檔dynamic Link 步驟
- Compile time
- ld -> 從shared linker取得relocation和symbol資訊,並將資訊放到binary執行檔內
- runtime:
- 使用者執行程式
- 系統從程式中取得dynamic linker路徑。
- dynamic linker會把shared library相關的檔案如libc.so的.data和.text relocate到記憶體中
- resolve相關的symbol
- 開始執行程式
- Compile time
- Unix世界中,dynamic linker屬於C library
- 優點
$ ls /lib64/ld-linux-x86-64.so.2 -
ld-linux-x86-64.so.2 -> /lib/x86_64-linux-gnu/ld-2.15.so*
$ file /lib/x86_64-linux-gnu/ld-2.15.so
/lib/x86_64-linux-gnu/ld-2.15.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=0x930bb48366d22fbd8e002ef1c09f3061a506b43e, stripped