上一篇谈到了共享库中引用全局变量的处理,在来谈谈对引用函数的处理。
$ cat test.c
int foo(void);
int function(void) {
return foo();
}
$ gcc -shared -fPIC -o libtest.so test.c
$ objdump --disassemble libtest.so
[...]
00000000000005bc <function>:
5bc: 55 push %rbp
5bd: 48 89 e5 mov %rsp,%rbp
5c0: e8 0b ff ff ff callq 4d0 <foo@plt>
5c5: 5d pop %rbp
$ objdump --disassemble-all libtest.so
00000000000004d0 <foo@plt>:
4d0: ff 25 82 03 20 00 jmpq *0x200382(%rip) # 200858 <_GLOBAL_OFFSET_TABLE_+0x18>
4d6: 68 00 00 00 00 pushq $0x0
4db: e9 e0 ff ff ff jmpq 4c0 <_init+0x18>
$ readelf --relocs libtest.so
Relocation section '.rela.plt' at offset 0x478 contains 2 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000200858 000400000007 R_X86_64_JUMP_SLO 0000000000000000 foo + 0
可以看到 callq 4d0 foo@plt,跳转到了4d0: ff 25 82 03 20 00 jmpq *0x200382(%rip),可以得出跳转到0x200858 这个位置存储的地址上的内容,看注释这个属于GOT表的地址。继续查看反汇编数据
$ objdump --disassemble-all libtest.so
Disassembly of section .got.plt:
0000000000200840 <.got.plt>:
200840: 98 cwtl
200841: 06 (bad)
200842: 20 00 and %al,(%rax)
...
200858: d6 (bad)
200859: 04 00 add $0x0,%al
20085b: 00 00 add %al,(%rax)
20085d: 00 00 add %al,(%rax)
20085f: 00 e6 add %ah,%dh
200861: 04 00 add $0x0,%al
200863: 00 00 add %al,(%rax)
200865: 00 00 add %al,(%rax)
...
可以看到0x200858 这个地址处存储的数据为0x04d6,这个刚好为plt条目里的下一条指令地址,所以这里等价于直接执行plt条目里的下一条指令,这里压栈一个数据0x0,然后跳转到4c0 。这里等价于调用动态链接器。然后动态链接器根据栈的条目确定foo函数的运行时的位置,把这个地址重写回0x200858(got表中的条目),最后把控制传递给foo。
当下次在调用foo时,可以直接从got表中的位置取到foo的地址,直接将控制转移给foo。
上述就是“延迟绑定”的意思,避免了像libc.so这样的共享库里有几百上千个函数,一个程序只会用到很少一部分,避免动态链接器加载时进行上千个函数的重定位。第一次调用函数开销比较大,后面只需要花费一条指令和一个间接引用内存。
网友评论