陈民禾——原创作品转载请注明出处—— 《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文件参与程序的联接(创建一个程序)和程序的执行(运行一个程序),
动态可执行文件:它要依赖这个可执行程序,需要其他的动态链接库,这个动态链接库,某一个点它也要依赖其他的动态链接库,动态链接库的动态链接库,动态链接库包括可执行文件,实际上动态链接库的依赖关系会形成一个图,ELF格式的文件,假如说都是一样的,就会对ELF文件进行解析,看它依赖了哪些动态链接库,这样它就会加载。
三.重要知识和过程
可执行文件产生过程:比如我们以一个hello world程序为例,我们可以把.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
readelf -h hello
找到hello这个可执行程序的入口地址。
这是一个静态编译的可执行文件。
new ip是返回用户态的第一条指令的地址。
五.学习本周知识的总结
用一个比较形象的比喻就是实际上我们把原来的可执行程序,也就是share可执行程序,int 0x80进入到这个execve的系统调用入睡,当他入睡的时候加载到一个新的可执行程序,加载到新的可执行程序,return 返回之后,也就是它醒来了,蝴蝶执行了在蝴蝶内部的程序,它如果加载庄子,这两者总是想相对的,但都是同一进程,只是把进程里面的可执行程序给替换掉了,这就是我们对进程如何加载和运行的一个基本的描述。