程式設計師的自我修養 鏈結 裝載與庫

2021-06-21 12:57:16 字數 4556 閱讀 8690

第一次接觸《程式設計師的自我修養》的時候,的確懷有一種疑惑的態度的。因為潛意識告訴我:在計算機這一行,更強調的是實踐動手,而***修養的顯然不屬於動手操作類,至少不是太適合我的需求。但是,當我以一種隨意的心態翻閱的時候,我才發現我的判斷是多麼的幼稚!

這是一本深入淺出、通俗易懂的權威教材,特別是當我了解到寫書的時候,作者還是在校的研究生?!我就深深的驚呆了。。。好吧,還是那句台詞:人和人的差別怎麼這麼大呢。這時候才明白:難怪書中行文的口氣太接近生活用語了,讀書的過程感覺非常親切,就像與學長聊天。難得的是他們以這種方式講解計算機中底層晦澀的原理,把枯燥的知識講得精確且有趣生動,真正的大師風範啊。廢話不多說,下面進入正題。

全書共分為四個部分,十三的章節:

一.簡介

二.靜態鏈結

三.裝載與動態鏈結

四.庫與執行庫   

在簡介部分

作者解釋了一些計算機領域的概念,如:簡單的hello world程式到底是怎麼執行的;作業系統在幕後為我們做了哪些工作;記憶體不夠了,會有哪些解決方式,諸如此類。作者在這裡只是為了提出問題,引發讀者的思考。畢竟,興趣才是最好的老師。也難怪,我在讀了這裡內容後,就決定:那天下午其他什麼作業都放下,只讀書。乙個下午,讀了一百多頁多內容(個人認為這速度對於計算機放方面多書籍來說已經夠快了,全書共計四百多頁)。他們似乎總能夠知道難點、疑問點在哪,然後一步一步深入,在讀書的過程中,你會發現這其實是乙個與作者互動的過程。有哪麼一種互動的感覺。在以前,我幾乎不怎麼讀書,更喜歡多學習方式是查網路資源。但是讀了這本書以後,我的好多觀點被撼動了,是不是已經錯過了好多好書了呢?不得而知,也不敢想了。。。所以在這裡奉勸大家,不要輕易的給某種事物貼標籤,如讀書。即使看到99.9%的無趣教材,也不要對那僅剩下的那一本放棄希望。

第二部分是靜態鏈結

在這一部分,主要的篇幅放在elf檔案格式的解釋上。linux下的目標檔案、靜態鏈結庫檔案、動態鏈結庫檔案採用的檔案格式都是elf檔案格式標準。對比下,windows下的程式檔案符合的標準是pe檔案。elf檔案有固定的格式控制,有52個位元組長度的檔案頭。它描述了整個檔案的檔案屬性,包括檔案是否可執行、是靜態鏈結還是動態鏈結及入口位址(如果是可執行檔案)、目標硬體、目標作業系統等資訊。而且同一份檔案有兩種檢視,分別對應於鏈結的過程、載入的過程。鏈結的過程,可以用節區的方式來看待檔案內容。整個檔案由不同的節區組成,在檔案頭有乙個字段標識節區表的位置,通過節區表可以方便的找到不同的節區。那麼節區裡面放的是什麼呢?比如:

.text(**段)、

.bss(未初始化的全域性變數或者靜態區域性變數)、

.data(初始化的全域性變數或者區域性靜態變數)

.rodata(唯讀資料段,放一些字串常量等資訊)、

.comment(注釋資訊段)、

.note.gnu-stack(堆疊提示段)

這些程式執行需要的資訊被分們別類放在不同的節區當中,但是載入進記憶體後,則是另外一番景象。載入後,elf的檢視是以一種稱為段表的方式管理的。不同的部分以段為單位進行管理,且有乙個稱為段表的資料結構輔助段的管理。因為節區表的資訊是以一種模型的方式固定產生的,有的程式可能並沒有.bss。所以這時候就需要去掉一些無用的資訊,同時進行一些節區的合併,以產生乙個段。以便os以段的方式進行許可權管理,如:唯讀的內容放在唯讀段、可讀寫的資訊放在另外乙個段(注意,os是以頁為最小粒度進行許可權管理的)。合併節區的好處不言而喻:可以減少記憶體碎片、提高記憶體利用率且方便os進行許可權管理。

第二部分的其他內容所佔篇幅不是很大,還講解了編譯器和鏈結器。編譯源**為可執行檔案的過程分為以下幾個步驟:

1.預處理

2.編譯源**

3.彙編

4.鏈結

1.預處理是解決一些巨集定義的替換等工作,為編譯做準備,對應的gcc操作為:gcc -e xx.c -o xx.i(xx為源檔名)。

2.編譯是將原始碼編譯為組合語言的過程,對應的gcc操作為:gcc -s xx.i -o xx.s。由xx.i 產生xx.s檔案。

3.彙編是將彙編**的檔案匯編為機器語言的過程,對應的gcc操作為:gcc -c xx.s -o xx.o

4.鏈結是將目標檔案鏈結為乙個整的可執行檔案的過程,對應的gcc操作為 gcc xx.o -o xx(xx成為可執行,執行時候可以用 "./xx" 的方式執行)。

當然,平時常用的方式是直截了當的 " gcc xx.c ",做完以上所有過程生成可執行檔案 " a.out "。在這裡,我將重點闡述鏈結的過程。鏈結的過程是不可避免的,比如我們很有可能用到系統函式printf()、scanf()...此時,也許有人會反駁:「我的程式沒用到這些個函式,所以鏈結的過程在這樣的程式中不存在!!」。真的是這回事嗎?!顯然不是嘛...要是那樣的話,我豈不是自己打自己的臉嗎?對不,呵呵。。。其實,在程式執行的時候,main()函式一定不是第乙個被執行的函式,那第乙個被執行的函式是什麼捏?start()函式(不信的話你 " objdump -d a.out " 一下看看唄)。乙個被忽視但又一直存在的函式,它才是第乙個被呼叫執行的函式。main()只不過是它呼叫的子函式而已。好吧,這傢伙是必須存在的,但顯然不在你寫的程式中吧,由此看來,鏈結過程是不是肯定存在?!

好吧,扯的有點多了啊。那麼鏈結過程肯定是有的,究竟鏈結幹了什麼呢?這裡我再舉乙個例子。兩個檔案 " a.c 、 b.c ",在a中有乙個外部變數x,在b中用到a中的這個變數怎麼辦呢?c語言提供支援,方法如下:「extern x 」,標識x在外部申明。這樣編譯的時候就不會報錯了。那麼編譯但不鏈結時候,b中用到a的x,位址如何確定呢?此時,編譯器充分發揮自己的主觀能動性:隨便給它乙個位址就行了。於是b中用到a中的x位址就被規定為乙個數值了,多少呢?不是其它值,就是0(資料位址被定為0,外部函式位址被定為乙個特定的之的值,如:call 0x08048900 ,跳到位址的最低位08)。這些假位址在執行的時候肯定是不行的,所以必須有乙個鏈結的過程來修正這些位址為正確的位址。

第三部分是裝載與動態鏈結

這部分的重點當然是虛擬空間的分配與管理啦。虛擬位址空間有4g大小(32位位址空間)!但是能用的空間才多大呢?linux有3g左右,而windows才2g左右(windows你為什麼總這麼的奇怪...省略一百字,呵呵)。4g的虛擬位址空間,理論上雖然都可以分配給程序,但是作業系統為了方便管理,有些空間被強制用作其它用途。邏輯位址空間(4g),典型的被分為4部分(windows分配方式):核心區(0xc0000000-0xffffffff)、隔離區(0xbfff0000-0xbfffffff)、使用者區(0x00010000-0xbffeffff)、null區(0x00000000-0x0000ffff)。核心區一般1g左右,所有程序必須對映物理的os所在的區域,即物理的1g空間被所有程序共享哦。緊接著核心區的是隔離區,這部分區域是os用來保護自己的黑色地帶,但凡有程序試圖通過緩衝區溢位等方式攻擊時,都將遭到os的阻止(理論上),這片區域就是os的外圍城牆。使用者區才是各程序區分自己的地方,其他區域都是一樣的。使用者區存放的是:**段、資料段、堆疊段...null區用來幹什麼的呢?是用來保證介面的一致性的的吧(僅僅是個人觀點。當然,估計還有其他原因,但是目前還未找到)。怎麼來保證一致性呢?在程式中,你也許會用到malloc()函式來為某個資料結構分配空間。大部分情況下,都可以正常分配,但是總有那麼些個情況它就是分配不成功-_-!!c語言有必要專門為申請空間不成功儲存狀態資訊嗎?如哪種情況導致的不成功。愚認為沒必要,一方面,分配失敗畢竟在概率上只是少數。另外一方面,程式關心的只是成功與否,至於原因,或許並不在意(其實空間不足或許是絕大部分原因,所以也就沒必要判斷分配失敗原因了)。分配不成功時候,os將指標指向null區。使用者只需要判斷是否分配在null區,就可以得到分配是否成功。如果失敗,至於原因如何,c語言未提供支援(分析見上)。

第四部分是庫與執行庫。

這部分則關於動態庫知識的介紹,如api、公共執行庫等,這部分知識是關於規則的介紹及使用,較為瑣碎。其他一些內容如:棧的構造、堆的分配演算法、執行時多執行緒的困擾、crt的改進、中斷與系統呼叫...其中頗為感興趣的是微軟的hot patch prologue技術,詳細內容見《程式設計師的自我修養》的p292頁。本人另外一篇部落格就是介紹這項技術的ppt截圖,感興趣的話可以翻翻看,應該很快就可以找到吧,總共沒幾篇博文。微軟在編譯現代的程式的時候,會人為的加上7個位元組的內容,就這7個位元組的內容可以實現熱修復(不需要宕機就可以實現dll的替換哦)。當然,實現這項技術的基礎是api hook。api hook可以改變函式的執行流程,微軟是這項技術的狂熱愛好者,並在os層面提供支援。這是為什麼呢?我們知道微軟的os有很多版本,應用程式開發時,只能對某一款os提供最完美的支援。如:有很多遊戲都是針對經典的xp開發的,但是在win7上出現相容性問題(很多人都會遇到吧,呵呵)。這時候就體現熱修復的威力了,它可以同時儲存兩個版本的dll。出現相容性問題後,改變dll的版本,當然執行完還需要改回來。這個過程就需要那7個位元組來實現跳轉到目標dll,7個位元組的內容包含兩個跳**2(短跳)+5(長跳)。5個位元組的長跳就可以跳到32位位址空間的任意位置,那為什麼還需要2個位元組的短跳呢?原因很簡單,概率問題。因為實現的時候並不僅僅需要理論,更需要實踐的支援。實踐概率表明:大部分的情況不需要跳轉(即不需要版本相容)。這種情況下,5個位元組的長跳就會被忽略,就是浪費5個位元組的空間。但是通過2+5的方式(短跳的目的位址是長跳的位置,它倆緊挨著),浪費的空間只有兩個位元組。所以出現這種2+5的組合跳轉方式。

《程式設計師的自我修養 鏈結 裝載與庫》 鏈結

對於平常的應用程式開發,我們很少需要關注編譯和鏈結過程,因為通常的開發環境都是流行的整合開發環境 ide 比如visual studio myeclipse等。這樣的ide一般都將編譯和鏈結的過程一步完成,通常將這種編譯和鏈結合併在一起的過程稱為構建,即使使用命令列來編譯乙個源 檔案,簡單的一句 g...

《程式設計師的自我修養 鏈結 裝載與庫》

先不說別的,就單看書名就知道是什麼意思了。作者的意思是想 演員的自我修養 的作者 斯坦尼斯拉夫斯基 致敬。老斯的那本書我沒看過。但我看這本書的意思就是培養程式設計師的基本素質。你說啥叫基本素質?那就是你能夠了解你編寫的程式的任何乙個執行的細節。就拿乙個簡單的 hello world 來說,它是如何執...

Notes 《程式設計師的自我修養 鏈結 裝載與庫》

記錄下每章的知識點,便於以後對著這份知識圖譜,複習和重組。掌握硬體中的核心部件 cpu 記憶體 i o控制晶元 了解cpu核心頻率提公升過程中硬體構架的演進 從bus,到pci isa,再到pci express 搶占式cpu分配方式 cpu由作業系統統一分配,因為cpu分配給每個process的時...