Compare commits

...

39 Commits
lab1 ... lab3

Author SHA1 Message Date
7965d399dc Update lab3 answers 2019-05-13 23:05:00 -07:00
5e04fb305e Add answers for lab3 part A 2019-05-06 22:22:13 -07:00
d943a2be37 Fix up bad merge conflict fix. 2019-05-02 17:05:07 -07:00
34e9433d15 Merge changes from Lab 2 2019-05-02 16:12:12 -07:00
9bec3d83bd Merge branch 'lab2' of gitlab.unexploitable.systems:fedorind/jos into lab2 2019-04-29 21:01:43 -07:00
0289ec3b3e Change some memory mappings. 2019-04-29 21:00:57 -07:00
46cc5c9478 Add answers? 2019-04-29 20:58:01 -07:00
03b296f7e1 Add a getc program 2019-04-25 19:58:37 -07:00
74b1c2c69d Switch getc to also use fast_syscall. 2019-04-25 17:26:16 -07:00
3449b8c566 Implement the fast syscall challenge. 2019-04-25 17:14:57 -07:00
7d76204053 Add debugging kernel monitor functions 2019-04-24 23:49:13 -07:00
0fe97e52c3 Change address reporting to fit the grading script's standards. 2019-04-23 20:46:11 -07:00
8cd1daf8dd Implement the memcheck. 2019-04-23 20:37:53 -07:00
113093f919 Fix page table copying bug. 2019-04-23 20:37:41 -07:00
94b52c5730 Forward return value of syscall. 2019-04-23 17:10:35 -07:00
9231075799 Properly set environment pointer. 2019-04-23 17:10:25 -07:00
f4e3196494 Fix permissions. 2019-04-23 17:09:54 -07:00
f0e2ab8abd Do not crash on handled traps. 2019-04-23 16:31:01 -07:00
eae0475758 Seemingly implement system calls (still appears to have a bug). 2019-04-23 16:14:41 -07:00
bbdb8bd811 Avoid corrupting eax during system call. 2019-04-23 16:14:21 -07:00
6ab267bb9b Add basic trap handlers 2019-04-23 14:26:02 -07:00
667f2df4cc Fix the trap function.
God fucking damn it 16 bit registers!
2019-04-23 02:06:11 -07:00
8120dfde65 Intermediate commit (trap is close to working). 2019-04-23 01:44:23 -07:00
73dad8b484 Intermediate commit of lab 3 changes 2019-04-22 22:16:32 -07:00
c07415cffc fixup! Add permission changing kernel function 2019-04-19 18:54:37 -07:00
e1d239139a Add permission changing kernel function 2019-04-19 17:36:23 -07:00
e484704ce8 Implement the showmappings command 2019-04-19 13:21:08 -07:00
60ee3619af Add some macros for color support 2019-04-19 11:24:59 -07:00
ca51596893 Initial implementation of lab 2. 2019-04-19 01:14:51 -07:00
d7a96eb60f Merge branch 'lab1' into lab2 2019-04-18 23:49:39 -07:00
Yeongjin Jang
0b89b560f3 Merge branch 'lab2' into lab3 2019-04-01 21:31:58 -07:00
Yeongjin Jang
187496eb19 Merge branch 'lab1' into lab2 2019-04-01 21:31:47 -07:00
Yeongjin Jang
f1840b8b11 add some hints 2019-04-01 21:22:40 -07:00
Yeongjin Jang
a37b5511d0 Merge branch 'lab2' into lab3 2019-04-01 01:26:30 -07:00
Yeongjin Jang
c56b8ebcbd Merge branch 'lab1' into lab2 2019-04-01 01:23:10 -07:00
Yeongjin Jang
0ce94c7ca2 Merge branch 'lab2' into lab3 2019-04-01 00:01:31 -07:00
Yeongjin Jang
815502c0cc Merge branch 'lab1' into lab2 2019-04-01 00:01:15 -07:00
Anish Athalye
a9d7717cc4 Lab 3 2018-09-25 12:22:51 -04:00
Anish Athalye
2d1187aa3c Lab 2 2018-09-12 14:55:07 -04:00
54 changed files with 3482 additions and 28 deletions

View File

@ -67,6 +67,7 @@ endif
GDBPORT := $(shell expr `id -u` % 5000 + 25000) GDBPORT := $(shell expr `id -u` % 5000 + 25000)
CC := $(GCCPREFIX)gcc -pipe CC := $(GCCPREFIX)gcc -pipe
GDB := $(GCCPREFIX)gdb
AS := $(GCCPREFIX)as AS := $(GCCPREFIX)as
AR := $(GCCPREFIX)ar AR := $(GCCPREFIX)ar
LD := $(GCCPREFIX)ld LD := $(GCCPREFIX)ld
@ -137,6 +138,8 @@ $(OBJDIR)/.vars.%: FORCE
# Include Makefrags for subdirectories # Include Makefrags for subdirectories
include boot/Makefrag include boot/Makefrag
include kern/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) QEMUOPTS = -drive file=$(OBJDIR)/kern/kernel.img,index=0,media=disk,format=raw -serial mon:stdio -gdb tcp::$(GDBPORT)
@ -148,7 +151,7 @@ QEMUOPTS += $(QEMUEXTRA)
sed "s/localhost:1234/localhost:$(GDBPORT)/" < $^ > $@ sed "s/localhost:1234/localhost:$(GDBPORT)/" < $^ > $@
gdb: gdb:
gdb -n -x .gdbinit $(GDB) -n -x .gdbinit
pre-qemu: .gdbinit pre-qemu: .gdbinit
@ -297,6 +300,22 @@ myapi.key:
#handin-prep: #handin-prep:
# @./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 # This magic automatically generates makefile dependencies
# for header files included from C source files we compile, # for header files included from C source files we compile,

22
answers-lab2.txt Normal file
View File

@ -0,0 +1,22 @@
1. The entry given by PDX(UVPT) is mapped to the page directory itself,
to allow programs to read existing memory mappings. The entry at PDX(UPAGES)
is mapped to the pages variable in memory so that the kernel (and potentially other ring 0 programs) can access it. The entry pointed to by PDX(KSTACKTOP-KSTACKSIZE) is mapped
to the bootstack location. Finally, both the memory pointed to by 0 and PDX(KERNBASE) are mapped to kernel memory. However, the mappings at VA 0 are read-only, so user programs can't touch them, while the mappings at PDX(KERNBASE) are kernel-private and RW.
A table could be as follows:
400 - Kernel memory
... - Kernel memory
3c0 - Kernel memory
3bf - Kernel Stack
3bd - UVPT
3bc - UPAGES
2. The kernel memory is mapped as kernel read write. This means there are several flags set in the kernel page directory and page table entries, which indicate that this is restricted memory. The lower 3 bits of the CS register will be checked when an access to this memory is made, and, if they are not 0 or 1 (indicating kernel code), the CPU generates a fault, and the user program gives up control back to the OS. Thus, unless a program is started in ring 0, it will not be able to read or write kernel memory, as it should.
3. The absolute maximum is 4GB, since we use 32-bit integers for referencing memory. Some of this memory is used for the kernel itself, as well as for the page directory and tables.
4. The page directory and the corresponding pages are all 4kb. The page directory can have 1024 entries, and each of these point to a page table. Thus, we use approximatey 4MB (slightly more than that, actually, due to the size of the page directory itself) of memory. Additionally, the "pages" structs (which are used to keep track of available physical pages), will require ~1000000 entries, each of which is between 6 and 8 bytes (depending on whether GCC aligns struct sizes). This means another 8MB is used to keep track of free pages, to a total of around 12MB.
5. We switch to high EIP when we jump to "relocated". Relocated is a label, and a symbol that's inserted by the linker. Since the linker is configured to link the kernel high, relocated points to the upper portion of memory, where KERNBASE is. However, the entry page directory, just like our full page directory later on, sets up two mappings, one starting at 0 (creating a one to one mapping between some of the virtual addresses and their physical counterparts), and one starting at KERNBASE. Thus, we can continue to run at a low EIP. The only reason I can think of as to why we NEED to make the switch, besides the elementary "the kernel links high", is that we need to be able to write to various symbols, also linked above KERNBASE.

18
answers-lab3.txt Normal file
View File

@ -0,0 +1,18 @@
1. Some interrupts receive a different "shape" of stack frame - the kernel pushes
an error code for some, but not for the others. We thus need individual handlers
that would make sure that what we have on the stack is consistent, and
then call one centralized function for that. The handlers additionally
are able to pass in a trap number to that centrallized function. This way, a lot
of code can be re-used.
Conceptually, each trap has a different semantic meaning. Thus, it makes no sense
to perform the same action for both traps - they don't mean the same thing.
2. The general protection fault occurs when a process can access memory, but not do something else with it. We do not want page faults to be trigger-able by users (this is the case for many other traps). Thus, when the user tries to invoke int 14,
it's not allowed to execute it from userspace, so it is thrown a general protection fault, which it then prints.
3. Unlike most other traps, the debugging trap should be possible to trigger from user code. If the "privillege level" is set to 0 in the IDT, when the INT3 occurs, the user does not have permission to call that interrupt,
and thus, a general protection fault occurs. In order to make it work correctly, the privillege level needs to be 3 (for userland).
4. It's probably so that users cannot deliberately trigger certain faults for malicious purposes. It doesn't make sense to trigger a page fault without properly setting up CR2, and even then, though I can't think
of anything right now, there's likely security issues that could arise if users can deliberately page fault their own process.

View File

@ -1,2 +1,2 @@
LAB=1 LAB=3
PACKAGEDATE=Thu Aug 30 15:16:04 EDT 2018 PACKAGEDATE=Tue Sep 25 12:21:10 EDT 2018

28
grade-lab2 Executable file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env python
from gradelib import *
r = Runner(save("jos.out"),
stop_breakpoint("readline"))
@test(0, "running JOS")
def test_jos():
r.run_qemu()
@test(20, "Physical page allocator", parent=test_jos)
def test_check_page_alloc():
r.match(r"check_page_alloc\(\) succeeded!")
@test(20, "Page management", parent=test_jos)
def test_check_page():
r.match(r"check_page\(\) succeeded!")
@test(20, "Kernel page directory", parent=test_jos)
def test_check_kern_pgdir():
r.match(r"check_kern_pgdir\(\) succeeded!")
@test(10, "Page management 2", parent=test_jos)
def test_check_page_installed_pgdir():
r.match(r"check_page_installed_pgdir\(\) succeeded!")
run_tests()

135
grade-lab3 Executable file
View File

@ -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()

59
inc/env.h Normal file
View File

@ -0,0 +1,59 @@
/* See COPYRIGHT for copyright information. */
#ifndef JOS_INC_ENV_H
#define JOS_INC_ENV_H
#include <inc/types.h>
#include <inc/trap.h>
#include <inc/memlayout.h>
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

56
inc/lib.h Normal file
View File

@ -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 <inc/types.h>
#include <inc/stdio.h>
#include <inc/stdarg.h>
#include <inc/string.h>
#include <inc/error.h>
#include <inc/assert.h>
#include <inc/env.h>
#include <inc/memlayout.h>
#include <inc/syscall.h>
#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

View File

@ -143,5 +143,46 @@
typedef uint32_t pte_t; typedef uint32_t pte_t;
typedef uint32_t pde_t; typedef uint32_t pde_t;
#if JOS_USER
/*
* The page directory entry corresponding to the virtual address range
* [UVPT, UVPT + PTSIZE) points to the page directory itself. Thus, the page
* directory is treated as a page table as well as a page directory.
*
* One result of treating the page directory as a page table is that all PTEs
* can be accessed through a "virtual page table" at virtual address UVPT (to
* which uvpt is set in lib/entry.S). The PTE for page number N is stored in
* uvpt[N]. (It's worth drawing a diagram of this!)
*
* A second consequence is that the contents of the current page directory
* will always be available at virtual address (UVPT + (UVPT >> PGSHIFT)), to
* which uvpd is set in lib/entry.S.
*/
extern volatile pte_t uvpt[]; // VA of "virtual page table"
extern volatile pde_t uvpd[]; // VA of current page directory
#endif
/*
* Page descriptor structures, mapped at UPAGES.
* Read/write to the kernel, read-only to user programs.
*
* Each struct PageInfo stores metadata for one physical page.
* Is it NOT the physical page itself, but there is a one-to-one
* correspondence between physical pages and struct PageInfo's.
* You can map a struct PageInfo * to the corresponding physical address
* with page2pa() in kern/pmap.h.
*/
struct PageInfo {
// Next page on the free list.
struct PageInfo *pp_link;
// pp_ref is the count of pointers (usually in page table entries)
// to this page, for pages allocated using page_alloc.
// Pages allocated at boot time using pmap.c's
// boot_alloc do not have valid reference count fields.
uint16_t pp_ref;
};
#endif /* !__ASSEMBLER__ */ #endif /* !__ASSEMBLER__ */
#endif /* !JOS_INC_MEMLAYOUT_H */ #endif /* !JOS_INC_MEMLAYOUT_H */

13
inc/syscall.h Normal file
View File

@ -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 */

80
inc/trap.h Normal file
View File

@ -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 <inc/types.h>
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 */

View File

@ -3,6 +3,10 @@
#include <inc/types.h> #include <inc/types.h>
#define MSR_IA32_SYSENTER_CS 0x174
#define MSR_IA32_SYSENTER_EIP 0x176
#define MSR_IA32_SYSENTER_ESP 0x175
static inline void static inline void
breakpoint(void) breakpoint(void)
{ {
@ -261,4 +265,19 @@ xchg(volatile uint32_t *addr, uint32_t newval)
return result; return result;
} }
static inline void
write_msr(uint32_t reg, uint32_t low, uint32_t high) {
asm volatile("wrmsr\n\t"
:: "c" (reg), "a" (low), "d" (high));
}
static inline void
read_msr(uint32_t reg, uint32_t* low, uint32_t* high) {
uint32_t eax, edx;
asm volatile("rdmsr\n\t"
: "=a" (eax), "=d" (edx) : "c" (reg));
*low = eax;
*high = edx;
}
#endif /* !JOS_INC_X86_H */ #endif /* !JOS_INC_X86_H */

View File

@ -37,7 +37,21 @@ KERN_SRCFILES := kern/entry.S \
KERN_SRCFILES := $(wildcard $(KERN_SRCFILES)) KERN_SRCFILES := $(wildcard $(KERN_SRCFILES))
# Binary program images to embed within the kernel. # 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 \
user/getc
KERN_OBJFILES := $(patsubst %.c, $(OBJDIR)/%.o, $(KERN_SRCFILES)) KERN_OBJFILES := $(patsubst %.c, $(OBJDIR)/%.o, $(KERN_SRCFILES))
KERN_OBJFILES := $(patsubst %.S, $(OBJDIR)/%.o, $(KERN_OBJFILES)) KERN_OBJFILES := $(patsubst %.S, $(OBJDIR)/%.o, $(KERN_OBJFILES))

View File

@ -3,6 +3,22 @@
#include <inc/types.h> #include <inc/types.h>
#define ACOL_WRAP(s) "\33[" s "m"
#define ACOL_BLACK ACOL_WRAP("30")
#define ACOL_RED ACOL_WRAP("31")
#define ACOL_GREEN ACOL_WRAP("32")
#define ACOL_YELLOW ACOL_WRAP("33")
#define ACOL_BLUE ACOL_WRAP("34")
#define ACOL_MAGENTA ACOL_WRAP("35")
#define ACOL_CYAN ACOL_WRAP("36")
#define ACOL_WHITE ACOL_WRAP("37")
#define ACOL_CLEAR ACOL_WRAP("0")
#define ACOL_TAG(c, s) "[" c s ACOL_CLEAR "] "
#define ACOL_NOTE(s) ACOL_TAG(ACOL_WHITE, " Note ") s
#define ACOL_WARN(s) ACOL_TAG(ACOL_YELLOW, "Warning") s
#define ACOL_ERR(s) ACOL_TAG(ACOL_RED, " Error ") s
struct AttrState { struct AttrState {
uint8_t cattrs; uint8_t cattrs;
uint8_t attrs; uint8_t attrs;

View File

@ -2,6 +2,7 @@
#include <inc/mmu.h> #include <inc/mmu.h>
#include <inc/memlayout.h> #include <inc/memlayout.h>
#include <inc/trap.h>
# Shift Right Logical # Shift Right Logical
#define SRL(val, shamt) (((val) >> (shamt)) & ~(-1 << (32 - (shamt)))) #define SRL(val, shamt) (((val) >> (shamt)) & ~(-1 << (32 - (shamt))))

507
kern/env.c Normal file
View File

@ -0,0 +1,507 @@
/* See COPYRIGHT for copyright information. */
#include <inc/x86.h>
#include <inc/mmu.h>
#include <inc/error.h>
#include <inc/string.h>
#include <inc/assert.h>
#include <inc/elf.h>
#include <kern/env.h>
#include <kern/pmap.h>
#include <kern/trap.h>
#include <kern/monitor.h>
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.
size_t i = NENV;
while(true) {
i--;
envs[i].env_link = env_free_list;
env_free_list = &envs[i];
if(i == 0) break;
}
// 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.
p->pp_ref++;
e->env_pgdir = page2kva(p);
memcpy(e->env_pgdir + PDX(UTOP), kern_pgdir + PDX(UTOP), sizeof(pde_t) * (NPDENTRIES - PDX(UTOP)));
// 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!)
va = ROUNDDOWN(va, PGSIZE);
size_t count = ROUNDUP(len, PGSIZE) / PGSIZE;
struct PageInfo* p;
while(count--) {
if(!(p = page_alloc(0)))
panic("Failed to allocate memory");
if(page_insert(e->env_pgdir, p, va, PTE_U | PTE_W))
panic("Failed to allocate memory for page table");
va += PGSIZE;
}
}
//
// 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.
// TODO validate the headers
struct Elf* elf = (struct Elf*) binary;
struct Proghdr* ph = (struct Proghdr*) (binary + elf->e_phoff);
struct Proghdr* phend = ph + elf->e_phnum;
for(; ph < phend; ph++) {
if(ph->p_type != ELF_PROG_LOAD) continue;
region_alloc(e, (void*) ph->p_va, ph->p_memsz);
lcr3(PADDR(e->env_pgdir));
memcpy((void*) ph->p_va, binary + ph->p_offset, ph->p_filesz);
memset((void*) ph->p_va + ph->p_filesz, 0, ph->p_memsz - ph->p_filesz);
lcr3(PADDR(kern_pgdir));
}
e->env_tf.tf_eip = elf->e_entry;
// Now map one page for the program's initial stack
// at virtual address USTACKTOP - PGSIZE.
region_alloc(e, (void*) USTACKTOP - PGSIZE, PGSIZE);
}
//
// 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.
struct Env* new_env;
if(env_alloc(&new_env, 0) < 0)
panic("Failed to allocate environment");
new_env->env_type = type;
load_icode(new_env, binary);
}
//
// 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.
if(curenv && curenv->env_status == ENV_RUNNING) {
curenv->env_status = ENV_RUNNABLE;
}
curenv = e;
e->env_status = ENV_RUNNING;
e->env_runs++;
lcr3(PADDR(e->env_pgdir));
env_pop_tf(&e->env_tf);
}

35
kern/env.h Normal file
View File

@ -0,0 +1,35 @@
/* See COPYRIGHT for copyright information. */
#ifndef JOS_KERN_ENV_H
#define JOS_KERN_ENV_H
#include <inc/env.h>
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

View File

@ -3,21 +3,16 @@
#include <inc/stdio.h> #include <inc/stdio.h>
#include <inc/string.h> #include <inc/string.h>
#include <inc/assert.h> #include <inc/assert.h>
#include <inc/x86.h>
#include <kern/monitor.h> #include <kern/monitor.h>
#include <kern/console.h> #include <kern/console.h>
#include <kern/pmap.h>
#include <kern/kclock.h>
#include <kern/env.h>
#include <kern/trap.h>
// Test the stack backtrace function (lab 1 only) void sysenter_handler();
void
test_backtrace(int x)
{
cprintf("entering test_backtrace %d\n", x);
if (x > 0)
test_backtrace(x-1);
else
mon_backtrace(0, 0, 0);
cprintf("leaving test_backtrace %d\n", x);
}
void void
i386_init(void) i386_init(void)
@ -34,11 +29,6 @@ i386_init(void)
cons_init(); cons_init();
cprintf("444544 decimal is %o octal!\n", 444544); cprintf("444544 decimal is %o octal!\n", 444544);
// Test the stack backtrace function (lab 1 only)
test_backtrace(5);
// Test ANSI color escape codes
cprintf("\33[31m" "C" cprintf("\33[31m" "C"
"\33[33m" "o" "\33[33m" "o"
"\33[32m" "l" "\33[32m" "l"
@ -46,9 +36,27 @@ i386_init(void)
"\33[34m" "r" "\33[34m" "r"
"\33[0m" " Works!" "\n"); "\33[0m" " Works!" "\n");
// Drop into the kernel monitor. write_msr(MSR_IA32_SYSENTER_EIP, (uint32_t) sysenter_handler, 0);
while (1) write_msr(MSR_IA32_SYSENTER_ESP, KSTACKTOP, 0);
monitor(NULL); write_msr(MSR_IA32_SYSENTER_CS, GD_KT, 0);
// Lab 2 memory management initialization functions
mem_init();
// 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]);
} }

22
kern/kclock.c Normal file
View File

@ -0,0 +1,22 @@
/* See COPYRIGHT for copyright information. */
/* Support for reading the NVRAM from the real-time clock. */
#include <inc/x86.h>
#include <kern/kclock.h>
unsigned
mc146818_read(unsigned reg)
{
outb(IO_RTC, reg);
return inb(IO_RTC+1);
}
void
mc146818_write(unsigned reg, unsigned datum)
{
outb(IO_RTC, reg);
outb(IO_RTC+1, datum);
}

29
kern/kclock.h Normal file
View File

@ -0,0 +1,29 @@
/* See COPYRIGHT for copyright information. */
#ifndef JOS_KERN_KCLOCK_H
#define JOS_KERN_KCLOCK_H
#ifndef JOS_KERNEL
# error "This is a JOS kernel header; user programs should not #include it"
#endif
#define IO_RTC 0x070 /* RTC port */
#define MC_NVRAM_START 0xe /* start of NVRAM: offset 14 */
#define MC_NVRAM_SIZE 50 /* 50 bytes of NVRAM */
/* NVRAM bytes 7 & 8: base memory size */
#define NVRAM_BASELO (MC_NVRAM_START + 7) /* low byte; RTC off. 0x15 */
#define NVRAM_BASEHI (MC_NVRAM_START + 8) /* high byte; RTC off. 0x16 */
/* NVRAM bytes 9 & 10: extended memory size (between 1MB and 16MB) */
#define NVRAM_EXTLO (MC_NVRAM_START + 9) /* low byte; RTC off. 0x17 */
#define NVRAM_EXTHI (MC_NVRAM_START + 10) /* high byte; RTC off. 0x18 */
/* NVRAM bytes 38 and 39: extended memory size (between 16MB and 4G) */
#define NVRAM_EXT16LO (MC_NVRAM_START + 38) /* low byte; RTC off. 0x34 */
#define NVRAM_EXT16HI (MC_NVRAM_START + 39) /* high byte; RTC off. 0x35 */
unsigned mc146818_read(unsigned reg);
void mc146818_write(unsigned reg, unsigned datum);
#endif // !JOS_KERN_KCLOCK_H

View File

@ -4,12 +4,21 @@
#include <inc/assert.h> #include <inc/assert.h>
#include <kern/kdebug.h> #include <kern/kdebug.h>
#include <kern/pmap.h>
#include <kern/env.h>
extern const struct Stab __STAB_BEGIN__[]; // Beginning of stabs table extern const struct Stab __STAB_BEGIN__[]; // Beginning of stabs table
extern const struct Stab __STAB_END__[]; // End 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_BEGIN__[]; // Beginning of string table
extern const char __STABSTR_END__[]; // End 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) // 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 = __STABSTR_BEGIN__;
stabstr_end = __STABSTR_END__; stabstr_end = __STABSTR_END__;
} else { } else {
// Can't search for user-level addresses yet! // The user-application linker script, user/user.ld,
panic("User address"); // 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 // String table validity checks

View File

@ -7,9 +7,14 @@
#include <inc/assert.h> #include <inc/assert.h>
#include <inc/x86.h> #include <inc/x86.h>
#include <kern/env.h>
#include <kern/ansi.h>
#include <kern/console.h> #include <kern/console.h>
#include <kern/monitor.h> #include <kern/monitor.h>
#include <kern/kdebug.h> #include <kern/kdebug.h>
#include <kern/trap.h>
#include <kern/pmap.h>
#include <inc/string.h>
#define CMDBUF_SIZE 80 // enough for one VGA text line #define CMDBUF_SIZE 80 // enough for one VGA text line
@ -25,7 +30,12 @@ struct Command {
static struct Command commands[] = { static struct Command commands[] = {
{ "help", "Display this list of commands", mon_help }, { "help", "Display this list of commands", mon_help },
{ "kerninfo", "Display information about the kernel", mon_kerninfo }, { "kerninfo", "Display information about the kernel", mon_kerninfo },
{ "backtrace", "Display current backtrace", mon_backtrace } { "backtrace", "Display current backtrace", mon_backtrace },
{ "showmappings", "Display the physical mappings for range", mon_showmappings },
{ "mperms", "Change the permissions of a memory range", mon_mperms },
{ "resume", "Resume from a breakpoint", mon_resume },
{ "step", "Step to next instruction", mon_step }
}; };
/***** Implementations of basic kernel monitor commands *****/ /***** Implementations of basic kernel monitor commands *****/
@ -46,7 +56,7 @@ mon_kerninfo(int argc, char **argv, struct Trapframe *tf)
extern char _start[], entry[], etext[], edata[], end[]; extern char _start[], entry[], etext[], edata[], end[];
cprintf("Special kernel symbols:\n"); cprintf("Special kernel symbols:\n");
cprintf(" _start %08x (phys)\n", _start); cprintf(" _start %08x (phys)\n", _start);
cprintf(" entry %08x (virt) %08x (phys)\n", entry, entry - KERNBASE); cprintf(" entry %08x (virt) %08x (phys)\n", entry, entry - KERNBASE);
cprintf(" etext %08x (virt) %08x (phys)\n", etext, etext - KERNBASE); cprintf(" etext %08x (virt) %08x (phys)\n", etext, etext - KERNBASE);
cprintf(" edata %08x (virt) %08x (phys)\n", edata, edata - KERNBASE); cprintf(" edata %08x (virt) %08x (phys)\n", edata, edata - KERNBASE);
@ -75,7 +85,7 @@ mon_backtrace(int argc, char **argv, struct Trapframe *tf)
ebp = (uint32_t*) ebp[0]; ebp = (uint32_t*) ebp[0];
debuginfo_eip(eip, &info); debuginfo_eip(eip, &info);
cprintf(" %s:%d: %.*s+%d\n", cprintf(" %s:%d: %.*s+%d\n",
info.eip_file, info.eip_line, info.eip_file, info.eip_line,
info.eip_fn_namelen, info.eip_fn_name, info.eip_fn_namelen, info.eip_fn_name,
eip - info.eip_fn_addr); eip - info.eip_fn_addr);
@ -84,7 +94,139 @@ mon_backtrace(int argc, char **argv, struct Trapframe *tf)
return 0; return 0;
} }
#define EXPECT_ARGS(n, ac) if(ac - 1 != n) { \
cprintf(ACOL_ERR("Expected %d arguments, " \
"got %d\n"), n, ac - 1); \
return 1; }
#define VA_TXT ACOL_CYAN "VA" ACOL_CLEAR
#define PA_TXT ACOL_GREEN "PA" ACOL_CLEAR
char*
decode_pte_perms(pte_t pte, char* buffer) {
buffer[0] = (pte & PTE_W) ? 'w' : 'r';
buffer[1] = (pte & PTE_U) ? 'u' : 'k';
return buffer;
}
void
get_pagebounds(char* one, char* two, uintptr_t* pone, uintptr_t* ptwo) {
long from = strtol(one, NULL, 0);
long to = strtol(two, NULL, 0);
*pone = ROUNDDOWN(from, PGSIZE);
*ptwo = ROUNDUP(to, PGSIZE);
if(*pone != from) cprintf(ACOL_WARN("Aligning start address %p down to %p\n"),
from, *pone);
if(*ptwo != to) cprintf(ACOL_WARN("Aligning end address %p up to %p\n"),
to, *ptwo);
}
int
mon_showmappings(int argc, char** argv, struct Trapframe* tf) {
EXPECT_ARGS(2, argc);
uintptr_t va_start, va_end;
char buffer[3] = {0};
get_pagebounds(argv[1], argv[2], &va_start, &va_end);
if(va_start == va_end) va_end += PGSIZE;
while(va_start < va_end) {
pte_t* pte = pgdir_walk(kern_pgdir, (const void*) va_start, 0);
cprintf(VA_TXT " 0x%08p -> ", va_start);
if(pte && (*pte & PTE_P)) {
cprintf(PA_TXT " 0x%08p (%s)\n", PTE_ADDR(*pte),
decode_pte_perms(*pte, buffer));
} else {
cprintf(PA_TXT " ------------\n");
}
va_start += PGSIZE;
}
return 0;
}
int mon_mperms(int argc, char** argv, struct Trapframe* tf) {
EXPECT_ARGS(3, argc);
pte_t perms = 0;
enum {
PERM_ADD,
PERM_REMOVE,
PERM_SET
} pmode;
const char* str = argv[1];
if(str[0] == '+') { pmode = PERM_ADD; str++; }
else if(str[0] == '-') { pmode = PERM_REMOVE; str++; }
else pmode = PERM_SET;
while(*str) {
if(*str == 'w') perms |= PTE_W;
else if(*str == 'u') perms |= PTE_U;
else {
cprintf(ACOL_ERR("Unknown permission character %c\n"), *str);
return 1;
}
str++;
}
uintptr_t va_start, va_end;
get_pagebounds(argv[2], argv[3], &va_start, &va_end);
if(va_start == va_end) va_end += PGSIZE;
while(va_start < va_end) {
pte_t* pte = pgdir_walk(kern_pgdir, (void*) va_start, 0);
if(!pte || !(*pte & PTE_P)) { va_start += PGSIZE; continue; }
if(pmode == PERM_ADD) {
*pte |= perms;
} else if(pmode == PERM_REMOVE) {
*pte &= ~perms;
} else if(pmode == PERM_SET) {
*pte = PTE_ADDR(*pte) | perms | PTE_P;
}
va_start += PGSIZE;
}
return 0;
}
// This is a nice thought and all...
// But we should modify the trap frame.
// I feel stupid.
static inline void
set_eflag(uint16_t flagno, int value) {
uint32_t temp;
uint32_t mask = ~(1 << flagno);
uint32_t regv = value << flagno;
asm volatile("pushfl\n\t"
"pop %w0\n\t"
"and %w1, %w0\n\t"
"or %w2, %w0\n\t"
"push %w0\n\t"
"popfl\n\t"
: "=r" (temp)
: "r" (mask), "r" (regv));
}
#define EXPECT_BRKPT if(!(tf->tf_trapno == T_BRKPT || tf->tf_trapno == T_DEBUG)) { \
cprintf(ACOL_ERR("I don't think I should resume from this.\n")); \
return 0; }
#define EFLAGS_TF 0x8
int mon_resume(int argc, char** argv, struct Trapframe* tf) {
EXPECT_BRKPT;
tf->tf_eflags &= ~(1 << EFLAGS_TF);
env_pop_tf(tf);
return 0;
}
int mon_step(int argc, char** argv, struct Trapframe* tf) {
EXPECT_BRKPT;
tf->tf_eflags |= 1 << EFLAGS_TF;
env_pop_tf(tf);
return 0;
}
/***** Kernel monitor command interpreter *****/ /***** Kernel monitor command interpreter *****/
@ -138,6 +280,8 @@ monitor(struct Trapframe *tf)
cprintf("Welcome to the JOS kernel monitor!\n"); cprintf("Welcome to the JOS kernel monitor!\n");
cprintf("Type 'help' for a list of commands.\n"); cprintf("Type 'help' for a list of commands.\n");
if (tf != NULL)
print_trapframe(tf);
while (1) { while (1) {
buf = readline("K> "); buf = readline("K> ");

View File

@ -15,5 +15,9 @@ void monitor(struct Trapframe *tf);
int mon_help(int argc, char **argv, struct Trapframe *tf); int mon_help(int argc, char **argv, struct Trapframe *tf);
int mon_kerninfo(int argc, char **argv, struct Trapframe *tf); int mon_kerninfo(int argc, char **argv, struct Trapframe *tf);
int mon_backtrace(int argc, char **argv, struct Trapframe *tf); int mon_backtrace(int argc, char **argv, struct Trapframe *tf);
int mon_showmappings(int argc, char **argv, struct Trapframe *tf);
int mon_mperms(int argc, char** argv, struct Trapframe* tf);
int mon_resume(int argc, char** argv, struct Trapframe* tf);
int mon_step(int argc, char** argv, struct Trapframe* tf);
#endif // !JOS_KERN_MONITOR_H #endif // !JOS_KERN_MONITOR_H

1008
kern/pmap.c Normal file

File diff suppressed because it is too large Load Diff

91
kern/pmap.h Normal file
View File

@ -0,0 +1,91 @@
/* See COPYRIGHT for copyright information. */
#ifndef JOS_KERN_PMAP_H
#define JOS_KERN_PMAP_H
#ifndef JOS_KERNEL
# error "This is a JOS kernel header; user programs should not #include it"
#endif
#include <inc/memlayout.h>
#include <inc/assert.h>
struct Env;
extern char bootstacktop[], bootstack[];
extern struct PageInfo *pages;
extern size_t npages;
extern pde_t *kern_pgdir;
/* This macro takes a kernel virtual address -- an address that points above
* KERNBASE, where the machine's maximum 256MB of physical memory is mapped --
* and returns the corresponding physical address. It panics if you pass it a
* non-kernel virtual address.
*/
#define PADDR(kva) _paddr(__FILE__, __LINE__, kva)
static inline physaddr_t
_paddr(const char *file, int line, void *kva)
{
if ((uint32_t)kva < KERNBASE)
_panic(file, line, "PADDR called with invalid kva %08lx", kva);
return (physaddr_t)kva - KERNBASE;
}
/* This macro takes a physical address and returns the corresponding kernel
* virtual address. It panics if you pass an invalid physical address. */
#define KADDR(pa) _kaddr(__FILE__, __LINE__, pa)
static inline void*
_kaddr(const char *file, int line, physaddr_t pa)
{
if (PGNUM(pa) >= npages)
_panic(file, line, "KADDR called with invalid pa %08lx", pa);
return (void *)(pa + KERNBASE);
}
enum {
// For page_alloc, zero the returned physical page.
ALLOC_ZERO = 1<<0,
};
void mem_init(void);
void page_init(void);
struct PageInfo *page_alloc(int alloc_flags);
void page_free(struct PageInfo *pp);
int page_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm);
void page_remove(pde_t *pgdir, void *va);
struct PageInfo *page_lookup(pde_t *pgdir, void *va, pte_t **pte_store);
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)
{
return (pp - pages) << PGSHIFT;
}
static inline struct PageInfo*
pa2page(physaddr_t pa)
{
if (PGNUM(pa) >= npages)
panic("pa2page called with invalid pa");
return &pages[PGNUM(pa)];
}
static inline void*
page2kva(struct PageInfo *pp)
{
return KADDR(page2pa(pp));
}
pte_t *pgdir_walk(pde_t *pgdir, const void *va, int create);
#endif /* !JOS_KERN_PMAP_H */

85
kern/syscall.c Normal file
View File

@ -0,0 +1,85 @@
/* See COPYRIGHT for copyright information. */
#include <inc/x86.h>
#include <inc/error.h>
#include <inc/string.h>
#include <inc/assert.h>
#include <kern/env.h>
#include <kern/pmap.h>
#include <kern/trap.h>
#include <kern/syscall.h>
#include <kern/console.h>
// 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.
user_mem_assert(curenv, s, len, 0);
// 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.
switch (syscallno) {
case SYS_cputs:
sys_cputs((const char*) a1, a2);
return 0;
case SYS_cgetc:
return sys_cgetc();
case SYS_getenvid:
return sys_getenvid();
case SYS_env_destroy:
return sys_env_destroy(a1);
default:
return -E_INVAL;
}
}

11
kern/syscall.h Normal file
View File

@ -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 <inc/syscall.h>
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 */

286
kern/trap.c Normal file
View File

@ -0,0 +1,286 @@
#include <inc/mmu.h>
#include <inc/x86.h>
#include <inc/assert.h>
#include <kern/pmap.h>
#include <kern/trap.h>
#include <kern/console.h>
#include <kern/monitor.h>
#include <kern/env.h>
#include <kern/syscall.h>
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)";
}
// XYZ: write a function declaration here...
// e.g., void t_divide();
void t_divide();
void t_debug();
void t_nmi();
void t_brkpt();
void t_oflow();
void t_bound();
void t_illop();
void t_device();
void t_dblflt();
void t_tss();
void t_segnp();
void t_stack();
void t_gpflt();
void t_pgflt();
void t_fperr();
void t_align();
void t_mchk();
void t_simderr();
void t_syscall();
void t_default();
void
trap_init(void)
{
extern struct Segdesc gdt[];
/*
*
* HINT
* Do something like this: SETGATE(idt[T_DIVIDE], 0, GD_KT, t_divide, 0);
* if your trap handler's name for divide by zero is t_device.
* Additionally, you should declare trap handler as a function
* to refer that in C code... (see the comment XYZ above)
*
*/
// LAB 3: Your code here.
SETGATE(idt[T_DIVIDE], 0, GD_KT, t_divide, 0);
SETGATE(idt[T_DEBUG], 0, GD_KT, t_debug, 0);
SETGATE(idt[T_NMI], 0, GD_KT, t_nmi, 0);
SETGATE(idt[T_BRKPT], 0, GD_KT, t_brkpt, 3);
SETGATE(idt[T_OFLOW], 0, GD_KT, t_oflow, 0);
SETGATE(idt[T_BOUND], 0, GD_KT, t_bound, 0);
SETGATE(idt[T_ILLOP], 0, GD_KT, t_illop, 0);
SETGATE(idt[T_DEVICE], 0, GD_KT, t_device, 0);
SETGATE(idt[T_DBLFLT], 0, GD_KT, t_dblflt, 0);
SETGATE(idt[T_TSS], 0, GD_KT, t_tss, 0);
SETGATE(idt[T_SEGNP], 0, GD_KT, t_segnp, 0);
SETGATE(idt[T_STACK], 0, GD_KT, t_stack, 0);
SETGATE(idt[T_GPFLT], 0, GD_KT, t_gpflt, 0);
SETGATE(idt[T_PGFLT], 0, GD_KT, t_pgflt, 0);
SETGATE(idt[T_FPERR], 0, GD_KT, t_fperr, 0);
SETGATE(idt[T_ALIGN], 0, GD_KT, t_align, 0);
SETGATE(idt[T_MCHK], 0, GD_KT, t_mchk, 0);
SETGATE(idt[T_SIMDERR], 0, GD_KT, t_simderr, 0);
SETGATE(idt[T_SYSCALL], 0, GD_KT, t_syscall, 3);
SETGATE(idt[T_DEFAULT], 0, GD_KT, t_default, 0);
// 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.
if (tf->tf_trapno == T_PGFLT) {
page_fault_handler(tf);
return;
} else if (tf->tf_trapno == T_BRKPT || tf->tf_trapno == T_DEBUG) {
monitor(tf);
return;
} else if (tf->tf_trapno == T_SYSCALL) {
int32_t returned = syscall(tf->tf_regs.reg_eax,
tf->tf_regs.reg_edx,
tf->tf_regs.reg_ecx,
tf->tf_regs.reg_ebx,
tf->tf_regs.reg_edi,
tf->tf_regs.reg_esi);
tf->tf_regs.reg_eax = returned;
return;
}
// 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);
}

23
kern/trap.h Normal file
View File

@ -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 <inc/trap.h>
#include <inc/mmu.h>
/* 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 */

107
kern/trapentry.S Normal file
View File

@ -0,0 +1,107 @@
/* See COPYRIGHT for copyright information. */
#include <inc/mmu.h>
#include <inc/memlayout.h>
#include <inc/trap.h>
###################################################################
# 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
.globl sysenter_handler
sysenter_handler:
push %ebp // holds env's stack pointer
push %esi // holds the env's return addr
push %edi
push %ebx
push %ecx
push %edx
push %eax
call syscall
add $0x14, %esp
pop %edx
pop %ecx
sysexit
/*
* Lab 3: Your code here for generating entry points for the different traps.
*/
TRAPHANDLER_NOEC(t_divide, T_DIVIDE);
TRAPHANDLER_NOEC(t_debug, T_DEBUG);
TRAPHANDLER_NOEC(t_nmi, T_NMI);
TRAPHANDLER_NOEC(t_brkpt, T_BRKPT);
TRAPHANDLER_NOEC(t_oflow, T_OFLOW);
TRAPHANDLER_NOEC(t_bound, T_OFLOW);
TRAPHANDLER_NOEC(t_illop, T_OFLOW);
TRAPHANDLER_NOEC(t_device, T_OFLOW);
TRAPHANDLER(t_dblflt, T_OFLOW);
TRAPHANDLER(t_tss, T_TSS);
TRAPHANDLER(t_segnp, T_SEGNP);
TRAPHANDLER(t_stack, T_STACK);
TRAPHANDLER(t_gpflt, T_GPFLT);
TRAPHANDLER(t_pgflt, T_PGFLT);
TRAPHANDLER_NOEC(t_fperr, T_FPERR);
TRAPHANDLER(t_align, T_ALIGN);
TRAPHANDLER_NOEC(t_mchk, T_MCHK);
TRAPHANDLER_NOEC(t_simderr, T_SIMDERR);
TRAPHANDLER_NOEC(t_syscall, T_SYSCALL);
TRAPHANDLER(t_default, T_DEFAULT);
// HINT 1 : TRAPHANDLER_NOEC(t_divide, T_DIVIDE);
// Do something like this if there is no error code for the trap
// HINT 2 : TRAPHANDLER(t_dblflt, T_DBLFLT);
// Do something like this if the trap includes an error code..
// HINT 3 : READ Intel's manual to check if the trap includes an error code
// or not...
/*
* Lab 3: Your code here for _alltraps
*/
.globl _alltraps
_alltraps:
sub $0x2, %esp
pushw %ds
sub $0x2, %esp
pushw %es
pushal
mov $(GD_KD), %eax
movw %ax, %ds
movw %ax, %es
pushl %esp
call trap

31
lib/Makefrag Normal file
View File

@ -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)

25
lib/console.c Normal file
View File

@ -0,0 +1,25 @@
#include <inc/string.h>
#include <inc/lib.h>
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;
}

35
lib/entry.S Normal file
View File

@ -0,0 +1,35 @@
#include <inc/mmu.h>
#include <inc/memlayout.h>
.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

9
lib/exit.c Normal file
View File

@ -0,0 +1,9 @@
#include <inc/lib.h>
void
exit(void)
{
sys_env_destroy(0);
}

29
lib/libmain.c Normal file
View File

@ -0,0 +1,29 @@
// Called from entry.S to get us going.
// entry.S already took care of defining envs, pages, uvpd, and uvpt.
#include <inc/lib.h>
extern void umain(int argc, char **argv);
const volatile struct Env *thisenv;
const char *binaryname = "<unknown>";
void
libmain(int argc, char **argv)
{
// set thisenv to point at our Env structure in envs[].
// LAB 3: Your code here.
envid_t id = sys_getenvid();
thisenv = &envs[ENVX(id)];
// 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();
}

26
lib/panic.c Normal file
View File

@ -0,0 +1,26 @@
#include <inc/lib.h>
/*
* Panic is called on unresolvable fatal errors.
* It prints "panic: <message>", 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");
}

62
lib/printf.c Normal file
View File

@ -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 <inc/types.h>
#include <inc/stdio.h>
#include <inc/stdarg.h>
#include <inc/lib.h>
// 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;
}

78
lib/syscall.c Normal file
View File

@ -0,0 +1,78 @@
// System call stubs.
#include <inc/syscall.h>
#include <inc/lib.h>
#include <inc/x86.h>
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;
}
int32_t
fast_syscall(int num, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4) {
asm volatile(
"push %%ebp\n\t"
"mov %%esp, %%ebp\n\t"
"lea syscall_ret_%=, %%esi\n\t"
"sysenter\n\t"
"syscall_ret_%=: pop %%ebp\n\t"
: "+a" (num)
: "d" (a1), "c" (a2), "b" (a3), "D" (a4)
: "esi");
return num;
}
void
sys_cputs(const char *s, size_t len)
{
fast_syscall(SYS_cputs, (uint32_t)s, len, 0, 0);
}
int
sys_cgetc(void)
{
return fast_syscall(SYS_cgetc, 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);
}

16
user/Makefrag Normal file
View File

@ -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

11
user/badsegment.c Normal file
View File

@ -0,0 +1,11 @@
// program to cause a general protection exception
#include <inc/lib.h>
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");
}

10
user/breakpoint.c Normal file
View File

@ -0,0 +1,10 @@
// program to cause a breakpoint trap
#include <inc/lib.h>
void
umain(int argc, char **argv)
{
asm volatile("int $3");
}

11
user/buggyhello.c Normal file
View File

@ -0,0 +1,11 @@
// buggy hello world -- unmapped pointer passed to kernel
// kernel should destroy user environment in response
#include <inc/lib.h>
void
umain(int argc, char **argv)
{
sys_cputs((char*)1, 1);
}

13
user/buggyhello2.c Normal file
View File

@ -0,0 +1,13 @@
// buggy hello world 2 -- pointed-to region extends into unmapped memory
// kernel should destroy user environment in response
#include <inc/lib.h>
const char *hello = "hello, world\n";
void
umain(int argc, char **argv)
{
sys_cputs(hello, 1024*1024);
}

13
user/divzero.c Normal file
View File

@ -0,0 +1,13 @@
// buggy program - causes a divide by zero exception
#include <inc/lib.h>
int zero;
void
umain(int argc, char **argv)
{
zero = 0;
cprintf("1/0 is %08x!\n", 1/zero);
}

12
user/evilhello.c Normal file
View File

@ -0,0 +1,12 @@
// evil hello world -- kernel pointer passed to kernel
// kernel should destroy user environment in response
#include <inc/lib.h>
void
umain(int argc, char **argv)
{
// try to print the kernel entry point as a string! mua ha ha!
sys_cputs((char*)0xf010000c, 100);
}

10
user/faultread.c Normal file
View File

@ -0,0 +1,10 @@
// buggy program - faults with a read from location zero
#include <inc/lib.h>
void
umain(int argc, char **argv)
{
cprintf("I read %08x from location 0!\n", *(unsigned*)0);
}

10
user/faultreadkernel.c Normal file
View File

@ -0,0 +1,10 @@
// buggy program - faults with a read from kernel space
#include <inc/lib.h>
void
umain(int argc, char **argv)
{
cprintf("I read %08x from location 0xf0100000!\n", *(unsigned*)0xf0100000);
}

10
user/faultwrite.c Normal file
View File

@ -0,0 +1,10 @@
// buggy program - faults with a write to location zero
#include <inc/lib.h>
void
umain(int argc, char **argv)
{
*(unsigned*)0 = 0;
}

10
user/faultwritekernel.c Normal file
View File

@ -0,0 +1,10 @@
// buggy program - faults with a write to a kernel location
#include <inc/lib.h>
void
umain(int argc, char **argv)
{
*(unsigned*)0xf0100000 = 0;
}

9
user/getc.c Normal file
View File

@ -0,0 +1,9 @@
#include <inc/lib.h>
void
umain(int argc, char **argv)
{
char c;
while(!(c = sys_cgetc()));
cprintf("got character %c\n", c);
}

9
user/hello.c Normal file
View File

@ -0,0 +1,9 @@
// hello, world
#include <inc/lib.h>
void
umain(int argc, char **argv)
{
cprintf("hello, world\n");
cprintf("i am environment %08x\n", thisenv->env_id);
}

10
user/softint.c Normal file
View File

@ -0,0 +1,10 @@
// buggy program - causes an illegal software interrupt
#include <inc/lib.h>
void
umain(int argc, char **argv)
{
asm volatile("int $14"); // page fault
}

27
user/testbss.c Normal file
View File

@ -0,0 +1,27 @@
// test reads and writes to a large bss
#include <inc/lib.h>
#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!!!");
}

72
user/user.ld Normal file
View File

@ -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)
}
}