###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)
添加如下代码

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

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. 用户调用
    1
    
    kern:printf.c int cprintf(const char *fmt, ...)
  2. 1
    
    cprintf
    会提取出可变参数列表, 并且传递给
    1
    
    kern:printf.c int vcprintf(const char *fmt, va_list ap)
  3. 1
    
    vcprintf
    之后会调用
    1
    
    lib/printfmt.c void vprintfmt(void (*putch)(int, void*), void *putdat, const char *fmt, va_list ap)
  4. 1
    
    vprintfmt
    会使用vcprintf传递的参数
    1
    
    putch
    函数来打印字符, 另外主要的逻辑(扫描字符串)也是在
    1
    
    vprintfmt
    完成.
  5. 1
    
    vprintfmt
    也会将成功打印的字符数通过参数
    1
    
    putdat
    返回给他的调用者
    1
    
    vcprintf
    ,
    1
    
    vcprintf
    也会返回成功打印的字符数给
    1
    
    cprintf
    的调用者.
  6. 1
    
    putch
    则是负责打印单个字符的函数.

Explain the following from console.c:

if (crt_pos >= CRT_SIZE) {
        int i;
        memcpy(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;
}

有宏

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:

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?

    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.

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?

    输出为

    1
    
    He110 World

    • e110好理解, 是57616的 hex 表示. 那么 rld 是从哪儿来的呢?
    • x86小端(little-endian)模式下, 高位在低地址存放. 假设局部变量i在栈中的地址为[addr, addr + 3]
    • 0x00(最高位)的地址为addr + 3, 0x72(最低位)的地址则是 addr + 0
    • 所以
      1
      
      printf("%s", &i);
      则打印的是rld’\0’
  • 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
的最后几行代码

.data
###################################################################
# boot stack
###################################################################
	.p2align	PGSHIFT		# force page alignment
	.globl		bootstack
bootstack:
	.space		KSTKSIZE
	.globl		bootstacktop   
bootstacktop:

注意到

1
.data .space .globl
三个指令.他们的用处是:

  • 1
    
    .data
    .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
    
    .space
    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
    
    .globl
    .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.
  • .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
  • 有了这些指令, 那这段汇报代码就比较好理解了.
    1. 在当前的.data段, 开一新的页(PGSHIFT = 12) 也就是在2^12 = 4k处对齐.
    2. 1
      
      bootstack
      指向该地址的起始位置.
    3. 栈顶(
      1
      
      bootstacktop
      ) 则是
      1
      
      bootstack + KSTKSIZE
      (通过
      1
      
      .space
      来分配空间)
    4. 1
      
      KSTKSIZE
      的大小为
      1
      
      8 * PGSIZE = 32kb
    5. 栈顶的地址是
      1
      
      0xf0110000
  • 验证, 将
    1
    
    .space KSTKSIZE
    改为
    1
    
    .space KSTKSIZE 0x1
    可以验证. 然后在
    1
    
    kern:entry.S
    第77行下断点.gdb输出为:
(gdb) p/x bootstacktop
$4 = 0x111021
(gdb) x/10x (0x110000-4096*8-3)
    0x107ffd:   0x01000000  0x01010101  0x01010101  0x01010101
    0x10800d:   0x01010101  0x01010101  0x01010101  0x01010101
    0x10801d:   0x01010101  0x01010101
  • 具体有几字节对不上, 可能是因为编译器增加了一些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
函数.输出的格式如下所示:

Stack backtrace:
ebp f0109e58  eip f0100a62  args 00000001 f0109e80 f0109e98 f0100ed2 00000031
ebp f0109ed8  eip f01000d6  args 00000000 00000000 f0100058 f0109f28 00000061

栈调用的结构应该都非常熟悉了. 并且在

1
inc/x86.h
里有非常方便的帮助函数
1
read_ebp()

获得寄存器

1
ebp
的值之后, 我们可以得到如下几个信息(ebp为unsigned int):

  1. 当前frame的返回指位于地址
    1
    
    ebp+4
    处, 也就是
    1
    
    eip
  2. 当前函数scope内, 第n个参数的地址为
    1
    
    ebp+4(n+1)
  3. 内存
    1
    
    ebp
    处的值就是
    1
    
    调用者(caller)的ebp
    , 然后这些ebp就可以连起来了.

具体编码需要注意的还是c的老问题

uint32_t ui = 0x00000000;
uint32_t *uip = 0x00000000;
ui += 1;  //ui = 0x00000001
uip += 1; //uip = 0x00000004

这个清楚之后, 看代码

1
kern/monitor.c

int
mon_backtrace(int argc, char **argv, struct Trapframe *tf)
{
	// Your code here.
	volatile uint32_t ebp;
	volatile uint32_t *p;
	struct Eipdebuginfo info;
	ebp = read_ebp();
	while (ebp > KSTACKTOP) {
		p = (uint32_t *)ebp;
		cprintf("ebp %08x eip %08x args %08x %08x %08x %08x\n",
                p, *(p+1), *(p+2), *(p+3), *(p+4), *(p+5), *(p+6));
		ebp = *p;
	}
	return 0;
}

Git: rebase is better than pull

why we should use rebase more often, 为什么要多使用rebase Continue reading

Mit 6828 lab2 Memory Management

Published on December 06, 2013

Mit 6828 lab1 ex12, Backtrace

Published on October 13, 2013