浅谈GOT表与PLT表

文章目录

浅谈GOT表与PLT表

我们都知道动态链接库是我们程序开发中比较基础的手段,我们将公共的函数封装在一个so库中,即可用减少主程序的大小,也能增加公共代码的复用度。

那么对于一个共享库中的导出变量和函数的使用,是怎么引用和解析的呢?这里就是我们所说的GOT和PLT表的作用,下面我们详细来看一下基本流程和原理。

1. 实例

下面我们从一个最熟悉的例子除非,来讲述相关知识点,例子如下:

#include <stdio.h>

int main(int argc, char* argv[])
{
        printf("hello world.\n");
        return 0;
}

然后我们需要对这个代码进行编译如下:

$ gcc -g -z lazy elf.c -o elf.o

这里有个非常重要的参数是必须添加的,那就是-z lazy,需要延迟加载动态库函数,不然我们的调试就看不到效果了(可执行文件加载的时候就已经解析完了共享库函数了)。

2. PLT表

PLT表是过程链接表(Procedure Linkage Table,PLT),这个表的存在就是为了使得代码能够方便的访问共享的函数或者变量,每一个引用的共享函数在PTL表中都会存在一个条目,我们可用看一下程序对PLT的使用,如下:

f$ objdump -d elf.o

elf.o:     file format elf64-x86-64

0000000000000510 <puts@plt>:
 510:   ff 25 02 0b 20 00       jmpq   *0x200b02(%rip)        # 201018 <puts@GLIBC_2.2.5>
 516:   68 00 00 00 00          pushq  $0x0
 51b:   e9 e0 ff ff ff          jmpq   500 <.plt>

000000000000063a <main>:
 63a:   55                      push   %rbp
 63b:   48 89 e5                mov    %rsp,%rbp
 63e:   48 83 ec 10             sub    $0x10,%rsp
 642:   89 7d fc                mov    %edi,-0x4(%rbp)
 645:   48 89 75 f0             mov    %rsi,-0x10(%rbp)
 649:   48 8d 3d 94 00 00 00    lea    0x94(%rip),%rdi        # 6e4 <_IO_stdin_used+0x4>
 650:   e8 bb fe ff ff          callq  510 <puts@plt>
 655:   b8 00 00 00 00          mov    $0x0,%eax
 65a:   c9                      leaveq
 65b:   c3                      retq
 65c:   0f 1f 40 00             nopl   0x0(%rax)

我们可用看到callq 510 <puts@plt>这一条汇编代码,对应的源码就是:

printf("hello world.\n");

也就是说的可执行文件中,并没有对导出函数puts直接进行调用,而是通过一个打桩函数puts@plt完成对puts的真实引用,这样就可用做到不用修改任何可执行的代码就可以对动态库已经装载。

3. 动态解析

我们先来看一下动态解析的流程图:

浅谈GOT表与PLT表

基本流程如下:

  1. 主程序调用打桩函数puts@plt
  2. puts@plt代码如下(其中jmpq *0x200b02(%rip) # 0x8201018这一条语句跳转到GOT表中):
(gdb) disassemble 0x8000510
Dump of assembler code for function puts@plt:
   0x0000000008000510 <+0>:     jmpq   *0x200b02(%rip)        # 0x8201018
   0x0000000008000516 <+6>:     pushq  $0x0
   0x000000000800051b <+11>:    jmpq   0x8000500
  1. 0x8201018是GOT中的一项,值为0x0000000008000516,也就是jmpq *0x200b02(%rip)下一条语句的地址。
  2. jmpq 0x8000500这一条语句跳转到真实的动态解析过程,这个语句代码如下(这个小代码中主要是jmpq *0x200b04(%rip)跳转语句):
(gdb) x /5i 0x8000500
   0x8000500:   pushq  0x200b02(%rip)        # 0x8201008
   0x8000506:   jmpq   *0x200b04(%rip)        # 0x8201010
   0x800050c:   nopl   0x0(%rax)
   0x8000510 <puts@plt>:        jmpq   *0x200b02(%rip)        # 0x8201018
   0x8000516 <puts@plt+6>:      pushq  $0x0
  1. jmpq *0x200b04(%rip) # 0x8201010 调用的GOT表中的动态解析函数_dl_runtime_resolve_xsavec
  2. _dl_runtime_resolve_xsavec有两处功能:
    1. 修改GOT表的条目(为了解决不需要每次都动态解析)。
    2. 跳转到_IO_puts 执行动态库函数。

当动态解析完成知乎,整个调用关系如下:
浅谈GOT表与PLT表

过程如下:

(gdb) x /3i 0x8000650
   0x8000650 <main+22>: callq  0x8000510 <puts@plt>
   0x8000655 <main+27>: mov    $0x0,%eax
   0x800065a <main+32>: leaveq

(gdb) x /3i 0x8000510
   0x8000510 <puts@plt>:        jmpq   *0x200b02(%rip)        # 0x8201018
   0x8000516 <puts@plt+6>:      pushq  $0x0
   0x800051b <puts@plt+11>:     jmpq   0x8000500

(gdb) x /1xg 0x8201018
0x8201018:      0x00007fffff080aa0

(gdb) x /5i 0x00007fffff080aa0
   0x7fffff080aa0 <_IO_puts>:   push   %r13
   0x7fffff080aa2 <_IO_puts+2>: push   %r12
   0x7fffff080aa4 <_IO_puts+4>: mov    %rdi,%r12
   0x7fffff080aa7 <_IO_puts+7>: push   %rbp
   0x7fffff080aa8 <_IO_puts+8>: push   %rbx

4. 总结

下面我们用一个图总结一项可执行文件调用共享库函数的整个调用过程,如下:

浅谈GOT表与PLT表

上一篇:狂神Msql中 school.sql文件


下一篇:mysql-死锁问题