-------------Linux内核如何装载和启动一个可执行程序---------------
这次实验是要分析Linux内核是如何装载和启动一个可执行程序的。这个实验可以说有点意思。为什么这样说呢?倒不是这次实验有多难,而是这次涉及到的背后的知识其实一门非常有趣又古老的学问。
让我们一起来看看!每次我们写好了一段代码,最后都生成一个C文件(后缀名.c),我们那编译器编译运行,这段代码就变成了一个可执行的程序(后缀名.out等)跑了起来(注:对于操作系统来说,每个文件都是一样的,CPU只认识二进制码,并不认识后缀名。所以无论是.c还是.out等等,至于CPU如何区分先不讨论,在这使用后缀名标注,仅为了我们自己更好识别、区分、理解)。那么,这背后发生了什么呢?其实这就是那门古老的技术:编译、链接和运行。
之所以说它古老,是因为这门技术打计算机出现之时就相应而生,到c等中级语言的产生就基本接近完成(后面只是对其他语言的扩展)。而在这之后到现在的几十年间,它几乎没什么变化(不考虑对新版本的兼容与语言的扩展,单从技术角度,甚至可以说毫无变化)。而且对其的资料与研究也几乎没有,它就好像被忽略了一样。直到2009年,一本书的出版,让这门技术重新回到了人们的视野中,为每个程序员所熟知,并将其奉为经典(虽然该技术还是没什么变化)。这本书就是《程序员的自我修养—链接、装载与库》。
希望大家能有空读一下这本书,详细易懂。具体的内容不再赘述,我们还是以实验的方式来一起看看。
这里再补充一点点相关知识。
一个可执行程序的格式:*.out;COFF;PE;ELF等。
(1)*.out是最古老的,伴随着CC编译器而生。
(2)COFF(Common Object File Format通用对象文件格式),是为了和编译器产生的目标文件(*.o/*.obj)相区别,并非纯粹为了可执行程序而生,许多库文件也用这种格式。
(3)PE目前windows主要的可执行程序格式,就是人们口中常说的PE格式,其文件用后缀面.exe标识。
(4)ELF目前linux主要的可执行程序格式。
我们这次实验主要针对的是ELF格式。
ELF头部结构体如下图。
开始实验
这次跟踪分析一个execve系统调用内核处理函数sys_execve。
将test.c替换为test_exec.c,仍命名为test.c,为menu增加exec功能。
新的test.c的main函数中为界面增加了exec的选项。
查看exec本身代码内容。就是一个简单的程序。子进程执行了hello。
查看hello.c代码。就是输出hello world。
重新编译运行menu,然后输入exec命令,来看一下最终执行的效果。
重新加上-s –S运行,然后利用gdb跟踪,设3个断点:sys_execve、load_elf_binary、start_thread。
继续运行,启动menu过程中也会触发断点,不要理会,继续就好。待menu启动完成,输入exec命令,这时会停在第一个断点。
可以看到接下来真正运行的是SYSCALL_DEFINE3,查看这部分代码。
继续运行,会停在第二个断点处。这部分装载的代码为了对各种文件与版本的兼容,非常复杂,我们不予理会,只要知道现在是装载的过程既可。
继续运行,停在第三个断点。
我们看到,经过之前的步骤,有一个新的ip地址new_ip作为参数传递过来。我们打印一下,发现这个地址是0x8048d2a(.text段中),其内容目前无法访问。从这里我们可以推测出,这个就是我们的可执行程序的起始位置,即用户态第一条指令的位置。
我们来看看我们的可执行文件。这时要用到shell命令。shell命令就是在gdb中打开一个shell,简单地说,就是在gdb中调用linux的命令窗口(注意我们在gdb中),然后我们可以跟平常正常使用linux命令行一模一样(这个过程本质上就是靠进程调度实现的)。
查看hello(ELF文件)的头部信息。
退出后又回到了之前的gdb。
查看start_thread代码。
单步运行。可以看到对寄存器的修改情况。
这之后经过一点点的善后工作,程序就将进入用户态执行。
总结
一个可执行程序产生的过程基本如下图描述。
产生了该可执行程序后,原可执行程序通过修改内核堆栈eip,从new_ip开始执行后start_thread把返回到用户态的位置从int 0x80的下一条指令变成新可执行文件的开始。
执行execve系统调用时,进入内核态,用execve()加载的可执行文件覆盖当前进程的可执行程序,当execve系统调用返回时,返回子进程的执行起点,而后新的子进程能顺利执行。
显然在这个过程中,第一个可执行程序是要手动装载的,也就是init。
-------------------------------------END----------------------------------------
刘建鑫 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程
http://mooc.study.163.com/course/USTC-1000029000
-----------------------------------------------------------------------------------