深入理解計算機系統》(CSAPP)讀書筆記 —— 第七章 鏈接

      網友投稿 854 2025-04-01

      鏈接( Clinking)是將各種代碼和數據片段收集并組合成為一個單一文件的過程,這個文件可被加載(復制)到內存并執行。鏈接可以執行于編譯時( compile time),也就是在源代碼被翻譯成機器代碼時;也可以執行于加載時( load time),也就是在程序被加載器(lad er)加載到內存并執行時;甚至執行于運行時( run time),也就是由應用程序來執行。在早期的計算機系統中,鏈接是手動執行的。在現代系統中,鏈接是由叫做鏈接器( linker)的程序自動執行的


      文章目錄

      為什么需要了解鏈接器?

      靜態鏈接

      目標文件

      可重定位目標文件

      符號和符號表

      如何解析多重定義的全局符號

      重定位

      加載可執行目標文件

      總結

      為什么需要了解鏈接器?

      理解鏈接器將幫助你構造大型程序。構造大型程序的程序員經常會遇到由于缺少模塊、缺少庫或者不兼容的庫版本引起的鏈接器錯誤。除非你理解鏈接器是如何解析引用、什么是庫以及鏈接器是如何使用庫來解析引用的,否則這類錯誤將令你感到迷惑和挫敗。

      理解鏈接器將幫助你避免一些危險的編程錯誤。Linux鏈接器解析符號引用時所做的決定可以不動聲色地影響你程序的正確性。在默認情況下,錯誤地定義多個全局變量的程序將通過鏈接器,而不產生任何警告信息。由此得到的程序會產生令人迷惑的運行時行為,而且非常難以調試。我們將向你展示這是如何發生的,以及該如何避免它。

      理解鏈接將幫助你理解語言的作用域規則是如何實現的。例如,全局和局部變量之間的區別是什么?當你定義一個具有 static屬性的變量或者函數時,實際到底意味著什么。

      理解鏈接將幫助你理解其他重要的系統概念。鏈接器產生的可執行目標文件在重要的系統功能中扮演著關鍵角色,比如加載和運行程序、虛擬內存、分頁、內存映射。

      理解鏈接將使你能夠利用共享庫。多年以來,鏈接都被認為是相當簡單和無趣的然而,隨著共享庫和動態鏈接在現代操作系統中重要性的日益加強,鏈接成為一個復雜的過程,為掌握它的程序員提供了強大的能力。比如,許多軟件產品在運行時使用共享庫來升級壓縮包裝的( shrink- wrapped)二進制程序。還有,大多數Web服共享庫的動牡動太內

      靜態鏈接

      像 Linux LD程序這樣的靜態鏈接器以一組可重定位目標文件和命令行參數作為輸入,生成一個完全鏈接的、可以加載和運行的可執行目標文件作為輸出。輸入的可重定位目標文件由各種不同的代碼和數據節( section)組成,每一節都是一個連續的字節序列。指令在一節中,初始化了的全局變量在另一節中,而未初始化的變量又在另外節中。

      為了構造可執行文件,鏈接器必須完成兩個主要任務:

      符號解析( symbol resolution)。目標文件定義和引用符號,每個符號對應于一個函數、一個全局變量或一個靜態變量(即C語言中任何以 static屬性聲明的變量)。符號解析的目的是將每個符號引用正好和一個符號定義關聯起來。

      重定位( relocation)。編譯器和匯編器生成從地址0開始的代碼和數據節。鏈接器通過把每個符號定義與一個內存位置關聯起來,從而重定位這些節,然后修改所有對這些符號的引用,使得它們指向這個內存位置。鏈接器使用匯編器產生的重定位條目( relocation entry)的詳細指令,不加甄別地執行這樣的重定位。

      目標文件

      目標文件有三種形式:

      可重定位目標文件。包含二進制代碼和數據,其形式可以在編譯時與其他可重定位目標文件合并起來,創建一個可執行目標文件可執行目標文件。包含二進制代碼和數據,其形式可以被直接復制到內存并執行。

      可執行目標文件。包含二進制代碼和數據,其形式可以被直接復制到內存并執行。

      共享目標文件。一種特殊類型的可重定位目標文件,可以在加載或者運行時被動態地加載進內存并鏈接。

      編譯器和匯編器生成可重定位目標文件(包括共享目標文件)。鏈接器生成可執行目標文件。

      可重定位目標文件

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

      .rodata:只讀數據,比如 printf語句中的格式串和開關語句的跳轉表。

      .data:已初始化的全局和靜態C變量。局部C變量在運行時被保存在棧中,既不出現在,data節中,也不出現在.bss節中

      .bss:未初始化的全局和靜態C變量,以及所有被初始化為0的全局或靜態變量。在目標文件中這個節不占據實際的空間,它僅僅是一個占位符。目標文件格式區分已初始化和未初始化變量是為了空間效率:在目標文件中,未初始化變量不需要占據任何實際的磁盤空間。運行時,在內存中分配這些變量,初始值為0。

      .symtab:一個符號表,它存放在程序中定義和引用的函數和全局變量的信息。一些程序員錯誤地認為必須通過-g選項來編譯一個程序,才能得到符號表信息。實際上,每個可重定位目標文件在. symtab中都有一張符號表(除非程序員特意用 STRIP命令去掉它)。然而,和編譯器中的符號表不同, symtab符號表不包含局部變量的條目。

      .rel.text:一個.text節中位置的列表,當鏈接器把這個目標文件和其他文件組合時,需要修改這些位置。一般而言,任何調用外部函數或者引用全局變量的指令都需要修改。另一方面,調用本地函數的指令則不需要修改。注意,可執行目標文件中并不需要重定位信息,因此通常省略,除非用戶顯式地指示鏈接器包含這些信息。

      .rel.data:被模塊引用或定義的所有全局變量的重定位信息。一般而言,任何已初始化的全局變量,如果它的初始值是一個全局變量地址或者外部定義函數的地址,都需要被修改。

      .debug:一個調試符號表,其條目是程序中定義的局部變量和類型定義,程序中定義和引用的全局變量,以及原始的C源文件。只有以-g選項調用編譯器驅動程序時,才會得到這張表。

      .line:原始C源程序中的行號和.text節中機器指令之間的映射。只有以-g選項調用編譯器驅動程序時,才會得到這張表。

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

      符號和符號表

      每個可重定位目標模塊m都有一個符號表,它包含m定義和引用的符號的信息。在鏈接器的上下文中,有三種不同的符號:

      由模塊m定義并能被其他模塊引用的全局符號。全局鏈接器符號對應于非靜態的C函數和全局變量。

      由其他模塊定義并被模塊m引用的全局符號。這些符號稱為外部符號,對應于在其他模塊中定義的非靜態C函數和全局變量。

      只被模塊m定義和引用的局部符號。它們對應于帶 static屬性的C函數和全局變量。這些符號在模塊m中任何位置都可見,但是不能被其他模塊引用。

      如何解析多重定義的全局符號

      鏈接器的輸入是一組可重定位目標模塊。每個模塊定義一組符號,有些是局部的(只對定義該符號的模塊可見),有些是全局的(對其他模塊也可見)。如果多個模塊定義同名的全局符號,會發生什么呢?下面是 Linux編譯系統采用的方法。

      在編譯時,編譯器向匯編器輸出每個全局符號,或者是強( strong)或者是弱(weak),而匯編器把這個信息隱含地編碼在可重定位目標文件的符號表里。函數和已初始化的全局變量是強符號,未初始化的全局變量是弱符號。

      根據強弱符號的定義, Linux鏈接器使用下面的規則來處理多重定義的符號名

      規則1:不允許有多個同名的強符號。

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

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

      重定位

      一旦鏈接器完成了符號解析這一步,就把代碼中的每個符號引用和正好一個符號定義(即它的一個輸入目標模塊中的一個符號表條目)關聯起來。此時,鏈接器就知道它的輸入目標模塊中的代碼節和數據節的確切大小。現在就可以開始重定位步驟了,在這個步驟中,將合并輸入模塊,并為每個符號分配運行時地址。重定位由兩步組成:

      《深入理解計算機系統》(CSAPP)讀書筆記 —— 第七章 鏈接

      重定位節和符號定義。在這一步中,鏈接器將所有相同類型的節合并為同一類型的新的聚合節。例如,來自所有輸入模塊的.data節被全部合并成一個節,這個節成為輸出的可執行目標文件的.data節。然后,鏈接器將運行時內存地址賦給新的聚合節,賦給輸人模塊定義的每個節,以及賦給輸人模塊定義的每個符號。當這一步完成時,程序中的每條指令和全局變量都有唯一的運行時內存地址了。

      重定位節中的符號引用。在這一步中,鏈接器修改代碼節和數據節中對每個符號的引用,使得它們指向正確的運行時地址。要執行這一步,鏈接器依賴于可重定位目標模塊中稱為重定位條目( relocation entry)的數據結構,我們接下來將會描述這種數據結構。

      加載可執行目標文件

      每個 Linux程序都有一個運行時內存映像,類似于圖7-15中所示。在 Linux x86-64系統中,代碼段總是從地址0x400000處開始,后面是數據段。運行時堆在數據段之后,通過調用malloc庫往上增長。堆后面的區域是為共享模塊保留的。用戶棧總是從最大的合法用戶地址( 2 48 ? 1 2^{48}-1 248?1)開始,向較小內存地址增長。棧上的區域,從地址 2 48 ? 1 2^{48}-1 248?1開始,是為內核( kernel)中的代碼和數據保留的,所謂內核就是操作系統駐留在內存的部分。

      為了簡潔,我們把堆、數據和代碼段畫得彼此相鄰,并且把棧頂放在了最大的合法用戶地址處。實際上,由于.data段有對齊要求,所以代碼段和數據段之間是有間隙的。同時,在分配棧、共享庫和堆段運行時地址的時候,鏈接器還會使用地址空間布局隨機化。雖然每次程序運行時這些區域的地址都會改變,它們的相對位置是不變的。

      當加載器運行時,它創建類似于圖7-15所示的內存映像。在程序頭部表的引導下,加載器將可執行文件的片( chunk)復制到代碼段和數據段。接下來,加載器跳轉到程序的入口點,也就是 _start 函數的地址。這個函數是在系統目標文件ctrl.o中定義的,對所有的C程序都是一樣的。 _start 函數調用系統啟動函數 __libc_start_main,該函數定義在libc.so中。它初始化執行環境,調用用戶層的main函數,處理main函數的返回值,并且在需要的時候把控制返回給內核。

      總結

      鏈接可以在編譯時由靜態編譯器來完成,也可以在加載時和運行時由動態鏈接器來完成。鏈接器處理稱為目標文件的二進制文件,它有3種不同的形式:可重定位的、可執行的和共享的。可重定位的目標文件由靜態鏈接器合并成一個可執行的目標文件,它可以加載到內存中并執行。共享目標文件(共享庫)是在運行時由動態鏈接器鏈接和加載的,或者隱含地在調用程序被加載和開始執行時,或者根據需要在程序調用dlopen庫的函數時。

      鏈接器的兩個主要任務是符號解析和重定位,符號解析將目標文件中的每個全局符號都綁定到一個唯一的定義,而重定位確定每個符號的最終內存地址,并修改對那些目標的引用。

      靜態鏈接器是由像GCC這樣的編譯驅動程序調用的。它們將多個可重定位目標文件合并成一個單獨的可執行目標文件。多個目標文件可以定義相同的符號,而鏈接器用來悄悄地解析這些多重定義的規則可能在用戶程序中引入微妙的錯誤。

      多個目標文件可以被連接到一個單獨的靜態庫中。鏈接器用庫來解析其他目標模塊中的符號引用。許多鏈接器通過從左到右的順序掃描來解析符號引用,這是另一個引起令人迷惑的鏈接時錯誤的來源。

      加載器將可執行文件的內容映射到內存,并運行這個程序。鏈接器還可能生成部分鏈接的可執行目標文件,這樣的文件中有對定義在共享庫中的例程和數據的未解析的引用。在加載時,加載器將部分鏈接的可執行文件映射到內存,然后調用動態鏈接器,它通過加載共享庫和重定位程序中的引用來完成鏈接任務。

      被編譯為位置無關代碼的共享庫可以加載到任何地方,也可以在運行時被多個進程共享。為了加載鏈接和訪問共享庫的函數和數據,應用程序也可以在運行時使用動態鏈接器。

      Linux 匯編語言

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:wps表格怎么在柱形圖中顯示合并數據(wps如何合并圖表)
      下一篇:如何在word文檔加水印
      相關文章
      国外亚洲成AV人片在线观看| 久久亚洲AV无码西西人体| 亚洲综合在线另类色区奇米| 亚洲国产香蕉人人爽成AV片久久| 亚洲a∨无码精品色午夜| WWW国产亚洲精品久久麻豆| 亚洲av无一区二区三区| MM1313亚洲国产精品| 国产亚洲午夜精品| 亚洲国产中文字幕在线观看| 亚洲男女内射在线播放| 亚洲午夜福利精品久久| 最新亚洲成av人免费看| 亚洲精品自在在线观看| 亚洲国产精华液网站w| 亚洲国产精品SSS在线观看AV| 亚洲ⅴ国产v天堂a无码二区| 亚洲免费在线视频| 亚洲精品午夜视频| 亚洲人成网站色在线观看| 国产成人亚洲综合网站不卡| 亚洲乱妇熟女爽到高潮的片| 国产精品无码亚洲精品2021| 亚洲第一页综合图片自拍| 国外亚洲成AV人片在线观看 | 亚洲αv在线精品糸列| 亚洲第一精品在线视频| 亚洲综合一区二区| 亚洲永久在线观看| 激情无码亚洲一区二区三区| 亚洲精品麻豆av| 亚洲精品高清无码视频| 久久亚洲AV成人无码| 亚洲AV成人无码天堂| 亚洲av无码专区在线观看下载| 亚洲av无码国产精品色在线看不卡 | 久久水蜜桃亚洲av无码精品麻豆| 亚洲成人免费电影| 亚洲中文字幕精品久久| 亚洲精品国产高清不卡在线| 久久被窝电影亚洲爽爽爽|