本書講述構(gòu)建程序的關(guān)鍵工具鏈接器和加載器,內(nèi)容包括鏈接和加載、體系結(jié)構(gòu)、目標(biāo)文件、存儲(chǔ)分配、符號(hào)管理、庫(kù)、重定位、加載和覆蓋、共享庫(kù)、動(dòng)態(tài)鏈接和加載、動(dòng)態(tài)鏈接的共享庫(kù),以及著眼于成熟的現(xiàn)代鏈接器所做的一些變化;并介紹一個(gè)持續(xù)的實(shí)踐項(xiàng)目,即使用Perl語言開發(fā)一個(gè)可用的小鏈接器。本書適合高校計(jì)算機(jī)相關(guān)專業(yè)的學(xué)生、實(shí)習(xí)程序員、語言設(shè)計(jì)者和開發(fā)人員閱讀參考。
自計(jì)算機(jī)出現(xiàn)以來,鏈接器和加載器可以說一直是重要的軟件開發(fā)工具之一。鏈接器和加載器使得我們可以按照模塊來開發(fā)程序,而不必開發(fā)一個(gè)單獨(dú)的大文件。
早在1947年,程序員就開始使用加載器技術(shù)。這是一種很初級(jí)的加載器工作方式,如果程序的若干個(gè)例程(routine)存儲(chǔ)在多個(gè)不同的磁帶上,那么就借助加載器將它們依次加載到內(nèi)存中,并將它們合并、重定位以組合成一個(gè)程序。在20世紀(jì)60年代早期,這些加載器就已經(jīng)發(fā)展得相當(dāng)完善了,甚至具備編輯的功能。由于當(dāng)時(shí)內(nèi)存很貴且容量有限,計(jì)算機(jī)的速度也很慢(以今天的標(biāo)準(zhǔn)),為了充分利用這樣的硬件,這些加載器引入了很多復(fù)雜的特性。例如,使用復(fù)雜的內(nèi)存覆蓋策略解決內(nèi)存不足的問題,將大容量的程序加載到有限的內(nèi)存中;使用鏈接文件重編輯的機(jī)制解決算力不足的問題;使用已鏈接的模塊以節(jié)省重新編譯程序的時(shí)間;等等。
20世紀(jì)70到80年代,鏈接技術(shù)幾乎沒有什么進(jìn)展。鏈接器趨向于更加簡(jiǎn)單。虛擬內(nèi)存技術(shù)將應(yīng)用程序和覆蓋機(jī)制中的大多數(shù)內(nèi)存管理工作都轉(zhuǎn)移給了操作系統(tǒng),同時(shí),計(jì)算機(jī)的處理速度變得越來越快,硬盤容量越來越大,這使得程序員在更新個(gè)別模塊時(shí)也可以重新鏈接整個(gè)程序,而不必僅僅鏈接修改的地方。從20世紀(jì)90年代起,由于增加了諸如動(dòng)態(tài)鏈接共享庫(kù)和C 的諸多現(xiàn)代特性,鏈接器又開始變得復(fù)雜起來。處理器技術(shù)的發(fā)展也促進(jìn)了鏈接器的發(fā)展。例如,具有長(zhǎng)指令字和編譯時(shí)訪存調(diào)度等特性的先進(jìn)處理器架構(gòu)(在IA64處理器中開始出現(xiàn))需要將一些新的特性加入鏈接器中,以確保在鏈接器中生成的代碼可以滿足處理器的一些復(fù)雜需求,從而充分發(fā)揮硬件的新特性。
讀者對(duì)象
本書可供下述幾類讀者閱讀。
學(xué)生:由于鏈接過程看起來似乎非常簡(jiǎn)單,操作的過程也很簡(jiǎn)捷自然,編譯原理和操作系統(tǒng)課程通常對(duì)鏈接和加載的過程缺乏重視。對(duì)于使用Fortran、Pascal、C進(jìn)行簡(jiǎn)單編程的任務(wù),以及不使用內(nèi)存映射或共享庫(kù)的操作系統(tǒng)而言,這么做可能是對(duì)的;但是現(xiàn)在情況不一樣了。C 、Java和其他的面向?qū)ο笳Z言需要更加復(fù)雜的鏈接環(huán)境。使用內(nèi)存映射的可執(zhí)行程序、共享庫(kù)和動(dòng)態(tài)鏈接技術(shù)都會(huì)影響操作系統(tǒng)的很多部分,操作系統(tǒng)的設(shè)計(jì)者如果忽略鏈接問題可能會(huì)給系統(tǒng)帶來很大的麻煩。
程序員:程序員也需要知道鏈接器都做了什么,尤其是對(duì)現(xiàn)代語言而言。C 語言在鏈接器中引入了很多新的特性,如果不能正確理解這一過程,在鏈接大型的C 程序時(shí)就容易產(chǎn)生一些難以診斷的bug。例如,常見的情況是靜態(tài)構(gòu)造函數(shù)沒有按照程序員預(yù)期的順序執(zhí)行。反之,如果能正確合理地使用鏈接器,就能夠發(fā)揮共享庫(kù)和動(dòng)態(tài)鏈接等特性的強(qiáng)大功能,提高程序的靈活性。
編程語言的設(shè)計(jì)者和開發(fā)者。編程語言的設(shè)計(jì)者應(yīng)該在構(gòu)建語言和編譯器時(shí)了解鏈接器應(yīng)該做什么,以及能做什么。在過去的30年中必須借助手工完成的編程細(xì)節(jié),今天在C 中已經(jīng)可以借助鏈接器自動(dòng)處理了。(想象一下,如何能在C語言中實(shí)現(xiàn)和C 中的模板(template)相同的功能;或者,對(duì)于數(shù)百個(gè)C語言源文件組成的工程,如何保證這些文件中的初始化例程可以在主函數(shù)開始之前被正確地執(zhí)行。為了做到這些,程序員需要完成大量工作。)有了功能更強(qiáng)大的鏈接器,未來的語言將更加智能,能夠自動(dòng)完成更多的常規(guī)任務(wù)。由于鏈接器是編譯過程中將整個(gè)程序的代碼放在一起處理的階段,因此鏈接器可以將程序作為一個(gè)整體進(jìn)行變換處理,也可以引入更多的全局程序優(yōu)化功能。
(編寫鏈接器的人員當(dāng)然都需要本書。但是全球所有的鏈接器設(shè)計(jì)者大概只能坐滿一個(gè)房間,而且其中至少有一半被邀請(qǐng)作為本書的審閱人,相信他們已經(jīng)看過本書了。)
章節(jié)內(nèi)容
第1章,鏈接和加載。這一章對(duì)鏈接的過程進(jìn)行了簡(jiǎn)短的歷史回顧,并討論了鏈接過程中的各個(gè)階段。后通過一個(gè)麻雀雖小,五臟俱全的例子來展示鏈接器的工作過程:對(duì)于一個(gè)Hello,world程序,我們分析了以編譯好的目標(biāo)文件為輸入,生成一個(gè)可執(zhí)行程序的過程。
第2章,體系結(jié)構(gòu)相關(guān)問題。這一章從鏈接器設(shè)計(jì)的角度分析了計(jì)算機(jī)體系結(jié)構(gòu)的技術(shù)發(fā)展方向。我們分析了典型的精簡(jiǎn)指令集體系結(jié)構(gòu)SPARC,古老而富有活力的寄存器內(nèi)存體系結(jié)構(gòu)IBM 360/370,以及自成一派的Intel x86體系結(jié)構(gòu)。對(duì)于每種體系結(jié)構(gòu),我們會(huì)討論內(nèi)存架構(gòu)、程序?qū)ぶ芳軜?gòu)和指令中的地址格式等重要因素對(duì)鏈接器的影響。
第3章,目標(biāo)文件。這一章分析了目標(biāo)文件和可執(zhí)行文件的內(nèi)部結(jié)構(gòu)。本章從分析簡(jiǎn)單的MS-DOS的.COM文件開始,進(jìn)而不斷擴(kuò)展到其他復(fù)雜的文件,包括DOS的EXE文件格式、Windows的COFF格式和PE格式(EXE和DLL)、UNIX的a.out格式和ELF格式以及Intel/Microsoft的OMF格式等。
第4章,存儲(chǔ)空間管理。本章介紹了鏈接過程的個(gè)階段,即以段為單位為被鏈接的程序分配存儲(chǔ)空間。我們以一個(gè)實(shí)際使用的鏈接器為例分析了這一過程。
第5章,符號(hào)管理。本章介紹了符號(hào)綁定和解析的過程,這是一個(gè)將符號(hào)解析為機(jī)器地址的過程,程序中的符號(hào)可能在一個(gè)文件中被引用,而它的定義出現(xiàn)在另一個(gè)文件中。
第6章,庫(kù)。本章介紹了關(guān)于目標(biāo)代碼庫(kù)創(chuàng)建和使用的
譯者序
前言
第1章 鏈接和加載1
1.1 鏈接器和加載器做什么1
1.2 從歷史發(fā)展的角度分析地址綁定1
1.3 鏈接與加載3
1.3.1 兩遍鏈接4
1.3.2 目標(biāo)代碼庫(kù)5
1.3.3 重定位和代碼修改6
1.4 編譯驅(qū)動(dòng)器7
1.5 鏈接:一個(gè)真實(shí)的例子9
1.6 練習(xí)12
第2章 體系結(jié)構(gòu)相關(guān)問題13
2.1 應(yīng)用程序二進(jìn)制接口13
2.2 內(nèi)存地址13
2.3 地址構(gòu)成規(guī)則15
2.4 指令格式15
2.5 過程調(diào)用和可尋址性16
2.6 數(shù)據(jù)訪問和指令引用19
2.6.1 IBM 37019
2.6.2 SPARC21
2.6.3 Intel x8623
2.7 分頁(yè)和虛擬內(nèi)存24
2.7.1 程序的地址空間26
2.7.2 文件映射27
2.7.3 共享庫(kù)和程序28
2.7.4 位置無關(guān)代碼28
2.8 Intel 386分段29
2.9 嵌入式體系結(jié)構(gòu)31
2.9.1 怪異的地址空間31
2.9.2 非統(tǒng)一內(nèi)存31
2.9.3 內(nèi)存對(duì)齊31
2.10 練習(xí)32
第3章 目標(biāo)文件35
3.1 目標(biāo)文件中有什么35
3.2 空目標(biāo)文件格式:MS-DOS的.COM文件36
3.3 代碼分段:UNIX的a.out文件36
3.3.1 a.out文件頭37
3.3.2 與虛擬內(nèi)存的交互38
3.4 重定位:MS-DOS的EXE文件41
3.5 符號(hào)和重定位43
3.6 可重定位的a.out格式43
3.6.1 重定位項(xiàng)44
3.6.2 符號(hào)和字符串44
3.6.3 a.out格式小結(jié)45
3.7 UNIX ELF格式45
3.7.1 可重定位文件47
3.7.2 ELF可執(zhí)行文件51
3.7.3 ELF格式小結(jié)52
3.8 IBM 360目標(biāo)文件格式52
3.8.1 ESD記錄53
3.8.2 TXT記錄54
3.8.3 RLD記錄54
3.8.4 END記錄55
3.8.5 小結(jié)55
3.9 微軟的可移植可執(zhí)行文件格式55
3.9.1 PE特有區(qū)段59
3.9.2 運(yùn)行PE可執(zhí)行文件60
3.9.3 PE和COFF61
3.9.4 PE文件小結(jié)61
3.10 Intel/Microsoft的OMF文件格式61
3.10.1 OMF記錄62
3.10.2 OMF文件的細(xì)節(jié)63
3.10.3 OMF格式小結(jié)65
3.11 不同目標(biāo)文件格式的比較65
3.12 練習(xí)66
3.13 項(xiàng)目66
第4章 存儲(chǔ)空間管理69
4.1 段和地址69
4.2 簡(jiǎn)單的存儲(chǔ)布局69
4.3 多種類型的段70
4.4 段與頁(yè)面的對(duì)齊72
4.5 公共塊和其他特殊段72
4.5.1 公共塊72
4.5.2 C 重復(fù)代碼消除73
4.5.3 初始化和終結(jié)75
4.5.4 IBM偽寄存器76
4.5.5 專用鏈接表78
4.5.6 x86的存儲(chǔ)分配策略78
4.6 鏈接器控制腳本79
4.7 嵌入式系統(tǒng)的存儲(chǔ)分配81
4.8 實(shí)際使用的存儲(chǔ)分配策略81
4.8.1 UNIX a.out鏈接器的存儲(chǔ)分配策略81
4.8.2 ELF文件中的存儲(chǔ)分配策略82
4.8.3 Windows鏈接器的存儲(chǔ)分配策略83
4.9 練習(xí)84
4.10 項(xiàng)目85
第5章 符號(hào)管理87
5.1 符號(hào)名綁定和解析87
5.2 符號(hào)表的格式87
5.2.1 模塊表89
5.2.2 全局符號(hào)表90
5.2.3 符號(hào)解析91
5.2.4 特殊符號(hào)91
5.3 名稱修改92
5.3.1 簡(jiǎn)單的C和Fortran名稱修改92
5.3.2 C 類型編碼:類型和范圍93
5.3.3 鏈接時(shí)類型檢查95
5.4 弱外部符號(hào)和其他類型的符號(hào)95
5.5 維護(hù)調(diào)試信息96
5.5.1 行號(hào)信息96
5.5.2 符號(hào)和變量信息96
5.5.3 實(shí)際的問題97
5.6 練習(xí)98
5.7 項(xiàng)目98
第6章 庫(kù)99
6.1 庫(kù)的目的99
6.2 庫(kù)的格式99
6.2.1 使用操作系統(tǒng)99
6.2.2 UNIX和Windows的歸檔文件100
6.2.3 擴(kuò)展到64位102
6.2.4 Intel OMF庫(kù)文件102
6.3 創(chuàng)建庫(kù)文件103
6.4 搜索庫(kù)文件104
6.5 性能問題105
6.6 弱外部符號(hào)105
6.7 練習(xí)106
6.8 項(xiàng)目106
第7章 重定位109
7.1 硬件和軟件重定位109
7.2 鏈接時(shí)重定位和加載時(shí)重定位110
7.3 符號(hào)重定位和段重定位110
7.4 基本的重定位技術(shù)111
7.4.1 指令重定位112
7.4.2 ECOFF段重定位114
7.4.3 ELF重定位115
7.4.4 OMF重定位116
7.5 可重鏈接和可重定位的輸出格式116
7.6 重定位項(xiàng)的其他格式117
7.6.1 以鏈表形式組織的引用117
7.6.2 以位圖形式組織的引用117
7.6.3 特殊段117
7.7 特殊情況的重定位118
7.8 練習(xí)118
7.9 項(xiàng)目119
第8章 加載和覆蓋121
8.1 基本的加載過程121
8.2 帶重定位的基本加載過程122
8.3 位置無關(guān)代碼122
8.3.1 TSS/360的位置無關(guān)代碼123
8.3.2 為每個(gè)例程建立的指針表123
8.3.3 目錄表123
8.3.4 ELF的位置無關(guān)代碼124
8.3.5 位置無關(guān)代碼的開銷和收益126
8.4 自舉加載127
8.5 基于樹狀結(jié)構(gòu)的覆蓋技術(shù)128
8.5.1 定義覆蓋技術(shù)129
8.5.2 覆蓋技術(shù)的實(shí)現(xiàn)131
8.5.3 覆蓋技術(shù)的其他細(xì)節(jié)132
8.5.4 覆蓋技術(shù)小結(jié)132
8.6 練習(xí)13