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

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

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

2 进程的信任状和权能

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

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

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

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

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

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

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

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

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

main()添加:

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

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

设置断点:

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

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

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

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

继续执行,停在do_open_exec:

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

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

继续执行:

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

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

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

输入exec:

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

发现程序停在:

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

执行:

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

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

接着执行:

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

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

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

我们知道只有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(regself_entrybprm->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

上一篇:计算机网络课设之基于UDP协议的简易聊天机器人


下一篇:.Net程序员学用Oracle系列(5):三大数据类型