程序的鏈接(一):鏈接的概述

學(xué)習(xí)ELF文件,除了要學(xué)習(xí)其文件格式本身,不可避免要了解其可執(zhí)行文件的鏈接過程。這樣可以為后續(xù)學(xué)習(xí)Linux/Android的hook打下基礎(chǔ)。

這個系列文章,是筆者自己學(xué)習(xí)ELF的筆記的重新整理。

另外,這里推薦幾本相關(guān)書籍,是筆者在學(xué)習(xí)ELF相關(guān)內(nèi)容時讀過的:

  • 《計算機(jī)系統(tǒng)基礎(chǔ)》作者:袁春風(fēng) --> 配套視頻 文章中用到的很多圖來源于課程的PPT
  • 《程序員的自我修養(yǎng)》作者:俞甲子

好,下面進(jìn)入正題:


可執(zhí)行文件的生成

我們先通過一個簡單C程序,回顧一下可執(zhí)行文件的生成過程。


image.png

可執(zhí)行文件的生成過程如下圖:


image.png

如圖,可執(zhí)行文件的生成需要經(jīng)過預(yù)處理、編譯、匯編和鏈接這4個過程。其中:

  • 預(yù)處理的工作:
  • 刪除 #define 并展開宏定義
  • 處理所有的條件預(yù)編譯指令,如 "#if","#ifdef","#endif"等
  • 插入頭文件到 "#include" 處,可以遞歸方式進(jìn)行處理
  • 刪除所有的注釋
  • 添加行號和文件名標(biāo)識,以便編譯時編譯器產(chǎn)生調(diào)試用的行號信息
  • 保留所有 #pragma 編譯指令(編譯器需要用)
    命令示例如下:
  • gcc -E hello.c -o hello.i

經(jīng)過預(yù)編譯處理后,得到的是預(yù)處理文件(如,hello.i),它還是一個可讀的文本文件,但不包含任何宏定義。

  • 編譯的工作

編譯過程就是將預(yù)處理后得到的預(yù)處理文件(如hello.i)進(jìn)行詞法分析、語法分析、語義分析、優(yōu)化后,生成匯編代碼文件。
經(jīng)過編譯后,得到的匯編代碼文件(如,hello.S)還是一個可讀的文本文件。
命令示例如下:

  • gcc -S hello.i -o hello.s
  • gcc -S hello.c -o hello.s
  • 匯編的工作

匯編器將編譯得到的匯編代碼文件轉(zhuǎn)換成機(jī)器指令序列。
匯編的結(jié)果是一個可重定位目標(biāo)文件(如,hello.o)其中包含的是不可讀的二進(jìn)制代碼。
命令示例如下:

  • gcc -c hello.s -o hello.o
  • gcc -c hello.c -o hello.o
  • as hello.s -o hello.o
  • 鏈接的工作

鏈接過程將多個可重定位目標(biāo)文件合并以生成可執(zhí)行目標(biāo)文件。
命令示例如下:

  • gcc -static -o myproc main.o test.o
  • ld -static -o myproc main.o test.o
鏈接的好處

學(xué)習(xí)鏈接之前,可能會有疑問,鏈接有什么好處?
其實鏈接的概念可以追溯到最早程序員用機(jī)器語言編寫程序的時期。
來看下圖,假設(shè)穿孔表示0,未穿孔為1,且 0010代表跳轉(zhuǎn)jmp


image.png

如果要在第5條指令前加入指令,則程序員就得重新計算jmp指令的目標(biāo)地址(重定位),然后重新打孔。由此可以看到,這樣很繁瑣。
后來匯編語言的出現(xiàn)后,程序員用助記符表示操作碼,用符號表示位置,用助記符表示寄存器,如下圖:


image.png

如圖,可見,如果jmp L0 和 sub C之間加入了新的指令,則只要重新確定sub C指令的地址,再填入L0即可。而這個重定位的工作就是在鏈接的過程中完成的。

此外,鏈接還有如下好處:

  • 模塊化
  • 一個程序可以分成很多源程序文件;
  • 可構(gòu)建公共函數(shù)庫,如數(shù)學(xué)庫,標(biāo)準(zhǔn)C庫等。以便代碼重用,提高開發(fā)效率。
  • 效率高
  • 時間上,可分開編譯:只需要重新編譯修改的源程序文件,然后重新鏈接;
  • 空間上,無需包含共享庫所有代碼:源文件中無需包含共享庫函數(shù)的源碼,只要直接調(diào)用即可(如,只要直接調(diào)用printf() 函數(shù),無需包含其源碼),另外,可執(zhí)行文件和運(yùn)行時的內(nèi)存中只需包含所調(diào)用函數(shù)的代碼,而不需要包含整個共享庫。
鏈接的步驟

再來看個C程序的例子:


image.png

通過命令生成可執(zhí)行程序:

gcc -O2 -g -o p main.c swap.c

其生成過程如下圖:


image.png

其中,鏈接的步驟如下

  1. Step-1:符號解析

程序中有定義和引用的符號(包括變量和函數(shù)等)

void swap() {...}  /* 定義符號swap */
swap();              /* 引用符號swap */
int *xp = &x;        /* 定義符號xp,引用符號x */

編譯器將定義的符號存放在符號表中。
符號解析就是將符號的引用和符號的定義建立關(guān)聯(lián)

  1. Step-2:重定位
  • 將多個代碼段與數(shù)據(jù)段分別合并為一個單獨的代碼段和數(shù)據(jù)段
  • 計算每個定義的符號在虛擬地址空間中的絕對地址
  • 將可執(zhí)行文件中的符號引用處的地址修改為重定位后的地址信息

這個步驟可用下圖來簡單描述:


image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容