看了一个很牛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)0×8049570存储的地址(addr2)处,即*addr1 == addr2,若包含printf的库libc.so尚未加载,这时addr2的值就是0x080482de,即指令push $0×10。将0×10压入堆栈,用来定位printf在libc.so的位置。接下来的jmp指令将程序定位在0x80482a8,即<__gmon_start__@plt-0x10>处,将0×8049560压入堆栈。jmp *0×8049564指令是程序跳转到一个固定的加载程序处,即原帖中的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.

  此时的堆栈情况见原帖。以下三条指令,将0×10和0×8049560作为参数放入寄存器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 $0×8,它将栈顶元素即printf的地址弹出至程序计数器PC,作为下一条将执行的指令地址,同时,清除堆栈中的0×10和0×8049560。此时堆栈中的情形,就如同直接调用了printf函数,似乎什么都没发生过。

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

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

Tags: ,,.
你好!除了代码,此处没有多少原创之物,皆为本人搜集、整理、总结之记录与心得,欢迎转载分享!转载时请尽量注明出处,将不胜感激。祝你健康、快乐!
Home

RFC: Request For Comments. Orz..

Name(required)
Mail (required),(will not be published)

RFC: Request For Comments. Orz..

Website(recommended)