MIT6.828LAB1 (6)

LAB1_Part3 The Kernel


前言

记录一下自己的学习过程
实验内容翻译:
https://gitee.com/cherrydance/mit6.828
该翻译仅供参考

练习9

确定内核初始化其堆栈的位置,以及堆栈在内存中的确切位置。内核如何为其堆栈保留空间?堆栈指针在这个保留区域的哪个“端”初始化指向?

直接查看entry.S文件,发现在文件末尾存在对堆栈的初始化。其中movl $0x0,%ebpmovl $(bootstacktop),%esp两条指令设置栈指针。
在这里插入图片描述
使用gdb调试运行该点,打印寄存器esp的值
在这里插入图片描述
可以得到esp的值是0xf0110000,而在bootstack中可以看到其声明了KSTKSIZE,即8*4096=32KB大小的空间。而栈是由高地址向低地址延伸的。因此栈的地址为
0xf010 8000到0xf110 0000。

练习10

为了熟悉x86架构上的C调用约定,并了解test_backtrace函数的行为,您可以按照以下步骤进行操作:
找到obj/kern/kernel.asm文件中test_backtrace函数的地址,在该地址处设置一个断点,在内核启动后,每次调用test_backtrace函数时,观察发生了什么?对于每个递归嵌套级别的test_backtrace函数,注意观察它在堆栈上推入了多少个32位字(32-bit words),以及这些字是什么。
请注意,为了正确执行此练习,您应该使用工具页面上或Athena上提供的修补过的QEMU版本。否则,您将需要手动将断点和内存地址转换为线性地址。

查看test_backtrace的c语言代码:

void
test_backtrace(int x)
{
	cprintf("entering test_backtrace %d\n", x);
	if (x > 0)
		test_backtrace(x-1);
	else
		mon_backtrace(0, 0, 0);
	cprintf("leaving test_backtrace %d\n", x);
}

得知该函数是递归调用。接下来查看kernel.asm文件找到test_backtrace。其代码如下:
在这里插入图片描述
我发现我的代码与网上流传的不太一样,没办法只能自己分析。

f0100040:	f3 0f 1e fb          	endbr32 
f0100044:	55                   	push   %ebp
f0100045:	89 e5                	mov    %esp,%ebp
f0100047:	56                   	push   %esi
f0100048:	53                   	push   %ebx

这里是函数调用前的准备代码。在将上个函数的ebp入栈之后将esp的值赋值给ebp。所以这个ebp就是当前函数的栈底。然后入栈两个寄存器保存值。
在这里插入图片描述
此时ebp的值为0xf010ffd8。在保存两个寄存器之后esp = 0xf010ffd0。
在这里插入图片描述
打印得到结果确实是这样。

f0100057:	83 ec 08             	sub    $0x8,%esp
f010005a:	56                   	push   %esi
f010005b:	8d 83 58 08 ff ff    	lea    -0xf7a8(%ebx),%eax
f0100061:	50                   	push   %eax
f0100062:	e8 69 0a 00 00       	call   f0100ad0 <cprintf>
	if (x > 0)
f0100067:	83 c4 10             	add    $0x10,%esp

这一段代码执行完之后esp不变
在这里插入图片描述
在下一次调用前最后的代码如下

		test_backtrace(x-1);
f010006e:	83 ec 0c             	sub    $0xc,%esp
f0100071:	8d 46 ff             	lea    -0x1(%esi),%eax
f0100074:	50                   	push   %eax
f0100075:	e8 c6 ff ff ff       	call   f0100040 <test_backtrace>

指令sub $0xc,%esp是临时空间,再push %eax,esp减少16。
在这里插入图片描述
所以在下一次调用前栈里多了24byte的数据。但是考虑到ebp的数据和返回地址。一次调用应该是有32byte的数据压入。如图,下个ebp比上个ebp小0x20
在这里插入图片描述
其地址格式如下图所示:
在这里插入图片描述

练习11

请根据上述规定实现backtrace函数。使用与示例中相同的格式,否则评分脚本可能会混淆。当您认为函数已经正确运行时,运行make grade命令,查看输出是否符合我们的评分脚本所期望的格式,如果不符合,则进行修正。在提交了Lab 1的代码之后,您可以随意更改backtrace函数的输出格式。
如果您使用了read_ebp()函数,请注意,GCC可能会生成在mon_backtrace()函数的函数前导代码之前调用read_ebp()的"优化"代码,这会导致堆栈跟踪不完整(最近一次函数调用的堆栈帧缺失)。虽然我们已经尝试禁用导致这种重新排序的优化,但您可能需要检查mon_backtrace()的汇编代码,确保调用read_ebp()的位置在函数前导代码之后。

首先查看提示给出的read_ebp函数。

static inline uint32_t
read_ebp(void)
{
	uint32_t ebp;
	asm volatile("movl %%ebp,%0" : "=r" (ebp));
	return ebp;
}

它会返回ebp的值,所以我们可以用这个函数读取当前栈中ebp的值。根据练习10的图,我们可以得到下一个ebp = *(uint32_t *)ebp
eip是函数返回的指令地址,eip = *((uint32_t *)ebp+1)。同理,其他参数也是这样获取。
以一个循环来读取整个栈的值,那结束条件是啥呢?由前文我们可以得到,ebp的初始值为0,因此只要ebp不等于,那就继续循环读取栈帧。
最终代码如下:

int
mon_backtrace(int argc, char **argv, struct Trapframe *tf)
{
	// Your code here.
	cprintf("Stack backtrace:\n");
	uint32_t ebp = read_ebp();
	for(;ebp != 0; ebp = *(uint32_t *)ebp){
		cprintf(" ebp %08x eip %08x args %08x %08x %08x %08x %08x\n", ebp, *((uint32_t *)ebp + 1),
		*((uint32_t *)ebp + 2), *((uint32_t *)ebp + 3), *((uint32_t *)ebp + 4), *((uint32_t *)ebp + 5),
		*((uint32_t *)ebp + 6));
	}
	return 0;
}

运行结果如下:
在这里插入图片描述

练习12

要修改您的堆栈回溯函数,以显示每个eip对应的函数名、源文件名和行号。
通过插入对stab_binsearch的调用,完成debuginfo_eip函数的实现,以便根据地址查找行号。
在内核监视器中添加一个backtrace命令,并扩展您对mon_backtrace函数的实现,使其调用debuginfo_eip函数,并打印出每个堆栈帧的行,格式如下:
K> backtrace
Stack backtrace:
ebp f010ff78 eip f01008ae args 00000001 f010ff8c 00000000 f0110580 00000000
kern/monitor.c:143: monitor+106
ebp f010ffd8 eip f0100193 args 00000000 00001aac 00000660 00000000 00000000
kern/init.c:49: i386_init+59
ebp f010fff8 eip f010003d args 00000000 00000000 0000ffff 10cf9a00 0000ffff
kern/entry.S:70: <unknown>+0
K>
每行显示堆栈帧的eip所在的文件名和文件中的行号,然后是函数的名称和eip相对于函数开始位置的偏移量(例如,monitor+106表示返回eip距离monitor函数开头106字节)。

看了一遍建议的操作,没看出啥。考虑到说要使用stab_binsearch函数,所以直接看整个函数的参数和返回值。

static void
stab_binsearch(const struct Stab *stabs, int *region_left, int *region_right,
	       int type, uintptr_t addr)

文件提示详细说明了函数作用。

Stab结构体如图:

struct Stab {
	uint32_t n_strx;	// index into string table of name
	uint8_t n_type;         // type of symbol
	uint8_t n_other;        // misc info (usually empty)
	uint16_t n_desc;        // description field
	uintptr_t n_value;	// value of symbol
};

文件给出了代码实现的要求和提示,根据提示,该部分代码如下

	stab_binsearch(stabs, &lline, &rline, N_SLINE, addr);
	if(lline <= rline){
		info->eip_line = stabs[lline].n_desc;
		//这里有个坑,提示说要用rline,但一直过不去最后一个test
		//只能说离谱,最后改为lline救过了
	}else {
		info->eip_line = -1;
	}

要得出目标结果明显是要我们调用函数debuginfo_eip(uintptr_t addr, struct Eipdebuginfo *info),前面的addr我们知道就是eip,所以我们重点关注后面的结构体

// Debug information about a particular instruction pointer
struct Eipdebuginfo {
	const char *eip_file;		// Source code filename for EIP
	int eip_line;			// Source code linenumber for EIP

	const char *eip_fn_name;	// Name of function containing EIP
					//  - Note: not null terminated!
	int eip_fn_namelen;		// Length of function name
	uintptr_t eip_fn_addr;		// Address of start of function
	int eip_fn_narg;		// Number of function arguments
};

看到这就明白了,最后我们要打印的输出就是存储在这个结构体中,最终代码如下:

debuginfo_eip(eip, &info);
		cprintf("         %s:%d: %.*s+%d\n", info.eip_file,
		 info.eip_line, info.eip_fn_namelen, info.eip_fn_name,
		 eip-info.eip_fn_addr);

make grade结果如图:
在这里插入图片描述

总结

总算是完成了lab1,感觉头都秃了!!!!!
虽说是完成了,但感觉其中还有很多值得考虑的细节,只能算是勉强过了一遍。
后续lab继续。
有问题欢迎大家指出。

  • 26
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值