记 Lab cow: Copy-on-write fork 的 debug 过程

标签: 问题解决 debug C 发布于:2023-01-31 20:43:28 编辑于:2023-02-04 17:28:15 浏览量:1072

问题表现

进入 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 找不到对应的源码属实正常), 只好看一下寄存器的值,发现有些奇怪的地方:

image.png

好多寄存器的值是 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 的代码,通过不断的替换找到的问题区域。

未经允许,禁止转载,本文源站链接:https://iamazing.cn/