學習如何編寫C和C 代碼僅僅是個開始。如果你希望從事系統(tǒng)底層開發(fā)工作,或想深入理解操作系統(tǒng)、編譯器及它們之間內(nèi)在的關聯(lián),成為編程專家,那么就必須充分了解編譯器生成的二進制文件(目標文件、靜態(tài)庫、動態(tài)庫和可執(zhí)行文件)的作用和結構。開源已經(jīng)在許多方面從根本上改變了軟件的原有面貌,越來越多的系統(tǒng)開始采用或集成開源代碼,因此對每位開發(fā)人員來說,學習和理解這些底層技術也變得十分重要。
本書深入淺出地講解了構建過程(編譯、鏈接)中的細節(jié),從多個角度展示了程序與庫文件或代碼的集成方法,提出了面向代碼重用和系統(tǒng)集成的軟件架構設計方法,同時展示了系統(tǒng)開發(fā)過程中疑難問題的解決方案。另外,本書也是一本C和C 二進制文件方面的軟件工程指南,涵蓋中級和專家級程序員所需的各方面內(nèi)容和信息。
通過閱讀本書,你將學到:
*構建過程(編譯和鏈接)與裝載過程的內(nèi)部原理。
*靜態(tài)庫、動態(tài)庫和可執(zhí)行文件的內(nèi)部工作機制。
*面向代碼重用和系統(tǒng)集成的軟件架構設計方法。
*編譯、鏈接與運行時問題的排查技巧。
*在Linux和Windows平臺下利用二進制文件分析工具進行分析的方法。
由于Linux市場份額增加,而且越來越多的人都將Linux作為其編程環(huán)境,這促使開發(fā)人員開始關注Linux編程的相關問題。與在一些封裝良好的平臺上開發(fā)軟件的開發(fā)人員不同,在Windows和Mac平臺上利用IDE和SDK將程序員從一些特定的編程細節(jié)問題中解放出來,Linux開發(fā)人員在日常工作中需要將來自不同項目且編碼風格迥異的代碼組合起來,這需要開發(fā)人員充分理解編譯器、鏈接器的內(nèi)部工作機制和程序裝載機制,以及不同庫的設計細節(jié)和使用方法。
本書將許多零碎的知識點進行匯總,并討論其中有價值的內(nèi)容,再通過一系列精心設計的簡單示例進行驗證。需要注意的是,我并非計算機科學科班出身。在20世紀90年代末至今的數(shù)字革命中,我作為電氣工程師供職于硅谷的一家多媒體行業(yè)高新技術企業(yè),并因此掌握了相關領域的知識。希望本書的主題和內(nèi)容能夠讓更多讀者受益。
【讀者對象】
作為一名軟件設計實踐顧問(雖然很忙,但我還是非常自豪的),我經(jīng)常會與不同專業(yè)背景和資歷的人溝通。我經(jīng)常在不同的辦公環(huán)境中工作,因此接觸了許多開發(fā)人員(絕大多數(shù)來自硅谷),這也讓我更加了解了本書的受眾群體,其中包括以下幾類人群:
第一類受眾群體是來自不同工程領域(電氣工程、機械、機器人技術和系統(tǒng)控制、航天、物理和化學等領域)的C/C 開發(fā)人員,這類人需要在日常工作中通過編程來解決問題。對缺乏正規(guī)計算機科學課程和理論教學的人來說,本書所提供的資料彌足
珍貴。
第二類受眾群體是具有計算機科學教育背景的初級程序員。本書能夠幫助這類人將主修課程中學到的知識具體化,并注重實踐。對資深工程師而言,將第12~14章的內(nèi)容作為手冊查閱也很有益。
第三類受眾群體是操作系統(tǒng)集成和定制的愛好者。理解二進制文件及其內(nèi)部工作機制將有助于在解決問題的過程中掃除障礙。
不管怎么說……
就目前就業(yè)市場的情況而言,我認為(自2005年左右開始)熟悉C/C 語言的復雜性,甚至是算法、數(shù)據(jù)結構和設計模式,對于找到一份好工作是遠遠不夠的。
在開源盛行的今天,專業(yè)開發(fā)人員在日常工作中所編寫的代碼越來越少,取而代之的是將現(xiàn)有代碼集成到項目中。這不僅要求開發(fā)人員能夠讀懂其他人編寫的代碼(使用不同的代碼風格和實踐),還需要了解如何才能以最好的方式將現(xiàn)有的包(絕大多數(shù)以二進制文件/庫和導出頭文件的形式提供)集成到代碼中。
我希望本書能夠兼具教學(對急需這些知識的讀者而言)和快速查詢的功能(對分析C/C 二進制文件相關工作的工程師而言)。
【為何采用Linux進行演示】
選擇Linux并非我個人的偏好。實際上了解我的人都知道,我過去是多么喜歡使用Windows作為開發(fā)環(huán)境(原本這是我首選的設計平臺),原因是Windows平臺具有完善的文檔、完美的支持和符合規(guī)范的認證組件。我設計過許多專業(yè)化軟件(曾為Palm公司設計開發(fā)了Windows Mobile平臺的GraphEdit,其中包含許多復雜的功能,隨后又開發(fā)了多個媒體格式和DSP分析軟件),當時我對Windows技術了如指掌,并感嘆Windows相關技術所帶來的改變。
與此同時, Linux的時代到來了。有關Linux的技術隨處可見,而對開發(fā)人員來說,也必須順應這種趨勢去學習和使用它。
Linux軟件開發(fā)環(huán)境具有開放、透明和簡單明了的特點。在Linux中,我們可以對每個程序設計階段進行控制。同時,Linux提供了完善的文檔,再加上網(wǎng)絡上提供的資源,就可以輕松地使用GNU工具鏈。
實際上,由于Linux C/C 開發(fā)經(jīng)驗可以直接適用于Mac OS平臺的底層開發(fā),因此我最終決定選用Linux/GNU作為本書所涵蓋的主要開發(fā)環(huán)境。
別急!Linux與GNU完全是兩回事
實際上,Linux是內(nèi)核,而GNU中包含了Linux內(nèi)核之上的所有軟件。除了GNU編譯器可以在其他操作系統(tǒng)上使用(比如Windows上的MinGW)以外,在絕大多數(shù)情況下,GNU與Linux的關系其實非常緊密。為了簡單起見,同時為了符合一般開發(fā)人員對開發(fā)場景的認識,特別是為了將Linux與Windows進行對比,本書將GNU與Linux作為一個整體,簡稱為Linux。
【章節(jié)概覽】
第1~5章講解的內(nèi)容主要為后續(xù)內(nèi)容做鋪墊。擁有計算機科學背景的讀者可以快速閱讀這些章節(jié)(幸運的是,這些章節(jié)的內(nèi)容并不長)。實際上,任何計算機科學方面的教科書都會對這些內(nèi)容進行類似介紹,而且內(nèi)容會更為詳細。我個人推薦由Bryant和OHallaron編寫的《深入理解計算機系統(tǒng)》(Computer SystemsA Programmers Perspective)一書,原因是該書對很多主題都進行了非常有條理的梳理和總結。
第6~11章是本書的核心章節(jié)。為求整體內(nèi)容簡潔明了,我花費了相當大的精力,并嘗試使用一些日常生活中常見事物的文字和圖片來闡述那些最為重要的核心概念。如果你不是計算機科學科班出身,那么有必要先理解這些內(nèi)容。其實這些章節(jié)是本書主題的要點。
第12~14章主要概括了一些實踐方面的內(nèi)容,便于讀者快速查找相關的概念。這些章節(jié)針對一些特定平臺的二進制文件分
米蘭·斯特瓦諾維奇( Milan Stevanovic )
資深軟件顧問,在多個學科的工程領域有著豐富的經(jīng)驗。他主要從事Linux和Windows平臺下的多媒體壓縮格式及多媒體框架設計工作。他熱衷于開源,是avxsynth開源項目的主要貢獻者,對C和C 底層技術有著深入的研究。
譯者序
前言
第1章 多任務操作系統(tǒng)基礎 1
1.1 一些有用的抽象概念 1
1.2 存儲器層次結構與緩存策略 2
1.3 虛擬內(nèi)存 3
1.4 虛擬地址 5
1.5 進程的內(nèi)存劃分方案 5
1.6 二進制文件、編譯器、鏈接器與裝載器的作用 6
1.7 小結 7
第2章 程序生命周期階段基礎 8
2.1 基本假設 8
2.2 編寫代碼 9
2.3 編譯階段 11
2.3.1 基本概念 11
2.3.2 相關概念 11
2.3.3 編譯的各個階段 12
2.3.4 目標文件屬性 23
2.3.5 編譯過程的局限性 24
2.4 鏈接 26
2.4.1 鏈接階段 26
2.4.2 鏈接器視角 31
2.5 可執(zhí)行文件屬性 33
2.5.1 各種節(jié)的類型 34
2.5.2 各種符號類型 36
第3章 加載程序執(zhí)行階段 37
3.1 shell的重要性 37
3.2 內(nèi)核的作用 39
3.3 裝載器的作用 39
3.3.1 裝載器視角下的二進制文件(節(jié)與段) 39
3.3.2 程序加載階段 40
3.4 程序執(zhí)行入口點 43
3.4.1 裝載器查找入口點 43
3.4.2 _start()函數(shù)的作用 43
3.4.3 __libc_start_main()函數(shù)的作用 44
3.4.4 棧和調(diào)用慣例 44
第4章 重用概念的作用 46
4.1 靜態(tài)庫 46
4.2 動態(tài)庫 48
4.2.1 動態(tài)庫和共享庫 49
4.2.2 動態(tài)鏈接詳解 51
4.2.3 Windows平臺中動態(tài)鏈接的特點 54
4.2.4 動態(tài)庫的特點 56
4.2.5 應用程序二進制接口 56
4.3 靜態(tài)庫和動態(tài)庫對比 57
4.3.1 導入選擇條件的差異 57
4.3.2 部署難題 59
4.4 一些有用的類比 61
4.5 結論:二進制重用概念所產(chǎn)生的影響 63
第5章 使用靜態(tài)庫 64
5.1 創(chuàng)建靜態(tài)庫 64
5.1.1 創(chuàng)建Linux靜態(tài)庫 64
5.1.2 創(chuàng)建Windows靜態(tài)庫 65
5.2 靜態(tài)庫的使用場合 65
5.3 靜態(tài)庫設計技巧 66
5.3.1 丟失符號可見性和唯一性的可能性 66
5.3.2 靜態(tài)庫使用禁忌 67
5.3.3 靜態(tài)庫鏈接的具體規(guī)則 68
5.3.4 將靜態(tài)庫轉換成動態(tài)庫 68
5.3.5 靜態(tài)庫在64位Linux平臺上的問題 68
第6章 動態(tài)庫的設計:基礎篇 70
6.1 創(chuàng)建動態(tài)庫 70
6.1.1 在Linux中創(chuàng)建動態(tài)庫 70
6.1.2 在Windows中創(chuàng)建動態(tài)庫 72
6.2 設計動態(tài)庫 75
6.2.1 設計二進制接口 75
6.2.2 設計應用程序的二進制接口 79
6.2.3 控制動態(tài)庫符號的可見性 82
6.2.4 完成鏈接需要滿足的條件 94
6.3 動態(tài)鏈接模式 94
6.3.1 加載時動態(tài)鏈接 95
6.3.2 運行時動態(tài)鏈接 95
6.3.3 比較兩種動態(tài)鏈接模式 98
第7章 定位庫文件 99
7.1 典型用例場景 99
7.1.1 開發(fā)用例場景 99
7.1.2 用戶運行時用例場景 100
7.2 構建過程中庫文件的定位規(guī)則 101
7.2.1 Linux構建過程中的庫文件定位規(guī)則 101
7.2.2 Windows構建過程中的庫文件定位規(guī)則 105
7.3 運行時動態(tài)庫文件的定位規(guī)則 109
7.3.1 Linux運行時動態(tài)庫文件的定位規(guī)則 110
7.3.2 Windows運行時動態(tài)庫文件的定位規(guī)則 114
7.4 示例:Linux構建時與運行時的庫文件定位 115
第8章 動態(tài)庫的設計:進階篇 119
8.1 解析內(nèi)存地址的必要性 119
8.2 引用解析中的常見問題 120
8.3 地址轉換引發(fā)的問題 122
8.3.1 情景1:客戶二進制文件需要知道動態(tài)庫符號地址 122
8.3.2 情景2:被裝載的庫不需要知道其自身符號地址 123
8.4 鏈接器-裝載器協(xié)作 124
8.4.1 總體策略 125
8.4.2 具體技術 126
8.4.3 鏈接器重定位提示概述 127
8.5 鏈接器-裝載器協(xié)作實現(xiàn)技術 128
8.5.1 裝載時重定位 129
8.5.2 位置無關代碼 129
第9章 動態(tài)鏈接時的重復符號處理 134
9.1 重復符號的定義 134
9.2 重復符號的默認處理 137
9.3 在動態(tài)庫鏈接過程中處理重復符號 140
9.3.1 處理重復符號問題的一般策略 142
9.3.2 鏈接器解析動態(tài)庫重復符號的模糊算法準則 143
9.4 特定重復名稱案例分析 144
9.4.1 案例1:客戶二進制文件符號與動態(tài)庫ABI函數(shù)沖突 144
9.4.2 案例2:不同動態(tài)庫的ABI符號沖突 147
9.4.3 案例3:動態(tài)庫ABI符號和另一個動態(tài)庫局部符號沖突 151
9.4.4 案例4:兩個未導出的動態(tài)庫符號沖突 153
9.5 小提示:鏈接并不提供任何類型的命名空間繼承 161
第10章 動態(tài)庫的版本控制 162
10.1 主次版本號與向后兼容性 162
10.1.1 主版本號變更 162
10.1.2 次版本號變更 163
10.1.3 修訂版本號 163
10.2 Linux動態(tài)庫版本控制方案 163
10.2.1 基于soname的版本控制方案 163
10.2.2 基于