Skip to content

Instantly share code, notes, and snippets.

@lawrencejones
Last active August 29, 2015 13:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lawrencejones/9743770 to your computer and use it in GitHub Desktop.
Save lawrencejones/9743770 to your computer and use it in GitHub Desktop.
#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);
}
#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