【1】預處理編譯彙編過程
預處理器(預處理,生成ASCII碼中間文件)——》編譯器(編譯,生成ASCII碼彙編語言文件)——》彙編器(彙編,生成可重定位目標文件)——》鏈接器(鏈接,生成可執行目標文件)
【2】ELF格式的可重定位目標文件格式:
——————
| ELF頭 |
——————
| .text | —— 已經編譯程序的機器代碼
——————
| .rodata | —— 只讀數據
——————
| .data | —— 已經初始化的全局和靜態C變量
——————
| .bss | —— 未初始化的全局和靜態C變量
——————
| .symtab | —— 符號表,存放在程序中定義和引用的函數和全局變量信息。
——————
| .rel.text | ——
——————
| .rel.data | —— 被模塊引用或重定位的所有全局變量的重定位信息
——————
| .debug | ——
——————
| .line | ——
——————
| .strtab | ——
——————
【3】鏈接器如何解析多重定義的全局符號
編譯時,彙編器想鏈接器輸出每個全局符號,分強弱。函數和已經初始化的全局變量是強符號,未初始化的全局變量是若符號。
連接規則1:不允許有多個同名強符號;規則2:一個強符號,多個若符號,選擇強符號;規則3:多個弱符號,任選一個(很容易引起程序運行錯誤)。
【4】靜態鏈接庫
靜態鏈接庫(.a)是一系列可重定位目標文件的集合,有一個頭部用來描述每個成員目標文件的大小和位置。當鏈接器運行的時候,就只會複製 .a 中需要被用到的 .o 模塊到可執行文件中。
如果庫之間不是相互獨立的,存在依賴關係,那麼命令行方式中需要左邊放依賴的庫,右邊放被依賴的庫,也就是越獨立的、越基本的庫越在右邊。
【5】重定位
分兩步組成:
-
重定位節和符號定義。比如,把所有來自輸入模塊的節全部合併成一個聚合節。這一步完成之後,程序中的每條指令和全局變量都有唯一的運行時內存地址了。
-
重定位節中的符號引用。修改代碼節和數據節中對每個符號的引用,使得它們指向正確的運行地址。
【6】可執行目標文件
可分爲以下三段:只讀內存段(代碼段)、讀/寫內存段(數據段)、不加載到內存的符號表和調試信息。
【7】加載器
當在shell中輸入 > ./a.out 的時候,shell會認爲這是一個可執行目標文件,於是便調用一段稱爲“加載器(loader)”的代碼來運行它。
【8】共享庫(動態鏈接庫)
-
首先,在任何給定的文件系統中,所有引用該庫的可執行目標文件共享這個.so,而不是像靜態庫那樣把內容複製和嵌入到可執行文件中。其次,內存中,一個共享庫的.text節的一個副本可以被不同的正在運行的繼承共享。
-
因此靜態庫更新之後,所有使用了該靜態庫的軟件需要重新連接(因爲是內嵌在可執行文件中的),而動態庫更新了之後,允許應用程序在運行時連接新的共享庫。
-
位置無關代碼
【9】linux鏈接器支持打樁機制