欢迎光临散文网 会员登陆 & 注册

记一次修bug的艰险历程(Mosix4a)

2023-07-13 22:48 作者:xthoa  | 我要投稿

1. 背景介绍

我个人编写的操作系统项目Mosix 4a,在前一代Mosix 3d的基础上,修改了项目结构,变更了进程管理等的主要逻辑。

系统属于混合内核,采用kernel+archive的方式,其中kernel部分包含进程管理、内存管理、虚拟文件系统等功能,archive部分包含设备驱动(包括pty)、动态库和一个简单的shell(sh0.exe),都是PE格式程序。在kernel部分初始化完成后,会调用archive中的init.exe,由它加载设备驱动和shell。

2. 场景复现

在bochs虚拟机中调试系统,调试模式(bochsdbg),单核,软盘启动。

启动shell,用它运行三个其他的程序,第三个程序启动时崩溃,立即triple-fault重启。

看起来像是页表没了

根据log中最后的rip值,得知崩溃时代码处于进程切换时,确切的说是页表切换时。说明切换后的进程的cr3值有误,或页表结构出现问题。

在进程切换函数里面下了条件断点,看到了出问题的数据。发现cr3值正确,而是页表的末尾出现问题:自引用和内核pml4e值均有误。然后发现这值处于kernel代码段,查了数据发现它和内核栈kstack在同一个4K页。rsp值也在此范围内,证实了这一点。

便得出:kstack(内核栈)和pml4t(页表)出现在同一个页。于是在分配vmspace(即分配页表)和分配栈的函数加入debug输出,没想到故障表现发生了变化。

和原先不一样的表现:这次是#GP

这一次,shell启动时就发生了#GP,是write调用中file指针为non-canonical地址。file指针是从HandleTable(类似于fd表)中取出来的。看了一圈,发现这次是handletable和vmspace重合了。(vmspace是记录进程虚拟地址空间使用情况的数据结构)

然后忽然发现,init.exe的alloc_stack和do_reap两者显示的值不一样。do_reap显示的值是将要被释放的内核栈的地址,那他就应该和前面alloc_stack显示的分配的内核栈地址相同。

赶快查看alloc_stack的代码,终于找到问题:x86架构的栈向下生长,rsp初始值应该是分配的页的最高地址。可是代码里直接给了页的起始地址(最低地址)。

修改了这一处代码后,所有程序运行果然正常,不再崩溃。

这段代码伪造进程被切换走时的栈帧,这样进程被“切换回来”时,就从我们指定的函数开始运行。

上图中第101行便是问题的根源。原先代码没有top,而直接把base赋给rsp,这就导致栈的内容实际上处于它前面的一个页。而释放时,通过rsp所在页判断要释放的页,结果判断为前一个页,于是出现了重复释放,引发了后续进程的问题。

3. 结论

只因rsp指针赋了栈页基地址,而非栈顶地址,导致栈错位,并使得栈前面的一个页面重复释放,引发各种花式崩溃。

这绝对不是我的问题,是x86的栈向下生长的天坑设计的原因

记一次修bug的艰险历程(Mosix4a)的评论 (共 条)

分享到微博请遵守国家法律