上次我们分析了系统调用大致过程,现在我们把这两个系统调用的代码放到MenuOS中,并用gdb跟踪调试来看看从system_call开始到iret结束之间的整个过程。
边看实验过程边分析:
首先我们要将系统里面的menu目录删去,然后从github上更新到新版的menu。
接着,把我们之前写好的getpid()的c版本和asm版本加入到menu中,最后make rootfs启动系统。这样系统中就增加了两个命令。输入pid或者pid-asm就能看到当前进程号。
MenuOS系统装好之后,就该分析这个系统特别是系统调用的过程了。我们还是用老办法,先冻结内核加载,再gdb跟踪调试。
启动gdb,加载3.18.6内核符号表,设置gdbsever远程调试端口,在start_kernel处设置断点等等就不再赘述,前面的文章有详细操作。直接来看结果。
我们主要来看看在系统调用处,这里是getpid()函数处设置断点之后,处理过程是怎样的。
系统启动过后,输入pid命令:
这块出了点状况。当我输入pid时是可以得到pid号的,而输入pid-asm却被冻结。原因可能是c版本中的getpid不是通过调用int 0x80进入中断的,而我们的汇编程序是指定了用int 0x80指令。不过这并不影响我们继续分析。用list列出此处代码也能窥晓一二。一直用s单步执行程序,一步步的观察执行情况:
用finish结束,再接着s单步执行,直到能显示当前进程号。我们还可以载system_call处设置断点,接着s单步执行:
观察完整个过程后,主要的还是对整个过程的理解。
1、用户通过执行int 0x80汇编指令,使自己从用户态切换到了内核态。通过执行ireq汇编指令,从系统调用中退出。
2、当用户进程发出int 0x80指令时,cpu切换到内核态并开始从地址system_call处开始执行指令。
3、system_call()函数首先把系统调用号和这个异常处理程序可以用到的所有CPU寄存器保存到相应栈中,再对用户态进程传来的系统调用号进行有效性检查,最后调用与eax中所包含的系统调用号对应的特定服务例程。
4、当系统调用服务例程结束时,system_call()函数从eax获得返回值。如果所有标志都没有被设置,函数跳到restore_all处执行,并执行ireq。如果有被设置的标志,那么在返回前将有一些工作要完成。