计算机系统篇之链接(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 aint 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(intint);
extern int sub(intint);

int main()
{
    add(0xb0xc);
    add(0xb0xc);
    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
                 0x00000000000001f0x00000000000001f8  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
                 0x00000000000008d0x00000000000008dc  R E    200000
  LOAD           0x0000000000000df0x0000000000600df0x0000000000600df0
                 0x0000000000000250 0x0000000000000258  RW     200000
  DYNAMIC        0x0000000000000e08 0x0000000000600e08 0x0000000000600e08
                 0x00000000000001f0x00000000000001f0  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      0x0000000000000df0x0000000000600df0x0000000000600df0
                 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 140 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 0x4006eafile main_mix.cpp, line 6.
Starting program: /home/xuxiaoqiang/tx/dyn/t14/main_mix 

Temporary breakpoint 1main () at main_mix.cpp:6
6        add(0xb0xc);

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 0x4006eafile main_mix.cpp, line 6.
Starting program: /home/xuxiaoqiang/tx/dyn/t14/main_mix 

Temporary breakpoint 1main () at main_mix.cpp:6
6        add(0xb0xc);

2)反汇编 add 函数,并在 0x00007ffff7bd5652 地址处设置断点

(gdb) disas add
Dump of assembler code for function add(intint):
   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 20x00007ffff7bd5652 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 0x00007ffff76432d00x00007ffff76432d0+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 0x4006eafile main_mix.cpp, line 6.
Starting program: /home/xuxiaoqiang/tx/dyn/t14/main_mix 

Temporary breakpoint 1main () at main_mix.cpp:6
6        add(0xb0xc);

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/30x4005b0
   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 20x00000000004006f4 in main () at main_mix.cpp:6
6        add(0xb0xc);
(gdb) c
Continuing.

Breakpoint 30x00000000004005b0 in add(intint)@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(intint) in section .text of ./libadd_debug.so
(gdb) info symbol 0x00007ffff79d3640
sub(intint) 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 section 中可以用于存放全局变量的地址;
  • .got section 中也可以用于存放不需要延迟绑定的函数的地址。
  • .got.plt 数据段 RW .got.plt section 用于存放需要延迟绑定的函数的地址

    注:查看 section 属性的命令:readelf -S main_mix


    下一篇:计算机系统篇之链接(15):共享库拦截技术之运行时库打桩

    上一篇:计算机系统篇之链接(13):升级共享库导致程序运行时错误的惨痛经历

    首页