《深入理解電腦系統》第七章 連結 讀書筆記

來源:互聯網
上載者:User

標籤:

第七章連結

         連結:將各種代碼和資料部分收集起來並組合成為一個單一檔案的過程,這個檔案可被載入。串連可執行於編譯時間、載入時、運行時。由叫連結器的程式執行。

         連結器使得分離編譯成為可能。使得可以把 大程式分解成小模組,利於管理。

         理解連結器將協助你構築大程型序;避免一些危險的編程錯誤;協助你理解語言的範圍規則是如何?的;協助你理解其他重要的系統概念;使你能夠利用共用庫。

 

7.1 編譯器驅動程式

驅動程式的工作:1、運行C前置處理器,將C來源程式(.c)翻譯成一個ASCⅡ碼中間檔案(.i);2、運行C編譯器,將.i檔案翻譯成彙編檔案(.s);3、運行彙編器,翻譯成可重定位目標檔案(.o);4、運行連結器程式,將各個部分組合,建立可執行程式。

 

7.2靜態連結

         連結器的兩任務:1、符號解析:目標檔案定義和引用符號。將每個符號引用剛好和一;個符號定義聯絡起來;2、重定位:連結器通過把每個符號定義與一個儲存空間位置聯絡起來,然後修改所有對這些符號的引用。

         基本事實:目標檔案純粹是位元組塊的集合,

 

7.3目標檔案

三種形式:

l  可重定位目標檔案:包含二進位代碼和資料,可與其他可重定位目標檔案合并起來,建立可執行目標檔案。

l  可執行目標檔案: 包含二進位代碼和資料,其形式可以被直接拷貝到儲存空間並行。

l  共用目標檔案:特殊類型的可重定位目標檔案,可以在載入或者運行時被動態載入到儲存空間並連結。

編譯器和彙編器產生可重定位目標檔案,連結器產生可執行程式。

 

7.4可重定位目標檔案

         ELF可重定位目標檔案格式,

 

. text: 已編譯器的機器代碼。

.rodata: 唯讀資料,比如printf 語句中的格式串開關語句的跳轉表

.data: 已初始化的全域C 變數

.bss: 未初始化的全域C 變數。在目標檔案中這個節不佔據實際的空間,它僅僅是一個預留位置。

.symtab: 一個符號哀,它存放在程式中定義和引用典型的ELF 可重定位目標檔案的函數和全域變數的資訊。

. rel . text :一個.text 節中位置的列表,當連結器把這個目標檔案和其他檔案結合時,需要修改這些位置。

.rel.data: 被模組引用或定義的任何全域變數的重定位資訊。

.debug: 一個偵錯符號表,其條目是程式中定義的局部變數和類型定義, 程式中定義和引用的全域變數,以及原始的C 源檔案。

.line: 原始C 來源程式中的行號和.text 節中機器指令之間的映射。

.strtab: 一個字串表,其內容包括.symtab 和.debug 節中的符號表,以及節頭部中的節名字。

 

7.5符號和符號表

         每個可重定位目標模組m 都有一個符號表,它包含m 所定義和引用的符號的資訊。在連結器的上下文中,有三種不同的符號:

·由m 定義並能被其他模組引用的全域符號。全域連結器符號對應於非靜態C以及被定義為不帶C static 屬性的全域變數。

·由其他模組定義並被模組m 引用的全域符號。這些符號稱為外部符號,對應於定義在其他模組中的C 函數和變數。

·只被模組m 定義和引用的本地符號。有的本地連結器符號對應於帶static 屬性的C 函數和全域變數。這些符號在模組m 中隨處可見,但是不能被其他模組引用。目標檔案中對應於模組m 的節和相應的源檔案的名字也能獲得本地符號。

 

name 是字串表中的位元組位移,指向符號的以null 結尾的字串名字.value 是符號的地址。對於可重定位的模組來說, value 是距定義目標的節的起始位置的位移。

 

每個符號都和目標檔案的某個節相關聯,由section 欄位表示,該欄位也是一個到節頭部表的索引.有三個特殊的偽節(pseudo section) ,它們在節頭部表中是沒有條目的: ABS 代表不該被重定位的符號; UNDEF 代表未定義的符號,也就是在本目標模組中引用,但是卻在其他地方定義的符號; COMMON 表示還未被分配位置的未初始化的資料目標.

 

7.6符號解析

 

連結器解析符號引用的方法是將每個引用與它輸入的可重定位目標檔案的符號表中的一個確定的符號定義聯絡起來.編譯器只允許每個模組中每個本地符號只有一個定義。編譯器還確保靜態本地變數,也會有本地連結器符號,擁有唯一的名字。

 

根據強弱符號的定義, Unix 連結器使用下面的規則來處理多重定義的符號:

·規則1 :不允許有多個強符號。

·規則2 :如果有一個強符號和多個弱符號,那麼選擇強符號。

·規則3 :如果有多個弱符號,那麼從這些弱符號中任意選擇一個。

 

所有的編譯系統都提供一種機制,將所有相關的目標模組打包成為一個單獨的檔案,稱為靜態庫(static ),它可以用做連結器的輸入。將所有的標準C 畫數都放在一個單獨的可重定位目標模組中(如libc中),應用程式員可以把這個模組連結到他們的可執行檔中:

unix> gcc main.c /usr/lib/libc.o

 

一個很大的缺點是系統中每個可執行檔現在都包含著一份標準函數集合的完全拷貝,這對磁碟空間是很大的浪費。我們可以通過為每個標準函數建立一個獨立的可重定位檔案,把它們存放在一個為大家都知道的目錄中來解決其中的一些問題.

unix> gcc main.c /usr/lib/printf.o /usr/lib/scanf.o ...

 

在Unix 系統中,靜態庫以一種稱為存檔(archive) 的特殊檔案格式存放在磁碟中。。封存檔案是一組串連起來的可重定位目標檔案的集合,有一個頭部用來描述每個成員目標檔案的大小和位置。封存檔案名由尾碼.a 標識。為了建立這個可執行檔,我們要編譯和連結輸入檔案main.o

unix> gcc -02 -c main2 .c

unix> gcc -static -0 p2 main2 .o ./libvector.a

 

7.7重定位

重定位由兩步組成:

·重定位節和符號定義。在這一步中,連結器將所有相同類型的節合并為同一類型的新的彙總節。·重定位節中的符號引用。在這一步中,連結器修改代碼節和資料節中對每個符號的引用,使得它們指向正確的運行時地址。

當彙編器產生一個目標模組時,它並不知道資料和代碼最終將存放在儲存空間中的什麼位置。它也不知道這個模組引用的任何外部定義的函數或者全域變數的位置。代碼的重定位條目放在.rel.text 中。已初始化資料的重定位
條目放在.rel.data 中。

中兩種最基本的重定位類型:

R_386_PC32: 重定位一個使用32 位PC 相對位址的引用.一個PC 相對位址就是距程式計數器(PC) 的當前運行時值的位移量。當CPU 執行一條使用PC 相對定址的指令時,它就將在指令中編碼的32 位值上加上PC 的當前運行時值,得到有效地址。R_386_32: 重定位一個使用32 位絕對位址的引用。通過絕對定址, CPU 直接使用在指令中編碼的32 位值作為有效地址,不需要進一步修改。
7.8 可執行目標檔案

執行目標檔案的格式類似於可重定位目標檔案的格式。ELF 頭部描述檔案的總體格式。它還包括程式的進入點(Centry point) ,也就是當程式運行時要執行的第一條指令的地址。

.init 節定義了一個小函數,叫做init ,程式的初始化代碼會調用它。因為可執行檔是完全連結的(已被重定位了),所以它不再需要.rel。

7.9 載入可執行目標檔案

要運行可執行目標檔案p ,可以在Unix 外殼的命令列中輸入它的名字:

unix> . /p

因為p 不是一個內建的外殼命令,所以外殼會認為p 是一個可執行目標檔案,通過調用某個駐留在儲存空間中稱為載入器(loader) 的作業系統代碼來運行它.載入器將可執行目標檔案中的代碼和資料從磁碟拷貝到儲存空間中,然後通過跳轉到程式的第一條指令或進入點來運行該程式。這個將程式拷貝到儲存空間並啟動並執行過程叫做加截(loading) 。

在32 位Linu系統中,程式碼片段總是從地址Ox08048000 處開始。資料區段是在接下來的下一個4KB 對齊的地址處。運行時堆在讀/寫段之後接下來的第一個4KB 對齊的地址處,並通過調用malloc 庫往上增長。

 

7.10動態連結共事庫

共用庫是致力於解決靜態庫缺陷的一個現代創新產物。共用庫是一個目標模
塊,在運行時,可以載入到任意的儲存空間地址,並和一個在儲存空間中的程式連結起來。這個過程稱為動態連結(dynamic linking) ,是由一個叫做動態連結器(dynamic linker) 的程式來執行的。

共用庫也稱為共用目標 ,在Unix 系統中通常用. s。尾碼來表示。微軟的作業系統大量地利用了共用庫,它們稱為DLL (動態連結程式庫)。

首先,在任何給定的檔案系統中,對於一個庫只有一個.5。檔案。所有引用該庫的可執行目標檔案共用這個.5。檔案中的代碼和資料,而不是像靜態庫的內容那樣被拷貝和嵌人到引用它們的可執行檔檔案中。其次,在儲存中,一個共用庫的.text 節的一個副本可以被不同的正在啟動並執行進程共用.

給連結器如下特殊指令:

unix> gcc -sbared -ÎPIC -0 libvector.so addvec.c multvec.c//-fPIC 選項指示編譯器生戚與位置無關的代碼 -5hared 選項指示連結器建立一個享的目標檔案。unix> gcc -0‘p2 main2 .c ./libvector.so//這樣就建立了一個可執行目標檔案p2 ,而此檔案的形式使得它在運行時

然後,動態連結器通過執行下面的重定位完成連結任務:

.重定位libc.5 。的文本和資料到某個儲存空間段。·重定位lib飛Tector.5 。的文本和資料到另一個儲存空間段。·重定位p2 中所有對由libc.5。和libvector.5。定義的符號的引用。
7.11 從應用程式中載入和連結共用庫
#include <dlfcn.h>void *dlopen(const char *filename , int flag);返回若成功則為指向控制代碼的指標,若出錯則為NULL 。

dlopen 函數載入和連結共用庫filename 。用以前帶RTLD GLOBAL 選項開啟的庫解析filename 中的外部符號。如果當前可執行檔是帶rdynamic 選項編譯的,那麼對符號解析而言,它的全域符號也是可用的。

#include <dlfcn.h>void *dlsym(void *handle , char *symbol);返回若成功為指向符號的指標,若出錯則為NULL 。

dlsym 函數的輸入是一個指向前面已經開啟共用庫的控制代碼和一個符號名字,如果該符號存在,就返回符號的地址,否則返回NULL 。

#include <dlfcn.h>int dlclose (void *handle);返回:若成功為0 ,若出錯則為1.

如果沒有其他共用庫正在使用這個共用庫, dlclose 函數就卸載該共用庫。

#include <dlfcn.h>const char *dlerror(void);返回如采前面對dlopen 、dlsym 或dlclose 的調用失敗,則為錯誤訊息,如果前面的調用成功,則為NULL.

dlerror 函數返回一個字串,它描述的是調用dlopen 、dlsym 或者dlclose 函數時發生的最近的錯誤,如果沒有錯誤發生,就返回NULL 。

7.12 與位置無關的代碼

多個進程是如何共用程式的一個拷貝的呢?一種方法是給每個共用庫分配一個事先預備的專用的地址空間片(chunk) ,然後要求載入器總是在這個地址載入共用庫。一種更好的方法是編譯庫代碼,使得不需要連結器修改庫代碼就可以在任何地址載入和執行這些代碼。這樣的代碼叫做與位置無關的代碼.

無論我們在儲存空間中的何處載入一個目標模組〈包括共用目標模組),資料區段總是被分配成緊隨在程式碼片段後面。為了運用這個事實,編譯器在資料區段開始的地方建立了一個表,叫做全域位移量.

7.13 處理目標檔案的工具
.AR: 建立靜態庫,插入、刪除、列出和提取成員。.STRINGS: 列也一個???標檔案中所有可列印的字串..STRIP: 從目標檔案中刪除符號表資訊。.NM: 列出一個目標檔案的符號表中定義的符號。.SIZE: 列出目標檔案中節的名字和大小。.READELF: 顯示一個目標檔案的完整結構,包括且F 頭中編碼的所有資訊。包含SIZE 和NM 的功能。.OBJDUMP :所有二進位工具之母。能夠顯示一個目標檔案中所有的資訊。它最大的作用是反組譯碼.text 節中的二進位指令。.LDD: 列出一個可執行檔在運行時所需要的共用庫。

《深入理解電腦系統》第七章 連結 讀書筆記

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.