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

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输出,没想到故障表现发生了变化。

这一次,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的栈向下生长的天坑设计的原因