###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
lib/printfmt.c:void vprintfmt(void (*putch)(int, void*), void *putdat, const char *fmt, va_list ap)
Be able to answer the following questions:
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打印出字符的流程如下:
- 用户调用
1
kern:printf.c int cprintf(const char *fmt, ...)
会提取出可变参数列表, 并且传递给1
cprintf
1
kern:printf.c int vcprintf(const char *fmt, va_list ap)
之后会调用1
vcprintf
1
lib/printfmt.c void vprintfmt(void (*putch)(int, void*), void *putdat, const char *fmt, va_list ap)
会使用vcprintf传递的参数1
vprintfmt
函数来打印字符, 另外主要的逻辑(扫描字符串)也是在1
putch
完成.1
vprintfmt
也会将成功打印的字符数通过参数1
vprintfmt
返回给他的调用者1
putdat
,1
vcprintf
也会返回成功打印的字符数给1
vcprintf
的调用者.1
cprintf
则是负责打印单个字符的函数.1
putch
Explain the following from console.c:
有宏
, 1
#define CRT_SIZE (CRT_ROWS * CRT_COLS)
为一屏幕最大显示的字符数, 可以看出, 该代码的左右就是当显示满一屏幕之后, 换屏幕, 并在新的屏幕继续输出字符.1
CRT_SIZE
- 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?
That’s Too Easy.
-
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.
上文已经有说.
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. 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?
输出为
1
He110 World
- e110好理解, 是57616的 hex 表示. 那么 rld 是从哪儿来的呢?
- x86小端(little-endian)模式下, 高位在低地址存放. 假设局部变量i在栈中的地址为[addr, addr + 3]
- 0x00(最高位)的地址为addr + 3, 0x72(最低位)的地址则是 addr + 0
- 所以
则打印的是rld’\0’1
printf("%s", &i);
-
In the following code, what is going to be printed after ‘y=’? (note: the answer is not a specific value.) Why does this happen?
1
cprintf("x=%d y=%d", 3);
Too Easy.
-
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 the final exercise of this lab, we will explore in more detail the way the C language uses the stack on the x86, and in the process write a useful new 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.
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?
阅读
的最后几行代码1
kern/entry.S
注意到
三个指令.他们的用处是:1
.data .space .globl
.data tells as to assemble the following statements onto the end of the data subsection numbered subsection (which is an absolute expression). If subsection is omitted, it defaults to zero.1
.data
This directive emits size bytes, each of value fill. Both size and fill are absolute expressions. If the comma and fill are omitted, fill is assumed to be zero. This is the same as `.skip’.1
.space
.global makes the symbol visible to ld. If you define symbol in your partial program, its value is made available to other partial programs that are linked with it. Otherwise, symbol takes its attributes from a symbol of the same name from another file linked into the same program.1
.globl
- .p2align[wl] abs-expr, abs-expr, abs-expr
-
Pad the location counter (in the current subsection) to a particular storage boundary. The first expression (which must be absolute) is the number of low-order zero bits the location counter must have after advancement. For example `.p2align 3’ advances the location counter until it a multiple of 8. If the location counter is already a multiple of 8, no change is needed.
-
The second expression (also absolute) gives the fill value to be stored in the padding bytes. It (and the comma) may be omitted. If it is omitted, the padding bytes are normally zero. However, on some systems, if the section is marked as containing code and the fill value is omitted, the space is filled with no-op instructions.
-
The third expression is also absolute, and is also optional. If it is present, it is the maximum number of bytes that should be skipped by this alignment directive. If doing the alignment would require skipping more bytes than the specified maximum, then the alignment is not done at all. You can omit the fill value (the second argument) entirely by simply using two commas after the required alignment; this can be useful if you want the alignment to be filled with no-op instructions when appropriate.
-
- GNU Assembler(GAS) 完整的指令列表可以在这查看Assembler Directives
- 有了这些指令, 那这段汇报代码就比较好理解了.
- 在当前的.data段, 开一新的页(PGSHIFT = 12) 也就是在2^12 = 4k处对齐.
指向该地址的起始位置.1
bootstack
- 栈顶(
) 则是1
bootstacktop
(通过1
bootstack + KSTKSIZE
来分配空间)1
.space
的大小为1
KSTKSIZE
1
8 * PGSIZE = 32kb
- 栈顶的地址是
1
0xf0110000
- 验证, 将
改为1
.space KSTKSIZE
可以验证. 然后在1
.space KSTKSIZE 0x1
第77行下断点.gdb输出为:1
kern:entry.S
- 具体有几字节对不上, 可能是因为编译器增加了一些guard之类的举措. 但是结果基本是符合我们结论的.
###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. When you think you have it working right, run make grade to see if its output conforms to what our grading script expects, and fix it if it doesn’t. After you have handed in your Lab 1 code, you are welcome to change the output format of the backtrace function any way you like.
实现
函数.输出的格式如下所示:1
kern/monitor.c:mon_backtrace
栈调用的结构应该都非常熟悉了. 并且在
里有非常方便的帮助函数1
inc/x86.h
1
read_ebp()
获得寄存器
的值之后, 我们可以得到如下几个信息(ebp为unsigned int):1
ebp
- 当前frame的返回指位于地址
处, 也就是1
ebp+4
1
eip
- 当前函数scope内, 第n个参数的地址为
1
ebp+4(n+1)
- 内存
处的值就是1
ebp
, 然后这些ebp就可以连起来了.1
调用者(caller)的ebp
具体编码需要注意的还是c的老问题
这个清楚之后, 看代码1
kern/monitor.c