diff --git a/GNUmakefile b/GNUmakefile index 115c1bf..1820f31 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -138,6 +138,8 @@ $(OBJDIR)/.vars.%: FORCE # Include Makefrags for subdirectories include boot/Makefrag include kern/Makefrag +include lib/Makefrag +include user/Makefrag QEMUOPTS = -drive file=$(OBJDIR)/kern/kernel.img,index=0,media=disk,format=raw -serial mon:stdio -gdb tcp::$(GDBPORT) @@ -298,6 +300,22 @@ myapi.key: #handin-prep: # @./handin-prep +# For test runs + +prep-%: + $(V)$(MAKE) "INIT_CFLAGS=${INIT_CFLAGS} -DTEST=`case $* in *_*) echo $*;; *) echo user_$*;; esac`" $(IMAGES) + +run-%-nox-gdb: prep-% pre-qemu + $(QEMU) -nographic $(QEMUOPTS) -S + +run-%-gdb: prep-% pre-qemu + $(QEMU) $(QEMUOPTS) -S + +run-%-nox: prep-% pre-qemu + $(QEMU) -nographic $(QEMUOPTS) + +run-%: prep-% pre-qemu + $(QEMU) $(QEMUOPTS) # This magic automatically generates makefile dependencies # for header files included from C source files we compile, diff --git a/conf/lab.mk b/conf/lab.mk index c175f9c..f74edb8 100644 --- a/conf/lab.mk +++ b/conf/lab.mk @@ -1,2 +1,2 @@ -LAB=2 -PACKAGEDATE=Wed Sep 12 14:51:29 EDT 2018 +LAB=3 +PACKAGEDATE=Tue Sep 25 12:21:10 EDT 2018 diff --git a/grade-lab3 b/grade-lab3 new file mode 100755 index 0000000..365ba56 --- /dev/null +++ b/grade-lab3 @@ -0,0 +1,135 @@ +#!/usr/bin/env python + +from gradelib import * + +r = Runner(save("jos.out"), + stop_breakpoint("readline")) + +@test(10) +def test_divzero(): + r.user_test("divzero") + r.match('Incoming TRAP frame at 0xefffff..', + 'TRAP frame at 0xf.......', + ' trap 0x00000000 Divide error', + ' eip 0x008.....', + ' ss 0x----0023', + '.00001000. free env 00001000', + no=['1/0 is ........!']) + +@test(10) +def test_softint(): + r.user_test("softint") + r.match('Welcome to the JOS kernel monitor!', + 'Incoming TRAP frame at 0xefffffbc', + 'TRAP frame at 0xf.......', + ' trap 0x0000000d General Protection', + ' eip 0x008.....', + ' ss 0x----0023', + '.00001000. free env 0000100') + +@test(10) +def test_badsegment(): + r.user_test("badsegment") + r.match('Incoming TRAP frame at 0xefffffbc', + 'TRAP frame at 0xf.......', + ' trap 0x0000000d General Protection', + ' err 0x00000028', + ' eip 0x008.....', + ' ss 0x----0023', + '.00001000. free env 0000100') + +end_part("A") + +@test(5) +def test_faultread(): + r.user_test("faultread") + r.match('.00001000. user fault va 00000000 ip 008.....', + 'Incoming TRAP frame at 0xefffffbc', + 'TRAP frame at 0xf.......', + ' trap 0x0000000e Page Fault', + ' err 0x00000004.*', + '.00001000. free env 0000100', + no=['I read ........ from location 0!']) + +@test(5) +def test_faultreadkernel(): + r.user_test("faultreadkernel") + r.match('.00001000. user fault va f0100000 ip 008.....', + 'Incoming TRAP frame at 0xefffffbc', + 'TRAP frame at 0xf.......', + ' trap 0x0000000e Page Fault', + ' err 0x00000005.*', + '.00001000. free env 00001000', + no=['I read ........ from location 0xf0100000!']) + +@test(5) +def test_faultwrite(): + r.user_test("faultwrite") + r.match('.00001000. user fault va 00000000 ip 008.....', + 'Incoming TRAP frame at 0xefffffbc', + 'TRAP frame at 0xf.......', + ' trap 0x0000000e Page Fault', + ' err 0x00000006.*', + '.00001000. free env 0000100') + +@test(5) +def test_faultwritekernel(): + r.user_test("faultwritekernel") + r.match('.00001000. user fault va f0100000 ip 008.....', + 'Incoming TRAP frame at 0xefffffbc', + 'TRAP frame at 0xf.......', + ' trap 0x0000000e Page Fault', + ' err 0x00000007.*', + '.00001000. free env 0000100') + +@test(5) +def test_breakpoint(): + r.user_test("breakpoint") + r.match('Welcome to the JOS kernel monitor!', + 'Incoming TRAP frame at 0xefffffbc', + 'TRAP frame at 0xf.......', + ' trap 0x00000003 Breakpoint', + ' eip 0x008.....', + ' ss 0x----0023', + no=['.00001000. free env 00001000']) + +@test(5) +def test_testbss(): + r.user_test("testbss") + r.match('Making sure bss works right...', + 'Yes, good. Now doing a wild write off the end...', + '.00001000. user fault va 00c..... ip 008.....', + '.00001000. free env 0000100') + +@test(5) +def test_hello(): + r.user_test("hello") + r.match('.00000000. new env 00001000', + 'hello, world', + 'i am environment 00001000', + '.00001000. exiting gracefully', + '.00001000. free env 00001000', + 'Destroyed the only environment - nothing more to do!') + +@test(5) +def test_buggyhello(): + r.user_test("buggyhello") + r.match('.00001000. user_mem_check assertion failure for va 00000001', + '.00001000. free env 00001000') + +@test(5) +def test_buggyhello2(): + r.user_test("buggyhello2") + r.match('.00001000. user_mem_check assertion failure for va 0....000', + '.00001000. free env 00001000', + no=['hello, world']) + +@test(5) +def test_evilhello(): + r.user_test("evilhello") + r.match('.00001000. user_mem_check assertion failure for va f0100...', + '.00001000. free env 00001000') + +end_part("B") + +run_tests() diff --git a/inc/env.h b/inc/env.h new file mode 100644 index 0000000..19b3de7 --- /dev/null +++ b/inc/env.h @@ -0,0 +1,59 @@ +/* See COPYRIGHT for copyright information. */ + +#ifndef JOS_INC_ENV_H +#define JOS_INC_ENV_H + +#include +#include +#include + +typedef int32_t envid_t; + +// An environment ID 'envid_t' has three parts: +// +// +1+---------------21-----------------+--------10--------+ +// |0| Uniqueifier | Environment | +// | | | Index | +// +------------------------------------+------------------+ +// \--- ENVX(eid) --/ +// +// The environment index ENVX(eid) equals the environment's index in the +// 'envs[]' array. The uniqueifier distinguishes environments that were +// created at different times, but share the same environment index. +// +// All real environments are greater than 0 (so the sign bit is zero). +// envid_ts less than 0 signify errors. The envid_t == 0 is special, and +// stands for the current environment. + +#define LOG2NENV 10 +#define NENV (1 << LOG2NENV) +#define ENVX(envid) ((envid) & (NENV - 1)) + +// Values of env_status in struct Env +enum { + ENV_FREE = 0, + ENV_DYING, + ENV_RUNNABLE, + ENV_RUNNING, + ENV_NOT_RUNNABLE +}; + +// Special environment types +enum EnvType { + ENV_TYPE_USER = 0, +}; + +struct Env { + struct Trapframe env_tf; // Saved registers + struct Env *env_link; // Next free Env + envid_t env_id; // Unique environment identifier + envid_t env_parent_id; // env_id of this env's parent + enum EnvType env_type; // Indicates special system environments + unsigned env_status; // Status of the environment + uint32_t env_runs; // Number of times environment has run + + // Address space + pde_t *env_pgdir; // Kernel virtual address of page dir +}; + +#endif // !JOS_INC_ENV_H diff --git a/inc/lib.h b/inc/lib.h new file mode 100644 index 0000000..611a57d --- /dev/null +++ b/inc/lib.h @@ -0,0 +1,56 @@ +// Main public header file for our user-land support library, +// whose code lives in the lib directory. +// This library is roughly our OS's version of a standard C library, +// and is intended to be linked into all user-mode applications +// (NOT the kernel or boot loader). + +#ifndef JOS_INC_LIB_H +#define JOS_INC_LIB_H 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USED(x) (void)(x) + +// main user program +void umain(int argc, char **argv); + +// libmain.c or entry.S +extern const char *binaryname; +extern const volatile struct Env *thisenv; +extern const volatile struct Env envs[NENV]; +extern const volatile struct PageInfo pages[]; + +// exit.c +void exit(void); + +// readline.c +char* readline(const char *buf); + +// syscall.c +void sys_cputs(const char *string, size_t len); +int sys_cgetc(void); +envid_t sys_getenvid(void); +int sys_env_destroy(envid_t); + + + +/* File open modes */ +#define O_RDONLY 0x0000 /* open for reading only */ +#define O_WRONLY 0x0001 /* open for writing only */ +#define O_RDWR 0x0002 /* open for reading and writing */ +#define O_ACCMODE 0x0003 /* mask for above modes */ + +#define O_CREAT 0x0100 /* create if nonexistent */ +#define O_TRUNC 0x0200 /* truncate to zero length */ +#define O_EXCL 0x0400 /* error if already exists */ +#define O_MKDIR 0x0800 /* create directory, not regular file */ + +#endif // !JOS_INC_LIB_H diff --git a/inc/syscall.h b/inc/syscall.h new file mode 100644 index 0000000..fd8df06 --- /dev/null +++ b/inc/syscall.h @@ -0,0 +1,13 @@ +#ifndef JOS_INC_SYSCALL_H +#define JOS_INC_SYSCALL_H + +/* system call numbers */ +enum { + SYS_cputs = 0, + SYS_cgetc, + SYS_getenvid, + SYS_env_destroy, + NSYSCALLS +}; + +#endif /* !JOS_INC_SYSCALL_H */ diff --git a/inc/trap.h b/inc/trap.h new file mode 100644 index 0000000..c3437af --- /dev/null +++ b/inc/trap.h @@ -0,0 +1,80 @@ +#ifndef JOS_INC_TRAP_H +#define JOS_INC_TRAP_H + +// Trap numbers +// These are processor defined: +#define T_DIVIDE 0 // divide error +#define T_DEBUG 1 // debug exception +#define T_NMI 2 // non-maskable interrupt +#define T_BRKPT 3 // breakpoint +#define T_OFLOW 4 // overflow +#define T_BOUND 5 // bounds check +#define T_ILLOP 6 // illegal opcode +#define T_DEVICE 7 // device not available +#define T_DBLFLT 8 // double fault +/* #define T_COPROC 9 */ // reserved (not generated by recent processors) +#define T_TSS 10 // invalid task switch segment +#define T_SEGNP 11 // segment not present +#define T_STACK 12 // stack exception +#define T_GPFLT 13 // general protection fault +#define T_PGFLT 14 // page fault +/* #define T_RES 15 */ // reserved +#define T_FPERR 16 // floating point error +#define T_ALIGN 17 // aligment check +#define T_MCHK 18 // machine check +#define T_SIMDERR 19 // SIMD floating point error + +// These are arbitrarily chosen, but with care not to overlap +// processor defined exceptions or interrupt vectors. +#define T_SYSCALL 48 // system call +#define T_DEFAULT 500 // catchall + +#define IRQ_OFFSET 32 // IRQ 0 corresponds to int IRQ_OFFSET + +// Hardware IRQ numbers. We receive these as (IRQ_OFFSET+IRQ_WHATEVER) +#define IRQ_TIMER 0 +#define IRQ_KBD 1 +#define IRQ_SERIAL 4 +#define IRQ_SPURIOUS 7 +#define IRQ_IDE 14 +#define IRQ_ERROR 19 + +#ifndef __ASSEMBLER__ + +#include + +struct PushRegs { + /* registers as pushed by pusha */ + uint32_t reg_edi; + uint32_t reg_esi; + uint32_t reg_ebp; + uint32_t reg_oesp; /* Useless */ + uint32_t reg_ebx; + uint32_t reg_edx; + uint32_t reg_ecx; + uint32_t reg_eax; +} __attribute__((packed)); + +struct Trapframe { + struct PushRegs tf_regs; + uint16_t tf_es; + uint16_t tf_padding1; + uint16_t tf_ds; + uint16_t tf_padding2; + uint32_t tf_trapno; + /* below here defined by x86 hardware */ + uint32_t tf_err; + uintptr_t tf_eip; + uint16_t tf_cs; + uint16_t tf_padding3; + uint32_t tf_eflags; + /* below here only when crossing rings, such as from user to kernel */ + uintptr_t tf_esp; + uint16_t tf_ss; + uint16_t tf_padding4; +} __attribute__((packed)); + + +#endif /* !__ASSEMBLER__ */ + +#endif /* !JOS_INC_TRAP_H */ diff --git a/kern/Makefrag b/kern/Makefrag index 3b2982e..b39cff2 100644 --- a/kern/Makefrag +++ b/kern/Makefrag @@ -36,7 +36,20 @@ KERN_SRCFILES := kern/entry.S \ KERN_SRCFILES := $(wildcard $(KERN_SRCFILES)) # Binary program images to embed within the kernel. -KERN_BINFILES := +# Binary files for LAB3 +KERN_BINFILES := user/hello \ + user/buggyhello \ + user/buggyhello2 \ + user/evilhello \ + user/testbss \ + user/divzero \ + user/breakpoint \ + user/softint \ + user/badsegment \ + user/faultread \ + user/faultreadkernel \ + user/faultwrite \ + user/faultwritekernel KERN_OBJFILES := $(patsubst %.c, $(OBJDIR)/%.o, $(KERN_SRCFILES)) KERN_OBJFILES := $(patsubst %.S, $(OBJDIR)/%.o, $(KERN_OBJFILES)) diff --git a/kern/entry.S b/kern/entry.S index 9550bbb..6ab9bad 100644 --- a/kern/entry.S +++ b/kern/entry.S @@ -2,6 +2,7 @@ #include #include +#include # Shift Right Logical #define SRL(val, shamt) (((val) >> (shamt)) & ~(-1 << (32 - (shamt)))) diff --git a/kern/env.c b/kern/env.c new file mode 100644 index 0000000..db2fda9 --- /dev/null +++ b/kern/env.c @@ -0,0 +1,463 @@ +/* See COPYRIGHT for copyright information. */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +struct Env *envs = NULL; // All environments +struct Env *curenv = NULL; // The current env +static struct Env *env_free_list; // Free environment list + // (linked by Env->env_link) + +#define ENVGENSHIFT 12 // >= LOGNENV + +// Global descriptor table. +// +// Set up global descriptor table (GDT) with separate segments for +// kernel mode and user mode. Segments serve many purposes on the x86. +// We don't use any of their memory-mapping capabilities, but we need +// them to switch privilege levels. +// +// The kernel and user segments are identical except for the DPL. +// To load the SS register, the CPL must equal the DPL. Thus, +// we must duplicate the segments for the user and the kernel. +// +// In particular, the last argument to the SEG macro used in the +// definition of gdt specifies the Descriptor Privilege Level (DPL) +// of that descriptor: 0 for kernel and 3 for user. +// +struct Segdesc gdt[] = +{ + // 0x0 - unused (always faults -- for trapping NULL far pointers) + SEG_NULL, + + // 0x8 - kernel code segment + [GD_KT >> 3] = SEG(STA_X | STA_R, 0x0, 0xffffffff, 0), + + // 0x10 - kernel data segment + [GD_KD >> 3] = SEG(STA_W, 0x0, 0xffffffff, 0), + + // 0x18 - user code segment + [GD_UT >> 3] = SEG(STA_X | STA_R, 0x0, 0xffffffff, 3), + + // 0x20 - user data segment + [GD_UD >> 3] = SEG(STA_W, 0x0, 0xffffffff, 3), + + // 0x28 - tss, initialized in trap_init_percpu() + [GD_TSS0 >> 3] = SEG_NULL +}; + +struct Pseudodesc gdt_pd = { + sizeof(gdt) - 1, (unsigned long) gdt +}; + +// +// Converts an envid to an env pointer. +// If checkperm is set, the specified environment must be either the +// current environment or an immediate child of the current environment. +// +// RETURNS +// 0 on success, -E_BAD_ENV on error. +// On success, sets *env_store to the environment. +// On error, sets *env_store to NULL. +// +int +envid2env(envid_t envid, struct Env **env_store, bool checkperm) +{ + struct Env *e; + + // If envid is zero, return the current environment. + if (envid == 0) { + *env_store = curenv; + return 0; + } + + // Look up the Env structure via the index part of the envid, + // then check the env_id field in that struct Env + // to ensure that the envid is not stale + // (i.e., does not refer to a _previous_ environment + // that used the same slot in the envs[] array). + e = &envs[ENVX(envid)]; + if (e->env_status == ENV_FREE || e->env_id != envid) { + *env_store = 0; + return -E_BAD_ENV; + } + + // Check that the calling environment has legitimate permission + // to manipulate the specified environment. + // If checkperm is set, the specified environment + // must be either the current environment + // or an immediate child of the current environment. + if (checkperm && e != curenv && e->env_parent_id != curenv->env_id) { + *env_store = 0; + return -E_BAD_ENV; + } + + *env_store = e; + return 0; +} + +// Mark all environments in 'envs' as free, set their env_ids to 0, +// and insert them into the env_free_list. +// Make sure the environments are in the free list in the same order +// they are in the envs array (i.e., so that the first call to +// env_alloc() returns envs[0]). +// +void +env_init(void) +{ + // Set up envs array + // LAB 3: Your code here. + + // Per-CPU part of the initialization + env_init_percpu(); +} + +// Load GDT and segment descriptors. +void +env_init_percpu(void) +{ + lgdt(&gdt_pd); + // The kernel never uses GS or FS, so we leave those set to + // the user data segment. + asm volatile("movw %%ax,%%gs" : : "a" (GD_UD|3)); + asm volatile("movw %%ax,%%fs" : : "a" (GD_UD|3)); + // The kernel does use ES, DS, and SS. We'll change between + // the kernel and user data segments as needed. + asm volatile("movw %%ax,%%es" : : "a" (GD_KD)); + asm volatile("movw %%ax,%%ds" : : "a" (GD_KD)); + asm volatile("movw %%ax,%%ss" : : "a" (GD_KD)); + // Load the kernel text segment into CS. + asm volatile("ljmp %0,$1f\n 1:\n" : : "i" (GD_KT)); + // For good measure, clear the local descriptor table (LDT), + // since we don't use it. + lldt(0); +} + +// +// Initialize the kernel virtual memory layout for environment e. +// Allocate a page directory, set e->env_pgdir accordingly, +// and initialize the kernel portion of the new environment's address space. +// Do NOT (yet) map anything into the user portion +// of the environment's virtual address space. +// +// Returns 0 on success, < 0 on error. Errors include: +// -E_NO_MEM if page directory or table could not be allocated. +// +static int +env_setup_vm(struct Env *e) +{ + int i; + struct PageInfo *p = NULL; + + // Allocate a page for the page directory + if (!(p = page_alloc(ALLOC_ZERO))) + return -E_NO_MEM; + + // Now, set e->env_pgdir and initialize the page directory. + // + // Hint: + // - The VA space of all envs is identical above UTOP + // (except at UVPT, which we've set below). + // See inc/memlayout.h for permissions and layout. + // Can you use kern_pgdir as a template? Hint: Yes. + // (Make sure you got the permissions right in Lab 2.) + // - The initial VA below UTOP is empty. + // - You do not need to make any more calls to page_alloc. + // - Note: In general, pp_ref is not maintained for + // physical pages mapped only above UTOP, but env_pgdir + // is an exception -- you need to increment env_pgdir's + // pp_ref for env_free to work correctly. + // - The functions in kern/pmap.h are handy. + + // LAB 3: Your code here. + + // UVPT maps the env's own page table read-only. + // Permissions: kernel R, user R + e->env_pgdir[PDX(UVPT)] = PADDR(e->env_pgdir) | PTE_P | PTE_U; + + return 0; +} + +// +// Allocates and initializes a new environment. +// On success, the new environment is stored in *newenv_store. +// +// Returns 0 on success, < 0 on failure. Errors include: +// -E_NO_FREE_ENV if all NENV environments are allocated +// -E_NO_MEM on memory exhaustion +// +int +env_alloc(struct Env **newenv_store, envid_t parent_id) +{ + int32_t generation; + int r; + struct Env *e; + + if (!(e = env_free_list)) + return -E_NO_FREE_ENV; + + // Allocate and set up the page directory for this environment. + if ((r = env_setup_vm(e)) < 0) + return r; + + // Generate an env_id for this environment. + generation = (e->env_id + (1 << ENVGENSHIFT)) & ~(NENV - 1); + if (generation <= 0) // Don't create a negative env_id. + generation = 1 << ENVGENSHIFT; + e->env_id = generation | (e - envs); + + // Set the basic status variables. + e->env_parent_id = parent_id; + e->env_type = ENV_TYPE_USER; + e->env_status = ENV_RUNNABLE; + e->env_runs = 0; + + // Clear out all the saved register state, + // to prevent the register values + // of a prior environment inhabiting this Env structure + // from "leaking" into our new environment. + memset(&e->env_tf, 0, sizeof(e->env_tf)); + + // Set up appropriate initial values for the segment registers. + // GD_UD is the user data segment selector in the GDT, and + // GD_UT is the user text segment selector (see inc/memlayout.h). + // The low 2 bits of each segment register contains the + // Requestor Privilege Level (RPL); 3 means user mode. When + // we switch privilege levels, the hardware does various + // checks involving the RPL and the Descriptor Privilege Level + // (DPL) stored in the descriptors themselves. + e->env_tf.tf_ds = GD_UD | 3; + e->env_tf.tf_es = GD_UD | 3; + e->env_tf.tf_ss = GD_UD | 3; + e->env_tf.tf_esp = USTACKTOP; + e->env_tf.tf_cs = GD_UT | 3; + // You will set e->env_tf.tf_eip later. + + // commit the allocation + env_free_list = e->env_link; + *newenv_store = e; + + cprintf("[%08x] new env %08x\n", curenv ? curenv->env_id : 0, e->env_id); + return 0; +} + +// +// Allocate len bytes of physical memory for environment env, +// and map it at virtual address va in the environment's address space. +// Does not zero or otherwise initialize the mapped pages in any way. +// Pages should be writable by user and kernel. +// Panic if any allocation attempt fails. +// +static void +region_alloc(struct Env *e, void *va, size_t len) +{ + // LAB 3: Your code here. + // (But only if you need it for load_icode.) + // + // Hint: It is easier to use region_alloc if the caller can pass + // 'va' and 'len' values that are not page-aligned. + // You should round va down, and round (va + len) up. + // (Watch out for corner-cases!) +} + +// +// Set up the initial program binary, stack, and processor flags +// for a user process. +// This function is ONLY called during kernel initialization, +// before running the first user-mode environment. +// +// This function loads all loadable segments from the ELF binary image +// into the environment's user memory, starting at the appropriate +// virtual addresses indicated in the ELF program header. +// At the same time it clears to zero any portions of these segments +// that are marked in the program header as being mapped +// but not actually present in the ELF file - i.e., the program's bss section. +// +// All this is very similar to what our boot loader does, except the boot +// loader also needs to read the code from disk. Take a look at +// boot/main.c to get ideas. +// +// Finally, this function maps one page for the program's initial stack. +// +// load_icode panics if it encounters problems. +// - How might load_icode fail? What might be wrong with the given input? +// +static void +load_icode(struct Env *e, uint8_t *binary) +{ + // Hints: + // Load each program segment into virtual memory + // at the address specified in the ELF segment header. + // You should only load segments with ph->p_type == ELF_PROG_LOAD. + // Each segment's virtual address can be found in ph->p_va + // and its size in memory can be found in ph->p_memsz. + // The ph->p_filesz bytes from the ELF binary, starting at + // 'binary + ph->p_offset', should be copied to virtual address + // ph->p_va. Any remaining memory bytes should be cleared to zero. + // (The ELF header should have ph->p_filesz <= ph->p_memsz.) + // Use functions from the previous lab to allocate and map pages. + // + // All page protection bits should be user read/write for now. + // ELF segments are not necessarily page-aligned, but you can + // assume for this function that no two segments will touch + // the same virtual page. + // + // You may find a function like region_alloc useful. + // + // Loading the segments is much simpler if you can move data + // directly into the virtual addresses stored in the ELF binary. + // So which page directory should be in force during + // this function? + // + // You must also do something with the program's entry point, + // to make sure that the environment starts executing there. + // What? (See env_run() and env_pop_tf() below.) + + // LAB 3: Your code here. + + // Now map one page for the program's initial stack + // at virtual address USTACKTOP - PGSIZE. + + // LAB 3: Your code here. +} + +// +// Allocates a new env with env_alloc, loads the named elf +// binary into it with load_icode, and sets its env_type. +// This function is ONLY called during kernel initialization, +// before running the first user-mode environment. +// The new env's parent ID is set to 0. +// +void +env_create(uint8_t *binary, enum EnvType type) +{ + // LAB 3: Your code here. +} + +// +// Frees env e and all memory it uses. +// +void +env_free(struct Env *e) +{ + pte_t *pt; + uint32_t pdeno, pteno; + physaddr_t pa; + + // If freeing the current environment, switch to kern_pgdir + // before freeing the page directory, just in case the page + // gets reused. + if (e == curenv) + lcr3(PADDR(kern_pgdir)); + + // Note the environment's demise. + cprintf("[%08x] free env %08x\n", curenv ? curenv->env_id : 0, e->env_id); + + // Flush all mapped pages in the user portion of the address space + static_assert(UTOP % PTSIZE == 0); + for (pdeno = 0; pdeno < PDX(UTOP); pdeno++) { + + // only look at mapped page tables + if (!(e->env_pgdir[pdeno] & PTE_P)) + continue; + + // find the pa and va of the page table + pa = PTE_ADDR(e->env_pgdir[pdeno]); + pt = (pte_t*) KADDR(pa); + + // unmap all PTEs in this page table + for (pteno = 0; pteno <= PTX(~0); pteno++) { + if (pt[pteno] & PTE_P) + page_remove(e->env_pgdir, PGADDR(pdeno, pteno, 0)); + } + + // free the page table itself + e->env_pgdir[pdeno] = 0; + page_decref(pa2page(pa)); + } + + // free the page directory + pa = PADDR(e->env_pgdir); + e->env_pgdir = 0; + page_decref(pa2page(pa)); + + // return the environment to the free list + e->env_status = ENV_FREE; + e->env_link = env_free_list; + env_free_list = e; +} + +// +// Frees environment e. +// +void +env_destroy(struct Env *e) +{ + env_free(e); + + cprintf("Destroyed the only environment - nothing more to do!\n"); + while (1) + monitor(NULL); +} + + +// +// Restores the register values in the Trapframe with the 'iret' instruction. +// This exits the kernel and starts executing some environment's code. +// +// This function does not return. +// +void +env_pop_tf(struct Trapframe *tf) +{ + asm volatile( + "\tmovl %0,%%esp\n" + "\tpopal\n" + "\tpopl %%es\n" + "\tpopl %%ds\n" + "\taddl $0x8,%%esp\n" /* skip tf_trapno and tf_errcode */ + "\tiret\n" + : : "g" (tf) : "memory"); + panic("iret failed"); /* mostly to placate the compiler */ +} + +// +// Context switch from curenv to env e. +// Note: if this is the first call to env_run, curenv is NULL. +// +// This function does not return. +// +void +env_run(struct Env *e) +{ + // Step 1: If this is a context switch (a new environment is running): + // 1. Set the current environment (if any) back to + // ENV_RUNNABLE if it is ENV_RUNNING (think about + // what other states it can be in), + // 2. Set 'curenv' to the new environment, + // 3. Set its status to ENV_RUNNING, + // 4. Update its 'env_runs' counter, + // 5. Use lcr3() to switch to its address space. + // Step 2: Use env_pop_tf() to restore the environment's + // registers and drop into user mode in the + // environment. + + // Hint: This function loads the new environment's state from + // e->env_tf. Go back through the code you wrote above + // and make sure you have set the relevant parts of + // e->env_tf to sensible values. + + // LAB 3: Your code here. + + panic("env_run not yet implemented"); +} + diff --git a/kern/env.h b/kern/env.h new file mode 100644 index 0000000..9c574c1 --- /dev/null +++ b/kern/env.h @@ -0,0 +1,35 @@ +/* See COPYRIGHT for copyright information. */ + +#ifndef JOS_KERN_ENV_H +#define JOS_KERN_ENV_H + +#include + +extern struct Env *envs; // All environments +extern struct Env *curenv; // Current environment +extern struct Segdesc gdt[]; + +void env_init(void); +void env_init_percpu(void); +int env_alloc(struct Env **e, envid_t parent_id); +void env_free(struct Env *e); +void env_create(uint8_t *binary, enum EnvType type); +void env_destroy(struct Env *e); // Does not return if e == curenv + +int envid2env(envid_t envid, struct Env **env_store, bool checkperm); +// The following two functions do not return +void env_run(struct Env *e) __attribute__((noreturn)); +void env_pop_tf(struct Trapframe *tf) __attribute__((noreturn)); + +// Without this extra macro, we couldn't pass macros like TEST to +// ENV_CREATE because of the C pre-processor's argument prescan rule. +#define ENV_PASTE3(x, y, z) x ## y ## z + +#define ENV_CREATE(x, type) \ + do { \ + extern uint8_t ENV_PASTE3(_binary_obj_, x, _start)[]; \ + env_create(ENV_PASTE3(_binary_obj_, x, _start), \ + type); \ + } while (0) + +#endif // !JOS_KERN_ENV_H diff --git a/kern/init.c b/kern/init.c index 1fb9152..3d4122c 100644 --- a/kern/init.c +++ b/kern/init.c @@ -8,6 +8,8 @@ #include #include #include +#include +#include void @@ -29,9 +31,20 @@ i386_init(void) // Lab 2 memory management initialization functions mem_init(); - // Drop into the kernel monitor. - while (1) - monitor(NULL); + // Lab 3 user environment initialization functions + env_init(); + trap_init(); + +#if defined(TEST) + // Don't touch -- used by grading script! + ENV_CREATE(TEST, ENV_TYPE_USER); +#else + // Touch all you want. + ENV_CREATE(user_hello, ENV_TYPE_USER); +#endif // TEST* + + // We only have one user environment for now, so just run it. + env_run(&envs[0]); } diff --git a/kern/kdebug.c b/kern/kdebug.c index 9547143..f4ee8ee 100644 --- a/kern/kdebug.c +++ b/kern/kdebug.c @@ -4,12 +4,21 @@ #include #include +#include +#include extern const struct Stab __STAB_BEGIN__[]; // Beginning of stabs table extern const struct Stab __STAB_END__[]; // End of stabs table extern const char __STABSTR_BEGIN__[]; // Beginning of string table extern const char __STABSTR_END__[]; // End of string table +struct UserStabData { + const struct Stab *stabs; + const struct Stab *stab_end; + const char *stabstr; + const char *stabstr_end; +}; + // stab_binsearch(stabs, region_left, region_right, type, addr) // @@ -123,8 +132,24 @@ debuginfo_eip(uintptr_t addr, struct Eipdebuginfo *info) stabstr = __STABSTR_BEGIN__; stabstr_end = __STABSTR_END__; } else { - // Can't search for user-level addresses yet! - panic("User address"); + // The user-application linker script, user/user.ld, + // puts information about the application's stabs (equivalent + // to __STAB_BEGIN__, __STAB_END__, __STABSTR_BEGIN__, and + // __STABSTR_END__) in a structure located at virtual address + // USTABDATA. + const struct UserStabData *usd = (const struct UserStabData *) USTABDATA; + + // Make sure this memory is valid. + // Return -1 if it is not. Hint: Call user_mem_check. + // LAB 3: Your code here. + + stabs = usd->stabs; + stab_end = usd->stab_end; + stabstr = usd->stabstr; + stabstr_end = usd->stabstr_end; + + // Make sure the STABS and string table memory is valid. + // LAB 3: Your code here. } // String table validity checks diff --git a/kern/monitor.c b/kern/monitor.c index e137e92..4e00796 100644 --- a/kern/monitor.c +++ b/kern/monitor.c @@ -10,6 +10,7 @@ #include #include #include +#include #define CMDBUF_SIZE 80 // enough for one VGA text line @@ -115,6 +116,8 @@ monitor(struct Trapframe *tf) cprintf("Welcome to the JOS kernel monitor!\n"); cprintf("Type 'help' for a list of commands.\n"); + if (tf != NULL) + print_trapframe(tf); while (1) { buf = readline("K> "); diff --git a/kern/pmap.c b/kern/pmap.c index 8c809f1..1716265 100644 --- a/kern/pmap.c +++ b/kern/pmap.c @@ -8,6 +8,7 @@ #include #include +#include // These variables are set by i386_detect_memory() size_t npages; // Amount of physical memory (in pages) @@ -150,6 +151,10 @@ mem_init(void) // Your code goes here: + ////////////////////////////////////////////////////////////////////// + // Make 'envs' point to an array of size 'NENV' of 'struct Env'. + // LAB 3: Your code here. + ////////////////////////////////////////////////////////////////////// // Now that we've allocated the initial kernel data structures, we set // up the list of free physical pages. Once we've done so, all further @@ -173,6 +178,14 @@ mem_init(void) // - pages itself -- kernel RW, user NONE // Your code goes here: + ////////////////////////////////////////////////////////////////////// + // Map the 'envs' array read-only by the user at linear address UENVS + // (ie. perm = PTE_U | PTE_P). + // Permissions: + // - the new image at UENVS -- kernel R, user R + // - envs itself -- kernel RW, user NONE + // LAB 3: Your code here. + ////////////////////////////////////////////////////////////////////// // Use the physical memory that 'bootstack' refers to as the kernel // stack. The kernel stack grows down from virtual address KSTACKTOP. @@ -430,6 +443,51 @@ tlb_invalidate(pde_t *pgdir, void *va) invlpg(va); } +static uintptr_t user_mem_check_addr; + +// +// Check that an environment is allowed to access the range of memory +// [va, va+len) with permissions 'perm | PTE_P'. +// Normally 'perm' will contain PTE_U at least, but this is not required. +// 'va' and 'len' need not be page-aligned; you must test every page that +// contains any of that range. You will test either 'len/PGSIZE', +// 'len/PGSIZE + 1', or 'len/PGSIZE + 2' pages. +// +// A user program can access a virtual address if (1) the address is below +// ULIM, and (2) the page table gives it permission. These are exactly +// the tests you should implement here. +// +// If there is an error, set the 'user_mem_check_addr' variable to the first +// erroneous virtual address. +// +// Returns 0 if the user program can access this range of addresses, +// and -E_FAULT otherwise. +// +int +user_mem_check(struct Env *env, const void *va, size_t len, int perm) +{ + // LAB 3: Your code here. + + return 0; +} + +// +// Checks that environment 'env' is allowed to access the range +// of memory [va, va+len) with permissions 'perm | PTE_U | PTE_P'. +// If it can, then the function simply returns. +// If it cannot, 'env' is destroyed and, if env is the current +// environment, this function will not return. +// +void +user_mem_assert(struct Env *env, const void *va, size_t len, int perm) +{ + if (user_mem_check(env, va, len, perm | PTE_U) < 0) { + cprintf("[%08x] user_mem_check assertion failure for " + "va %08x\n", env->env_id, user_mem_check_addr); + env_destroy(env); // may not return + } +} + // -------------------------------------------------------------- // Checking functions. @@ -595,6 +653,10 @@ check_kern_pgdir(void) for (i = 0; i < n; i += PGSIZE) assert(check_va2pa(pgdir, UPAGES + i) == PADDR(pages) + i); + // check envs array (new test for lab 3) + n = ROUNDUP(NENV*sizeof(struct Env), PGSIZE); + for (i = 0; i < n; i += PGSIZE) + assert(check_va2pa(pgdir, UENVS + i) == PADDR(envs) + i); // check phys mem for (i = 0; i < npages * PGSIZE; i += PGSIZE) @@ -611,6 +673,7 @@ check_kern_pgdir(void) case PDX(UVPT): case PDX(KSTACKTOP-1): case PDX(UPAGES): + case PDX(UENVS): assert(pgdir[i] & PTE_P); break; default: diff --git a/kern/pmap.h b/kern/pmap.h index 950cca1..ab0bee9 100644 --- a/kern/pmap.h +++ b/kern/pmap.h @@ -8,6 +8,7 @@ #include #include +struct Env; extern char bootstacktop[], bootstack[]; @@ -62,6 +63,9 @@ void page_decref(struct PageInfo *pp); void tlb_invalidate(pde_t *pgdir, void *va); +int user_mem_check(struct Env *env, const void *va, size_t len, int perm); +void user_mem_assert(struct Env *env, const void *va, size_t len, int perm); + static inline physaddr_t page2pa(struct PageInfo *pp) { diff --git a/kern/syscall.c b/kern/syscall.c new file mode 100644 index 0000000..414d489 --- /dev/null +++ b/kern/syscall.c @@ -0,0 +1,80 @@ +/* See COPYRIGHT for copyright information. */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +// Print a string to the system console. +// The string is exactly 'len' characters long. +// Destroys the environment on memory errors. +static void +sys_cputs(const char *s, size_t len) +{ + // Check that the user has permission to read memory [s, s+len). + // Destroy the environment if not. + + // LAB 3: Your code here. + + // Print the string supplied by the user. + cprintf("%.*s", len, s); +} + +// Read a character from the system console without blocking. +// Returns the character, or 0 if there is no input waiting. +static int +sys_cgetc(void) +{ + return cons_getc(); +} + +// Returns the current environment's envid. +static envid_t +sys_getenvid(void) +{ + return curenv->env_id; +} + +// Destroy a given environment (possibly the currently running environment). +// +// Returns 0 on success, < 0 on error. Errors are: +// -E_BAD_ENV if environment envid doesn't currently exist, +// or the caller doesn't have permission to change envid. +static int +sys_env_destroy(envid_t envid) +{ + int r; + struct Env *e; + + if ((r = envid2env(envid, &e, 1)) < 0) + return r; + if (e == curenv) + cprintf("[%08x] exiting gracefully\n", curenv->env_id); + else + cprintf("[%08x] destroying %08x\n", curenv->env_id, e->env_id); + env_destroy(e); + return 0; +} + +// Dispatches to the correct kernel function, passing the arguments. +int32_t +syscall(uint32_t syscallno, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5) +{ + // Call the function corresponding to the 'syscallno' parameter. + // Return any appropriate return value. + // LAB 3: Your code here. + + panic("syscall not implemented"); + + switch (syscallno) { + default: + return -E_INVAL; + } +} + diff --git a/kern/syscall.h b/kern/syscall.h new file mode 100644 index 0000000..e370801 --- /dev/null +++ b/kern/syscall.h @@ -0,0 +1,11 @@ +#ifndef JOS_KERN_SYSCALL_H +#define JOS_KERN_SYSCALL_H +#ifndef JOS_KERNEL +# error "This is a JOS kernel header; user programs should not #include it" +#endif + +#include + +int32_t syscall(uint32_t num, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5); + +#endif /* !JOS_KERN_SYSCALL_H */ diff --git a/kern/trap.c b/kern/trap.c new file mode 100644 index 0000000..e27b556 --- /dev/null +++ b/kern/trap.c @@ -0,0 +1,218 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static struct Taskstate ts; + +/* For debugging, so print_trapframe can distinguish between printing + * a saved trapframe and printing the current trapframe and print some + * additional information in the latter case. + */ +static struct Trapframe *last_tf; + +/* Interrupt descriptor table. (Must be built at run time because + * shifted function addresses can't be represented in relocation records.) + */ +struct Gatedesc idt[256] = { { 0 } }; +struct Pseudodesc idt_pd = { + sizeof(idt) - 1, (uint32_t) idt +}; + + +static const char *trapname(int trapno) +{ + static const char * const excnames[] = { + "Divide error", + "Debug", + "Non-Maskable Interrupt", + "Breakpoint", + "Overflow", + "BOUND Range Exceeded", + "Invalid Opcode", + "Device Not Available", + "Double Fault", + "Coprocessor Segment Overrun", + "Invalid TSS", + "Segment Not Present", + "Stack Fault", + "General Protection", + "Page Fault", + "(unknown trap)", + "x87 FPU Floating-Point Error", + "Alignment Check", + "Machine-Check", + "SIMD Floating-Point Exception" + }; + + if (trapno < ARRAY_SIZE(excnames)) + return excnames[trapno]; + if (trapno == T_SYSCALL) + return "System call"; + return "(unknown trap)"; +} + + +void +trap_init(void) +{ + extern struct Segdesc gdt[]; + + // LAB 3: Your code here. + + // Per-CPU setup + trap_init_percpu(); +} + +// Initialize and load the per-CPU TSS and IDT +void +trap_init_percpu(void) +{ + // Setup a TSS so that we get the right stack + // when we trap to the kernel. + ts.ts_esp0 = KSTACKTOP; + ts.ts_ss0 = GD_KD; + ts.ts_iomb = sizeof(struct Taskstate); + + // Initialize the TSS slot of the gdt. + gdt[GD_TSS0 >> 3] = SEG16(STS_T32A, (uint32_t) (&ts), + sizeof(struct Taskstate) - 1, 0); + gdt[GD_TSS0 >> 3].sd_s = 0; + + // Load the TSS selector (like other segment selectors, the + // bottom three bits are special; we leave them 0) + ltr(GD_TSS0); + + // Load the IDT + lidt(&idt_pd); +} + +void +print_trapframe(struct Trapframe *tf) +{ + cprintf("TRAP frame at %p\n", tf); + print_regs(&tf->tf_regs); + cprintf(" es 0x----%04x\n", tf->tf_es); + cprintf(" ds 0x----%04x\n", tf->tf_ds); + cprintf(" trap 0x%08x %s\n", tf->tf_trapno, trapname(tf->tf_trapno)); + // If this trap was a page fault that just happened + // (so %cr2 is meaningful), print the faulting linear address. + if (tf == last_tf && tf->tf_trapno == T_PGFLT) + cprintf(" cr2 0x%08x\n", rcr2()); + cprintf(" err 0x%08x", tf->tf_err); + // For page faults, print decoded fault error code: + // U/K=fault occurred in user/kernel mode + // W/R=a write/read caused the fault + // PR=a protection violation caused the fault (NP=page not present). + if (tf->tf_trapno == T_PGFLT) + cprintf(" [%s, %s, %s]\n", + tf->tf_err & 4 ? "user" : "kernel", + tf->tf_err & 2 ? "write" : "read", + tf->tf_err & 1 ? "protection" : "not-present"); + else + cprintf("\n"); + cprintf(" eip 0x%08x\n", tf->tf_eip); + cprintf(" cs 0x----%04x\n", tf->tf_cs); + cprintf(" flag 0x%08x\n", tf->tf_eflags); + if ((tf->tf_cs & 3) != 0) { + cprintf(" esp 0x%08x\n", tf->tf_esp); + cprintf(" ss 0x----%04x\n", tf->tf_ss); + } +} + +void +print_regs(struct PushRegs *regs) +{ + cprintf(" edi 0x%08x\n", regs->reg_edi); + cprintf(" esi 0x%08x\n", regs->reg_esi); + cprintf(" ebp 0x%08x\n", regs->reg_ebp); + cprintf(" oesp 0x%08x\n", regs->reg_oesp); + cprintf(" ebx 0x%08x\n", regs->reg_ebx); + cprintf(" edx 0x%08x\n", regs->reg_edx); + cprintf(" ecx 0x%08x\n", regs->reg_ecx); + cprintf(" eax 0x%08x\n", regs->reg_eax); +} + +static void +trap_dispatch(struct Trapframe *tf) +{ + // Handle processor exceptions. + // LAB 3: Your code here. + + // Unexpected trap: The user process or the kernel has a bug. + print_trapframe(tf); + if (tf->tf_cs == GD_KT) + panic("unhandled trap in kernel"); + else { + env_destroy(curenv); + return; + } +} + +void +trap(struct Trapframe *tf) +{ + // The environment may have set DF and some versions + // of GCC rely on DF being clear + asm volatile("cld" ::: "cc"); + + // Check that interrupts are disabled. If this assertion + // fails, DO NOT be tempted to fix it by inserting a "cli" in + // the interrupt path. + assert(!(read_eflags() & FL_IF)); + + cprintf("Incoming TRAP frame at %p\n", tf); + + if ((tf->tf_cs & 3) == 3) { + // Trapped from user mode. + assert(curenv); + + // Copy trap frame (which is currently on the stack) + // into 'curenv->env_tf', so that running the environment + // will restart at the trap point. + curenv->env_tf = *tf; + // The trapframe on the stack should be ignored from here on. + tf = &curenv->env_tf; + } + + // Record that tf is the last real trapframe so + // print_trapframe can print some additional information. + last_tf = tf; + + // Dispatch based on what type of trap occurred + trap_dispatch(tf); + + // Return to the current environment, which should be running. + assert(curenv && curenv->env_status == ENV_RUNNING); + env_run(curenv); +} + + +void +page_fault_handler(struct Trapframe *tf) +{ + uint32_t fault_va; + + // Read processor's CR2 register to find the faulting address + fault_va = rcr2(); + + // Handle kernel-mode page faults. + + // LAB 3: Your code here. + + // We've already handled kernel-mode exceptions, so if we get here, + // the page fault happened in user mode. + + // Destroy the environment that caused the fault. + cprintf("[%08x] user fault va %08x ip %08x\n", + curenv->env_id, fault_va, tf->tf_eip); + print_trapframe(tf); + env_destroy(curenv); +} + diff --git a/kern/trap.h b/kern/trap.h new file mode 100644 index 0000000..36b8758 --- /dev/null +++ b/kern/trap.h @@ -0,0 +1,23 @@ +/* See COPYRIGHT for copyright information. */ + +#ifndef JOS_KERN_TRAP_H +#define JOS_KERN_TRAP_H +#ifndef JOS_KERNEL +# error "This is a JOS kernel header; user programs should not #include it" +#endif + +#include +#include + +/* The kernel's interrupt descriptor table */ +extern struct Gatedesc idt[]; +extern struct Pseudodesc idt_pd; + +void trap_init(void); +void trap_init_percpu(void); +void print_regs(struct PushRegs *regs); +void print_trapframe(struct Trapframe *tf); +void page_fault_handler(struct Trapframe *); +void backtrace(struct Trapframe *); + +#endif /* JOS_KERN_TRAP_H */ diff --git a/kern/trapentry.S b/kern/trapentry.S new file mode 100644 index 0000000..22fc640 --- /dev/null +++ b/kern/trapentry.S @@ -0,0 +1,55 @@ +/* See COPYRIGHT for copyright information. */ + +#include +#include +#include + + + +################################################################### +# exceptions/interrupts +################################################################### + +/* TRAPHANDLER defines a globally-visible function for handling a trap. + * It pushes a trap number onto the stack, then jumps to _alltraps. + * Use TRAPHANDLER for traps where the CPU automatically pushes an error code. + * + * You shouldn't call a TRAPHANDLER function from C, but you may + * need to _declare_ one in C (for instance, to get a function pointer + * during IDT setup). You can declare the function with + * void NAME(); + * where NAME is the argument passed to TRAPHANDLER. + */ +#define TRAPHANDLER(name, num) \ + .globl name; /* define global symbol for 'name' */ \ + .type name, @function; /* symbol type is function */ \ + .align 2; /* align function definition */ \ + name: /* function starts here */ \ + pushl $(num); \ + jmp _alltraps + +/* Use TRAPHANDLER_NOEC for traps where the CPU doesn't push an error code. + * It pushes a 0 in place of the error code, so the trap frame has the same + * format in either case. + */ +#define TRAPHANDLER_NOEC(name, num) \ + .globl name; \ + .type name, @function; \ + .align 2; \ + name: \ + pushl $0; \ + pushl $(num); \ + jmp _alltraps + +.text + +/* + * Lab 3: Your code here for generating entry points for the different traps. + */ + + + +/* + * Lab 3: Your code here for _alltraps + */ + diff --git a/lib/Makefrag b/lib/Makefrag new file mode 100644 index 0000000..2f80706 --- /dev/null +++ b/lib/Makefrag @@ -0,0 +1,31 @@ +OBJDIRS += lib + +LIB_SRCFILES := lib/console.c \ + lib/libmain.c \ + lib/exit.c \ + lib/panic.c \ + lib/printf.c \ + lib/printfmt.c \ + lib/readline.c \ + lib/string.c \ + lib/syscall.c + + + + +LIB_OBJFILES := $(patsubst lib/%.c, $(OBJDIR)/lib/%.o, $(LIB_SRCFILES)) +LIB_OBJFILES := $(patsubst lib/%.S, $(OBJDIR)/lib/%.o, $(LIB_OBJFILES)) + +$(OBJDIR)/lib/%.o: lib/%.c $(OBJDIR)/.vars.USER_CFLAGS + @echo + cc[USER] $< + @mkdir -p $(@D) + $(V)$(CC) -nostdinc $(USER_CFLAGS) -c -o $@ $< + +$(OBJDIR)/lib/%.o: lib/%.S $(OBJDIR)/.vars.USER_CFLAGS + @echo + as[USER] $< + @mkdir -p $(@D) + $(V)$(CC) -nostdinc $(USER_CFLAGS) -c -o $@ $< + +$(OBJDIR)/lib/libjos.a: $(LIB_OBJFILES) + @echo + ar $@ + $(V)$(AR) r $@ $(LIB_OBJFILES) diff --git a/lib/console.c b/lib/console.c new file mode 100644 index 0000000..8856873 --- /dev/null +++ b/lib/console.c @@ -0,0 +1,25 @@ + +#include +#include + +void +cputchar(int ch) +{ + char c = ch; + + // Unlike standard Unix's putchar, + // the cputchar function _always_ outputs to the system console. + sys_cputs(&c, 1); +} + +int +getchar(void) +{ + int r; + // sys_cgetc does not block, but getchar should. + while ((r = sys_cgetc()) == 0) + ; + return r; +} + + diff --git a/lib/entry.S b/lib/entry.S new file mode 100644 index 0000000..222d16c --- /dev/null +++ b/lib/entry.S @@ -0,0 +1,35 @@ +#include +#include + +.data +// Define the global symbols 'envs', 'pages', 'uvpt', and 'uvpd' +// so that they can be used in C as if they were ordinary global arrays. + .globl envs + .set envs, UENVS + .globl pages + .set pages, UPAGES + .globl uvpt + .set uvpt, UVPT + .globl uvpd + .set uvpd, (UVPT+(UVPT>>12)*4) + + +// Entrypoint - this is where the kernel (or our parent environment) +// starts us running when we are initially loaded into a new environment. +.text +.globl _start +_start: + // See if we were started with arguments on the stack + cmpl $USTACKTOP, %esp + jne args_exist + + // If not, push dummy argc/argv arguments. + // This happens when we are loaded by the kernel, + // because the kernel does not know about passing arguments. + pushl $0 + pushl $0 + +args_exist: + call libmain +1: jmp 1b + diff --git a/lib/exit.c b/lib/exit.c new file mode 100644 index 0000000..85c64d6 --- /dev/null +++ b/lib/exit.c @@ -0,0 +1,9 @@ + +#include + +void +exit(void) +{ + sys_env_destroy(0); +} + diff --git a/lib/libmain.c b/lib/libmain.c new file mode 100644 index 0000000..8a14b29 --- /dev/null +++ b/lib/libmain.c @@ -0,0 +1,28 @@ +// Called from entry.S to get us going. +// entry.S already took care of defining envs, pages, uvpd, and uvpt. + +#include + +extern void umain(int argc, char **argv); + +const volatile struct Env *thisenv; +const char *binaryname = ""; + +void +libmain(int argc, char **argv) +{ + // set thisenv to point at our Env structure in envs[]. + // LAB 3: Your code here. + thisenv = 0; + + // save the name of the program so that panic() can use it + if (argc > 0) + binaryname = argv[0]; + + // call user main routine + umain(argc, argv); + + // exit gracefully + exit(); +} + diff --git a/lib/panic.c b/lib/panic.c new file mode 100644 index 0000000..8d76788 --- /dev/null +++ b/lib/panic.c @@ -0,0 +1,26 @@ + +#include + +/* + * Panic is called on unresolvable fatal errors. + * It prints "panic: ", then causes a breakpoint exception, + * which causes JOS to enter the JOS kernel monitor. + */ +void +_panic(const char *file, int line, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + + // Print the panic message + cprintf("[%08x] user panic in %s at %s:%d: ", + sys_getenvid(), binaryname, file, line); + vcprintf(fmt, ap); + cprintf("\n"); + + // Cause a breakpoint exception + while (1) + asm volatile("int3"); +} + diff --git a/lib/printf.c b/lib/printf.c new file mode 100644 index 0000000..3923b10 --- /dev/null +++ b/lib/printf.c @@ -0,0 +1,62 @@ +// Implementation of cprintf console output for user environments, +// based on printfmt() and the sys_cputs() system call. +// +// cprintf is a debugging statement, not a generic output statement. +// It is very important that it always go to the console, especially when +// debugging file descriptor code! + +#include +#include +#include +#include + + +// Collect up to 256 characters into a buffer +// and perform ONE system call to print all of them, +// in order to make the lines output to the console atomic +// and prevent interrupts from causing context switches +// in the middle of a console output line and such. +struct printbuf { + int idx; // current buffer index + int cnt; // total bytes printed so far + char buf[256]; +}; + + +static void +putch(int ch, struct printbuf *b) +{ + b->buf[b->idx++] = ch; + if (b->idx == 256-1) { + sys_cputs(b->buf, b->idx); + b->idx = 0; + } + b->cnt++; +} + +int +vcprintf(const char *fmt, va_list ap) +{ + struct printbuf b; + + b.idx = 0; + b.cnt = 0; + vprintfmt((void*)putch, &b, fmt, ap); + sys_cputs(b.buf, b.idx); + + return b.cnt; +} + +int +cprintf(const char *fmt, ...) +{ + va_list ap; + int cnt; + + va_start(ap, fmt); + cnt = vcprintf(fmt, ap); + va_end(ap); + + return cnt; +} + diff --git a/lib/syscall.c b/lib/syscall.c new file mode 100644 index 0000000..8d28dda --- /dev/null +++ b/lib/syscall.c @@ -0,0 +1,63 @@ +// System call stubs. + +#include +#include + +static inline int32_t +syscall(int num, int check, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5) +{ + int32_t ret; + + // Generic system call: pass system call number in AX, + // up to five parameters in DX, CX, BX, DI, SI. + // Interrupt kernel with T_SYSCALL. + // + // The "volatile" tells the assembler not to optimize + // this instruction away just because we don't use the + // return value. + // + // The last clause tells the assembler that this can + // potentially change the condition codes and arbitrary + // memory locations. + + asm volatile("int %1\n" + : "=a" (ret) + : "i" (T_SYSCALL), + "a" (num), + "d" (a1), + "c" (a2), + "b" (a3), + "D" (a4), + "S" (a5) + : "cc", "memory"); + + if(check && ret > 0) + panic("syscall %d returned %d (> 0)", num, ret); + + return ret; +} + +void +sys_cputs(const char *s, size_t len) +{ + syscall(SYS_cputs, 0, (uint32_t)s, len, 0, 0, 0); +} + +int +sys_cgetc(void) +{ + return syscall(SYS_cgetc, 0, 0, 0, 0, 0, 0); +} + +int +sys_env_destroy(envid_t envid) +{ + return syscall(SYS_env_destroy, 1, envid, 0, 0, 0, 0); +} + +envid_t +sys_getenvid(void) +{ + return syscall(SYS_getenvid, 0, 0, 0, 0, 0, 0); +} + diff --git a/user/Makefrag b/user/Makefrag new file mode 100644 index 0000000..a9ebd8d --- /dev/null +++ b/user/Makefrag @@ -0,0 +1,16 @@ +OBJDIRS += user + + +USERLIBS += jos + +$(OBJDIR)/user/%.o: user/%.c $(OBJDIR)/.vars.USER_CFLAGS + @echo + cc[USER] $< + @mkdir -p $(@D) + $(V)$(CC) -nostdinc $(USER_CFLAGS) -c -o $@ $< + +$(OBJDIR)/user/%: $(OBJDIR)/user/%.o $(OBJDIR)/lib/entry.o $(USERLIBS:%=$(OBJDIR)/lib/lib%.a) user/user.ld + @echo + ld $@ + $(V)$(LD) -o $@ $(ULDFLAGS) $(LDFLAGS) -nostdlib $(OBJDIR)/lib/entry.o $@.o -L$(OBJDIR)/lib $(USERLIBS:%=-l%) $(GCC_LIB) + $(V)$(OBJDUMP) -S $@ > $@.asm + $(V)$(NM) -n $@ > $@.sym + diff --git a/user/badsegment.c b/user/badsegment.c new file mode 100644 index 0000000..1dc74af --- /dev/null +++ b/user/badsegment.c @@ -0,0 +1,11 @@ +// program to cause a general protection exception + +#include + +void +umain(int argc, char **argv) +{ + // Try to load the kernel's TSS selector into the DS register. + asm volatile("movw $0x28,%ax; movw %ax,%ds"); +} + diff --git a/user/breakpoint.c b/user/breakpoint.c new file mode 100644 index 0000000..47e4cb2 --- /dev/null +++ b/user/breakpoint.c @@ -0,0 +1,10 @@ +// program to cause a breakpoint trap + +#include + +void +umain(int argc, char **argv) +{ + asm volatile("int $3"); +} + diff --git a/user/buggyhello.c b/user/buggyhello.c new file mode 100644 index 0000000..1ca1b7b --- /dev/null +++ b/user/buggyhello.c @@ -0,0 +1,11 @@ +// buggy hello world -- unmapped pointer passed to kernel +// kernel should destroy user environment in response + +#include + +void +umain(int argc, char **argv) +{ + sys_cputs((char*)1, 1); +} + diff --git a/user/buggyhello2.c b/user/buggyhello2.c new file mode 100644 index 0000000..0f15702 --- /dev/null +++ b/user/buggyhello2.c @@ -0,0 +1,13 @@ +// buggy hello world 2 -- pointed-to region extends into unmapped memory +// kernel should destroy user environment in response + +#include + +const char *hello = "hello, world\n"; + +void +umain(int argc, char **argv) +{ + sys_cputs(hello, 1024*1024); +} + diff --git a/user/divzero.c b/user/divzero.c new file mode 100644 index 0000000..ca379e2 --- /dev/null +++ b/user/divzero.c @@ -0,0 +1,13 @@ +// buggy program - causes a divide by zero exception + +#include + +int zero; + +void +umain(int argc, char **argv) +{ + zero = 0; + cprintf("1/0 is %08x!\n", 1/zero); +} + diff --git a/user/evilhello.c b/user/evilhello.c new file mode 100644 index 0000000..261e32c --- /dev/null +++ b/user/evilhello.c @@ -0,0 +1,12 @@ +// evil hello world -- kernel pointer passed to kernel +// kernel should destroy user environment in response + +#include + +void +umain(int argc, char **argv) +{ + // try to print the kernel entry point as a string! mua ha ha! + sys_cputs((char*)0xf010000c, 100); +} + diff --git a/user/faultread.c b/user/faultread.c new file mode 100644 index 0000000..b76902f --- /dev/null +++ b/user/faultread.c @@ -0,0 +1,10 @@ +// buggy program - faults with a read from location zero + +#include + +void +umain(int argc, char **argv) +{ + cprintf("I read %08x from location 0!\n", *(unsigned*)0); +} + diff --git a/user/faultreadkernel.c b/user/faultreadkernel.c new file mode 100644 index 0000000..79057a1 --- /dev/null +++ b/user/faultreadkernel.c @@ -0,0 +1,10 @@ +// buggy program - faults with a read from kernel space + +#include + +void +umain(int argc, char **argv) +{ + cprintf("I read %08x from location 0xf0100000!\n", *(unsigned*)0xf0100000); +} + diff --git a/user/faultwrite.c b/user/faultwrite.c new file mode 100644 index 0000000..b8512f5 --- /dev/null +++ b/user/faultwrite.c @@ -0,0 +1,10 @@ +// buggy program - faults with a write to location zero + +#include + +void +umain(int argc, char **argv) +{ + *(unsigned*)0 = 0; +} + diff --git a/user/faultwritekernel.c b/user/faultwritekernel.c new file mode 100644 index 0000000..fbcc32f --- /dev/null +++ b/user/faultwritekernel.c @@ -0,0 +1,10 @@ +// buggy program - faults with a write to a kernel location + +#include + +void +umain(int argc, char **argv) +{ + *(unsigned*)0xf0100000 = 0; +} + diff --git a/user/hello.c b/user/hello.c new file mode 100644 index 0000000..486c9dc --- /dev/null +++ b/user/hello.c @@ -0,0 +1,9 @@ +// hello, world +#include + +void +umain(int argc, char **argv) +{ + cprintf("hello, world\n"); + cprintf("i am environment %08x\n", thisenv->env_id); +} diff --git a/user/softint.c b/user/softint.c new file mode 100644 index 0000000..cd8d3b5 --- /dev/null +++ b/user/softint.c @@ -0,0 +1,10 @@ +// buggy program - causes an illegal software interrupt + +#include + +void +umain(int argc, char **argv) +{ + asm volatile("int $14"); // page fault +} + diff --git a/user/testbss.c b/user/testbss.c new file mode 100644 index 0000000..2f8ee8e --- /dev/null +++ b/user/testbss.c @@ -0,0 +1,27 @@ +// test reads and writes to a large bss + +#include + +#define ARRAYSIZE (1024*1024) + +uint32_t bigarray[ARRAYSIZE]; + +void +umain(int argc, char **argv) +{ + int i; + + cprintf("Making sure bss works right...\n"); + for (i = 0; i < ARRAYSIZE; i++) + if (bigarray[i] != 0) + panic("bigarray[%d] isn't cleared!\n", i); + for (i = 0; i < ARRAYSIZE; i++) + bigarray[i] = i; + for (i = 0; i < ARRAYSIZE; i++) + if (bigarray[i] != i) + panic("bigarray[%d] didn't hold its value!\n", i); + + cprintf("Yes, good. Now doing a wild write off the end...\n"); + bigarray[ARRAYSIZE+1024] = 0; + panic("SHOULD HAVE TRAPPED!!!"); +} diff --git a/user/user.ld b/user/user.ld new file mode 100644 index 0000000..1902ae3 --- /dev/null +++ b/user/user.ld @@ -0,0 +1,72 @@ +/* Simple linker script for JOS user-level programs. + See the GNU ld 'info' manual ("info ld") to learn the syntax. */ + +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(_start) + +SECTIONS +{ + /* Load programs at this address: "." means the current address */ + . = 0x800020; + + .text : { + *(.text .stub .text.* .gnu.linkonce.t.*) + } + + PROVIDE(etext = .); /* Define the 'etext' symbol to this value */ + + .rodata : { + *(.rodata .rodata.* .gnu.linkonce.r.*) + } + + /* Adjust the address for the data segment to the next page */ + . = ALIGN(0x1000); + + .data : { + *(.data) + } + + PROVIDE(edata = .); + + .bss : { + *(.bss) + } + + PROVIDE(end = .); + + + /* Place debugging symbols so that they can be found by + * the kernel debugger. + * Specifically, the four words at 0x200000 mark the beginning of + * the stabs, the end of the stabs, the beginning of the stabs + * string table, and the end of the stabs string table, respectively. + */ + + .stab_info 0x200000 : { + LONG(__STAB_BEGIN__); + LONG(__STAB_END__); + LONG(__STABSTR_BEGIN__); + LONG(__STABSTR_END__); + } + + .stab : { + __STAB_BEGIN__ = DEFINED(__STAB_BEGIN__) ? __STAB_BEGIN__ : .; + *(.stab); + __STAB_END__ = DEFINED(__STAB_END__) ? __STAB_END__ : .; + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + .stabstr : { + __STABSTR_BEGIN__ = DEFINED(__STABSTR_BEGIN__) ? __STABSTR_BEGIN__ : .; + *(.stabstr); + __STABSTR_END__ = DEFINED(__STABSTR_END__) ? __STABSTR_END__ : .; + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + /DISCARD/ : { + *(.eh_frame .note.GNU-stack .comment) + } +}