Linux内核如何装载和启动一个可执行程序
编译链接的过程和ELF可执行文件格式
ELF(Executable and Linking Format)是一种对象文件的格式,用于定义不同类型的对象文件(Object files)中都放了什么东西、以及都以什么样的格式去放这些东西。它自最早在 System V 系统上出现后,被 xNIX 世界所广泛接受,作为缺省的二进制文件格式来使用。
所谓对象文件(Object files)有三个种类:
- 可重定位的对象文件(Relocatable file)
这是由汇编器汇编生成的 .o 文件。后面的链接器(link editor)拿一个或一些 Relocatable object files 作为输入,经链接处理后,生成一个可执行的对象文件 (Executable file) 或者一个可被共享的对象文件(Shared object file)。我们可以使用 ar 工具将众多的 .o Relocatable object files 归档(archive)成 .a 静态库文件。
- 可执行的对象文件(Executable file)
文本编辑器vi、调式用的工具gdb、播放mp3歌曲的软件mplayer等等都是Executable object file。
- 可被共享的对象文件(Shared object file)
这些就是所谓的动态库文件,也即 .so 文件。如果拿前面的静态库来生成可执行程序,那每个生成的可执行程序中都会有一份库代码的拷贝。如果在磁盘中存储这些可执行程序,那就会占用额外的磁盘空间;另外如果拿它们放到Linux系统上一起运行,也会浪费掉宝贵的物理内存。如果将静态库换成动态库,那么这些问题都不会出现。动态库在发挥作用的过程中,必须经过两个步骤:
-
链接编辑器(link editor)拿它和其他Relocatable object file以及其他shared object file作为输入,经链接处理后,生存另外的 shared object file 或者 executable file。
-
在运行时,动态链接器(dynamic linker)拿它和一个Executable file以及另外一些 Shared object file 来一起处理,在Linux系统里面创建一个进程映像。
ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。实际上,一个文件中不一定包含全部内容,而且他们的位置也未必如同所示这样安排,只有ELF头的位置是固定的,其余各部分的位置、大小等信息有ELF头中的各项值来决定。
ELF文件:ELF(Excutable and Linking Format)是一个文件格式的标准。通过readelf-h hello查看可执行文件hello的头部(-a查看全部信息,-h只查看头部信息),头部里面注明了目标文件类型ELF32。Entry point address是程序入口,地址为0x400440
exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件,如果不是可以执行的文件,那么就解释成为一个shell文件,shell执行。
实验:使用gdb跟踪分析一个execve系统调用内核处理函数
- 更新menu内核
- 查看test.c文件,可以看到新增加了exec系统调用
- 启动内核并验证execv函数
- 启动GDB调试
- 停在sys_execve处,再设置其它断点;按c一路运行下去直到断点sys_execve
- 退出调试状态,输入readelf -h hello可以查看hello的EIF头部
总结
当Linux内核或程序使用fork函数创建子进程后,子进程往往要调用一种exec函数(exec家族的一种)以执行另一个程序;在调用一种exec函数时,该进程执行的程序完全被替换为新程序,而新程序则从其main函数处开始执行,因为调用exec函数并不创建新进程,所以前后的进程ID并未改变,或者说exec函数只是用了一个全新的程序替换了当前进程的正文、数据段和堆栈段。