Jos Lab 1: Booting a PC

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 - phELFHDR->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,再放入ebx0xf010fede,然后esp0xc处理cprintf,处理完后再eax入栈,再把下一个返回地址入栈,如下表

实际值寄存器
参数15
返回地址0xf010018d
原ebp0xf010fff8< - ebp
原ebx0xf010fede
函数内临时数据/下一层调用第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是因为第二个timeargc = 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

雪原老师的CSDN
YeXiaoRain的Github

特别感谢雪老师和这位大佬!

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值