2012年11月11日 星期日

GOT與PLT的運作

ELF(Executable and Linking)格式的執行檔 是利用PLT(Procedure Linkage Table)與 GOT(Global Offset Table)來處理函式庫中的函式呼叫
(呼叫使用者自定義函式則不會使用到PLT及GOT)
例如 若是一個執行檔中有呼叫到printf函式 程式在第一次呼叫到此函式時 會跳到PLT中的printf函式entry
組合語言長的像這樣:
080482c8 < printf@plt >: 80482c8:       ff 25 d4 95 04 08       jmp    *0x80495d4 80482ce:       68 18 00 00 00          push   $0x18 80482d3:       e9 b0 ff ff ff          jmp    8048288 <_init+0x18>

第一個jump指令 跳到GOT中與printf相關的entry(0x80495d4)所包含的記憶體位址 這個位址在ELF執行檔load time的時候會被dynamic linker填入第二個push指令的位址
這個push指令將某個資料push入stack(這個資料之後dynamic linker會用到) 然後第三個jump指令跳到PLT0的位址(代表PLT第一個entry)
所以 任何函式 在"第一次"執行時 都會直接跳到第二個push指令 然後跳到PLT0去執行 PLT0長的像這樣
08048288 <__gmon_start__@plt-0x10>: 8048288:       ff 35 c0 95 04 08       pushl  0x80495c0 804828e:       ff 25 c4 95 04 08       jmp    *0x80495c4 8048294:       00 00                   add    %al,(%eax)

PLT0所做的事情 第一個push也是將某個資料push入stack(之後dynamic linker會用到)然後跳到dynamic linker去解析出printf函式的位址
作解析的動作需要額外資訊 也就是先前那兩個push指令push到stack的資料 等解析出printf函式的位址後 將位址存放到GOT中的printf相關entry 也就是0x80495d4中
之後再呼叫到printf時 一樣跳到0x80495d4中存放的位址 此時這個位址已經是printf函式的位址 也就不會跳到PLT0 一切正常運作

而為什麼要這樣作 根據linker and loader中所說 因為一個program可能會包含很多他有可能不會呼叫到的函式
所以 為了加速程式的起始速度 在load time不會解析出所有函式的位址
改而在run time第一次執行到該函式時 才解析出函式位址來用 而這稱為lazy evaluation

沒有留言:

張貼留言