在模块中进行各类数据引用的方式总共分有:模块内数据、函数访问,模块间数据、函数访问,其中模块内的访问在链接时就已经决定了他们的相对偏移,在运行时不再关心这部分的内容,而模块间访问相对就比较复杂了。
为了复用物理内存,发明了PIC/PIE技术,将数据和指令分开GOT和PLT stub,这样重定位代码只需要修改GOT的数据部分就可以访问正确的函数了,ELF的PLT和GOT分析了模块间函数访问过程中的lazy bind机制。
reflink:http://www.qnx.com/developers/docs/qnxcar2/index.jsp?topic=%2Fcom.qnx.doc.neutrino.prog%2Ftopic%2Fdevel_Lazy_binding.html
Lazy binding (also known as lazy linking or on-demand symbol resolution) is the process by which symbol resolution isn't done until a symbol is actually used.
Functions can be bound on-demand, but data references can't.
函数可以在运行时按需进行动态链接重定位,但是数据访问是不行的,必须在程序启动的时候就完成重定位操作。函数调用可以跳转到plt项,第一次符号引用触发符号解析,后面的函数调用直接就进行跳转,额外的消耗是多jmp了一次。但是数据访问就不同了,数据地址是不可执行的,你只能加载数据而不能跳转到数据地址执行。所以LAZY BIND对于模块间数据引用是不成立的,它总是BIND_NOW。
各种平台上ELF文件有各种各样的section名称:.plt,.got,.plt.got,.got.plt,.rela.plt
,不要被名称迷惑,根据section的类型它总共有三种的:
- PLT:program independent table,代码段,在运行期间不会改变的,是模块间函数跳转的跳板
- GOT:global offset table,数据段,存储有模块间函数调用的地址和模块间数据访问的地址,它可能是分开的section:
.got,.got.plt
,也可能只有一个.got
- relocation:对section进行重定位的表,在符号重定位的时候需要根据重定位项信息进行符号解析和修改GOT中的信息,GOT中不管是数据还是函数的地址重定位都需要引用重定位项信息。
来验证一下我们上面的猜测,在glibc链接器初始化的时候进行了数据符号初始化:
extern char **environ; //访问libc的数据
int main(int argc, char **argv)
{
environ++; //进行计算
return 0;
打开linker的调试:LD_DEBUG=all ~/tmp/a.out
我们看到它经历了几个步骤:
26285: relocation processing: /lib/x86_64-linux-gnu/libc.so.6 (lazy)
...
26285: relocation processing: /home/linux/tmp/a.out (lazy) //lazy bind模式
26285: symbol=__environ; lookup in file=/lib/x86_64-linux-gnu/libc.so.6 [0]
26285: binding file /home/linux/tmp/a.out [0] to /lib/x86_64-linux-gnu/libc.so.6 [0]: normal symbol `__environ' [GLIBC_2.2.5]
26285:
26285: relocation processing: /lib64/ld-linux-x86-64.so.2
...