计算机系统篇之链接(14):.plt、.plt.got、.got 和 .got.plt sections 之间的区别
Author: stormQ
Created: Saturday, 21. December 2019 11:58AM
Last Modified: Sunday, 01. November 2020 12:27PM
本文通过示例剖析了 .plt、.plt.got、.got 和 .got.plt sections 之间的区别,从而有助于理解位置无关代码技术。
step 1: 生成共享库
1)第一个共享库的源文件——add.cpp:
int g_sum = 0;
int add(int a, int b)
{
g_sum = a + b;
return g_sum;
}
生成第一个共享库——libadd_debug.so
$ g++ -fpic -shared -g -o libadd_debug.so add.cpp
2)第二个共享库的源文件——sub.cpp:
int g_sub = 0;
int sub(int a, int b)
{
g_sub = a - b;
return g_sub;
}
生成第二个共享库——libsub_debug.so
$ g++ -fpic -shared -g -o libsub_debug.so sub.cpp
step 2: 生成测试程序(用于调用以上两个共享库)——main_mix
测试程序的源文件——main_mix.cpp:
extern int add(int, int);
extern int sub(int, int);
int main()
{
add(0xb, 0xc);
add(0xb, 0xc);
sub(0xf, 0xd);
sub(0xf, 0xd);
return 0;
}
生成测试程序——main_mix
$ g++ -o main_mix main_mix.cpp ./libadd_debug.so ./libsub_debug.so -g
step 3: 查看可执行目标文件 main_mix 的程序表
$ readelf -l main_mix
Elf file type is EXEC (Executable file)
Entry point 0x4005f0
There are 9 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001f8 0x00000000000001f8 R E 8
INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238
0x000000000000001c 0x000000000000001c R 1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000008dc 0x00000000000008dc R E 200000
LOAD 0x0000000000000df0 0x0000000000600df0 0x0000000000600df0
0x0000000000000250 0x0000000000000258 RW 200000
DYNAMIC 0x0000000000000e08 0x0000000000600e08 0x0000000000600e08
0x00000000000001f0 0x00000000000001f0 RW 8
NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x00000000000007b4 0x00000000004007b4 0x00000000004007b4
0x0000000000000034 0x0000000000000034 R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 10
GNU_RELRO 0x0000000000000df0 0x0000000000600df0 0x0000000000600df0
0x0000000000000210 0x0000000000000210 R 1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .jcr .dynamic .got
从上面可以看出,.plt
、.plt.got
和.text
等位于同一个 segment(代码段),.got
、.got.plt
和.data
等位于同一个 segment(数据段)。
step 4: 分析 .plt section 的作用
1)查看可执行目标文件 main_mix 的 .plt section
$ objdump -d --section=.plt main_mix
main_mix: file format elf64-x86-64
Disassembly of section .plt:
00000000004005a0 <_Z3addii@plt-0x10>:
4005a0: ff 35 62 0a 20 00 pushq 0x200a62(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
4005a6: ff 25 64 0a 20 00 jmpq *0x200a64(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
4005ac: 0f 1f 40 00 nopl 0x0(%rax)
00000000004005b0 <_Z3addii@plt>:
4005b0: ff 25 62 0a 20 00 jmpq *0x200a62(%rip) # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
4005b6: 68 00 00 00 00 pushq $0x0
4005bb: e9 e0 ff ff ff jmpq 4005a0 <_init+0x20>
00000000004005c0 <_Z3subii@plt>:
4005c0: ff 25 5a 0a 20 00 jmpq *0x200a5a(%rip) # 601020 <_GLOBAL_OFFSET_TABLE_+0x20>
4005c6: 68 01 00 00 00 pushq $0x1
4005cb: e9 d0 ff ff ff jmpq 4005a0 <_init+0x20>
00000000004005d0 <__libc_start_main@plt>:
4005d0: ff 25 52 0a 20 00 jmpq *0x200a52(%rip) # 601028 <_GLOBAL_OFFSET_TABLE_+0x28>
4005d6: 68 02 00 00 00 pushq $0x2
4005db: e9 c0 ff ff ff jmpq 4005a0 <_init+0x20>
从上面可以看出,.plt section 中的条目有4 个,后 3 个分别是函数 add、sub 和 __libc_start_main 的 PLT 条目。
另外,.plt section 中的第 1 个条目用于跳转到动态链接器,具体的目的跳转函数为 _dl_runtime_resolve_avx。关于这一点,有兴趣的可以自己验证。
因此,.plt section 实际就是通常所说的过程链接表(Procedure Linkage Table, PLT)。
step 5: 分析 .plt.got section 的作用
1)查看可共享目标文件 libadd_debug.so 的 .plt.got section
$ objdump -d --section=.plt.got libadd_debug.so
libadd_debug.so: file format elf64-x86-64
Disassembly of section .plt.got:
0000000000000530 <.plt.got>:
530: ff 25 9a 0a 20 00 jmpq *0x200a9a(%rip) # 200fd0 <_DYNAMIC+0x150>
536: 66 90 xchg %ax,%ax
538: ff 25 ba 0a 20 00 jmpq *0x200aba(%rip) # 200ff8 <_DYNAMIC+0x178>
53e: 66 90 xchg %ax,%ax
从上面可以看出,.plt.got section 中的条目只有 1 个。
2)使用 gdb 运行可执行目标文件 main_mix
$ gdb -q main_mix
Reading symbols from main_mix...done.
(gdb) start
Temporary breakpoint 1 at 0x4006ea: file main_mix.cpp, line 6.
Starting program: /home/xuxiaoqiang/tx/dyn/t14/main_mix
Temporary breakpoint 1, main () at main_mix.cpp:6
6 add(0xb, 0xc);
3)查看 libadd_debug.so 的 .plt.got 和 .got section 的内存映射
(gdb) info files
# 省略 ...
0x00007ffff7bd5530 - 0x00007ffff7bd5540 is .plt.got in ./libadd_debug.so
# 省略 ...
0x00007ffff7bd3fd0 - 0x00007ffff7bd4000 is .got in ./libsub_debug.so
# 省略 ...
4)查看运行期 libadd_debug.so 的 .plt.got section 中唯一条目的内容
(gdb) x/3i 0x00007ffff79d3530
0x7ffff79d3530: jmpq *0x200a9a(%rip) # 0x7ffff7bd3fd0
0x7ffff79d3536: xchg %ax,%ax
0x7ffff79d3538: jmpq *0x200aba(%rip) # 0x7ffff7bd3ff8
结合“3)”中的信息,可以看出 libadd_debug.so 的 .plt.got section 的唯一条目中第一条指令的目的地址为 0x7ffff7bd3fd0 的内容(即 .got section 中第 1 个条目的内容)。
5)查看起始地址为 0x7ffff7bd3fd0 后面 8 个字节的内容
(gdb) x/gx 0x7ffff7bd3fd0
0x7ffff7bd3fd0: 0x00007ffff76432d0
(gdb) info symbol 0x00007ffff76432d0
__cxa_finalize in section .text of /lib/x86_64-linux-gnu/libc.so.6
从上面可以看出,libadd_debug.so 的 .plt.got section 中唯一条目中第一条指令的目的地址为 0x00007ffff76432d0,即 __cxa_finalize 函数的地址。
因此,可以得出结论:.plt.got section 用于存放 __cxa_finalize 函数对应的 PLT 条目。
step 6: 分析 .got section 的作用
1)使用 gdb 运行可执行目标文件 main_mix
$ gdb -q main_mix
Reading symbols from main_mix...done.
(gdb) start
Temporary breakpoint 1 at 0x4006ea: file main_mix.cpp, line 6.
Starting program: /home/xuxiaoqiang/tx/dyn/t14/main_mix
Temporary breakpoint 1, main () at main_mix.cpp:6
6 add(0xb, 0xc);
2)反汇编 add 函数,并在 0x00007ffff7bd5652 地址处设置断点
(gdb) disas add
Dump of assembler code for function add(int, int):
0x00007ffff7bd5640 <+0>: push %rbp
0x00007ffff7bd5641 <+1>: mov %rsp,%rbp
0x00007ffff7bd5644 <+4>: mov %edi,-0x4(%rbp)
0x00007ffff7bd5647 <+7>: mov %esi,-0x8(%rbp)
0x00007ffff7bd564a <+10>: mov -0x4(%rbp),%edx
0x00007ffff7bd564d <+13>: mov -0x8(%rbp),%eax
0x00007ffff7bd5650 <+16>: add %eax,%edx
0x00007ffff7bd5652 <+18>: mov 0x20097f(%rip),%rax # 0x7ffff7dd5fd8
0x00007ffff7bd5659 <+25>: mov %edx,(%rax)
0x00007ffff7bd565b <+27>: mov 0x200976(%rip),%rax # 0x7ffff7dd5fd8
0x00007ffff7bd5662 <+34>: mov (%rax),%eax
0x00007ffff7bd5664 <+36>: pop %rbp
0x00007ffff7bd5665 <+37>: retq
End of assembler dump.
(gdb) b *0x00007ffff7bd5652
Breakpoint 2 at 0x7ffff7bd5652: file add.cpp, line 5.
3)继续执行,并查看起始地址为 0x7ffff7dd5fd8 的内容
(gdb) c
Continuing.
Breakpoint 2, 0x00007ffff7bd5652 in add (a=11, b=12) at add.cpp:5
5 g_sum = a + b;
(gdb) ni
0x00007ffff7bd5659 5 g_sum = a + b;
(gdb) p/x $rip+0x20097f
$1 = 0x7ffff7dd5fd8
(gdb) x/gx $rip+0x20097f
0x7ffff7dd5fd8: 0x00007ffff7dd6024
(gdb) p/x &g_sum
$2 = 0x7ffff7dd6024
从上面可以看出,起始地址为 0x7ffff7dd5fd8 后面 8 字节的内容即为全局变量 g_sum 的地址。
4)查看地址 0x7ffff7dd5fd8 位于哪个 section
(gdb) info files
# 省略 ...
0x00007ffff7dd5fd0 - 0x00007ffff7dd6000 is .got in ./libadd_debug.so
# 省略 ...
从上面可以看出,0x7ffff7dd5fd8 为 libadd_debug.so 的 .got section 中第二个条目的地址。
因此,可以得出结论:.got section 的其中一个条目的内容为全局变量 g_sum 的地址。
5)查看完整的运行期 libadd_debug.so 的 .got section
(gdb) x/6gx 0x00007ffff7dd5fd0
0x7ffff7dd5fd0: 0x00007ffff76432d0 0x00007ffff7dd6024
0x7ffff7dd5fe0: 0x0000000000000000 0x0000000000000000
0x7ffff7dd5ff0: 0x0000000000000000 0x0000000000000000
从上面可以看出,在运行期 libadd_debug.so 的 .got section 的条目有 6 个。并且 GOT[2] ~ GOT[5] 的内容全为 0。
6)查看运行期 libadd_debug.so 的 .got section 的第一个条目的内容
(gdb) disas 0x00007ffff76432d0, 0x00007ffff76432d0+10
Dump of assembler code from 0x7ffff76432d0 to 0x7ffff76432da:
0x00007ffff76432d0 <__cxa_finalize+0>: push %r15
0x00007ffff76432d2 <__cxa_finalize+2>: push %r14
0x00007ffff76432d4 <__cxa_finalize+4>: push %r13
0x00007ffff76432d6 <__cxa_finalize+6>: push %r12
0x00007ffff76432d8 <__cxa_finalize+8>: xor %r13d,%r13d
End of assembler dump.
(gdb) info symbol 0x00007ffff76432d0
__cxa_finalize in section .text of /lib/x86_64-linux-gnu/libc.so.6
从上面可以看出,在运行期 libadd_debug.so 的 .got section 的第一个条目的内容为 __cxa_finalize 函数的地址,定义在 /lib/x86_64-linux-gnu/libc.so.6 的 .text section 中。
另外,__cxa_finalize 函数没有对应的 PLT 条目(从“step 4”步骤中可以看出),意味着该函数不需要延迟绑定。
因此,可以得出结论:.got section 的其中一个条目的内容为不需要延迟绑定的 __cxa_finalize 函数的地址。
step 7: 分析 .got.plt section 的作用
1)使用 gdb 运行可执行目标文件 main_mix
$ gdb -q main_mix
Reading symbols from main_mix...done.
(gdb) start
Temporary breakpoint 1 at 0x4006ea: file main_mix.cpp, line 6.
Starting program: /home/xuxiaoqiang/tx/dyn/t14/main_mix
Temporary breakpoint 1, main () at main_mix.cpp:6
6 add(0xb, 0xc);
2)反汇编 main 函数和 _Z3addii@plt,并单步调试 _Z3addii@plt 的汇编代码
(gdb) disas
Dump of assembler code for function main():
0x00000000004006e6 <+0>: push %rbp
0x00000000004006e7 <+1>: mov %rsp,%rbp
=> 0x00000000004006ea <+4>: mov $0xc,%esi
0x00000000004006ef <+9>: mov $0xb,%edi
0x00000000004006f4 <+14>: callq 0x4005b0 <_Z3addii@plt>
0x00000000004006f9 <+19>: mov $0xc,%esi
0x00000000004006fe <+24>: mov $0xb,%edi
0x0000000000400703 <+29>: callq 0x4005b0 <_Z3addii@plt>
0x0000000000400708 <+34>: mov $0xd,%esi
0x000000000040070d <+39>: mov $0xf,%edi
0x0000000000400712 <+44>: callq 0x4005c0 <_Z3subii@plt>
0x0000000000400717 <+49>: mov $0xd,%esi
0x000000000040071c <+54>: mov $0xf,%edi
0x0000000000400721 <+59>: callq 0x4005c0 <_Z3subii@plt>
0x0000000000400726 <+64>: mov $0x0,%eax
0x000000000040072b <+69>: pop %rbp
0x000000000040072c <+70>: retq
End of assembler dump.
(gdb) b *0x00000000004006f4
Breakpoint 2 at 0x4006f4: file main_mix.cpp, line 6.
(gdb) x/3i 0x4005b0
0x4005b0 <_Z3addii@plt>: jmpq *0x200a62(%rip) # 0x601018
0x4005b6 <_Z3addii@plt+6>: pushq $0x0
0x4005bb <_Z3addii@plt+11>: jmpq 0x4005a0
(gdb) b *0x4005b0
Breakpoint 3 at 0x4005b0
(gdb) b * 0x4005bb
Breakpoint 4 at 0x4005bb
(gdb) c
Continuing.
Breakpoint 2, 0x00000000004006f4 in main () at main_mix.cpp:6
6 add(0xb, 0xc);
(gdb) c
Continuing.
Breakpoint 3, 0x00000000004005b0 in add(int, int)@plt ()
(gdb) disas
Dump of assembler code for function _Z3addii@plt:
=> 0x00000000004005b0 <+0>: jmpq *0x200a62(%rip) # 0x601018
0x00000000004005b6 <+6>: pushq $0x0
0x00000000004005bb <+11>: jmpq 0x4005a0
End of assembler dump.
3)查看 _Z3addii@plt 中第一个 jumq 指令的目的地址
(gdb) x/gx 0x00000000004005b6+0x200a62
0x601018: 0x00000000004005b6
从上面看出,_Z3addii@plt 中第一个 jumq 指令的目的地址为 0x00000000004005b6,即 _Z3addii@plt 中第三条指令的地址。
4)查看地址 0x601018 位于哪个 section
(gdb) info files
# 省略 ...
0x0000000000601000 - 0x0000000000601030 is .got.plt
# 省略 ...
从上面可以看出,0x601018 为 main_mix 的 .got.plt section 中第 4 个条目的地址。
5)查看完整的 main_mix 的 .got.plt section 及其条目的内容
(gdb) x/6gx 0x0000000000601000
0x601000: 0x0000000000600e08 0x00007ffff7ffe168
0x601010: 0x00007ffff7dee870 0x00000000004005b6
0x601020: 0x00000000004005c6 0x00007ffff7629740
(gdb) info symbol 0x0000000000600e08
_DYNAMIC in section .dynamic of /home/test/main_mix
(gdb) info symbol 0x00007ffff7dee870
_dl_runtime_resolve_avx in section .text of /lib64/ld-linux-x86-64.so.2
(gdb) info symbol 0x00007ffff7629740
__libc_start_main in section .text of /lib/x86_64-linux-gnu/libc.so.6
# 省略 ...
(gdb) x/6gx 0x0000000000601000
0x601000: 0x0000000000600e08 0x00007ffff7ffe168
0x601010: 0x00007ffff7dee870 0x00007ffff7bd5640
0x601020: 0x00007ffff79d3640 0x00007ffff7629740
(gdb) info symbol 0x00007ffff7bd5640
add(int, int) in section .text of ./libadd_debug.so
(gdb) info symbol 0x00007ffff79d3640
sub(int, int) in section .text of ./libsub_debug.so
从上面可以看出,main_mix 的 .got.plt section 中第 1 个条目的内容为 .dynamic section 的地址,第 3 个条目的内容为 _dl_runtime_resolve_avx 函数的地址,第 6 个条目的内容为 __libc_start_main 函数的地址。
另外,第 4、5 个条目的最终内容为 add、sub 函数的地址。这一点,有兴趣的可以自己验证。
因此,可以得出结论:.got.plt section 用于存放需要延迟绑定的函数的地址。
所以,可以得出如下结论:
section | 所在 segment | section 属性 | 用途 |
---|---|---|---|
.plt | 代码段 | RE(可读,可执行) | .plt section 实际就是通常所说的过程链接表(Procedure Linkage Table, PLT) |
.plt.got | 代码段 | RE | .plt.got section 用于存放 __cxa_finalize 函数对应的 PLT 条目 |
.got | 数据段 | RW(可读,可写) | |
.got.plt | 数据段 | RW | .got.plt section 用于存放需要延迟绑定的函数的地址 |
注:查看 section 属性的命令:readelf -S main_mix
。
下一篇:计算机系统篇之链接(15):共享库拦截技术之运行时库打桩