靜態(tài)鏈接解析:為什么“打包”后可執(zhí)行程序會變大?
0.簡介
前面文章中已經(jīng)對于編譯鏈接整體流程以及目標文件格式進行了解讀,本文將會介紹靜態(tài)鏈接的原理,幫助讀者了解靜態(tài)鏈接流程以及其優(yōu)缺點。
1.靜態(tài)鏈接整體說明
對于靜態(tài)鏈接來說,其核心功能就是將輸入的幾個目標文件整合后輸出一個可執(zhí)行文件(可以理解其解決了前期通過分治方式模塊化編碼的后期整合問題)。既然是整合,其避免不了的就是整合方式的選擇,最簡單的就是各個.o的各個段直接疊加,另外一種就是按段進行整合,相同段進行合并。第一種優(yōu)勢就是簡單,但因為一些地址空間對齊會造成空間浪費;第二種的話稍復(fù)雜一些,但避免了空間的浪費,所以一般都是使用方式二。整合完之后接下來就應(yīng)該考慮如何找到符號地址了,其通過第一步收集到的信息(如段數(shù)據(jù),重定位信息等)來進行符號的解析和重定位。
圖片
接下來本文將使用下面兩個程序來分析靜態(tài)鏈接做的事情:
//main.c
extern int nExt;
int main()
{
int nData = 50;
swap(&nData, &nExt);
return 0;
}//func.c
int nExt = 1;
void swap(int* a, int* b){
int tmp = *a;
*a = *b;
*b = tmp;
}通過執(zhí)行下面命令來分別生成main.o,func.o以及鏈接后的a.out。
#需要-fno-stack-protector關(guān)閉棧保護的檢查,不然需要鏈接libc,影響后續(xù)分析
gcc -c main.c -fno-stack-protector
gcc -c func.c -fno-stack-protector
ld main.o func.o -e main -o a.out我們先來看main.o的整體內(nèi)容objdump -h main.o:
圖片
接下來來看func.o的內(nèi)容objdump -h func.o:
圖片
接下來是a.out的內(nèi)容objdump -h a.out:
圖片
從上面可以看到段合并的效果,main.o的.text段大小為2d,func.o的.text段大小為31,而a.out的.text段大小為5e,剛好為二者相加;而鏈接在段合并的同時還計算了各個部分加載到內(nèi)存時的虛擬地址,也就是VMA,可以看到其和file off的區(qū)別,一個是加載到內(nèi)存的地址,一個是文件中的偏移。接下面我們詳細來看鏈接過程的符號解析和重定義。
2.符號解析和重定位
符號解析我們可以先看一下main.o和func.o以及a.out的符號信息:
readelf -s main.o
圖片
可以看到其都處于未定義的狀態(tài)。
readelf -s func.o
圖片
可以看到其處于全局定義的狀態(tài)。
readelf -s a.out
圖片
可以看到其不再是未定義狀態(tài),那么其地址是怎么來的那?又是如何知道那個地方需要重定位那?首先來看地址是怎么來的,我們來回顧一下a.out的整體信息,其中text段VMA地址是401000,而main.o的.text段大小是2d,合并后大小就是40102d,接下來是func.o的text段,swap函數(shù)相對偏移地址是0,所以應(yīng)該為40102d,我們可以通過objdump -d a.out來看地址,其他符號也是類似的原理。
圖片
接下來我們來看第二個問題,如何知道哪些指令要被調(diào)整那?其依賴于重定位段,我們來看main.o中的重定位段objdump -r main.o,以swap為例可以看到其在text段的22位置需要進行R_X86_64_PLT32類型的重定位。
圖片
接下來來看這個22地址對應(yīng)的指令,objdump -d main.o,可以看到其對應(yīng)的剛好是callq后的地址,也就是重定位的入口。重定位就是根據(jù)這種方式去做的。
圖片
3.鏈接優(yōu)化及靜態(tài)鏈接優(yōu)缺點
在鏈接過程中編譯器會有一些優(yōu)化項:
1)重復(fù)代碼消除:比如實例化模板實例化多份,多個cpp文件中有重復(fù)。
2)靜態(tài)庫的特殊處理:.a其本質(zhì)上就是一堆.o文件的集合,鏈接時可以按需提取,遞歸解析依賴。
3)可以使用命令使得靜態(tài)庫變小:通過分離調(diào)試信息可以使得輸出文件變小:objcopy --strip-debug xxx。
優(yōu)缺點我們可以根據(jù)去原理來進行理解:
1)優(yōu)點:運行獨立,部署簡單;啟動速度快,無需動態(tài)鏈接器。
2)缺點:可執(zhí)行文件體積大;更新維護困難,必須全量編譯替換;不適合動態(tài)擴展場景(如插件)。
4.總結(jié)
本文描述了靜態(tài)鏈接的過程和基本原理,其在輸出文件里整合了所有的.o文件信息,這就是“打包”后變大的原因,接下來的文章將去講述動態(tài)鏈接原理,去理解其如何做到縮小程序體積以及“一次編譯,多程序調(diào)用”。




























