瞭解動態連結(四)—— 延遲綁定,動態綁定
基本思想是當函數第一次被調用時才進行綁定,所謂綁定就是符號尋找和地址重定位。對於一些錯誤處理函數或不常用的功能函數,可能就避免了“綁定浪費”。採用延遲綁定,能加快程式的啟動速度,特別有利於一些大型程式。
當函數第一次被調用時,由動態連結器完成地址綁定工作。他必須知道地址綁定發生在哪個模組的哪個函數,並且要有一個完成綁定工作的函數。
具體實現時,調用某個外部函數要先跳轉到 PLT,再跳轉到 GOT 得到外部函數地址。每個外部函數都在 PLT 表中有一個相應的項。比如:
1 bar@plt:2 jmp *(bar@GOT)3 push n4 push moduleID5 jump _dl_runtime_resolve
第一條指令跳轉 bar@GOT,此時 bar@GOT 中儲存的是上面代碼中的第二條指令“push n”的地址,所以又跳回來。兩條 push 指令分別將符號在重定位表中的下標和模組 ID 入棧,然後調用動態連結器的 _dl_runtime_resolve 函數來完成符號尋找和地址重定位,最後將 bar 函數的真正地址填入 bar@GOT。當再次調用 bar@plt 時,第一條 jmp 指令就可以經由 bar@GOT 直接跳轉到 bar 函數,而無需再做重定位了。
在具體實現時,ELF 將儲存外部函數地址的 GOT 剝離出來,放到 .got.plt 中。.got.plt 的前三項特殊:
- 第一項儲存“.dynamic”的地址;
- 第二項儲存本模組的 ID;
- 第三項儲存 _dl_runtime_resolve 的地址
所以上面的 bar@plt 可以這樣:
1 bar@plt:2 jmp *(bar@GOT)3 push n4 push *(GOT + 4)5 jump *(GOT + 8)
因為最後兩條指令對於每個 plt 項都一樣,為了避免代碼重複,將這兩條指令提出來,放到 PLT0。所以 bar@plt 實際上是這樣:
1 PLT0:2 push *(GOT + 4)3 jump *(GOT + 8)4 …5 bar@plt:6 jmp *(bar@GOT)7 push n8 jmp PLT0
學習資料: 《程式員的自我修養——連結、裝載和庫》