转自:http://blog.csdn.net/gary_ygl/article/details/8506007

1 最简单的程序 

1)编辑helloworld程序,$vim helloworld.c

  1. 1 #include <stdio.h>
  2. 2
  3. 3 int main (int argc, char *argv[])
  4. 4 {
  5. 5         printf("Hello world!\n");
  6. 6
  7. 7         return 0;
  8. 8 }

2) 编译,$ gcc helloworld.c -o helloworld

3) 运行,$./helloworld

Hello world!

2 最简单的程序其实不简单

上面这个helloword程序已经再简单不过了,先来看一看它的反汇编代码:

  1. $ objdump -d helloworld
  2. helloworld:     file format elf32-i386
  3. Disassembly of section .init:
  4. 080482b0 <_init>:
  5. 80482b0:   53                      push   %ebx
  6. 80482b1:   83 ec 08                sub    $0x8,%esp
  7. 80482b4:   e8 00 00 00 00          call   80482b9 <_init+0x9>
  8. 80482b9:   5b                      pop    %ebx
  9. 80482ba:   81 c3 3b 1d 00 00       add    $0x1d3b,%ebx
  10. 80482c0:   8b 83 fc ff ff ff       mov    -0x4(%ebx),%eax
  11. 80482c6:   85 c0                   test   %eax,%eax
  12. 80482c8:   74 05                   je     80482cf <_init+0x1f>
  13. 80482ca:   e8 31 00 00 00          call   8048300 <__gmon_start__@plt>
  14. 80482cf:   e8 dc 00 00 00          call   80483b0 <frame_dummy>
  15. 80482d4:   e8 97 01 00 00          call   8048470 <__do_global_ctors_aux>
  16. 80482d9:   83 c4 08                add    $0x8,%esp
  17. 80482dc:   5b                      pop    %ebx
  18. 80482dd:   c3                      ret
  19. Disassembly of section .plt:
  20. 080482e0 <puts@plt-0x10>:
  21. 80482e0:   ff 35 f8 9f 04 08       pushl  0x8049ff8
  22. 80482e6:   ff 25 fc 9f 04 08       jmp    *0x8049ffc
  23. 80482ec:   00 00                   add    %al,(%eax)
  24. ...
  25. 080482f0 <puts@plt>:
  26. 80482f0:   ff 25 00 a0 04 08       jmp    *0x804a000
  27. 80482f6:   68 00 00 00 00          push   $0x0
  28. 80482fb:   e9 e0 ff ff ff          jmp    80482e0 <_init+0x30>
  29. 08048300 <__gmon_start__@plt>:
  30. 8048300:   ff 25 04 a0 04 08       jmp    *0x804a004
  31. 8048306:   68 08 00 00 00          push   $0x8
  32. 804830b:   e9 d0 ff ff ff          jmp    80482e0 <_init+0x30>
  33. 08048310 <__libc_start_main@plt>:
  34. 8048310:   ff 25 08 a0 04 08       jmp    *0x804a008
  35. 8048316:   68 10 00 00 00          push   $0x10
  36. 804831b:   e9 c0 ff ff ff          jmp    80482e0 <_init+0x30>
  37. Disassembly of section .text:
  38. 08048320 <_start>:
  39. 8048320:   31 ed                   xor    %ebp,%ebp
  40. 8048322:   5e                      pop    %esi
  41. 8048323:   89 e1                   mov    %esp,%ecx
  42. 8048325:   83 e4 f0                and    $0xfffffff0,%esp
  43. 8048328:   50                      push   %eax
  44. 8048329:   54                      push   %esp
  45. 804832a:   52                      push   %edx
  46. 804832b:   68 60 84 04 08          push   $0x8048460
  47. 8048330:   68 f0 83 04 08          push   $0x80483f0
  48. 8048335:   51                      push   %ecx
  49. 8048336:   56                      push   %esi
  50. 8048337:   68 d4 83 04 08          push   $0x80483d4
  51. 804833c:   e8 cf ff ff ff          call   8048310 <__libc_start_main@plt>
  52. 8048341:   f4                      hlt
  53. 8048342:   90                      nop
  54. 8048343:   90                      nop
  55. 8048344:   90                      nop
  56. 8048345:   90                      nop
  57. 8048346:   90                      nop
  58. 8048347:   90                      nop
  59. 8048348:   90                      nop
  60. 8048349:   90                      nop
  61. 804834a:   90                      nop
  62. 804834b:   90                      nop
  63. 804834c:   90                      nop
  64. 804834d:   90                      nop
  65. 804834e:   90                      nop
  66. 804834f:   90                      nop
  67. 08048350 <__do_global_dtors_aux>:
  68. 8048350:   55                      push   %ebp
  69. 8048351:   89 e5                   mov    %esp,%ebp
  70. 8048353:   53                      push   %ebx
  71. 8048354:   83 ec 04                sub    $0x4,%esp
  72. 8048357:   80 3d 14 a0 04 08 00    cmpb   $0x0,0x804a014
  73. 804835e:   75 3f                   jne    804839f <__do_global_dtors_aux+0x4f>
  74. 8048360:   a1 18 a0 04 08          mov    0x804a018,%eax
  75. 8048365:   bb 20 9f 04 08          mov    $0x8049f20,%ebx
  76. 804836a:   81 eb 1c 9f 04 08       sub    $0x8049f1c,%ebx
  77. 8048370:   c1 fb 02                sar    $0x2,%ebx
  78. 8048373:   83 eb 01                sub    $0x1,%ebx
  79. 8048376:   39 d8                   cmp    %ebx,%eax
  80. 8048378:   73 1e                   jae    8048398 <__do_global_dtors_aux+0x48>
  81. 804837a:   8d b6 00 00 00 00       lea    0x0(%esi),%esi
  82. 8048380:   83 c0 01                add    $0x1,%eax
  83. 8048383:   a3 18 a0 04 08          mov    %eax,0x804a018
  84. 8048388:   ff 14 85 1c 9f 04 08    call   *0x8049f1c(,%eax,4)
  85. 804838f:   a1 18 a0 04 08          mov    0x804a018,%eax
  86. 8048394:   39 d8                   cmp    %ebx,%eax
  87. 8048396:   72 e8                   jb     8048380 <__do_global_dtors_aux+0x30>
  88. 8048398:   c6 05 14 a0 04 08 01    movb   $0x1,0x804a014
  89. 804839f:   83 c4 04                add    $0x4,%esp
  90. 80483a2:   5b                      pop    %ebx
  91. 80483a3:   5d                      pop    %ebp
  92. 80483a4:   c3                      ret
  93. 80483a5:   8d 74 26 00             lea    0x0(%esi,%eiz,1),%esi
  94. 80483a9:   8d bc 27 00 00 00 00    lea    0x0(%edi,%eiz,1),%edi
  95. 080483b0 <frame_dummy>:
  96. 80483b0:   55                      push   %ebp
  97. 80483b1:   89 e5                   mov    %esp,%ebp
  98. 80483b3:   83 ec 18                sub    $0x18,%esp
  99. 80483b6:   a1 24 9f 04 08          mov    0x8049f24,%eax
  100. 80483bb:   85 c0                   test   %eax,%eax
  101. 80483bd:   74 12                   je     80483d1 <frame_dummy+0x21>
  102. 80483bf:   b8 00 00 00 00          mov    $0x0,%eax
  103. 80483c4:   85 c0                   test   %eax,%eax
  104. 80483c6:   74 09                   je     80483d1 <frame_dummy+0x21>
  105. 80483c8:   c7 04 24 24 9f 04 08    movl   $0x8049f24,(%esp)
  106. 80483cf:   ff d0                   call   *%eax
  107. 80483d1:   c9                      leave
  108. 80483d2:   c3                      ret
  109. 80483d3:   90                      nop
  110. 080483d4 <main>:
  111. 80483d4:   55                      push   %ebp
  112. 80483d5:   89 e5                   mov    %esp,%ebp
  113. 80483d7:   83 e4 f0                and    $0xfffffff0,%esp
  114. 80483da:   83 ec 10                sub    $0x10,%esp
  115. 80483dd:   c7 04 24 c0 84 04 08    movl   $0x80484c0,(%esp)
  116. 80483e4:   e8 07 ff ff ff          call   80482f0 <puts@plt>
  117. 80483e9:   b8 00 00 00 00          mov    $0x0,%eax
  118. 80483ee:   c9                      leave
  119. 80483ef:   c3                      ret
  120. 080483f0 <__libc_csu_init>:
  121. 80483f0:   55                      push   %ebp
  122. 80483f1:   57                      push   %edi
  123. 80483f2:   56                      push   %esi
  124. 80483f3:   53                      push   %ebx
  125. 80483f4:   e8 69 00 00 00          call   8048462 <__i686.get_pc_thunk.bx>
  126. 80483f9:   81 c3 fb 1b 00 00       add    $0x1bfb,%ebx
  127. 80483ff:   83 ec 1c                sub    $0x1c,%esp
  128. 8048402:   8b 6c 24 30             mov    0x30(%esp),%ebp
  129. 8048406:   8d bb 20 ff ff ff       lea    -0xe0(%ebx),%edi
  130. 804840c:   e8 9f fe ff ff          call   80482b0 <_init>
  131. 8048411:   8d 83 20 ff ff ff       lea    -0xe0(%ebx),%eax
  132. 8048417:   29 c7                   sub    %eax,%edi
  133. 8048419:   c1 ff 02                sar    $0x2,%edi
  134. 804841c:   85 ff                   test   %edi,%edi
  135. 804841e:   74 29                   je     8048449 <__libc_csu_init+0x59>
  136. 8048420:   31 f6                   xor    %esi,%esi
  137. 8048422:   8d b6 00 00 00 00       lea    0x0(%esi),%esi
  138. 8048428:   8b 44 24 38             mov    0x38(%esp),%eax
  139. 804842c:   89 2c 24                mov    %ebp,(%esp)
  140. 804842f:   89 44 24 08             mov    %eax,0x8(%esp)
  141. 8048433:   8b 44 24 34             mov    0x34(%esp),%eax
  142. 8048437:   89 44 24 04             mov    %eax,0x4(%esp)
  143. 804843b:   ff 94 b3 20 ff ff ff    call   *-0xe0(%ebx,%esi,4)
  144. 8048442:   83 c6 01                add    $0x1,%esi
  145. 8048445:   39 fe                   cmp    %edi,%esi
  146. 8048447:   75 df                   jne    8048428 <__libc_csu_init+0x38>
  147. 8048449:   83 c4 1c                add    $0x1c,%esp
  148. 804844c:   5b                      pop    %ebx
  149. 804844d:   5e                      pop    %esi
  150. 804844e:   5f                      pop    %edi
  151. 804844f:   5d                      pop    %ebp
  152. 8048450:   c3                      ret
  153. 8048451:   eb 0d                   jmp    8048460 <__libc_csu_fini>
  154. 8048453:   90                      nop
  155. 8048454:   90                      nop
  156. 8048455:   90                      nop
  157. 8048456:   90                      nop
  158. 8048457:   90                      nop
  159. 8048458:   90                      nop
  160. 8048459:   90                      nop
  161. 804845a:   90                      nop
  162. 804845b:   90                      nop
  163. 804845c:   90                      nop
  164. 804845d:   90                      nop
  165. 804845e:   90                      nop
  166. 804845f:   90                      nop
  167. 08048460 <__libc_csu_fini>:
  168. 8048460:   f3 c3                   repz ret
  169. 08048462 <__i686.get_pc_thunk.bx>:
  170. 8048462:   8b 1c 24                mov    (%esp),%ebx
  171. 8048465:   c3                      ret
  172. 8048466:   90                      nop
  173. 8048467:   90                      nop
  174. 8048468:   90                      nop
  175. 8048469:   90                      nop
  176. 804846a:   90                      nop
  177. 804846b:   90                      nop
  178. 804846c:   90                      nop
  179. 804846d:   90                      nop
  180. 804846e:   90                      nop
  181. 804846f:   90                      nop
  182. 08048470 <__do_global_ctors_aux>:
  183. 8048470:   55                      push   %ebp
  184. 8048471:   89 e5                   mov    %esp,%ebp
  185. 8048473:   53                      push   %ebx
  186. 8048474:   83 ec 04                sub    $0x4,%esp
  187. 8048477:   a1 14 9f 04 08          mov    0x8049f14,%eax
  188. 804847c:   83 f8 ff                cmp    $0xffffffff,%eax
  189. 804847f:   74 13                   je     8048494 <__do_global_ctors_aux+0x24>
  190. 8048481:   bb 14 9f 04 08          mov    $0x8049f14,%ebx
  191. 8048486:   66 90                   xchg   %ax,%ax
  192. 8048488:   83 eb 04                sub    $0x4,%ebx
  193. 804848b:   ff d0                   call   *%eax
  194. 804848d:   8b 03                   mov    (%ebx),%eax
  195. 804848f:   83 f8 ff                cmp    $0xffffffff,%eax
  196. 8048492:   75 f4                   jne    8048488 <__do_global_ctors_aux+0x18>
  197. 8048494:   83 c4 04                add    $0x4,%esp
  198. 8048497:   5b                      pop    %ebx
  199. 8048498:   5d                      pop    %ebp
  200. 8048499:   c3                      ret
  201. 804849a:   90                      nop
  202. 804849b:   90                      nop
  203. Disassembly of section .fini:
  204. 0804849c <_fini>:
  205. 804849c:   53                      push   %ebx
  206. 804849d:   83 ec 08                sub    $0x8,%esp
  207. 80484a0:   e8 00 00 00 00          call   80484a5 <_fini+0x9>
  208. 80484a5:   5b                      pop    %ebx
  209. 80484a6:   81 c3 4f 1b 00 00       add    $0x1b4f,%ebx
  210. 80484ac:   e8 9f fe ff ff          call   8048350 <__do_global_dtors_aux>
  211. 80484b1:   83 c4 08                add    $0x8,%esp
  212. 80484b4:   5b                      pop    %ebx
  213. 80484b5:   c3                      ret

从反汇编结果来看,helloworld可执行文件并不只是main函数,除main外,还有几个重要的函数,_init,_fini,_start,__libc_start_main,__libc_csu_init,__libc_csu_fini等。

除main函数外,其它函数从哪来的?分析一下编译好的可执行文件helloworld的库依赖关系如下:
  1. $ ldd helloworld
  2. linux-gate.so.1 =>  (0x00876000)
  3. libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0x00110000)
  4. /lib/ld-linux.so.2 (0x006a3000)

其中linux-gate.so.1为系统调用相关的动态库,ld-linux.so.2为动态链接库,libc.so.6为GNU C库。

从反汇编出来的相关函数名称(__libc_start_main)来看,我们把目标锁定在GNU C库,即glibc,也是GNU开源项目,可以从GNU 源码ftp站点(http://ftp.gnu.org/gnu/libc/)上找到。
__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));

__libc_csu_init,__libc_csu_fini函数可以在glibc源码的csu/elf-init.c中找到:

  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. }
  20. /* This function should not be used anymore.  We run the executable's
  21. destructor now just like any other.  We cannot remove the function,
  22. though.  */
  23. void
  24. __libc_csu_fini (void)
  25. {
  26. #ifndef LIBC_NONSHARED
  27. size_t i = __fini_array_end - __fini_array_start;
  28. while (i-- > 0)
  29. (*__fini_array_start [i]) ();
  30. _fini ();
  31. #endif
  32. }

_start函数可以在glibc源码的sysdeps/i386/elf/start.S中找到:

  1. #include "bp-sym.h"
  2. .text
  3. .globl _start
  4. .type _start,@function
  5. _start:
  6. /* Clear the frame pointer.  The ABI suggests this be done, to mark
  7. the outermost frame obviously.  */
  8. xorl %ebp, %ebp
  9. /* Extract the arguments as encoded on the stack and set up
  10. the arguments for `main': argc, argv.  envp will be determined
  11. later in __libc_start_main.  */
  12. popl %esi       /* Pop the argument count.  */
  13. movl %esp, %ecx     /* argv starts just at the current stack top.*/
  14. /* Before pushing the arguments align the stack to a 16-byte
  15. (SSE needs 16-byte alignment) boundary to avoid penalties from
  16. misaligned accesses.  Thanks to Edward Seidl <seidl@janed.com>
  17. for pointing this out.  */
  18. andl $0xfffffff0, %esp
  19. pushl %eax      /* Push garbage because we allocate
  20. 28 more bytes.  */
  21. /* Provide the highest stack address to the user code (for stacks
  22. which grow downwards).  */
  23. pushl %esp
  24. pushl %edx      /* Push address of the shared library
  25. termination function.  */
  26. #ifdef SHARED
  27. /* Load PIC register.  */
  28. call 1f
  29. addl $_GLOBAL_OFFSET_TABLE_, %ebx
  30. /* Push address of our own entry points to .fini and .init.  */
  31. leal __libc_csu_fini@GOTOFF(%ebx), %eax
  32. pushl %eax
  33. leal __libc_csu_init@GOTOFF(%ebx), %eax
  34. pushl %eax
  35. pushl %ecx      /* Push second argument: argv.  */
  36. pushl %esi      /* Push first argument: argc.  */
  37. pushl BP_SYM (main)@GOT(%ebx)
  38. /* Call the user's main function, and exit with its value.
  39. But let the libc call main.    */
  40. call BP_SYM (__libc_start_main)@PLT
  41. #else
  42. /* Push address of our own entry points to .fini and .init.  */
  43. pushl $__libc_csu_fini
  44. pushl $__libc_csu_init
  45. pushl %ecx      /* Push second argument: argv.  */
  46. pushl %esi      /* Push first argument: argc.  */
  47. pushl $BP_SYM (main)
  48. /* Call the user's main function, and exit with its value.
  49. But let the libc call main.    */
  50. call BP_SYM (__libc_start_main)
  51. #endif
  52. hlt         /* Crash if somehow `exit' does return.  */

_init,_fini函数可以在glibc源码的sysdeps/generic/initfini.c中找到:

  1. /* The beginning of _init:  */
  2. asm ("\n/*@_init_PROLOG_BEGINS*/");
  3. static void
  4. call_gmon_start(void)
  5. {
  6. extern void __gmon_start__ (void) __attribute__ ((weak)); /*weak_extern (__gmon_start__);*/
  7. void (*gmon_start) (void) = __gmon_start__;
  8. if (gmon_start)
  9. gmon_start ();
  10. }
  11. SECTION (".init");
  12. extern void __attribute__ ((section (".init"))) _init (void);
  13. void
  14. _init (void)
  15. {
  16. /* We cannot use the normal constructor mechanism in gcrt1.o because it
  17. appears before crtbegin.o in the link, so the header elt of .ctors
  18. would come after the elt for __gmon_start__.  One approach is for
  19. gcrt1.o to reference a symbol which would be defined by some library
  20. module which has a constructor; but then user code's constructors
  21. would come first, and not be profiled.  */
  22. call_gmon_start ();
  23. asm ("ALIGN");
  24. asm("END_INIT");
  25. /* Now the epilog. */
  26. asm ("\n/*@_init_PROLOG_ENDS*/");
  27. asm ("\n/*@_init_EPILOG_BEGINS*/");
  28. SECTION(".init");
  29. }
  30. asm ("END_INIT");
  31. /* End of the _init epilog, beginning of the _fini prolog. */
  32. asm ("\n/*@_init_EPILOG_ENDS*/");
  33. asm ("\n/*@_fini_PROLOG_BEGINS*/");
  34. SECTION (".fini");
  35. extern void __attribute__ ((section (".fini"))) _fini (void);
  36. void
  37. _fini (void)
  38. {
  39. /* End of the _fini prolog. */
  40. asm ("ALIGN");
  41. asm ("END_FINI");
  42. asm ("\n/*@_fini_PROLOG_ENDS*/");
  43. {
  44. /* Let GCC know that _fini is not a leaf function by having a dummy
  45. function call here.  We arrange for this call to be omitted from
  46. either crt file.  */
  47. extern void i_am_not_a_leaf (void);
  48. i_am_not_a_leaf ();
  49. }
  50. /* Beginning of the _fini epilog. */
  51. asm ("\n/*@_fini_EPILOG_BEGINS*/");
  52. SECTION (".fini");
  53. }
  54. asm ("END_FINI");
  55. /* End of the _fini epilog.  Any further generated assembly (e.g. .ident)
  56. is shared between both crt files. */
  57. asm ("\n/*@_fini_EPILOG_ENDS*/");
  58. asm ("\n/*@TRAILER_BEGINS*/");

但是为什么不能在libc.so.6中找到_start,_init,_fini这三个函数呢?

是因为GNU把这三个作为了程序启动和结束的最基本运行库函数,分别放在crt1.o,crti.o,crtn.o这三个object文件中供程序链接时使用。
从glibc的源码sysdeps/generic/initfini.c相关注释也可以看出:
  1. /* This file is compiled into assembly code which is then munged by a sed
  2. script into two files: crti.s and crtn.s.
  3. * crti.s puts a function prologue at the beginning of the
  4. .init and .fini sections and defines global symbols for
  5. those addresses, so they can be called as functions.
  6. * crtn.s puts the corresponding function epilogues
  7. in the .init and .fini sections. */

从上面注释来看crti.o,crtn.o分别包含.init和.fini段的开头和结束部分,分析它们的反汇编代码也可以看出:

  1. $ objdump -d /usr/lib/i386-linux-gnu/crti.o
  2. /usr/lib/i386-linux-gnu/crti.o:     file format elf32-i386
  3. Disassembly of section .init:
  4. 00000000 <_init>:
  5. 0:   53                      push   %ebx
  6. 1:   83 ec 08                sub    $0x8,%esp
  7. 4:   e8 00 00 00 00          call   9 <_init+0x9>
  8. 9:   5b                      pop    %ebx
  9. a:   81 c3 03 00 00 00       add    $0x3,%ebx
  10. 10:   8b 83 00 00 00 00       mov    0x0(%ebx),%eax
  11. 16:   85 c0                   test   %eax,%eax
  12. 18:   74 05                   je     1f <_init+0x1f>
  13. 1a:   e8 fc ff ff ff          call   1b <_init+0x1b>
  14. Disassembly of section .fini:
  15. 00000000 <_fini>:
  16. 0:   53                      push   %ebx
  17. 1:   83 ec 08                sub    $0x8,%esp
  18. 4:   e8 00 00 00 00          call   9 <_fini+0x9>
  19. 9:   5b                      pop    %ebx
  20. a:   81 c3 03 00 00 00       add    $0x3,%ebx
  1. $ objdump -d /usr/lib/i386-linux-gnu/crtn.o
  2. /usr/lib/i386-linux-gnu/crtn.o:     file format elf32-i386
  3. Disassembly of section .init:
  4. 00000000 <.init>:
  5. 0:   83 c4 08                add    $0x8,%esp
  6. 3:   5b                      pop    %ebx
  7. 4:   c3                      ret
  8. Disassembly of section .fini:
  9. 00000000 <.fini>:
  10. 0:   83 c4 08                add    $0x8,%esp
  11. 3:   5b                      pop    %ebx
  12. 4:   c3                      ret

回过头来看看helloworld程序反汇编代码中的_init,_fini函数的组成:

  1. 080482b0 <_init>:
  2. 80482b0:   53                      push   %ebx
  3. 80482b1:   83 ec 08                sub    $0x8,%esp
  4. 80482b4:   e8 00 00 00 00          call   80482b9 <_init+0x9>
  5. 80482b9:   5b                      pop    %ebx
  6. 80482ba:   81 c3 3b 1d 00 00       add    $0x1d3b,%ebx
  7. 80482c0:   8b 83 fc ff ff ff       mov    -0x4(%ebx),%eax
  8. 80482c6:   85 c0                   test   %eax,%eax
  9. 80482c8:   74 05                   je     80482cf <_init+0x1f>
  1. 80482ca:    e8 31 00 00 00          call   8048300 <__gmon_start__@plt>
  2. 80482cf:    e8 dc 00 00 00          call   80483b0 <frame_dummy>
  3. 80482d4:    e8 97 01 00 00          call   8048470 <__do_global_ctors_aux>
  1. 80482d9:    83 c4 08                add    $0x8,%esp
  2. 80482dc:    5b                      pop    %ebx
  3. 80482dd:    c3                      ret
  1. 0804849c <_fini>:
  2. 804849c:   53                      push   %ebx
  3. 804849d:   83 ec 08                sub    $0x8,%esp
  4. 80484a0:   e8 00 00 00 00          call   80484a5 <_fini+0x9>
  5. 80484a5:   5b                      pop    %ebx
  6. 80484a6:   81 c3 4f 1b 00 00       add    $0x1b4f,%ebx
  1. 80484ac:    e8 9f fe ff ff          call   8048350 <__do_global_dtors_aux>
  1. 80484b1:    83 c4 08                add    $0x8,%esp
  2. 80484b4:    5b                      pop    %ebx
  3. 80484b5:    c3                      ret

helloworld程序反汇编代码中的_init,_fini函数中间多出来的部分是其它库或用户自定义的.init和.fini段代码。

从上面helloworld程序反汇编来看,程序启动的过程应该:
_start -> __libc_start_main -> main. 具体一点就是:
_start -> __libc_start_main -> __libc_csu_init -> main. 再具体一点就是:
_start -> __libc_start_main -> __libc_csu_init -> _init -> main -> _fini.
可以通过分析__libc_start_main函数进一步了解,下面的__libc_start_main函数是我精简后的伪代码:
  1. /* Note: the fini parameter is ignored here for shared library.  It
  2. is registered with __cxa_atexit.  This had the disadvantage that
  3. finalizers were called in more than one place.  */
  4. STATIC int
  5. LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
  6. int argc, char *__unbounded *__unbounded ubp_av,
  7. #ifdef LIBC_START_MAIN_AUXVEC_ARG
  8. ElfW(auxv_t) *__unbounded auxvec,
  9. #endif
  10. __typeof (main) init,
  11. void (*fini) (void),
  12. void (*rtld_fini) (void), void *__unbounded stack_end)
  13. {
  14. ...
  15. /* Register the destructor of the dynamic linker if there is any.  */
  16. if (__builtin_expect (rtld_fini != NULL, 1))
  17. __cxa_atexit ((void (*) (void *)) rtld_fini, NULL, NULL);
  18. /* Call the initializer of the libc.  This is only needed here if we
  19. are compiling for the static library in which case we haven't
  20. run the constructors in `_dl_start_user'.  */
  21. __libc_init_first (argc, argv, __environ);
  22. /* Register the destructor of the program, if any.  */
  23. if (fini)
  24. __cxa_atexit ((void (*) (void *)) fini, NULL, NULL);
  25. /* Call the initializer of the program, if any.  */
  26. if (init)
  27. (*init) (argc, argv, __environ MAIN_AUXVEC_PARAM);
  28. /* Nothing fancy, just call the function.  */
  29. result = main (argc, argv, __environ MAIN_AUXVEC_PARAM);
  30. exit (result);
  31. }

__libc_start_main函数的参数main, argc, ubp_av, init, fini, rtld_fini都是通过_start入栈得到。

其中argc, ubp_av为传递给main函数的参数argc, argv。init为__libc_csu_init函数指针,fini为__libc_csu_fini函数指针,rtld_fini为运行库加载收尾函数指针。

从__libc_start_main函数可以看出在call init之前还通过__cxa_atexit向exit函数注册了rtlf_fini和fini函数,目的是为了在main结束后call exit自动完成一些收尾工作。

这里的_init, _fini函数功能还主要负责完成C++程序全局/静态对象的构造与析构,有兴趣的可以深入一下。

__cxa_atexit函数用于注册main结束后程序退出时调用的函数,例如:

  1. 1 #include <stdio.h>
  2. 2 #include <stdlib.h>
  3. 3
  4. 4 void pre_exit (void)
  5. 5 {
  6. 6         printf("Will be exit!\n");
  7. 7 }
  8. 8
  9. 9 int main (int argc, char *argv[])
  10. 10 {
  11. 11         __cxa_atexit(pre_exit, NULL, NULL);
  12. 12
  13. 13         printf("Hello world!\n");
  14. 14
  15. 15         return 0;
  16. 16 }
  1. $ gcc helloworld2.c -o helloworld2
  2. $ ./helloworld2
  3. Hello world!
  4. Will be exit!

可以看到pre_exit函数并不是靠main来执行的,而是靠__libc_start_main函数里面的exit函数来调用完成。

为了更进一步理解_init函数的作用,我们再改写一下代码:

因为链接器会自动把.init段粘贴到一起组成_init函数,所以为了不使_init函数半途返回退出,我们只能用下面汇编的方法定义哪些程序块是需要被链接到_init函数,且该程序块不能有函数的返回指令,这样我们必须在该程序块结束的地方加入汇编指令告诉编译器后面的程序不属于.init段,因此就有了下面这个测试函数pre_init,它会被link到两个section,即.init和.text段。

  1. 1 #include <stdio.h>
  2. 2 #include <stdlib.h>
  3. 3
  4. 4 void __attribute__((used)) pre_init (void)
  5. 5 {
  6. 6         asm (".section .init");
  7. 7         printf("pre-init section .init part\n");
  8. 8         printf("Call pre_init before main\n");
  9. 9         asm (".section .text");
  10. 10         printf("pre-init section .text part\n");
  11. 11         printf("call pre_init in main\n");
  12. 12 }
  13. 13
  14. 14 void pre_exit (void)
  15. 15 {
  16. 16         printf("Call pre_exit after main!\n");
  17. 17 }
  18. 18
  19. 19 int main (int argc, char *argv[])
  20. 20 {
  21. 21         printf("Enter main\n");
  22. 22
  23. 23         __cxa_atexit(pre_exit, NULL, NULL);
  24. 24
  25. 25         printf("Hello world!\n");
  26. 26
  27. 27         pre_init();
  28. 28
  29. 29         printf("Exit main\n");
  30. 30
  31. 31         return 0;
  32. 32 }
  1. $ gcc helloworld3.c -o helloworld3
  2. $ ./helloworld3
  3. pre-init section .init part
  4. Call pre_init before main
  5. Enter main
  6. Hello world!
  7. pre-init section .text part
  8. call pre_init in main
  9. Exit main
  10. Call pre_exit after main!

从helloworld3程序的运行结果也可以看出该程序函数的调用过程:

pre_init (.init段部分 代码) -> main -> pre_init (.text段部分 代码) -> pre_exit.

同样我们也可以添加.fini段代码,链接器会把.fini段代码部分链接进_fini函数,这样_fini函数就会帮我们在main函数退出后自动执行这段代码:

  1. 14 void __attribute__((used)) pre_fini (void)
  2. 15 {
  3. 16         asm (".section .fini");
  4. 17         printf("pre-fini section .fini part\n");
  5. 18         printf("Call pre_fini after main\n");
  6. 19         asm (".section .text");
  7. 20 }
  1. $ gcc helloworld4.c -o helloworld4
  2. $ ./helloworld4
  3. pre-init section .init part
  4. Call pre_init before main
  5. Enter main
  6. Hello world!
  7. pre-init section .text part
  8. call pre_init in main
  9. Exit main
  10. Call pre_exit after main!
  11. pre-fini section .fini part
  12. Call pre_fini after main

从以上运行结果可知pre_fini函数是在main函数退出后才被调用的,但是为什么pre_fini打印的内容比pre_exit打印的内容要晚出来呢?这是因为pre_exit函数注册比pre_fini要晚,而通过__cxa_atexit注册的过程其实就是建立一个exit函数指针链表栈,按栈的规则是后进先出,因此pre_exit比pre_fini要先被调用。

从helloworld4程序的运行结果也可以看出该程序函数的调用过程:

pre_init (.init段部分 代码) -> main -> pre_init (.text段部分 代码) -> pre_exit -> pre_fini (.fini段部分 代码).

为了让程序正常运行和结束,链接器ld帮我们做了好多事情,可以用下面来表示:

ld crt1.o crti.o [usr object] [lib] crtn.o

最后总结一下,程序启动和结束的过程可以描述为:

_start -> __libc_start_main ->
 init -> main -> exit.

3 Self-check

如果想对main的启动及结束有进一步的理解,最好的方法就亲自读一读glibc的相关源码。
另外,介绍一本好书《程序员的自我修养—链接、装载与库》。
(待完善)

版权声明:本文为博主原创文章,未经博主允许不得转载。

linux编程之main()函数启动过程【转】的更多相关文章

  1. Linux下main函数启动过程【程序员自我修养笔记】【自用】

    1. 入口函数和程序初始化 1.1 程序从main开始吗? 当程序执行到main函数的第一行时,很多事情都已经完成了: [证1]如下是一段C语言代码: 代码中可以看到,在程序刚刚执行到main的时候, ...

  2. Linux编程之fork函数

    在Linux中,fork函数的功能就是在一个进程中创建一个新的进程,当前调用fork函数的进程就是产生的新进程的父进程,新进程在以下也称为子进程.在新进程生成之后就会在系统中开始执行. 函数原型:pi ...

  3. linux编程之pipe()函数

    管道是一种把两个进程之间的标准输入和标准输出连接起来的机制,从而提供一种让多个进程间通信的方法,当进程创建管道时,每次 都需要提供两个文件描述符来操作管道.其中一个对管道进行写操作,另一个对管道进行读 ...

  4. Linux编程之ICMP洪水攻击

    我的上一篇文章<Linux编程之PING的实现>里使用ICMP协议实现了PING的程序,ICMP除了实现这么一个PING程序,还有哪些不为人知或者好玩的用途?这里我将介绍ICMP另一个很有 ...

  5. Windows编程之connect函数研究

    写在前面:本博客为本人原创,严禁任何形式的转载!本博客只允许放在博客园(.cnblogs.com),如果您在其他网站看到这篇博文,请通过下面这个唯一的合法链接转到原文! 本博客全网唯一合法URL:ht ...

  6. 浅析嵌入式Linux系统的构成和启动过程

    在我们的周围,大量的嵌入式设备都是基于Linux系统来构建的,嵌入式Linux与主机Linux相比有着自己的一些特点,本文就嵌入式Linux系统的构成和启动过程做一些总结. 一.嵌入式Linux系统构 ...

  7. 嵌入式Linux系统的构成和启动过程

    转自:http://blog.csdn.net/weiganyi/article/details/11561859 在我们的周围,大量的嵌入式设备都是基于Linux系统来构建的,嵌入式Linux与主机 ...

  8. WPF应用程序启动的问题(自定义Main函数启动)

    问题引入: 一般WPF创建之后可以直接运行并不需要编写Main函数指定入口,但是在开发的过程中会遇到一些情况需要自定义Main让WPF从指定的Main函数中进行启动,这样可能会更好控制一点.但是我们再 ...

  9. Linux基础-6.系统的启动过程

    Linux启动时我们会看到许多启动信息 Linux系统的启动过程并不是大家想象中的那么复杂,其过程可以分为5个阶段: 内核的引导 运行init 系统初始化 建立终端 用户登录系统 init程序的类型: ...

随机推荐

  1. 【bzoj3560】DZY Loves Math V 欧拉函数

    题目描述 给定n个正整数a1,a2,…,an,求 的值(答案模10^9+7). 输入 第一行一个正整数n. 接下来n行,每行一个正整数,分别为a1,a2,…,an. 输出 仅一行答案. 样例输入 3 ...

  2. 转发---[沧海拾遗]java并发之CountDownLatch、Semaphore和CyclicBarrier

    JAVA并发包中有三个类用于同步一批线程的行为,分别是CountDownLatch.Semaphore和CyclicBarrier. CountDownLatch CountDownLatch是一个计 ...

  3. S-T平面图

    给定一个平面图和一个源点S.汇点T都在图中无边界的区域上,这样的图叫S-T平面图 我们把图中每一个独立的面看做一个点,对于每条边e,将它两侧的面连一条边,其中靠近S的一段与S相连,与T相连的一段与T相 ...

  4. 延长xss的攻击(转)

    XSS 的本质仍是一段脚本.和其他文档元素一样,页面关了一切都销毁.除非能将脚本蔓延到页面以外的地方,那样才能获得更长的生命力. 庆幸的是,从 DOM 诞生的那一天起,就已为我们准备了这个特殊的功能, ...

  5. Linux内核设计第五周学习总结 分析system_call中断处理过程

    陈巧然原创作品 转载请注明出处   <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 使用gdb跟踪分析一 ...

  6. 谷哥的小弟学前端(01)——HTML常用标签(1)

    探索Android软键盘的疑难杂症 深入探讨Android异步精髓Handler 详解Android主流框架不可或缺的基石 站在源码的肩膀上全解Scroller工作机制 Android多分辨率适配框架 ...

  7. 开放接口/RESTful/Api服务的设计和安全方案

    总体思路 这个涉及到两个方面问题:一个是接口访问认证问题,主要解决谁可以使用接口(用户登录验证.来路验证)一个是数据数据传输安全,主要解决接口数据被监听(HTTPS安全传输.敏感内容加密.数字签名) ...

  8. [学习笔记]矩阵乘法及其优化dp

    1.定义: $c[i][j]=\sum a[i][k]\times b[k][j]$ 所以矩阵乘法有条件,(n*m)*(m*p)=n*p 即第一个矩阵的列数等于第二个矩阵的行数,否则没有意义. 2.结 ...

  9. 【agc004C】AND Grid

    Portal --> agc004C Description 给你一个\(n*m\)的网格图\(A\),有一些格子是'#',现在要构造出两个新的网格图\(B\)和\(C\)满足: 1.如果\(A ...

  10. hibernate中evict()和clear()的区别

    session.evict(obj):会把指定的缓冲对象进行清除: session.clear():把缓冲区内的全部对象清除,但不包括操作中的对象. hibernate执行的顺序如下: (1)生成一个 ...