summaryrefslogtreecommitdiff
path: root/kernel/old/user.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/old/user.c')
-rw-r--r--kernel/old/user.c929
1 files changed, 929 insertions, 0 deletions
diff --git a/kernel/old/user.c b/kernel/old/user.c
new file mode 100644
index 0000000..5759534
--- /dev/null
+++ b/kernel/old/user.c
@@ -0,0 +1,929 @@
+/**
+** @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
+ATTR_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
+** @param sys Is the argument vector from kernel code?
+**
+** @return A (user VA) pointer to the context_t on the stack, or NULL
+*/
+static context_t *stack_setup(pcb_t *pcb, uint32_t entry, const char **args,
+ bool_t sys)
+{
+#if TRACING_USER
+ cio_printf("stksetup: pcb %08x, entry %08x, args %08x\n", (uint32_t)pcb,
+ entry, (uint32_t)args);
+#endif
+
+ /*
+ ** First, we need to calculate the space we'll need for the argument
+ ** vector and strings.
+ **
+ ** Keeping track of kernel vs. user VAs is tricky, so we'll use
+ ** a prefix on variable names: kv_* is a kernel virtual address;
+ ** uv_* is a user virtual address.
+ **
+ ** We rely on the C standard, section 6.7.8, to clear these arrays:
+ **
+ ** "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."
+ */
+
+ int argbytes = 0; // total length of arg strings
+ int argc = 0; // number of argv entries
+ const char *kv_strs[N_ARGS] = { 0 }; // converted user arg string pointers
+ int strlengths[N_ARGS] = { 0 }; // length of each string
+ const char *uv_argv[N_ARGS] = { 0 }; // argv pointers
+
+ /*
+ ** IF the argument list given to us came from user code, we need
+ ** to convert its address and the addresses it contains to kernel
+ ** VAs; otherwise, we can use them directly.
+ */
+ char **kv_args = sys ? args : vm_uva2kva(pcb->pdir, (void *)args);
+
+ while (kv_args[argc] != NULL) {
+ kv_strs[argc] = sys ? args[argc] :
+ vm_uva2kva(pcb->pdir, (void *)(kv_args[argc]));
+ strlengths[argc] = strlen(kv_strs[argc]) + 1;
+ // can't go over one page in size
+ if ((argbytes + strlengths[argc]) > SZ_PAGE) {
+ // oops - ignore this and any others
+ break;
+ }
+ argbytes += strlengths[argc];
+ ++argc;
+ }
+
+ // Round up the byte count to the next multiple of four.
+ argbytes = (argbytes + 3) & MOD4_MASK;
+
+ /*
+ ** 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.
+ **
+ ** Ultimately, this is what the bottom end of the stack will look like:
+ **
+ ** kvavptr
+ ** kvacptr |
+ ** | |
+ ** v v
+ ** argc argv av[0] av[1] etc NULL str0 str1 etc.
+ ** [....][....][....][....] ... [0000] ... [......0......0.........]
+ ** | ^ | | ^ ^
+ ** | | | | | |
+ ** ------ | ---------------------|-------
+ ** ---------------------------
+ */
+
+ /*
+ ** We need to find the last page of the user stack. Find the page
+ ** table for the 4MB user address space. The physical address of its
+ ** frame is in the first page directory entry. Extract that from the
+ ** entry and convert it into a virtual address for the kernel to use.
+ */
+ pde_t *kv_userpt = (pde_t *)P2V(PTE_ADDR(pcb->pdir[USER_PDE]));
+ assert(kv_userpt != NULL);
+
+ /*
+ ** The final entries in that PMT are for the pages of the user stack.
+ ** Grab the physical address of the frame for the last one. (Again,
+ ** we need to convert it to a virtual address we can use.)
+ */
+
+ // the PMT entry for that page
+ pte_t pmt_entry = kv_userpt[USER_STK_LAST_PTE];
+ assert(IS_PRESENT(pmt_entry));
+
+ // user VA for the first byte of that page
+ uint32_t *uvptr = (uint32_t *)USER_STACK_P2;
+
+ // convert that address to a kernel VA
+ uint32_t *kvptr = (uint32_t *)vm_uva2kva(pcb->pdir, (void *)uvptr);
+
+ /*
+ ** Move these pointers to where the string area will begin. We
+ ** will then back up to the next lower multiple-of-four address.
+ */
+
+ uint32_t uvstrptr = ((uint32_t)uvptr) + SZ_PAGE - argbytes;
+ uvstrptr &= MOD4_MASK;
+
+ uint32_t kvstrptr = ((uint32_t)kvptr) + SZ_PAGE - argbytes;
+ kvstrptr &= MOD4_MASK;
+
+ // Copy over the argv strings, remembering where each string begins
+ for (int i = 0; i < argc; ++i) {
+ // copy the string using kernel addresses
+ strcpy((char *)kvstrptr, kv_args[i]);
+
+ // remember the user address where this string went
+ uv_argv[i] = (char *)uvstrptr;
+
+ // adjust both string addresses
+ kvstrptr += strlengths[i];
+ uvstrptr += strlengths[i];
+ }
+
+ /*
+ ** Next, we need to copy over the other data. 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. In our case, that is 'argc'.
+ */
+
+ /*
+ ** 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 the string area.
+ */
+
+ int nwords = argc + 3;
+ uint32_t *kvacptr = ((uint32_t *)kvstrptr) - nwords;
+ uint32_t *uvacptr = ((uint32_t *)uvstrptr) - nwords;
+
+ // back these up to multiple-of-16 addresses for stack alignment
+ kvacptr = (uint32_t *)(((uint32_t)kvacptr) & MOD16_MASK);
+ uvacptr = (uint32_t *)(((uint32_t)uvacptr) & MOD16_MASK);
+
+ // copy in 'argc'
+ *kvacptr = argc;
+
+ // 'argv' immediately follows 'argc', and 'argv[0]' immediately
+ // follows 'argv'
+ uint32_t *kvavptr = kvacptr + 2;
+ *(kvavptr - 1) = (uint32_t)kvavptr;
+
+ // now, the argv entries themselves
+ for (int i = 0; i < argc; ++i) {
+ *kvavptr++ = (uint32_t)uv_argv[i];
+ }
+
+ // and the trailing NULL
+ *kvavptr = NULL;
+
+ /*
+ ** Almost done!
+ **
+ ** 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 by backup up one
+ // "context" from where the argc value is saved
+ context_t *kvctx = ((context_t *)kvacptr) - 1;
+ uint32_t uvctx = (uint32_t)(((context_t *)uvacptr) - 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.
+ **
+ ** Note: we don't need to set the ESP value for the process,
+ ** as the 'popa' that restores the general registers doesn't
+ ** actually restore ESP from the context area - it leaves it
+ ** where it winds up.
+ */
+
+ kvctx->eflags = DEFAULT_EFLAGS; // IF enabled, IOPL 0
+ kvctx->eip = entry; // initial EIP
+ kvctx->cs = GDT_CODE; // segment registers
+ kvctx->ss = GDT_STACK;
+ kvctx->ds = kvctx->es = kvctx->fs = kvctx->gs = GDT_DATA;
+
+ /*
+ ** Return the new context pointer to the caller as a user
+ ** space virtual address.
+ */
+
+ return ((context_t *)uvctx);
+}
+
+/*
+** 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 *)P2V(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)
+{
+#if TRACING_USER
+ cio_printf("ulocate: %u\n", what);
+#endif
+
+ // 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)
+{
+#if TRACING_USER
+ cio_printf("udup: old %08x new %08x\n", (uint32_t)old, (uint32_t)new);
+#endif
+
+ // 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(new->pdir, old->pdir)) {
+ // check for memory leak?
+ return E_NO_MEMORY;
+ }
+
+ // We don't do copy-on-write, so we must duplicate all the
+ // individual page frames. Iterate through all the user-level
+ // PDE entries, and replace the existing frames with duplicates.
+ //
+ // NOTE: we only deal with pdir[0] here, as we are limiting
+ // the user address space to the first 4MB. If the size of
+ // the address space goes up, this code will need to be
+ // modified to loop over the larger space.
+
+ // pointer to the PMT for the user
+ pte_t *pt = (pte_t *)(pdir[USER_PDE]);
+ assert(pt != NULL);
+
+ for (int i = 0; i < N_PTE; ++i) {
+ // get the current entry from the PMT
+ pte_t entry = *pt;
+
+ // if this entry is present,
+ if (IS_PRESENT(entry)) {
+ // duplicate the frame pointed to by this PTE
+ void *tmp = vm_pagedup((void *)PTE_ADDR(entry));
+
+ // replace the old frame number with the new one
+ *pt = (pte_t)(((uint32_t)tmp) | PERMS(entry));
+
+ } 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
+** @param sys Is the argument vector from kernel code?
+**
+** @return the status of the load attempt
+*/
+int user_load(prog_t *ptab, pcb_t *pcb, const char **args, bool_t sys)
+{
+ // NULL pointers are bad!
+ assert1(ptab != NULL);
+ assert1(pcb != NULL);
+ assert1(args != NULL);
+
+#if TRACING_USER
+ cio_printf("Uload: prog '%s' pcb %08x args %08x\n",
+ ptab->name[0] ? ptab->name : "?", (uint32_t)pcb, (uint32_t)args);
+#endif
+
+ // 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);
+ if (sys) {
+ for (int i = 0; args[i] != NULL; ++i) {
+ cio_printf(" [%d] %s", i, args[i]);
+ }
+ } else {
+ char **kv_args = vm_uva2kva(pcb->pdir, args);
+ for (int i = 0; kv_args[i] != NULL; ++i) {
+ cio_printf(" [%d] %s", i,
+ (char *)vm_uva2kva(pcb->pdir, kv_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) {
+ cio_printf("Uload: read_phdrs('%s') returned %d\n", ptab->name, stat);
+ 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) {
+ cio_printf("Uload: vm_add('%s') stack returned %d\n", ptab->name, stat);
+ PANIC(0, "user_load: vm_add stack failed");
+ }
+
+ // set up the command-line arguments
+ pcb->context = stack_setup(pcb, hdr->e_entry, args, sys);
+
+ 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 TRACING_USER
+ cio_printf("Uclean: %08x\n", (uint32_t)pcb);
+#endif
+
+ if (pcb == NULL) {
+ // should this be an error?
+ return;
+ }
+
+ vm_free(pcb->pdir);
+ pcb->pdir = NULL;
+}