Lab3 User environments
Fix Bug for lab2
在切换到lab3分支后,出现了lab2没有的bug1
2
3
4$ make qemu
....
kernel panic at kern/pmap.c:163: PADDR called with invalid kva 00000000
....
根据错误信息,不难找到出问题的代码在哪:1
kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P;
通过在该行前加入一些代码输出地址信息:
1 | kern_pgdir = (pde_t *) boot_alloc(PGSIZE); |
然后再运行下会得到:1
2kern_padir: f018f000
kern_padir: 00000000 // ????
十分迷惑,好像memset()
写错了位置。
一个合理的猜想就是kern_pgdir的地址包含到[kern_pgdir, kern_pgdir+PGSIZE)中去了,很可能的原因是“加载到内存”这个过程出了问题,load工作是由loader来做的,而其地址是linker提供的(如果你读过CSAPP的第7章,下面的相信你都很熟悉)
首先kern_pgdir是哪来的?是通过boot_alloc()
来的,我们来看下boot_alloc()
的代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34static void *
boot_alloc(uint32_t n)
{
static char *nextfree = NULL; // virtual address of next byte of free memory
char *result;
// Initialize nextfree if this is the first time.
// 'end' is a magic symbol automatically generated by the linker,
// which points to the end of the kernel's bss segment:
// the first virtual address that the linker did *not* assign
// to any kernel code or global variables.
if (!nextfree) {
extern char end[];
nextfree = ROUNDUP((char *) end, PGSIZE);
}
if (n == 0) {
return nextfree;
} else if (n > 0) {
result = nextfree;
nextfree = (char*)ROUNDUP((uint32_t)nextfree + n, PGSIZE);
//assert ( - n > KERNBASE);
return result;
}
// Allocate a chunk large enough to hold 'n' bytes, then update
// nextfree. Make sure nextfree is kept aligned
// to a multiple of PGSIZE.
//
// LAB 2: Your code here.
return NULL;
}
透过代码,最值得怀疑的是 end,因为第一个 boot_alloc()
获得的地址就是 end 通过ROUNDUP()
提升过来的,根据注释,它是一个magic symbol,由链接器生成,而JOS的链接器是有脚本的。
我们通过objdump
可以分析ELF文件,即熟悉的.o
之类的,
end 指向 .bss
section的尾部,于是我们看下其范围是:1
2
3
4
5
6
7
8
9
10$ objdump -h obj/kern/kernel
obj/kern/kernel: file format elf32-i386
Sections:
Idx Name Size VMA LMA File off Algn
...
9 .bss 00000f14 f018e100 0018e100 0008f100 2**5
CONTENTS, ALLOC, LOAD, DATA
...
范围为[0xf018e100, 0xf018f014),
实际上,end 并未如期指向 .bss
的尾部,但我们先来分析下kern_pgdir的符号信息
由于kern_pgdir是全局未初始化变量,所以应该在.bss section中,
我们可以通过objdump
检测:1
2$ objdump -t obj/kern/kernel | ag kern_pgdir
f018f00c g O .bss 00000004 kern_pgdir
问题就出来这,kern_pgdir的地址包含在了[f018f000, f019f000)中,于是memset()
就把它写没了
其实,按理说 end 是 .bss 的尾后地址,不应该被覆盖的,但实际上看来并不是,问题究竟出在哪呢?注意:这个 kernel
并不是 make
生成的,而是预先准备的,其 .ld
也是预先写好的。
我们还应该查看下 pmap.o
的symbol table,1
2$objdump -t obj/kern/pmap.o | ag kern_pgdir
00000004 O *COM* 00000004 kern_pgdir
你会发现这个与kernel的不一样,为什么呢?COMMON
section,如果读过CSAPP的话应该知道它与.bss
的区别,
section | meaning |
---|---|
COMMON | uninitialized global variables |
.bss | uninitialized static variables, and global or static variables that are initialized to zero |
不过注意这个通过objdump -h
是不会显示的,因为COMMON
只是pseudosection。
至于为什么会出现这种问题,我想可能是编译器版本问题,我用的是gcc 9.3,所以编译得到的有COMMON
,而kernel文件是更古老的编译器得到的。
最后,修复问题只要找到kernel.ld
在 .bss
和 end 之间加上 COMMON
就行了:1
2
3
4
5
6
7.bss : {
PROVIDE(edata = .);
*(.bss)
*(COMMON) /* new */
PROVIDE(end = .);
BYTE(0)
}
再次make qemu
会成功: