www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當前位置:首頁 > 公眾號精選 > 程序喵大人
[導(dǎo)讀]在前面的文章中程序喵已經(jīng)介紹過靜態(tài)鏈接的原理,這篇文章我們來解密動態(tài)鏈接。 老規(guī)矩,先拋出幾個問題: 為什么要進行動態(tài)鏈接? 如何進行動態(tài)鏈接? 什么是地址無關(guān)代碼技術(shù)? 什么是延遲綁定技術(shù)? 如何在程序運行過程中進行顯式鏈接? 為什么要進行動態(tài)


在前面的文章中程序喵已經(jīng)介紹過靜態(tài)鏈接的原理,這篇文章我們來解密動態(tài)鏈接。

老規(guī)矩,先拋出幾個問題:


  • 為什么要進行動態(tài)鏈接?

  • 如何進行動態(tài)鏈接?

  • 什么是地址無關(guān)代碼技術(shù)?

  • 什么是延遲綁定技術(shù)?

  • 如何在程序運行過程中進行顯式鏈接?


為什么要進行動態(tài)鏈接?

因為靜態(tài)鏈接有缺點:

  1. 浪費內(nèi)存和磁盤空間:如下圖,

Program1和Program2分別包含Program1.o和Program2.o兩個模塊,他們都需要Lib.o模塊。靜態(tài)鏈接情況下,兩個目標文件都用到Lib.o這個模塊,所以它們同時在鏈接輸出的可執(zhí)行文件Program1和program2中有副本,同時運行時,Lib.o在磁盤和內(nèi)存中有兩份副本,當系統(tǒng)中有大量類似Lib.o的多個程序共享目標文件時,就會浪費很大空間。

  1. 靜態(tài)鏈接對程序的更新部署和發(fā)布很不友好:假如一個模塊依賴20個模塊,當20個模塊其中有一個模塊需要更新時,需要將所有的模塊都找出來重新編譯出一個可執(zhí)行程序才可以更新成功,每次更新任何一個模塊,用戶就需要重新獲得一個非常大的程序,程序如果使用靜態(tài)鏈接,那么通過網(wǎng)絡(luò)來更新程序也會非常不便,一旦程序任何位置有一個小改動,都會導(dǎo)致整個程序重新下載。

為了解決靜態(tài)鏈接的缺點,所以引入了動態(tài)鏈接,動態(tài)鏈接的內(nèi)存分布如圖,



多個程序依賴同一個共享目標文件,這個共享目標文件在磁盤和內(nèi)存中僅有一份,不會產(chǎn)生副本,簡單來講就是不像靜態(tài)鏈接一樣對那些組成程序的目標文件進行鏈接,等到程序要運行時才進行鏈接,把鏈接這個過程推遲到運行時才執(zhí)行。動態(tài)鏈接的方式使得開發(fā)過程中各個模塊更加獨立,耦合度更小,便于不同的開發(fā)者和開發(fā)組織之間獨立的進行開發(fā)和測試。

如何進行動態(tài)鏈接?

看如下代碼:

// lib.c#include <stdio.h>
void func(int i) { printf("func %d \n", i);}

// Program.cvoid func(int i);
int main() { func(1); return 0;}

編譯運行過程如下:

$ gcc -fPIC -shared -o lib.so lib.c$ gcc -o test Program.c ./lib.so$ ./test$ func 1

通過-fPIC和-shared可以生成一個動態(tài)鏈接庫,再鏈接到可執(zhí)行程序就可以正常運行。

通過readelf命令可以查看動態(tài)鏈接庫的segment信息:

~/test$ readelf -l lib.so
Elf file type is DYN (Shared object file)Entry point 0x530There are 7 program headers, starting at offset 64
Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x00000000000006e4 0x00000000000006e4 R E 0x200000 LOAD 0x0000000000000e10 0x0000000000200e10 0x0000000000200e10 0x0000000000000218 0x0000000000000220 RW 0x200000 DYNAMIC 0x0000000000000e20 0x0000000000200e20 0x0000000000200e20 0x00000000000001c0 0x00000000000001c0 RW 0x8 NOTE 0x00000000000001c8 0x00000000000001c8 0x00000000000001c8 0x0000000000000024 0x0000000000000024 R 0x4 GNU_EH_FRAME 0x0000000000000644 0x0000000000000644 0x0000000000000644 0x0000000000000024 0x0000000000000024 R 0x4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 0x10 GNU_RELRO 0x0000000000000e10 0x0000000000200e10 0x0000000000200e10 0x00000000000001f0 0x00000000000001f0 R 0x1
Section to Segment mapping: Segment Sections... 00 .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame 01 .init_array .fini_array .dynamic .got .got.plt .data .bss 02 .dynamic 03 .note.gnu.build-id 04 .eh_frame_hdr 05 06 .init_array .fini_array .dynamic .got

可以看見動態(tài)鏈接模塊的裝載地址從0開始,0是無效地址,它的裝載地址會在程序運行時再確定,在編譯時是不確定的。

改一下程序:

// Program.c#include <stdio.h>void func(int i);
int main() { func(1); sleep(-1); return 0;}

運行讀取maps信息:

~/test$ ./test &[1] 126~/test$ func 1cat /proc/126/maps7ff2c59f0000-7ff2c5bd7000 r-xp 00000000 00:00 516391 /lib/x86_64-linux-gnu/libc-2.27.so7ff2c5bd7000-7ff2c5be0000 ---p 001e7000 00:00 516391 /lib/x86_64-linux-gnu/libc-2.27.so7ff2c5be0000-7ff2c5dd7000 ---p 000001f0 00:00 516391 /lib/x86_64-linux-gnu/libc-2.27.so7ff2c5dd7000-7ff2c5ddb000 r--p 001e7000 00:00 516391 /lib/x86_64-linux-gnu/libc-2.27.so7ff2c5ddb000-7ff2c5ddd000 rw-p 001eb000 00:00 516391 /lib/x86_64-linux-gnu/libc-2.27.so7ff2c5ddd000-7ff2c5de1000 rw-p 00000000 00:00 07ff2c5df0000-7ff2c5df1000 r-xp 00000000 00:00 189022 /mnt/d/wzq/wzq/util/test/lib.so7ff2c5df1000-7ff2c5df2000 ---p 00001000 00:00 189022 /mnt/d/wzq/wzq/util/test/lib.so7ff2c5df2000-7ff2c5ff0000 ---p 00000002 00:00 189022 /mnt/d/wzq/wzq/util/test/lib.so7ff2c5ff0000-7ff2c5ff1000 r--p 00000000 00:00 189022 /mnt/d/wzq/wzq/util/test/lib.so7ff2c5ff1000-7ff2c5ff2000 rw-p 00001000 00:00 189022 /mnt/d/wzq/wzq/util/test/lib.so7ff2c6000000-7ff2c6026000 r-xp 00000000 00:00 516353 /lib/x86_64-linux-gnu/ld-2.27.so7ff2c6026000-7ff2c6027000 r-xp 00026000 00:00 516353 /lib/x86_64-linux-gnu/ld-2.27.so7ff2c6227000-7ff2c6228000 r--p 00027000 00:00 516353 /lib/x86_64-linux-gnu/ld-2.27.so7ff2c6228000-7ff2c6229000 rw-p 00028000 00:00 516353 /lib/x86_64-linux-gnu/ld-2.27.so7ff2c6229000-7ff2c622a000 rw-p 00000000 00:00 07ff2c62e0000-7ff2c62e3000 rw-p 00000000 00:00 07ff2c62f0000-7ff2c62f2000 rw-p 00000000 00:00 07ff2c6400000-7ff2c6401000 r-xp 00000000 00:00 189023 /mnt/d/wzq/wzq/util/test/test7ff2c6600000-7ff2c6601000 r--p 00000000 00:00 189023 /mnt/d/wzq/wzq/util/test/test7ff2c6601000-7ff2c6602000 rw-p 00001000 00:00 189023 /mnt/d/wzq/wzq/util/test/test7fffee96f000-7fffee990000 rw-p 00000000 00:00 0 [heap]7ffff6417000-7ffff6c17000 rw-p 00000000 00:00 0 [stack]7ffff729d000-7ffff729e000 r-xp 00000000 00:00 0 [vdso]

可以看到,整個進程虛擬地址空間中,多出了幾個文件的映射,lib.so和test一樣,它們都是被操作系統(tǒng)用同樣的方法映射到進程的虛擬地址空間,只是它們占據(jù)的虛擬地址和長度不同,從maps里可以看見里面還有l(wèi)ibc-2.27.so,這是C語言運行庫,還有一個ld-2.27.so,這是Linux下的動態(tài)鏈接器,動態(tài)鏈接器和普通共享對象一樣被映射到進程的地址空間,在系統(tǒng)開始運行test前,會先把控制權(quán)交給動態(tài)鏈接器,動態(tài)鏈接器完成所有的動態(tài)鏈接工作后會把控制權(quán)交給test,然后執(zhí)行test程序。

當鏈接器將Program.o鏈接成可執(zhí)行文件時,這時候鏈接器必須確定目標文件中所引用的func函數(shù)的性質(zhì),如果是一個定義于其它靜態(tài)目標文件中的函數(shù),那么鏈接器將會按照靜態(tài)鏈接的規(guī)則,將Program.o的func函數(shù)地址進行重定位,如果func是一個定義在某個動態(tài)鏈接共享對象中的函數(shù),那么鏈接器將會將這個符號的引用標記為一個動態(tài)鏈接的符號,不對它進行地址重定位,將這個過程留在裝載時再進行。

動態(tài)鏈接的方式

動態(tài)鏈接有兩種方式:裝載時重定位和地址無關(guān)代碼技術(shù)。

裝載時重定位:在鏈接時對所有絕對地址的引用不作重定位,而把這一步推遲到裝載時完成,也叫基址重置,每個指令和數(shù)據(jù)相當于模塊裝載地址是固定的,系統(tǒng)會分配足夠大的空間給裝載模塊,當裝載地址確定后,那指令和數(shù)據(jù)地址自然也就確定了。然而動態(tài)鏈接模塊被裝載映射到虛擬空間,指令被重定位后對于每個進程來講是不同的,沒有辦法做到同一份指令被多個進程共享,所以指令對不同的進程來說有不同的副本,還是空間浪費,怎么解決這個問題?使用fPIC方法。

地址無關(guān)代碼:指令部分無法在多個進程之間共享,不能節(jié)省內(nèi)存,所以引入了地址無關(guān)代碼的技術(shù)。我們平時編程過程中可能都見過-fPIC的編譯選項,這個就代表使用了地址無關(guān)代碼技術(shù)來實現(xiàn)真正的動態(tài)鏈接?;舅枷刖褪鞘褂肎OT(全局偏移表),這是一個指向變量或函數(shù)地址的指針數(shù)組,當指令要訪問變量或者調(diào)用函數(shù)時,會去GOT中找到相應(yīng)的地址進行間接跳轉(zhuǎn)訪問,每個變量或函數(shù)都對應(yīng)一個地址,鏈接器在裝載模塊的時候會查找每個變量和函數(shù)的地址,然后填充GOT中的各個項,確保每個指針指向的地址正確。GOT放在數(shù)據(jù)段,所以它可以在模塊裝載時被修改,并且每個進程都可以有獨立的副本,相互不受影響。

tips

-fpic和-fPIC的區(qū)別:它們都是地址無關(guān)代碼技術(shù),-fpic產(chǎn)生的代碼相對較小較快,但是在某些平臺會有些限制,所以大多數(shù)情況下都是用-fPIC來產(chǎn)生地址無關(guān)代碼。

-fPIC和-fPIE的區(qū)別:一個作用于共享對象,一個作用于可執(zhí)行文件,一個以地址無關(guān)方式編譯的可執(zhí)行文件被稱作地址無關(guān)可執(zhí)行文件。

-fpie和-fPIE的區(qū)別:類似于-fpic和-fPIC的區(qū)別

延遲綁定技術(shù)

在程序剛啟動時動態(tài)鏈接器會尋找并裝載所需要的共享對象,然后進行符號地址尋址重定位等工作,這些工作會減慢程序的啟動速度,如果解決?

使用PLT延遲綁定技術(shù),這里會單獨有一個叫.PLT的段,ELF將 GOT拆分成兩個表.GOT和.GOT.PLT,其中.GOT用來保存全局變量的引用地址,.GOT.PLT用來保存外部函數(shù)的地址,每個外部函數(shù)在PLT中都有一個對應(yīng)項,在初始化時不會綁定,而是在函數(shù)第一次被用到時才進行綁定,將函數(shù)真實地址與對應(yīng)表項進行綁定,之后就可以進行間接跳轉(zhuǎn)。

顯式運行時鏈接

支持動態(tài)鏈接的系統(tǒng)往往都支持顯式運行時鏈接,也叫運行時加載,讓程序自己在運行時控制加載的模塊,在需要時加載需要的模塊,在不需要時將其卸載。這種運行時加載方式使得程序的模塊組織變得很靈活,可以用來實現(xiàn)一些諸如插件、驅(qū)動等功能。

通過這四個API可以進行顯式運行時鏈接:

dlopen():打開動態(tài)鏈接庫dlsym():查找符號dlerror():錯誤處理dlclose():關(guān)閉動態(tài)鏈接庫

參考這段使用代碼:

#include <stdio.h>#include <dlfcn.h>
int main() { void *handle; void (*f)(int); char *error;
handle = dlopen("./lib.so", RTLD_NOW); if (handle == NULL) { printf("handle null \n"); return -1; } f = dlsym(handle, "func"); do { if ((error = dlerror()) != NULL) { printf("error\n"); break; } f(100); } while (0); dlclose(handle);
return 0;}

編譯運行:

$ gcc -o test program.c -ldl$ ./testfunc 100

總結(jié)




為什么要進行動態(tài)鏈接?為了解決靜態(tài)鏈接浪費空間和更新困難的缺點。

動態(tài)鏈接的方式?裝載時重定位和地址無關(guān)代碼技術(shù)。

地址無關(guān)代碼技術(shù)原理?通過GOT段實現(xiàn)間接跳轉(zhuǎn)。

延遲加載技術(shù)原理?對外部函數(shù)符號通過PLT段實現(xiàn)延遲綁定及間接跳轉(zhuǎn)。

如果進行顯式運行時鏈接?通過<dlfcn.h>頭文件中的四個函數(shù),代碼如上。




參考資料

https://www.ibm.com/developerworks/cn/linux/l-dynlink/index.html
http://chuquan.me/2018/06/03/linking-static-linking-dynamic-linking/
https://www.cnblogs.com/tracylee/archive/2012/10/15/2723816.html
《程序員的自我修養(yǎng):鏈接裝載與庫》


c++11新特性,所有知識點都在這了!

你的c++團隊還在禁用異常處理嗎?

內(nèi)存對齊之格式修訂版

c++11新特性之智能指針

gcc a.c 究竟經(jīng)歷了什么?

談?wù)劤绦蜴溄蛹胺侄文切┦?/span>

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
關(guān)閉
關(guān)閉