glibc 启动过程在不同的情况下差别很大,静/动态和库/执行文件四种组合都不一样
在静态可执行文件的情况下:
- bp 清零
- 在调用
_start
之前,装载器会将用户的参数和环境变量压入栈中,pop 出 argc 给 si - 将栈顶地址(sp)也就是 argv 地址传给 c
- 调用
__libc_start_main(main,argc,argv,__libc_csu_init,__libc_csu_fini,edx,top of the stack)
argv
里紧跟着环境变量表 init
是 main
调用前的初始化工作,fini
是 main
结束之后的收尾工作,rtld_fini
是和动态加载有关的收尾工作(runtime loader)
最后的 stack_end
标明了栈底地址
__libc_start_main
中:先找到环境表,赋值给一些全局变量
__pthread_initialize_minimal();
__cxa_atexit(rtld_fini,...);
__libc_init_first(fini,...);
(*init)(argc,argv,__environ);
安排一些关键函数执行
最后在 __libc_start_main
的末尾,最关键的两行代码:
result = main(argc,argv,__environ);
exit(result);
其中 exit 按照 __cxa_atexit()
和 atexit
注册的函数的量表循环调用这些注册的函数
最后的 _exit
函数由汇编实现,且和平台相关,一般是调用系统调用