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

一.上周内容总结复习

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

进程的状态大致如下图所示

二.几个重要概念

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

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

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

-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文件进行解析,看它依赖了哪些动态链接库,这样它就会加载。

三.重要知识和过程

可执行文件产生过程:比如我们以一个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 返回之后,也就是它醒来了,蝴蝶执行了在蝴蝶内部的程序,它如果加载庄子,这两者总是想相对的,但都是同一进程,只是把进程里面的可执行程序给替换掉了,这就是我们对进程如何加载和运行的一个基本的描述。 

posted on 2016-04-07 15:04  20135124  阅读(357)  评论(0编辑  收藏  举报