您的位置:首页 > 其它

程序入口函数与初始化

2015-10-19 23:02 246 查看

1.概述程序执行过程:

1)操作系统在创建进程后,将控制权交给了程序的入口,这个入口通常是运行库的某个入口函数

2)入口函数对运行库和程序运行环境进行初始化,包括堆,IO,线程,全局变量的构成

3)入口函数完成初始化后,调用 main 函数,正式执行程序主题

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

2.linux 中 glibc入口函数

1)操作系统将控制权交给进程入口,_start 函数,由汇编实现,与平台相关,以下代码仅供参考:

_start:
	xorl %ebp,%ebp					//将寄存器 ebp 清零
	popl %esi					// 弹出 argc,将其存放在 esi 中
	movl %esp,%ecp					// 使寄存器 ecp 指向 argv 与环境变量数组

	//以下为调用函数 __libc_start_main 压入参数
	pushl %esp					//压入栈底地址,即栈的最高地址
	pushl %edx					//压入函数指针,该函数完成与动态加载有关的收尾工作
	pushl $__libc_csu_fini				//压入函数指针,该函数完成 main 结束后的收尾工作
	pushl $__libc_csu_init				//压入函数指针,该函数完成 main 调用前的初始化工作
	pushl %ecs					//压入argv 与环境变量数组
	pushl %esi					//压入argc
	pushl main					// 压入 main 函数的指针
	call __libc_start_main				//调用另一个初始化函数,参数为上述压栈数据,从右至左压栈
	hlt						// 避免调用 __libc_start_main 函数中没有成功调用 exit ,hlt 将强制终止程序


2)_start 函数调用另一个函数 __libc_start_main,完成对运行环境的初始化,且传入该函数的参数为七个参数,上面代码中压入栈的七个数据从上到下对应调用函数中从右至左的参数。__lib_start_main函数的代码与解释如下:

int __libc_start_main(
	int(*main)(int, char**, char**),			//第一个参数,main 函数的指针
	int argc,
	char * __unbounded * __unbounded ubp_av,		// 第三个参数,包含了 argv 与环境变量的数组
	_typeof(main) init,					//第四个参数,初始函数指针
	void(*fini)(void),					//第五个参数,结束函数指针
	void(*rtld_fini)(void),					//第六个参数,处理动态链接的函数
	void * __unbounded stack_end				//第七个参数,栈底地址
	)
{
	char** ubp_ev = &ubp_av[argc + 1];			//使 ubp_ev 执行 argv
	__environ = ubp_env;					// 使 __environ 执行环境变量
	__libc_stack_end = stack_end;				//存储栈底地址

	//...省略许多步骤
	__cxa_atexit(rtld_fini, NULL, NULL);			//注册在 main 结束后的函数调用
	//...
	_cxa_atexit(fini, NULL, NULL);

	//...
	result = main(argc, argv, __environ);			//调用 main 函数
	exit(result);						//执行结束函数
}


函数在执行完 main 函数后,将调用 exit 退出进程,而 exit 中,将执行注册的收尾函数,对于非正常结束的进程,也会调用 exit,因此确保注册的收尾函数无论是在正常结束函数非正常结束时均会被调用。

* exit 调用所有注册的收尾函数后,将调用 _exit() 函数,该函数将直接结束,且该函数最后也有一个 "hlt" ,其功能与 _start 中的一样,预防程序调用 exit 系统调用失败时,无法结束进程。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: