main之前

1071阅读 0评论2012-12-26 qizheguang
分类:

    前面写过一篇北极之北之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。

  1. #include <stdio.h>
  2. #include <stdlib.h>

  3. void preinit(int argc, char **argv, char **envp) {
  4.     printf("%s\n", __FUNCTION__);
  5. }

  6. void init(int argc, char **argv, char **envp) {
  7.     printf("%s\n", __FUNCTION__);
  8. }

  9. void fini() {
  10.     printf("%s\n", __FUNCTION__);
  11. }

  12. __attribute__((section(".init_array"))) typeof(init) *__init = init;
  13. __attribute__((section(".preinit_array"))) typeof(preinit) *__preinit = preinit;
  14. __attribute__((section(".fini_array"))) typeof(fini) *__fini = fini;

  15. void __attribute__ ((constructor)) constructor() {
  16.     printf("%s\n", __FUNCTION__);
  17. }

  18. void __attribute__ ((constructor)) constructor_2() {
  19.     printf("%s\n", __FUNCTION__);
  20. }
  21. void __attribute__ ((destructor)) destructor() {
  22.     printf("%s\n", __FUNCTION__);
  23. }

  24. void __attribute__ ((destructor)) destructor_2() {
  25.     printf("%s\n", __FUNCTION__);
  26. }
  27. void my_atexit() {
  28.     printf("%s\n", __FUNCTION__);
  29. }

  30. void my_atexit2() {
  31.     printf("%s\n", __FUNCTION__);
  32. }

  33. int main() {
  34.     atexit(my_atexit);
  35.     atexit(my_atexit2);
  36.     printf("%s\n",__FUNCTION__);
  37. }
    我们写了好多的函数,我们定义了preinit_array,init_array, fini_array这些段,我们也定义了多个construct和destructor,还用atexit注册了两个函数,这是广撒英雄帖,把能召唤的兄弟们都聚集齐了。看执行结果:

  1. root@manu:~/code/c/self/initfini# ./test
  2. preinit
  3. init
  4. constructor_2
  5. constructor
  6. main
  7. my_atexit2
  8. my_atexit
  9. destructor
  10. destructor_2
  11. fini
    为什么会这样?问题的答案在libc里面。好在我们有glibc的代码,这也不算啥,我们先分析下main函数之前执行的动作,以及他们的先后顺序。
    我的glibc的版本号是:
   

  1. root@manu:~/code/c/self/initfini# ldd test
  2. linux-gate.so.1 => (0xb773c000)
  3. libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7578000)
  4. /lib/ld-linux.so.2 (0xb773d000)
  5. root@manu:~/code/c/self/initfini# ll /lib/i386-linux-gnu/libc.so.6
  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,准备工作就绪,我们开始调试和研究。

  1. (gdb) b preinit
  2. Breakpoint 1 at 0x804843a: file test.c, line 5.
  3. (gdb) r
  4. Starting program: /home/manu/code/c/self/initfini/test

  5. Breakpoint 1, preinit (argc=1, argv=0xbffff774, envp=0xbffff77c) at test.c:5
  6. 5 printf("%s\n", __FUNCTION__);
  7. (gdb) bt
  8. #0 preinit (argc=1, argv=0xbffff774, envp=0xbffff77c) at test.c:5
  9. #1 0xb7fecfd2 in _dl_init (main_map=0xb7fff918, argc=1, argv=0xbffff774, env=0xbffff77c) at dl-init.c:119
  10. #2 0xb7fdf20f in _dl_start_user () from /lib/ld-linux.so.2
    _dl_start_user调用了_dl_init.我们看下_dl_init的函数定义:

  1. void
  2. internal_function
  3. _dl_init (struct link_map *main_map, int argc, char **argv, char **env)
  4. {
  5.   ElfW(Dyn) *preinit_array = main_map->l_info[DT_PREINIT_ARRAY];
  6.   ElfW(Dyn) *preinit_array_size = main_map->l_info[DT_PREINIT_ARRAYSZ];
  7.   unsigned int i;

  8.   if (__builtin_expect (GL(dl_initfirst) != NULL, 0))
  9.     {
  10.       call_init (GL(dl_initfirst), argc, argv, env);
  11.       GL(dl_initfirst) = NULL;
  12.     }

  13.   /* Don't do anything if there is no preinit array. */
  14.   if (__builtin_expect (preinit_array != NULL, 0)
  15.       && preinit_array_size != NULL
  16.       && (i = preinit_array_size->d_un.d_val / sizeof (ElfW(Addr))) > 0)
  17.     {
  18.       ElfW(Addr) *addrs;
  19.       unsigned int cnt;

  20.       if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_IMPCALLS, 0))
  21.     _dl_debug_printf ("\ncalling preinit: %s\n\n",
  22.              main_map->l_name[0]
  23.              ? main_map->l_name : rtld_progname);

  24.       addrs = (ElfW(Addr) *) (preinit_array->d_un.d_ptr + main_map->l_addr);
  25.       for (cnt = 0; cnt < i; ++cnt)
  26.     ((init_t) addrs[cnt]) (argc, argv, env);
  27.     }

  28.   /* Stupid users forced the ELF specification to be changed. It now
  29.      says that the dynamic loader is responsible for determining the
  30.      order in which the constructors have to run. The constructors
  31.      for all dependencies of an object must run before the constructor
  32.      for the object itself. Circular dependencies are left unspecified.

  33.      This is highly questionable since it puts the burden on the dynamic
  34.      loader which has to find the dependencies at runtime instead of
  35.      letting the user do it right. Stupidity */

  36.   i = main_map->l_searchlist.r_nlist;
  37.   while (i-- > 0)
  38.     call_init (main_map->l_initfini[i], argc, argv, env);

  39. #ifndef HAVE_INLINED_SYSCALLS
  40.   /* Finished starting up. */
  41.   INTUSE(_dl_starting_up) = 0;
  42. #endif
  43. }
    加粗的两行是比较重要的代码,call_init我不太明白是干啥的,以后看。但是preinit相关的内容我看懂了,就是察看下有没有preinit_array这个段,如果有的话,执行。很明显,我们是有这个段的。


   
    下面我调试了下,
    注意DT_PREINIT_ARRAY = 32 , DT_PREINIT_ARRAYSZ=33,这是定义在头文件的宏。
  1. (gdb) p main_map->l_info[32]
  2. $5 = (Elf32_Dyn *) 0x8049f0c

  3. $7 = {d_tag = 0x20, d_un = {d_val = 0x8049ec4, d_ptr = 0x8049ec4}}
  4. (gdb) p/x *(main_map->l_info[33])
  5. $8 = {d_tag = 0x21, d_un = {d_val = 0x4, d_ptr = 0x4}}

  6. (gdb) x/4x 0x08049ec4
  7. 0x8049ec4 <__preinit>:    0x08048434    0x08048448    0x08048484    0x08048470
    我们可以看到这段代码就是查找preinit段的信息,并执行对应的函数。0x8049ec4和我们调用readelf -S获取的preinit的值是一样的。 最后我们得到了这个段对应的function的地址,也就是代码做的事情。

  1. addrs = (ElfW(Addr) *) (preinit_array->d_un.d_ptr + main_map->l_addr);
  2.       for (cnt = 0; cnt < i; ++cnt)
  3.     ((init_t) addrs[cnt]) (argc, argv, env);
    通过计算我们得到0x8049ec4存储的就是function的地址0x08048434

  1. 08048434 ::
  2.  8048434:    55     push %ebp
  3.  8048435:    89 e5     mov %esp,%ebp
  4.  8048437:    83 ec 18     sub $0x18,%esp
  5.  804843a:    c7 04 24 86 86 04 08     movl $0x8048686,(%esp)
  6.  8048441:    e8 0a ff ff ff     call 8048350
  7.  8048446:    c9     leave
  8.  8048447:    c3     ret



    OK ,我们preinit段对应的代码就分析结束了,值得一提的是,此时,我们的入口点_start还没有执行,我可以证明下:
  
  1. (gdb) b _start
  2. Breakpoint 2 at 0x8048380
  3. (gdb) b init
  4. Breakpoint 3 at 0x804844e: init. (5 locations)
  5. (gdb) b __libc_start_main
  6. Breakpoint 4 at 0xb7e323e0: file libc-start.c, line 96.
  7. (gdb) c
  8. Continuing.
  9. preinit

  10. Breakpoint 2, 0x08048380 in _start ()
  11. (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__);

  12. (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.

  1. void
  2. __libc_csu_init (int argc, char **argv, char **envp)
  3. {
  4.   /* For dynamically linked executables the preinit array is executed by
  5.      the dynamic linker (before initializing any shared object). */

  6. #ifndef LIBC_NONSHARED
  7.   /* For static executables, preinit happens right before init. */
  8.   {
  9.     const size_t size = __preinit_array_end - __preinit_array_start;
  10.     size_t i;
  11.     for (i = 0; i < size; i++)
  12.       (*__preinit_array_start [i]) (argc, argv, envp);
  13.   }
  14. #endif

  15.   _init ();

  16.   const size_t size = __init_array_end - __init_array_start;
  17.   for (size_t i = 0; i < size; i++)
  18.       (*__init_array_start [i]) (argc, argv, envp);
  19. }
    _init中会执行construct的代码, __init_array_start[i] 处执行init_array段的函数,所以我们可以看出执行顺序如下:
  1. Function pointers in .preinit_array section  ,before _start
  2. Functions marked as __attribute__ ((constructor)), via _init 
  3. 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 程序员的自我修养



上一篇:vim格式化C代码
下一篇:OpenCV移植到ARM9