程序员的自我修养 - 入口函数和程序初始化

来源:互联网 发布: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前,装载器会把用户的参数和环境变量压入栈中

这里写图片描述

明白了这点,再看回前三条指令:

  1. xor指令(异或)的作用是将ebp寄存器置0,目的是表明当前是程序的最外层函数。
  2. pop指令将argc值(即参数个数)传递给esi寄存器
  3. mov指令将arg0的地址存放到ecx寄存器

通过以上分析,可将_start简化成如下可读性代码
这里写图片描述

接着分析__libc_start_main,接口如下:
这里写图片描述

与上述push的七个参数正好一致,并且参数由右至左入栈(涉及调用惯例问题)。其中:

  1. ubp_av为arg0的地址
  2. init为main调用前的初始化函数
  3. fini为main调用后的收尾工作
  4. rtld_fini处理与动态加载有关的收尾工作(rtld = runtime loader)
  5. stack_end为栈底地址,即栈空间的起始地址(栈空间由高地址往地址增长),__libc_start_main内部将会保存栈底地址以备他用。

鉴于__libc_start_main代码略长,就不po出来了,以下简单讲解它主要完成的工作

  1. 保存环境变量栈地址(作为参数传递给init函数和main函数)
  2. 保存栈底地址(书里没讲解具体用途
  3. 检查操作系统版本
  4. 通过glibc内部函数__cxa_atexit(功能类似atexit)注册收尾函数rtld_fini和fini
  5. 执行init函数
  6. 调用main函数
  7. 调用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

0 0