程序员的自我修养 - 入口函数和程序初始化
来源:互联网 发布:pubwin2015免刷卡软件 编辑:程序博客网 时间:2024/06/14 07:34
glibc程序入口
程序从main开始吗?
由全局变量可知,main函数执行前就已经完成全局变量的初始化了,由此判断main并非程序的入口!
从全局对象及atexit可知,在main结束之后,仍然执行全局对象的析构及调用atexit注册的收尾函数,由此判断main结束不代表进程结束!
到底哪个函数才是程序的入口呢?
视平台不同,入口函数(又称入口点)的名称不同。在此,仅讨论linux系统的入口(glibc),windows的入口可参见《程序员的自我修养》P324。
glibc的启动过程在不同的情况下差别较大,比如静态glibc和动态glibc,glibc用于可执行文件和用于共享库,这样的差别可组合出4种情况,在此以最简单的可执行文件+静态glibc为例进行说明。
glibc的入口函数为_start(由ld链接器默认的链接脚本指定,可通过参数人为设定自己的入口)。_start由汇编实现,平台相关。书中以i386为例
这省略了不重要代码,可以看到_start最终调用了__libc_start_main。加粗部分为对该部分的完整调用过程:前7个push完成给__libc_start_main(有7个形参)传递参数的任务。
在看回最开始的三条指令前,必须清楚的知道:在调用_start前,装载器会把用户的参数和环境变量压入栈中。
明白了这点,再看回前三条指令:
- xor指令(异或)的作用是将ebp寄存器置0,目的是表明当前是程序的最外层函数。
- pop指令将argc值(即参数个数)传递给esi寄存器
- mov指令将arg0的地址存放到ecx寄存器
通过以上分析,可将_start简化成如下可读性代码:
接着分析__libc_start_main,接口如下:
与上述push的七个参数正好一致,并且参数由右至左入栈(涉及调用惯例问题)。其中:
- ubp_av为arg0的地址
- init为main调用前的初始化函数
- fini为main调用后的收尾工作
- rtld_fini处理与动态加载有关的收尾工作(rtld = runtime loader)
- stack_end为栈底地址,即栈空间的起始地址(栈空间由高地址往地址增长),__libc_start_main内部将会保存栈底地址以备他用。
鉴于__libc_start_main代码略长,就不po出来了,以下简单讲解它主要完成的工作:
- 保存环境变量栈地址(作为参数传递给init函数和main函数)
- 保存栈底地址(书里没讲解具体用途)
- 检查操作系统版本
- 通过glibc内部函数__cxa_atexit(功能类似atexit)注册收尾函数rtld_fini和fini
- 执行init函数
- 调用main函数
- 调用exit函数
exit的代码如下:
void exit (int status) { while (__exit_funcs != NULL) { // __exit_funcs是存储由__cxa_atexit和atexit注册的函数链表 // 执行注册函数 __exit_funcs = __exit_funcs->next; } ... _exit(status);}
最后的_exit由汇编实现,平台相关。以i386为例:
可见,_exit仅仅调用了exit这个系统调用(__NR_exit与exit系统调用 一样吗?如果一样的话,岂不是陷入endless loop?)。_exit调用后,进程就结束了。
由此可见,进程正常结束有两种情况:
- main正常返回即未调用exit系统调用,在__libc_start_main执行完main后会主动调用exit
- 若main里主动调用exit,则不执行进程直接结束,不返回到__libc_start_main
亦即,无论如何,exit必然会被执行到,它是进程结束的必经之路。
hlt作用
正常情况下,调用exit后进程就结束了,即hlt不会被执行。而当exit执行失败时,进程无法正常结束,此时调用hlt即可让进程结束,释放资源。
_exit里的hlt和_start里的hlt作用相似!
代码分析路径
__start -> __libc_start_main -> exit -> _exit()
__libc_start_main主要代码
int __libc_start_main ( int (*main)(int, char**, char **), int argc, char* __unbounded *__unbounded ubp_av, __typeof (main)init, void (*fini)(void), void (*rtld_fini)(void), void* __unbounded stack_end) {#if __UNBOUNDED_POINTERS__ char** argv;#else #define argv ubp_av#endif int result; ... char** ubp_ev = &ubp_av[argc + 1]; __environ = ubp_ev; __libc_stack_end = stack_end; ... DL_SYSDEP_OSCHECK(__libc_fatal); ... __pthread_initialize_minimal(); __cxa_atexit(rtld_fini, NULL, NULL); __libc_init_first(argc, argv, __environ); __cxa_atexit(fini, NULL, NULL); (*init)(argc, argv, __environ); ... result = main(argc, argv, __environ); exit(result);}
汇编参考
http://blog.chinaunix.net/uid-10386087-id-2958668.html
- 读书笔记--程序员的自我修养-入口函数和程序初始化
- 程序员的自我修养 - 入口函数和程序初始化
- 入口函数和程序初始化
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 程序员的自我修养
- 笔记本Ubuntu14.04双显卡安装cuda7.5步骤
- 字符数组作业
- UI布局two---对话框
- 树莓派两个无线网卡搭建wifi
- HDU-2833-WuKong
- 程序员的自我修养 - 入口函数和程序初始化
- 前端学习所遇问题01
- Spring boot学习笔记 002——Spring Boot的自动配置、Command-line Runner
- react-native中flexbox布局总结
- js this 函数详解
- 麻省理工分布式系统教程--课程计划安排
- 知乎live-李笑来-人人都能用英语-笔记
- Spring boot学习笔记 003——Spring boot持久层
- 【机器学习】SVM核函数的计算