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

陈民禾——原创做品转载请注明出处—— 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000编辑器

一.上周内容总结复习函数

上一周学习了Linux 如何存放和表示进程(用task_ struct 和thread_info ),如何建立进程(经过fork(),实际上最终是clone()),如何把新的执行映像装入到地址空间(经过execO 系统调用族〉,如何表示进程的层次关系,父进程又是如何收集其后代的信息(经过wait()系统调用族),以及进程最终如何消亡〈强制或自愿地调用exit()) 。学习

进程的状态大体以下图所示spa

二.几个重要概念3d

可执行程序:可执行程序以C语言代码为例,通过编译器的预处理,处理完以后把它编译成汇编代码,而后有个汇编器将它编译成汇编代码,而后,将其连接成可执行文件。调试

目标文件的格式ELF,常见的目标文件格式:A.out  COFF后来发展为PE和ELF,目标文件也常常叫作ABI ,也就是应用程序二进制接口,实际上在二进制兼容的格式这个目标文件已经适应了某种CPU体系结构上的二进制指令,好比说一个32位X86文件链接成arm可执行文件是不能够的,在ELF文件中有三种可执行文件,可重定位文件:保存着代码和适当的数据,用来和其余的object文件一块儿建立一个可执行文件或者是一个共享文件。可执行文件:保存着一个用来执行的程序,该文件指出了exec如何来建立程序进程映像共享object文件:保存着代码和合适的数据,用来被下面两个连接器链接,一个是链接编辑器,能够和其余的可重定位和共享object文件来建立其余的object;第二个是动态连接器,联合一个可执行文件和其余共享的object文件来建立一个进程映像ELF目标文件格式:code

object文件参与程序的联接(建立一个程序)和程序的执行(运行一个程序),orm

-Figure 1-1;Object File Format
  Linking view                                                          Execute View
  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)
一个ELF文件在文件的开头,保存了路线图(road map),描述了该文件的组织状况,程序头表(program header table)告诉系统如何来建立一个进程的内存映像。section头表(section header table)包含了描述文件的section信息,每一个section在这个表中有一个入口;每一个入口给出了该section的名字,大小,等等信息。
可使用read elf来看elf文件的信息。当建立或者增长一个进程映像的时候,系统在理论上将拷贝一个文件的段到一个虚拟的内存段。拷贝到进程的起点 0x8048000

动态可执行文件:它要依赖这个可执行程序,须要其余的动态连接库,这个动态连接库,某一个点它也要依赖其余的动态连接库,动态连接库的动态连接库,动态连接库包括可执行文件,实际上动态连接库的依赖关系会造成一个图,ELF格式的文件,假如说都是同样的,就会对ELF文件进行解析,看它依赖了哪些动态连接库,这样它就会加载。blog

三.重要知识和过程接口

可执行文件产生过程:好比咱们以一个hello world程序为例,咱们能够把.c文件作预处理

预处理命令:
gcc -E -o hello.cpp hello.c -m32
预处理负责把include的文件包含进来及宏替换等工做,咱们能够看到hello.cpp里面把原来的文件对字符串进行一个处理。
把与处理的文件编译成汇编代码:
gcc -x cpp -output -S -o hello.s hello.cpp -m32
汇编代码处理成目标文件:
gcc -x assembler -c hello.s -o hello.o -m32
编译汇编和连接,这样就获得了一个二进制的文件hello.o,前面的都是文本文件是可读的,这里hello.o打开以后是文本文件是乱码
hello.o将其连接为可执行文件
gcc -o hello hello.o -m32
这时候能够看到hello.o是一个可执行文件,也是一个二进制文件,这个二进制文件打开后都是elf格式的文件,
这样编译出来的hello文件时使用共享库的,它会调用printf也就是libc,c库里面的函数,若是咱们是静态编译的那就加一个-static.c,咱们编译出来的是一个彻底全部须要执行的程序都在内部,能够看到:
gcc -o hello.static hello.o -m32
hello.static把c库里面的东西也放到可执行程序里面了,待会儿会涉及到静态连接动态连接,共享库等内容,是怎样执行可执行文件的可能会有帮助

可执行文件和进程的地址空间:当一个可执行文件ELF加载到内存的时候,它是怎么加载的呢,咱们加载的效果知道,把代码的数据加载到一块内存中来,把数据加载到内存中来,固然代码有不少块代码,不少代码段,加载进来以后默认elf加载到0x8048000从这个位置开始加载,那么加载以后可能以前是一个ELF头部文件的信息,通常来说,这个头部大小的文件信息可能就是会有不一样,因此加载时的入口点的位置可能不一样,这个地方就是程序的实际入口,当启动一个新的程序的时候,它就是从这个地方开始执行,加载到启动一个新的进程,启动一个刚加载过可执行文件的进程,一个新的进程只是fork了原来的一份,它的执行位置仍是执行了原来那个进程的位置,加载了新的可执行文件以后,开始执行的入口点,这个是一个静态连接的ELF可执行文件,这个时候都已经帮咱们连接好了,从这里开始一个文件一个文件的开始执行,怎么压栈出栈,怎么来操做,能把整个程序执行完,也就是从main函数到main函数执行完毕。

装载可执行程序以前的工做:咱们通常是经过share程序来执行一个可执行程序,当咱们装载一个可执行程序,也就是咱们发起一个系统调用execve,咱们还须要准备哪些,这个share环境为咱们准备了哪些可执行的上下文环境,这样咱们就大概在用户态的执行文件大概了解一下,而后咱们看一下一个execve,它怎么把一个可执行文件在内核里面装载起来,又返回到用户态。

四.实验过程截图及分析

 打开窗口加载qemu,克隆新版本

查看makefile的代码

查看关键代码:父子进程

冻结窗口开始进行调试:

使用gdb设置断点,能够看到分别在sys_execve和load_elf_binary处设置断点

查看附近的代码:给新栈的赋值。

执行到start_thread的时候有一个问题:
new ip究竟是指向哪里的?
用po(print object)指令:

po new_ip 能够看到一个地址:0x80495ba

enter description here

readelf -h hello 找到hello这个可执行程序的入口地址。 这是一个静态编译的可执行文件。

enter description here

new ip是返回用户态的第一条指令的地址。

五.学习本周知识的总结

   用一个比较形象的比喻就是实际上咱们把原来的可执行程序,也就是share可执行程序,int 0x80进入到这个execve的系统调用入睡,当他入睡的时候加载到一个新的可执行程序,加载到新的可执行程序,return 返回以后,也就是它醒来了,蝴蝶执行了在蝴蝶内部的程序,它若是加载庄子,这二者老是想相对的,但都是同一进程,只是把进程里面的可执行程序给替换掉了,这就是咱们对进程如何加载和运行的一个基本的描述。 

相关文章
相关标签/搜索