文章标题

Linux内核如何装载和启动一个可执行程序

周恺祺 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

一、实验要求

  1. 理解编译链接的过程和ELF可执行文件格式,详细内容参考本周第一节;
  2. 编程使用exec*库函数加载一个可执行文件,动态链接分为可执行程序装载时动态链接和运行时动态链接,编程练习动态链接库的这两种使用方式,详细内容参考本周第二节;
  3. 使用gdb跟踪分析一个execve系统调用内核处理函数sys_execve ,验证您对Linux系统加载可执行程序所需处理过程的理解,详细内容参考本周第三节;推荐在实验楼Linux虚拟机环境下完成实验。
  4. 特别关注新的可执行程序是从哪里开始执行的?为什么execve系统调用返回后新的可执行程序能顺利执行?对于静态链接的可执行程序和动态链接的可执行程序execve系统调用返回时会有什么不同?

二、实验步骤

  1. 进入实验楼的Linux虚拟机环境,打开LinuxKernel文件夹,打开menu,修改test.c,参照网易云课堂的教学,添加Exec()函数,并修改主函数、创建hello.c;修改Makefile文件。即创建一个可执行文件hello。
    这里写图片描述
    这里写图片描述

修改Makefile文件
这里写图片描述

  1. 重新编译内核,并输入exec命令进行测试
    这里写图片描述

  2. 加上-s -S 进行追踪调试,利用gdb进行跟踪,并设置三个断点:sys_execve、load_elf_binary、start_thread
    这里写图片描述
    这里写图片描述

  3. 用 c 继续运行,再启动menu过程中会触发断点,此时点击c继续运行,等到menu启动完成,输入exec命令,此时会停在第一个断点
    这里写图片描述
    用list列出来跟踪,输入s可以进入do_execve内部。
    这里写图片描述
    按c继续执行,跑到load_elf_binary。list查看代码,可以输入n一句一句跟踪,可以跟踪到start_thread
    这里写图片描述
  4. 观察hello这个可执行程序的入口,发现也是0x8048d0a,和new_ip一样,new_ip是返回到用户态第一条指令的地址
    这里写图片描述

这里写图片描述

这里写图片描述

三、实验结果分析

(一)关于可执行文件

  1. 可执行文件的创建
    (1)预处理。gcc -E -o hello.cpp hello,c -m32
    预处理负责吧include文件包含进来及宏替换等工作
    (2)编译
    gcc -x cpp -output -S -o hello.s hello.cpp -m32
    编译成汇编代码
    (3)汇编 gcc -x assembler -c hello.s -o hello.o -m32
    汇编成目标代码(ELF格式)
    (4)链接 gcc -o hello hello.o -m32
    链接成可执行文件
    gcc -o hello hello.o -m32 static 静态链接:把执行所依赖东西都放在了程序内部

  2. ELF三种主要的目标文件
    (1)可重定位:保存代码和适当数据
    (2)可执行文件:指出了exec如何创建程序进程映像,怎么加载,从哪里开始执行
    (3)共享oblect文件:保存代码和适当数据用来被 链接editor;动态链接器 这两个连接器链接,

(二)对实验相关理解(参考网络)

http://jingyan.baidu.com/article/46650658c32266f548e5f86c.html
1. 新的可执行程序从哪里开始执行的?
1)当execve()系统调用终止且进程重新恢复它在用户态执行时,执行上下文被大幅度改变,要执行的新程序已被映射到进程空间,从elf头中的程序入口点开始执行新程序。
2)如果这个新程序是静态链接的,那么这个程序就可以独立运行,elf头中的这个入口地址就是本程序的入口地址。
3)如果这个新程序是动态链接的,那么此时还需要装载共享库,elf头中的这个入口地址是动态链接器ld的入口地址。

  1. 为什么execve系统调用返回后新的可执行程序能顺利执行?
    新的可执行程序执行,需要以下几个条件:
    1) 它所需要的库函数。
    2) 属于它的进程空间:代码段,数据段,内核栈,用户栈等。
    3) 它所需要的运行参数。
    4) 它所需要的系统资源。
    条件1:如果新进程是静态链接的,那么库函数已经在可执行程序文件中,条件满足。如果是动态链接的,新进程的入口地址是动态链接器ld的起始地址,可以完成对所需库函数的加载,也能满足条件。
    条件2:execve系统调用通过大幅度修改执行上下文,将用户态堆栈清空,将老进程的进程空间替换为新进程的进程空间,新进程从老进程那里继承了所需要的进程空间,条件满足。
    条件4:如果当前系统中没有所需要的资源,那么新进程会被挂起,直到资源有了,唤醒新进程,变为可运行态,条件可以满足。 综上所述,新的可执行程序可以顺利执行。

  2. 对于静态链接的可执行程序和动态链接的可执行程序execve系统调用返回时会有什么不同?
    1)execve系统调用会调用sys_execve,然后sys_execve调用do_execve,然后do_execve调用do_execve_common,然后do_execve_common调用exec_binprm,在exec_binprm中:
    2)对于ELF文件格式,fmt函数指针实际会执行load_elf_binary,load_elf_binary会调用start_thread,在start_thread中通过修改内核堆栈中EIP的值,使其指向elf_entry,跳转到elf_entry执行。 对于静态链接的可执行程序,elf_entry是新程序的执行起点。对于动态链接的可执行程序,需要先加载链接器ld, elf_entry = load_elf_interp(…) 将CPU控制权交给ld来加载依赖库,再由ld在完成加载工作后将CPU控制权还给新进程。

四、实验总结

Linux内核装载和启动一个可执行程序:
1. 首先需要创建新进程,新进程调用execve()系统调用执行指定的ELF文件,之后再调用内核的入口函数sys_execve(),sys_execve()服务例程修改当前进程的执行上下文;
2. 系统调用终止后,新进程开始执行放在可执行文件中的代码,也就是执行在当前目录下显示文件的功能。 当ELF被load_elf_binary()装载完成后,函数返回至do_execve(),再返回至sys_execve()。
3. ELF可执行文件的入口点取决于程序的链接方式,对于静态链接的可执行文件,elf_entry就是指向可执行文件里边规定的那个头部,即main函数对应的位置,若这个可执行文件是需要依赖其它动态链接库的话,则elf_entry就是指向动态链接器的起点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值