装载、链接与库——main函数的前世今生
来源:互联网 发布:值机软件 编辑:程序博客网 时间:2024/05/20 09:10
*当执行”Hello World“程序时,是从main函数开始执行的吗?答案当然是No,No,No!*
一个程序的运行步骤如下:
操作系统在在创建进程后,把控制权交给了程序的入口,这个入口一般是运行库中的某个入口函数
入口函数对运行库和程序运行环境进行初始化,包括堆、I/O、线程、全局变量构造等
入口函数完成初始化后,调用main函数,正式开始执行程序主体部分
main函数执行完毕后,返回到入口函数,入口函数进行清理工作,包括全局变量析构、堆销毁、关闭I/O等,然后进行系统调用结束进程
接下来具体分析的是基于glibc 2.6.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);}
栈分布情况
第二步:调用_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);//程序开始退出}
栈分布情况
第三步:调用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);}
第四步:调用_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
*终于写完了。。。经历了源码找不到、源码看不懂之后,最后还是搞定了。。(●ˇ∀ˇ●)*
阅读全文
0 0
- 装载、链接与库——main函数的前世今生
- main函数的前世今生
- MES的前世与今生
- C#的前世与今生
- GC的前世与今生
- 四元数的“前世”与“今生”
- 【DevOps】——DevOps的前世今生
- 程序的前世今生——编译、链接和加载简介
- 链接、装载与库:可执行文件的装载
- 《程序员的自我修养——链接、装载与库》
- 《程序员的自我修养——链接、装载与库》
- 链接、装载与库——进程的栈
- 链接、装载与库——进程的堆
- 《程序员的自我修养—链接、装载与库》
- 链接、装载与库——编译与链接
- 函数式编程的前世今生
- 大话面向对象的前世与今生
- SOA与水果蛋糕的前世今生
- Patrick’s blog is online now!
- nfs
- SublimeCodeIntel配置
- Netty(一)核心概念
- Python基础 for循环
- 装载、链接与库——main函数的前世今生
- pxe
- keras系列︱图像多分类训练与利用bottleneck features进行微调(三)
- LeetCodeOJ_001: Two Sum
- normalize.css使用方法
- Sql Server服务远程过程调用失败
- 个人学习总结一机器学习入门(十)
- Linux文件编程
- 关键字之操作符(运算符)