Lab1

MIT 6.828 Lab1笔记


PART 1: PC Bootstrapr

这一部分主要是实验环境的配置,跟随教程操作即可

实模式下的地址转换

物理地址 = 段基址*16 + 偏移量


PART 2: Boot Loader

在6.828课程的系统中,boot loader包含两部分:一份汇编代码 boot/boot.s 和一份c代码 boot/main.c 。它们构成的boot loader主要有两个作用

1. 将处理器从实模式切换道32位保护模式。在实模式中,处理器只能访问1MB物理空间,而在32位保护模式下,处理器可以访问1MB以上的物理空间。切换模式后,逻辑地址向物理地址的映射方式也发生了变化,(段基址:偏移量)转化需要的offset从16变为32。

2. 借由x86的特殊I/O指令,直接从IDE硬盘中读取内核程序

Exercise 3 的四个问题

  • Q: 处理器是何时开始执行32位代码的?是什么机制使得处理器从16位转为32位?

  • A: 如下图所示, 汇编操作将cr0寄存器的最后一位置为1,通过Intel手册我们可以知道,当cr0寄存器的最后一位为1时,开启保护模式。

Lab1-1.png

  • Q: boot loader 执行的最后一条指令是什么?它加载的kernel执行的第一条指令是什么?

  • A: 如下图所示,这是boot loader执行的最后一条指令。这条指令转到了kernel程序的入口。

Lab1-2.png
  • kernel执行的第一条指令如下图所示。
    Lab1-3.png


  • Q: kernel的第一条指令在哪里?

  • A: 根据上一张图,我们可以看到kernel第一条指令的地址在于 0x10000c


  • Q: boot loader在从硬盘读取内核时,它是怎样决定读取几个扇区的?它是怎样得知这个信息

  • A: bootmain函数如下图所示,程序首先读取足够大的数据(程序中是SECTSIZE*8),判断要读取的数据是不是一个ELF数据,如果是,则读取程序头,从表征程序头的结构体里面的e_phnum成员得知要读取的数据数量。

Lab1-4.png

Exercise 5

linker的地址改变,从结果上来看,kernel无法被加载进内存,现象如下图所示

Lab1-5.png

我在这里将链接地址从0x7c00改成了0x7cd0,用gdb跟踪程序,会发现boot loader的起始地址变为了我们改成的0x7cd0,如下图所示

Lab1-6.png

将链接地址修改成不同的值会出现不同的问题,但最终都会导致程序循环,无法正确加载kernel程序

Exercise 6

将断点打在boot loader的起始位置,观察地址0x100000,可以看出这一地址的数据全部为0。当程序进入kernel后,发现指定的地址位置出现了数据。原因可能是boot loader 将kernel加载进了内存,所以相应位置的数据出现了变化;

Exercise 7

在执行指令 movl %eax, %cr0 之前, 两个地址对应的数值如下图所示地址空间

Lab1-8.png

在执行指令过后, 两个地址对应的数值如下图所示地址空间

Lab1-9.png

执行指令前两个地址空间对应的数据不同,而执行指令后两个地址空间对应的数据相同,说明开启分页机制后这两个虚拟地址被映射成了同一个物理地址。


注释掉这条指令重新编译,从结果上来看,硬件启动失败,直接退出

gdb跟踪发现是一条jmp指令出错了,因为跳转过后的地址是一个无效地址。


PART 3: The Kernel

kern/printf.c, lib/printfmt.c 和 kern/console.c三个文件的关系

kern/console.c 最底层,用了大量汇编语言编写,主要是适配于硬件的屏幕输出;

kern/printf.c 使用了console.c中的底层函数;

lib/printfmt.c 的函数最上层,封装了内核中的函数,可以供上层使用。

Exercise 8

需要修改的代码在lib/printfmt.c中,改变后的代码为

case 'o':
// Replace this with your code.
     /*
     putch('X', putdat);
     putch('X', putdat);
     putch('X', putdat);
     break;
     */
     num = getuint(&ap, lflag);
     base = 8;
     goto number;

模仿上方的代码即可写出,下面是问题的回答

1. console.c 导出了 cputchar 函数供 printf.c 使用,cputchar封装在了 putch 函数中。

2. 下面代码处理的情况是:屏幕上已经充满了字符,若现在的操作需要向屏幕再次输入字符,则需要进行的动作是:1.所有的字符向上移动一行 2.最后一行清空。这段代码实现的就是这个功能。

if (crt_pos >= CRT_SIZE) {//*如果目前屏幕上的字符数超过了屏幕的容量
    int i;
    memmove(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t));//* 将从第二行开始的内容移动到第一行去
    for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i++)
        crt_buf[i] = 0x0700 | ' ';//*最后一行的内容清零,由上文的内容可知,对0x700进行或操作与显示的色彩有关
    crt_pos -= CRT_COLS;
}

3. 我将文中提及到的代码段加在了 kern/init.c 中,因为前方有明显的屏幕打印值,所以可以很方便地找到要追踪地语句地位置。把断点打在 cpirntf 上。

  • 在 cprintf 函数内,fmt指向的是字符串 "x %d, y %x, z %d\n" 的地址。函数原型中没有ap,所以这里ap也打印不出来,如下图所示

Lab1-10.png
  • 在 vcprintf 函数内,fmt指向的是字符串 "x %d, y %x, z %d\n" 的地址。ap指向一个地址,这个地址分别放有 x, y, z 的值,如下图所示

Lab1-11.png
  • cons_putc 中,变量是一个char型

  • va_arg 是一个宏定义,gdb无法在之上直接打断点。进入vprintfmt逐步跟踪后,可以看到每次va_arg后,ap都是指向下一个变量的地址

Lab1-12.png

4. 输出变成了 He110 World 。57616化成16进制是e110, 72,6c,64分别是rld的ascii码,如果在大端序的cpu上运行,则 Wo后面不会显示字符。

5. 虽然输入y没有对应的值,但还是会打印栈上的内容,在信心安全领域这种叫做格式化字符串漏洞,用于泄露栈上信息。

6. 函数原型声明为 cprintf(const char *fmt, va_list ap);

Exercise 11 12

要通过寄存器追溯函数调用栈,首先要弄懂一些细节:

1.pop 和 push 指令的操作顺序

  • push: 先减小esp寄存器里的值,再将要压入的数据写入到esp寄存器指向的地址;

  • pop: 先读出esp寄存器指向地址的值,再增加esp寄存器的值

  • 读写与寄存器值改变, 这两个顺序不要弄错了

2.即将进行函数调用时,进行的压栈操作

  • 首先按照参数的声明顺序,将参数压入栈中(关于压栈的顺序,可能根据编译器有所不同;32位操作系统下函数传参需要通过压栈,64位cpu寄存器资源丰富,传参直接用寄存器)。

  • 然后压入返回地址eip,传参和压入eip都是在call函数之前实现的

  • 最后将esp地址传递给ebp,并将ebp的值压入栈中。注意,这一部是在被调函数中实现的。

3.栈的生长方向,从高地址向低地址生长


关于在调用时显示符号信息,这个实验中的要求比较简单,已经写好了 stab_binsearch 函数,只需要利用即可,相应的代码中也有提示。

有一点需要注意的是,stab结构体中有很多成员,哪一项显示的是eip地址的行号呢,通过验证,我们可以得知是 n_value 这个变量,如果使用了其他变量,make grade 也能通过,不过返回地址是不正确的。具体看程序吧

kern/kdebug.c中代码如下图所示

Lab1-13.png

kern/monitor.c中代码如下图所示

Lab1-14.png

make qemu 的结果如下图所示

Lab1-15.png

通过查看 obj/kernel.asm ,我们判断 test_backtrace 的返回地址应该是基地址+0x28, 即十进制的40, 与实验结果相符。

make grade, 实验通过!!

Lab1-16.png

你可能感兴趣的:(Lab1)