JOS lab1

Lab 1: Booting a PC

PC Bootstrap

Exercise 1.

image

练习1:熟悉x86汇编语言,和原来学的汇编语言差不多,相近,这一块还好。

Exercise2

image

这一步需要分析的比较多,需要通过si单步分析jos的ROM区和BIOS区工作量比较大,暂时留坑。

Part 2: The Boot Loader

    此小结讲述的硬盘启动时,将512个字节的引导扇区加载到物理地址0x7c00到0x7dff的内存中,再使用jmp指令将CS:IP设置为0000:7c00,将控制权传递给引导装载机,因此要求加载程序必须适合512个字节,引导程序有汇编语言文件boot/boot.s和一个C源文件boot/main.c构成。
     加载程序必须实现两个功能:1.引导加载程序将处理器从实模式切换到32位保护模式,(仅有此模式,软件才能发文处理器地址空间中的1MB以上的所有内存,)。
2.引导程序通过x86特殊的IO指令直接访问IDE磁盘设备,从硬盘中读取内核。

Exercise 3

image

    在jos的文件夹内打开终端,执行make qemu-gdb 打开另一个终端 执行make gdb 在将断点设置在0x7c00(引导扇区的位置),在0x7c00后面通过si单步查看指令。
Be able to answer the following questions:

  • At what point does the processor start executing 32-bit code? What exactly causes the switch from 16- to 32-bit mode?
    答:在boot/boot.S文件中第10行,有以下代码
.set CR0_PE_ON,      0x1         # protected mode enable flag

由注释明显知这是保护模式的标示。再继续寻找可发现在boot.S的第51行起有以下的代码

  orl     $CR0_PE_ON, %eax
  movl    %eax, %cr0

image

当运行到这一行时即发现和boot.s中的内容一致,因此,由实模式转换为保护模式是在0:7c26开始的。
image

由上图可知,第一次运行32位代码是在转换完成32位保护模式后开始的,因此开始运行32位代码是在0:7c2d开始的。

  • What is the last instruction of the boot loader executed, and what is the first instruction of the kernel it just loaded?
    image

    在bootmain(main.c函数中的)函数中可以看到,如果正常执行最后一句语句为
    ((void (*)(void)) (ELFHDR->e_entry))();
    
    因为实际运行时在qemu中只是了解语句还是不行的。 因此还需要在编译后的文件中寻找此命令的指令,在obj/boot/booy.asm中得知
    image

    在第393行最后一行代码为
    7d61:   ff 15 18 00 01 00       call   *0x10018
    
    因为引导系统负责加载内核,因此内核的第一条代码就是引导的下一行的代码,因此吓一跳命令就是,在0x10018中寻找。
    设置断点
    image

    因此kernel的下一条指令就是0x10000c中的指令
    image
=> 0x10000c:    movw   $0x1234,0x472

  • Where is the first instruction of the kernel?
    由上述的第2问可知,第一步是0x0010000c所对应的指令。在网上找到了一周更加好的方法,用哪个objdump命令去查看相对应的情况(在obj/kern目录下查看)


    image
  • How does the boot loader decide how many sectors it must read in order to fetch the entire kernel from disk? Where does it find this information?
    这一个算是比较难的,首先寻找boot loader 的位置(相对比较好找,因为boot loader 就是boot启动程序,因此,对于此文件就应该在boot 文件夹中)boot 文件夹的分布比较明显,在boot文件夹中最重要的就是main.c文件。

    image

    由题意可知,ELF文件文件头即ELFHDR由此可知道ELF文件引导了多大的
    在文件目录下执行下面的命令

    objdump -p kernel
    

    image

    因此,在main函数中指定了寻找的区域和读取的扇区的的数量。

exercise4

image

此题目即查看pointer.c 的文件,认真的看了一下,确实很有用处。

如果 c的指针执行a[4]数组
那么令3[c]=200 //此时3c相当于c[3]
则 a[3]=200//因为c[3]和a[3]的位置指向相同,因此a[3]=200

Exercise 5

题目

Exercise 5. Trace through the first few instructions of the boot loader again and identify the first instruction that would "break" or otherwise do the wrong thing if you were to get the boot loader's link address wrong. Then change the link address in boot/Makefrag to something wrong, run make clean, recompile the lab with make, and trace into the boot loader again to see what happens. Don't forget to change the link address back and make clean again afterward!

第一步 改为错误的开始地址

image

在开始地址改为

Ttext 0x7c20 

第二步,删除原来的编译好的文件,进行重新编译

make clean
make

image

再次单步执行,发现在执行到0x7c00时,并没有报错,但是,当执行值如图所示的时候,程序出错且无法继续向前执行。发现后者距离正确的数值正好大于20个单位,正好是修改的正常的偏差的数值,因此再加上理论基础,确定程序是从0x7c00开始加载,即BIOS将boot loader 固定在0x7c00的位置。

查看入口位置

image

Exercise 6

题目

Exercise 6. We can examine memory using GDB's x command. The GDB manual has full details, but for now, it is enough to know that the command x/Nx ADDR prints N words of memory at ADDR. (Note that both 'x's in the command are lowercase.) Warning: The size of a word is not a universal standard. In GNU assembly, a word is two bytes (the 'w' in xorw, which stands for word, means 2 bytes).
Reset the machine (exit QEMU/GDB and start them again). Examine the 8 words of memory at 0x00100000 at the point the BIOS enters the boot loader, and then again at the point the boot loader enters the kernel. Why are they different? What is there at the second breakpoint? (You do not really need to use QEMU to answer this question. Just think.)

第一次

在0x7c00 设置一个断点,在0x10000出设置一个断点 然后在0x10000处使用如下的命令x/8x 0x10000
出现如下的结果

(gdb) x/8x 0x10000
0x10000:    0x464c457f  0x00010101  0x00000000  0x00000000
0x10010:    0x00030002  0x00000001  0x0010000c  0x00000034
image

第二次

在0x7c00处设置断点,直接检测0x10000出的代码

(gdb) x/8x 10000
0x2710: 0x00000000  0x00000000  0x00000000  0x00000000
0x2720: 0x00000000  0x00000000  0x00000000  0x00000000

image

由此可再次确认0x7c00出开始的引导代码是将启动代码引导到0x10000出使用。
若正常执行引导代码 ,第二个断点有程序的main代码 ,若非正常执行,则不含有任何代码,这也说明了,计算机会自动清空0x10000出的空间。

Exercise 7

题目

Exercise 7. Use QEMU and GDB to trace into the JOS kernel and stop at the movl %eax, %cr0. Examine memory at 0x00100000 and at 0xf0100000. Now, single step over that instruction using the stepi GDB command. Again, examine memory at 0x00100000 and at 0xf0100000. Make sure you understand what just happened.
What is the first instruction after the new mapping is established that would fail to work properly if the mapping weren't in place? Comment out the movl %eax, %cr0 in kern/entry.S, trace into it, and see if you were right.

正常执行代码

一开始做这个练习好没有头绪啊,映射地址,虚拟地址,太多的概念。
后来发现,只是在高位的地址映射到低位的物理地址上,好像也不是那么的难。

首先找到代码相对应的位置,这个位置在/obj/kern/kernel.asm中的

image

由此可见,代码在f0100025位置处,因此在0x100025位置设置断点,
image

在此处查看题目中的0x100000和高位置0xf0100000所储存的数值
image

再运行0x100025出的指令,执行si命令并执行下一条指令。紧接着查看上述两个位置的代码情况:
image

此时发现高位代码和低位(物理位置)的代码一致,再次验证了(说明了)题目中描述的,在执行相应的代码(在0x100025后执行si命令)后将低位代码(0x10000)虚拟到高位代码处(0xf010000)。 即启动了页表,完成了相对应的映射。

对可执行的代码进行处理

     根据题意,将相对应的代码进行注释。(Comment out the movl %eax, %cr0 in kern/entry.S)

image

再次编译运行,进行测试
image

由此可见没有开启分页,是不可以继续执行的 ,第一条出现错误的命令就是movl $0x0,%ebp 指令。

Exercise 8

题目

Exercise 8. We have omitted a small fragment of code - the code necessary to print octal numbers using patterns of the form "%o". Find and fill in this code fragment.

image

要修改的地方的,代码都写了注释,比较好找,下面是代码进行替换。

image

其实上面的代码都已经写好了,只需要自己稍微按照上面的事情修改一下就可以了。

问题1

Explain the interface between printf.c and console.c. Specifically, what function does console.c export? How is this function used by printf.c?
答:由代码可知console.c负责的是写出cputchar和getchar等函数供printf.c中的printf函数调用,而printf中的函数是负责分种类解析各种各样的字符进行输出。

问题2

Explain the following from console.c:

      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 | ' ';
              crt_pos -= CRT_COLS;
      }

由代码可知,其实现的功能是实现换行,首先进项判断crt_pos是大于CRT_SIZE,如果大约,这需要换行,换行是需要准备的是,将当前行已经上面的信息进行循环上移,再将光标进行移动到屏幕最左端进行输入操作。

问题3

For the following questions you might wish to consult the notes for Lecture 2. These notes cover GCC's calling convention on the x86.
Trace the execution of the following code step-by-step:
int x = 1, y = 3, z = 4;
cprintf("x %d, y %x, z %d\n", x, y, z);
In the call to cprintf(), to what does fmt point? To what does ap point?
List (in order of execution) each call to cons_putc, va_arg, and vcprintf. For cons_putc, list its argument as well. For va_arg, list what ap points to before and after the call. For vcprintf list the values of its two arguments.

答:
在cprintf()中,fmt指向的是字符串,在上述的例子中指的是cprintf中的前半部分即是"x %d,y %x,z%d",而ap指的是参数的第一个,在上述的函数中国即是指的是x。
由调试中可以发现,每次ap向后移动的都是下一个所需要的类型的位置。

问题4

Run the following code.
unsigned int i = 0x00646c72;
cprintf("H%x Wo%s", 57616, &i);
What is the output? Explain how this output is arrived at in the step-by-step manner of the previous exercise. Here's an ASCII table that maps bytes to characters.
The output depends on that fact that the x86 is little-endian. If the x86 were instead big-endian what would you set i to in order to yield the same output? Would you need to change 57616 to a different value?
Here's a description of little- and big-endian and a more whimsical description.

image

此处(kern处)的代码正好对应一开机的时候屏幕打印出来的代码,因此,在此处添加题目中的代码再好不过了。

void
monitor(struct Trapframe *tf)
{
    char *buf;
    unsigned int i=0x00646c72;
    cprintf("Welcome to the JOS kernel monitor!\n");
    cprintf("Type 'help' for a list of commands.\n");
    cprintf("H%x Wo%s",57616,&i);

    while (1) {
        buf = readline("K> ");
        if (buf != NULL)
            if (runcmd(buf, tf) < 0)
                break;
    }
}

image

由上图可知,输出的结果为He110 World &i对应的byte序列从低位开始计算的72 6c 64 00真好是字符串rld 所以最终的输出结果是He110 World
如果变更高字节的,则只需要将i对应的值即0x00646c72 改为0x726c6400即可,不需要对57616,进行修改,因为此处的值(57616)是直接的数值,不是地址的值,因此不需要对57616进行修改。

问题5

image

多次刷新,y的结果确实没有发生变化,再此印证了上面所说的事情,而为什么没有产生变化呢,就是因为每次打印出的变量的值都是根据第三题中的va_arg从ap指针不断的向后取值,得到此题目中的y的值,这是在每一个电脑上应该是不同的值,或者说,这电脑的不同的时候启动应该也不尽相同,但是在当下的一段时间内这个数值是固定的,因此,这个是存在且一段时间是固定的。

问题6

在我问题3中的有具体的实现方式,这问题3中的问题,在栈中栈是由高地址向低地址增长,因此在此问题上栈的顺序是从做向右做压栈,,因此如果对于问题中来说,压栈的顺序正好相反,因此需要对va_arg进行重新写,使得原来加上的地址,要减去。

颜色的问题

image

思考:题目的意思是在打印文件基础上,为文字配上相对应的颜色。 这个问题在汇编语言中也有涉及,在汇编中更改颜色是在字母的高八位,在这个过程中我认为应该也有相应的指令去接觉这个问题。
2.对于此问题,首先寻找的是,打印颜色的相应的函数的位置。,追溯到相应的功能发现打印字母的函数是一环套一环的,从cprintf到vcprintf再到vprintfmt再到putch再到cputchar再到cons_putc再到cga_putc. 在kern中的console.c也写的比较的明白。

image

在如下的函数中,如同汇编中一样,打印的高八位是颜色,低八位为实际的数值。
先对函数cga_putc函数进行修改
image

添加\033[0;32;40m 如图即可。 效果如图。
image

堆栈

Exercise 9

Exercise 9. Determine where the kernel initializes its stack, and exactly where in memory its stack is located. How does the kernel reserve space for its stack? And at which "end" of this reserved area is the stack pointer initialized to point to?

答:在代码中找了一会的题目中所说的ebp 发现在kern/entry.S中有关于ebp的代码。

image

由此注释可知#Clear the frame pointer register (EBP),此代码的作用是,将寄存器EBP初始化为0,%esp初始化为bootstacktop.
因此必须去bootstacktop去寻找该信息。

Exercise 10

image

在obj/kern/kern.asm 中有好多的test_backtrace

image

如图所示,在这里有好多的回溯函数,首先对%ebp进行研究,随后%ebp减去0x14,最后调用时call f0100967 cprintf 函数 是的%ebp 最后入栈,所以共有 8+20+4=21个字节,,从上述的代码值,cprintf函数进行循环递减调用。

Exercise11

image

首先来看这是一道什么问题,经过分析这问题是挂关于回溯函数的问题,思考这个问题的时候,就要想,这串代码在那里,

image

在上述的文件monitor.c中的使用mon_backtrace完成是对文件0-5的回溯。
代码如下:

image

评分结果如下:

image

可以判定回溯没有出现问题,此种解决方案是正确的。

Exercise12

题目

image

分析

这个问题是为操作程序, 这个及时添加一个命令行,backtack 函数,这个函数是debuginfo_eip 这个是打印栈区的每一行代码。

解答

第一步

在kern/kdebug.c的第183行添加如下的代码进行二分分析查找stab表确定函数的行号函数。

stab_binsearch(stabs, &lline, &rline, N_SLINE, addr);
    if (lline <= rline) {
        info->eip_line = stabs[lline].n_desc;
    } else {
        return -1;
    }

第二步 添加backtrace命令行

static struct Command commands[] = {
    { "help", "Display this list of commands", mon_help },
    { "kerninfo", "Display information about the kernel", mon_kerninfo },
{ "backtrace", "Display backtrace info", mon_backtrace },
};

第三步 在kern/monitor.c添加函数

修改monitor.c 中的mon_backtrace函数修改判断语句如下图

image

lab1 程序验收
image

看来通过了,实验完成了。收获非常的丰富而且也学会了很多的相关的知识,比如指针的用法,汇编语言不同的写法,等等很多的东西。

你可能感兴趣的:(JOS lab1)