summaryrefslogtreecommitdiff
path: root/kernel/user.c
diff options
context:
space:
mode:
authorFreya Murphy <freya@freyacat.org>2025-03-25 17:36:52 -0400
committerFreya Murphy <freya@freyacat.org>2025-03-25 17:38:22 -0400
commit6af21e6a4f2251e71353562d5df7f376fdffc270 (patch)
treede20c7afc9878422c81e34f30c6b010075e9e69a /kernel/user.c
downloadcomus-6af21e6a4f2251e71353562d5df7f376fdffc270.tar.gz
comus-6af21e6a4f2251e71353562d5df7f376fdffc270.tar.bz2
comus-6af21e6a4f2251e71353562d5df7f376fdffc270.zip
initial checkout from wrc
Diffstat (limited to 'kernel/user.c')
-rw-r--r--kernel/user.c774
1 files changed, 774 insertions, 0 deletions
diff --git a/kernel/user.c b/kernel/user.c
new file mode 100644
index 0000000..2d32157
--- /dev/null
+++ b/kernel/user.c
@@ -0,0 +1,774 @@
+/**
+** @file user.c
+**
+** @author CSCI-452 class of 20245
+**
+** @brief User-level code manipulation routines
+*/
+
+#define KERNEL_SRC
+
+#include <common.h>
+
+#include <bootstrap.h>
+#include <elf.h>
+#include <user.h>
+#include <vm.h>
+
+/*
+** PRIVATE DEFINITIONS
+*/
+
+/*
+** PRIVATE DATA TYPES
+*/
+
+/*
+** PRIVATE GLOBAL VARIABLES
+*/
+
+/*
+** PUBLIC GLOBAL VARIABLES
+*/
+
+/*
+** Location of the "user blob" in memory.
+**
+** These variables are filled in by the code in startup.S using values
+** passed to it from the bootstrap.
+**
+** These are visible so that the startup code can find them.
+*/
+uint16_t user_offset; // byte offset from the segment base
+uint16_t user_segment; // segment base address
+uint16_t user_sectors; // number of 512-byte sectors it occupies
+
+header_t *user_header; // filled in by the user_init routine
+prog_t *prog_table; // filled in by the user_init routine
+
+/*
+** PRIVATE FUNCTIONS
+*/
+
+#if TRACING_ELF
+
+/*
+** This is debugging support code; if not debugging the ELF
+** handling code, it won't be compiled into the kernel.
+*/
+
+// buffer used by some of these functions
+static char ebuf[16];
+
+/*
+** File header functions
+*/
+
+// interpret the file class
+static const char *fh_eclass( e32_si class ) {
+ switch( class ) {
+ case ELF_CLASS_NONE: return( "None" ); break;
+ case ELF_CLASS_32: return( "EC32" ); break;
+ case ELF_CLASS_64: return( "EC64" ); break;
+ }
+ return( "????" );
+}
+
+// interpret the data encoding
+static const char *fh_edata( e32_si data ) {
+ switch( data ) {
+ case ELF_DATA_NONE: return( "Invd" ); break;
+ case ELF_DATA_2LSB: return( "2CLE" ); break;
+ case ELF_DATA_2MSB: return( "2CBE" ); break;
+ }
+ return( "????" );
+}
+
+// interpret the file type
+static const char *fh_htype( e32_h type ) {
+ switch( type ) {
+ case ET_NONE: return( "none" ); break;
+ case ET_REL: return( "rel" ); break;
+ case ET_EXEC: return( "exec" ); break;
+ case ET_DYN: return( "dyn" ); break;
+ case ET_CORE: return( "core" ); break;
+ default:
+ if( type >= ET_LO_OS && type <= ET_HI_OS )
+ return( "OSsp" );
+ else if( type >= ET_LO_CP && type <= ET_HI_CP )
+ return( "CPsp" );
+ }
+ sprint( ebuf, "0x%04x", type );
+ return( (const char *) ebuf );
+}
+
+// interpret the machine type
+static const char *fh_mtype( e32_h machine ) {
+ switch( machine ) {
+ case EM_NONE: return( "None" ); break;
+ case EM_386: return( "386" ); break;
+ case EM_ARM: return( "ARM" ); break;
+ case EM_X86_64: return( "AMD64" ); break;
+ case EM_AARCH64: return( "AARCH64" ); break;
+ case EM_RISCV: return( "RISC-V" ); break;
+ }
+ return( "Other" );
+}
+
+// dump the program header
+static void dump_fhdr( elfhdr_t *hdr ) {
+ cio_puts( "File header: magic " );
+ for( int i = EI_MAG0; i <= EI_MAG3; ++i )
+ put_char_or_code( hdr->e_ident.bytes[i] );
+ cio_printf( " class %s", fh_eclass(hdr->e_ident.f.class) );
+ cio_printf( " enc %s", fh_edata(hdr->e_ident.f.data) );
+ cio_printf( " ver %u\n", hdr->e_ident.f.version );
+ cio_printf( " type %s", fh_htype(hdr->e_type) );
+ cio_printf( " mach %s", fh_mtype(hdr->e_machine) );
+ cio_printf( " vers %d", hdr->e_version );
+ cio_printf( " entr %08x\n", hdr->e_entry );
+
+ cio_printf( " phoff %08x", hdr->e_phoff );
+ cio_printf( " shoff %08x", hdr->e_shoff );
+ cio_printf( " flags %08x", (uint32_t) hdr->e_flags );
+ cio_printf( " ehsize %u\n", hdr->e_ehsize );
+ cio_printf( " phentsize %u", hdr->e_phentsize );
+ cio_printf( " phnum %u", hdr->e_phnum );
+ cio_printf( " shentsize %u", hdr->e_shentsize );
+ cio_printf( " shnum %u", hdr->e_shnum );
+ cio_printf( " shstrndx %u\n", hdr->e_shstrndx );
+}
+
+/*
+** Program header functions
+*/
+
+// categorize the header type
+static const char *ph_type( e32_w type ) {
+ switch( type ) {
+ case PT_NULL: return( "Unused" ); break;
+ case PT_LOAD: return( "Load" ); break;
+ case PT_DYNAMIC: return( "DLI" ); break;
+ case PT_INTERP: return( "Interp" ); break;
+ case PT_NOTE: return( "Aux" ); break;
+ case PT_SHLIB: return( "RSVD" ); break;
+ case PT_PHDR: return( "PTentry" ); break;
+ case PT_TLS: return( "TLS" ); break;
+ default:
+ if( type >= PT_LO_OS && type <= PT_HI_OS )
+ return( "OSsp" );
+ else if( type >= PT_LO_CP && type <= PT_HI_CP )
+ return( "CPsp" );
+ }
+ sprint( ebuf, "0x%08x", type );
+ return( (const char *) ebuf );
+}
+
+// report the individual flags
+static void ph_flags( e32_w flags ) {
+ if( (flags & PF_R) != 0 ) cio_putchar( 'R' );
+ if( (flags & PF_W) != 0 ) cio_putchar( 'W' );
+ if( (flags & PF_E) != 0 ) cio_putchar( 'X' );
+}
+
+// dump a program header
+static void dump_phdr( elfproghdr_t *hdr, int n ) {
+ cio_printf( "Prog header %d, type %s\n", n, ph_type(hdr->p_type) );
+ cio_printf( " offset %08x", hdr->p_offset );
+ cio_printf( " va %08x", hdr->p_va );
+ cio_printf( " pa %08x\n", hdr->p_pa );
+ cio_printf( " filesz %08x", hdr->p_filesz );
+ cio_printf( " memsz %08x", hdr->p_memsz );
+ cio_puts( " flags " );
+ ph_flags( hdr->p_flags );
+ cio_printf( " align %08x", hdr->p_align );
+ cio_putchar( '\n' );
+}
+
+/*
+** Section header functions
+*/
+
+// interpret the header type
+static const char *sh_type( e32_w type ) {
+ switch( type ) {
+ case SHT_NULL: return( "Unused" ); break;
+ case SHT_PROGBITS: return( "Progbits" ); break;
+ case SHT_SYMTAB: return( "Symtab" ); break;
+ case SHT_STRTAB: return( "Strtab" ); break;
+ case SHT_RELA: return( "Rela" ); break;
+ case SHT_HASH: return( "Hash" ); break;
+ case SHT_DYNAMIC: return( "Dynamic" ); break;
+ case SHT_NOTE: return( "Note" ); break;
+ case SHT_NOBITS: return( "Nobits" ); break;
+ case SHT_REL: return( "Rel" ); break;
+ case SHT_SHLIB: return( "Shlib" ); break;
+ case SHT_DYNSYM: return( "Dynsym" ); break;
+ default:
+ if( type >= SHT_LO_CP && type <= SHT_HI_CP )
+ return( "CCsp" );
+ else if( type >= SHT_LO_US && type <= SHT_HI_US )
+ return( "User" );
+ }
+ sprint( ebuf, "0x%08x", type );
+ return( (const char *) ebuf );
+}
+
+// report the various flags
+static void sh_flags( unsigned int flags ) {
+ if( (flags & SHF_WRITE) != 0 ) cio_putchar( 'W' );
+ if( (flags & SHF_ALLOC) != 0 ) cio_putchar( 'A' );
+ if( (flags & SHF_EXECINSTR) != 0 ) cio_putchar( 'X' );
+ if( (flags & SHF_MERGE) != 0 ) cio_putchar( 'M' );
+ if( (flags & SHF_STRINGS) != 0 ) cio_putchar( 'S' );
+ if( (flags & SHF_INFO_LINK) != 0 ) cio_putchar( 'L' );
+ if( (flags & SHF_LINK_ORDER) != 0 ) cio_putchar( 'o' );
+ if( (flags & SHF_OS_NONCON) != 0 ) cio_putchar( 'n' );
+ if( (flags & SHF_GROUP) != 0 ) cio_putchar( 'g' );
+ if( (flags & SHF_TLS) != 0 ) cio_putchar( 't' );
+}
+
+// dump a section header
+__attribute__((__unused__))
+static void dump_shdr( elfsecthdr_t *hdr, int n ) {
+ cio_printf( "Sect header %d, type %d (%s), name %s\n",
+ n, hdr->sh_type, sh_type(hdr->sh_type) );
+ cio_printf( " flags %08x ", (uint32_t) hdr->sh_flags );
+ sh_flags( hdr->sh_flags );
+ cio_printf( " addr %08x", hdr->sh_addr );
+ cio_printf( " offset %08x", hdr->sh_offset );
+ cio_printf( " size %08x\n", hdr->sh_size );
+ cio_printf( " link %08x", hdr->sh_link );
+ cio_printf( " info %08x", hdr->sh_info );
+ cio_printf( " align %08x", hdr->sh_addralign );
+ cio_printf( " entsz %08x\n", hdr->sh_entsize );
+}
+#endif
+
+/**
+** read_phdrs(addr,phoff,phentsize,phnum)
+**
+** Parses the ELF program headers and each segment described into memory.
+**
+** @param hdr Pointer to the program header
+** @param pcb Pointer to the PCB (and its PDE)
+**
+** @return status of the attempt:
+** SUCCESS everything loaded correctly
+** E_LOAD_LIMIT more than N_LOADABLE PT_LOAD sections
+** other status returned from vm_add()
+*/
+static int read_phdrs( elfhdr_t *hdr, pcb_t *pcb ) {
+
+ // sanity check
+ assert1( hdr != NULL );
+ assert2( pcb != NULL );
+
+#if TRACING_USER
+ cio_printf( "read_phdrs(%08x,%08x)\n", (uint32_t) hdr, (uint32_t) pcb );
+#endif
+
+ // iterate through the program headers
+ uint_t nhdrs = hdr->e_phnum;
+
+ // pointer to the first header table entry
+ elfproghdr_t *curr = (elfproghdr_t *) ((uint32_t) hdr + hdr->e_phoff);
+
+ // process them all
+ int loaded = 0;
+ for( uint_t i = 0; i < nhdrs; ++i, ++curr ) {
+
+#if TRACING_ELF
+ dump_phdr( curr, i );
+#endif
+ if( curr->p_type != PT_LOAD ) {
+ // not loadable --> we'll skip it
+ continue;
+ }
+
+ if( loaded >= N_LOADABLE ) {
+#if TRACING_USER
+ cio_puts( " LIMIT\n" );
+#endif
+ return E_LOAD_LIMIT;
+ }
+
+ // set a pointer to the bytes within the object file
+ char *data = (char *) (((uint32_t)hdr) + curr->p_offset);
+#if TRACING_USER
+ cio_printf( " data @ %08x", (uint32_t) data );
+#endif
+
+ // copy the pages into memory
+ int stat = vm_add( pcb->pdir, curr->p_flags & PF_W, false,
+ (char *) curr->p_va, curr->p_memsz, data, curr->p_filesz );
+ if( stat != SUCCESS ) {
+ // TODO what else should we do here? check for memory leak?
+ return stat;
+ }
+
+ // set the section table entry in the PCB
+ pcb->sects[loaded].length = curr->p_memsz;
+ pcb->sects[loaded].addr = curr->p_va;
+#if TRACING_USER
+ cio_printf( " loaded %u @ %08x\n",
+ pcb->sects[loaded].length, pcb->sects[loaded].addr );
+#endif
+ ++loaded;
+ }
+
+ return SUCCESS;
+}
+
+/**
+** Name: stack_setup
+**
+** Set up the stack for a new process
+**
+** @param pcb Pointer to the PCB for the process
+** @param entry Entry point for the new process
+** @param args Argument vector to be put in place
+**
+** @return A pointer to the context_t on the stack, or NULL
+*/
+static context_t *stack_setup( pcb_t *pcb, uint32_t entry, const char **args ) {
+
+ /*
+ ** First, we need to count the space we'll need for the argument
+ ** vector and strings.
+ */
+
+ int argbytes = 0;
+ int argc = 0;
+
+ while( args[argc] != NULL ) {
+ int n = strlen( args[argc] ) + 1;
+ // can't go over one page in size
+ if( (argbytes + n) > SZ_PAGE ) {
+ // oops - ignore this and any others
+ break;
+ }
+ argbytes += n;
+ ++argc;
+ }
+
+ // Round up the byte count to the next multiple of four.
+ argbytes = (argbytes + 3) & MOD4_MASK;
+
+ /*
+ ** Allocate the arrays. We are safe using dynamic arrays here
+ ** because we're using the OS stack, not the user stack.
+ **
+ ** We want the argstrings and argv arrays to contain all zeroes.
+ ** The C standard states, in section 6.7.8, that
+ **
+ ** "21 If there are fewer initializers in a brace-enclosed list
+ ** than there are elements or members of an aggregate, or
+ ** fewer characters in a string literal used to initialize an
+ ** array of known size than there are elements in the array,
+ ** the remainder of the aggregate shall be initialized
+ ** implicitly the same as objects that have static storage
+ ** duration."
+ **
+ ** Sadly, because we're using variable-sized arrays, we can't
+ ** rely on this, so we have to call memclr() instead. :-( In
+ ** truth, it doesn't really cost us much more time, but it's an
+ ** annoyance.
+ */
+
+ char argstrings[ argbytes ];
+ char *argv[ argc + 1 ];
+
+ CLEAR( argstrings );
+ CLEAR( argv );
+
+ // Next, duplicate the argument strings, and create pointers to
+ // each one in our argv.
+ char *tmp = argstrings;
+ for( int i = 0; i < argc; ++i ) {
+ int nb = strlen(args[i]) + 1; // bytes (incl. NUL) in this string
+ strcpy( tmp, args[i] ); // add to our buffer
+ argv[i] = tmp; // remember where it was
+ tmp += nb; // move on
+ }
+
+ // trailing NULL pointer
+ argv[argc] = NULL;
+
+ /*
+ ** The pages for the stack were cleared when they were allocated,
+ ** so we don't need to remember to do that.
+ **
+ ** We reserve one longword at the bottom of the stack to hold a
+ ** pointer to where argv is on the stack.
+ **
+ ** The user code was linked with a startup function that defines
+ ** the entry point (_start), calls main(), and then calls exit()
+ ** if main() returns. We need to set up the stack this way:
+ **
+ ** esp -> context <- context save area
+ ** ... <- context save area
+ ** context <- context save area
+ ** entry_pt <- return address for the ISR
+ ** argc <- argument count for main()
+ ** /-> argv <- argv pointer for main()
+ ** | ... <- argv array w/trailing NULL
+ ** | ... <- argv character strings
+ ** \--- ptr <- last word in stack
+ **
+ ** Stack alignment rules for the SysV ABI i386 supplement dictate that
+ ** the 'argc' parameter must be at an address that is a multiple of 16;
+ ** see below for more information.
+ */
+
+ // Pointer to the last word in stack. We get this from the
+ // VM hierarchy. Get the PDE entry for the user address space.
+ pde_t stack_pde = pcb->pdir[USER_PDE];
+
+ // The PDE entry points to the PT, which is an array of PTE. The last
+ // two entries are for the stack; pull out the last one.
+ pte_t stack_pte = ((pte_t *)(stack_pde & MOD4K_MASK))[USER_STK_PTE2];
+
+ // OK, now we have the PTE. The frame address of the last page is
+ // in this PTE. Find the address immediately after that.
+ uint32_t *ptr = (uint32_t *)
+ ((uint32_t)(stack_pte & MOD4K_MASK) + SZ_PAGE);
+
+ // Pointer to where the arg strings should be filled in.
+ char *strings = (char *) ( (uint32_t) ptr - argbytes );
+
+ // back the pointer up to the nearest word boundary; because we're
+ // moving toward location 0, the nearest word boundary is just the
+ // next smaller address whose low-order two bits are zeroes
+ strings = (char *) ((uint32_t) strings & MOD4_MASK);
+
+ // Copy over the argv strings.
+ memcpy( (void *)strings, argstrings, argbytes );
+
+ /*
+ ** Next, we need to copy over the argv pointers. Start by
+ ** determining where 'argc' should go.
+ **
+ ** Stack alignment is controlled by the SysV ABI i386 supplement,
+ ** version 1.2 (June 23, 2016), which states in section 2.2.2:
+ **
+ ** "The end of the input argument area shall be aligned on a 16
+ ** (32 or 64, if __m256 or __m512 is passed on stack) byte boundary.
+ ** In other words, the value (%esp + 4) is always a multiple of 16
+ ** (32 or 64) when control is transferred to the function entry
+ ** point. The stack pointer, %esp, always points to the end of the
+ ** latest allocated stack frame."
+ **
+ ** Isn't technical documentation fun? Ultimately, this means that
+ ** the first parameter to main() should be on the stack at an address
+ ** that is a multiple of 16.
+ **
+ ** The space needed for argc, argv, and the argv array itself is
+ ** argc + 3 words (argc+1 for the argv entries, plus one word each
+ ** for argc and argv). We back up that much from 'strings'.
+ */
+
+ int nwords = argc + 3;
+ uint32_t *acptr = ((uint32_t *) strings) - nwords;
+
+ /*
+ ** Next, back up until we're at a multiple-of-16 address. Because we
+ ** are moving to a lower address, its upper 28 bits are identical to
+ ** the address we currently have, so we can do this with a bitwise
+ ** AND to just turn off the lower four bits.
+ */
+
+ acptr = (uint32_t *) ( ((uint32_t)acptr) & MOD16_MASK );
+
+ // copy in 'argc'
+ *acptr = argc;
+
+ // next, 'argv', which follows 'argc'; 'argv' points to the
+ // word that follows it in the stack
+ uint32_t *avptr = acptr + 2;
+ *(acptr+1) = (uint32_t) avptr;
+
+ /*
+ ** Next, we copy in all argc+1 pointers.
+ */
+
+ // Adjust and copy the string pointers.
+ for( int i = 0; i <= argc; ++i ) {
+ if( argv[i] != NULL ) {
+ // an actual pointer - adjust it and copy it in
+ *avptr = (uint32_t) strings;
+ // skip to the next entry in the array
+ strings += strlen(argv[i]) + 1;
+ } else {
+ // end of the line!
+ *avptr = NULL;
+ }
+ ++avptr;
+ }
+
+ /*
+ ** Now, we need to set up the initial context for the executing
+ ** process.
+ **
+ ** When this process is dispatched, the context restore code will
+ ** pop all the saved context information off the stack, including
+ ** the saved EIP, CS, and EFLAGS. We set those fields up so that
+ ** the interrupt "returns" to the entry point of the process.
+ */
+
+ // Locate the context save area on the stack.
+ context_t *ctx = ((context_t *) avptr) - 1;
+
+ /*
+ ** We cleared the entire stack earlier, so all the context
+ ** fields currently contain zeroes. We now need to fill in
+ ** all the important fields.
+ */
+
+ ctx->eflags = DEFAULT_EFLAGS; // IE enabled, PPL 0
+ ctx->eip = entry; // initial EIP
+ ctx->cs = GDT_CODE; // segment registers
+ ctx->ss = GDT_STACK;
+ ctx->ds = ctx->es = ctx->fs = ctx->gs = GDT_DATA;
+
+ /*
+ ** Return the new context pointer to the caller. It will be our
+ ** caller's responsibility to schedule this process.
+ */
+
+ return( ctx );
+}
+
+/*
+** PUBLIC FUNCTIONS
+*/
+
+/**
+** Name: user_init
+**
+** Initializes the user support module.
+*/
+void user_init( void ) {
+
+#if TRACING_INIT
+ cio_puts( " User" );
+#endif
+
+ // This is gross, but we need to get this information somehow.
+ // Access the "user blob" data in the second bootstrap sector
+ uint16_t *blobdata = (uint16_t *) USER_BLOB_DATA;
+ user_offset = *blobdata++;
+ user_segment = *blobdata++;
+ user_sectors = *blobdata++;
+
+#if TRACING_USER
+ cio_printf( "\nUser blob: %u sectors @ %04x:%04x", user_sectors,
+ user_segment, user_offset );
+#endif
+
+ // calculate the location of the user blob
+ if( user_sectors > 0 ) {
+
+ // calculate the address of the header
+ user_header = (header_t *)
+ ( KERN_BASE +
+ ( (((uint_t)user_segment) << 4) + ((uint_t)user_offset) )
+ );
+
+ // the program table immediate follows the blob header
+ prog_table = (prog_t *) (user_header + 1);
+
+#if TRACING_USER
+ cio_printf( ", hdr %08x, %u progs, tbl %08x\n", (uint32_t) user_header,
+ user_header->num, (uint32_t) prog_table );
+#endif
+
+ } else {
+ // too bad, so sad!
+ user_header = NULL;
+ prog_table = NULL;
+#if TRACING_USER
+ cio_putchar( '\n' );
+#endif
+ }
+}
+
+/**
+** Name: user_locate
+**
+** Locates a user program in the user code archive.
+**
+** @param what The ID of the user program to find
+**
+** @return pointer to the program table entry in the code archive, or NULL
+*/
+prog_t *user_locate( uint_t what ) {
+
+ // no programs if there is no blob!
+ if( user_header == NULL ) {
+ return NULL;
+ }
+
+ // make sure this is a reasonable program to request
+ if( what >= user_header->num ) {
+ // no such program!
+ return NULL;
+ }
+
+ // find the entry in the program table
+ prog_t *prog = &prog_table[what];
+
+ // if there are no bytes, it's useless
+ if( prog->size < 1 ) {
+ return NULL;
+ }
+
+ // return the program table pointer
+ return prog;
+}
+
+/**
+** Name: user_duplicate
+**
+** Duplicates the memory setup for an existing process.
+**
+** @param new The PCB for the new copy of the program
+** @param old The PCB for the existing the program
+**
+** @return the status of the duplicate attempt
+*/
+int user_duplicate( pcb_t *new, pcb_t *old ) {
+
+ // We need to do a recursive duplication of the process address
+ // space of the current process. First, we create a new user
+ // page directory. Next, we'll duplicate the USER_PDE page
+ // table. Finally, we'll go through that table and duplicate
+ // all the frames.
+
+ // create the initial VM hierarchy
+ pde_t *pdir = vm_mkuvm();
+ if( pdir == NULL ) {
+ return E_NO_MEMORY;
+ }
+ new->pdir = pdir;
+
+ // next, add a USER_PDE page table that's a duplicate of the
+ // current process' page table
+ if( !vm_uvmdup(old->pdir,new->pdir) ) {
+ // check for memory leak?
+ return E_NO_MEMORY;
+ }
+
+ // now, iterate through the entries, replacing the frame
+ // numbers with duplicate frames
+ //
+ // NOTE: we only deal with pdir[0] here, as we are limiting
+ // the user address space to the first 4MB
+ pte_t *pt = (pte_t *) (pdir[USER_PDE]);
+
+ for( int i = 0; i < N_PTE; ++i ) {
+
+ // if this entry is present,
+ if( IS_PRESENT(*pt) ) {
+
+ // duplicate the page
+ void *tmp = vm_pagedup( (void *) (*pt & FRAME_MASK) );
+ // replace the old frame number with the new one
+ *pt = (pte_t) (((uint32_t)tmp) | (*pt & PERM_MASK));
+
+ } else {
+
+ *pt = 0;
+
+ }
+ ++pt;
+ }
+
+ return SUCCESS;
+}
+
+/**
+** Name: user_load
+**
+** Loads a user program from the user code archive into memory.
+** Allocates all needed frames and sets up the VM tables.
+**
+** @param ptab A pointer to the program table entry to be loaded
+** @param pcb The PCB for the program being loaded
+** @param args The argument vector for the program
+**
+** @return the status of the load attempt
+*/
+int user_load( prog_t *ptab, pcb_t *pcb, const char **args ) {
+
+ // NULL pointers are bad!
+ assert1( ptab != NULL );
+ assert1( pcb != NULL );
+ assert1( args != NULL );
+
+ // locate the ELF binary
+ elfhdr_t *hdr = (elfhdr_t *) ((uint32_t)user_header + ptab->offset);
+
+#if TRACING_ELF
+ cio_printf( "Load: ptab %08x: '%s', off %08x, size %08x, flags %08x\n",
+ (uint32_t) ptab, ptab->name, ptab->offset, ptab->size,
+ ptab->flags );
+ cio_printf( " args %08x:", (uint32_t) args );
+ for( int i = 0; args[i] != NULL; ++i ) {
+ cio_printf( " [%d] %s", i, args[i] );
+ }
+ cio_printf( "\n pcb %08x (pid %u)\n", (uint32_t) pcb, pcb->pid );
+ dump_fhdr( hdr );
+#endif
+
+ // verify the ELF header
+ if( hdr->e_ident.f.magic != ELF_MAGIC ) {
+ return E_BAD_PARAM;
+ }
+
+ // allocate a page directory
+ pcb->pdir = vm_mkuvm();
+ if( pcb->pdir == NULL ) {
+ return E_NO_MEMORY;
+ }
+
+ // read all the program headers
+ int stat = read_phdrs( hdr, pcb );
+ if( stat != SUCCESS ) {
+ // TODO figure out a better way to deal with this
+ PANIC( 0, "user_load: phdr read failed" );
+ }
+
+ // next, set up the runtime stack - just like setting up loadable
+ // sections, except nothing to copy
+ stat = vm_add( pcb->pdir, true, false, (void *) USER_STACK,
+ SZ_USTACK, NULL, 0 );
+ if( stat != SUCCESS ) {
+ // TODO yadda yadda...
+ PANIC( 0, "user_load: vm_add failed" );
+ }
+
+ // set up the command-line arguments
+ pcb->context = stack_setup( pcb, hdr->e_entry, args );
+
+ return SUCCESS;
+}
+
+/**
+** Name: user_cleanup
+**
+** "Unloads" a user program. Deallocates all memory frames and
+** cleans up the VM structures.
+**
+** @param pcb The PCB of the program to be unloaded
+*/
+void user_cleanup( pcb_t *pcb ) {
+
+ if( pcb == NULL ) {
+ // should this be an error?
+ return;
+ }
+
+ vm_free( pcb->pdir );
+ pcb->pdir = NULL;
+}