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

李洋  原创作品转载请注明出处 

Linux内核分析》MOOC课程

首先简单介绍一下相关背景:ELF(Executable and Linking Format)是一种对象文件的格式,用于定义不同类型的对象文件(Object files)中都放了什么东西、以及都以什么样的格式去放这些东西。它自最早在 System V 系统上出现后,被 xNIX 世界所广泛接受,作为缺省的二进制文件格式来使用。

所谓对象文件(Object files)有三个种类:

1) 可重定位的对象文件(Relocatable file)

这是由汇编器汇编生成的 .o 文件。后面的链接器(link editor)拿一个或一些 Relocatable object files 作为输入,经链接处理后,生成一个可执行的对象文件 (Executable file) 或者一个可被共享的对象文件(Shared object file)。我们可以使用 ar 工具将众多的 .o Relocatable object files 归档(archive)成 .a 静态库文件。

2) 可执行的对象文件(Executable file)

这我们见的多了。文本编辑器vi、调式用的工具gdb、播放mp3歌曲的软件mplayer等等都是Executable object file。

3) 可被共享的对象文件(Shared object file)

这些就是所谓的动态库文件,也即 .so 文件。如果拿前面的静态库来生成可执行程序,那每个生成的可执行程序中都会有一份库代码的拷贝。如果在磁盘中存储这些可执行程序,那就会占用额外的磁盘空间;另外如果拿它们放到Linux系统上一起运行,也会浪费掉宝贵的物理内存。如果将静态库换成动态库,那么这些问题都不会出现。动态库在发挥作用的过程中,必须经过两个步骤:

a) 链接编辑器(link editor)拿它和其他Relocatable object file以及其他shared object file作为输入,经链接处理后,生存另外的 shared object file 或者 executable file。

b) 在运行时,动态链接器(dynamic linker)拿它和一个Executable file以及另外一些 Shared object file 来一起处理,在Linux系统里面创建一个进程映像。

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

Linking 视角                      Execution 视角
  ============                      ==============
  ELF header                        ELF header
  Program header table (optional)   Program header table
  Section 1                         Segment 1
  ...                               Segment 2
  Section n                         ...
  Section header table              Section header table (optional)

具体结构定义如下:

 1  #define EI_NIDENT       16
 2 
 3   typedef struct {
 4       unsigned char       e_ident[EI_NIDENT];
 5       Elf32_Half          e_type;
 6       Elf32_Half          e_machine;
 7       Elf32_Word          e_version;
 8       Elf32_Addr          e_entry;
 9       Elf32_Off           e_phoff;
10       Elf32_Off           e_shoff;
11       Elf32_Word          e_flags;
12       Elf32_Half          e_ehsize;
13       Elf32_Half          e_phentsize;
14       Elf32_Half          e_phnum;
15       Elf32_Half          e_shentsize;
16       Elf32_Half          e_shnum;
17       Elf32_Half          e_shstrndx;
18   } Elf32_Ehdr;

e_type 它标识的是该文件的类型。 
e_machine 表明运行该程序需要的体系结构。 
e_version 表示文件的版本。 
e_entry 程序的入口地址。 
e_phoff 表示Program header table 在文件中的偏移量(以字节计数)。 
e_shoff 表示Section header table 在文件中的偏移量(以字节计数)。 
e_flags 对IA32而言,此项为0。 
e_ehsize 表示ELF header大小(以字节计数)。 
e_phentsize 表示Program header table中每一个条目的大小。 
e_phnum 表示Program header table中有多少个条目。 
e_shentsize 表示Section header table中的每一个条目的大小。 
e_shnum 表示Section header table中有多少个条目。 
e_shstrndx 包含节名称的字符串是第几个节(从零开始计数)。

而exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件,如果不是可以执行的文件,那么就解释成为一个shell文件,sh **执行。
然后就是实验过程:
1.首先设置断点sys_execve:
2.然后设置断点在load_elf_binary
3 设置断点在start_thread,并且在这里可以查看hello程序入口地址:
.
4.和静态编译的hello文件头部信息比较,发现入口地址没错:
5.寄存器状态的改变。
 

最后总结一下Linux内核ELF文件的装载过程:

检查ELF可执行文件的有效性,比如程序头表中段的数量。 
寻找动态链接的“.interp”段,设置动态链接器路径(与动态链接有关)。 
根据ELF可执行文件的程序头表的描述,对ELF文件进行映射,比如代码,数据,只读数据。 
初始化ELF进程环境。
将系统调用的返回地址修改为ELF可执行文件的入口,这个入口点取决于程序的链接方式,对于静态链接的可执行文件,这个程序入口就是ELF文件的文件头中e_entry所指的地址;对于动态链接的ELF可执行文件,程序入口点是动态链接器。新的程序开始执行,ELF可执行文件装载完成。 
当新程序开始运行后,每当遇到缺页错误(可以理解为没有为虚拟页面关联实际的物理页面,因此产生缺页错误),就会新分配一个物理页,并把该缺页内容(ELF文件的相应内容)从磁盘中读取到内存中,同时设置虚拟页面也物理页面的映射关系,并从缺页的虚拟地址处开始执行。

 

posted @ 2015-04-20 13:04  不知东方之既白  阅读(591)  评论(0编辑  收藏  举报