鏈接器/加載器/庫

    花了一週的時間,學習了鏈接、加載和庫的相關內容。閱讀了《鏈接器和加載器》、《程序員的自我修養-鏈接、加載和庫》這兩本書(這個話題相關的資料很少見,這兩本一個國外一個國內的,算是比較經典),當然,一遍是肯定不夠的,計劃是完成五遍。

    這一些系列的文章,可算是學習筆記,但我想拋開書本,憑藉自己的記憶和理解來寫這部分內容。

    對於磁盤上所存儲的數以千計的可執行文件,普通程序員往往是心存敬畏的。我們不知道這些文件的面貌,只知道他們如同有生命一樣能完成各種各樣的功能,而這些來自於我們從編輯器敲出的字符,至於這些字符最後生成什麼,如何加載到內存並啓動執行,並不清楚。提到可執行文件,腦子裏出現一個詞叫做“二進制”,也是因爲這“二進制”,讓我們有了一種從心底的恐懼。提到庫(動態庫、靜態庫)更是隻知道我們的程序依賴它,因爲沒有它鏈接就不通過,至於怎麼依賴、爲什麼依賴,仍然是迷迷糊糊。

    這篇文章,我想搞清楚的是鏈接、加載這兩個過程都幹了些什麼;我們談之色變的二進制可執行文件裏面到底有些什麼內容,有什麼作用。

    爲什麼要學習這個?首先:好奇心,神祕的東西往往最具有吸引力;其次:能解決我們的問題,我們寫的程序編譯、鏈接爲什麼不通過,從這兒可以找到答案;再次:逆向、破解這些高深的技術要求對這些過程,還有ELF,PE文件格式等熟悉和理解

    一,爲什麼要鏈接?鏈接過程做了什麼事兒?

    只要不是實例性質的小程序,我們的程序往往不可能寫到一個源文件中,一個大型的程序往往分成無數的模塊,更無數的文件,這些模塊和文件單獨編譯生成目標文件,這是編譯過程;這些文件文件並不能單獨執行,因爲他們之間必然存在變量、函數的引用和調用,單獨編譯的時候,這些引用的地址並無法確定

    所以,鏈接的一個重要過程就是爲我們引用的變量和函數找到定義的地方,確定其地址(單獨編譯的時候,由於不能確定,這些地址都處於未定義狀態,以一個特殊的地址作爲替代),讓實際的工作可以執行;此外,當生成最終的可執行文件的時候,我們需要按照文件加載到內存的虛擬地址來進行佈局和地址的指定並進行重定位,32位linux默認加載位置是0x08048000

    其次,各模塊單獨編譯,都會產生各種各樣的稱之爲“段”的一些區域,鏈接過程需要將相同類型的段進行合併,方便與加載到內存執行;

    由於各模塊單獨編譯的時候,模塊內的指令、變量、函數定義的地址都是相對於本模塊來確定,當將段合併之後,各指令、變量等地址需要重新指定,這個過程叫做重定位;這個過程是爲了確定所有undefined的變量、函數,如果不能將所有undefined的符號都正確的解析,就會出現常見的鏈接錯誤

二、加載,爲什麼加載?加載的過程?

    上面鏈接的概況闡述了其目的,讀入併合並各目標對象,地址重定位(伴隨符號解析),最後輸出的是可執行文件。也就是存儲在我們磁盤上的“二進制老虎”。

    現在,我們該讓他執行起來,大家可能覺得這很簡單,我們直接在命令行啓動執行就ok了。真的如此簡單麼?

    現在來看看一個可執行文件加載需要經歷哪些過程。

    存儲在磁盤的可執行文件,要由操作系統將其映射到進程的虛擬地址空間內;系統爲其分配堆棧、堆的環境,填充環境變量,main函數參數,執行.init段的全局內容,然後跳轉到程序main入口

    在加載的過程中,如果程序使用到了動態鏈接庫,還要查找並映射相應的動態庫,並進行動態庫相關的地址重定位,這又涉及到地址無關代碼等內容,涉及到got/plt,動態鏈接重定位等內容

    至此,程序才真正的能夠運行起來,去做他想做的事情,可main退出後,還要幫他打掃戰場,例如.finit段的執行

    可見,鏈接主要是對目標對象文件的一個調整操作,讀入文件,合併各種段,如果有靜態庫,還要將其也算作在內,然後對各文件中的未解析符號進行地址重定位;加載的主要內容是爲進程分配空間(鏈接過程也設計到分配空間,這主要是指的在文件中分配空間,分配各段所佔用的空間),將可執行文件和動態庫映射到進程的虛擬地址空間,然後爲程序的執行準備執行環境,如堆棧初始化等,如果涉及到動態鏈接還要進行加載時重定位(爲了避免簡單的加載時重定位所帶來的不能共享代碼段的問題,還牽扯到地址無關代碼,延遲加載技術等內容)

 

以上大致分析了鏈接和加載的內容,下面說說我們的可執行文件裏面都有些什麼東西

    可執行文件在大家心裏或多或少還是有一個概念,至少我們知道我們的c/c++代碼最終會轉換成二進制指令,肯定是存在於這個文件咯,只是不知道怎麼存儲而已。

    沒錯,二進制裏面的確存儲着我們的指令、當然還有數據,我們的指令存儲於.text段,具有可讀可執行屬性,可共享,不能修改;數據存儲於.data 、 .bss段,具有可寫屬性,各進程不能共享,當然.bss段存儲的是未初始化數據,在文件中並不佔用存儲空間,加載到內存後會佔用存儲空間

    除了這些顯而易見的段之外,我們的可執行文件中還存儲有很多輔助段,比如符號表、字符串表、區段頭部表、重定位表等等,這些段主要是用於進行地址解析之用,也就是用於我們的鏈接過程(其實加載和鏈接都涉及到地址重定位);另外還可能有調試信息等相關的段

    每個undefined的符號,是一個重定位入口,都對應這一個符號表項,符號的字符串表示存儲在字符串表中,未定義的符號會存儲於稱爲導入符號的表中(定義這些符號的表會生成一份叫做導出符號表的東東),在鏈接過程這些導入符號需要被確定,並將相應的地址在符號表中做調整;重定位表中記錄的是需要進行重定位的符號

 

   鏈接和加載的主要內容如上,有不全的地方,以後會逐一進行解釋;比如ELF和PE文件格式,地址重定位方案,這些都是鏈接和加載部分的關鍵點

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章