關於連結的一些總結
1.程式的編譯過程
假設現在有如下兩個c程式檔案,sum.c內容如下:
int sum(int *a, int n){ int i, s = 0; for( i = 0; i < n ; i++){ s += a[i]; } return s; }
主程式main.c的內容如下:
int sum(int *a,int n); int array[2]= {1, 2}; int main(){ int val = sum(array,2 ); return val; }
在linux上執行如下語句:
gcc -Og -o prog main.c sum.c
以main.c為例,程式首先呼叫C前處理器,將main.c翻譯成一個Ascii碼的中介軟體,mian.i,接下來將main.i翻譯成一個組合語言檔案main.s,然後調用匯編器將main.s翻譯可重定位的目標檔案main.o,同理會生成相應的sum.o檔案。最後,執行連結器程式ld,將main.o和sum.o以及一些必要的檔案組合起來,建立一個可執行檔案prog。
在構造可執行檔案的時候,連結器要完成兩個任務:
1、符號解析,符號解析的目的是將每個全域性符號引用與符號定義關聯起來,即可重定位的目標檔案定義了符號,而可執行的目標檔案引用符號,將兩者的符號相互繫結,這裡的符號對應一個函式、一個全域性變數、或者一個靜態變數。
2、重定位,編譯器和彙編器生成從0開始的的程式碼和資料節,連結器將符號定義和記憶體的位置關聯起來來重定位程式碼和資料節,然後修改所有這些對符號的引用,使得它們指向記憶體的位置。
2.一些基礎知識
目標檔案有三種類型:
可重定位的目標檔案:包含二進位制資料和程式碼,其形式可以在編譯時與其他可重定位合併起來,建立一個可執行的目標檔案
可執行的目標檔案:包含二進位制和程式碼,可以直接複製到記憶體並執行
共享目標檔案:一類特殊的可重定位目標檔案,可以在載入或者執行時被動態的載入到記憶體執行。
一個典型的ELF(可執行、可連結格式)的可重定位目標檔案包含幾個重要的部分,如下圖:
.text 已編譯程式的機器程式碼
.data 已初始化的全域性和靜態C變數,區域性C變數的值儲存在棧中。
.bss 未初始化的全域性和靜態C變數,以及所有被初始化為0的靜態或全域性變數。
.sysmtab 一個符號表,存放程式中定義和引用的函式以及全域性或靜態變數,不包含區域性變數的資訊。
對重定位的理解,在重定位,有兩個步驟。
首先重定位節和符號定義,連結器將不同目標檔案所有相同型別的節合併到同一個新的聚合節中,例如,來自所有輸入模組的.data節會被合併到同一個節中。然後連結器將執行時記憶體的地址賦給新的聚合節,賦給輸入模組中定義的那個節和其中定義的每個符號。此時,程式中的每條指令和全域性變數都有一個唯一的執行時記憶體了。
其次是重定位符號的引用,修改資料節和程式碼節中符號的引用,使得他們指向正確的執行時記憶體地址。
處理目標檔案的一些工具,如下:
AR :建立靜態庫,插入、刪除、列出和提取成員。
NM :列出一個目標檔案的符號表中定義的符號。
收藏: 不周山之讀薄 CSAPP
參考:《深入理解計算機系統》