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

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


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

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


  • 為什么要進(jìn)行動(dòng)態(tài)鏈接?

  • 如何進(jìn)行動(dòng)態(tài)鏈接?

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

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

  • 如何在程序運(yùn)行過(guò)程中進(jìn)行顯式鏈接?


為什么要進(jìn)行動(dòng)態(tài)鏈接?

因?yàn)殪o態(tài)鏈接有缺點(diǎn):

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

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

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

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



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

如何進(jìn)行動(dòng)態(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;}

編譯運(yùn)行過(guò)程如下:

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

通過(guò)-fPIC和-shared可以生成一個(gè)動(dòng)態(tài)鏈接庫(kù),再鏈接到可執(zhí)行程序就可以正常運(yùn)行。

通過(guò)readelf命令可以查看動(dòng)態(tài)鏈接庫(kù)的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

可以看見動(dòng)態(tài)鏈接模塊的裝載地址從0開始,0是無(wú)效地址,它的裝載地址會(huì)在程序運(yùn)行時(shí)再確定,在編譯時(shí)是不確定的。

改一下程序:

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

運(yùn)行讀取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]

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

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

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

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

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

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

tips

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

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

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

延遲綁定技術(shù)

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

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

顯式運(yùn)行時(shí)鏈接

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

通過(guò)這四個(gè)API可以進(jìn)行顯式運(yùn)行時(shí)鏈接:

dlopen():打開動(dòng)態(tài)鏈接庫(kù)dlsym():查找符號(hào)dlerror():錯(cuò)誤處理dlclose():關(guān)閉動(dòng)態(tài)鏈接庫(kù)

參考這段使用代碼:

#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;}

編譯運(yùn)行:

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

總結(jié)




為什么要進(jìn)行動(dòng)態(tài)鏈接?為了解決靜態(tài)鏈接浪費(fèi)空間和更新困難的缺點(diǎn)。

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

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

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

如果進(jìn)行顯式運(yùn)行時(shí)鏈接?通過(guò)<dlfcn.h>頭文件中的四個(gè)函數(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):鏈接裝載與庫(kù)》


c++11新特性,所有知識(shí)點(diǎn)都在這了!

你的c++團(tuán)隊(duì)還在禁用異常處理嗎?

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

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

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

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

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

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