BSS段中end变量引发的惨案
问题的发现是本人在进行lab3实验时,有memset失败而进一步探索的结果,如果你的lab2成功pass,不代表不会存在以下问题,另外,与你的狗屎运也有点儿关系。
背景:lab3中的env.c文件已经填完了,发现boot_alloc存在问题,以下代码展示均在branch lab3下进行。
上案发现场:
// This simple physical memory allocator is used only while JOS is setting
// up its virtual memory system. page_alloc() is the real allocator.
//
// If n>0, allocates enough pages of contiguous physical memory to hold 'n'
// bytes. Doesn't initialize the memory. Returns a kernel virtual address.
//
// If n==0, returns the address of the next free page without allocating
// anything.
//
// If we're out of memory, boot_alloc should panic.
// This function may ONLY be used during initialization,
// before the page_free_list list has been set up.
static void *
boot_alloc(uint32_t n)
{
static char *nextfree; // 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);
}
// 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.
result = nextfree;
// be kept aligned to a multiple of PGSIZE
nextfree = ROUNDUP(nextfree + n, PGSIZE);
if ((unsigned)nextfree - KERNBASE > (npages * PGSIZE)) // npages * PGSIZE = totalmem
{ // since npages count from KERNBASE then when compare it should use KERNBASE
panic("out of memory in boot_alloc\n");
}
return 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);
}
首先分析它这一部分注释的问题,它说end变量是bss段中对后一个变量,如果它说的是正确的,那么我们可以使用end作为bss段的末尾,同时在roundup的作用下可以保证nextfree是绝对在bss段后面,而且是可以对4K取整的。
但是问题是,它说的“end是bss段的末尾”是不是正确的呢?那就瞅一瞅。
首先,查看下ELF中对各个段的定义
objdump -h obj/kern/kernel
上面是我的结果,先不用管具体的虚拟地址(我的是在lab3下,可能因人而异),看到bss段的虚拟地址是0xf0182100,段的大小为0xf14,那么计算下可知bss的范围为[0xf0182100, 0xf0183014) ,之后呢,我们使用gdb看一下,这个end的具体地址:
在mem_init中,第一次调用boot_alloc是在开辟页目录内存的位置,也就是下面的
kern_pgdir = (pde_t *) boot_alloc(PGSIZE);
我们使用gdb跳转到上面代码的位置,然后进入boot_alloc的体内,再调到if判断的地方,si进入,可以明显的看到,end的地址是0xf0183000。
这时候有人会觉得,哎,这不是很正常吗,end是一个数组,所以后面还有20个元素?那继续往后看:
通过查阅代码我们可以知道,kern_pgdir也是一个全局变量,而且也没有进行初始化,那么他一定是在bss段中的。
在第一次boot_alloc处作一下更改:
//
// create initial page directory. !!!!!!!!!!!!!
kern_pgdir = (pde_t *) boot_alloc(PGSIZE);
cprintf("%x\n", &kern_pgdir);
memset(kern_pgdir, 0, PGSIZE);
打印下信息
看结果:
没错,你没有看错,kern_pgdir所在的内存地址为0xf018300c。进一步验证下,使用以下的命令来查看全部表头信息:(这里输出到文件,好定位)
objdump -x obj/kern/kernel > ~/work/test
除了kern_pgdir外,还有好多变量都是在end之后的,比如panicstr、npages。
然后说一下,这样就会导致一个什么样的问题:
以我遇到的问题为例,我的end所在的地址为0xf0183000,因此我通过
nextfree = ROUNDUP((char *) end, PGSIZE);
获取到的result(也就是kern_pgdir)的值为0xf0183000(jtm离谱),也就是说kern_pgdir(0xf018300c)所指向的地址为0xf0183000,在之后的memset中,显然,会将期自身清空,导致,memset运行完后,kern_pgdir自身的值为0了,导致了后面一系列的错误。
进一步分析可知,原文中说的magic,其实一点儿都不magic,而是存在一定的问题,但是我觉得这个可能也跟编译器的编译顺序有关吧,就没有再深入的研究下,如果kern_pgdir等变量的存放地址在end之前的话就不会存在这个问题,或者说,如果你的end经过roundup之后,与nextfree之间有很大的gap,足以保存下其余的变量的话,也不会出现问题,当然这与你的运气有关。
明白了之后,直接上简单粗暴的解决方案:
// 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) + PGSIZE;
}
即nextfree在初始化的时候加上个PGSIZE,虽然这样不会根治这个问题,当end后面的变量继续边多,直到后面变量占用的内存达到了4K,那么这里还是会出错。但是就目前而言,是足够了吧。如果有更好的解决方案欢迎在评论取留言,希望我的经验能够对看到这里的你有一点帮助。
如有错误,欢迎指正。