自然flw

linux内核学习之七 可执行程序的装载和运行

一 程序的装载和运行的基本知识补充

   1 当进程开始执行一个新的程序时,从父进程继承的所有页被释放,以便在新的用户地址空间开始执行新的计算,甚至进程的特权都可能发生改变,但是,进程的PID不会改变。

   2 进程的信任状和权能

      进程的信任状决定一个进程的权限,也就是能做什么,不能做什么。这对多用户系统,系统的稳定性很重要。

      进程被创建时,总是继承父进程的信任状。

      权能是引入进程信任状的另一种模式。他表示是否允许进程执行一个特定的操作或一组特定的操作。

   3 目标文件不能被执行,因为它不含源代码文件所用的全局外部符号名的线性地址,这些地址的分配是由链接程序完成的。链接程序还分析程序所用的库函数。

  4 静态链接生成的可执行文件不仅包含原程序的代码,还包含程序所引用的库函数的代码。缺点是占用大量的磁盘空间。

     动态链接程序把一个共享库链接到进程时,不拷贝目标代码,仅执行一个内存映射,把库文件的相关部分映射到进程的地址空间中,缺点是程序的启动时间较长。

二 跟踪分析执行程序的系统调用execve()

  2.1在以前的代码基础上添加

    

main()添加:

利用qemu查看调试结果(相关的基本设置可以参考前面博客)

设置断点:

执行,停在第一个断点处:

 

继续执行,停在do_open_exec:

继续执行:

输入exec:

发现程序停在:

执行:

接着执行:

 

分析:新的可执行程序是从哪里开始执行的?

     我们知道只有pc能代表程序的执行流, 父进程fork创造椅子进程,子进程执行新的可执行程序,当子进程“获得”pc时,才是子进程(可执行程序)开始执行的地方。通过GDB跟踪以及阅读源码,

void start_thread(struct pt_regs *regs, unsigned long new_pc,
		  unsigned long new_sp)
{
	regs->pr = 0;
	regs->sr = SR_FD;
	regs->pc = new_pc;
	regs->regs[15] = new_sp;

	free_thread_xstate(current);
}

 

  可以看到在函数load_elf_binary()的最后调用了函数start_thread(regs, elf_entry, bprm->p),所以elf_entry是可执行程序开始执行的地方。

为什么execve系统调用返回后新的可执行程序能顺利执行?

   因为在系统调用过程中,父进程的大部分资源被抛弃,堆栈被清空,新的可执行程序根据传递的参数和环境变量配置了一个新的堆栈。所以返回时能够正常执行。

对于静态链接的可执行程序和动态链接的可执行程序execve系统调用返回时会有什么不同?

在函数load_elf_binary中可以看到如下的一段代码:

887	if (elf_interpreter) {
888		unsigned long interp_map_addr = 0;
889
890		elf_entry = load_elf_interp(&loc->interp_elf_ex,
891					    interpreter,
892					    &interp_map_addr,
893					    load_bias);
894		if (!IS_ERR((void *)elf_entry)) {
895			/*
896			 * load_elf_interp() returns relocation
897			 * adjustment
898			 */
899			interp_load_addr = elf_entry;
900			elf_entry += loc->interp_elf_ex.e_entry;
901		}
902		if (BAD_ADDR(elf_entry)) {
903			retval = IS_ERR((void *)elf_entry) ?
904					(int)elf_entry : -EINVAL;
905			goto out_free_dentry;
906		}
907		reloc_func_desc = interp_load_addr;
908
909		allow_write_access(interpreter);
910		fput(interpreter);
911		kfree(elf_interpreter);
912	} else {
913		elf_entry = loc->elf_ex.e_entry;
914		if (BAD_ADDR(elf_entry)) {
915			retval = -EINVAL;
916			goto out_free_dentry;
917		}

 我们知道 elf_entry是系统调用返回时可执行文件开始执行的地方,由以上代码,通过静态链接返回时,elf_entry代表的是可执行文件规定的头部,而动态链接,elf_entry代表的是动态链接器的起点。

三 总结

  自己对“Linux内核装载和启动一个可执行程序”的理解

   通过以上的学习,我们知道装载和启动一个可执行程序主要是通过系统调用execve()来实现的,和普通的系统调用不同,execve系统调用返回时不是INT $0x80语句的后面一条语句,而是变成了一个新的进程,按照用户要求(参数和环境变量)创建的可执行程序。

通过一系列函数调用:do_execve()->do_execve_common()->exec_binprm()->search_binary_handler()->list_for_each_entry()->load_binary()->load_elf_binary()->start_thread(),

start_thread(regself_entrybprm->p)中修改了ip,elf_entry是可执行程序开始执行的地方。

 

 

by:方龙伟

原创作品 转载请注明出处

《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

posted on 2016-04-10 21:13  自然flw  阅读(499)  评论(0编辑  收藏  举报

导航