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

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

       这次实验是要分析Linux内核是如何装载和启动一个可执行程序的。这个实验可以说有点意思。为什么这样说呢?倒不是这次实验有多难,而是这次涉及到的背后的知识其实一门非常有趣又古老的学问。

       让我们一起来看看!每次我们写好了一段代码,最后都生成一个C文件(后缀名.c),我们那编译器编译运行,这段代码就变成了一个可执行的程序(后缀名.out等)跑了起来(注:对于操作系统来说,每个文件都是一样的,CPU只认识二进制码,并不认识后缀名。所以无论是.c还是.out等等,至于CPU如何区分先不讨论,在这使用后缀名标注,仅为了我们自己更好识别、区分、理解)。那么,这背后发生了什么呢?其实这就是那门古老的技术:编译、链接和运行

       之所以说它古老,是因为这门技术打计算机出现之时就相应而生,到c等中级语言的产生就基本接近完成(后面只是对其他语言的扩展)。而在这之后到现在的几十年间,它几乎没什么变化(不考虑对新版本的兼容与语言的扩展,单从技术角度,甚至可以说毫无变化)。而且对其的资料与研究也几乎没有,它就好像被忽略了一样。直到2009年,一本书的出版,让这门技术重新回到了人们的视野中,为每个程序员所熟知,并将其奉为经典(虽然该技术还是没什么变化)。这本书就是《程序员的自我修养—链接、装载与库》

       希望大家能有空读一下这本书,详细易懂。具体的内容不再赘述,我们还是以实验的方式来一起看看。


       这里再补充一点点相关知识。

       一个可执行程序的格式:*.outCOFFPEELF等。

              (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.cmain函数中为界面增加了exec的选项



       查看exec本身代码内容。就是一个简单的程序。子进程执行了hello



       查看hello.c代码。就是输出hello world。



       重新编译运行menu,然后输入exec命令,来看一下最终执行的效果。



       重新加上-s –S运行,然后利用gdb跟踪,设3个断点:sys_execveload_elf_binarystart_thread



       继续运行,启动menu过程中也会触发断点,不要理会,继续就好。待menu启动完成,输入exec命令,这时会停在第一个断点。



       可以看到接下来真正运行的是SYSCALL_DEFINE3,查看这部分代码。



       继续运行,会停在第二个断点处。这部分装载的代码为了对各种文件与版本的兼容,非常复杂,我们不予理会,只要知道现在是装载的过程既可。



       继续运行,停在第三个断点。



       我们看到,经过之前的步骤,有一个新的ip地址new_ip作为参数传递过来。我们打印一下,发现这个地址是0x8048d2a.text段中),其内容目前无法访问。从这里我们可以推测出,这个就是我们的可执行程序的起始位置,即用户态第一条指令的位置



       我们来看看我们的可执行文件。这时要用到shell命令。shell命令就是在gdb中打开一个shell,简单地说,就是在gdb中调用linux的命令窗口注意我们在gdb中),然后我们可以跟平常正常使用linux命令行一模一样(这个过程本质上就是靠进程调度实现的)。



      查看helloELF文件)的头部信息。



      退出后又回到了之前的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

-----------------------------------------------------------------------------------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值