看了一个很牛B的帖子,关于动态链接的:
http://www.ibm.com/developerworks/cn/linux/l-dynlink/index.html。
对Linux中C程序的动态链接过程,在这里对几个关键点做一下注解。
反汇编后的汇编代码中,调用printf是通过call指令来实现的:
804838c: e8 47 ff ff ff call 80482d88048391: 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 0x4000a7400x4000a970 <_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并调用
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函数,似乎什么都没发生过。
此外
不知道我说清楚了没有,感觉说的很乱,文字也很乱。:-)
你好!除了代码,此处没有多少原创之物,皆为本人搜集、整理、总结之记录与心得,欢迎转载分享!转载时请尽量注明出处,将不胜感激。祝你健康、快乐!
linux中C是以动态链接的方式调用C库中的函数的 ? 我竟然没发现…
我一直以为那些静态的C库(二进制)在编译时已经编译进目标二进制中了,不会再链接来链接去了。
编译时你加上-static 选项,会发现一个hello,world就五六百K。
看不太懂这个语句:
ret $0×8
能再详细解释下吗?
谢谢了
关于单独的一条ret都做了些什么你应该很清除了吧?不清楚的话你就得翻翻书咯。ret n就是除了设置PC(AT&T中用%eip寄存器标识)之外还会有一个副作用,相当于addl $n, %esp;最终的目的,就是让printf能够正确地得到它需要的参数。原文中讲的已经很清楚了啊!