原文:http://www.cnblogs.com/petede/p/5351696.html

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

姓名:李冬辉

学号:20133201

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

云课堂笔记:

(1)可执行文件的创建

C代码(.c) - 经过编译器预处理,编译成汇编代码(.asm) - 汇编器,生成目标代码(.o) - 链接器,链接成可执行文件(.out) - OS将可执行文件加载到内存里执行。如图

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    链接成可执行文件(ELF格式,二进制文件)
在hello可执行文件里面使用了共享库,会调用printf,libc库里的函数
gcc -o hello.static hello.o -m32 -static    静态链接
把执行所需要依赖的东西都放在程序内部

(2)ELF三种主要的目标文件:

1.可重定位:保存代码和适当数据,用来和其他的object文件一起创建可执行/共享文件,主要是.o文件

2.可执行文件:指出了exec如何创建程序进程映像,怎么加载,从哪里开始执行

3.共享object文件:保存代码和适当数据,用来被下面的两个连接器链接。

(1)连接editor,连接可重定位、共享object文件。即装载时链接。

(2)动态链接器,联合可执行、其他共享object文件创建进程映像。即运行时链接。

(3)可执行程序的执行环境

  • 命令行参数和shell环境,一般我们执行一个程序的Shell环境,我们的实验直接使用execve系统调用。

    • $ ls -l /usr/bin 列出/usr/bin下的目录信息

    • Shell本身不限制命令行参数的个数,命令行参数的个数受限于命令自身

      • 例如,int main(int argc, char *argv[])

      • 又如, int main(int argc, char *argv[], char *envp[])

    • Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数

      • int execve(const char * filename,char * const argv[ ],char * const envp[ ]);

      • 库函数exec*都是execve的封装例程

命令行参数和环境串都放在用户态堆栈中

(4)可执行程序的装载

  • 命令行参数和shell环境,一般我们执行一个程序的Shell环境,我们的实验直接使用execve系统调用。

    • Shell本身不限制命令行参数的个数,命令行参数的个数受限于命令自身

      • 例如,int main(int argc, char *argv[])

      • 又如, int main(int argc, char *argv[], char *envp[])

    • Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数

      • int execve(const char * filename,char * const argv[ ],char * const envp[ ]);

      • 库函数exec*都是execve的封装例程

  • sys_execve内部会解析可执行文件格式

实验步骤:

1、先把menu删掉,在克隆一个,用test_exec.c覆盖掉test.c。

2、打开test.c。发现增加了一句MenuConfig。

3、打开Makefile,首先静态编译了hello.c,生成根文件系统时把init和hello都放入rootfs image里面,这样执行exec的时候就自动的帮我们加载hello这个文件。

4、执行结果hello world! 是新加载的一个可执行程序输出的。

5、-S -s单步调试,窗口被冻结。

6、设置三个断点:sys_execve,load_elf_binary,start_thread。

7、list列出来跟踪, 输入s可以进入do_execve的内部。按c继续执行,跑到load_elf_binary。list查看代码,输入n一句一句跟踪,nnnc,追踪到start_thread。

8、观察hello这个可执行程序的入口,发现也是0x8048d0a,和new_ip的位置一样。new_ip是返回到用户态第一条指令的地址。

9、将new_ip和new_sp赋值,并设了一个新堆栈。

实验截图:

Linux系统加载可执行程序所需处理过程的理解

1. 新的可执行程序是从哪里开始执行的?

当execve()系统调用终止且进程重新恢复它在用户态执行时,执行上下文被大幅度改变,要执行的新程序已被映射到进程空间,从elf头中的程序入口点开始执行新程序。

如果这个新程序是静态链接的,那么这个程序就可以独立运行,elf头中的这个入口地址就是本程序的入口地址。

如果这个新程序是动态链接的,那么此时还需要装载共享库,elf头中的这个入口地址是动态链接器ld的入口地址。

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

新的可执行程序执行,需要以下: 
1. 它所需要的库函数。 
2. 属于它的进程空间:代码段,数据段,内核栈,用户栈等。 
3. 它所需要的运行参数。 
4. 它所需要的系统资源。 
如果满足以上4个条件,那么新的可执行程序就会处于可运行态,只要被调度到,就可以正常执行。我们一个一个看这几个条件能不能满足。 
条件1:如果新进程是静态链接的,那么库函数已经在可执行程序文件中,条件满足。如果是动态链接的,新进程的入口地址是动态链接器ld的起始地址,可以完成对所需库函数的加载,也能满足条件。 
条件2:execve系统调用通过大幅度修改执行上下文,将用户态堆栈清空,将老进程的进程空间替换为新进程的进程空间,新进程从老进程那里继承了所需要的进程空间,条件满足。 
条件3:我们一般在shell中,输入可执行程序所需要的参数,shell程序把这些参数用函数参数传递的方式传给给execve系统调用,然后execve系统调用以系统调用参数传递的方式传给sys_execve,最后sys_execve在初始化新程序的用户态堆栈时,将这些参数放在main函数取参数的位置上。条件满足。 
条件4:如果当前系统中没有所需要的资源,那么新进程会被挂起,直到资源有了,唤醒新进程,变为可运行态,条件可以满足。 
综上所述,新的可执行程序可以顺利执行。

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

execve系统调用会调用sys_execve,然后sys_execve调用do_execve,然后do_execve调用do_execve_common,然后do_execve_common调用exec_binprm,在exec_binprm中:

对于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控制权还给新进程。

总结:

可执行文件是一个普通的文件,它描述了如何初始化一个新的执行上下文,也就是如何开始一个新的计算。可执行文件类别有很多,在内核中有一个链表,在init的时候会将支持的可执行程序解析程序注册添加到链表中,那么在对可执行文件进行解析时,就从链表头开始找,找到匹配的处理函数就可以对其进行解析。 
在shell中启动一个可执行程序时,会创建一个新进程,它通过覆盖父进程(也就是shell进程)的进程环境,并将用户态堆栈清空,获得需要的执行上下文环境。 
命令行参数和环境变量会通过shell传递给execve,excve通过系统调用参数传递,传递给sys_execve,最后sys_execve在初始化新进程堆栈的时候拷贝进去。 
load_elf_binary->start_thread(…)通过修改内核堆栈中EIP的值作为新程序的起点。

Linux内核如何装载和启动一个可执行程序(转)的更多相关文章

  1. 20135202闫佳歆--week 7 Linux内核如何装载和启动一个可执行程序--实验及总结

    week 7 实验:Linux内核如何装载和启动一个可执行程序 1.环境搭建: rm menu -rf git clone https://github.com/megnning/menu.git c ...

  2. 实验七:Linux内核如何装载和启动一个可执行程序

    原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 题目自拟,内容围绕对Linu ...

  3. 作业七:Linux内核如何装载和启动一个可执行程序

    作业七:Linux内核如何装载和启动一个可执行程序 一.编译链接的过程和ELF可执行文件格式 可执行文件的创建——预处理.编译和链接 在object文件中有三种主要的类型. 一个可重定位(reloca ...

  4. linux内核分析 第七周 Linux内核如何装载和启动一个可执行程序

    一.编译链接的过程和ELF可执行文件格式 vi hello.c gcc -E -o hello.cpp hello.c -m32 //预处理.c文件,预处理包括把include的文件包含进来以及宏替换 ...

  5. Linux内核设计第七周学习总结 Linux内核如何装载和启动一个可执行程序

    陈巧然原创作品 转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-100002900 实验目的 使用gdb跟踪s ...

  6. Linux内核分析第七周学习笔记——Linux内核如何装载和启动一个可执行程序

    Linux内核分析第七周学习笔记--Linux内核如何装载和启动一个可执行程序 zl + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study. ...

  7. 20135323符运锦----第七周:Linux内核如何装载和启动一个可执行程序

    可执行程序的装载 一.预处理.编译.链接和目标文件的格式 1.可执行程序是怎么得来的 ①编译器预处理 gcc -E -o XX.cpp XX.c (-m32)// 注:把include的文件包含进来, ...

  8. Linux内核分析作业7:Linux内核如何装载和启动一个可执行程序

            1.可执行文件的格式 在 Linux 平台下主要有以下三种可执行文件格式: 1.a.out(assembler and link editor output 汇编器和链接编辑器的输出) ...

  9. 第七周——Linux内核如何装载和启动一个可执行程序

    万子惠 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 part1 实验 ...

随机推荐

  1. 点云PCL中小细节

    计算点与点之间的距离的平局距离 double computeCloudResolution (const pcl::PointCloud<PointType>::ConstPtr & ...

  2. c#.net基础

    值类型:值类型的实例一般在线程的栈上分配 引用类型:引用类型的实例在线程的托管堆上分配 引用类型变量的Equals比较的是二者的引用地址而不是内部的值,值类型变量的Equals方法比较的是二者的值. ...

  3. Kiss MySQL goodbye for development and say hello to HSQLDB

    The days of using MySQL, DB2, PostgreSQL etc for development is  over.. I don’t know why any program ...

  4. Spark学习笔记——基于MLlib的机器学习

    使用MLlib库中的机器学习算法对垃圾邮件进行分类 分类的垃圾邮件的如图中分成4个文件夹,两个文件夹是训练集合,两个文件夹是测试集合 build.sbt文件 name := "spark-f ...

  5. mybatis xml 文件中like模糊查询

    1.直接传参法 直接传参法,就是将要查询的关键字keyword,在代码中拼接好要查询的格式,如%keyword%,然后直接作为参数传入mapper.xml的映射文件中. 2.CONCAT()函数 My ...

  6. [React] 14 - Redux: Redux Saga

    Ref: Build Real App with React #14: Redux Saga Ref: 聊一聊 redux 异步流之 redux-saga  [入门] Ref: 从redux-thun ...

  7. Kubernetes – Ingress

    用户在 Kubernetes 上部署的服务一般运行于私有网络,Pod和Service 提供了 hostPort,NodePort等参数用于暴露这些服务端口到K8S节点上,供使用者访问.这样的方法有明显 ...

  8. CentOS 6 添加root权限账户

    sudo adduser william sudo passwd william //两次输入密码 赋予root权限 visudo 找到 ## Allow root to run any comman ...

  9. 【CF576E】Painting Edges 线段树按时间分治+并查集

    [CF576E]Painting Edges 题意:给你一张n个点,m条边的无向图,每条边是k种颜色中的一种,满足所有颜色相同的边内部形成一个二分图.有q个询问,每次询问给出a,b代表将编号为a的边染 ...

  10. Easy Way to Get All Dependent Library Names 快速获得所有依赖库名称

    在编译一些大型SDK的时候,比如Qt,OpenCV, PCL, VTK, ITK等等,在VS中,我们需要将编译生成的.lib文件加入Linker->Input中,但是往往生成的.lib文件有很多 ...