记 Lab cow: Copy-on-write fork 的 debug 过程
问题表现
进入 shell 后,直接按 Enter,发现内核 panic 了:
init: starting sh
$
too many args
usertrap(): unexpected scause 0x000000000000000c pid=2
sepc=0x0000000000002000 stval=0x0000000000002000
pte: 0x0000000021fd71d3
usertrap(): unexpected scause 0x000000000000000c pid=1
sepc=0x0000000000001000 stval=0x0000000000001000
pte: 0x0000000021fd9913
panic: init exiting
用 gdb 在 0x0000000000002000
处打断点,不知道为啥看不到源码(额,非法的 PC 值,gdb 找不到对应的源码属实正常),
只好看一下寄存器的值,发现有些奇怪的地方:
好多寄存器的值是 50
的重复,这正好是我们给分配的内存填充的垃圾数据:
memset((char*)r, 5, PGSIZE); // fill with junk
结合 sepc=0x0000000000002000 stval=0x0000000000002000
的值,
我感觉是用户程序的内存的部分区域没有正确加载?
代码段起码有一部分是正常的,因为上面 too many args
的输出即 sh
的输出(pid 为 2),
说明程序可以执行一部分。
问题解决
果然和我们之前填充的 junk
有关,太蠢了。
计数是否为 0 的判断应当在 memset
之前。
错误的顺序导致程序还在使用的 page 被 kfree
重置为垃圾数据,堆栈中的数据都被污染了,进而导致寄存器里有大量 05
。
之后又导致 pc 的值为一个无效的地址,触发 instruction page faults,sh
被 kernel kill 掉。
之后 init
进程想要重启 sh
进程,由于同样的问题也被 kernel kill 掉。
kernel kill 的时候发现是 init
进程,因此触发 panic。
相关代码:
void
kfree(void *pa)
{
struct run *r;
if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
panic("kfree");
// Fill with junk to catch dangling refs.
memset(pa, 1, PGSIZE);
r = (struct run*)pa;
uint64 idx = PA2IDX(pa);
acquire(&page_counter_lock);
page_counter[idx] --;
if (page_counter[idx] != 0) {
release(&page_counter_lock);
return;
}
release(&page_counter_lock);
acquire(&kmem.lock);
r->next = kmem.freelist;
kmem.freelist = r;
release(&kmem.lock);
}
后记
该问题整整耗费了我一天时间,最后实在忍不住了,只好找别人的 work 的代码,通过不断的替换找到的问题区域。
Links: 6.828-lab-cow-debug