码迷,mamicode.com
首页 > 系统相关 > 详细

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

时间:2016-04-10 21:29:34      阅读:332      评论:0      收藏:0      [点我收藏+]

标签:

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

一.实验原理

1.elf可执行文件格式

ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。实际上,一个文件中不一定包含全部内容,而且他们的位置也未必如同所示这样安排,只有ELF头的位置是固定的,其余各部分的位置、大小等信息由ELF头中的各项值来决定。

技术分享

技术分享

2.目标文件

目标文件至少有编译后的机器指中的内容令代码、数据。没错,除了这些内容以 外,目标文件中还包括了链接时所须要的一些信息,比如符号表、调试信息、字符串 等。

3.目标文件种类

 

(1)可重定位(relocatable)文件

 

保存着代码和适当的数据,用来和其他的object文件一起来创建一个可执行文件或者是一个共享文件。

 

(2)可执行(executable)文件

 

保存着一个用来执行的程序;该文件指出了exec(BA_OS)如何来创建程序进程映象

 

(3)共享object文件

 

保存着代码和合适的数据,用来被下面的两个链接器链接。第一个是连接编辑器[请参看ld(SD_CMD)],可以和其他的可重定位和共享object文件来创建其他的object。第二个是动态链接器,联合一个可执行文件和其他的共享object文件来创建一个进程映象。

 

4.可执行程序生成过程

技术分享

 

5.链接

链接是一个收集、组织程序所需的不同代码 和数据的过程,以便程序能被装入内存并被执行。 链接过程分为两步:

(1)空间与地址分配

扫描所有的输入目标文件,获得它们的各个段的长度、属性和位置,并且将输入目标文件中的符号定义和符号引用收集起来,统一放到一个全局符号表。这一步中,链接器将能获得所有输入目标文件的段长度,并且将它们合并,计算出输出文件中各个段合并后的长度与位置,并建立映射关系。

(2)符号解析与重定位

使用上面第一步中收集到的所有信息,读取输入文件中段的数据、重定位信息,并且进行符号解析与重定位、调整代码中的地址等。事实上第二步是链接过程的核心,特别是重定位过程。

6.静态、共享和动态库的区别

C语言中有一些函数不需要进行编译,有一些函数也可以在多个文件中使用。一般来说,这些函数都会执行一些标准任务,如数据库输入/输出操作或屏幕控制等。可以事先对这些函数进行编译,然后将它们放置在一些特殊的目标代码文件中,这些目标代码文件就称为库。库文件中的函数可以通过连接程序与应用程序进行连接。这样就不必在每次开发程序时都对这些通用的函数进行编译了。 

库可以有三种使用的形式:静态、共享和动态。静态库的代码在编译时就已连接到开发人员开发的应用程序中,而共享库只是在程序开始运行时才载入,在编译时,只是简单地指定需要使用的库函数。动态库则是共享库的另一种变化形式。动态库也是在程序运行时载入,但与共享库不同的是,使用的库函数不是在程序运行开始,而是在程序中的语句需要使用该函数时才载入。动态库可以在程序运行期间释放动态库所占用的内存,腾出空间供其它程序使用。由于共享库和动态库并没有在程序中包括库函数的内容,只是包含了对库函数的引用,因此代码的规模比较小。

静态库可以认为是一些目标代码的集合。按照习惯,一般以".a"做为文件后缀名。使用ar(archiver)命令可以创建静态库。因为共享库有着更大的优势,静态库已经不被经常使用。但静态库使用简单,仍有使用的余地,并会一直存在。

静态库在应用程序生成时,可以不必再编译,节省再编译时间。但在编译器越来越快的今天,这一点似乎已不重要。如果其他开发人员要使用你的代码,而你又不想给其源码,提供静态库是一种选择。从理论上讲,应用程序使用了静态库,要比使用动态加载库速度快1-5%,但由于莫名的原因,实际上可能并非如此。由此看来,除了使用方便外,静态库可能并非一种好的选择。

共享库
共享库是在程序启动时被装载。当一个应用程序装载了一个共享库后,其它应用程序仍可以装载同一个共享库。基于linux的使用方法,共享库还有其它灵活的而又精妙的特性:
更新库并不影响应用程序使用旧的,非向后兼容的版本;在执行特定程序时,可以覆盖整个库或更新库中的特定函数;以上操作不会影响已经运行的程序,他们仍会使用已经装载的库。

 

二.实验步骤

1.首先将exec函数写入到我们的系统之中,即test.c文件中,如下图所示:

技术分享

2.重新编译操作系统,结果如下图所示:

技术分享

3.运行测试命令如下图所示:

技术分享

4.下断点,如下图所示:

技术分享

 

5.单步进入SyS_execve函数,如下图所示:

技术分享

 

6.单步进入load_elf_binary函数中,如下图所示:

技术分享

 

7.单步进入start_thread函数中

技术分享

 

8.查看new_ip的值,如下图所示:

技术分享

 

9.查看可执行文件的elf头,如下图所示:

技术分享

 

10.可以发现程序的入口点地址正是new_ip的值

 

三.实验总结

特别关注新的可执行程序是从哪里开始执行的?为什么execve系统调用返回后新的可执行程序能顺利执行?对于静态链接的可执行程序和动态链接的可执行程序execve系统调用返回时会有什么不同?

execve过程:

(1)将可执行文件的文件名从用户空间都到内核空间    filename = getname(name); 

(2)打开可执行文件:    file = open_exec(filename);
(3)初始化用于在加载二进制可执行文件时存储与其相关的所有信息的linux_binprm数据结构:    retval = bprm_mm_init(bprm);
(4)将运行所需的参数和环境变量收集到bprm中:连续的三个copy_strings()

(5)函数的核心是:search_binary_handler。加载可执行文件。

新的可执行程序通过修改内核堆栈eip作为新程序的起点,从new_ip开始执行后start_thread把返回到用户态的位置从int 0x80的下一条指令变成新加载的可执行文件的入口位置。当执行到execve系统调用时,进入内核态,用execve()加载的可执行文件覆盖当前进程的可执行程序,当execve系统调用返回时,返回新的可执行程序的执行起点(main函数),所以execve系统调用返回后新的可执行程序能顺利执行。execve系统调用返回时,如果是静态链接,elf_entry指向可执行文件规定的头部(main函数对应的位置0x8048000);如果需要依赖动态链接库,elf_entry指向动态链接器的起点。动态链接主要是由动态链接器ld来完成的。

 

本周的实验虽然难度跟之前的实验差不多,但是非常难理解,希望自己能够利用课余时间理解消化,(*^__^*) 

 

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

标签:

原文地址:http://www.cnblogs.com/crowpurple/p/5375348.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!