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 ---Typeto continue, or qto quit--- 0 x7c40: 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:
- Explain the interface between printf.c and console.c. Specifically, what function does console.c export? How is this function used by printf.c?
- 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 }
- 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 doesfmt
point? To what doesap
point? - List (in order of execution) each call to
cons_putc
,va_arg
, andvcprintf
. Forcons_putc
, list its argument as well. Forva_arg
, list whatap
points to before and after the call. Forvcprintf
list the values of its two arguments.
- In the call to
- 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 change57616
to a different value?Here's a description of little- and big-endian and a more whimsical description.
- 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);
- 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 f0100937if (x > 0) f01000fe: 85 db test %ebx,%ebx f0100100: 7e 0d jle f010010ftest_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 f01000e4f010010d: eb 1c jmp f010012b 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 f010069acprintf( "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} 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
会自动压栈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行为添加内容。