#include "userprog/exception.h" #include #include #include "stdbool.h" #include "userprog/gdt.h" #include "threads/interrupt.h" #include "threads/thread.h" #ifdef VM #include "vm/frame.h" #include "vm/page.h" #include "devices/swap.h" #include "threads/vaddr.h" #include "userprog/pagedir.h" #endif #define MAX_STACK_SIZE (8 * 1024 * 1024) // 8MB #define MAX_STACK_OFFSET 32 // 32 bytes offset below stack pointer (ESP) /* Number of page faults processed. */ static long long page_fault_cnt; static void kill (struct intr_frame *); static void page_fault (struct intr_frame *); static bool is_valid_stack_access (const void *fault_addr, const void *esp); static bool grow_stack (void *upage); bool fetch_page (void *upage, bool write); /* Registers handlers for interrupts that can be caused by user programs. In a real Unix-like OS, most of these interrupts would be passed along to the user process in the form of signals, as described in [SV-386] 3-24 and 3-25, but we don't implement signals. Instead, we'll make them simply kill the user process. Page faults are an exception. Here they are treated the same way as other exceptions, but this will need to change to implement virtual memory. Refer to [IA32-v3a] section 5.15 "Exception and Interrupt Reference" for a description of each of these exceptions. */ void exception_init (void) { /* These exceptions can be raised explicitly by a user program, e.g. via the INT, INT3, INTO, and BOUND instructions. Thus, we set DPL==3, meaning that user programs are allowed to invoke them via these instructions. */ intr_register_int (3, 3, INTR_ON, kill, "#BP Breakpoint Exception"); intr_register_int (4, 3, INTR_ON, kill, "#OF Overflow Exception"); intr_register_int (5, 3, INTR_ON, kill, "#BR BOUND Range Exceeded Exception"); /* These exceptions have DPL==0, preventing user processes from invoking them via the INT instruction. They can still be caused indirectly, e.g. #DE can be caused by dividing by 0. */ intr_register_int (0, 0, INTR_ON, kill, "#DE Divide Error"); intr_register_int (1, 0, INTR_ON, kill, "#DB Debug Exception"); intr_register_int (6, 0, INTR_ON, kill, "#UD Invalid Opcode Exception"); intr_register_int (7, 0, INTR_ON, kill, "#NM Device Not Available Exception"); intr_register_int (11, 0, INTR_ON, kill, "#NP Segment Not Present"); intr_register_int (12, 0, INTR_ON, kill, "#SS Stack Fault Exception"); intr_register_int (13, 0, INTR_ON, kill, "#GP General Protection Exception"); intr_register_int (16, 0, INTR_ON, kill, "#MF x87 FPU Floating-Point Error"); intr_register_int (19, 0, INTR_ON, kill, "#XF SIMD Floating-Point Exception"); /* Most exceptions can be handled with interrupts turned on. We need to disable interrupts for page faults because the fault address is stored in CR2 and needs to be preserved. */ intr_register_int (14, 0, INTR_OFF, page_fault, "#PF Page-Fault Exception"); } /* Prints exception statistics. */ void exception_print_stats (void) { printf ("Exception: %lld page faults\n", page_fault_cnt); } /* Handler for an exception (probably) caused by a user process. */ static void kill (struct intr_frame *f) { /* This interrupt is one (probably) caused by a user process. For example, the process might have tried to access unmapped virtual memory (a page fault). For now, we simply kill the user process. Later, we'll want to handle page faults in the kernel. Real Unix-like operating systems pass most exceptions back to the process via signals, but we don't implement them. */ /* The interrupt frame's code segment value tells us where the exception originated. */ switch (f->cs) { case SEL_UCSEG: /* User's code segment, so it's a user exception, as we expected. Kill the user process. */ printf ("%s: dying due to interrupt %#04x (%s).\n", thread_name (), f->vec_no, intr_name (f->vec_no)); intr_dump_frame (f); thread_exit (); case SEL_KCSEG: /* Kernel's code segment, which indicates a kernel bug. Kernel code shouldn't throw exceptions. (Page faults may cause kernel exceptions--but they shouldn't arrive here.) Panic the kernel to make the point. */ intr_dump_frame (f); PANIC ("Kernel bug - unexpected interrupt in kernel"); default: /* Some other code segment? Shouldn't happen. Panic the kernel. */ printf ("Interrupt %#04x (%s) in unknown segment %04x\n", f->vec_no, intr_name (f->vec_no), f->cs); PANIC ("Kernel bug - this shouldn't be possible!"); } } /* Page fault handler. This is a skeleton that must be filled in to implement virtual memory. Some solutions to task 2 may also require modifying this code. At entry, the address that faulted is in CR2 (Control Register 2) and information about the fault, formatted as described in the PF_* macros in exception.h, is in F's error_code member. The example code here shows how to parse that information. You can find more information about both of these in the description of "Interrupt 14--Page Fault Exception (#PF)" in [IA32-v3a] section 5.15 "Exception and Interrupt Reference". */ static void page_fault (struct intr_frame *f) { bool not_present; /* True: not-present page, false: writing r/o page. */ bool write; /* True: access was write, false: access was read. */ bool user; /* True: access by user, false: access by kernel. */ void *fault_addr; /* Fault address. */ /* Obtain faulting address, the virtual address that was accessed to cause the fault. It may point to code or to data. It is not necessarily the address of the instruction that caused the fault (that's f->eip). See [IA32-v2a] "MOV--Move to/from Control Registers" and [IA32-v3a] 5.15 "Interrupt 14--Page Fault Exception (#PF)". */ asm ("movl %%cr2, %0" : "=r" (fault_addr)); /* Turn interrupts back on (they were only off so that we could be assured of reading CR2 before it changed). */ intr_enable (); /* Count page faults. */ page_fault_cnt++; /* Determine cause. */ not_present = (f->error_code & PF_P) == 0; write = (f->error_code & PF_W) != 0; user = (f->error_code & PF_U) != 0; /* Select the appropriate stack pointer based on the context of the fault. */ void *esp = user ? f->esp : thread_current()->curr_esp; /* If the fault address is in a user page that is not present, then it might be just that the stack needs to grow or that it needs to be lazily loaded. So we attempt to grow the stack. If this does not work, we check our SPT to see if the page is expected to have data loaded in memory. */ void *upage = pg_round_down (fault_addr); if (not_present && is_user_vaddr (upage) && upage != NULL) { if (fetch_page (upage, write)) return; if (is_valid_stack_access (fault_addr, esp)) if (grow_stack (upage)) return; } /* If the page fault occurred in kernel mode, then we intentionally indicate a fault (for get_user() etc). */ if (!user) { f->eip = (void *)f->eax; f->eax = 0xffffffff; return; } /* To implement virtual memory, delete the rest of the function body, and replace it with code that brings in the page to which fault_addr refers. */ printf ("Page fault at %p: %s error %s page in %s context.\n", fault_addr, not_present ? "not present" : "rights violation", write ? "writing" : "reading", user ? "user" : "kernel"); kill (f); } /* Validates whether the fault address is a valid stack access. Access is a valid stack access under the following two conditions: 1. The fault address must be within MAX_STACK_OFFSET (32) bytes below the current stack pointer. (Accounts for both PUSH and PUSHA instructions) 2. Growing this stack to this address does not cause it to exceed the MAX_STACK_SIZE (8MB) limit. Returns true if both conditions are met, false otherwise. Pre: fault_addr is a valid user virtual address (so also not NULL). */ static bool is_valid_stack_access (const void *fault_addr, const void *esp) { uint32_t new_stack_size = PHYS_BASE - pg_round_down (fault_addr); uint32_t *lowest_valid_push_addr = (uint32_t *)esp - MAX_STACK_OFFSET; bool is_within_push_range = (uint32_t *)fault_addr >= lowest_valid_push_addr; return is_within_push_range && new_stack_size <= MAX_STACK_SIZE; } /* Attempts to grow the stack by allocating and mapping a new page. This involves: 1. Allocating a zeroed page from the user pool 2. Installing it into the page table with write permissions Returns true if the stack was successfully grown, false if either allocation or installation fails. Pre: upage is a valid page-aligned address (so also not NULL). */ static bool grow_stack (void *upage) { /* Allocate new page for stack */ void *new_page = frame_alloc (PAL_ZERO, upage, thread_current ()); if (new_page == NULL) return false; /* Install the page into user page table */ if (!pagedir_set_page (thread_current ()->pagedir, upage, new_page, true)) { frame_free (new_page); return false; } return true; } bool fetch_page (void *upage, bool write) { /* Check if the page is in the supplemental page table. That is, it is a page that is expected to be in memory. */ struct page_entry *page = page_get (thread_current (), upage); if (page == NULL) return false; /* Check if the non-present user page is in the swap partition. If so, swap it back into main memory, updating the PTE for the faulted virtual address to point to the newly allocated frame. */ struct thread *t = thread_current (); if (page_in_swap (t, upage)) { /* NOTE: This code should be refactored and moved into helper functions within 'page.c'.*/ void *kpage = frame_alloc (0, upage, t); lock_acquire (&page->lock); size_t swap_slot = page_get_swap (t, upage); swap_in (kpage, swap_slot); lock_release (&page->lock); bool writeable = pagedir_is_writable (t->pagedir, upage); /* TODO: When this returns false we should quit the page fault, but currently we continue and check the stack conditions in the page fault handler. */ return pagedir_set_page (t->pagedir, upage, kpage, writeable); } /* An attempt to write to a non-writeable should fail. */ if (write && !page->writable) return false; /* Load the page into memory based on the type of data it is expecting. */ bool success = false; switch (page->type) { case PAGE_MMAP: case PAGE_EXECUTABLE: case PAGE_SHARED: success = page_load_file (page); break; default: return false; } if (success && page->writable && !pagedir_is_writable(thread_current()->pagedir, upage)) pagedir_set_writable(thread_current()->pagedir, upage, true); return success; }