linux 动态定向,Linux动态链接库函数加载之GOT与Lazy Binding

Linux在运行与位置无关PIC(Position IndependentCode)的用户态程序并加载动态链接库时,函数符号的解析过程将涉及到全局偏移量表GOT(Global Offset Table)与Lazy Binding("懒绑定")。

在位置无关代码PIC中一般不能包含动态链接库中符号的绝对地址。当运行某个调用动态库函数符号的用户态程序时,用户态程序在编译链接阶段并不知晓该符号的具体位置,只有等到运行阶段,动态加载器将所需要的共享库加载到内存后,才最终确定符号的地址。而在编译阶段所有与位置无关的函数调用都将被保存到ELF文件的过程链接表PLT(Procedure Linkage Table)中。

现在通过GDB调试演示Glibc动态链接库中"syscall"函数的动态解析过程。源代码如下:

1  /* syscall_test.cpp-- GOT and Lazy Binding mechanism debugging */

16  /* Sandbox needs to be configured with policy by init_sandbox.

17   * New syscall 400 are reserved to config policy, then switch filter on!

18   * but funcode=20 is specially reserved to directly ret -1 for debugging.

19   */

20

21  int init_sandbox()

22  {

…       /*policy configuration details ignored*/

51  }

52

53  int main()

54  {

55      init_sandbox();

56      // here ignore the ret value check for init_sandbox

57      // try to see if (400,20) return -1 or not

58      int x = syscall(400,20);

59      cout <

65      return 0;

66  }

首先,反汇编包用户态程序,基本工具为readelf和objdump:

118081367_1_20171204074520225

在编译阶段生成的ELF文件中,用于获取实际syscall地址的syscall@plt函数代码只有寥寥3条指令,事实上过程链接表PLT中的每个子函数都只有3条指令。

现在来分析syscall@plt:第1条指令”jmpq *0x20228a(%rip)”会跳转至GOT表中的某个位置。如果此时syscall是第一次被调用,那么GOT表中相应位置保存的是0x00400e06,即第2条指令的位置;如果syccall函数地址之前已经被解析完成,那么GOT表中相应位置保存的便是函数的实际地址,此时syscall@plt的第2,3条指令便不会被执行。这种机制就是所谓的Lazy Binding,即当且仅当动态库中函数符号第一次被调用时才去解析函数的实际位置,然后交由相应过程链接表PLT中的第2,3条指令完成函数实际地址的解析工作。也就是说,动态链接库中的函数符号只有在第一次被调用时,才会被定位绑定到其函数实际地址,这也是术语Lazy

Binding名字的来源。

然后,继续分析syscall@plt的第2条指令”pushq  $0xf”,将立即数$0xf压栈,该立即数的含义为函数名”syscall”在重定向段”.rela.plt”中的偏移量下标。运行时地址重定向的相关数据结构定义如下:

/* Relocation table entry with addend (in section of type SHT_RELA).  */

typedef struct

{

Elf64_Addr            r_offset;           /* Address */

Elf64_Xword   r_info;             /* Relocation type and symbol index */

Elf64_Sxword  r_addend;       /* Addend */

} Elf64_Rela;

静态反汇编之后的重定向符号段.rela.plt如下所示:

[elvis@localhost CentOS7]$ readelf --relocs syscall_test

Relocation section '.rela.dyn' at offset 0xa08 contains 2 entries:

Offset          Info           Type           Sym. Value    Sym. Name + Addend

000000602ff8  000500000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0

000000603100  002200000005 R_X86_64_COPY     0000000000603100 _ZSt4cout + 0

Relocation section '.rela.plt' at offset 0xa38 contains 28 entries:

Offset          Info           Type           Sym. Value    Sym. Name + Addend

000000603018  000100000007 R_X86_64_JUMP_SLO 0000000000000000 _ZNSs6appendEPKcm + 0

000000603020  000200000007 R_X86_64_JUMP_SLO 0000000000000000 _ZNSsC1Ev + 0

000000603028  000300000007 R_X86_64_JUMP_SLO 0000000000000000 _ZNSolsEi + 0

000000603030  000400000007 R_X86_64_JUMP_SLO 0000000000000000 snprintf + 0

000000603038  000500000007 R_X86_64_JUMP_SLO 0000000000000000 __gmon_start__ + 0

000000603040  000700000007 R_X86_64_JUMP_SLO 0000000000000000 _ZNKSs5c_strEv + 0

000000603048  000800000007 R_X86_64_JUMP_SLO 0000000000000000 _ZNSs7reserveEm + 0

000000603050  000900000007 R_X86_64_JUMP_SLO 0000000000000000 _ZNKSs4sizeEv + 0

000000603058  000a00000007 R_X86_64_JUMP_SLO 0000000000000000 _ZNSt8ios_base4InitC1E+0

000000603060  000b00000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main + 0

000000603068  000c00000007 R_X86_64_JUMP_SLO 0000000000000000 _ZNSsC1ERKSs + 0

000000603070  000d00000007 R_X86_64_JUMP_SLO 0000000000000000 __cxa_atexit + 0

000000603078  002100000007 R_X86_64_JUMP_SLO 0000000000400dd0 _ZNSt8ios_base4InitD1E+0

000000603080  000f00000007 R_X86_64_JUMP_SLO 0000000000000000 _ZStlsISt11char_traits+0

000000603088  001000000007 R_X86_64_JUMP_SLO 0000000000000000 _ZNSsD1Ev + 0

000000603090  001200000007 R_X86_64_JUMP_SLO 0000000000000000 syscall + 0

000000603098  001300000007 R_X86_64_JUMP_SLO 0000000000000000 _ZNSsC1EPKcRKSaIcE + 0

0000006030a0  001400000007 R_X86_64_JUMP_SLO 0000000000000000 _ZNSaIcED1Ev + 0

0000006030a8  001600000007 R_X86_64_JUMP_SLO 0000000000000000 _ZNSs6appendEPKc + 0

0000006030b0  001700000007 R_X86_64_JUMP_SLO 0000000000000000 _ZNSolsEPFRSoS_E + 0

0000006030b8  002000000007 R_X86_64_JUMP_SLO 0000000000400e50 _ZSt4endlIcSt11char_tr+0

0000006030c0  001800000007 R_X86_64_JUMP_SLO 0000000000000000 _ZNSaIcEC1Ev + 0

0000006030c8  002300000007 R_X86_64_JUMP_SLO 0000000000400e70 __gxx_personality_v0 + 0

0000006030d0  001900000007 R_X86_64_JUMP_SLO 0000000000000000 localtime + 0

0000006030d8  001a00000007 R_X86_64_JUMP_SLO 0000000000000000 _Unwind_Resume + 0

0000006030e0  001b00000007 R_X86_64_JUMP_SLO 0000000000000000 _ZNSs6appendERKSs + 0

0000006030e8  001c00000007 R_X86_64_JUMP_SLO 0000000000000000 open + 0

0000006030f0  001d00000007 R_X86_64_JUMP_SLO 0000000000000000 time + 0

最后,分析syscall@plt的第3条指令,其将跳转至PLT表的第一个子函数继续执行:将GOT[1]压栈,然后跳转至GOT[2]保存的函数地址继续执行。至此,代码执行流完成了从过程链接表PLT表到全局偏移量表GOT表的跳转。

关于全局偏移量表GOT,其相关定义如下:

/* The GOT entries for functions in the PLT have not yet been filled

* in.  Their initial contents will arrange when called to push an

* offset into the .rel.plt section, push _GLOBAL_OFFSET_TABLE_[1],

* and then jump to _GLOBAL_OFFSET_TABLE_[2].  */

* got = (Elf64_Addr *) D_PTR (l, l_info[DT_PLTGOT]);

* If a library is prelinked but we have to relocate anyway,

* we have to be able to undo the prelinking of .got.plt.

* The prelinker saved us here address of .plt + 0x16.

*

* The got[2] entry contains the address of a function which gets

* called to get the address of a so far unresolved function and

* jump to it.  The profiling extension of the dynamic linker allows

* to intercept the calls to collect information.  In this case we

* don't store the address in the GOT so that all future calls also

* end in this function.

*/

Elf64_Addr *got;

即:GOT表中每一项都是64bit的Elf64_Addr地址,但其中GOT表前三项用于保存特殊的数据结构地址:

GOT[0]为段”.dynamic”的加载地址。

GOT[1]为ELF所依赖的动态链接库链表头struct link_map结构体描述符地址。

GOT[2]为_dl_runtime_resolve函数地址,即GOT[2] =&_dl_runtime_resolve,该函数的作用是遍历GOT[1]指向的动态链接库链表直至找到某个符号的地址,然后将该符号地址保存至相应的GOT表项中。

通过GDB调试动态链接库符号解析的过程详情如下:

118081367_2_2017120407452184

紧接着上图反汇编代码(由于上图里面有个箭头需要以截图表示)

0x00007ffff7df0263 :  nopw   0x30(%rsp)

0x00007ffff7df0269 :  nopw   0x20(%rsp)

0x00007ffff7df026f :  nopw   0x10(%rsp)

0x00007ffff7df0275 : nopw   (%rsp)

0x00007ffff7df027a : mov    0x70(%rsp),%r9

0x00007ffff7df027f : mov    0x68(%rsp),%r8

0x00007ffff7df0284 : mov    0x60(%rsp),%rdi

0x00007ffff7df0289 : mov    0x58(%rsp),%rsi

0x00007ffff7df028e : mov    0x50(%rsp),%rdx

0x00007ffff7df0293 : mov    0x48(%rsp),%rcx

0x00007ffff7df0298 : mov    0x40(%rsp),%rax

0x00007ffff7df029d : add    $0x88,%rsp

0x00007ffff7df02a4 : jmpq   *%r11

End of assembler dump.

(gdb) ni

Breakpoint 4, 0x0000000000401898 in main () at syscall_test.cpp:58

58      int x = syscall(324,20);

3: $rip = (void (*)(void)) 0x401898 

2: $eip = void

1: $rax = 0

(gdb) disass main

Dump of assembler code for function main():

0x000000000040187c :   push   %rbp

0x000000000040187d :   mov    %rsp,%rbp

0x0000000000401880 :   sub    $0x10,%rsp

0x0000000000401884 :   callq  0x400fda 

0x0000000000401889 :mov    $0x14,%esi

0x000000000040188e :mov    $0x190,%edi

0x0000000000401893 :mov    $0x0,%eax

=> 0x0000000000401898 :callq  0x400e00 

0x000000000040189d :mov    %eax,-0x8(%rbp)

(gdb) info registers

rax            0x0  0

...

rip            0x401898 0x401898 

eflags         0x202    [ IF ]

...

(gdb) si

0x0000000000400e00 in syscall@plt ()

(gdb) disass $rip

Dump of assembler code for function syscall@plt:

=> 0x0000000000400e00 :     jmpq  *0x20228a(%rip) #0x603090 

0x0000000000400e06 :    pushq $0xf

0x0000000000400e0b :jmpq  0x400d00

End of assembler dump.

(gdb) bt

#0  0x00007ffff72ea9a1 in syscall () from /lib64/libc.so.6

#1  0x000000000040189d in main () at syscall_test.cpp:58

(gdb) disass $rip

Dump of assembler code for function syscall:

0x00007ffff72ea980 :   mov   %rdi,%rax

0x00007ffff72ea983 :   mov   %rsi,%rdi

0x00007ffff72ea986 :   mov   %rdx,%rsi

0x00007ffff72ea989 :   mov   %rcx,%rdx

0x00007ffff72ea98c :mov   %r8,%r10

0x00007ffff72ea98f :mov   %r9,%r8

0x00007ffff72ea992 :mov   0x8(%rsp),%r9

0x00007ffff72ea997 :syscall

0x00007ffff72ea999 :cmp   $0xfffffffffffff001,%rax

0x00007ffff72ea99f :jae   0x7ffff72ea9a2 

=> 0x00007ffff72ea9a1 :retq

0x00007ffff72ea9a2 :mov   0x2c94bf(%rip),%rcx # 0x7ffff75b3e68

0x00007ffff72ea9a9 :neg   %eax

0x00007ffff72ea9ab :mov   %eax,%fs:(%rcx)

0x00007ffff72ea9ae :or    $0xffffffffffffffff,%rax

0x00007ffff72ea9b2 :retq

End of assembler dump.

(gdb) info registers

rax            0xfffffffd   4294967293

...

rip            0x7ffff72ea9a1   0x7ffff72ea9a1 

eflags         0x207    [ CF PF IF ]

...

至此,可以明晰Linux中的程序在使用Glibc所封装的系统调用过程中涉及的懒绑定技术原理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值