MIT OS lab1

Lab 1: Booting a PC

1、Introduction

Software setup

Get jos code:

athena% mkdir ~/6.828

athena% cd ~/6.828

athena% add git

athena% git clone http://pdos.csail.mit.edu/6.828/2014-jos.git lab

Cloning into lab...

athena% cd lab

Part 1: PC Bootstrap

1.1   getting started with X86 assembly.

1 Exercise 1. Familiarize yourself with the assembly language materials available on the 6.828 reference page. You don't have to read them now, but you'll almost certainly want to refer to some of this material when reading and writing x86 assembly.
2 
3 We do recommend reading the section "The Syntax" in Brennan's Guide to Inline Assembly. It gives a good (and quite brief) description of the AT&T assembly syntax we'll be using with the GNU assembler in JOS.

 熟悉Intel格式的汇编和AT&T格式汇编即可。

1.2   simulating the X86

查看主目录下的Makefile,可知make/ make qemu 等编译命令操作内容。

使用make编译代码,make qemu启动qemu虚拟机。启动以后可以看到如下命令

K> help
help - display this list of commands
kerninfo - display information about the kernel
K> kerninfo
Special kernel symbols:
  entry  f010000c (virt)  0010000c (phys)
  etext  f0101a75 (virt)  00101a75 (phys)
  edata  f0112300 (virt)  00112300 (phys)
  end    f0112960 (virt)  00112960 (phys)
Kernel executable memory footprint: 75KB
K>
PC机的物理地址空间如下:
+------------------+  <- 0xFFFFFFFF (4GB)
|      32-bit      |
|  memory mapped   |
|     devices      |
|                  |
/\/\/\/\/\/\/\/\/\/\

/\/\/\/\/\/\/\/\/\/\
|                  |
|      Unused      |
|                  |
+------------------+  <- depends on amount of RAM
|                  |
|                  |
| Extended Memory  |
|                  |
|                  |
+------------------+  <- 0x00100000 (1MB)
|     BIOS ROM     |
+------------------+  <- 0x000F0000 (960KB)
|  16-bit devices, |
|  expansion ROMs  |
+------------------+  <- 0x000C0000 (768KB)
|   VGA Display    |
+------------------+  <- 0x000A0000 (640KB)
|                  |
|    Low Memory    |
|                  |
+------------------+  <- 0x00000000

1.3 the ROM BIOS

Exercise 2. Use GDB's si (Step Instruction) command to trace into the ROM BIOS for a few more instructions, and try to guess what it might be doing. You might want to look at Phil Storrs I/O Ports Description, as well as other materials on the 6.828 reference materials page. No need to figure out all the details - just the general idea of what the BIOS is doing first.

 在一个窗口输入make qemu-gdb, 之后再另一个窗口输入make gdb:

make gdb
gdb -x .gdbinit
...
+ target remote localhost:25000
The target architecture is assumed to be i8086
[f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b
0x0000fff0 in ?? ()
+ symbol-file obj/kern/kernel

 1 (gdb) i registers 
 2 eax            0x0    0
 3 ecx            0x0    0
 4 edx            0x663    1635
 5 ebx            0x0    0
 6 esp            0x0    0x0
 7 ebp            0x0    0x0
 8 esi            0x0    0
 9 edi            0x0    0
10 eip            0xfff0    0xfff0
11 eflags         0x2    [ ]
12 cs             0xf000    61440
13 ss             0x0    0
14 ds             0x0    0
15 es             0x0    0
16 fs             0x0    0
17 gs             0x0    0

可以看到CS:IP 为 0xf000:0xfff0, 即BIOS第一条指令地址为0xf000 * 0x10 + 0xfff0 = 0xffff0, 此处指令内容为:

  ljump 0xf000, 0xe05b

即跳转到0xfe05b. 如下:

(gdb) si
[f000:e05b]    0xfe05b:    cmpl   $0x0,%cs:0x65a4
0x0000e05b in ?? ()
(gdb) x/10i 0xfe05b
   0xfe05b:    cmpl   $0x0,%cs:0x65a4
   0xfe062:    jne    0xfd2b9
   0xfe066:    xor    %ax,%ax
   0xfe068:    mov    %ax,%ss
   0xfe06a:    mov    $0x7000,%esp
   0xfe070:    mov    $0xf3c2f,%edx
   0xfe076:    jmp    0xfd12a
   0xfe079:    push   %ebp
   0xfe07b:    push   %edi
   0xfe07d:    push   %esi

 

Part 2: The Boot Loader

BIOS会把floppy或hard disk中的512字节的boot sector 加载到物理地址0x7C00, 并通过jmp指令将CS:IP设置为0x0000:0x7C00

Exercise 3. Take a look at the lab tools guide, especially the section on GDB commands. Even if you're familiar with GDB, this includes some esoteric GDB commands that are useful for OS work.

Set a breakpoint at address 0x7c00, which is where the boot sector will be loaded. Continue execution until that breakpoint. Trace through the code in boot/boot.S, using the source code and the disassembly file obj/boot/boot.asm to keep track of where you are. Also use the x/i command in GDB to disassemble sequences of instructions in the boot loader, and compare the original boot loader source code with both the disassembly in obj/boot/boot.asm and GDB.

Trace into bootmain() in boot/main.c, and then into readsect(). Identify the exact assembly instructions that correspond to each of the statements in readsect(). Trace through the rest of readsect() and back out into bootmain(), and identify the begin and end of the for loop that reads the remaining sectors of the kernel from the disk. Find out what code will run when the loop is finished, set a breakpoint there, and continue to that breakpoint. Then step through the remainder of the boot loader.

Be able to answer the following questions:
1、At what point does the processor start executing 32-bit code? What exactly causes the switch from 16- to 32-bit mode?
2、What is the last instruction of the boot loader executed, and what is the first instruction of the kernel it just loaded?
3、Where is the first instruction of the kernel?
4、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?

gdb显示0x7C00处的代码如下:

(gdb) x/36i 0x7c00
   0x7c00:    cli    
=> 0x7c01:    cld    
   0x7c02:    xor    %ax,%ax
   0x7c04:    mov    %ax,%ds
   0x7c06:    mov    %ax,%es
   0x7c08:    mov    %ax,%ss
   0x7c0a:    in     $0x64,%al
   0x7c0c:    test   $0x2,%al
   0x7c0e:    jne    0x7c0a
   0x7c10:    mov    $0xd1,%al
   0x7c12:    out    %al,$0x64
   0x7c14:    in     $0x64,%al
   0x7c16:    test   $0x2,%al
   0x7c18:    jne    0x7c14
   0x7c1a:    mov    $0xdf,%al
   0x7c1c:    out    %al,$0x60
   0x7c1e:    lgdtw  0x7c64
   0x7c23:    mov    %cr0,%eax
   0x7c26:    or     $0x1,%eax
   0x7c2a:    mov    %eax,%cr0
   0x7c2d:    ljmp   $0x8,$0x7c32
   0x7c32:    mov    $0xd88e0010,%eax
   0x7c38:    mov    %ax,%es
   0x7c3a:    mov    %ax,%fs
   0x7c3c:    mov    %ax,%gs
   0x7c3e:    mov    %ax,%ss
---Type <return> to continue, or q <return> to quit---
   0x7c40:    mov    $0x7c00,%sp
   0x7c43:    add    %al,(%bx,%si)
   0x7c45:    call   0x7d1a
   0x7c48:    add    %al,(%bx,%si)
   0x7c4a:    jmp    0x7c4a
(gdb) x/36i 0x7c00
   0x7c00:    cli    
=> 0x7c01:    cld    
   0x7c02:    xor    %ax,%ax
   0x7c04:    mov    %ax,%ds
   0x7c06:    mov    %ax,%es
   0x7c08:    mov    %ax,%ss
   0x7c0a:    in     $0x64,%al
   0x7c0c:    test   $0x2,%al
   0x7c0e:    jne    0x7c0a
   0x7c10:    mov    $0xd1,%al
   0x7c12:    out    %al,$0x64
   0x7c14:    in     $0x64,%al
   0x7c16:    test   $0x2,%al
   0x7c18:    jne    0x7c14
   0x7c1a:    mov    $0xdf,%al
   0x7c1c:    out    %al,$0x60
   0x7c1e:    lgdtw  0x7c64
   0x7c23:    mov    %cr0,%eax
   0x7c26:    or     $0x1,%eax
   0x7c2a:    mov    %eax,%cr0
   0x7c2d:    ljmp   $0x8,$0x7c32
   0x7c32:    mov    $0xd88e0010,%eax
   0x7c38:    mov    %ax,%es
   0x7c3a:    mov    %ax,%fs
   0x7c3c:    mov    %ax,%gs
   0x7c3e:    mov    %ax,%ss
   0x7c40:    mov    $0x7c00,%sp
   0x7c43:    add    %al,(%bx,%si)
   0x7c45:    call   0x7d1a
   0x7c48:    add    %al,(%bx,%si)
   0x7c4a:    jmp    0x7c4a

1、处理器进入boot loader后,0x7c2a mov %eax, cr0 将cr0低位置1,处理器从实模式跳入保护模式, 通过ljmp $0x8, $0x7c32跳入地址0x7c32,开始执行32位实模式代码.

2、在boot/main.c的第58行进入kernel:

    // call the entry point from the ELF header
    // note: does not return!
    ((void (*)(void)) (ELFHDR->e_entry))();

3、用gdb调试代码:

(gdb) x/36i 0x7d1c
   0x7d1c:    push   %ebp
   0x7d1d:    mov    %esp,%ebp
 ...
   0x7d6d:    add    $0xc,%esp
   0x7d70:    cmp    %esi,%ebx
   0x7d72:    jb     0x7d5c
   0x7d74:    call   *0x10018
   0x7d7a:    mov    $0x8a00,%edx
   0x7d7f:    mov    $0xffff8a00,%eax
   0x7d84:    out    %ax,(%dx)
   0x7d86:    mov    $0xffff8e00,%eax
   0x7d8b:    out    %ax,(%dx)

结合source code, 可知通过call *0x10018执行上述 e_entry,

(gdb) b *0x7d74
Breakpoint 2 at 0x7d74
(gdb) c
Continuing.
=> 0x7d74:    call   *0x10018

Breakpoint 2, 0x00007d74 in ?? ()
(gdb) si
=> 0x10000c:    movw   $0x1234,0x472
0x0010000c in ?? ()

可知地址0x10000c为kernel中的首地址.

4、通过objdump即可

root@ubuntu:2014-jos# objdump -x obj/kern/kernel

obj/kern/kernel:     file format elf32-i386
obj/kern/kernel
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x0010000c

Program Header:
    LOAD off    0x00001000 vaddr 0xf0100000 paddr 0x00100000 align 2**12
         filesz 0x000073d9 memsz 0x000073d9 flags r-x
    LOAD off    0x00009000 vaddr 0xf0108000 paddr 0x00108000 align 2**12
         filesz 0x0000a300 memsz 0x0000a944 flags rw-
   STACK off    0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**2
         filesz 0x00000000 memsz 0x00000000 flags rwx

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00001915  f0100000  00100000  00001000  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .rodata       00000710  f0101920  00101920  00002920  2**5
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .stab         00003a21  f0102030  00102030  00003030  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .stabstr      00001988  f0105a51  00105a51  00006a51  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .data         0000a300  f0108000  00108000  00009000  2**12
                  CONTENTS, ALLOC, LOAD, DATA
  5 .bss          00000644  f0112300  00112300  00013300  2**5
                  ALLOC
  6 .comment      0000002d  00000000  00000000  00013300  2**0
                  CONTENTS, READONLY

2.1 Loading the kernel

在loading kernel阶段,主要由C语言完成。

Exercise 4. Read about programming with pointers in C. The best reference for the C language is The C Programming Language by Brian Kernighan and Dennis Ritchie (known as 'K&R'). We recommend that students purchase this book (here is an Amazon Link) or find one of MIT's 7 copies.

Read 5.1 (Pointers and Addresses) through 5.5 (Character Pointers and Functions) in K&R. Then download the code for pointers.c, run it, and make sure you understand where all of the printed values come from. In particular, make sure you understand where the pointer addresses in lines 1 and 6 come from, how all the values in lines 2 through 4 get there, and why the values printed in line 5 are seemingly corrupted.

There are other references on pointers in C (e.g., A tutorial by Ted Jensen that cites K&R heavily), though not as strongly recommended.

Warning: Unless you are already thoroughly versed in C, do not skip or even skim this reading exercise. If you do not really understand pointers in C, you will suffer untold pain and misery in subsequent labs, and then eventually come to understand them the hard way. Trust us; you don't want to find out what "the hard way" is.

只需自己熟悉C语言即可。

 

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!

正常情况下:

(gdb) x/30i 0x7c00
=> 0x7c00:    cli    
   0x7c01:    cld    
。。。
   0x7c0a:    in     $0x64,%al
   0x7c0c:    test   $0x2,%al
   0x7c0e:    jne    0x7c0a
   0x7c10:    mov    $0xd1,%al
   0x7c12:    out    %al,$0x64
   0x7c14:    in     $0x64,%al
   0x7c16:    test   $0x2,%al
   0x7c18:    jne    0x7c14
   0x7c1a:    mov    $0xdf,%al
   0x7c1c:    out    %al,$0x60
   0x7c1e:    lgdtw  0x7c64
   0x7c23:    mov    %cr0,%eax
   0x7c26:    or     $0x1,%eax
   0x7c2a:    mov    %eax,%cr0
   0x7c2d:    ljmp   $0x8,$0x7c32
   0x7c32:    mov    $0xd88e0010,%eax
。。。
   0x7c45:    call   0x7d1a
   0x7c48:    add    %al,(%bx,%si)

将-Ttext 0x7c00修改为 -Ttext 0x7c04后,

(gdb) x/30i 0x7c00
...
   0x7c2d:    ljmp   $0x8,$0x7c36
   0x7c32:    mov    $0xd88e0010,%eax
   0x7c38:    mov    %ax,%es
   0x7c3a:    mov    %ax,%fs
   0x7c3c:    mov    %ax,%gs
   0x7c3e:    mov    %ax,%ss
   0x7c40:    mov    $0x7c04,%sp
   0x7c43:    add    %al,(%bx,%si)
   0x7c45:    call   0x7d1a
   0x7c48:    add    %al,(%bx,%si)

跳转到了0x7c36, 并非一个正常指令地址,导致错误。

 

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

调试gdb

(gdb) b *0x7c00
Breakpoint 1 at 0x7c00
(gdb) c
Continuing.
[   0:7c00] => 0x7c00:    cli    

Breakpoint 1, 0x00007c00 in ?? ()
(gdb) x/8x 0x100000
0x100000:    0x00000000    0x00000000    0x00000000    0x00000000
0x100010:    0x00000000    0x00000000    0x00000000    0x00000000
(gdb) b *0x10000c
Breakpoint 2 at 0x10000c
(gdb) c
Continuing.
The target architecture is assumed to be i386
=> 0x10000c:    movw   $0x1234,0x472

Breakpoint 2, 0x0010000c in ?? ()

(gdb) x/8i 0x10000c
=> 0x10000c: movw $0x1234,0x472
0x100015: mov $0x110000,%eax
0x10001a: mov %eax,%cr3
0x10001d: mov %cr0,%eax
0x100020: or $0x80010001,%eax
0x100025: mov %eax,%cr0
0x100028: mov $0xf010002f,%eax
0x10002d: jmp *%eax

在刚进入boot loader时,kernel没有被载入内存,因此0x100000地址为空.

boot loader 执行完毕,kernel已经载入。

Part 3: The Kernel

3.1 Use Virtual Memory

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.

gdb跟踪如下:

(gdb) b *0x10000c
Breakpoint 1 at 0x10000c
(gdb) c
Continuing.
The target architecture is assumed to be i386
=> 0x10000c:    movw   $0x1234,0x472

Breakpoint 1, 0x0010000c in ?? ()
(gdb) si
=> 0x100015:    mov    $0x110000,%eax
0x00100015 in ?? ()
(gdb) 
=> 0x10001a:    mov    %eax,%cr3
0x0010001a in ?? ()
(gdb) x 0x00100000
   0x100000:    add    0x1bad(%eax),%dh
(gdb) x 0xf0100000
   0xf0100000:    add    %al,(%eax)
(gdb) si
=> 0x10001d:    mov    %cr0,%eax
0x0010001d in ?? ()
(gdb) 
=> 0x100020:    or     $0x80010001,%eax
0x00100020 in ?? ()
(gdb) 
=> 0x100025:    mov    %eax,%cr0
0x00100025 in ?? ()
(gdb) 
=> 0x100028:    mov    $0xf010002f,%eax
0x00100028 in ?? ()
(gdb) x 0x00100000
   0x100000:    add    0x1bad(%eax),%dh
(gdb) x 0xf0100000
   0xf0100000:    add    0x1bad(%eax),%dh
(gdb) 

在执行 mov %eax, %cr0之前,0xf0100000和0x00100000的内容不一致,当CR0_PG标志位改变之后,(高位的)虚拟地址被(页表)转换为物理地址。

然后我们执行stepi指令,此时Paging已经启用,entry_pgdir将0xf0000000到0xf0400000范围内虚拟地址转换成了0x00000000到0x00400000范围的物理地址。所以再次检查地址内容时候发现地址0xf0100000的内容和0x00100000的内容相同。

 

3.2 Formatted Printing 

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.

Be able to answer the following questions:

  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?
  2. Explain the following from console.c:
    1      if (crt_pos >= CRT_SIZE) {
    2              int i;
    3              memcpy(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t));
    4              for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i++)
    5                      crt_buf[i] = 0x0700 | ' ';
    6              crt_pos -= CRT_COLS;
    7      }
    
  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_putcva_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.
  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.

  5. In the following code, what is going to be printed after 'y='? (note: the answer is not a specific value.) Why does this happen?
        cprintf("x=%d y=%d", 3);
    
  6. 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?
Challenge Enhance the console to allow text to be printed in different colors. The traditional way to do this is to make it interpret ANSI escape sequences embedded in the text strings printed to the console, but you may use any mechanism you like. There is plenty of information on the 6.828 reference page and elsewhere on the web on programming the VGA display hardware. If you're feeling really adventurous, you could try switching the VGA hardware into a graphics mode and making the console draw text onto the graphical frame buffer.

将vprintfmt函数中的case 'o'修改如下:

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

1、console.c中到处函数cputchar, 打印一个字符, 供printf.c中的打印函数。

2、打印满屏时的处理,如果满屏,整体向上移动一行。

3、fmt指向格式化字符串,即 "x %d, y %x, z %d\n", ap指向不定长参数第一个参数x的地址。

4、He110, World 57616即0xe110, 0x00646c72的ascii码字符为{r, l, d, '\0'}

5、打印未知, 因为第二个参数没有指定,ap向后找一个位置,内容未知。

6、修改宏va_start和va_arg

 

3.3 The Stack

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?

stack初始化在调用i386_init之前:

    # Clear the frame pointer register (EBP)
    # so that once we get into debugging C code,
    # stack backtraces will be terminated properly.
    movl    $0x0,%ebp            # nuke frame pointer

    # Set the stack pointer
    movl    $(bootstacktop),%esp
.data
###################################################################
# boot stack
###################################################################
    .p2align    PGSHIFT        # force page alignment
    .globl        bootstack
bootstack:
    .space        KSTKSIZE
    .globl        bootstacktop   
bootstacktop:

stack放在.data段,共分配了KSTKSIZE大小的堆栈,bootstacktop指向栈顶.

// Test the stack backtrace function (lab 1 only)
void
test_backtrace(int x)
{
f01000e4:    55                       push   %ebp
f01000e5:    89 e5                    mov    %esp,%ebp
f01000e7:    53                       push   %ebx
f01000e8:    83 ec 14                 sub    $0x14,%esp
f01000eb:    8b 5d 08                 mov    0x8(%ebp),%ebx
    cprintf("entering test_backtrace %d\n", x);
f01000ee:    89 5c 24 04              mov    %ebx,0x4(%esp)
f01000f2:    c7 04 24 72 19 10 f0     movl   $0xf0101972,(%esp)
f01000f9:    e8 39 08 00 00           call   f0100937 <cprintf>
    if (x > 0)
f01000fe:    85 db                    test   %ebx,%ebx
f0100100:    7e 0d                    jle    f010010f <test_backtrace+0x2b>
        test_backtrace(x-1);
f0100102:    8d 43 ff                 lea    -0x1(%ebx),%eax
f0100105:    89 04 24                 mov    %eax,(%esp)
f0100108:    e8 d7 ff ff ff           call   f01000e4 <test_backtrace>
f010010d:    eb 1c                    jmp    f010012b <test_backtrace+0x47>
    else
        mon_backtrace(0, 0, 0);
f010010f:    c7 44 24 08 00 00 00     movl   $0x0,0x8(%esp)
f0100116:    00 
f0100117:    c7 44 24 04 00 00 00     movl   $0x0,0x4(%esp)
f010011e:    00 
f010011f:    c7 04 24 00 00 00 00     movl   $0x0,(%esp)
f0100126:    e8 6f 05 00 00           call   f010069a <mon_backtrace>
    cprintf("leaving test_backtrace %d\n", x);
f010012b:    89 5c 24 04              mov    %ebx,0x4(%esp)
f010012f:    c7 04 24 8e 19 10 f0     movl   $0xf010198e,(%esp)
f0100136:    e8 fc 07 00 00           call   f0100937 <cprintf>
}
f010013b:    83 c4 14                 add    $0x14,%esp
f010013e:    5b                       pop    %ebx
f010013f:    5d                       pop    %ebp
f0100140:    c3                       ret    

两次push和一次sub 0x14, 共有4 + 4 + 20 = 28Bytes, 再加上

call f01000e4 <test_backtrace>

会自动压栈eip,共有32Bytes。

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.

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.
 1 int
 2 mon_backtrace(int argc, char **argv, struct Trapframe *tf)
 3 {
 4     // Your code here.
 5     uint32_t *ebp = (uint32_t *)read_ebp();
 6     uint32_t *eip = (uint32_t *)ebp[1];
 7     uint32_t args[5], i;
 8     for (i = 0; i < 5; i++)
 9         args[i] = ebp[i + 2];
10     
11     cprintf("Stack_backtrace:\n");
12     while (ebp != NULL)
13     {
14         cprintf(" ebp:0x%08x eip:0x%08x args:0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n",
15             ebp, eip, args[0], args[1], args[2], args[3], args[4]);
16         ebp = (uint32_t *)ebp[0];
17         eip = (uint32_t *)ebp[1];
18         for (i = 0; i < 5; i++)
19             args[i] = ebp[i + 2];
20     }
21     return 0;
22 }

运行结果:

entering test_backtrace 5
entering test_backtrace 4
entering test_backtrace 3
entering test_backtrace 2
entering test_backtrace 1
entering test_backtrace 0
Stack_backtrace:
 ebp:0xf010ff18 eip:0xf010012b args:0x00000000 0x00000000 0x00000000 0x00000000 0xf01009c1
 ebp:0xf010ff38 eip:0xf010010d args:0x00000000 0x00000001 0xf010ff78 0x00000000 0xf01009c1
 ebp:0xf010ff58 eip:0xf010010d args:0x00000001 0x00000002 0xf010ff98 0x00000000 0xf01009c1
 ebp:0xf010ff78 eip:0xf010010d args:0x00000002 0x00000003 0xf010ffb8 0x00000000 0xf01009c1
 ebp:0xf010ff98 eip:0xf010010d args:0x00000003 0x00000004 0x00000000 0x00000000 0x00000000
 ebp:0xf010ffb8 eip:0xf010010d args:0x00000004 0x00000005 0x00000000 0x00010094 0x00010094
 ebp:0xf010ffd8 eip:0xf010018e args:0x00000005 0x00001aac 0x00000644 0x00000000 0x00000000
 ebp:0xf010fff8 eip:0xf010003e args:0x00111021 0x00000000 0x00000000 0x00000000 0x00000000
leaving test_backtrace 0
leaving test_backtrace 1
leaving test_backtrace 2
leaving test_backtrace 3
leaving test_backtrace 4
leaving test_backtrace 5
Welcome to the JOS kernel monitor!

 

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.

在kern/kdebug.c中添加

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

修改kern/monitor.c修改

 1 int
 2 mon_backtrace(int argc, char **argv, struct Trapframe *tf)
 3 {
 4     // Your code here.
 5     struct Eipdebuginfo dbg_info;
 6     uint32_t *ebp = (uint32_t *)read_ebp();
 7     uint32_t *eip = (uint32_t *)ebp[1];
 8     uint32_t args[5], i;
 9     for (i = 0; i < 5; i++)
10         args[i] = ebp[i + 2];
11     
12     cprintf("Stack_backtrace:\n");
13     while (ebp != NULL)
14     {
15         cprintf(" ebp:0x%08x eip:0x%08x args:0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n",
16             ebp, eip, args[0], args[1], args[2], args[3], args[4]);
17         debuginfo_eip((uintptr_t)eip, &dbg_info);
18         cprintf("\t%s:%d %.*s+%d\n", dbg_info.eip_file, dbg_info.eip_line, 
19             dbg_info.eip_fn_namelen, dbg_info.eip_fn_name, ebp[1] - dbg_info.eip_fn_addr);
20         ebp = (uint32_t *)ebp[0];
21         eip = (uint32_t *)ebp[1];
22         for (i = 0; i < 5; i++)
23             args[i] = ebp[i + 2];
24     }
25     return 0;
26 }

17,18行为添加内容。

posted on 2015-02-21 16:37  ym65536  阅读(3319)  评论(1编辑  收藏  举报