装载、链接与库——main函数的前世今生

来源:互联网 发布:值机软件 编辑:程序博客网 时间:2024/05/20 09:10

*当执行”Hello World“程序时,是从main函数开始执行的吗?答案当然是No,No,No!*

一个程序的运行步骤如下:

  • 操作系统在在创建进程后,把控制权交给了程序的入口,这个入口一般是运行库中的某个入口函数

  • 入口函数对运行库和程序运行环境进行初始化,包括堆、I/O、线程、全局变量构造等

  • 入口函数完成初始化后,调用main函数,正式开始执行程序主体部分

  • main函数执行完毕后,返回到入口函数,入口函数进行清理工作,包括全局变量析构、堆销毁、关闭I/O等,然后进行系统调用结束进程

接下来具体分析的是基于glibc 2.6.1中静态链接的、用于可执行文件的情况

  1. 第一步:glibc的程序入口为_start(这个入口是有ld链接器默认的链接脚本指定的,可以通过相关参数设定入口)

    • _start由汇编实现且和平台相关

      //将汇编改写为伪代码void start(){    %ebp = 0;//使ebp为0,证明其是最外层函数    int argc = pop from stack;//从栈中获取argc,隐含envp    char **argv = top from stack;//从栈中获取argv    //调用_libc_start_main()函数    _libc_start_main(main, argc, argv, _libc_csu_init, _libc_csu_fini, edx, top of stack);}
    • 栈分布情况
      环境变量和参数数组

  2. 第二步:调用_libc_start_main()函数,下面对_libc_start_main()函数进行源码分析

    • 源码分析

      //glibc-2.6.1\csu\Libc-start.c//为使结构条理更加清晰,删减了部分代码/*_libc_start_main()参数说明mainargcubp_av:包括argv和envp函数指针init:main调用前的初始化工作函数指针fini:main结束后的收尾工作函数指针rtld_fini:动态加载有关的收尾工作(runtime loader)stack_end:指明栈地址,即最高的栈地址*/STATIC intLIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),         int argc,          char *__unbounded *__unbounded ubp_av,         __typeof (main) init,         void (*fini) (void),         void (*rtld_fini) (void),          void *__unbounded stack_end){    #if __BOUNDED_POINTERS__      char **argv;    #else    # define argv ubp_av    #endif      /* Result of the 'main' function.  */      int result;    ......    #ifndef SHARED      char *__unbounded *__unbounded ubp_ev = &ubp_av[argc + 1];      INIT_ARGV_and_ENVIRON;//将宏展开得到 __environ = ubp_ev,即让__environ指针指向envp      /* Store the lowest stack address.  This is done in ld.so if this is         the code for the DSO.  */      __libc_stack_end = stack_end;    ......    # ifdef DL_SYSDEP_OSCHECK      if (!__libc_multiple_libcs)        {          /* This needs to run to initiliaze _dl_osversion before TLS         setup might check it.  */          DL_SYSDEP_OSCHECK (__libc_fatal);//检查操作系统版本        }    # endif    ......    __pthread_initialize_minimal ();    //__cxa_atexit()为glibc内部函数,等同于atexit    //rtld_fini在main函数结束后调用    __cxa_atexit ((void (*) (void *)) rtld_fini, NULL, NULL);    __libc_init_first (argc, argv, __environ);    //fini在main函数结束后调用    __cxa_atexit ((void (*) (void *)) fini, NULL, NULL);    (*init) (argc, argv, __environ MAIN_AUXVEC_PARAM);    ......    result = main (argc, argv, __environ MAIN_AUXVEC_PARAM);//开始执行main函数,result为退出码    ......    exit (result);//程序开始退出}
    • 栈分布情况
      环境变量和参数数组(2)

  3. 第三步:调用exit()函数,下面对exit()函数进行源码分析

    //glibc-2.6.1\stdlib\Exit.cvoid exit (int status){    //__exit_funcs存储由cxa_atexit和atexit注册的函数链表    //遍历该链表并逐个调用注册函数    while (__exit_funcs != NULL)    {        struct exit_function_list *old;        ......        old = __exit_funcs;        __exit_funcs = __exit_funcs->next;//依次指向节点        if (__exit_funcs != NULL)            free (old);    }    ......    _exit (status);}
  4. 第四步:调用_exit()函数,下面对_exit()函数进行源码分析

    // glibc-2.6.1\sysdeps\mach\hurd\Dl-sysdep.c//_exit函数调用后,进程就会直接结束//程序正常结束的两种情况//1.通过main函数正常返回//2.程序代码中使用exitvoid weak_function attribute_hidden _exit (int status){    __proc_mark_exit (_dl_hurd_data->portarray[INIT_PORT_PROC], W_EXITCODE (status, 0), 0);    while (__task_terminate (__mach_task_self ()))        __mach_task_self_ = (__mach_task_self) ();}

经过对源代码的分析,可以肯定”Hello World“程序的确不是从main函数开始执行的!

综上所述,函数调用过程如下:
start -> libc_start_main -> exit -> _exit

*终于写完了。。。经历了源码找不到、源码看不懂之后,最后还是搞定了。。(●ˇ∀ˇ●)*