[MIT6.828] LAB3总结

LAB3:
Q1.What is the purpose of having an individual handler function for each exception/interrupt? (i.e., if all exceptions/interrupts were delivered to the same handler, what feature that exists in the current implementation could not be provided?)
A1.因为目前的中断机制不能给出足够的信息来在一个函数里面识别不同的中断,除非CPU可以自动保存中断向量这样可以分辨中断的数据给函数使用。

Q2.Did you have to do anything to make the user/softint program behave correctly? The grade script expects it to produce a general protection fault (trap 13), but softint’s code says int $14. Why should this produce interrupt vector 13? What happens if the kernel actually allows softint’s int $14 instruction to invoke the kernel’s page fault handler (which is interrupt vector 14)?
A2.int 14 Page Fault并非一个用户可以直接调用的中断,在实现的时候是给与其内核级别DPL,所以用户直接访问的时候会产生保护错误,从而转到int 13 General Protection。如果内核允许用户直接调用int 14,用户调用时不会压入错误码,会导致栈错位。

Q3.The break point test case will either generate a break point exception or a general protection fault depending on how you initialized the break point entry in the IDT (i.e., your call to SETGATE from idt_init). Why? How did you need to set it in order to get the breakpoint exception to work as specified above?
A3.设置断点IDT的DPL为3即可让用户直接调用。

Q4.What do you think is the point of these mechanisms, particularly in light of what the user/softint test program does?
A4.我没看懂这问题问的什么意思,难道是让解释intel 中断权限机制?查手册就好了

E1.修改kern/pmap.c,分配并映射envs数组。
1、在i386_vm_init()内为envs分配空间,参考源文件内pages的分配方式,需要页对齐。

envs = boot_alloc(NENV*sizeof(struct Env), PGSIZE);

2、在page_init()内设置envs所在页状态为被占用。

	//env
	pages[i].pp_cnt = (ROUNDUP(PADDR(boot_freemem), PGSIZE)>>PGSHIFT) -i;
	for(i; i< (ROUNDUP(PADDR(boot_freemem), PGSIZE)>>PGSHIFT); ++i)
		pages[i].pp_ref =2;

3、在i386_vm_init()内映射envs到UENVS虚拟地址。

boot_map_segment(pgdir, UENVS, ROUNDUP(NENV*sizeof(struct Env), PGSIZE), PADDR(envs), PTE_U|PTE_P);

E2.在kern/env.c文件中,完成以下函数。
1、env_init():初始化env空闲链表env_free_list,并把envs数组全部元素都插入到链表,注意反向插入,使得第一次从链表取内容时返回envs[0]

void
env_init(void)
{
	int i;
	LIST_INIT(&env_free_list);
	memset(envs, 0, NENV*sizeof(struct Env));

	for (i = NENV-1; i&gt;=0 ; i--)
	{
		LIST_INSERT_HEAD(&env_free_list, &envs[i], env_link);
	}

}

2、env_setup_vm():初始化一个用户环境的虚拟内存空间。首先要要分配一页内存作为用户空间的页目录,其内容复制内核页目录,然后把VPT和UVPT映射这个页目录,注意要增加某些页的计数,使得这些页面可以正常回收。

static int
env_setup_vm(struct Env *e)
{
	int i, r;
	struct Page *p = NULL;
	// Allocate a page for the page directory
	if ((r = page_alloc(&p)) < 0)
		return r;
	p->pp_ref = 2;
	e->env_pgdir = page2kva(p);
	e->env_cr3 = PADDR(e->env_pgdir);
	memmove(e->env_pgdir, boot_pgdir, PGSIZE);
	pa2page(boot_pgdir[PDX(UENVS)])->pp_ref++;
	pa2page(boot_pgdir[PDX(UPAGES)])->pp_ref++;
	pa2page(boot_pgdir[PDX(KSTACK)])->pp_ref++;

	// VPT and UVPT map the env's own page table, with
	// different permissions.
	e->env_pgdir[PDX(VPT)]  = e->env_cr3 | PTE_P | PTE_W;
	e->env_pgdir[PDX(UVPT)] = e->env_cr3 | PTE_P | PTE_U;
	return 0;
}

3、segment_alloc():给用户空间分配指定大小的内存并映射到指定虚拟地址上,注意处理页对齐这样的操作,出错则panic。

static void
segment_alloc(struct Env *e, void *va, size_t len)
{
	int i;
	for(i=0,va=ROUNDDOWN(va,PGSIZE); i<len; i+= PGSIZE)
	{
		struct Page *pp;
		if (page_alloc(&pp) == -E_NO_MEM)
			panic("segment_alloc error!/n");
		page_insert(e->env_pgdir, pp, va+i, PTE_U|PTE_W|PTE_P);
		pp->pp_cnt = 1;
	}

}

4、load_icode():从指定内存中复制ELF程序到用户空间,并给用户分配栈。需要注意当前函数操作的是用户空间,所以需要使用用户的页目录,进行复制操作。不直接映射到用户空间而是复制的原因在于,这些程序处于内核空间,直接映射可能会让用户有机会操作内核其他数据,不安全。还需要注意的就是有些段的内存大小大于于ELF文件中的大小,多余的部分需要清零。

static void
load_icode(struct Env *e, uint8_t *binary, size_t size)
{
	lcr3(e->env_cr3);
	struct Elf *eh = (struct Elf *)binary;
	struct Proghdr *ph, *eph;
	if (eh->e_magic != ELF_MAGIC)
		panic("Not a elf image");

	ph = (struct Proghdr *) (binary + eh->e_phoff);
	eph = ph + eh->e_phnum;
	for (; ph < eph; ph++)
	{
		if (ph->p_type == ELF_PROG_LOAD)
		{

			segment_alloc(e, (void*)ph->p_va, ph->p_memsz);
			memmove((void *)ph->p_va, binary+ph->p_offset, ph->p_filesz);
			memset((void *)(ph->p_va+ph->p_filesz), 0, ph->p_memsz-ph->p_filesz);
		}
	}
	e->env_tf.tf_eip = eh->e_entry;
	// Now map one page for the program's initial stack at virtual address USTACKTOP - PGSIZE.
	segment_alloc(e, (void *)(USTACKTOP-PGSIZE), PGSIZE);
}

5、env_create():调用env_alloc()和load_icode()来完成新建用户环境的操作。

void
env_create(uint8_t *binary, size_t size)
{
	struct Env *e;
	if (env_alloc(&e, 0) &lt;0)
		panic(&quot;Cann't alloc user environment.&quot;);
	load_icode(e, binary, size);
}

6、env_run():运行用户环境,如果当前用户环境和想要运行的用户环境不同,需要切换页表并设置一些其他内容再运行。

void
env_run(struct Env *e)
{
	if (e != curenv)
	{

		curenv = e;
		e-&gt;env_runs ++;
		lcr3(e-&gt;env_cr3);
	}
	env_pop_tf(&e-&gt;env_tf);
}

 

E3.阅读intel手册了解异常和中断的处理

E4.修改kern/trapentry.S以及kern/trap.c,定义中断处理函数,并且设置IDT
1、根据kern/trapentry.S和inc/trap.h中的宏来定义异常和中断,算上系统调用共19个。
2、在trapentry.S中添加_alltraps:标号的代码,这是异常和中断传递的共同部分,主要内容是压入寄存器使得栈空间和struct Trapframe相匹配,载入内核数据段,给trap()函数压入%esp作为参数,调用trap,并处理如果trap切换失败返回回来的情况(平衡栈空间)

/* The TRAPHANDLER macro defines a globally-visible function for handling
 * a trap.  It pushes a trap number onto the stack, then jumps to _alltraps.
 * Use error=1 for traps where the CPU automatically pushes an error code.
 * Use error=0 for traps where the CPU doesn't push an error code.
 * user = 1 for an syscall; int = 0 for an normal trap
 */
#define TRAPHANDLER(num, name, ec, user)				/
.text;									/
	.globl name;		/* define global symbol for 'name' */	/
	.type name, @function;	/* symbol type is function */		/
	.align 2;		/* align function definition */		/
	name:;			/* function starts here */		/
	.if ec==0 ;		/* have error number?*/			/
		pushl /* The TRAPHANDLER macro defines a globally-visible function for handling
 * a trap.  It pushes a trap number onto the stack, then jumps to _alltraps.
 * Use error=1 for traps where the CPU automatically pushes an error code.
 * Use error=0 for traps where the CPU doesn\'t push an error code.
 * user = 1 for an syscall; int = 0 for an normal trap
 */
#define TRAPHANDLER(num, name, ec, user)				/
.text;									/
	.globl name;		/* define global symbol for \'name\' */	/
	.type name, @function;	/* symbol type is function */		/
	.align 2;		/* align function definition */		/
	name:;			/* function starts here */		/
	.if ec==0 ;		/* have error number?*/			/
		pushl /* The TRAPHANDLER macro defines a globally-visible function for handling
 * a trap.  It pushes a trap number onto the stack, then jumps to _alltraps.
 * Use error=1 for traps where the CPU automatically pushes an error code.
 * Use error=0 for traps where the CPU doesn't push an error code.
 * user = 1 for an syscall; int = 0 for an normal trap
 */
#define TRAPHANDLER(num, name, ec, user)				/
.text;									/
	.globl name;		/* define global symbol for 'name' */	/
	.type name, @function;	/* symbol type is function */		/
	.align 2;		/* align function definition */		/
	name:;			/* function starts here */		/
	.if ec==0 ;		/* have error number?*/			/
		pushl /* The TRAPHANDLER macro defines a globally-visible function for handling
 * a trap.  It pushes a trap number onto the stack, then jumps to _alltraps.
 * Use error=1 for traps where the CPU automatically pushes an error code.
 * Use error=0 for traps where the CPU doesn't push an error code.
 * user = 1 for an syscall; int = 0 for an normal trap
 */
#define TRAPHANDLER(num, name, ec, user)				/
.text;									/
	.globl name;		/* define global symbol for 'name' */	/
	.type name, @function;	/* symbol type is function */		/
	.align 2;		/* align function definition */		/
	name:;			/* function starts here */		/
	.if ec==0 ;		/* have error number?*/			/
		pushl $0 ;						/
	.endif;								/
	pushl $(num);							/
	jmp _alltraps;							/
.data;									/
	.long num , name, user
.data
	.globl entry_data
	entry_data:
.text
	TRAPHANDLER(T_DIVIDE, divide_entry, 0, 0);
	TRAPHANDLER(T_DEBUG, debug_entry, 0, 0);
	TRAPHANDLER(T_NMI, nmi_entry, 0, 0);
	TRAPHANDLER(T_BRKPT, brkpt_entry, 0, 1);
	TRAPHANDLER(T_OFLOW, oflow_entry, 0, 0);
	TRAPHANDLER(T_BOUND, bound_entry, 0, 0);
	TRAPHANDLER(T_ILLOP, illop_entry, 0, 0);
	TRAPHANDLER(T_DEVICE, device_entry, 0, 0);
	TRAPHANDLER(T_DBLFLT, dblflt_entry, 1, 0);
	TRAPHANDLER(T_TSS, tts_entry, 1, 0);
	TRAPHANDLER(T_SEGNP, segnp_entry, 1, 0);
	TRAPHANDLER(T_STACK, stack_entry, 1, 0);
	TRAPHANDLER(T_GPFLT, gpflt_entry, 1, 0);
	TRAPHANDLER(T_PGFLT,pgflt_entry, 1, 0);
	TRAPHANDLER(T_FPERR, fperr_entry, 0, 0);
	TRAPHANDLER(T_ALIGN, align_entry, 1, 0);
	TRAPHANDLER(T_MCHK, mchk_entry, 0, 0);
	TRAPHANDLER(T_SIMDERR, simderr_entry, 0, 0);
	TRAPHANDLER(T_SYSCALL, syscall_entry, 0, 1);
.data
	.long 0,0,0	/*interupt end identify*/
/*
 * Lab 3: Your code here for _alltraps
 */
.text
.func _alltraps
_alltraps:
	/*make Trapframe*/
	pushl %ds;
	pushl %es;
	pushal;
	/*load kernel data segment*/
	movl $GD_KD, %eax;
	movw %ax, %ds;
	movw %ax, %es;
	/*push trap argument pointer to Trapframe*/
	pushl %esp;
	call trap;
	/*trap fall*/
	popal;
	popl %es;
	popl %ds;
	addl $8, %esp;
	iret ;						/
	.endif;								/
	pushl $(num);							/
	jmp _alltraps;							/
.data;									/
	.long num , name, user
.data
	.globl entry_data
	entry_data:
.text
	TRAPHANDLER(T_DIVIDE, divide_entry, 0, 0);
	TRAPHANDLER(T_DEBUG, debug_entry, 0, 0);
	TRAPHANDLER(T_NMI, nmi_entry, 0, 0);
	TRAPHANDLER(T_BRKPT, brkpt_entry, 0, 1);
	TRAPHANDLER(T_OFLOW, oflow_entry, 0, 0);
	TRAPHANDLER(T_BOUND, bound_entry, 0, 0);
	TRAPHANDLER(T_ILLOP, illop_entry, 0, 0);
	TRAPHANDLER(T_DEVICE, device_entry, 0, 0);
	TRAPHANDLER(T_DBLFLT, dblflt_entry, 1, 0);
	TRAPHANDLER(T_TSS, tts_entry, 1, 0);
	TRAPHANDLER(T_SEGNP, segnp_entry, 1, 0);
	TRAPHANDLER(T_STACK, stack_entry, 1, 0);
	TRAPHANDLER(T_GPFLT, gpflt_entry, 1, 0);
	TRAPHANDLER(T_PGFLT,pgflt_entry, 1, 0);
	TRAPHANDLER(T_FPERR, fperr_entry, 0, 0);
	TRAPHANDLER(T_ALIGN, align_entry, 1, 0);
	TRAPHANDLER(T_MCHK, mchk_entry, 0, 0);
	TRAPHANDLER(T_SIMDERR, simderr_entry, 0, 0);
	TRAPHANDLER(T_SYSCALL, syscall_entry, 0, 1);
.data
	.long 0,0,0	/*interupt end identify*/
/*
 * Lab 3: Your code here for _alltraps
 */
.text
.func _alltraps
_alltraps:
	/*make Trapframe*/
	pushl %ds;
	pushl %es;
	pushal;
	/*load kernel data segment*/
	movl $GD_KD, %eax;
	movw %ax, %ds;
	movw %ax, %es;
	/*push trap argument pointer to Trapframe*/
	pushl %esp;
	call trap;
	/*trap fall*/
	popal;
	popl %es;
	popl %ds;
	addl , %esp;
	iret ;						/
	.endif;								/
	pushl $(num);							/
	jmp _alltraps;							/
.data;									/
	.long num , name, user
.data
	.globl entry_data
	entry_data:
.text
	TRAPHANDLER(T_DIVIDE, divide_entry, 0, 0);
	TRAPHANDLER(T_DEBUG, debug_entry, 0, 0);
	TRAPHANDLER(T_NMI, nmi_entry, 0, 0);
	TRAPHANDLER(T_BRKPT, brkpt_entry, 0, 1);
	TRAPHANDLER(T_OFLOW, oflow_entry, 0, 0);
	TRAPHANDLER(T_BOUND, bound_entry, 0, 0);
	TRAPHANDLER(T_ILLOP, illop_entry, 0, 0);
	TRAPHANDLER(T_DEVICE, device_entry, 0, 0);
	TRAPHANDLER(T_DBLFLT, dblflt_entry, 1, 0);
	TRAPHANDLER(T_TSS, tts_entry, 1, 0);
	TRAPHANDLER(T_SEGNP, segnp_entry, 1, 0);
	TRAPHANDLER(T_STACK, stack_entry, 1, 0);
	TRAPHANDLER(T_GPFLT, gpflt_entry, 1, 0);
	TRAPHANDLER(T_PGFLT,pgflt_entry, 1, 0);
	TRAPHANDLER(T_FPERR, fperr_entry, 0, 0);
	TRAPHANDLER(T_ALIGN, align_entry, 1, 0);
	TRAPHANDLER(T_MCHK, mchk_entry, 0, 0);
	TRAPHANDLER(T_SIMDERR, simderr_entry, 0, 0);
	TRAPHANDLER(T_SYSCALL, syscall_entry, 0, 1);
.data
	.long 0,0,0	/*interupt end identify*/
/*
 * Lab 3: Your code here for _alltraps
 */
.text
.func _alltraps
_alltraps:
	/*make Trapframe*/
	pushl %ds;
	pushl %es;
	pushal;
	/*load kernel data segment*/
	movl $GD_KD, %eax;
	movw %ax, %ds;
	movw %ax, %es;
	/*push trap argument pointer to Trapframe*/
	pushl %esp;
	call trap;
	/*trap fall*/
	popal;
	popl %es;
	popl %ds;
	addl , %esp;
	iret ;						/
	.endif;								/
	pushl $(num);							/
	jmp _alltraps;							/
.data;									/
	.long num , name, user
.data
	.globl entry_data
	entry_data:
.text
	TRAPHANDLER(T_DIVIDE, divide_entry, 0, 0);
	TRAPHANDLER(T_DEBUG, debug_entry, 0, 0);
	TRAPHANDLER(T_NMI, nmi_entry, 0, 0);
	TRAPHANDLER(T_BRKPT, brkpt_entry, 0, 1);
	TRAPHANDLER(T_OFLOW, oflow_entry, 0, 0);
	TRAPHANDLER(T_BOUND, bound_entry, 0, 0);
	TRAPHANDLER(T_ILLOP, illop_entry, 0, 0);
	TRAPHANDLER(T_DEVICE, device_entry, 0, 0);
	TRAPHANDLER(T_DBLFLT, dblflt_entry, 1, 0);
	TRAPHANDLER(T_TSS, tts_entry, 1, 0);
	TRAPHANDLER(T_SEGNP, segnp_entry, 1, 0);
	TRAPHANDLER(T_STACK, stack_entry, 1, 0);
	TRAPHANDLER(T_GPFLT, gpflt_entry, 1, 0);
	TRAPHANDLER(T_PGFLT,pgflt_entry, 1, 0);
	TRAPHANDLER(T_FPERR, fperr_entry, 0, 0);
	TRAPHANDLER(T_ALIGN, align_entry, 1, 0);
	TRAPHANDLER(T_MCHK, mchk_entry, 0, 0);
	TRAPHANDLER(T_SIMDERR, simderr_entry, 0, 0);
	TRAPHANDLER(T_SYSCALL, syscall_entry, 0, 1);
.data
	.long 0,0,0	/*interupt end identify*/
/*
 * Lab 3: Your code here for _alltraps
 */
.text
.func _alltraps
_alltraps:
	/*make Trapframe*/
	pushl %ds;
	pushl %es;
	pushal;
	/*load kernel data segment*/
	movl $GD_KD, %eax;
	movw %ax, %ds;
	movw %ax, %es;
	/*push trap argument pointer to Trapframe*/
	pushl %esp;
	call trap;
	/*trap fall*/
	popal;
	popl %es;
	popl %ds;
	addl , %esp;
	iret

3、修改kern/trap.c的idt_init()函数来初始化IDT数组,使用SETGATE宏处理不同的异常/中断入口生成IDT。

void
idt_init(void)
{
	extern struct Segdesc gdt[];
	extern long entry_data[][3];
	int i;
	// LAB 3: Your code here.
	for(i=0; entry_data[i][1]!=0; i++)
	{
		SETGATE(idt[entry_data[i][0]], entry_data[i][2], GD_KT, entry_data[i][1], entry_data[i][2]*3);
	}
	// Setup a TSS so that we get the right stack
	// when we trap to the kernel.
	ts.ts_esp0 = KSTACKTOP;
	ts.ts_ss0 = GD_KD;
	// Initialize the TSS field of the gdt.
	gdt[GD_TSS >> 3] = SEG16(STS_T32A, (uint32_t) (&ts),
					sizeof(struct Taskstate), 0);
	gdt[GD_TSS >> 3].sd_s = 0;
	// Load the TSS
	ltr(GD_TSS);
	// Load the IDT
	asm volatile("lidt idt_pd");
}

 

E5.修改kern/trap.c中trap_dispatch()函数,把页错误分配给page_fault_handler()函数处理
1、只需要根据Trapframe中的trap类型判断是否0x14(页错误),然后调用page_fault_handler即可。
2、这里我使用了一个trap_hdls数组来存储每个中断的处理函数,中断号为下标直接调用,就不用每个中断写一个case或者if那么麻烦了。

//define real trap handler
typedef void (*Traphandler_t)(struct Trapframe *);
void
trap(struct Trapframe *tf)
{
	static Traphandler_t trap_hdls[256]=
	{
		dt_hdl,	monitor,dt_hdl,	monitor,	//0x0 - 0x3
		dt_hdl,	dt_hdl,	dt_hdl,	dt_hdl,		//0x4 - 0x7
		dt_hdl,	dt_hdl,	dt_hdl,	dt_hdl,		//0x8 - 0xb
		dt_hdl,	dt_hdl,	pf_hdl,	dt_hdl,		//0xc -	0xf
		dt_hdl,	dt_hdl,	dt_hdl,	dt_hdl,		//0x10- 0x13
		dt_hdl,	dt_hdl,	dt_hdl,	dt_hdl,		//0x14- 0x17
		dt_hdl,	dt_hdl,	dt_hdl,	dt_hdl,		//0x18- 0x1b
		dt_hdl,	dt_hdl,	dt_hdl,	dt_hdl,		//0x1c- 0x1f
		dt_hdl,	dt_hdl,	dt_hdl,	dt_hdl,		//0x20- 0x23
		dt_hdl,	dt_hdl,	dt_hdl,	dt_hdl,		//0x24- 0x27
		dt_hdl,	dt_hdl,	dt_hdl,	dt_hdl,		//0x29- 0x2b
		dt_hdl,	dt_hdl,	dt_hdl,	dt_hdl,		//0x2c- 0x2f
		syscal, dt_hdl,	dt_hdl,	dt_hdl,		//0x30- 0x33

	};
	// The environment may have set DF and some versions
	// of GCC rely on DF being clear
	asm volatile(&quot;cld&quot; ::: &quot;cc&quot;);
	// Check that interrupts are disabled.  If this assertion
	// fails, DO NOT be tempted to fix it by inserting a &quot;cli&quot; in
	// the interrupt path.
	assert(!(read_eflags() & FL_IF));
	//cprintf(&quot;/nIncoming TRAP frame at %p/n&quot;, tf);
	if ((tf-&gt;tf_cs & 3) == 3) {
		// Trapped from user mode.
		// Copy trap frame (which is currently on the stack)
		// into 'curenv-&gt;env_tf', so that running the environment
		// will restart at the trap point.
		assert(curenv);
		curenv-&gt;env_tf = *tf;
		// The trapframe on the stack should be ignored from here on.
		tf = &curenv-&gt;env_tf;
	}

	// Dispatch based on what type of trap occurred
	//trap_dispatch(tf);
	trap_hdls[tf-&gt;tf_trapno&gt;0x30?0x31:tf-&gt;tf_trapno](tf);
	// Return to the current environment, which should be runnable.
	assert(curenv && curenv-&gt;env_status == ENV_RUNNABLE);
	env_run(curenv);
}

 

E6.修改kern/trap.c中trap_dispatch()函数,把断点分配给monitor()函数处理

E7.添加系统调用处理机制
1、这里为了跟其他中断保持一致,我把syscall()函数原型改成跟其他中断处理函数一样的类型,可以直接在trap_hdls数组中直接定位并调用。
2、理解Trapframe中不同寄存器在系统调用中的作用,有些寄存器是作为参数传递过来的,要分别对待,返回值放到tf_eax中。
3、按照以上指示把kern/syscall.c 中的所有系统调用都在syscall()中注册。

void
syscal(struct Trapframe *tf)
{
	struct PushRegs *regs = & tf-&gt;tf_regs;
	switch (regs-&gt;reg_eax)
	{
		case SYS_cputs :	sys_cputs((const char *)regs-&gt;reg_edx, (size_t)regs-&gt;reg_ecx);break;
		case SYS_cgetc :	regs-&gt;reg_eax = sys_cgetc(); break;
		case SYS_getenvid :	regs-&gt;reg_eax = sys_getenvid(); break;
		case SYS_env_destroy :	regs-&gt;reg_eax = sys_env_destroy(regs-&gt;reg_edx); break;
		default:		regs-&gt;reg_eax = -E_INVAL;  break;
	}
}

E8.修改用户库文件,使得用户的hello world程序能正常运行
1、修改lib/libmain.c文件,给env变量赋值,这里需要调用sys_getenvid()系统调用来返回一个用户环境ID,把这个ID转化成envs下标,该下表所代表的元素就是env的所指向的内容。

void
libmain(int argc, char **argv)
{
	// set env to point at our env structure in envs[].
	env = envs+ENVX(sys_getenvid());
	// save the name of the program so that panic() can use it
	if (argc > 0)
		binaryname = argv[0];
	// call user main routine
	umain(argc, argv);
	// exit gracefully
	exit();
}

 

E9.
1、修改kern/trap.c 中页错误处理函数,在内核(tf->tf_cs==GD_KT)发生缺页中断时,painc掉。

//page fault handler
void pf_hdl(struct Trapframe *tf)
{
	uint32_t fault_va;
	// Read processor's CR2 register to find the faulting address
	fault_va = rcr2();
	// Handle kernel-mode page faults.
	if (tf->tf_cs == GD_KT)
		panic("Page fault in kernel");
	// We've already handled kernel-mode exceptions, so if we get here,
	// the page fault happened in user mode.
	// Destroy the environment that caused the fault.
	cprintf("[%08x] user fault va %08x ip %08x/n",
		curenv->env_id, fault_va, tf->tf_eip);
	print_trapframe(tf);
	env_destroy(curenv);
}

2、在kern/pmap.c中实现user_mem_check()函数,用来检测用户对一块内存是否具有指定的权限。用户对高于ULIM的内存没有任何权限。注意页对齐的情况。

int
user_mem_check(struct Env *env, const void *va, size_t len, int perm)
{
	//check ULIM
	if ((uint32_t)va >= ULIM)
	{	user_mem_check_addr = (uintptr_t)va;
		return -E_FAULT;
	}
	//check priority
	const void *eva;
	pte_t *ppte;
	for (eva=va+len,ppte=NULL; va<eva; va=ROUNDDOWN(va,PGSIZE)+PGSIZE,ppte++)
	{
		if(PGOFF(ppte)==0)//change page table?
			ppte = pgdir_walk(env->env_pgdir, va, 0);
		if((ppte == NULL) || ((*ppte|perm) != *ppte))
		{
			user_mem_check_addr = (uintptr_t)va;
			return -E_FAULT;//no map or no priority
		}
	}
	return 0;
}

3、修改kern/syscall.c ,对于用户传入内核的指针(sys_cputs()),我们要通过user_mem_assert()函数检查用户对这块内存有没有特定权限。

static void
sys_cputs(const char *s, size_t len)
{
	// Check that the user has permission to read memory [s, s+len).
	// Destroy the environment if not.
	user_mem_assert(curenv, s, len, PTE_U);

	// Print the string supplied by the user.
	cprintf("%.*s", len, s);
}

4、修改kern/kdebug.c,对用户空间的数据使用user_mem_check()函数检查当前用户空间是否对其有PTE_U权限。

	// Find the relevant set of stabs
	if (addr >= ULIM) {
		stabs = __STAB_BEGIN__;
		stab_end = __STAB_END__;
		stabstr = __STABSTR_BEGIN__;
		stabstr_end = __STABSTR_END__;
	} else {
		// The user-application linker script, user/user.ld,
		// puts information about the application's stabs (equivalent
		// to __STAB_BEGIN__, __STAB_END__, __STABSTR_BEGIN__, and
		// __STABSTR_END__) in a structure located at virtual address
		// USTABDATA.
		const struct UserStabData *usd = (const struct UserStabData *) USTABDATA;
		// Make sure this memory is valid.
		// Return -1 if it is not.  Hint: Call user_mem_check.
		// LAB 3: Your code here.
		if (user_mem_check(curenv, usd, sizeof(*usd),PTE_U) <0 )
			return -1;
		stabs = usd->stabs;
		stab_end = usd->stab_end;
		stabstr = usd->stabstr;
		stabstr_end = usd->stabstr_end;
		// Make sure the STABS and string table memory is valid.
		// LAB 3: Your code here.a
		if (user_mem_check(curenv, stabs, stab_end-stabs,PTE_U) <0 ||
			user_mem_check(curenv, stabstr, stabstr_end-stabstr,PTE_U) <0)
				return -1;
	}

5、查看breakpoint程序函数调用栈出现页错误原因是:用户栈显示到了最顶端,再往上是没有映射的空间,访问这些空间会导致页错误。

E10.因为E9的第3步所描述的原因,所以这个邪恶的程序没有实现它的企图,被绳之以法。

C1.整理kern/trapentry.S和kern/trap.c中的代码,改变这种有坏代码味道(很多看起来差不多的代码,这意味着我们要重构了)的状态。
1、在trapentry.S中修改TRAPHANDLER,利用.text 和.data伪指令定义一块数据,这块数据保存了异常入口点以及它的属性。
2、在trap.c的idt_init()函数中,利用循环来读取trapentry.S中定义的数据区,这样就减少了idt_init()中的代码重覆数内容。这体现了Unix一个很重要的设计理念:“Don’t Repeat Youself”,同样的代码写一遍就可以了,其他都是引用这块代码。这样可以有效防止程序不一致(有的地方修改了,有的地方还没有修改)的情况。

C2.修改kern/monitor.c,给monitor加入单步调试,反汇编等功能。
加入了以下功能:
1、反汇编,移植了bochs内置的反汇编代码,已经作成静态库放到群共享中,其中遇到最大的问题就是C/C++混合编程(用extern "C"搞定)以及标准库依赖(把这些依赖修改为在jos的库函数即可,主要是printf系列,assert断言,可变传参等)(见disassemble()和u()函数)。

static int
disassemble(void *va, uint32_t cs, size_t cnt, int view)
{
	unsigned char in[16];
	char out[128];
	int ilen ,i;
	uint32_t pa , eip;
	pte_t *ppte;
	void *cva = va;
	for(cnt; cnt&gt;0; cnt--)
	{
		if ((page_lookup(curenv-&gt;env_pgdir, cva, &ppte) ==NULL)
			||!ISPTE_P(*ppte) || !ISPTE_U(*ppte))
		{
			cprintf(&quot;Memory read error!/n&quot;);
			return 0;
		}
		pa = PTE_ADDR(*ppte)|PGOFF(cva);
		GET_BRK(&eip);
		if (eip &gt;=(uint32_t)cva && eip&lt;(uint32_t)cva +16)
		{
			DISABLE_BRK(&eip);
			memmove(in, cva, 16);
			ENABLE_BRK(&eip);
		}
		else
			memmove(in,cva,16);
		ilen = disasm(0,(uint32_t)cva, in, out);
		if (view)
		{
			cprintf(&quot;[%08x] %04x:%08x/t%s/t/t; &quot;, pa, cs, cva, out);
			for (i=0; i&lt;ilen; i++)
				cprintf(&quot;%02x&quot;,in[i]);
			cprintf(&quot;/n&quot;);
		}
		cva += ilen;
	}
	return cva-va;
}

int u(int argc, char **argv, struct Trapframe *tf)
{

	char *endptr;
	uint32_t cnt,begin;
	if (tf == NULL )
		return 0;

	if (argc == 1)
	{
		cnt = 1;
		begin = tf-&gt;tf_eip;

	}
	else if (argc ==2)
	{
		begin = tf-&gt;tf_eip;
		cnt = strtol(argv[1], &endptr, 0);
		TESTERR( *endptr != '/0');
	}
	else if (argc == 3)
	{
		cnt = strtol(argv[1], &endptr, 0);
		TESTERR( *endptr != '/0');
		begin = strtol(argv[2], &endptr, 0);
		TESTERR( *endptr != '/0');
	}
	else
		goto ERR;
	return  disassemble((void *)begin, tf-&gt;tf_cs, cnt, 1);
	LERR;
}

2、单步,使用标志寄存器中的trap位来使得每运行一条指令发生一次int 1中断(见 s()函数)

int s(int argc, char **argv, struct Trapframe *tf)
{
	extern uint32_t icnt;
	char *endptr;
	if (tf == NULL)
		return 0;
	if (argc == 1)
	{
		icnt =1;
	}
	else if (argc == 2)
	{
		icnt = strtol(argv[1], &endptr, 0);
		TESTERR( *endptr != '/0');

	}
	else goto ERR;

	tf-&gt;tf_eflags |= 0x100;

	return -1;

	LERR;
}

3、继续,把trap标志位清零即可。(见c()函数)

int c(int argc, char **argv, struct Trapframe *tf)
{
	if (tf == NULL )
		return 0;
	tf-&gt;tf_eflags &= ~0x100;
	return -1;
}

4、单步运行N条指令,原理同单步,但是要用一个静态变量作为指令计数器,如果到了指定数目就会停下来让用户指示下一步操作(见s(),treat_trap())

5、stepover, 就是把call当成一条指令计数的单步,原理同单步,但是需要增加一个层次变量nextflag 用来标示进入了多少层call,遇到ret就会减少一个层次,当nextflag为1的时候才会修改指令计数器(见n(),tread_trap())。

int n(int argc, char **argv, struct Trapframe *tf)
{
	extern uint32_t nextflag;
	if (s(argc, argv, tf) == 0)
		return 0;
	nextflag = 1;
	//next op is call?
	if (TEST_OP(tf->tf_eip, OP_CALL_REL)||TEST_OP(tf->tf_eip, OP_CALL_ABS))
		nextflag ++;
	return -1;

}

6、断点,原理是用int3指令替换断点位置的代码。说起来比较简单,实际实现起来是很繁琐的(见b(),break_point(),treat_trap())。

#define ENABLE_BRK(peip)	break_point((peip), 1)
#define DISABLE_BRK(peip)	break_point((peip), 0)
#define GET_BRK(peip)		break_point((peip), 2)
static inline void
break_point(uint32_t *peip, int mode)
{
	static uint8_t itmp;
	static uint32_t eiptmp=0;
	//cprintf(&quot;it=%x,eipt=%x,mode=%d/n&quot;,itmp,eiptmp,mode);
	//if (eiptmp != 0)
	//	cprintf(&quot;*eipt = %02x/n&quot;,*((uint8_t *)eiptmp));
	if (mode == 1)
	{
		if (eiptmp !=0)
		{
			*((uint8_t *)eiptmp) = itmp;//resume

		}
		eiptmp = *peip;	//set
		itmp =*((uint8_t  *)eiptmp);
		 *((uint8_t *)eiptmp)= 0xcc;

	}
	else if(mode == 0)
	{
		*((uint8_t  *)eiptmp) = itmp;
		*peip = eiptmp;
		eiptmp = 0;
	}
	else
		*peip = eiptmp;
}
int b(int argc, char **argv, struct Trapframe *tf)
{
	char *endptr;
	uint32_t eip;
	TESTERR(argc!=2)
	eip = strtol(argv[1], &endptr, 0);
	TESTERR( *endptr != '/0');
	if (user_mem_check(curenv, (void *)eip, 1, PTE_U|PTE_P)&lt;0)
	{
		cprintf(&quot;Memory check error!/n&quot;);
		return 0;
	}
	ENABLE_BRK(&eip);
	return 0;
	LERR;
}

(1)首先要判断用户对断点位置内存是否有权限,有则保存断点指令,并替换为int3;
(2)修改反汇编函数,使得在断点所在反汇编区域的时显示的是原指令,而不是int3;
(3)处理当trap位和用户设置的断点相遇时,暂停两次的情况;
(4)当运行到断点位置时要临时禁用断点,并开启trap标志位,使得下次指令执行完毕后中断;
(5)恢复上次执行指令的时候被临时禁用的中断。
在实现上述功能时使用了很多静态变量,这在会造成程序的耦合度增大,结构变得混乱。这些静态变量可以改进成用户环境变量,每个用户程序都有这些调试专用的变量,以及对应函数来操作这些变量,这样结构就变得清晰,多任务的时候也不会出错。不过现在这样还够用,等需要的时候再修改吧。

//static vars for debug/breakpoint trap
static uint32_t	icnt=1; //step instruction count
static uint32_t	eflag=0;//last eflag for resume a temp disabled breakpoint
static uint32_t	eip=0;//temp disabled breakpoint eip;
static uint32_t	nextflag =0;// step over flag
int
treat_trap(struct Trapframe *tf)
{
	extern uint32_t eflag, eip ,icnt, nextflag;
	// resume a temp disabled breakpoint
	if (eip != 0 )
	{
		ENABLE_BRK(&eip);
		tf-&gt;tf_eflags = eip;
		eip = 0;
		if ((eflag &0x100) ==0)
			return -1;
	}
	// check breakpoint
	GET_BRK(&eip);
	if ( (eip == tf-&gt;tf_eip))// when current is debug trap & next op is user brk
	{
		DISABLE_BRK(&eip);//disable it temp ,prevent twice puase on one brkpt
	}
	else if((eip == tf-&gt;tf_eip-1)) // when current is user brk
	{
		DISABLE_BRK(&eip); // disable it temporarily
		tf-&gt;tf_eip--;// rerun current instruction
	}
	else
	{
		eip = 0;
	}
	//treat debug trap /step
	if(icnt ==1 && nextflag &lt;=1)//catch it
	{
		nextflag = 0;
		disassemble((void *)tf-&gt;tf_eip, tf-&gt;tf_cs, 1, 1);
		return 0;
	}
	else
	{	//process steps
		if (nextflag) //is step over ?
		{
			if (TEST_OP(tf-&gt;tf_eip, OP_CALL_REL)||TEST_OP(tf-&gt;tf_eip, OP_CALL_ABS)) //op call?
				nextflag++;
			else if (TEST_OP(tf-&gt;tf_eip, OP_RET)) //op ret ?
			{
				if (nextflag == 1)
					icnt --;
				else
					nextflag--;
			}
			else //other
			{
				if (nextflag == 1)
					icnt --;
			}
		}
		else
			icnt --;
		//cprintf(&quot;icnt=%d,nextflag=%d/n&quot;,icnt, nextflag);
		return -1;
	}
}
void
monitor(struct Trapframe *tf)
{
	char *buf;
	extern uint32_t eflag, eip;

	if (tf != NULL) // is a trap?
	{
		if (treat_trap(tf) &lt;0)
			return;
	}
	else
	{
		cprintf(&quot;Welcome to the JOS kernel monitor!/n&quot;);
		cprintf(&quot;Type 'help' for a list of commands./n&quot;);
	}

	while (1) {
		buf = readline(&quot;K&gt; &quot;);
		if (buf != NULL)
			if (runcmd(buf, tf) &lt; 0)
				break;
	}

	if(eip) // save eflag for resume breakpoint
	{
		eflag = tf-&gt;tf_eflags;
		tf-&gt;tf_eflags |= 0x100;
	}
}

C3.使用sysenter/sysexit代替int/iret实现系统调用。
阿,这个太麻烦,而且以后的实验也用不到,偷偷懒,不做了啦~

This entry was posted in 操作系统 and tagged . Bookmark the permalink.

11 Responses to [MIT6.828] LAB3总结

  1. Hi,楼主!
    我最近也在做MIT的这个6.828,在lab3跑hello那个test的时候,访问env_id报page fault的错误,单步调试envs数组物理地址分配没有问题,env_pgdir的map也没有问题,cr3寄存器内容也对,但用户空间就是不能访问UENVS。不知楼主当时做的时候有没有碰到类似问题,有没有什么建议?

    • davelv says:

      回复 shmilydx_2009:
      envs的物理地址是在kern/pmap.c里面利用boot_alloc()分配的;用boot_map_segment()映射到UENVS,权限是PTE_U|PTE_P;
      在kern/env.c里用env_init()初始化envs。
      在lib/entry.S里面定义用户空间符号(当指针用)envs值为UENVS
      在lib/libmain.c里env = envs+ENVX(sys_getenvid());
      如果上面检查都没有错误的话,那非常可能是sys_getenvid()这个系统调用出错了,检查下系统调用的每个步骤吧。

  2. 52computer says:

    在env_setup_vm()中,e->env_pgdir = page2kva(p);
    虽然我知道下面这个写法是错的,但很好奇为什么不是:
    e->env_pgdir = pgdir_walk(kern_pgdir, page2kva(p), true);

    调用page2kva(p);直接从空闲链表分配,这样的改动是不会体现到内核页表上的,除非内核主动遍历空闲链表。
    内核页表只管理内核使用的内存,不用管用户的吗?
    我一直理解内核页表管理了整个系统的物理内存的使用情况。
    小白伤不起。。。。有答案都看不明白。。。。。

    • davelv says:

      在我的记忆里,我们的内核页表只是在系统初始化的时候用到,是属于模板。
      初始化完毕之后,生成的每个用户进程有自己的页表,其模板就是内核页表,但是不同进程的用户页映射部分是不同的。
      你不能更改模板,否则就全乱套了。

  3. 52computer says:

    感慨一下D大太牛逼了,我现在都是一边看答案一边抄的,D大是原创,这差距啊。。。

回复 davelv 取消回复

您的电子邮箱地址不会被公开。 必填项已用*标注