linux got分析,聊聊Linux动态链接中的PLT和GOT(3)——公共GOT表项
前文(聊聊Linux動(dòng)態(tài)鏈接中的PLT和GOT(2)——延遲重定位)提到所有動(dòng)態(tài)庫函數(shù)的plt指令最終都跳進(jìn)公共plt執(zhí)行,那么公共plt指令里面的地址是什么鬼?
把test可執(zhí)行文的共公plt貼出來:
080482a0 :
80482a0: pushl 0x80496f0
80482a6: jmp *0x80496f4
...
第一句,pushl 0x80496f0,是將地址壓到棧上,也即向最終調(diào)用的函數(shù)傳遞參數(shù)。
第二句,jmp *0x80496f4,這是跳到最終的函數(shù)去執(zhí)行,不過猜猜就能想到,這是跳到能解析動(dòng)態(tài)庫函數(shù)地址的代碼里面執(zhí)行。
0x80496f4里面住著是何方圣呢?下面使用gdb調(diào)試器將它請(qǐng)出來:
$ gdb -q ./test
...
(gdb)x/xw 0x80496f4
0x80496f4 8>: 0x00000000
(gdb) b main
Breakpoint 1 at 0x80483f3
(gdb) r
Starting program: /home/ivan/test/test/test
Breakpoint 1, 0x80483f3 in main ()
(gdb) x/xw 0x80496f4
0x80496f4 8>: 0xf7ff06a0
從調(diào)試過程可以發(fā)現(xiàn),0x80496f4屬于GOT表中的一項(xiàng),進(jìn)程還沒有運(yùn)行時(shí)它的值是0x00000000,當(dāng)進(jìn)程運(yùn)行起來后,它的值變成了0xf7ff06a0。如果做更進(jìn)一步的調(diào)試會(huì)發(fā)現(xiàn)這個(gè)地址位于動(dòng)態(tài)鏈接器內(nèi),對(duì)應(yīng)的函數(shù)是_dl_runtime_resolve。
嗯,是不是想到了什么呢。所有動(dòng)態(tài)庫函數(shù)在第一次調(diào)用時(shí),都是通過XXX@plt -> 公共@plt -> _dl_runtime_resolve調(diào)用關(guān)系做地址解析和重定位的。
談到這里,其實(shí)還有謎底是沒有解開的,以printf函數(shù)為例:
_dl_runtime_resolve是怎么知要查找printf函數(shù)的
_dl_runtime_resolve找到printf函數(shù)地址之后,它怎么知道回填到哪個(gè)GOT表項(xiàng)
到底_dl_runtime_resolve是什么時(shí)候被寫到GOT表的
前2個(gè)問題,只需要一個(gè)信息就可以了知道,這個(gè)信息就在藏在在函數(shù)對(duì)應(yīng)的xxx@plt表中,以printf@plt為例:
printf@plt>:
jmp *0x80496f8
push $0x00
jmp common@plt
第二條指令就是秘密所在,每個(gè)xxx@plt的第二條指令push的操作數(shù)都是不一樣的,它就相當(dāng)于函數(shù)的id,動(dòng)態(tài)鏈接器通過它就可以知道是要解析哪個(gè)函數(shù)了。
真有這么神嗎?這不是神,是編譯鏈接器和動(dòng)態(tài)鏈接器故意安排的巧合罷了。
使用readelf -r test命令可以查看test可執(zhí)行文件中的重定位信息,其中.rel.plt這一段就大有秘密:
$ readelf -r test
....
Relocation section '.rel.plt' at offset 0x25c contains 3 entries:
Offset Info Type Sym.Value Sym. Name
080496f8 00000107 R_386_JUMP_SLOT 00000000 puts
080496fc 00000207 R_386_JUMP_SLOT 00000000 __gmon_start__
08049700 00000407 R_386_JUMP_SLOT 000000000 __libc_start_main
再看看各函數(shù)plt指令中的push操作數(shù):
printf對(duì)應(yīng)push 0x0
gmon_start對(duì)應(yīng)push 0x8
__libc_start_main對(duì)應(yīng)push 0x10
這3個(gè)push操作數(shù)剛好對(duì)應(yīng)3個(gè)函數(shù)在.rel.plt段的偏移量。在_dl_runtime_resolve函數(shù)內(nèi),根據(jù)這個(gè)offset和.rel.plt段的信息,就知道要解析的函數(shù)。再看看.rel.plt最左邊的offset字段,它就是GOT表項(xiàng)的地址,也即_dl_runtime_resolve做完符號(hào)解析之后,重定位回寫的空間。
第三個(gè)問題:到底_dl_runtime_resolve是什么時(shí)候被寫到GOT表的。
答案很簡單,可執(zhí)行文件在Linux內(nèi)核通過exeve裝載完成之后,不直接執(zhí)行,而是先跳到動(dòng)態(tài)鏈接器(ld-linux-XXX)執(zhí)行。在ld-linux-XXX里將_dl_runtime_resolve地址寫到GOT表項(xiàng)內(nèi)。
事實(shí)上,不單單是預(yù)先寫_dl_runtime_resolve地址到GOT表項(xiàng)中,在i386架構(gòu)下,除了每個(gè)函數(shù)占用一個(gè)GOT表項(xiàng)外,GOT表項(xiàng)還保留了3個(gè)公共表項(xiàng),也即got的前3項(xiàng),分別保存:
got[0]: 本ELF動(dòng)態(tài)段(.dynamic段)的裝載地址
got[1]:本ELF的link_map數(shù)據(jù)結(jié)構(gòu)描述符地址
got[2]:_dl_runtime_resolve函數(shù)的地址
動(dòng)態(tài)鏈接器在加載完ELF之后,都會(huì)將這3地址寫到GOT表的前3項(xiàng)。
其實(shí)上述公共的plt指令里面,還有一個(gè)操作數(shù)是沒有分析的,其實(shí)它就是got[1](本ELF的link_map)地址,因?yàn)橹挥衛(wèi)ink_map結(jié)構(gòu),結(jié)合.rel.plt段的偏移量,才能真正找到該elf的.rel.plt表項(xiàng)。
有興趣的讀者可以使用gdb,在執(zhí)行到main函數(shù)時(shí),將GOT表的這3項(xiàng)數(shù)據(jù)看一下,驗(yàn)證一下。
好了,談到這里是否對(duì)PLT和GOT機(jī)制有個(gè)更清晰認(rèn)識(shí)了呢?最后一篇會(huì)使用圖文結(jié)構(gòu)將整個(gè)PLT/GOT機(jī)制串起來。
總結(jié)
以上是生活随笔為你收集整理的linux got分析,聊聊Linux动态链接中的PLT和GOT(3)——公共GOT表项的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux 移植qt,Linux下移植Q
- 下一篇: 红帽linux iso镜像,红帽 Red