6.828 Lab3 PartB Writeup
Page Faults, Breakpoints Exceptions, and System Calls
Exercise 5,6,7
在trap_dispath中添加代码,能够传到相应的处理函数执行。
static void
trap_dispatch(struct Trapframe *tf)
{
if (tf->tf_trapno == T_PGFLT) {
page_fault_handler(tf);
return ;
}
if (tf->tf_trapno == T_BRKPT) {
break_point_handler(tf);
return ;
}
if (tf->tf_trapno == T_SYSCALL) {
system_call_handler(tf);
return ;
}
// Unexpected trap: The user process or the kernel has a bug.
print_trapframe(tf);
if (tf->tf_cs == GD_KT)
panic("unhandled trap in kernel");
else {
env_destroy(curenv);
return;
}
}
添加system_call 参数传递代码。注意系统调用对应的中断的 DPL = 3。
void
system_call_handler(struct Trapframe *tf)
{
uint32_t syscallno, a1, a2, a3, a4, a5;
syscallno = (tf->tf_regs).reg_eax;
a1 = (tf->tf_regs).reg_edx;
a2 = (tf->tf_regs).reg_ecx;
a3 = (tf->tf_regs).reg_ebx;
a4 = (tf->tf_regs).reg_edi;
a5 = (tf->tf_regs).reg_esi;
(tf->tf_regs).reg_eax = (uint32_t)syscall(syscallno, a1, a2, a3, a4, a5);
}
添加dispatch system_call 的代码:
// Dispatches to the correct kernel function, passing the arguments.
int32_t
syscall(uint32_t syscallno, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5)
{
uint32_t ret = 0;
switch (syscallno) {
case SYS_cputs :
sys_cputs((char *)a1, (size_t)a2);
break;
case SYS_cgetc :
ret = (uint32_t)sys_cgetc();
break;
case SYS_getenvid :
ret = (envid_t)sys_getenvid();
break;
case SYS_env_destroy :
ret = (uint32_t)sys_env_destroy((envid_t)a1);
break;
default :
ret = -E_INVAL;
break;
}
return ret;
}
Exercise 8,9,10
在libmain.c 中添加代码,初始化 thisenv,调用sys_getenvid()获取当前运行进程的envid,然后根据ENVX(envid)得出当前env在envs数组中的偏移量,获取thisenv。 进程退出的时候,exit() 函数调用系统调用sys_env_destory(0), 0 表示当前的env。
void
libmain(int argc, char **argv)
{
// set thisenv to point at our Env structure in envs[].
// LAB 3: Your code here.
envid_t envid = sys_getenvid();
thisenv = &(envs[ENVX(envid)]);
// 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();
}
在lib/entry.S 中已经初始化了envs,指向系统的envs数组,所以envs可以直接 extern 之后使用。
.data
// Define the global symbols 'envs', 'pages', 'uvpt', and 'uvpd'
// so that they can be used in C as if they were ordinary global arrays.
.globl envs
.set envs, UENVS
.globl pages
.set pages, UPAGES
.globl uvpt
.set uvpt, UVPT
.globl uvpd
.set uvpd, (UVPT+(UVPT>>12)*4)
处理PageFault。对于这个中断,如果发生在kernel处理自己的数据结构的时候,那么说明kernel bug了,这时候就应该把kernel panic 掉。如果发生在kernel 去引用user的一个指针的时候(一个指针就是一个虚拟地址),如kernel 访问 user 的一个地址,出现了PGFLT,那么就需要把 user 的这个env panic 掉。因为kernel有更多的权限,所以在处理PGFLT的时候,应该小心。 为了在kernel态区分这两种PGFLT,user_mem_check()这个函数就是判断这个env是否有访问一个虚拟地址va的权限的。
int
user_mem_check(struct Env *env, const void *va, size_t len, int perm)
{
void * va_end = (void*)ROUNDUP(va + len, PGSIZE);
pte_t * pte;
// 注意这里 第一次的va不用ROUND,之后的都需要
// 如果 va = va + PGSIZE,那么在 buggyhello2 就过不了
// 囧,虽然我觉得功能上都一样
for ( ; va < va_end ; va = ROUNDUP(va + PGSIZE, PGSIZE)) {
pte = pgdir_walk(env->env_pgdir, va, 0);
if (!pte || ( (*pte & (perm | PTE_P)) != (perm | PTE_P) )) {
user_mem_check_addr = (uintptr_t)va;
return -E_FAULT;
}
}
return 0;
}
在PGFLT中添加判断,如果是kernel在处理自己的数据结构的时候产生的,那么就直接panic掉。因为tf->cs保存的是中断之前的cs,所以通过判断cs的CPL就能够判断出是user的还是kernel的。通过error code 的 U/S 字段也一样。
void
page_fault_handler(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.
// LAB 3: Your code here.
if ((tf->tf_cs & 3 ) == 0){
panic("page_fault_halder : page fault in kernel mode.\n");
return ;
}
// 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);
}
user发出系统调用,kernel在访问user的地址空间的时候都需要做检查,在系统调用中添加mem检查,下面的const char *s 就是用户的一个地址空间。
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, (void*)s, len, PTE_W | PTE_U);
// Print the string supplied by the user.
cprintf("%.*s", len, s);
}
在kern/kdebug.c 中添加对 user 地址的检查。
int
debuginfo_eip(uintptr_t addr, struct Eipdebuginfo *info)
{
...
...
// 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(struct UserStabData), 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.
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;
...
...
}
– EOF –