找到动态连接器的入口代码。
先参看《Before main() 分析》。redhat上测试,其他linux的gdb结果不一样或有问题.
=======================================================================
[test@redhat]# more test.c
#include
int main(int argc, char *argv[])
{
printf("Hello, world\n");
return 0;
}
[test@redhat]# gcc -g -o test test.c
[test@redhat]# readelf -h ./test
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x80482b0
Start of program headers: 52 (bytes into file)
Start of section headers: 2644 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 7
Size of section headers: 40 (bytes)
Number of section headers: 36
Section header string table index: 33
# test程序的入口地址是:0x80482b0
[test@redhat]# ldd ./test
linux-gate.so.1 => (0x00f28000)
libc.so.6 => /lib/libc.so.6 (0x4f93e000)
/lib/ld-linux.so.2 (0x4f921000)
[test@redhat]# readelf -h /lib/ld-linux.so.2
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x4f921810
Start of program headers: 52 (bytes into file)
Start of section headers: 120604 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 6
Size of section headers: 40 (bytes)
Number of section headers: 27
Section header string table index: 26
# 动态连接器程序/lib/ld-linux.so.2的入口地址是:0x4f921810
[test@redhat]# gdb -q ./test
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) disass _start # _start是test程序的入口地址。
Dump of assembler code for function _start:
0x080482b0 <_start+0>: xor %ebp,%ebp
0x080482b2 <_start+2>: pop %esi
0x080482b3 <_start+3>: mov %esp,%ecx
0x080482b5 <_start+5>: and $0xfffffff0,%esp
0x080482b8 <_start+8>: push %eax
0x080482b9 <_start+9>: push %esp
0x080482ba <_start+10>: push %edx
0x080482bb <_start+11>: push $0x8048380
0x080482c0 <_start+16>: push $0x8048390
0x080482c5 <_start+21>: push %ecx
0x080482c6 <_start+22>: push %esi
0x080482c7 <_start+23>: push $0x8048354
0x080482cc <_start+28>: call 0x8048284 <>
0x080482d1 <_start+33>: hlt
0x080482d2 <_start+34>: nop
0x080482d3 <_start+35>: nop
End of assembler dump.
# 现在test还没执行,但test内部定义的一些符号(比如 _start)已经可见。但是来自其他共享库的函数是看不见的。
(gdb) x/20 0x4f921810 # test程序没有执行,此时动态连接器还没映射到test的地址空间里。
0x4f921810: Cannot access memory at address 0x4f921810
(gdb) b * 0x4f921810 # 在动态连接器的起始地址下断点。
Breakpoint 1 at 0x4f921810
(gdb) r # 执行test程序
Starting program: /lzzz/test
Breakpoint 1, 0x4f921810 in ?? ()
(gdb) disass
No function contains program counter for selected frame.
(gdb) disass 0x4f921810
No function contains specified address.
(gdb) x/20i 0x4f921810 # 动态连接器的起始地址已经可以访问了,说明动态连接器已经映射到test的地址空间里了,可查看汇编指令,但看不出相关函数信息。
0x4f921810: mov %esp,%eax
0x4f921812: call 0x4f921f60
0x4f921817: mov %eax,%edi
0x4f921819: call 0x4f921800
0x4f92181e: add $0x197a2,%ebx
0x4f921824: mov 0xffffff04(%ebx),%eax
0x4f92182a: pop %edx
0x4f92182b: lea (%esp,%eax,4),%esp
0x4f92182e: sub %eax,%edx
0x4f921830: push %edx
0x4f921831: mov 0x40(%ebx),%eax
0x4f921837: lea 0x8(%esp,%edx,4),%esi
0x4f92183b: lea 0x4(%esp),%ecx
0x4f92183f: mov %esp,%ebp
0x4f921841: and $0xfffffff0,%esp
0x4f921844: push %eax
0x4f921845: push %eax
0x4f921846: push %ebp
0x4f921847: push %esi
0x4f921848: xor %ebp,%ebp
(gdb) c # 让程序完全执行1遍。
Continuing.
Hello, world
Program exited normally.
(gdb) disass 0x4f921810 # 再看动态连接器的代码,主要调用 _dl_start函数
Dump of assembler code for function _start:
0x4f921810 <_start+0>: mov %esp,%eax
0x4f921812 <_start+2>: call 0x4f921f60 <_dl_start>
End of assembler dump.
# 注意动态连接器代码也以_start开头,_start符号是在动态连接器rtld.c里定义的全局符号,start.s里也定义了一个 _start全局符号,那个才是test程序真正的入口0x080482b0。
(gdb) disass 0x4f921f60 # 动态连接器里的 _dl_start代码.
Dump of assembler code for function _dl_start:
0x4f921f60 <_dl_start+0>: push %ebp
0x4f921f61 <_dl_start+1>: mov %esp,%ebp
0x4f921f63 <_dl_start+3>: push %edi
0x4f921f64 <_dl_start+4>: push %esi
0x4f921f65 <_dl_start+5>: push %ebx
0x4f921f66 <_dl_start+6>: sub $0x3c,%esp
0x4f921f69 <_dl_start+9>: call 0x4f93672b <__i686.get_pc_thunk.bx>
0x4f921f6e <_dl_start+14>: add $0x19052,%ebx
0x4f921f74 <_dl_start+20>: mov %eax,0xffffffc8(%ebp)
0x4f921f77 <_dl_start+23>: rdtsc
0x4f921f79 <_dl_start+25>: mov %edx,0xfffffeec(%ebx)
0x4f921f7f <_dl_start+31>: mov 0x0(%ebx),%edx
0x4f921f85 <_dl_start+37>: mov %eax,0xfffffee8(%ebx)
0x4f921f8b <_dl_start+43>: lea 0xffffff38(%ebx),%eax
---Type to continue, or q to quit---
(gdb) disass _dl_start # 这个不是我们想要的_dl_start函数,估计是因为其他代码里也定义了 _dl_start变量
Dump of assembler code for function _dl_start:
0x4f953c50 <_dl_start+0>: push %ebp
0x4f953c51 <_dl_start+1>: mov %esp,%ebp
0x4f953c53 <_dl_start+3>: sub $0x8,%esp
0x4f953c56 <_dl_start+6>: call 0x4f968350
0x4f953c5b <_dl_start+11>: nop
0x4f953c5c <_dl_start+12>: lea 0x0(%esi),%esi
End of assembler dump.
(gdb) x/20 0x4f921810
0x4f921810 <_start>: 0x49e8e089 0x89000007 0xffe2e8c7 0xc381ffff
0x4f921820 <_dl_start_user+9>: 0x000197a2 0xff04838b 0x8d5affff 0xc2298424
0x4f921830 <_dl_start_user+25>: 0x40838b52 0x8d000000 0x8d089474 0x8904244c
0x4f921840 <_dl_start_user+41>: 0xf0e483e5 0x56555050 0x51e8ed31 0x8d0000d7
0x4f921850 <_dl_start_user+57>: 0xff431093 0x24248bff 0xb68de7ff 0x00000000
# _dl_start_user 也是在动态连接器rtld.c里定义的全局符号,紧接在_dl_start之后执行
(gdb) disass _dl_start_user
Dump of assembler code for function _dl_start_user:
0x4f921817 <_dl_start_user+0>: mov %eax,%edi # %eax里存放前面_dl_start()获取的_start的地址.
0x4f921819 <_dl_start_user+2>: call 0x4f921800 <_dl_sysinfo_int80+16>
0x4f92181e <_dl_start_user+7>: add $0x197a2,%ebx
0x4f921824 <_dl_start_user+13>: mov 0xffffff04(%ebx),%eax
0x4f92182a <_dl_start_user+19>: pop %edx
0x4f92182b <_dl_start_user+20>: lea (%esp,%eax,4),%esp
0x4f92182e <_dl_start_user+23>: sub %eax,%edx
0x4f921830 <_dl_start_user+25>: push %edx
0x4f921831 <_dl_start_user+26>: mov 0x40(%ebx),%eax
0x4f921837 <_dl_start_user+32>: lea 0x8(%esp,%edx,4),%esi
0x4f92183b <_dl_start_user+36>: lea 0x4(%esp),%ecx
0x4f92183f <_dl_start_user+40>: mov %esp,%ebp
0x4f921841 <_dl_start_user+42>: and $0xfffffff0,%esp
0x4f921844 <_dl_start_user+45>: push %eax
0x4f921845 <_dl_start_user+46>: push %eax
0x4f921846 <_dl_start_user+47>: push %ebp
0x4f921847 <_dl_start_user+48>: push %esi
0x4f921848 <_dl_start_user+49>: xor %ebp,%ebp
0x4f92184a <_dl_start_user+51>: call 0x4f92efa0 <_dl_init_internal>
0x4f92184f <_dl_start_user+56>: lea 0xffff4310(%ebx),%edx
0x4f921855 <_dl_start_user+62>: mov (%esp),%esp
0x4f921858 <_dl_start_user+65>: jmp *%edi # 跳转到test程序入口 _start开始执行
0x4f92185a <_dl_start_user+67>: lea 0x0(%esi),%esi
End of assembler dump.
(gdb) q
[test@redhat]#
===========================================================================
[test@redhat]# readelf -a ./test
# 可以看到 print函数在 .sybtab表 里对应的是puts函数()
[test@redhat]# gdb -q ./test
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) disass _init # 即使程序还没执行,test程序内部定义的很多符号(.symtab符号表)已经可以看到。但是来自其他共享库的函数是看不见的。随便找几个看看.
Dump of assembler code for function _init:
0x0804824c <_init+0>: push %ebp
0x0804824d <_init+1>: mov %esp,%ebp
0x0804824f <_init+3>: sub $0x8,%esp
0x08048252 <_init+6>: call 0x80482d4
0x08048257 <_init+11>: call 0x8048330
0x0804825c <_init+16>: call 0x8048400 <__do_global_ctors_aux>
0x08048261 <_init+21>: leave
0x08048262 <_init+22>: ret
End of assembler dump.
(gdb) disass __bss_start
No function contains specified address.
(gdb) disass __libc_start_main
No symbol "__libc_start_main" in current context.
(gdb) disass __i686.get_pc_thunk.bx
No symbol "__i686" in current context.
(gdb) disass __libc_csu_fini
Dump of assembler code for function __libc_csu_fini:
0x08048380 <__libc_csu_fini+0>: push %ebp
0x08048381 <__libc_csu_fini+1>: mov %esp,%ebp
0x08048383 <__libc_csu_fini+3>: pop %ebp
0x08048384 <__libc_csu_fini+4>: ret
0x08048385 <__libc_csu_fini+5>: lea 0x0(%esi),%esi
0x08048389 <__libc_csu_fini+9>: lea 0x0(%edi),%edi
End of assembler dump.
(gdb) disass puts # 此时print函数属于其他共享库,肯定也是看不见的。
No symbol "puts" in current context.
(gdb) b * 0x4f921810 # 在 动态连接器开始地址下断点。
Breakpoint 1 at 0x4f921810
(gdb) b * 0x4f921817 # 在 _dl_start_user处下断点。此时_dl_start刚刚执行完毕。
Breakpoint 2 at 0x4f921817
(gdb) r
Starting program: /lzzz/test
Breakpoint 1, 0x4f921810 in ?? ()
(gdb) disass puts # 在 _dl_start执行之前,puts函数解析不出来。
No symbol "puts" in current context.
(gdb) c
Continuing.
Breakpoint 2, 0x4f921817 in _dl_start_user () from /lib/ld-linux.so.2
(gdb) disass puts # _dl_start执行完毕。puts函数可以解析出来了。
Dump of assembler code for function puts:
0x4f9953f0
: push %ebp
0x4f9953f1 : mov %esp,%ebp
0x4f9953f3 : sub $0x1c,%esp
0x4f9953f6 : mov %ebx,0xfffffff4(%ebp)
0x4f9953f9 : mov 0x8(%ebp),%eax
0x4f9953fc : call 0x4f953c30 <__i686.get_pc_thunk.bx>
0x4f995401 : add $0xe1bf3,%ebx
0x4f995407 : mov %esi,0xfffffff8(%ebp)
0x4f99540a : mov %edi,0xfffffffc(%ebp)
0x4f99540d : mov %eax,(%esp)
0x4f995410 : call 0x4f9aa320
---Type to continue, or q to quit---q
Quit
(gdb) c
Continuing.
Hello, world
Program exited normally.
(gdb) r # 多执行几次test,可看到,在gdb里,test在第1次执行时会从动态连接器开头执行,以后总是从_dl_start_user处执行(第1断点拦截不下来了)。
Starting program: /lzzz/test
Breakpoint 2, 0x4f921817 in _dl_start_user () from /lib/ld-linux.so.2
(gdb) c
Continuing.
Hello, world
Program exited normally.
(gdb) r
Starting program: /lzzz/test
Breakpoint 2, 0x4f921817 in _dl_start_user () from /lib/ld-linux.so.2
(gdb) c
Continuing.
Hello, world
Program exited normally.
(gdb)
=============================================================================
# 执行 readelf -a ./test 在.symtab 符号表里多选择几个函数,每个都加上断点,看看他们的执行顺序
[test@redhat]# gdb -q ./test
(gdb) b * 0x4f921810 # 动态连接器开头地址
(gdb) b * _init
(gdb) b * _start
(gdb) b * __register_frame_info
(gdb) b * __deregister_frame_info
(gdb) b * __libc_start_main
(gdb) b * _main
(gdb) b * call_gmon_start
(gdb) b * __do_global_ctors_aux
(gdb) r # 开始执行程序
(gdb) c # 继续执行
# 可以看到,从动态连接器到main() 大致的执行顺序为:
_dl_start --> __register_frame_info --> _start() --> __libc_start_main -->
_init() --> call_gmon_start --> __register_frame_info --> __do_global_ctors_aux
--> main ()