-------------------------------------------------------------------------
刘旸 + 原创作品转载请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
-------------------------------------------------------------------------
基础知识:
1. 可执行程序是怎么得来的:
2. 目标文件的格式ELF
三种:可重定位目标文件、共享目标文件、可执行目标文件
一个可重定位文件保存着代码和适当的数据,用来和其他的object文件一起来创建一个可执行文件或者是一个共享文件。
一个可执行文件保存着一个用来执行的程序;该文件指出了exec(BA_OS)如何来创建程序进程映象。
一个共享object文件保存着代码和合适的数据,用来被下面的两个链接器链接。第一个是连接编辑器[请参看ld(SD_CMD)],可以和其他的可重定位和共享object文件来创建其他的object。第二个是动态链接器,联合一个可执行文件和其他的共享object文件来创建一个进程映象。
3. 可执行程序的装载
execve与fork是比较特殊的系统调用,execve用它加载的可执行文件把当前的进程覆盖掉,返回之后就不是原来的程序而是新的可执行程序起点。
sys_execve内部会解析可执行文件格式:do_execve -> do_execve_common -> exec_binprm,earch_binary_handler符合寻找文件格式对应的解析模块。
ELF可执行文件默认映射到0x8048000这个地址,需要动态链接的可执行文件先加载连接器ld,否则直接把elf文件entry地址赋值给entry即可。start_thread(regs,
elf_entry, bprm->p)会将CPU控制权交给ld来加载依赖库并完成动态链接;对于静态链接的文件elf_entry是新程序执行的起点。
下面我们通过实验来验证上述的过程:
1. 首先更新menu文件夹。
2. 查看更新后的menu文件夹,发现多了一个test_exec.c文件和一个hello.c文件:
3. 查看test_exec.c的代码,发现相较test.c,加入了exec命令:
4. 再查看hello.c的代码,发现其目的是为了打印hello world:
5. 用test_exec.c文件替换掉原来的test.c文件。
6. 查看Makefile文件内容:
7. 使用make rootfs命令将MenuOS运行起来:
8. 在MenuOS中键入help,发现新添加了exec命令;键入exec命令,效果如下:
9. 使用qemu命令重新编译运行MenuOS。
10. 分割出一个窗口,使用gdb的一系列命令调试MenuOS。断点设置如下:
11. continue到可以在MenuOS中键入命令;键入命令exec,可以看到程序停在了sys_exec处:
12. 使用s命令进入sys_exec中,跟踪它的行为。
13. continue至第三个断点处。
可以看到start_thread函数将会修改ip和sp
14. 查看new_ip的地址:
15. 使用readelf -h hello命令查看可执行文件hello的elf文件:
可以看到hello的入口地址就是上面new_ip的地址。
分析与总结:
新的可执行程序通过修改内核堆栈eip作为新程序的起点,从new_ip开始执行后start_thread把返回到用户态的位置从int
0x80的下一条指令变成新加载的可执行文件的入口位置。
当执行到execve系统调用时,进入内核态,用execve()加载的可执行文件覆盖当前进程的可执行程序,当execve系统调用返回时,返回新的可执行程序的执行起点(main函数),所以execve系统调用返回后新的可执行程序能顺利执行。
execve系统调用返回时,如果是静态链接,elf_entry指向可执行文件规定的头部(main函数对应的位置0x8048***);如果需要依赖动态链接库,elf_entry指向动态链接器的起点。动态链接主要是由动态链接器ld来完成的。
附上sys_execve系统调用过程的简易示意图: