Jos Lab 1: Booting a PC
Part 1: PC Bootstrap
Exercise 1
Read or at least carefully scan the entire PC Assembly Language book,except that you should skip all sections after 1.3.5 in chapter 1, which talk about features of the NASM assembler that do not apply directly to the GNU assembler. You may also skip chapters 5 and 6, and all sections under 7.2, which deal with processor and language features we won’t use in JOS. Also read the section “The Syntax” in Brennan’s Guide to Inline Assembly to familiarize yourself with the most important features of GNU assembler syntax.
好难啊,就稍微看了看 😦
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 Jos reference materials page. No need to figure out all the details - just the general idea of what the BIOS is doing first.
首先是跳转到较早的BIOS
[f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b
接下来的指令看不太懂,查资料得到
当 BIOS 运行后,它将:
1.设置一个中断描述符表
2.初始化各种设备,比如, VGA 显示。
3.在初始化 PCI 产品线和 BIOS 知道的所有重要设备之后,它将搜索可引导设备,比如,一个软盘、硬盘、或者 CD-ROM。最后,当它 找到可引导磁盘之后,BIOS 从可引导硬盘上读取引导加载器,然后将控制权交给它。
Part 2: The Boot Loader
Exercise 3
Set a breakpoint at address 0x7c00, which is where the boot sector will be loaded. Continue execution until that break point. 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 GNU disassembly in obj/boot/boot.asm and the 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
(1)进入32bits,见boot/boot.S
中代码
# Switch from real to protected mode, using a bootstrap GDT
# and segment translation that makes virtual addresses
# identical to their physical addresses, so that the
# effective memory map does not change during the switch.
lgdt gdtdesc
movl %cr0, %eax
orl $CR0_PE_ON, %eax
movl %eax, %cr0
# Jump to next instruction, but in 32-bit code segment.
# Switches processor into 32-bit mode.
ljmp $PROT_MODE_CSEG, $protcseg
运行时地址
(gdb)
[ 0:7c1e] => 0x7c1e: lgdtw 0x7c64
0x00007c1e in ?? ()
(gdb)
[ 0:7c23] => 0x7c23: mov %cr0,%eax
0x00007c23 in ?? ()
(gdb)
[ 0:7c26] => 0x7c26: or $0x1,%eax
0x00007c26 in ?? ()
(gdb)
[ 0:7c2a] => 0x7c2a: mov %eax,%cr0
0x00007c2a in ?? ()
(gdb)
[ 0:7c2d] => 0x7c2d: ljmp $0x8,$0x7c32
0x00007c2d in ?? ()
可见运行32bits开始地址为0x00007c2d
(2)引导加载器最后一条指令见boot.asm
7d6b: ff 15 18 00 01 00 call *0x10018
接着设置断点运行
(gdb) b *0x7d6b
Breakpoint 1 at 0x7d6b
(gdb) c
Continuing.
The target architecture is assumed to be i386
=> 0x7d6b: call *0x10018
Breakpoint 1, 0x00007d6b in ?? ()
(gdb) x/1x 0x10018
0x10018: 0x0010000c
(gdb) x/1i 0x10000c
0x10000c: movw $0x1234,0x472
(gdb) si
=> 0x10000c: movw $0x1234,0x472
0x0010000c in ?? ()
我们可以看到进入内核第一条指令地址为0x0010000c
(3)在main.c
中,bootmain
函数里
ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff);
eph = ph + ELFHDR->e_phnum;
for (; ph < eph; ph++)
// p_pa is the load address of this segment (as well
// as the physical address)
readseg(ph->p_pa, ph->p_memsz, ph->p_offset);
我们可以看到加载的扇区个数为eph - ph
即ELFHDR->e_phnum
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).
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, though not as strongly recommended. A tutorial by Ted Jensen that cites K&R heavily is available in the course readings.
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
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) 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/8x 0x100000
0x100000: 0x1badb002 0x00000000 0xe4524ffe 0x7205c766
0x100010: 0x34000004 0x0000b812 0x220f0011 0xc0200fd8
可以看到刚进入引导加载器,内存0x100000
后的8个word都是0,进入内核后都有了值。这是因为0x100000
是内核装载地址,引导加载器装载完内核后,内存0x100000
往后就有了内容
Exercise 6
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
afterwards!
没有更改:
(gdb) b *0x7d6b
Breakpoint 1 at 0x7d6b
(gdb) c
Continuing.
The target architecture is assumed to be i386
=> 0x7d6b: call *0x10018
Breakpoint 1, 0x00007d6b in ?? ()
(gdb) si
=> 0x10000c: movw $0x1234,0x472
0x0010000c in ?? ()
更链接地址后:
(gdb) b *0x7d6b
Breakpoint 1 at 0x7d6b
(gdb) c
Continuing.
我们发现更改前能正确跳入内核,更改后就死循环无法跳入内核
Part 3: The Kernel
Exercise 7
Use QEMU and GDB to trace into the JOS kernel and find where the new virtual-to-physical mapping takes effect. Then examine the Global Descriptor Table (GDT) that the code uses to achieve this effect, and make sure you understand what’s going on.
What is the first instruction after the new mapping is established that would fail to work properly if the old mapping were still in place? Comment out or otherwise intentionally break the segmentation setup code in kern/entry.S , trace into it, and see if you were right.
在kern/kernal.asm
中找到了
movl %eax, %cr0
f0100025: 0f 22 c0 mov %eax,%cr0
断点调试,si
前后检查内存
(gdb) b *0x100025
Breakpoint 1 at 0x100025
(gdb) c
Continuing.
The target architecture is assumed to be i386
=> 0x100025: mov %eax,%cr0
Breakpoint 1, 0x00100025 in ?? ()
(gdb) x/1x 0x00100000
0x100000: 0x1badb002
(gdb) x/1x 0xf0100000
0xf0100000 <_start+4026531828>: 0x00000000
(gdb) si
=> 0x100028: mov $0xf010002f,%eax
0x00100028 in ?? ()
(gdb) x/1x 0x00100000
0x100000: 0x1badb002
(gdb) x/1x 0xf0100000
0xf0100000 <_start+4026531828>: 0x1badb002
我们发现指令执行前,两内存内容不一样,执行后变一样,说明映射成功
我们再注释掉movl %eax, %cr0
,再进行一次
(gdb) b *0x100025
Breakpoint 1 at 0x100025
(gdb) c
Continuing.
The target architecture is assumed to be i386
=> 0x100025: mov $0xf010002c,%eax
Breakpoint 1, 0x00100025 in ?? ()
(gdb) x/1x 0x00100000
0x100000: 0x1badb002
(gdb) x/1x 0xf0100000
0xf0100000 <_start+4026531828>: 0x00000000
我们看到映射并没有成功
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. Remember the octal number should begin with ‘0’.
终于开始写代码了hhh
进入printfmt.c
,我们要实现以8进制形式输出。可以借鉴十进制
case 'd':
num = getint(&ap, lflag);
if ((long long) num < 0) {
putch('-', putdat);
num = -(long long) num;
}
base = 10;
goto number;
仿照上面,写出%o
代码
case 'o':
// Replace this with your code.
putch('0', putdat);
num = getuint(&ap, lflag);
base = 8;
goto number;
由题目要求,不要忘记输出前导0;而且8进制我们就当成无符号数处理,不用处理负数情况
Exercise 9
You need also to add support for the “+” flag, which forces to precede the result with a plus or minus sign (+ or -) even for positive numbers.
仿照 -
写 +
case '-':
padc = '-';
goto reswitch;
case '+':
padc = '+';
goto reswitch;
同时更新 d
case 'd':
num = getint(&ap, lflag);
if ((long long) num < 0) {
putch('-', putdat);
num = -(long long) num;
}
else if(padc == '+')
putch('+', putdat);
base = 10;
goto number;
即,如果是正数先输出一个 +
Exercise 10
Enhance the cprintf function to allow it print with the %n specifier, you can consult the %n specifier specification of the C99 printf function for your reference by typing “man 3 printf” on the console. In this lab, we will use the char * type argument instead of the C99 int * argument, that is, “the number of characters written so far is stored into the signed char type integer indicated by the char * pointer argument. No argument is converted.” You must deal with some special cases properly, because we are in kernel, such as when the argument is a NULL pointer, or when the char integer pointed by the argument has been overflowed. Find and fill in this code fragment.
%n
是把当前输出的字符个数写回,如printf("ics%n",&b)
则把3写入b
case 'n': {
// You can consult the %n specifier specification of the C99 printf function
// for your reference by typing "man 3 printf" on the console.
//
// Requirements:
// Nothing printed. The argument must be a pointer to a signed char,
// where the number of characters written so far is stored.
//
// hint: use the following strings to display the error messages
// when the cprintf function ecounters the specific cases,
// for example, when the argument pointer is NULL
// or when the number of characters written so far
// is beyond the range of the integers the signed char type
// can represent.
const char *null_error = "\nerror! writing through NULL pointer! (%n argument)\n";
const char *overflow_error = "\nwarning! The value %n argument pointed to has been overflowed!\n";
// Your code here
char *pos = va_arg(ap, char *);
if (pos == NULL)
printfmt(putch, putdat, "错误内容:%s", null_error);
else if ((*(unsigned int *)putdat)>254){
printfmt(putch, putdat, "错误内容:%s", overflow_error);
*pos = -1;
}
else
*pos = *(char *)putdat;
break;
}
先读到写入的变量的指针pos,如果为空指针就输出错误;溢出就输出错误并把*pos
设为-1
;否则就正常赋值
由
static void
putch(int ch, int *cnt)
{
cputchar(ch);
(*cnt)++;
}
以及调用形式
putch('0', putdat);
可以知道putdat
就是已经输出的字符个数,变换一下类型赋值给*pos
即可
Exercise 11
Modify the function printnum()
in lib/printfmt.c
to support "%-"
when printing numbers. With the directives starting with "%-"
, the printed number should be left adjusted. (i.e., paddings are on the right side.) For example, the following function call:
cprintf("test:[%-5d]", 3)
, should give a result as
"test:[3 ]"
(4 spaces after ‘3’). Before modifying printnum()
, make sure you know what happened in function vprintffmt()
.
我们查看printnum()
函数
static void printnum(void (*putch)(int, void*), void *putdat,
unsigned long long num, unsigned base, int width, int padc)
{
// if cprintf'parameter includes pattern of the form "%-", padding
// space on the right side if neccesary.
// you can add helper function if needed.
// your code here:
if(padc == '-'){
int outlen=0;
int mid=num;
while(mid){
++outlen;
mid/=base;
}
printnum(putch, putdat, num, base, outlen, ' ');
for(int i=0;i<width-outlen;++i)
putch(' ',putdat);
}
// first recursively print all preceding (more significant) digits
if (num >= base) {
printnum(putch, putdat, num / base, base, width - 1, padc);
} else {
// print any needed pad characters before first digit
while (--width > 0)
putch(padc, putdat);
}
// then print this (the least significant) digit
putch("0123456789abcdef"[num % base], putdat);
}
输入的时候padc = '-'
,width
为我们的设定值
首先计算出我们输出了多少位outlen
,然后把要输出的输出,最后再根据输出的剩余位数width-outlen
输出空格
Exercise 12
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?
在kern/entry.S
中有这样一段代码
relocated:
# 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
# now to C code
call i386_init
# Should never get here, but in case we do, just spin.
spin: jmp spin
obj/kern/kernal.asm
里面
f010002f <relocated>:
relocated:
# 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
f010002f: bd 00 00 00 00 mov $0x0,%ebp
# Set the stack pointer
movl $(bootstacktop),%esp
f0100034: bc 00 00 11 f0 mov $0xf0110000,%esp
# now to C code
call i386_init
f0100039: e8 56 00 00 00 call f0100094 <i386_init>
可见初始化地址在0xf010002f
,将ebp
设为0
,将esp
设为(bootstacktop)
Exercise 13
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?
Note that, for this exercise to work properly, you should be using the patched version of QEMU available on the tools page. Otherwise, you’ll have to manually translate all breakpoint and memory addresses to linear addresses.
找到这个函数
f0100040 <test_backtrace>:
#include <kern/console.h>
// Test the stack backtrace function (lab 1 only)
void
test_backtrace(int x)
{
f0100040: 55 push %ebp
f0100041: 89 e5 mov %esp,%ebp
f0100043: 53 push %ebx
f0100044: 83 ec 0c sub $0xc,%esp
f0100047: 8b 5d 08 mov 0x8(%ebp),%ebx
cprintf("entering test_backtrace %d\n", x);
f010004a: 53 push %ebx
f010004b: 68 00 1c 10 f0 push $0xf0101c00
f0100050: e8 0f 0b 00 00 call f0100b64 <cprintf>
if (x > 0)
f0100055: 83 c4 10 add $0x10,%esp
f0100058: 85 db test %ebx,%ebx
f010005a: 7e 11 jle f010006d <test_backtrace+0x2d>
test_backtrace(x-1);
f010005c: 83 ec 0c sub $0xc,%esp
f010005f: 8d 43 ff lea -0x1(%ebx),%eax
f0100062: 50 push %eax
f0100063: e8 d8 ff ff ff call f0100040 <test_backtrace>
f0100068: 83 c4 10 add $0x10,%esp
f010006b: eb 11 jmp f010007e <test_backtrace+0x3e>
else
mon_backtrace(0, 0, 0);
f010006d: 83 ec 04 sub $0x4,%esp
f0100070: 6a 00 push $0x0
f0100072: 6a 00 push $0x0
f0100074: 6a 00 push $0x0
f0100076: e8 d2 08 00 00 call f010094d <mon_backtrace>
f010007b: 83 c4 10 add $0x10,%esp
cprintf("leaving test_backtrace %d\n", x);
f010007e: 83 ec 08 sub $0x8,%esp
f0100081: 53 push %ebx
f0100082: 68 1c 1c 10 f0 push $0xf0101c1c
f0100087: e8 d8 0a 00 00 call f0100b64 <cprintf>
}
f010008c: 83 c4 10 add $0x10,%esp
f010008f: 8b 5d fc mov -0x4(%ebp),%ebx
f0100092: c9 leave
f0100093: c3 ret
函数地址在f0100040
,设置断点查看
(gdb) b *0xf0100040
Breakpoint 1 at 0xf0100040: file kern/init.c, line 13.
(gdb) c
Continuing.
The target architecture is assumed to be i386
=> 0xf0100040 <test_backtrace>: push %ebp
Breakpoint 1, test_backtrace (x=5) at kern/init.c:13
13 {
(gdb) i r
eax 0x1f 31
ecx 0x3d4 980
edx 0x3d5 981
ebx 0xf010fede -267321634
esp 0xf010febc 0xf010febc
ebp 0xf010fff8 0xf010fff8
esi 0xf010ffdf -267321377
edi 0xf010ffdc -267321380
eip 0xf0100040 0xf0100040 <test_backtrace>
(gdb) x/12x 0xf010fe9c
0xf010fe9c: 0xf0100b76 0xf0101c5f 0xf010fec4 0xf010fff8
0xf010feac: 0xf010fede 0xf010ffdf 0xf010ffdc 0xf010fff8
0xf010febc: 0xf010018d 0x00000005 0x00000400 0xfffffc00
(gdb) si
=> 0xf0100041 <test_backtrace+1>: mov %esp,%ebp
0xf0100041 13 {
(gdb) x/12x 0xf010fe9c
0xf010fe9c: 0xf0100b76 0xf0101c5f 0xf010fec4 0xf010fff8
0xf010feac: 0xf010fede 0xf010ffdf 0xf010ffdc 0xf010fff8
0xf010febc: 0xf010018d 0x00000005 0x00000400 0xfffffc00
(gdb) si
=> 0xf0100043 <test_backtrace+3>: push %ebx
0xf0100043 13 {
(gdb) si
=> 0xf0100044 <test_backtrace+4>: sub $0xc,%esp
0xf0100044 13 {
(gdb) x/12x 0xf010fe9c
0xf010fe9c: 0xf0100b76 0xf0101c5f 0xf010fec4 0xf010fff8
0xf010feac: 0xf010fede 0xf010ffdf 0xf010fede 0xf010fff8
0xf010febc: 0xf010018d 0x00000005 0x00000400 0xfffffc00
(gdb) si
=> 0xf0100047 <test_backtrace+7>: mov 0x8(%ebp),%ebx
0xf0100047 13 {
(gdb) i r esp
esp 0xf010fea8 0xf010fea8
(gdb) b *0xf010005c
Breakpoint 2 at 0xf010005c: file kern/init.c, line 16.
(gdb) c
Continuing.
=> 0xf010005c <test_backtrace+28>: sub $0xc,%esp
Breakpoint 2, test_backtrace (x=5) at kern/init.c:16
16 test_backtrace(x-1);
(gdb) i r esp
esp 0xf010feb0 0xf010feb0
(gdb) si
=> 0xf010005f <test_backtrace+31>: lea -0x1(%ebx),%eax
0xf010005f 16 test_backtrace(x-1);
(gdb) i r esp
esp 0xf010fea4 0xf010fea4
(gdb) x/12x 0xf010fe9c
0xf010fe9c: 0xf0100055 0xf0101c00 0x00000005 0xf010fff8
0xf010feac: 0xf010fede 0xf010ffdf 0xf010fede 0xf010fff8
0xf010febc: 0xf010018d 0x00000005 0x00000400 0xfffffc00
(gdb) si
=> 0xf0100062 <test_backtrace+34>: push %eax
0xf0100062 16 test_backtrace(x-1);
(gdb) si
=> 0xf0100063 <test_backtrace+35>: call 0xf0100040 <test_backtrace>
0xf0100063 16 test_backtrace(x-1);
(gdb) i r esp
esp 0xf010fea0 0xf010fea0
(gdb) x/12x 0xf010fe9c
0xf010fe9c: 0xf0100055 0x00000004 0x00000005 0xf010fff8
0xf010feac: 0xf010fede 0xf010ffdf 0xf010fede 0xf010fff8
0xf010febc: 0xf010018d 0x00000005 0x00000400 0xfffffc00
(gdb) si
=> 0xf0100063 <test_backtrace+35>: call 0xf0100040 <test_backtrace>
0xf0100063 16 test_backtrace(x-1);
(gdb) i r esp
esp 0xf010fea0 0xf010fea0
(gdb) x/12x 0xf010fe9c
0xf010fe9c: 0xf0100055 0x00000004 0x00000005 0xf010fff8
0xf010feac: 0xf010fede 0xf010ffdf 0xf010fede 0xf010fff8
0xf010febc: 0xf010018d 0x00000005 0x00000400 0xfffffc00
(gdb) si
=> 0xf0100040 <test_backtrace>: push %ebp
Breakpoint 1, test_backtrace (x=4) at kern/init.c:13
13 {
(gdb) i r esp
esp 0xf010fe9c 0xf010fe9c
(gdb) x/12x 0xf010fe9c
0xf010fe9c: 0xf0100068 0x00000004 0x00000005 0xf010fff8
0xf010feac: 0xf010fede 0xf010ffdf 0xf010fede 0xf010fff8
0xf010febc: 0xf010018d 0x00000005 0x00000400 0xfffffc00
结合代码,两次对比可以看到,栈先放入原ebp
的值0xf010fff8
,再放入ebx
的0xf010fede
,然后esp
减0xc
处理cprintf
,处理完后再eax
入栈,再把下一个返回地址入栈,如下表
栈 | 实际值 | 寄存器 |
---|---|---|
参数1 | 5 | |
返回地址 | 0xf010018d | |
原ebp | 0xf010fff8 | < - ebp |
原ebx | 0xf010fede | |
函数内临时数据/下一层调用第5个参数 | ||
函数内临时数据/下一层调用第4个参数 | ||
函数内临时数据/下一层调用第3个参数 | ||
函数内临时数据/下一层调用第2个参数 | ||
eax (下一层调用第1个参数) | 4 | |
下一层返回地址 | 0xf0100068 | < - esp |
注意,eip记录程序运行位置,每次调用函数call
时,把eip+4
入栈即把返回地址入栈
Exercise 14
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.
编写函数
int mon_backtrace(int argc, char **argv, struct Trapframe *tf)
{
// Your code here.
cprintf("Stack backtrace:\n");
uint32_t *ebp = (unsigned int *)read_ebp();
struct Eipdebuginfo wk;
while(ebp!=0x00){
cprintf(" eip %08x ebp %08x args %08x %08x %08x %08x %08x\n",
ebp[1], ebp, ebp[2], ebp[3], ebp[4], ebp[5], ebp[6]);
ebp=(unsigned int *)*ebp;
}
//overflow_me();
cprintf("Backtrace success\n");
return 0;
}
ebp
表示函数进入栈的基指针:即,栈指针的位置,它就是函数进入之后,函数的前序代码设置的基指针。eip
值列出的是函数的返回指令指针:当函数返回时,指令地址将控制返回。返回指令指针一般指向 call
指令之后的指令。在 args
之后列出的五个十六进制值是在问题中传递给函数的前五个参数。
根据上一个练习我们能清楚知道他们的相对位置,输出,再把ebp
设置成原ebp
循环即可
注意要求输出补齐8位
Exercise 15
Modify your stack backtrace function to display, for each eip
, the function name, source file name, and line number corresponding to that eip
.
In debuginfo_eip
, 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.
··see if the bootloader loads the symbol table in memory as part of loading the kernel binary
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:
K> backtrace
Stack backtrace:
eip f01008ae ebp f010ff78 args 00000001 f010ff8c 00000000 f0110580 00000000
kern/monitor.c:143 monitor+106
eip f0100193 ebp f010ffd8 args 00000000 00001aac 00000660 00000000 00000000
kern/init.c:49 i386_init+59
eip f010003d ebp f010fff8 args 00000000 00000000 0000ffff 10cf9a00 0000ffff
kern/entry.S:70 <unknown>+0
K>
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.
You may find that the some functions are missing from the backtrace. For example, you will probably see a call to monitor()
but not to runcmd()
. This is because the compiler in-lines some function calls. Other optimizations may cause you to see unexpected line numbers. If you get rid of the -O2
from GNUMakefile
, the backtraces may make more sense (but your kernel will run more slowly)
这个题让我们在上一个练习基础上多输出一些东西
先添加命令
static struct Command commands[] = {
{ "help", "Display this list of commands", mon_help },
{ "kerninfo", "Display information about the kernel", mon_kerninfo },
{ "backtrace", "Display backtrace info", mon_backtrace },
};
编写函数
int
mon_backtrace(int argc, char **argv, struct Trapframe *tf)
{
// Your code here.
cprintf("Stack backtrace:\n");
uint32_t *ebp = (unsigned int *)read_ebp();
struct Eipdebuginfo wk;
while(ebp!=0x00){
cprintf(" eip %08x ebp %08x args %08x %08x %08x %08x %08x\n",
ebp[1], ebp, ebp[2], ebp[3], ebp[4], ebp[5], ebp[6]);
if(debuginfo_eip(ebp[1], &wk)==0){
cprintf("\t\t%s:%d %.*s+%d\n", wk.eip_file, wk.eip_line,
wk.eip_fn_namelen, wk.eip_fn_name, ebp[1] - wk.eip_fn_addr);
}
ebp=(unsigned int *)*ebp;
}
//overflow_me();
cprintf("Backtrace success\n");
return 0;
}
运行失败,发现还需补齐debuginfo_eip
函数
// Search within that file's stabs for the function definition
// (N_FUN).
lfun = lfile;
rfun = rfile;
stab_binsearch(stabs, &lfun, &rfun, N_FUN, addr);
if (lfun <= rfun) {
// stabs[lfun] points to the function name
// in the string table, but check bounds just in case.
if (stabs[lfun].n_strx < stabstr_end - stabstr)
info->eip_fn_name = stabstr + stabs[lfun].n_strx;
info->eip_fn_addr = stabs[lfun].n_value;
addr -= info->eip_fn_addr;
// Search within the function definition for the line number.
lline = lfun;
rline = rfun;
} else {
// Couldn't find function stab! Maybe we're in an assembly
// file. Search the whole file for the line number.
info->eip_fn_addr = addr;
lline = lfile;
rline = rfile;
}
// Ignore stuff after the colon.
info->eip_fn_namelen = strfind(info->eip_fn_name, ':') - info->eip_fn_name;
// Search within [lline, rline] for the line number stab.
// If found, set info->eip_line to the right line number.
// If not found, return -1.
//
// Hint:
// There's a particular stabs type used for line numbers.
// Look at the STABS documentation and <inc/stab.h> to find
// which one.
// Your code here.
stab_binsearch(stabs, &lline, &rline, N_SLINE, addr);
if (lline <= rline)
info->eip_line = stabs[lline].n_desc;
else
return -1;
// Search backwards from the line number for the relevant filename
// stab.
// We can't just use the "lfile" stab because inlined functions
// can interpolate code from a different file!
// Such included source files use the N_SOL stab type.
while (lline >= lfile
&& stabs[lline].n_type != N_SOL
&& (stabs[lline].n_type != N_SO || !stabs[lline].n_value))
lline--;
if (lline >= lfile && stabs[lline].n_strx < stabstr_end - stabstr)
info->eip_file = stabstr + stabs[lline].n_strx;
根据上下可以判断出要找到在哪一行,调用二分搜索即可
Exercise 16
Recall the buffer overflow attack in ICS Lab. Modify your start_overflow function to use a technique similar to the buffer overflow to invoke the do_overflow function. You must use the above cprintf function with the %n specifier you augmented in “Exercise 10” to do this job, or else you won’t get the points of this exercise, and the do_overflow function should return normally.
利用%n
的写值功能,覆盖掉原来的地址
void start_overflow(void)
{
// You should use a techique similar to buffer overflow
// to invoke the do_overflow function and
// the procedure must return normally.
// And you must use the "cprintf" function with %n specifier
// you augmented in the "Exercise 9" to do this job.
// hint: You can use the read_pretaddr function to retrieve
// the pointer to the function call return address;
char str[256] = {};
int nstr = 0;
// Your code here.
char *pret_addr = (char *) read_pretaddr();
uint32_t overflow_addr = (uint32_t) do_overflow;
int i;
for (i = 0; i < 4; ++i)
cprintf("%*s%n\n", pret_addr[i]&0xFF,"", pret_addr + 4 + i);
for (i = 0; i < 4; ++i)
cprintf("%*s%n\n", (overflow_addr >> (8*i)) & 0xFF,"", pret_addr + i);
}
代码是借鉴的,第一个for
循环是把地址保存在原地址+4的位置,以便正常返回;第二个for
循环是把do_overflow
的地址写在返回地址上,以便进入do_overflow
函数
覆盖地址是把地址切成四段分别覆盖,利用输出的空字符数来通过%n
赋值给地址处
Exercise 17
There is a “time” command in Linux. The command counts the program’s running time.
$time ls
a.file b.file ...
real 0m0.002s
user 0m0.001s
sys 0m0.001s
In this exercise, you need to implement a rather easy “time” command. The output of the “time” is the running time (in clocks cycles) of the command. The usage of this commandis like this: “time [command]”.
K> time kerninfo
Special kernel symbols:
_start f010000c (virt) 0010000c (phys)
etext f0101a75 (virt) 00101a75 (phys)
edata f010f320 (virt) 0010f320 (phys)
end f010f980 (virt) 0010f980 (phys)
Kernel executable memory footprint: 63KB
kerninfo cycles: 23199409
K>
Here, 23199409 is the running time of the program in cycles. As JOS has no support for time system, we could use CPU time stamp counter to measure the time.
Hint: You can refer to instruction “rdtsc” in Intel Mannual for measuring time stamp. (“rdtsc” may not be very accurate in virtual machine environment. But it’s not a problem in this exercise.)
本题让我们再实现一个time
功能
再添加一个time
的功能
static struct Command commands[] = {
{ "help", "Display this list of commands", mon_help },
{ "kerninfo", "Display information about the kernel", mon_kerninfo },
{ "backtrace", "Display backtrace info", mon_backtrace },
{ "time", "Display running cycles", mon_time },
};
我们发现mon_time
在头文件没有声明,再声明一下
#ifndef JOS_KERN_MONITOR_H
#define JOS_KERN_MONITOR_H
#ifndef JOS_KERNEL
# error "This is a JOS kernel header; user programs should not #include it"
#endif
struct Trapframe;
// Activate the kernel monitor,
// optionally providing a trap frame indicating the current state
// (NULL if none).
void monitor(struct Trapframe *tf);
// Functions implementing monitor commands.
int mon_help(int argc, char **argv, struct Trapframe *tf);
int mon_kerninfo(int argc, char **argv, struct Trapframe *tf);
int mon_backtrace(int argc, char **argv, struct Trapframe *tf);
int mon_time(int argc, char **argv, struct Trapframe *tf);
#endif // !JOS_KERN_MONITOR_H
下面来写函数
int mon_time(int argc, char **argv, struct Trapframe *tf)
{
if (argc != 2)
return -1;
uint64_t before, after;
int i;
/* search */
for (i = 0; i < ARRAY_SIZE(commands); i++)
if (strcmp(commands[i].name, argv[1]) == 0)
break;
if (i == ARRAY_SIZE(commands))
return -1;
/* run */
before = read_tsc();
(commands[i].func)(1, argv+1, tf);
after = read_tsc();
cprintf("%s cycles: %d\n", commands[i].name, after - before);
return 0;
}
这个是抄雪原老师的代码,我大概能看懂
argc
是屏幕输入的参数个数,time xxx
只有两个,否则报错;然后从现有命令查找有没有屏幕输入的第二个命令argv[1]
;然后用before
记录开始时间,运行time
要测试的功能(输入命令去掉time
,此时该功能的argc
只有1,命令字符数组开头指针变成argv+1
),再记录结束时间,减一下输出即可
测试一下
Welcome to the JOS kernel monitor!
Type 'help' for a list of commands.
K> help
help - Display this list of commands
kerninfo - Display information about the kernel
backtrace - Display backtrace info
time - Display running cycles
K> time help
help - Display this list of commands
kerninfo - Display information about the kernel
backtrace - Display backtrace info
time - Display running cycles
help cycles: 11075986
K> time kerninfo
Special kernel symbols:
_start 0010000c (phys)
entry f010000c (virt) 0010000c (phys)
etext f0101be1 (virt) 00101be1 (phys)
edata f0112300 (virt) 00112300 (phys)
end f0112940 (virt) 00112940 (phys)
Kernel executable memory footprint: 75KB
kerninfo cycles: 22246995
K> time backtrace
Stack backtrace:
eip f01008ac ebp f010fe08 args 00000001 f010fe64 00000000 f01015c3 0000000a
./inc/x86.h:247 mon_time+113
eip f0100aed ebp f010fe48 args 00000002 f010fe60 00000000 f010fea8 f0112540
kern/monitor.c:211 monitor+256
eip f010019a ebp f010feb8 args 00000000 00000400 fffffc00 00000000 00000000
kern/init.c:52 i386_init+262
eip f010003e ebp f010fff8 args 00111021 00000000 00000000 00000000 00000000
kern/entry.S:83 <unknown>+0
Overflow success
Backtrace success
backtrace cycles: 82800779
K> time time
time cycles: 23538
可以看到很符合我们的预期
time time
是因为第二个time
的argc = 1
,直接退出
Grade
下面测一下成绩
wljs@ubuntu:~/19307130152/jos-2020-spring$ make grade
make clean
make[1]: Entering directory '/home/wljs/19307130152/jos-2020-spring'
rm -rf obj .gdbinit jos.in qemu.log
make[1]: Leaving directory '/home/wljs/19307130152/jos-2020-spring'
./grade-lab1
make[1]: Entering directory '/home/wljs/19307130152/jos-2020-spring'
sh: echo: I/O error
+ as kern/entry.S
+ cc kern/entrypgdir.c
sh: echo: I/O error
+ cc kern/init.c
+ cc kern/console.c
+ cc kern/monitor.c
+ cc kern/printf.c
+ cc kern/kdebug.c
+ cc lib/printfmt.c
+ cc lib/readline.c
+ cc lib/string.c
sh: echo: I/O error
+ ld obj/kern/kernel
ld: warning: section `.bss' type changed to PROGBITS
+ as boot/boot.S
+ cc -Os boot/main.c
+ ld boot/boot
boot block is 390 bytes (max 510)
+ mk obj/kern/kernel.img
make[1]: Leaving directory '/home/wljs/19307130152/jos-2020-spring'
running JOS: (1.3s)
printf: OK
sign: OK
padding: OK
overflow warning: OK
backtrace count: OK
backtrace arguments: OK
backtrace symbols: OK
backtrace lines: OK
backtrace lines: OK
overflow success: OK
Score: 90/90
满分啦,孩子很满意
其中time
功能并没有评测
Reference
特别感谢雪老师和这位大佬!