日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

《深入理解计算机系统》第七章 链接

發布時間:2025/3/15 windows 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《深入理解计算机系统》第七章 链接 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

《深入理解計算機系統》第七章 鏈接

鏈接是將各種代碼和數據部分收集起來并組合成為一個單一文件的過程,這個文件可被加載(貨被拷貝)到存儲器并執行。

鏈接的時機

  • 編譯時,也就是在源代碼被翻譯成機器代碼時
  • 加載時,也就是在程序被加載器加載到存儲器并執行時
  • 運行時,由應用程序執行

鏈接器使分離編譯稱為可能。

鏈接是將各種代碼和數據部分收集起來并組合成為一個單一文件的過程,這個文件可被加載(或拷貝)到存儲器并執行。

鏈接可以執行于編譯時,也就是在源代碼被翻譯成機器代碼時;也可以執行于加載時,也就是在程序被加載器加載到存儲器并執行時;甚至執行于運行時,由應用程序來執行。

在早期的計算機系統中,鏈接是手動執行的。在現代系統中,鏈接是由叫鏈接器的自動執行的。

7.1 編譯器驅動程序

?????? 大多數編譯系統提供編譯驅動程序,它代表用戶在需要時調用語言預處理器、編譯器、匯編器和鏈接器。

例子:?函數main()調用swap交換外部全局數據buf中的兩個元素。這個例子貫穿全文,分析鏈接是如何工作的。

1 2 3 4 5 6 7 8 9 10 11 12 /* $begin main */ /* main.c */ void?swap(); int?buf[2] = {1, 2}; int?main() { ????swap(); ????return?0; } /* $end main */

  

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /* $begin swap */ /* swap.c */ extern?int?buf[]; int?*bufp0 = &buf[0]; int?*bufp1; void?swap() { ????int?temp; ????bufp1 = &buf[1]; ????temp = *bufp0; ????*bufp0 = *bufp1; ????*bufp1 = temp; } /* $end swap */

7.2 靜態鏈接

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

為了構造可執行文件,鏈接器必須完成兩個任務:符號解析,重定位
  • 符號解析?目標文件定義和引用符號。符號解析的目的是將每個符號引用剛好和一個符號定義聯系起來。
  • 重定位?????編譯器和匯編器生成從地址0開始的餓代碼和數據節。鏈接器通過把每個符號定義與一個存儲器位置聯系起來,然后修改所有對這些符號的引用,使得它們指向這個存儲器位置,從而重定位這些節。

???????鏈接器的一些基本事實:目標文件純粹是字節塊的集合。這些塊中,有些包含程序代碼,有些則包含程序數據,而其他的則包含指導鏈接器和加載器的數據結構。鏈接器將這些塊連接起來,確定被連接塊的運行時位置,并且修改代碼和數據塊中的各種位置。鏈接器和匯編器已經完成了大部分工作。

目標文件純粹是字節快的集合。這些塊中,有些包含程序代碼,有些則包含程序數據,而其他的則包括指導鏈接器和加載器的數據結構。鏈接器將這些塊鏈接起來,確定被連接塊的運行時位置,并且修改代碼和數據塊中的各種位置。鏈接器對目標機器了解甚少。產生目標文件的編譯器和匯編器已經完成了大部分工作。

7.3 目標文件

1.三種形式

  • 可重定位目標文件。包含二進制代碼和數據,其形式可以在編譯時與其他可重定位目標文件合并起來,創建一個可執行目標文件。
  • 可執行目標文件。包含二進制代碼和數據,其形式可以被直接拷貝到存儲器并執行。
  • 共享目標文件。一種特殊類型的可重定位目標文件,可以在加載或者運行地被動態地加載到存儲器并鏈接。
編譯器和匯編器生成可重定位目標文件(包括共享目標文件)。鏈接器生成可執行目標文件。從技術上來說,一個目標模塊就是一個字節序列,而一個目標文件就是一個存放在磁盤文件中的目標模塊。

????? 編譯器和匯編器生成可重定位目標文件(包括共享目標文件)。鏈接器生成可執行目標文件。從技術上來說,一個目標模塊就是一個字節序列,而一個目標文件就是一個存放在磁盤文件中的目標模塊。

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

? ? ? 各個系統之間,目標文件格式都不相同。

7.4 可重定位目標文件

??????一個典型的ELF可重定位目標文件的格式P451。ELF頭(ELF header)以一個16字節的序列開始,這個序列描述了生成該文件的系統的字的大小和字節順序。ELF頭剩下的部分包含幫助鏈接器語法分析和解釋目標文件的信息。其中包括ELF頭的大小、目標文件的類型(如可重定位、可執行或是共享的)、機器類型(如IA32)、節頭部表的文件偏移,以及節頭部表中的條目大小和數量。不同的節的位置和大小是由節頭部表描述的,其中目標文件中每個節都有一個固定大小的條目。

????? 夾在ELF頭和節頭部表之間的都是借。一個典型的ELF可重定位目標文件包含下面幾個節:

  • .text???????? 已編譯程序的機器代碼
  • .rodata???? 只讀數據
  • .data???????? 已初始化的全局C變量。局部C變量在運行時保存在棧中,既不出現在.data節中 ,也不出現在.bss節中。
  • .bass??????? 未初始化的全局C變量。在目標文件中這個節不占據實際的空間,它僅僅是一個占位符。目標文件格式區分初始化和未初始化變量是為了空間效率:在目標文件中,未初始化變量不需要占據任何實際的磁盤空間。
  • .symtab???一個符號表,它存放在程序中定義和引用的函數和全局變量的信息。每個可重定位目標文件在.symtab中都有一張符號表 。
  • .rel.text????一個.text節中位置的列表,當鏈接器吧這個目標文件和其他文件結合時,需要修改這些位置。一般而言,任何調用外部函數或引用全局變量的指令都需要修改。另一方面,調用本地函數的指令則不需要修改。注意,可執行目標文件中并不需要重定位信息,因此通常省略,除非用戶顯示第指示鏈接器包含這些信息。
  • .rel.data????被模塊引用或定義的任何全局變量的重定位信息。一般而言,任何已初始化的全局變量,如果它的初始值是一個全局變量地址或者外部定義函數的地址,都需要被修改。
  • .debug??????一個調試符號表,其條目是程序總定義的局部變量和類型定義,程序中定義和引用的 全局變量,以及原始的C源文件。
  • .line???????? 原始C源文件中的行號和.text節中機器指令之間的映射。
  • .strtab???? 一個字符串表,其內容包括.symtab和.debug節中的符號表,以及節頭部中的節名字。

7.5 符號和符號表

每個可重定位目標模塊m都有一個符號表,包含m所定義和引用的符號的信息。 在鏈接器的上下文中,三種不同的符號: 1.由m定義并能被其他模塊引用的全局符號。全局鏈接器對應于非靜態的C函數以及被定義為Cstatic 屬性的全局變量。 2.由其他模塊定義并被模塊m以引用的全局符號——外部符號,對應于定義在其他模塊中的C函數和變量 3.只被模塊m定義和引用的本地符號。

在鏈接器的上下文中,有三種不同的符號:

  • 由m定義并能被其他模塊引用的全局符號
  • 由其他模塊定義并被模塊m引用的全局符號
  • 只被模塊m引用的本地符號

7.6 符號解析

7.6.1 鏈接器如何解析多重定義的全局符號?????

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

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

  • 規則1:不允許有多個強符號。
  • 規則2:如果有一個強符號和多個弱符號,那么選擇強符號。
  • 規則3:如果有多個弱符號,那么從這些弱符號中任意選擇一個。

7.6.2 與靜態庫鏈接

??????? 在Unix系統中,靜態庫以一種稱為存檔的特殊文件格式村凡在磁盤中。存檔文件是一組連接起來的可重定位目標文件的集合,有一個頭部用來描述每個成員目標文件的大小和位置。存檔文件名由后綴.a標識。

7.6.3 鏈接器如何使用靜態庫來解析引用 ??

??????? 在符號解析的階段,鏈接器從左到右按照它們在編譯器驅動程序命令行上出現的相同順序來掃描可重定位目標文件和存檔文件。在這次掃描中,鏈接器維持一個可重定位目標文件的集合E(這個集合中的文件會被合并起來形成可執行文件),一個未解析的符號(即引用了但是尚未定義的符號)集合U,以及一個在前面輸入文件中已定義的符號集合D。初始時,E、U和D都是空的。

1.對于命令行上的每個輸入文件f,鏈接器會判斷f是一個目標文件還是一個存檔文件。如果f是一個目標文件,那么鏈接器吧f添加到E, 修改U和D來反映f中的符號定義和引用,并繼續下一個輸入文件。

2.如果f是一個存檔文件,那么鏈接器就嘗試匹配U中未解析的符號和由存檔文件成員定義的符號。如果某個存檔文件成員m,定義了一個符號來解析U中的一個引用,那么就將m加到E中,并且鏈接器修改U和D來反映m中的符號定義和引用。對存檔文件中所有的成員目標文件都反復進行這個過程,直到U和D都不再發生變化。在此時,任何不包含在E中的目標文件都簡單地被丟棄,而鏈接器將繼續處理下一個輸入文件。

3.如果當鏈接器完成對命令行上輸入文件的掃描后,U是非空的,那么鏈接器就好輸出一個錯誤并終止。否則,它會合并和重定位E中的目標文件,從而構建輸出的可執行文件。

???????這種算法會導致一些令人困擾的鏈接時錯誤,因為命令行上的庫和目標文件的順序非常重要。在命令行中,如果定義一個符號的庫出現在引用這個符號的目標文件之前,那么引用就不能被解析,鏈接會失敗。關于庫的一般準則是將它們放在命令行的 結尾。

????? 另一方面,如果庫不是相互獨立的,那么它們必須排序,使得對于每個被存檔文件的成員外部引用的符號s,在命令行中至少有一個s的定義實在對s的引用之后的。

??????如果需要滿足依賴需求,可以在命令行上重復庫。

7.7 重定位

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

? ? ? 重定位有兩步組成:

1.重定位節和符號定義。在這一步中,鏈接器將所有相同類型的節合并為同一類型的新的聚合節。然后,鏈接器將運行時存儲器地址賦給新的聚合節,賦給輸入模塊定義的每個節,以及賦給輸入模塊定義的每個符號。當這一步完成時,程序中的每個指令和全局變量都有唯一的運行時存儲器地址了。

2.重定位節中的符號引用。在這一步中,鏈接器修改代碼節和數據節中對每個符號的引用,使得它們指向正確的運行時地址。為了執行這一步,鏈接器依賴于稱為重定位條目的可重定位目標模塊中的數據結構。

7.7.1 重定位條目

?????? 當匯編器生成一個目標模塊時,它并不知道數據和代碼最終存放在存儲器中的什么位置。它也不知道這個模塊引用的任何外部定義的函數或者全局變量的位置。所以,無論何時匯編器遇到對最終位置位置的目標引用,它就會生成一個重定位條目,告訴鏈接器在將目標文件合并成可執行文件時如何修改這個引用。代碼的重定位條目放在.rel.text中。? 已初始化的數據的重定位條目放在.rel.data中。

?????? ELF定義了11種不同的重定位類型。我們只關心其中兩種最基本的重定位類型:

  • R_386_PC32? 重定位一個使用32位PC相對地址的引用。
  • R_386_32?????? 重定位一個使用32位絕對地址的引用。

7.7.2 重定位符號引用?

7.8 可執行目標文件?

???? ?可執行目標文件的格式類似于可重定位目標文件的格式。ELF頭部描述文件的總體格式。它還包括程序的入口點,也就是當程序運行時要執行的第一條指令的地址。.text 、.rodata和.data 節和可重定位目標文件中的節是相似的,除了這些節已經被重定位到它們最終的運行時存儲器地址以外。.init節定義了一個小函數,叫做_init,程序的初始化代碼會調用它。因為可執行文件是完全鏈接的(已被重定位了),所以它不再需要.rel節。

???? ELF可執行文件被設計得很容易加載到存儲器,可執行文件的連續的片被映射到連續的存儲器段。段頭部表描述了這種映射關系。

7.9 加載可執行目標文件

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

1 unix> ./p

? ? ? ? 因為p不是一個內置的外殼命令,所以外殼會認為p是一個可執行目標文件,通過調用某個駐留在存儲器中的稱為加載器(loader)的操作系統代碼來運行它。任何Unix程序都可以通過調用execve函數來調用加載器。加載器將可執行目標文件中的代碼和數據從磁盤拷貝到存儲器中,然后通過跳轉到程序的第一條指令或入口點來運行該程序。這個將程序拷貝到存儲器并運行的過程叫做加載。

??????? 每個Unix程序都有一個運行時存儲器映像。例如:在32位Linux系統中,代碼段總是從地址(0x8048000)處開始。數據段是在接下來的下一個4KB對齊的地址處。運行時堆在讀/寫段之后接下來的第一個4KB對齊的地址處,并童工調用malloc庫往上增長。還有一個段是為共享庫保留的。用戶??偸菑淖畲蟮暮戏ㄓ脩舻刂烽_始,向下增長的(向低存儲器地方向增長)。從棧的上部開始的段是為操作系統駐留存儲器的部分(也就是內核)的代碼和數據保留的。

?????? 在可執行文件中段頭部表的指導下,加載器將可執行文件的相關內容拷貝到代碼和數據段。接下來,加載器跳轉到程序的入口點,也就是符號_start的地址。在_start地址處的啟動代碼是在目標文件ctrl.o中定義的,對所有的C程序都是一樣的。在從.text和.init節中調用了初始化例程后,啟動代碼調用atexti例程,這個程序附加了一系列在應用程序正常中止時應該調用的程序。exit函數運行atexit注冊的函數,然后通過調用_exit將控制返回給操作系統。接著,啟動代碼調用應用程序的main程序,它會開始執行我們的C代碼。在應用程序返回之后,啟動代碼調用_exit程序,它將控制返回給操作系統。

? ? ? ?加載的工作流程:

? ? ? ?UNIX系統中的每個程序都運行在一個進程上下文中,有自己的虛擬地址空間。當外殼運行一個程序時,父外殼進程生成一個子進程,它是父進程的一個復制品。子進程通過execve系統調用啟動加載器。加載器刪除子進程現有的虛擬存儲器段,并創建一組新的代碼、數據、堆和棧段、新的棧和堆段被初始化為零。通過將虛擬地址空間中的頁映射到可執行文件的頁大小的片,新的代碼和數據段被初始化為可執行文件的內容。最后,加載器跳轉到_start地址,它最終會調用應用程序的main函數。除了一些頭部信息,在加載過程中沒有任何從磁盤到存儲器的數據拷貝。直到CPU應用一個被映射的虛擬頁才會進行拷貝,此時,操作系統利用它的頁面調度機制自動將頁面從磁盤傳送到存儲器。

7.10 動態鏈接共享庫

?????? 共享庫是致力與解決靜態庫缺陷的一個現代創新產物。共享庫是一個目標模塊,在運行時,可以加載到任意的存儲器地址,并加一個在存儲器中的程序鏈接起來。這個過程稱為動態鏈接,是由一個叫做動態鏈接器的程序來執行的。共享庫也稱為共享目標,在Unix系統中通常用.so后綴來表示。

7.11 從應用程序中加載和鏈接共享庫

動態鏈接在現實中的例子:

  • 分發軟件
  • 構建高性能Web服務器

7.12 與位置無關的代碼(PIC)

PIC數據引用

PIC函數調用

7.13 處理目標文件的工具

  • AR:創建靜態庫,插入、刪除、列出和提取成員。
  • STRINGS:列出一個目標文件中所有可打印的字符串。
  • STRIP:從目標文件中刪除符號表信息。
  • NM:列出一個目標文件中符號表定義的符號。
  • SIZE:列出目標文件中節的名字和大小。
  • READELF:能夠顯示一個目標文件的所有信息。
  • OBJDUMP:反匯編
  • LDD:列出一個可執行文件運行時需要的共享庫。

?

每個可重定位目標模塊m都有一個符號表,包含m所定義和引用的符號的信息。 在鏈接器的上下文中,三種不同的符號: 1.由m定義并能被其他模塊引用的全局符號。全局鏈接器對應于非靜態的C函數以及被定義為Cstatic 屬性的全局變量。 2.由其他模塊定義并被模塊m以引用的全局符號——外部符號,對應于定義在其他模塊中的C函數和變量 3.只被模塊m定義和引用的本地符號。

轉載于:https://www.cnblogs.com/huangbobo/p/5371410.html

總結

以上是生活随笔為你收集整理的《深入理解计算机系统》第七章 链接的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。