使用 LDD、Readelf 和 Objdump 的 GCC 鏈接過程

      網友投稿 986 2025-04-04

      鏈接是 gcc 編譯過程的最后階段。


      在鏈接過程中,目標文件被鏈接在一起,所有對外部符號的引用都被解析,最終地址被分配給函數調用等。

      在本文中,我們將主要關注 gcc 鏈接過程的以下幾個方面:

      目標文件以及它們如何鏈接在一起

      代碼重定位

      在閱讀本文之前,請確保您了解 C 程序在成為可執行文件之前必須經過的所有 4 個階段(預處理、編譯、匯編和鏈接)。

      鏈接對象文件

      讓我們通過一個例子來理解這第一步。首先創建以下 main.c 程序。

      $ vi main.c #include extern void func(void); int main(void) { printf("\n Inside main()\n"); func(); return 0; }

      接下來創建以下 func.c 程序。在 main.c 文件中,我們通過關鍵字“extern”聲明了一個函數 func(),并在一個單獨的文件 func.c 中定義了這個函數

      $ vi func.c void func(void) { printf("\n inside func()\n"); }

      為 func.c 創建目標文件,如下所示。這將在當前目錄中創建文件 func.o。

      $ gcc -c func.c

      同樣為 main.c 創建目標文件,如下所示。這將在當前目錄中創建文件 main.o。

      $ gcc -c main.c

      現在執行以下命令來鏈接這兩個目標文件以生成最終的可執行文件。這將在當前目錄中創建文件“main”。

      $ gcc func.o main.o -o main

      當您執行此“主”程序時,您將看到以下輸出。

      $ ./main Inside main() Inside func()

      從上面的輸出中可以清楚地看出,我們能夠成功地將兩個目標文件鏈接到最終的可執行文件中。

      當我們將函數 func() 從 main.c 中分離出來并寫在 func.c 中時,我們得到了什么?

      答案是如果我們也將函數 func() 寫在同一個文件中,這可能無關緊要,但想想我們可能有數千行代碼的非常大的程序。對一行代碼的更改可能會導致重新編譯整個源代碼,這在大多數情況下是不可接受的。因此,非常大的程序有時會分成小塊,這些小塊最終鏈接在一起以生成可執行文件。

      在大多數情況下,適用于 makefile的make 實用程序都會發揮作用,因為該實用程序知道哪些源文件已被更改以及哪些目標文件需要重新編譯。相應源文件未被更改的目標文件按原樣鏈接。這使得編譯過程非常容易和易于管理。

      所以,現在我們明白了,當我們鏈接兩個目標文件 func.o 和 main.o 時,gcc 鏈接器能夠解析對 func() 的函數調用,并且當最終的可執行文件 main 執行時,我們會看到 printf()在正在執行的函數 func() 內部。

      鏈接器在哪里找到函數 printf() 的定義?由于鏈接器沒有給出任何錯誤,這肯定意味著鏈接器找到了 printf() 的定義。printf() 是在 stdio.h 中聲明并定義為標準“C”共享庫 (libc.so) 的一部分的函數

      我們沒有將此共享對象文件鏈接到我們的程序。那么,這是如何工作的呢?使用ldd工具一探究竟,它會打印出每個程序所需的共享庫或命令行指定的共享庫。

      在“main”可執行文件上執行 ldd,它將顯示以下輸出。

      $ ldd main linux-vdso.so.1 => (0x00007fff1c1ff000) libc.so.6 => /lib/libc.so.6 (0x00007f32fa6ad000) /lib64/ld-linux-x86-64.so.2 (0x00007f32faa4f000)

      上面的輸出表明主可執行文件依賴于三個庫。上述輸出中的第二行是“libc.so.6”(標準“C”庫)。這就是 gcc 鏈接器能夠解析對 printf() 的函數調用的方式。

      第一個庫是進行系統調用所必需的,而第三個共享庫是加載可執行文件所需的所有其他共享庫的庫。該庫將出現在每個依賴于任何其他共享庫執行的可執行文件中。

      在鏈接過程中,gcc 內部使用的命令很長,但對于用戶而言,我們只需要編寫即可。

      $ gcc -o

      代碼重定位

      重定位是二進制文件中的條目,它們在鏈接時或運行時被填充。一個典型的重定位條目說:找到'z'的值并將該值放入偏移量'x'的最終可執行文件中

      為此示例創建以下 reloc.c。

      $ vi reloc.c extern void func(void); void func1(void) { func(); }

      在上面的 reloc.c 中,我們聲明了一個尚未提供定義的函數 func(),但我們在 func1() 中調用了該函數。

      從 reloc.c 創建一個目標文件 reloc.o,如下所示。

      $ gcc -c reloc.c -o reloc.o

      使用 readelf 實用程序查看此目標文件中的重定位,如下所示。

      $ readelf --relocs reloc.o Relocation section '.rela.text' at offset 0x510 contains 1 entries: Offset Info Type Sym. Value Sym. Name + Addend 000000000005 000900000002 R_X86_64_PC32 0000000000000000 func - 4 ...

      在我們制作 reloc.o 時,func() 的地址是未知的,因此編譯器會留下 R_X86_64_PC32 類型的重定位。這種重定位間接表示“在最終可執行文件中填充函數 func() 的地址,偏移量為 000000000005”。

      上述重定位對應于目標文件 reloc.o 中的 .text 部分(再次需要了解 ELF 文件的結構才能理解各個部分),因此讓我們使用 objdump 實用程序反匯編 .text 部分:

      $ objdump --disassemble reloc.o reloc.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 : 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: e8 00 00 00 00 callq 9 9: c9 leaveq a: c3 retq

      在上面的輸出中,偏移量“5”(相對于起始地址 0000000000000000 的值為“4”的條目)有 4 個字節等待寫入函數 func() 的地址。

      因此,函數 func() 有一個未決的重定位,當我們將 reloc.o 與包含函數 func() 定義的目標文件或庫鏈接時,它將得到解決。

      讓我們試試看這個重定位是否得到解決。這是另一個提供 func() 定義的 main.c 文件:

      $ vi main.c #include void func(void) // Provides the defination { printf("\n Inside func()\n"); } int main(void) { printf("\n Inside main()\n"); func1(); return 0; }

      從 main.c 創建 main.o 目標文件,如下所示。

      $ gcc -c main.c -o main.o

      使用 LDD、Readelf 和 Objdump 的 GCC 鏈接過程

      將 reloc.o 與 main.o 鏈接并嘗試生成如下所示的可執行文件。

      $ gcc reloc.o main.o -o reloc

      再次執行objdump,看看重定位是否已經解決:

      $ objdump --disassemble reloc > output.txt

      我們重定向了輸出,因為可執行文件包含大量信息,我們不想迷失在標準輸出上。

      查看 output.txt 文件的內容。

      $ vi output.txt ... 0000000000400524 : 400524: 55 push %rbp 400525: 48 89 e5 mov %rsp,%rbp 400528: e8 03 00 00 00 callq 400530 40052d: c9 leaveq 40052e: c3 retq 40052f: 90 nop ...

      在第 4 行中,我們可以清楚地看到我們之前看到的空地址字節現在填充了函數 func() 的地址。

      總而言之,gcc 編譯器鏈接是一片廣闊的海洋,無法在一篇文章中涵蓋。盡管如此,本文還是嘗試剝離鏈接過程的第一層,讓您了解在承諾鏈接不同目標文件以生成可執行文件的 gcc 命令下會發生什么。

      gcc

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

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

      上一篇:使用Notebook保存的鏡像啟動訓練任務
      下一篇:Jupyter Notebook安裝
      相關文章
      久久久久亚洲AV无码专区首| 亚洲日韩中文字幕在线播放| 亚洲中久无码永久在线观看同| 亚洲精品色在线网站| 亚洲人成欧美中文字幕| 中文字幕亚洲综合小综合在线| 亚洲色欲www综合网| 久久亚洲sm情趣捆绑调教 | 亚洲国产成人久久综合碰碰动漫3d| 久久精品九九亚洲精品天堂| 亚洲码国产精品高潮在线| 亚洲精品午夜无码电影网| 亚洲乱码日产一区三区| 日本亚洲欧洲免费天堂午夜看片女人员| 国产亚洲成归v人片在线观看| 亚洲人成无码久久电影网站| 亚洲精品国产自在久久 | 久久精品国产96精品亚洲 | 亚洲人成人无码网www国产| 亚洲Aⅴ无码一区二区二三区软件| 精品久久久久久亚洲中文字幕| 午夜亚洲国产理论片二级港台二级| 亚洲av永久中文无码精品| 校园亚洲春色另类小说合集| 久久久久久久久无码精品亚洲日韩| 久久亚洲AV成人无码国产电影| 精品国产日韩亚洲一区在线| 亚洲欧洲久久av| 亚洲性猛交XXXX| 亚洲AV无码成人网站久久精品大 | 中国亚洲呦女专区| 亚洲av永久无码天堂网| 亚洲AV蜜桃永久无码精品| 国产亚洲精品资在线| 亚洲国产精品无码专区| 亚洲香蕉免费有线视频| 亚洲AV无码一区二区三区在线| 亚洲精品9999久久久久无码| 亚洲国产婷婷综合在线精品 | 91麻豆精品国产自产在线观看亚洲 | 久久亚洲精品AB无码播放|