got&plt-note

2018-09-08

一隻正常的ELF會用到兩種機制去執行─Static linking和Dynamic linking

Dynamic linking的話就會需要PLT&GOT table去定義要呼叫的函示在哪,這個機制大大加快了程式執行的時間
提到了GOT&PLT的話就不能不提到Lazy binding,這些下面都會提到

—-正文開始—-

GOT的全名是Global Offset Table,PLT的全名是Procedure Linkage Table

這兩個是ELF做dynamic linking很重要的兩個table

再說GOT&PLT前先科普一些知識

什麼是static library和shared library呢?

進階一點的人可以讀這篇
https://www.ptt.cc/bbs/LinuxDev/M.1162669989.A.2E6.html

詳細一點的話可以看看這篇
http://fred-zone.blogspot.com/2006/03/static-shared.html

如果懶得看只想看個大概的可以看我下面的整理

什麼是 static library?
以C語言來說,
今天我們需要使用到其他自定義的函式,可以寫進不同的檔案,在編譯時可以透過編譯器
去把他們全包再一起變成一個執行檔,優點是執行效能高(跟shared library相比),也不會有
執行時找不到library的問題
缺點是維護成本很高,今天假如函式庫出現bug時必須要重新連結一次執行檔

那什麼是 shared library呢?
就是我們不需要把他們全包再一起,可以把函式庫放在系統資料夾,並透過編譯器define好
在執行時才被動態載入,因為函式庫與main.c是分離的,所以維護性比較好,當然相應的缺點
就是會找不到所需的library或版本號對不上出現錯誤之類的

知道了這些之後…讓我們放在dynamic linking&shared library上

GOT的作用是什麼?

以gcc 內建的libc.so為例,因為你不可能用到libc.so內的所有函式,所以其實不用知道所有函式在記憶體內的位置。
其中GOT table只會列出你會用到的 fuction 或是 global variable的絕對位置。這樣會節省許多解析時間。

PLT的功用是什麼?

既然GOT已經列出需要的東西,那照理說工作就結束了,還需要PLT幹麻?
試想,當你的程式也大到跟 libc.so 一樣大時,你可能會呼叫上百個libc的函式,
所以當你的程式載入記憶體時,linker 會解析你需要的函式,這也會花上不少時間,
並導致使用者認為反應很慢。為了解決這個問題,所以GCC 改為呼叫shared library的函式前,
才去把位置填入GOT內。而PLT的功用就是呼叫 linker去填入 GOT,這個機制就是延遲解析 (lazy binding)。
(以linux來看的話上文的linker的部分都是ld.so這個shared library在做的事)
你可以把 PLT 當作Lazy binding機制的實現

要注意 lazy binding和 lazy loading的差異。Lazy loading 是透過 dlopen()等函式將library動態載入記憶體內。
GCC並沒有自動提供lazy loading的機制,所有的shared library都是一次載入到記憶體內,除非你使用dlopen()。

lazy binding

Linux 的 ELF 使用了 lazy binding 的方式來加快 dynamic linking 的速度。dynamic linking 之所以會慢,原因有二,
一是它透過 GOT 做間接跳轉的動作。
二是它在 run-time 時必需做符號搜尋及位址重定的動作。

而 lazy binding 的概念則是在函式第一次使用時再做 binding。
如果一個函式未被使用到,則它就不會被 binding。
假設我們呼叫一個 bar(),那在 glibc 中的 _dl_runtime_resolve() 則會做 binding 的動作。
dl_runtime_resolve() 需要知道呼叫者 module 名字及 bar() 的名字以完成 binding 動作。
假設我們在 liba.so 中呼叫 bar(),則 module name 就是 liba.so,function name 則是 bar。

總結上面就是─
程式在編譯時,編譯器會先把需要的函式全寫進PLT table裡面,這時PLT table裡面還沒有函式在libc的address,因為ASLR的緣故所以在執行時才會動態尋找
GOT Table原本是一張白紙,當程式執行遇到function需要呼叫時,程式會先跳去PLT Table,而PLT table會跳去GOT table裡面找對應的address,但是第一次執行時還不會知道address,所以這時候的GOT table所指的address其實是PLT table的第二行
(意思是 ─ 安安,這裡沒有資料歐,請你先叫人去libc找到address再來我這回報),
而PLT table的第二行會push一個值進去stack內(這裡假設值是0x0),好像還會再push一個address再呼叫_dl_runtime_resolve()
透過push的那兩個參數可以幫助_dl_runtime_resolve()去libc內找到address,把它寫進GOT table內,
當第二次呼叫的時候,因為GOT table內已經有真正的function address,所以一樣會先跳去PLT table但是下一步直接跳過去libc內的address,達到lazy binding的目的
這邊附上一張示意圖

總結再總結─所以網路上說得口沫橫飛的講Lazy binding一定會有的一個共通點─
程式第一次呼叫function時不會直接進去function裡面,要第二次呼叫才會,就是這個道理
關於運作的詳細過程,可以看看這篇蠻詳細的(只是link map&index那邊真心看不懂)
http://look3little.blogspot.com/2017/01/lazy-binding.html

參考資料:
http://brandon-hy-lin.blogspot.com/2015/12/shared-library-plt-got.html
http://wthung2.blogspot.com/2010/03/elf-lazy-binding.html
https://blog.csdn.net/linyt/article/details/51635768
http://fred-zone.blogspot.com/2006/03/static-shared.html