前面写过一篇北极之北之main函数之前,这篇文章其实解决的问题是main函数并不是第一个执行的函数,在main之前,函数的入口点是_start, _start会调用glibc里的__libc_start_main,main函数只是这个函数的入参。 在__libc_start_main中某一步,会执行main函数。这是上面一篇博文获取到的知识。
前两天,CU的gongping11写了一个博文atexit可以注册退出函数,在main之后,执行注册退出函数。讲的非常的好,我们在gongping11的基础上再进一步。
我写北极之北那篇博文之后,我发现了一个很好的blog,国外的这个大侠分享了很多ELF方面的宝贝,我也给别人推荐过这个blog, 如果英文实力比较高的筒子可以去看那篇博客。但是那篇博客稍微有点老了,有些过时的内容。但是我还是强力推荐。~charngda/elf.html。
- #include <stdio.h>
- #include <stdlib.h>
- void preinit(int argc, char **argv, char **envp) {
- printf("%s\n", __FUNCTION__);
- }
- void init(int argc, char **argv, char **envp) {
- printf("%s\n", __FUNCTION__);
- }
- void fini() {
- printf("%s\n", __FUNCTION__);
- }
- __attribute__((section(".init_array"))) typeof(init) *__init = init;
- __attribute__((section(".preinit_array"))) typeof(preinit) *__preinit = preinit;
- __attribute__((section(".fini_array"))) typeof(fini) *__fini = fini;
- void __attribute__ ((constructor)) constructor() {
- printf("%s\n", __FUNCTION__);
- }
- void __attribute__ ((constructor)) constructor_2() {
- printf("%s\n", __FUNCTION__);
- }
- void __attribute__ ((destructor)) destructor() {
- printf("%s\n", __FUNCTION__);
- }
- void __attribute__ ((destructor)) destructor_2() {
- printf("%s\n", __FUNCTION__);
- }
- void my_atexit() {
- printf("%s\n", __FUNCTION__);
- }
- void my_atexit2() {
- printf("%s\n", __FUNCTION__);
- }
- int main() {
- atexit(my_atexit);
- atexit(my_atexit2);
- printf("%s\n",__FUNCTION__);
- }
我们写了好多的函数,我们定义了preinit_array,init_array, fini_array这些段,我们也定义了多个construct和destructor,还用atexit注册了两个函数,这是广撒英雄帖,把能召唤的兄弟们都聚集齐了。看执行结果:
- root@manu:~/code/c/self/initfini# ./test
- preinit
- init
- constructor_2
- constructor
- main
- my_atexit2
- my_atexit
- destructor
- destructor_2
- fini
为什么会这样?问题的答案在libc里面。好在我们有glibc的代码,这也不算啥,我们先分析下main函数之前执行的动作,以及他们的先后顺序。
我的glibc的版本号是:
- root@manu:~/code/c/self/initfini# ldd test
- linux-gate.so.1 => (0xb773c000)
- libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7578000)
- /lib/ld-linux.so.2 (0xb773d000)
- root@manu:~/code/c/self/initfini# ll /lib/i386-linux-gnu/libc.so.6
- lrwxrwxrwx 1 root root 12 10月 6 04:39 /lib/i386-linux-gnu/libc.so.6 -> libc-2.15.so*
我去网上下载了一份glibc的2.15版本的code,准备工作就绪,我们开始调试和研究。
- (gdb) b preinit
- Breakpoint 1 at 0x804843a: file test.c, line 5.
- (gdb) r
- Starting program: /home/manu/code/c/self/initfini/test
- Breakpoint 1, preinit (argc=1, argv=0xbffff774, envp=0xbffff77c) at test.c:5
- 5 printf("%s\n", __FUNCTION__);
- (gdb) bt
- #0 preinit (argc=1, argv=0xbffff774, envp=0xbffff77c) at test.c:5
- #1 0xb7fecfd2 in _dl_init (main_map=0xb7fff918, argc=1, argv=0xbffff774, env=0xbffff77c) at dl-init.c:119
- #2 0xb7fdf20f in _dl_start_user () from /lib/ld-linux.so.2
_dl_start_user调用了_dl_init.我们看下_dl_init的函数定义:- void
- internal_function
- _dl_init (struct link_map *main_map, int argc, char **argv, char **env)
- {
- ElfW(Dyn) *preinit_array = main_map->l_info[DT_PREINIT_ARRAY];
- ElfW(Dyn) *preinit_array_size = main_map->l_info[DT_PREINIT_ARRAYSZ];
- unsigned int i;
- if (__builtin_expect (GL(dl_initfirst) != NULL, 0))
- {
- call_init (GL(dl_initfirst), argc, argv, env);
- GL(dl_initfirst) = NULL;
- }
- /* Don't do anything if there is no preinit array. */
- if (__builtin_expect (preinit_array != NULL, 0)
- && preinit_array_size != NULL
- && (i = preinit_array_size->d_un.d_val / sizeof (ElfW(Addr))) > 0)
- {
- ElfW(Addr) *addrs;
- unsigned int cnt;
- if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_IMPCALLS, 0))
- _dl_debug_printf ("\ncalling preinit: %s\n\n",
- main_map->l_name[0]
- ? main_map->l_name : rtld_progname);
- addrs = (ElfW(Addr) *) (preinit_array->d_un.d_ptr + main_map->l_addr);
- for (cnt = 0; cnt < i; ++cnt)
- ((init_t) addrs[cnt]) (argc, argv, env);
- }
- /* Stupid users forced the ELF specification to be changed. It now
- says that the dynamic loader is responsible for determining the
- order in which the constructors have to run. The constructors
- for all dependencies of an object must run before the constructor
- for the object itself. Circular dependencies are left unspecified.
- This is highly questionable since it puts the burden on the dynamic
- loader which has to find the dependencies at runtime instead of
- letting the user do it right. Stupidity */
- i = main_map->l_searchlist.r_nlist;
- while (i-- > 0)
- call_init (main_map->l_initfini[i], argc, argv, env);
- #ifndef HAVE_INLINED_SYSCALLS
- /* Finished starting up. */
- INTUSE(_dl_starting_up) = 0;
- #endif
- }
加粗的两行是比较重要的代码,call_init我不太明白是干啥的,以后看。但是preinit相关的内容我看懂了,就是察看下有没有preinit_array这个段,如果有的话,执行。很明显,我们是有这个段的。
下面我调试了下,
注意DT_PREINIT_ARRAY = 32 , DT_PREINIT_ARRAYSZ=33,这是定义在头文件的宏。- (gdb) p main_map->l_info[32]
- $5 = (Elf32_Dyn *) 0x8049f0c
- $7 = {d_tag = 0x20, d_un = {d_val = 0x8049ec4, d_ptr = 0x8049ec4}}
- (gdb) p/x *(main_map->l_info[33])
- $8 = {d_tag = 0x21, d_un = {d_val = 0x4, d_ptr = 0x4}}
- (gdb) x/4x 0x08049ec4
- 0x8049ec4 <__preinit>: 0x08048434 0x08048448 0x08048484 0x08048470
我们可以看到这段代码就是查找preinit段的信息,并执行对应的函数。0x8049ec4和我们调用readelf -S获取的preinit的值是一样的。 最后我们得到了这个段对应的function的地址,也就是代码做的事情。- addrs = (ElfW(Addr) *) (preinit_array->d_un.d_ptr + main_map->l_addr);
- for (cnt = 0; cnt < i; ++cnt)
- ((init_t) addrs[cnt]) (argc, argv, env);
通过计算我们得到0x8049ec4存储的就是function的地址0x08048434。- 08048434 ::
- 8048434: 55 push %ebp
- 8048435: 89 e5 mov %esp,%ebp
- 8048437: 83 ec 18 sub $0x18,%esp
- 804843a: c7 04 24 86 86 04 08 movl $0x8048686,(%esp)
- 8048441: e8 0a ff ff ff call 8048350
- 8048446: c9 leave
- 8048447: c3 ret
OK ,我们preinit段对应的代码就分析结束了,值得一提的是,此时,我们的入口点_start还没有执行,我可以证明下: - (gdb) b _start
- Breakpoint 2 at 0x8048380
- (gdb) b init
- Breakpoint 3 at 0x804844e: init. (5 locations)
- (gdb) b __libc_start_main
- Breakpoint 4 at 0xb7e323e0: file libc-start.c, line 96.
- (gdb) c
- Continuing.
- preinit
- Breakpoint 2, 0x08048380 in _start ()
- (gdb) c
Continuing.
Breakpoint 4, __libc_start_main (main=0x80484e8 , argc=1, ubp_av=0xbffff774, init=0x8048520 <__libc_csu_init>,
fini=0x8048590 <__libc_csu_fini>, rtld_fini=0xb7fed270 <_dl_fini>, stack_end=0xbffff76c) at libc-start.c:96
96 libc-start.c: 没有那个文件或目录.
(gdb) c
Continuing.
Breakpoint 3, init (argc=1, argv=0xbffff774, envp=0xbffff77c) at test.c:9
9 printf("%s\n", __FUNCTION__);
- (gdb) bt
#0 init (argc=1, argv=0xbffff774, envp=0xbffff77c) at test.c:9
#1 0x08048572 in __libc_csu_init ()
#2 0xb7e3246a in __libc_start_main (main=0x80484e8 , argc=1, ubp_av=0xbffff774, init=0x8048520 <__libc_csu_init>,
fini=0x8048590 <__libc_csu_fini>, rtld_fini=0xb7fed270 <_dl_fini>, stack_end=0xbffff76c) at libc-start.c:185
#3 0x080483a1 in _start ()
_start->__libc_start_main->__libc_csu_init->init,脉络是这样的,OK ,我们先看下__libc_csu_init.- void
- __libc_csu_init (int argc, char **argv, char **envp)
- {
- /* For dynamically linked executables the preinit array is executed by
- the dynamic linker (before initializing any shared object). */
- #ifndef LIBC_NONSHARED
- /* For static executables, preinit happens right before init. */
- {
- const size_t size = __preinit_array_end - __preinit_array_start;
- size_t i;
- for (i = 0; i < size; i++)
- (*__preinit_array_start [i]) (argc, argv, envp);
- }
- #endif
- _init ();
- const size_t size = __init_array_end - __init_array_start;
- for (size_t i = 0; i < size; i++)
- (*__init_array_start [i]) (argc, argv, envp);
- }
_init中会执行construct的代码, __init_array_start[i] 处执行init_array段的函数,所以我们可以看出执行顺序如下:
- Function pointers in .preinit_array section ,before _start
- Functions marked as __attribute__ ((constructor)), via _init
- Function pointers in .init_array section
_init函数的是定义在 /sysdeps/unix/sysv/linux/init-first.c,最后的最后,执行了_libc_global_ctors ,这就是我们说的marked as
__attribute__ ((constructor)) 的函数。
OK ,main函数之前的事情都了了,本来把退出一起写了,但是文章太长了,而且太晚了就写这些吧。
还是有很多东西不懂,没办法,一口吃不成胖子,心急吃不了热豆腐,还是得慢慢来。
参考文献
1 ~charngda/elf.html/
2 程序员的自我修养
阅读(322) | 评论(0) | 转发(0) |