北极以北 之 main函数之前

340阅读 0评论2013-11-20 lpwsw
分类:LINUX

    建议英语好的筒子,直接拉到最后阅读提到的三篇参考博文,我这篇博文受惠于下面参考文献中提到的三篇及程序员的自我修养,以及网上的一些资料,不敢妄称原创,光荣属于前辈。

    学C语言之初,老师都会告诉我们,main函数式入口点。函数执行从main 开始。对初学C编程的人这样说其实无可厚非,毕竟开始学C编程的时候讲一堆编译链接,反而增加了入门的难度。
    看下面这个函数:

   
  1. [root@localhost debug]# cat before_after.c
  2. #include <stdio.h>

  3. int main()
  4. {
  5.     printf("%s called \n",__FUNCTION__);
  6. }


  7. __attribute__((constructor))
  8. void function_before_main() 
  9. {
  10.     printf("%s called \n",__FUNCTION__);
  11. }

  12. __attribute__((destructor))
  13. void function_after_main()
  14. {
  15.     printf("%s called \n",__FUNCTION__);
  16. }
    看下运行结果:

  1. [root@localhost debug]# ./before_after
  2. function_before_main called
  3. main called
  4. function_after_main called
  5. [root@localhost debug]#
    从结果上看,function_before_main的确是先于main函数执行,而function_after_main是main执行完后再执行的。 呵呵。
  
    另外程序员的自我修养第十一章的开篇也提出了一些有趣的例子,其中atexit这个退出注册函数比较有意思,感兴趣的可以去看下。main函数不是首先执行的函数,这一点几乎是肯定的了。去年,我复习汇编编程的时候,提到了用gcc编译汇编程序,程序中需要有main,但是用ld链接的话,需要有_start 这个label。想了解详情的可以去阅读 。

  1. as -o cpuid.o cpuid.s
  2. ld -o cpuid cpuid.o
    现在我们写一个空转CPU,什么都不干的程序:nothing.c

  1. int main()
  2. {
  3.     while(1)
  4.     {
  5.             ;
  6.     }

  7.     return 0;
  8. }
    我们先按照正常的方式把他编译出nothing可执行程序:

  1. [root@localhost debug]# gcc -c nothing.c
  2. [root@localhost debug]# gcc -o nothing nothing.o
    通过这两步,我们生成了可执行程序nothing,运行良好,就是空转,浪费了cpu资源,这个按下不表。我们看下要想执行nothing需要哪些动态库:

  1. [root@localhost debug]# ldd nothing
  2. linux-gate.so.1 => (0x007c9000)
  3. libc.so.6 => /lib/libc.so.6 (0x00c10000)
  4. /lib/ld-linux.so.2 (0x00240000)
    几乎所有的程序都会用的上面三个程序,linux-gate.so这个是系统调用相关的,程序员的自我修养中有提到,网上也有相应的资源解释。我们注意到,我什么都没干,链接的时候还需要3个动态库。

    跟踪一下,看了调用了那些库函数,ltrace 就派上了大用场(ltrace和strace用法几乎一样,一个是跟踪系统调用,一个是跟踪lib函数):

   
  1. [root@localhost debug]# ltrace ./nothing
  2. __libc_start_main(0x8048354, 1, 0xbf802074, 0x8048380, 0x8048370

 [root@localhost debug]# nm /lib/libc.so.6 |grep __libc_start_main
 00c25dc0 T __libc_start_main


[root@localhost debug]# objdump -d /lib/libc.so.6 |grep -A15 __libc_start_main
00c25dc0 <__libc_start_main>:
  c25dc0:    55                       push   %ebp
  c25dc1:    31 d2                    xor    %edx,%edx
  c25dc3:    89 e5                    mov    %esp,%ebp
  c25dc5:    57                       push   %edi
  c25dc6:    56                       push   %esi
  c25dc7:    53                       push   %ebx
  c25dc8:    e8 13 ff ff ff           call   c25ce0 <__i686.get_pc_thunk.bx>
  c25dcd:    81 c3 27 b2 12 00        add    $0x12b227,%ebx
  c25dd3:    83 ec 4c                 sub    $0x4c,%esp

    调用了__libc_start_main这个函数,顾名思义,这个函数是libc下的函数,我们查看了符号表和汇编指令,确认这个函数的确是libc里的函数。
    OK,我们继续调查__libc_start_main函数之前,我们退一步做另外一个实验,不用gcc链接,改用ld直接链接,看下能否生成nothing可执行程序


  1. [root@localhost debug]# rm nothing.o nothing
  2. [root@localhost debug]# gcc -c nothing.c
  3. [root@localhost debug]# ld -o nothing nothing.o
  4. ld: warning: cannot find entry symbol _start; defaulting to 0000000008048074
    ld报错,表示找不到_start这个符号,所以采用了个默认值。有意思的是,这样居然生成的了可执行文件nothing。运行还不崩溃。我查看了 How statically linked programs run on linux,这篇文章提到这样生成的文件 运行起来会段错误的。 实际上的确如此,只不过我有个while死循环,程序始终运行不到崩溃的那个点而已。如果把while那个死循环去掉,的确段错误了。

   上面一段得出结论是从link的角度看,_start是函数的入口点,这个定义在crt1.so。

 
  1. [root@localhost debug]# ld /usr/lib/crt1.o -o nothing nothing.o
  2. /usr/lib/crt1.o: In function `_start':
  3. (.text+0xc): undefined reference to `__libc_csu_fini'
  4. /usr/lib/crt1.o: In function `_start':
  5. (.text+0x11): undefined reference to `__libc_csu_init'
  6. /usr/lib/crt1.o: In function `_start':
  7. (.text+0x1d): undefined reference to `__libc_start_main'
   又见__libc_start_main 我们用正常方法生成nothing 看下_start 和__libc_start_main的关系:
 
  1. [root@localhost debug]# gcc -o nothing nothing.c
  2. [root@localhost debug]# readelf -h nothing
  3. ELF Header:
  4. Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  5. Class: ELF32
  6. ...
  7. Machine: Intel 80386
  8. Version: 0x1
  9. Entry point address: 0x8048280
  10. Start of program headers: 52 (bytes into file)
  11. ....

  12. [root@localhost debug]# objdump -d nothing |grep -A15 "<_start"
  13. 08048280 <_start>:
  14. 8048280: 31 ed xor %ebp,%ebp
  15. 8048282: 5e pop %esi
  16. 8048283: 89 e1 mov %esp,%ecx
  17. 8048285: 83 e4 f0 and $0xfffffff0,%esp
  18. 8048288: 50 push %eax
  19. 8048289: 54 push %esp
  20. 804828a: 52 push %edx
  21. 804828b: 68 70 83 04 08 push $0x8048370
  22. 8048290: 68 80 83 04 08 push $0x8048380
  23. 8048295: 51 push %ecx
  24. 8048296: 56 push %esi
  25. 8048297: 68 54 83 04 08 push $0x8048354
  26. 804829c: e8 c7 ff ff ff call 8048268 <__libc_start_main@plt>
  27. 80482a1: f4 hlt
  28. 80482a2: 90 nop
    可以看出,函数的入口点是_start,start调用了__libc_start_main,其中main函数是__libc_start_main的一个入参而已。
   
    下面可以看出main函数的地址是__libc_start_main的第一个参数。

点击(此处)折叠或打开

  1. [root@localhost debug]# ltrace ./nothing
  2. __libc_start_main(0x8048354, 1, 0xbf874264, 0x8048380, 0x8048370
  3. --- SIGINT (Interrupt) ---
  4. +++ killed by SIGINT +++
  5. [root@localhost debug]# objdump -d nothing |grep main
  6. 08048268 <__libc_start_main@plt>:
  7. 804829c: e8 c7 ff ff ff call 8048268 <__libc_start_main@plt>
  8. 08048354
    :
  9. 8048362: eb fe jmp 8048362
  10. [root@localhost debug]#
    __libc_start_main这个函数式定义在glibc中csu目录下的libc-start.c中,看下函数声明

  1. STATIC int LIBC_START_MAIN (int (*main) (int, char **, char **
  2.                      MAIN_AUXVEC_DECL),
  3.              int argc,
  4.              char *__unbounded *__unbounded ubp_av,
  5. #ifdef LIBC_START_MAIN_AUXVEC_ARG
  6.              ElfW(auxv_t) *__unbounded auxvec,
  7. #endif
  8.              __typeof (main) init,
  9.              void (*fini) (void),
  10.              void (*rtld_fini) (void),
  11.              void *__unbounded stack_end)
  12.      __attribute__ ((noreturn));
    我们的main函数,入参个数argc,入参argv,环境变量 以及辅助信息数组(不了解的,摸我,and 再摸我)。

    __libc_start_main这个函数干了那些事情呢,这个,我目前还没看懂代码。我下面郑重推荐的三篇博文中有一篇提到了,也是语焉不详,没有深入到code这一层。好在glibc 的代码,你看,与不看,它就在那里,不怒不喜。

    最后,我对这篇文章其实并不满意,因为有太多的未解决的问题。成长是一个过程,我只能用这句话来安慰自己。本文要郑重鸣谢参考文献的三篇博文强烈建议读者去读这三篇。


 参考文献
1

2 Hello from a libc-free world! (Part 1)
3 Hello from a libc-free world! (Part 2)   
4 程序员的自我修养

上一篇:main之前
下一篇:Segmetation fault你来的真不是时候