User-level page fault handling
Exercise 8. Implement the sys_env_set_pgfault_upcall system call. Be sure to enable permission checking when looking up the environment ID of the target environment, since this is a "dangerous" system call.
static int
sys_env_set_pgfault_upcall(envid_t envid, void *func)
{
struct Env *e;
int ret = envid2env(envid, &e, 1);
if (ret) return ret; //bad_env
e->env_pgfault_upcall = func;
return 0;
}
Exercise 9. Implement the code in page_fault_handler in kern/trap.c required to dispatch page faults to the user-mode handler. Be sure to take appropriate precautions when writing into the exception stack. (What happens if the user environment runs out of space on the exception stack?)
void
page_fault_handler(struct Trapframe *tf)
{
uint32_t fault_va;
fault_va = rcr2();
cprintf("fault_va: %x\n", fault_va);
// LAB 3: Your code here.
if ((tf->tf_cs&3) == 0) {
panic("Kernel page fault!");
}
// LAB 4: Your code here.
if (curenv->env_pgfault_upcall) {
struct UTrapframe *utf;
uintptr_t utf_addr;
if (UXSTACKTOP-PGSIZE<=tf->tf_esp && tf->tf_esp<=UXSTACKTOP-1)
utf_addr = tf->tf_esp - sizeof(struct UTrapframe) - 4;
else
utf_addr = UXSTACKTOP - sizeof(struct UTrapframe);
user_mem_assert(curenv, (void*)utf_addr, 1, PTE_W);//1 is enough
utf = (struct UTrapframe *) utf_addr;
utf->utf_fault_va = fault_va;
utf->utf_err = tf->tf_err;
utf->utf_regs = tf->tf_regs;
utf->utf_eip = tf->tf_eip;
utf->utf_eflags = tf->tf_eflags;
utf->utf_esp = tf->tf_esp;
curenv->env_tf.tf_eip = (uintptr_t)curenv->env_pgfault_upcall;
curenv->env_tf.tf_esp = utf_addr;
env_run(curenv);
}
// 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);
}
Exercise 10. Implement the _pgfault_upcall routine in lib/pfentry.S. The interesting part is returning to the original point in the user code that caused the page fault. You'll return directly there, without going back through the kernel. The hard part is simultaneously switching stacks and re-loading the EIP.
.text
.globl _pgfault_upcall
_pgfault_upcall:
// Call the C page fault handler.
pushl %esp // function argument: pointer to UTF
movl _pgfault_handler, %eax
call *%eax
addl $4, %esp // pop function argument
movl 0x28(%esp), %edx # trap-time eip
subl $0x4, 0x30(%esp) # we have to use subl now because we can't use after popfl
movl 0x30(%esp), %eax # trap-time esp-4
movl %edx, (%eax)
addl $0x8, %esp
// Restore the trap-time registers. After you do this, you
// can no longer modify any general-purpose registers.
// LAB 4: Your code here.
popal
// Restore eflags from the stack. After you do this, you can
// no longer use arithmetic operations or anything else that
// modifies eflags.
// LAB 4: Your code here.
addl $0x4, %esp #eip
popfl
// Switch back to the adjusted trap-time stack.
// LAB 4: Your code here.
popl %esp
// Return to re-execute the instruction that faulted.
// LAB 4: Your code here.
ret
Exercise 11. Finish set_pgfault_handler() in lib/pgfault.c.
void
set_pgfault_handler(void (*handler)(struct UTrapframe *utf))
{
int r;
if (_pgfault_handler == 0) {
if (sys_page_alloc(0, (void*)(UXSTACKTOP-PGSIZE), PTE_W|PTE_U|PTE_P) < 0)
panic("set_pgfault_handler:sys_page_alloc failed");;
}
// Save handler pointer for assembly to call.
_pgfault_handler = handler;
if (sys_env_set_pgfault_upcall(0, _pgfault_upcall) < 0)
panic("set_pgfault_handler:sys_env_set_pgfault_upcall failed");
}
Challenge! Extend your kernel so that not only page faults, but all types of processor exceptions that code running in user space can generate, can be redirected to a user-mode exception handler. Write user-mode test programs to test user-mode handling of various exceptions such as divide-by-zero, general protection fault, and illegal opcode.