首页 > 代码库 > Linux内核分析第五周作业
Linux内核分析第五周作业
分析system_call中断处理过程
这次的目标是通过gdb来跟踪上周选择的uname系统调用。因为系统调用是通过中断在内核态实现的,gdb无法调试本机的系统调用。所以必须像之前的内核跟踪那样,用gdb远程连接至qemu虚拟机进行跟踪。
1. 首先修改之前的MenuOS,添加一个myuname函数通过API的方式调用uname系统调用,直接复制了上周的代码,注意要在main函数中把这个函数添加到菜单中
用make命令编译以后,会生成一个test的可执行文件,可以先在本地运行试一试
可以看到命令运行成功。
接下来就制作rootfs并且把MenuOS放在里面。然后启动qemu并指定initrd为刚才制作的rootfs。
可以看到uname也成功地运行在qemu中了。
2. 下面使用gdb进行远程调试。
首先qemu的启动参数要加上-s来打开远程调试端口。
然后gdb加载Linux内核的符号表,并且连接到qemu的调试端口1234
gdb (gdb)file linux-3.18.6/vmlinux (gdb)target remote:1234
在系统调用的入口——system_call函数加断点,然后让qemu继续运行
接下来在qemu中执行系统调用,输入uname命令,居然没有断下来 w(?Д?)w 果然和教程中说的一样。
因为system_call是汇编写的代码,直接用gdb直接查找“system_call”符号得到的地址不准确,但是也不会差的太远。尝试从刚才的断点0xc174d881前面15个字节开始反汇编20条指令,看看情况
果然0xc174d881并不是一个有效的代码地址。于是把断点直接放在0xc174d884上,就是system_call的真正起始地址
这次就成功地断了下来
然后对照着entry_32.S单步跟踪,看看究竟做了什么
这一大堆的push显然就是SAVE_ALL所做的事情,将当前的寄存器都保存起来。
这里把EAX和0x166比较了一下。EAX里面是系统调用号,0x166是十进制的358。看一眼系统调用表,最后一个系统调用号正好是357。因此这里是说如果传入的EAX超过了最大的系统调用号,就进入异常处理程序syscall_badsys
而如果是有效的系统调用号,就call一个地址。因此再看一眼-0x3e8aede0地址附近的内容,根据无符号整数的定义,-0x3e8aede0其实就是0xc1751220
这里面似乎都是地址。随便挑第二个地址查看一下
正好就是sys_exit的入口地址,而sys_exit就是1号系统调用exit的实现函数。
参考一下源文件
-0x3e8aede0就应该是sys_call_table的地址了。至于为什么用奇怪的负数地址,可能与CPU指令有关,也许这样的指令比较短 ╮(╯_╰)╭
因此这里call的是EAX对应的系统调用的入口地址。
uname的实现函数sys_newuname上周已经看过了
很简单,uname的内容一直在内核中保存着,每次系统调用只要将内容从内核态复制到用户态即可,并且复制的时候用锁保护了一下。
从sys_newuname返回以后,继续执行system_call
源文件是这样的
EAX是系统调用实现函数的返回值(uname用它返回错误码),这里把EAX保存在ESP+0x18的位置。再回顾一下system_call的开头,SAVE_ALL是如何保存现场的
之后在system_call中ESP就没有变过,因此ESP+0x18正好是EAX保存的地方。这样就把之前保存的EAX用函数的返回值替换掉了,而等到最后RESTORE_ALL的时候就会自动地把返回值恢复到EAX中
接下来就该进入收尾工作了
syscall_exit_work里面有一个进程调度的机会
------------------------------------
可惜实验中似乎条件不满足,没有发生调度。而是直接恢复了全部的寄存器
最后清空了堆栈,并且用iret指令从中断返回
整个系统调用的简单流程图如下
3. 总结
系统调用的方法是通过0x80中断进入内核态,然后内核根据EAX中的系统调用号去调用对应的系统调用函数,最后将返回值通过EAX返回给用户态程序。
此外,系统调用是进程调度切换的时机,因为此时控制交给了内核。对于用户程序来说仅仅是调用了系统调用,然后从系统调用返回。在系统调用中发生的进程切换对于用户程序是透明的。
对于一般的中断处理函数,首先要保存现场,最后要恢复现场
4. API与中断调用的区别
其实在实验中,系统调用的方式是通过汇编调用0x80中断,类似于上周的uname_asm程序。因为在设置了正确的system_call入口地址以后,用系统调用的API依然无法触发断点。经过跟踪代码,终于发现了原因。
教程中也提到过,从Pentium II开始新加入了一个专用的指令,快速系统调用——SYSENTER。而API都非常优化,自然使用的是新指令。因此中断0x80的IRQ system_call自然就不会运行,所以断点没有生效。
实际上sysenter的入口也在entry_32.S里面,叫做ia32_sysenter_target,而且代码、流程都和system_call很类似,感兴趣的话可以阅读一下。
王岩
原创作品转载请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
Linux内核分析第五周作业