ELF 的 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。


實作方面 ELF 使用了一個 procedure linkage table (PLT)。呼叫外部函式時不使用 GOT 跳轉,而是使用 PLT 跳轉。等於說呼叫函式時就等於呼叫 PLT 中某一項位址所指向的函式。每一個外部函式在 PLT 中都會有一項,比如我們稱作 bar@plt,則它的實作大概為:


bar@plt:
jmp *(bar@got)
push n
push moduleID
jump _dl_runtime_resolve


假設 bar@got 中有值,則第一行指令則會正確地呼叫 bar 函式,則第二行以後不會執行。但在 binding 之前,bar@got 的值事實上是存放著第二行 push n 的位址。也就是說第一行事實上會去執行第二行。而 push n 中的 n 是 bar 符號在 .rel.glt 中的索引。第二及第三行等於就是將 module name 及 function name 推入 stack,然後第四行呼叫 _dl_runtime_resolve()。


當 _dl_runtime_resolve 函式解析 bar 完畢後,即會將 bar 函式的位址填入 bar@got。則下次再呼叫 bar@glt 時,就能夠正確的呼叫到 bar()。


PLT 在 ELF 中以 .plt  section 存放。它本身是與位址無關的程式碼,因此屬性為可讀可執行。通常在 linking 時會與程式碼放在同一個 segment。


留言

熱門文章