Last active
August 29, 2015 13:57
-
-
Save lawrencejones/9743770 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "userprog/exception.h" | |
#include "userprog/gdt.h" | |
#include "userprog/pagedir.h" | |
#include <inttypes.h> | |
#include <stdio.h> | |
#include "threads/interrupt.h" | |
#include "threads/thread.h" | |
#include "threads/pte.h" | |
#include "vm/page.h" | |
#define EXIT_FAILURE -1 | |
/* Number of page faults processed. */ | |
static long long page_fault_cnt; | |
static void kill (struct intr_frame *); | |
static void page_fault (struct intr_frame *); | |
/* 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); | |
thread_exit (); | |
} | |
} | |
/* 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. */ | |
void* pg_fault; /* Fault address rounded to the beginning of the page */ | |
uint32_t* pg_dir; /* Page directory for the currently running thread */ | |
struct page* pte; /* Page table entry from supplemental page table */ | |
/* 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; | |
/* Look up page in supplemental page table */ | |
pg_fault = (void*)(PTE_ADDR & (uint32_t)fault_addr); | |
pte = page_lookup(pg_fault); | |
pg_dir = thread_current()->proc_mask.pagedir; | |
if(pte == NULL || !not_present || (write && !pte->write)) | |
syscall_redirect.exit(EXIT_FAILURE); | |
else if(false) {} // TODO: stack growth | |
else if(pte != NULL) | |
{ | |
// TODO: Change when frame table is implemented | |
if(pg_dir != NULL) | |
pagedir_clear_page(pg_dir, pg_fault); | |
if(!pagedir_set_page(pg_dir, pg_fault, pte->phys_addr, pte->write)) | |
syscall_redirect.exit(EXIT_FAILURE); | |
} | |
f->eip = (void*)f->eax; | |
f->eax = 0xffffffff; | |
// TODO: Remove rest after debugging | |
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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "userprog/syscall.h" | |
#include <stdio.h> | |
#include <syscall-nr.h> | |
#include <debug.h> | |
#include <string.h> | |
#include "threads/interrupt.h" | |
#include "threads/thread.h" | |
#include "threads/synch.h" | |
#include "threads/vaddr.h" | |
//needed for halt() syscall | |
#include "devices/shutdown.h" | |
//needed to checks validity of pointers received | |
#include "userprog/pagedir.h" | |
//implements the file handling functions defined in this interface | |
#include "userprog/userfiles.h" | |
//needed to measure input size | |
#include "userprog/utils.h" | |
//number of system call handlers | |
#define SYSCALL_HANDLER_NUM 13 | |
#define VALID_SYS_CALL(s) (0 <= s && s < SYSCALL_HANDLER_NUM) | |
#define USER_BASE ((void *) 0x08048000) | |
//third argument is used to check whether to store the result or not | |
#define CALL0(fu) fu() | |
#define CALL1(fu, arg0) fu(arg0) | |
#define CALL2(fu, arg0, arg1) fu(arg0, arg1) | |
#define CALL3(fu, arg0, arg1, arg2) fu(arg0, arg1, arg2) | |
//shorthand for function call | |
#define MAP(x) (fu_pointer_validate(x)) | |
//dereferences pointer to the given type | |
#define MAKE_KVM(type, pointer) \ | |
(*((type *)MAP(pointer))) | |
//adds the offset to the pointer & calls MAKE_KVM | |
#define MAKE_KVM3(type, pointer, offset) \ | |
MAKE_KVM(type, ((void *)((char *)pointer + WORD_SIZE*offset))) | |
static void syscall_handler (struct intr_frame *); | |
//handlers for system calls | |
//shuts down | |
static void fu_halt (void); | |
//communicates with both process.c and userfiles.c | |
static void fu_exit (int status) NO_RETURN; | |
//communicates with userfiles.c | |
static pid_t fu_exec (const char *file); | |
//communicate with process.c | |
static bool fu_create (const char *file, int initial_size); | |
static bool fu_remove (const char *file); | |
static int fu_open (const char *file); | |
static int fu_read (int fd, void *buffer, int length); | |
static int fu_write (int fd, const void *buffer, int length); | |
//instatiate the interface | |
td_sys_call_interface syscall_redirect = | |
{ | |
.halt = fu_halt, | |
.exit = fu_exit, | |
.exec = fu_exec, | |
.wait = process_wait, | |
.create = fu_create, | |
.remove = fu_remove, | |
.open = fu_open, | |
.filesize = fu_file_filesize, | |
.read = fu_read, | |
.write = fu_write, | |
.seek = fu_file_seek, | |
.tell = fu_file_tell, | |
.close = fu_file_close, | |
}; | |
//checks a given pointer for validity | |
//uses the simplest version(number 1) | |
//exits if the pointer is invalid | |
//returns the mapping to kernel memory | |
static void* fu_pointer_validate(const void *p); | |
static int get_valid_user_pointer(const uint8_t *uaddr); | |
//check validity of a supplied buffer | |
static bool fu_check_buffer(const void *buff, const int size); | |
//interface for validating user input | |
//for the functions checking pointers, the pointers have to be in kernel | |
//address space | |
/*typedef struct | |
{ | |
void (*check_buffer) (const void *buff, const int size); | |
void (*check_file_descriptor) (const int fd); | |
} td_input_check; | |
*/ | |
/* | |
//interface implementation | |
static td_input_check input_check = | |
{ | |
.check_buffer = fu_check_buffer; | |
.check_file_descriptor = fu_check_file_descriptor; | |
}; | |
*/ | |
//remembers number of arguments for each function | |
//called at initialization | |
void | |
syscall_init (void) | |
{ | |
intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall"); | |
//initializez file-handling code | |
userfiles_init(); | |
} | |
//USER INTERFACE component | |
//RECEIVES: | |
//a)value of stack pointer | |
//b)storage for possible result | |
//RECEIVES an interrupt frame passed by reference | |
//uses the interrupt frame's stack pointer, which is passed by value | |
//and the eax register, where it stores the result | |
//the result either does not exist or is passed by value but never by reference | |
static void | |
syscall_handler (struct intr_frame *f) | |
{ | |
//input check | |
//interrupt frame must respect the following conditions | |
ASSERT(f != NULL); | |
//get stack pointer value | |
//we must keep the stack pointer located in the user address space | |
//as we have to check the address of each argument we extract from the stack | |
void *sp = f->esp; | |
//extracts top of stack, which contains the system call index | |
int8_t ui8_call_index = MAKE_KVM(int8_t, sp); | |
//exits if system call index is not mapped to a system call | |
if(!VALID_SYS_CALL(ui8_call_index)) | |
syscall_redirect.exit(EXIT_FAILURE); | |
//printf("the handler has received system call:%d, by process:%d\n", | |
//ui8_call_index, thread_current()->tid); | |
//stores system call result | |
//necessary in case the system call fails, f->eax can not store a negative | |
//value while this variable can | |
int result; | |
bool b_res_changed = false; | |
//1)chooses which handler to call | |
//2)mapps the pointer to kernel space & checks them | |
//3)extracts arguments on the stack | |
//4)converts them | |
//5)and passes them to the handler | |
switch(ui8_call_index) | |
{ | |
case SYS_HALT: CALL0(syscall_redirect.halt); break; | |
case SYS_EXIT: CALL1(syscall_redirect.exit, | |
MAKE_KVM3(int, sp, 1)); break; | |
case SYS_EXEC: result = CALL1(syscall_redirect.exec, | |
MAKE_KVM3(const char*, sp, 1)); | |
b_res_changed = true; break; | |
case SYS_WAIT: result = CALL1(syscall_redirect.wait, | |
MAKE_KVM3(pid_t, sp, 1)); | |
b_res_changed = true; break; | |
case SYS_CREATE: result = CALL2(syscall_redirect.create, | |
MAKE_KVM3(const char*, sp, 1), | |
MAKE_KVM3(int, sp, 2)); | |
b_res_changed = true; break; | |
case SYS_REMOVE: result = CALL1(syscall_redirect.remove, | |
MAKE_KVM3(const char*, sp, 1)); | |
b_res_changed = true; break; | |
case SYS_OPEN: result = CALL1(syscall_redirect.open, | |
MAKE_KVM3(const char*, sp, 1)); | |
b_res_changed = true; break; | |
case SYS_FILESIZE: result = CALL1(syscall_redirect.filesize, | |
MAKE_KVM3(int, sp, 1)); | |
b_res_changed = true; break; | |
case SYS_READ: result = CALL3(syscall_redirect.read, | |
MAKE_KVM3(int, sp, 1), | |
MAKE_KVM3(void *, sp, 2), | |
MAKE_KVM3(int, sp, 3)); | |
b_res_changed = true; break; | |
case SYS_WRITE: result = CALL3(syscall_redirect.write, | |
MAKE_KVM3(int, sp, 1), | |
MAKE_KVM3(const void *, sp, 2), | |
MAKE_KVM3(int, sp, 3)); | |
b_res_changed = true; break; | |
case SYS_SEEK: CALL2(syscall_redirect.seek, | |
MAKE_KVM3(int, sp, 1), | |
MAKE_KVM3(int, sp, 2)); break; | |
case SYS_TELL: result = CALL1(syscall_redirect.tell, | |
MAKE_KVM3(int, sp, 1)); | |
b_res_changed = true; break; | |
case SYS_CLOSE: CALL1(syscall_redirect.close, | |
MAKE_KVM3(int, sp, 1)); break; | |
default: printf("Invalid call number\n"), | |
syscall_redirect.exit(EXIT_FAILURE); | |
} | |
if(b_res_changed) | |
{ | |
//must receive either positive or -1 result | |
ASSERT(result >= PID_ERROR); | |
//result is placed in eax register | |
f->eax = result; | |
} | |
} | |
//GUARANTEES: | |
//a)all arguments passed by the user have been given to the requested system | |
//call in proper order, and converted to the appropriate type | |
//b)if input specifications have not been respected, the process has been | |
//terminated | |
//stopps Pintos execution by shutting down | |
//no need to deallocate memory when shutting down | |
static void | |
fu_halt (void) | |
{ | |
shutdown_power_off(); | |
NOT_REACHED(); | |
} | |
//receives: exit status of which should be either positive or -1 | |
static void | |
fu_exit (int status) | |
{ | |
//asserting input | |
if(!MEANINGFULL_EXIT(status)) | |
{ | |
//printf("naughty exit status: %d\n", status); | |
status = EXIT_FAILURE; | |
} | |
struct lineage *lin = thread_current()->proc_mask.lin; | |
lin->i_exit_status = status; | |
lin->b_killed_by_user = true; | |
//printf ("%s: exit(%d)\n", thread_current()->name, thread_current()->proc_mask.lin->i_exit_status); | |
//this deallocates resources used to relate with other processes | |
//the thread executing the process exits, calling process_exit | |
//also needs to deallocate files, because it can be called by an exception | |
//handler | |
//GUARANTEES: | |
//a)the exit status has been stores in the lineage | |
//b)the fact that the process was exited because of the user | |
thread_exit(); | |
NOT_REACHED(); | |
} | |
//GUARANTEES: all resources held by a process have been deallocated | |
//data about its exit is stored, and can be querried by the parent | |
//receives a string refernced by a pointer which may be anywhere within the | |
//user vitual memory | |
static pid_t | |
fu_exec(const char *cmd_line) | |
{ | |
//printf("creating process: %s \n\n", cmd_line); | |
//verifies that the given pointer is from the user's address space | |
//and that th string has the proper size | |
if(!fu_pointer_validate(cmd_line) || | |
strlen(cmd_line) >= MAX_SIZE_BUFFER) | |
{ | |
//printf("naughty command\n"); | |
syscall_redirect.exit(EXIT_FAILURE); | |
} | |
//checks the input's validity | |
//to do this, it bound the input to the size of a page | |
fu_check_buffer(cmd_line, strlen(cmd_line)); | |
//then, it calls the current process to create a new process | |
//mapping the string pointer into kernel address space | |
return fu_process_create(MAP(cmd_line)); | |
} | |
//if the process loads succesfully, returns a valid process ID | |
//otherwise, it returns PID_ERROR | |
//receives an unchecked address from the user program, and an unknown size | |
static bool | |
fu_create(const char *file, int initial_size) | |
{ | |
//verifies that the buffer file is situated in user space | |
if(!fu_check_buffer(file, initial_size)) | |
{ | |
syscall_redirect.exit(EXIT_FAILURE); | |
} | |
return fu_file_create(MAP(file), initial_size); | |
} | |
//receives unmapped pointer | |
static bool | |
fu_remove(const char *file) | |
{ | |
//verifies that the buffer file is situated in user space | |
if(!fu_check_buffer(file, 0)) | |
{ | |
syscall_redirect.exit(EXIT_FAILURE); | |
} | |
return fu_file_remove(MAP(file)); | |
} | |
//receives unmapped pointer | |
static int | |
fu_open(const char *file) | |
{ | |
//verifies that the buffer file is situated in user space | |
//printf("open: %p\n, %d", file, strlen(file)); | |
if(!fu_check_buffer(file, 0)) | |
{ | |
//printf("failed initial check\n"); | |
syscall_redirect.exit(EXIT_FAILURE); | |
} | |
return fu_file_open(MAP(file)); | |
} | |
//receives an unmapped pointe to a buffer, and an unknown size | |
static int | |
fu_read(int fd, void *buffer, int length) | |
{ | |
//verifies that the buffer is situated in user space | |
if(!fu_check_buffer(buffer, length)) | |
{ | |
syscall_redirect.exit(EXIT_FAILURE); | |
} | |
return fu_file_read(fd, MAP(buffer), length); | |
} | |
//receives an unmapped buffer to a pointer, and an unknown size | |
static int | |
fu_write(int fd, const void *buffer, int length) | |
{ | |
if(!fu_check_buffer(buffer, length)) | |
{ | |
syscall_redirect.exit(EXIT_FAILURE); | |
} | |
return fu_file_write(fd, MAP(buffer), length); | |
} | |
//receives a user-supplied pointer | |
static void* | |
fu_pointer_validate(const void *p) | |
{ | |
if(get_vaild_user_pointer(p) == EXIT_FAILURE) | |
syscall_redirect.exit(EXIT_FAILURE); | |
return pagedir_get_page(thread_current()->proc_mask.pagedir, p); | |
} | |
//GUARANTEES:if the pointer was invalid, then the process will be exited | |
//returns the mapping to kernel address(which may be NULL) | |
//RECEIVES:user addressed pointer and a length which may be negative(malicious) | |
static bool | |
fu_check_buffer(const void *buff, const int size) | |
{ | |
//can not measure a negative size | |
//or a buffer to an unmapped memory location | |
if(size < 0 || !fu_pointer_validate(buff)) | |
return false; | |
int distance_from_start = 0; | |
char *s = (char *)pg_round_down(buff); | |
ASSERT(s); | |
int i_buffer_offset = (int)((char *)buff - s); | |
ASSERT(0 <= i_buffer_offset && i_buffer_offset < PAGE_SIZE); | |
while(true) | |
{ | |
//check validity by jumping the size of a page in bytes | |
if(!fu_pointer_validate(s + distance_from_start)) | |
return false; | |
distance_from_start += PAGE_SIZE; | |
if(distance_from_start >= size + i_buffer_offset) | |
return true; | |
} | |
NOT_REACHED(); | |
} | |
//RETURNS true if the buffer is located at a valid position | |
// Reads a byte at user virtual address UADDR. | |
// Checks that UADDR is below PHYS_BASE. | |
// Returns the byte value if successful, -1 if not | |
static int get_valid_user_pointer(const uint8_t *uaddr) | |
{ | |
//if the pointer isn't in valid user address space, exit the process | |
if(!is_user_vaddr(uaddr)) | |
return EXIT_FAILURE; | |
int result; | |
asm ("movl $1f, %0; movzbl %1, %0; 1:" : "=&a" (result) : "m" (*uaddr)); | |
return result; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment