分享

Linux动态链接的实现方式

 astrotycoon 2015-11-18

  看了一个很牛B的帖子,关于动态链接的:
http://www.ibm.com/developerworks/cn/linux/l-dynlink/index.html
  对Linux中C程序的动态链接过程,在这里对几个关键点做一下注解。
  反汇编后的汇编代码中,调用printf是通过call指令来实现的:

 804838c:       e8 47 ff ff ff          call   80482d8 
 8048391:       b8 00 00 00 00          mov    $0x0,%eax

  其中,0x80482d8是printf在plt中的地址(逻辑地址)。plt是结构这样的:

Disassembly of section .plt:

080482a8 <__gmon_start__@plt-0x10>:
 80482a8:       ff 35 60 95 04 08       pushl  0x8049560
 80482ae:       ff 25 64 95 04 08       jmp    *0x8049564
 80482b4:       00 00                   add    %al,(%eax)
080482b8 <__gmon_start__@plt>:
 80482b8:       ff 25 68 95 04 08       jmp    *0x8049568
 80482be:       68 00 00 00 00          push   $0x0
 80482c3:       e9 e0 ff ff ff          jmp    80482a8 <_init+0x30>
080482c8 <__libc_start_main@plt>:
 80482c8:       ff 25 6c 95 04 08       jmp    *0x804956c
 80482ce:       68 08 00 00 00          push   $0x8
 80482d3:       e9 d0 ff ff ff          jmp    80482a8 <_init+0x30>
080482d8 < printf @ plt>:
 80482d8:       ff 25 70 95 04 08       jmp    *0x8049570
 80482de:       68 10 00 00 00          push   $0x10
 80482e3:       e9 c0 ff ff ff          jmp    80482a8 <_init+0x30>

  在call指令执行后执行

1
jmp    *0x8049570

  程序跳转到地址(addr1)0x8049570存储的地址(addr2)处,即*addr1 == addr2,若包含printf的库libc.so尚未加载,这时addr2的值就是0x080482de,即指令push $0x10。将0x10压入堆栈,用来定位printf在libc.so的位置。接下来的jmp指令将程序定位在0x80482a8,即<__gmon_start__@plt-0x10>处,将0x8049560压入堆栈。jmp *0x8049564指令是程序跳转到一个固定的加载程序处,即原帖中的function _dl_runtime_resolve:

Dump of assembler code for function _dl_runtime_resolve:
0x4000a960 <_dl_runtime_resolve>:       pushl  %eax
0x4000a961 <_dl_runtime_resolve+1>:     pushl  %ecx
0x4000a962 <_dl_runtime_resolve+2>:     pushl  %edx
0x4000a963 <_dl_runtime_resolve+3>:     movl   0x10(%esp,1),%edx
0x4000a967 <_dl_runtime_resolve+7>:     movl   0xc(%esp,1),%eax
0x4000a96b <_dl_runtime_resolve+11>:    call   0x4000a740 
0x4000a970 <_dl_runtime_resolve+16>:    popl   %edx
0x4000a971 <_dl_runtime_resolve+17>:    popl   %ecx
0x4000a972 <_dl_runtime_resolve+18>:    xchgl  %eax,(%esp,1)
0x4000a975 <_dl_runtime_resolve+21>:    ret    $0x8
0x4000a978 <_dl_runtime_resolve+24>:    nop
0x4000a979 <_dl_runtime_resolve+25>:    leal   0x0(%esi,1),%esi
End of assembler dump.

  此时的堆栈情况见原帖。以下三条指令,将0x10和0x8049560作为参数放入寄存器edx、eax并调用用这两个参数定位并装入printf所在的库libc.so,并将printf的地址放入寄存器eax:

0x4000a963 <_dl_runtime_resolve+3>:     movl   0x10(%esp,1),%edx
0x4000a967 <_dl_runtime_resolve+7>:     movl   0xc(%esp,1),%eax
0x4000a96b <_dl_runtime_resolve+11>:    call   0x4000a740 

  指令xchgl %eax,(%esp,1)将printf的地址放入栈顶。最精彩的一条指令当属ret $0x8,它将栈顶元素即printf的地址弹出至程序计数器PC,作为下一条将执行的指令地址,同时,清除堆栈中的0x10和0x8049560。此时堆栈中的情形,就如同直接调用了printf函数,似乎什么都没发生过。

  此外还做了一件重要的工作,就是把前面提到的addr2替换为printf的地址。从而当再次调用printf时jmp *addr1就直接将程序定位到printf,不需要再次加载库libc.so了。

  不知道我说清楚了没有,感觉说的很乱,文字也很乱。:-)

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多