先准备一份测试代码:
#include <stdio.h>
void funcUp(void)
{
printf("Hello world 1!\n");
return;
}
int main(int argc, char* argv[])
{
funcUp();
funcDown();
return 0;
}
int funcDown(void)
{
printf("Hello world 2!\n");
return 0;
}
然后编译并反汇编:
gcc -g testElf.c -o test
objdump -S test
得到以下信息(部分):
可以看出在funcUp中调用printf时,汇编中显示的是callq 4003f0 <puts@plt>,下面就来研究一下printf在进程空间中的地址。puts@plt相当于编译器设定的中间函数,只有printf函数第一次被用到时才进行绑定(地址修正)。puts@plt对应的代码:
用调试器进行分析:
gdb test
设置断点,运行程序地址才会绑定:
(gdb) break main
Breakpoint 1 at 0x400513: file testElf.c, line 12.
(gdb) run
Starting program: /home/administrator/develop/test/elf/test
Breakpoint 1, main (argc=1, argv=0x7fffffffdae8) at testElf.c:12
12 funcUp();
分析puts@plt,先跳到0x601018看看:
(gdb) x 0x601018
0x601018 <puts@got.plt>: 0x00000000004003f6
发现函数跳到了第二行,接着执行下去,到了第三行,查看0x4003e0代码:
执行下去,来到GOT表偏移0x10(第5项)的位置:
(gdb) x 0x601010
0x601010: 0x00007ffff7def210
(gdb)disassemble 0x00007ffff7def210
Dump of assembler code for function _dl_runtime_resolve:
0x00007ffff7def210 <+0>: sub $0x38,%rsp
0x00007ffff7def214 <+4>: mov %rax,(%rsp)
0x00007ffff7def218 <+8>: mov %rcx,0x8(%rsp)
0x00007ffff7def21d <+13>: mov %rdx,0x10(%rsp)
0x00007ffff7def222 <+18>: mov %rsi,0x18(%rsp)
0x00007ffff7def227 <+23>: mov %rdi,0x20(%rsp)
0x00007ffff7def22c <+28>: mov %r8,0x28(%rsp)
0x00007ffff7def231 <+33>: mov %r9,0x30(%rsp)
0x00007ffff7def236 <+38>: mov 0x40(%rsp),%rsi
0x00007ffff7def23b <+43>: mov 0x38(%rsp),%rdi
0x00007ffff7def240 <+48>: callq 0x7ffff7de8690 <_dl_fixup>
0x00007ffff7def245 <+53>: mov %rax,%r11
0x00007ffff7def248 <+56>: mov 0x30(%rsp),%r9
0x00007ffff7def24d <+61>: mov 0x28(%rsp),%r8
0x00007ffff7def252 <+66>: mov 0x20(%rsp),%rdi
0x00007ffff7def257 <+71>: mov 0x18(%rsp),%rsi
0x00007ffff7def25c <+76>: mov 0x10(%rsp),%rdx
0x00007ffff7def261 <+81>: mov 0x8(%rsp),%rcx
0x00007ffff7def266 <+86>: mov (%rsp),%rax
0x00007ffff7def26a <+90>: add $0x48,%rsp
0x00007ffff7def26e <+94>: jmpq *%r11
End of assembler dump.
用disassemble命令反汇编指定函数得到上面的结果。可以看出程序最后跳转到 _dl_runtime_resolve这个函数,这个函数是用来完成函数绑定工作的。
这时再看看puts@plt的第一条指令,跳到0x601018查看:
(gdb) x 0x601018
0x601018 <puts@got.plt>: loopne 0x600fd6
对比之前的内容。得出结论是,第一次执行时,通过_dl_runtime_resolve解析到函数地址,并保存puts的地址到0x601018里,以后执行时就直接调用。
参考:http://www.cnblogs.com/xingyun/archive/2011/12/10/2283149.html