# Mit6.828 Lab1: Part3 Jos Bootstrap -- the kernel

## The kernel

Operating system kernels often like to be linked and run at very high virtual address, such as 0xf0100000, in order to leave the lower part of the processor’s virtual address space for user programs to use.

pte_t是页表项类型，NPTENTRIES表示页表项的数目，entry_pgtable就是一个页表映射到物理页，接下来定义页目录entry_pgdir，页目录的索引由虚拟地址右移22位得到，即虚拟地址的高10位。因此只初始化了[0, 4MB)以及[KERNBASE, KERNBASE+4MB)虚拟地址空间的表项。这两段虚拟地址空间指向同一页表，起始物理地址在(uintptr_t)entry_pgtable - KERNBASE。所以[0, 4MB)中的虚拟地址与[KERNBASE, KERNBASE+4MB)中的虚拟地址访问的是同一物理页，但是[0, 4MB)中的虚拟地址访问的物理内存是不可写的，KERNBASE, KERNBASE+4MB)中的虚拟地址访问的物理内存可写。在entry.S中有几条指令的虚拟地址在[0, 4MB)虚拟地址空间内，这个虚拟地址空间的这些指令执行完之后，再也不会使用[0, 4MB)虚拟地址空间。

## 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.

## 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.

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?
kern/printf.c中的putch函数调用了console.c中的cputchar函数。cputchar(int c)将整数c输出到控制台。
2. Explain the following from console.c:

1. 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:
• In the call to cprintf(), to what does fmt point? To what does ap point?
fmt指向格式化字符串,ap是va_list类型，指向参数列表未确定的部分。这个变量通过调用va_start来初始化，第一个参数是va_list变量的名字，第二个参数是省略号前最后一个有名字的参数。
va_arg宏接收两个参数，va_list变量和参数列表中下一个参数的类型。当访问完毕最后一个可变参数之后，需要调用va_end
• 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.
• vcprintf(“x %d, y %x, z %d\n”，ap)
• cons_putc(‘x’)
• va_arg // ap调用之前指向x,y,z 3个整数，调用之后指向y,z两个整数
1. Run the following code.

What is the output? Explain how this output is arrived at in the step-by-step manner of the previous exercise.
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?

57616对应的16进制是e110，如果格式符是%X,则会输出E110。多字节对象的存储中，最低有效字节存储在前面称为小端机，反之最高有效字节存储在前的称为大端机；Intel i386机器是小端机。0x72对应的ASCII码是’r’，0x6c对应的ASCII码是’l’，0x64对应的ASCII码是’d’。所以会输出“He110 World”。

1. In the following code, what is going to be printed after ‘y=’? (note: the answer is not a specific value.) Why does this happen?

lflag就是表示lld中有几个l，从而确定下个数字的类型是long long或者long或者int。所以cprintf("x=%d y=%d", 3);在正确输出x=3后解析到’y=%d时进入getint函数，执行return va_arg(ap, int);，此时\ap中已经为空了，所有会输出一个未定义的值。

1. Let’s say that GCC changed its calling convention so that it pushed arguments on the stack in declaration order, so that the last argument is pushed last. How would you have to change cprintf or its interface so that it would still be possible to pass it a variable number of arguments?

## The Stack

In file kern/monitor.c, kernel monitor function that prints a backtrace of the stack: a list of the saved Instruction Pointer (IP) values from the nested call instructions that led to the current point of execution.

x86栈指针(%esp)是往低地址发展的，在32bit模式下，栈只能保存32bit的数值，所以%esp一直被4整除。基址指针寄存器(%ebp)将一直是栈指针在函数开始的位置，所以可以说是对栈指针的常量引用。

### 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?

### Exercise 10.

To become familiar with the C calling conventions on the x86, find the address of the test_backtrace function in obj/kern/kernel.asm, set a breakpoint there, and examine what happens each time it gets called after the kernel starts. How many 32-bit words does each recursive nesting level of test_backtrace push on the stack, and what are those words?

### Exercise 11.

Implement the backtrace function as specified above. Use the same format as in the example, since otherwise the grading script will be confused.If you use read_ebp(), note that GCC may generate “optimized” code that calls read_ebp() before mon_backtrace()’s function prologue, which results in an incomplete stack trace (the stack frame of the most recent function call is missing). While we have tried to disable optimizations that cause this reordering, you may want to examine the assembly of mon_backtrace() and make sure the call to read_ebp() is happening after the function prologue.

[ ]来表示其对应的内容为可选项，基本语法规则由5部分组成：

• 关键字asm和volatile,asm为gcc关键字，表示接下来要嵌入汇编代码。为避免keyword asm与程序中其它部分产生命名冲突，gcc还支持asm关键字，与asm的作用等价, volatile为可选关键字，表示不需要gcc对下面的汇编代码做任何优化。
• assembler template是要嵌入的汇编命令。
• output operands,该字段为可选项，用以指明输出操作数。
• input operands, 该字段为可选项，用以指明输入操作数。
• list of clobbered registers,该字段为可选项，用于列出指令中涉及到的且没出现在output operands字段及input operands字段的那些寄存器。若寄存器被列入clobber-list，则等于是告诉gcc，这些寄存器可能会被内联汇编命令改写。因此，执行内联汇编的过程中，这些寄存器就不会被gcc分配给其它进程或命令使用。.

asm volatile("movl %%ebp,%0" : "=r" (ebp));的作用是将%bp的值返回给%0所引用的C语言变量ebp，根据”=r”约束可知具体的操作流程为：先将%eax值复制给任一通用寄存器，最终由该寄存器将值写入%0所代表的变量中。”r”约束指明gcc可以先将%ebp值存入任一可用的寄存器，然后由该寄存器负责更新内存变量。read_ebp就是将当前%ebp寄存器的内容以uint32_t格式返回。

### Exercise 12.

Modify your stack backtrace function to display, for each eip, the function name, source file name, and line number corresponding to that eip.

In debuginfoeip, where do __STAB* come from? This question has a long answer; to help you to discover the answer, here are some things you might want to do:

• look in the file kern/kernel.ld for _STAB*
• run i386-jos-elf-objdump -h obj/kern/kernel
• run i386-jos-elf-objdump -G obj/kern/kernel
• run i386-jos-elf-gcc -pipe -nostdinc -O2 -fno-builtin -I. -MD -Wall -Wno-format -DJOS_KERNEL -gstabs -c -S kern/init.c, and look at init.s.

Complete the implementation of debuginfo_eip by inserting the call to stab_binsearch to find the line number for an address.Add a backtrace command to the kernel monitor, and extend your implementation of mon_backtrace to call debuginfo_eip and print a line for each stack frame of the form:

Each line gives the file name and line within that file of the stack frame’s eip, followed by the name of the function and the offset of the eip from the first instruction of the function (e.g., monitor+106 means the return eip is 106 bytes past the beginning of monitor).

Be sure to print the file and function names on a separate line, to avoid confusing the grading script.

Tip: printf format strings provide an easy, albeit obscure, way to print non-null-terminated strings like those in STABS tables. printf(“%.*s”, length, string) prints at most length characters of string. Take a look at the printf man page to find out why this works.

kern/kdebug.h文件中查看指令指针的信息：