天天看點

重學計算機組成原理(九)- 動态連結(下)3 動态連結的解決方案4 總結5 推薦閱讀參考

3 動态連結的解決方案

PLT和GOT

要實作動态連結共享庫,也并不困難,和前面的靜态連結裡的符号表和重定向表類似

拿出一小段代碼來看一看。

lib.h

定義了動态連結庫的一個函數 show_me_the_money

重學計算機組成原理(九)- 動态連結(下)3 動态連結的解決方案4 總結5 推薦閱讀參考

lib.c

包含了lib.h的實際實作

重學計算機組成原理(九)- 動态連結(下)3 動态連結的解決方案4 總結5 推薦閱讀參考

show_me_poor.c

調用了 lib 裡面的函數

重學計算機組成原理(九)- 動态連結(下)3 動态連結的解決方案4 總結5 推薦閱讀參考

把 lib.c 編譯成了一個動态連結庫,也就是 .so 檔案

重學計算機組成原理(九)- 動态連結(下)3 動态連結的解決方案4 總結5 推薦閱讀參考

最終生成檔案集

重學計算機組成原理(九)- 動态連結(下)3 動态連結的解決方案4 總結5 推薦閱讀參考

在編譯的過程中,指定了一個 -fPIC 的參數

其實就是Position Independent Code意,也就是要把這個編譯成一個位址無關代碼

然後,我們再通過gcc編譯 show_me_poor 動态連結了 lib.so 的可執行檔案

在這些操作都完成了之後,我們把 show_me_poor 這個檔案通過objdump出來看一下。

重學計算機組成原理(九)- 動态連結(下)3 動态連結的解決方案4 總結5 推薦閱讀參考
0000000000400540 <show_me_the_money@plt-0x10>:
  400540:       ff 35 12 05 20 00       push   QWORD PTR [rip+0x200512]        # 600a58 <_GLOBAL_OFFSET_TABLE_+0x8>
  400546:       ff 25 14 05 20 00       jmp    QWORD PTR [rip+0x200514]        # 600a60 <_GLOBAL_OFFSET_TABLE_+0x10>
  40054c:       0f 1f 40 00             nop    DWORD PTR [rax+0x0]

0000000000400550 <show_me_the_money@plt>:
  400550:       ff 25 12 05 20 00       jmp    QWORD PTR [rip+0x200512]        # 600a68 <_GLOBAL_OFFSET_TABLE_+0x18>
  400556:       68 00 00 00 00          push   0x0
  40055b:       e9 e0 ff ff ff          jmp    400540 <_init+0x28>
……
0000000000400676 <main>:
  400676:       55                      push   rbp
  400677:       48 89 e5                mov    rbp,rsp
  40067a:       48 83 ec 10             sub    rsp,0x10
  40067e:       c7 45 fc 05 00 00 00    mov    DWORD PTR [rbp-0x4],0x5
  400685:       8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
  400688:       89 c7                   mov    edi,eax
  40068a:       e8 c1 fe ff ff          call   400550 <show_me_the_money@plt>
  40068f:       c9                      leave  
  400690:       c3                      ret    
  400691:       66 2e 0f 1f 84 00 00    nop    WORD PTR cs:[rax+rax*1+0x0]
  400698:       00 00 00 
  40069b:       0f 1f 44 00 00          nop    DWORD PTR [rax+rax*1+0x0]
      

我們還是隻關心整個可執行檔案中的一小部分内容

  • 在main函數調用show_me_the_money的函數的時候,對應的代碼是這樣的:
重學計算機組成原理(九)- 動态連結(下)3 動态連結的解決方案4 總結5 推薦閱讀參考

這裡後面有一個@plt的關鍵字,代表了我們需要從PLT,也就是程式連結表(Procedure Link Table)裡面找要調用的函數。對應的位址呢,則是400580這個位址。

那當我們把目光挪到上面的 400580 這個位址,你又會看到裡面進行了一次跳轉,

這個跳轉指定的跳轉位址,你可以在後面的注釋裡面可以看到:

重學計算機組成原理(九)- 動态連結(下)3 動态連結的解決方案4 總結5 推薦閱讀參考

這裡的 GLOBAL_OFFSET_TABLE,就是我接下來要說的全局偏移表。

在動态連結對應的共享庫,我們在共享庫的data section裡面,儲存了一張全局偏移表(GOT,Global Offset Table)

雖然共享庫的代碼部分的實體記憶體是共享的,但是資料部分是各個動态連結它的應用程式裡面各加載一份的。

所有需要引用目前共享庫外部的位址的指令,都會查詢GOT,來找到目前運作程式的虛拟記憶體裡的對應位置

而GOT表裡的資料,則是在我們加載一個個共享庫的時候寫進去的。

不同的程序,調用同樣的 lib.so,各自GOT裡面指向最終加載的動态連結庫裡面的虛拟記憶體位址是不同的。

這樣,雖然不同的程式調用的同樣的動态庫,各自的記憶體位址是獨立的,調用的又都是同一個動态庫,但是不需要去修改動态庫裡面的代碼所使用的位址,

而是各個程式各自維護好自己的GOT,能夠找到對應的動态庫就好了

重學計算機組成原理(九)- 動态連結(下)3 動态連結的解決方案4 總結5 推薦閱讀參考

GOT表位于共享庫自己的資料段裡

GOT表在記憶體裡和對應的代碼段位置之間的偏移量,始終是确定的

這樣,共享庫就是位址無關的代碼,對應的各個程式隻需在實體記憶體裡加載同一份代碼

而我們又要通過各個可執行程式在加載時,生成的各不相同的GOT表,找到它需要調用到的外部變量和函數的位址

這是一個典型的、不修改代碼,而是通過修改“位址資料”來進行關聯的辦法

它有點像我們在C語言裡面用函數指針來調用對應的函數,并不是通過預先已經确定好的函數名稱來調用,而是利用當時它在記憶體裡面的動态位址來調用。

4 總結

終于在靜态連結和程式裝載後,利用動态連結把我們的記憶體利用到了極緻

同樣功能的代碼生成的共享庫,我們隻要在記憶體裡面保留一份就好了

這樣

  • 不僅能夠做到代碼在開發階段的複用
  • 也能做到代碼在運作階段的複用。

實際上,在進行Linux程式開發,一直會用到各種各樣的動态連結庫。

C語言的标準庫就在1MB以上。

撰寫任何一個程式可能都需要用到這個庫,常見的Linux伺服器裡,/usr/bin下面就有上千個可執行檔案。

如果每一個都把标準庫靜态連結進來的,幾GB乃至幾十GB的磁盤空間一下子就用出去了。如果我們服務端的多程序應用要開上千個程序,幾GB的記憶體空間也會一下子就用出去了。這個問題在過去計算機的記憶體較少的時候更加顯著。

通過動态連結這個方式,可以說徹底解決了這個問題。

就像共享單車一樣,如果仔細經營,是一個很有社會價值的事情,但是如果粗暴地把它變成無限制地複制生産,給每個人造一輛,隻會在系統内制造大量無用的垃圾。

已經把程式怎麼從源代碼變成指令、資料,并裝載到記憶體裡面,由CPU一條條執行下去的過程講完了。希望你能有所收獲,對于一個程式是怎麼跑起來的,有了一個初步的認識。

5 推薦閱讀

想要更加深入地了解動态連結,推薦你可以讀一讀

《程式員的自我修養:連結、裝載和庫》的第7章
重學計算機組成原理(九)- 動态連結(下)3 動态連結的解決方案4 總結5 推薦閱讀參考
重學計算機組成原理(九)- 動态連結(下)3 動态連結的解決方案4 總結5 推薦閱讀參考

裡面深入地講解了,動态連結裡程式内的資料布局和對應資料的加載關系。

參考

  • 深入淺出計算機組成原理

繼續閱讀