在前面一篇关于PIC data中我们知道了PIC data的实现,现在我们将进一步去了解PIC中的function call,
延迟binding optimization
当共享库引用一些函数的时候,函数的real地址在加载到内存中之前并不知道,解析这个地址叫做binding,这是动态加载器在加载共享库到进程内存空间时候做的。绑定的过程非常重要,因为加载器必须在特殊的表中寻找函数符号。
解析每个函数都会花费一些时间,不是很多,但是库中的函数数量通常比全局变量多的多,进一步说,这些解析可能没什么意义,因为,通常被调用的函数
只占据一小部分。
对此,为了加速这个过程,a clever lazy binding 的方案被提出,”lazy“,将工作延迟到它确实需要的最后一刻,避免做一些在程序运行时不需要的其结果工作,比如copy-on-write.
这种延迟binding的方案是通过添加另一个间接层来实现的-PLT
The Procedure Linkage Table(PLT)
GOT是.data section的一部分,PLT is part of executable .text,它由a set of entries 组成,每个entry 对应一个共享库调用的外部函数。PLT中存储的不是地址而是一小块可执行代码,不是直接的函数调用,而是call 对应的PLT entry.entry中的代码负责实际的函数调用,sometime this arrangement called a “trampoline”,每个PLT entry又有一个对应的GOT entry,那里面包含了函数的actual offset,但是只有动态加载器解析函数的时候它才会这么做。
在共享库第一次被loaded的时候,函数调用还没有被解析,
解释:
1 在代码中,一个函数func被调用,编译器翻译it去调用对应的PLT entry,
2 PLT由一个特殊的第一个entry,即PLT[0],后面跟随着一堆相同结构的entry,每个entry对应着一个需要被解析的函数。
3 除了PLT[0],其他的PLT entry由三部分组成,
1)一个jump,跳转到对应的GOT entry中说明的位置
2) 为”resolver“准备参数
3)call PLT[0]中的resolver
4 第一个PLT entry call 一个resolver 例程,它本身位于动态加载器中,用来解析函数的实际地址
5 在函数的实际地址被解析之前,对应的GOT entry中存储的地址只是jmp后一条指令,
一起看看第一次调用会发生什么:
1,PLT[n]被调用,jmp到GOT[n]指向的地址
2,这个地址指向jmp下一条指令,指向为resolver解析参数
3,resolver接着被调用
4,resolver执行函数的实际的地址解析,并且把它放入GOT[n]中
在函数第一次调用之后,
现在GOT[n]指向对应的函数,直接进入函数,不在需要resolver解析地址,
resolver就是一块在加载器中底层代码,用来做符号解析的,就不细讲了,对目的来说并不重要。
PIC with function calls through PLT and GOT -an example
最后看个实例:
int myglob = 42;
int ml_util_func(int a)
{
return a + 1;
}
int ml_func(int a, int b)
{
int c = b + ml_util_func(a);
myglob += c;
return b + myglob;
}
将这段代码编译进libmlpic.so,现在把注意力放在ml_func调用ml_util_func上,使用objdump -d libmlpic.so
查看共享库的反汇编代码,
我们可以看到在偏移量0x71c处调用了ml_util_func函数,call 5f0
为什么是5f0呢,找了半天终于找到5f0l
0x5f0正好就是ml_util_func对应的PLT entry的位置。
接着看
jmpq *200a2a(%rip) 跳去哪里了,
rip=0x5f6,0x5f6+0x200a2a=0x201020,我们看一下.got.plt里面的内容
巧了,刚刚好就有一个0x00201020,并且里面的内容就是0x05f6
最后跳转到plt[0]处,5d0
跟我们第一次调用图一模一样。
readelf -r libmlpic.so
第二行告诉动态加载器应该将符号ml_util_func的地址放入0x201020的位置。
看到第一次调用后,这个GOT entry被修改应该很有趣,我们再次使用GDB观察。
gdb driver
b ml_func
set disassembly-flavor intel
disas ml_func
再通过反汇编0x7ffff7bd95f0得到对应的GOT的地址
我们可以看到对应的GOT地址是0x7ffff7dda0a0
我们看看它里面保存了什么
0x7ffff7bd95f6 就是plt的第二条指令的地址
step结束函数调用,
再次查看GOT中的值
0x7ffffbd96f5
我们再打印一下ml_util_func函数地址
结果一样!!!