動態鏈結PIC的疑問

2022-06-12 04:30:09 字數 1505 閱讀 6435

動態共享物件的裝載時重定位

最近讀程式設計師的自我修養--鏈結 裝載與庫,其中有句話:動態鏈結模組被裝載對映到虛擬空間後,指令部分是在多個程序之間共享的,由於裝載時重定位的方法需要修改指令,所以沒有辦法做到同乙份指令被多個程序共享,因為指令被重定位後對於每個程序來講是不同的。一直沒搞懂,花了不少時間查閱資料,了解原理。覺得自己一直理解的東西都太淺了。 以下是對這塊內容的總結,作為記錄。

其中的關鍵點在於掌握記憶體對映,重定位的實現以及共享物件的裝載。搞清楚虛擬記憶體空間和物理記憶體空間。

共享物件在被裝載時,是如何確定它在程序虛擬位址空間中的位置?

一種方法是固定裝載,這種方法弊端明顯:位址衝突;共享庫公升級困難;空間受限等等。(基本不用)

那麼只能採用另一種思路,即共享物件能在任意位址裝載。這種情況叫做裝載時重定位。當程式被裝載時,系統的動態鏈結器會將程式所需的所有動態鏈結庫(例如最基本的libc.so)裝載到程序的位址空間,且將程式中所有未決議的符號繫結到相應的動態鏈結庫中,並進行重定位工作(術語叫裝載時重定位-load time relocation,在windows中,又叫基址重置-rebasing,區別於靜態鏈結的鏈結時重定位-link time relocation)。也即,動態鏈結是把可執行elf的形成過程從本來的程式鏈結前推遲到裝載時。共享物件的最終裝載位址在編譯時是不確定的,而是在裝載時,裝載器根據當前位址空間的空閒情況,動態分配一塊足夠大小的虛擬位址空間給相應的共享物件。

裝載時重定位的原理來自鏈結時重定位,但是對於共享物件,單純的裝載時重定位顯然會引發問題,也就是文章開頭說到的那句話。動態鏈結模組被裝載對映到虛擬空間後,指令部分是在多個程序之間共享的,由於裝載時重定位的方法需要修改指令,所以沒有辦法做到同乙份指令被多個程序共享,因為指令被重定位後對於每個程序來講是不同的。

在網上看到有不少人問了這個問題,答案看得我也是迷迷糊糊。折騰了一下午,發現其實是自己對基本的原理都沒搞清楚才會看不懂這句話。關鍵在於共享物件也就是動態鏈結庫在被裝載到物理記憶體後,始終是只有乙份的,不管有多少個程序使用它。但是對於每乙個程序,共享物件會對映一次到虛擬位址空間,也就是每個程序空間都有乙份共享物件的對映,此時,對於不同的程序,對映的位址(基址)是不一樣的(大部分情況下)。緊接著,進行裝載時重定位。裝載時重定位由動態鏈結器完成,動態鏈結器會被一起對映到程序空間中。它根據共享物件在虛擬記憶體空間中的位址修改在物理記憶體中的共享物件中的指令,為什麼會修改指令,原因在於絕對位址訪問(如模組內的變數訪問)是直接用mov指令完成的,也就是直接將位址打入暫存器,所以,此時的重定位會直接修改指令。進一步,共享物件中修改的指令是根據共享物件被對映到虛擬空間中的位址(基址)決定的,而每個程序對共享物件的對映不可能都是在相同位址。所以也就無法完成這一部分**的共享。

批註:那麼如何解決這個問題,就要用到位置無關**(pic),也有叫位址無關**的。基本思路是把指令中那些需要被修改的部分分離出來,跟資料部分放到一起,這樣,剩下的指令就可以保持不變,而資料部分在每個程序中擁有乙個副本。elf針對各種可能的訪問型別(模組內部指令呼叫、模組內部資料訪問、模組間指令呼叫、模組間資料訪問),實現了對應位址引用方式,從而實現了pic。具體的細節不在這裡贅述。

參考資料:

動態鏈結庫疑問?

下面這句話 我們在windows目錄下的system32資料夾中會看到kernel32.dll user32.dll和gdi32.dll,windows的大多數api都包含在這些dll中。kernel32.dll中的函式主要處理記憶體管理和程序排程 user32.dll中的函式主要控制使用者介面 g...

動態鏈結 靜態鏈結

在linux系統中,ld鏈結器將彙編器編譯出來的目標檔案和靜態庫里的.a檔案鏈結生成可執行檔案。靜態庫中的.a檔案的 會在靜態鏈結過程中新增到可執行檔案中,可執行檔案會變得很大。與靜態鏈結不同,linux系統的ld鏈結器會將動態庫.so檔案進行符號重定位生成可執行檔案,動態庫.so檔案並不新增到可執...

靜態鏈結 動態鏈結

如果函式庫的乙份拷貝是可執行檔案的物理組成部分,那麼我們稱之為靜態鏈結。如果可執行檔案只是包含了檔名,讓載入器在執行時能夠尋找程式所需的函式庫,那麼稱為動態鏈結。即根據函式庫是不是可執行檔案的組成部分區分靜態鏈結和動態鏈結。1 可執行檔案的體積小。2 雖然執行速度稍慢,但是能更加有效的利用磁碟空間,...