Part A: User Environments and Exception Handling
ENV=PROCESS
Environment State
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
};
Allocating the Environments Array
重新运行qemu时,出现了kernel panic at kern/pmap.c:147: PADDR called with invalid kva 00000000
,问题原因详见MIT 6.828 labs walkthroughs: Lab 3 User Environments
Exercise1
// Make 'envs' point to an array of size 'NENV' of 'struct Env'.
// LAB 3: Your code here.
envs = (struct Env*) boot_alloc(sizeof(struct Env) * NENV);
// 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.
boot_map_region(kern_pgdir, UENVS, PTSIZE, PADDR(envs), PTE_U);
Creating and Running Environments
Exercise2
env_init()
for (int i = NENV - 1; i >= 0; i--) {
envs[i].env_status = ENV_FREE;
envs[i].env_id = 0;
envs[i].env_link = env_free_list;
env_free_list = &envs[i];
}
env_setup_vm()
e->env_pgdir = page2kva(p);
p->pp_ref += 1;
memcpy(e->env_pgdir, kern_pgdir, PGSIZE);
region_alloc()
void *begin = ROUNDDOWN(va, PGSIZE);
void *end = ROUNDUP(va + len, PGSIZE);
if ((uint32_t)end > UTOP) {
panic("region_alloc: cannot allocate pages over UTOP");
}
while (begin < end) {
struct PageInfo *pp;
if ((pp = page_alloc(0)) == NULL) {
panic("region_alloc: out of free memory");
}
int r = page_insert(e->env_pgdir, pp, begin, PTE_U | PTE_W);
if (r != 0) {
panic("region_alloc: %e", r);
}
begin += PGSIZE;
}
load_icode()
struct Elf *elfhr = (struct Elf *)binary;
if (elfhr->e_magic != ELF_MAGIC) {
panic("load_icode: invalid elf header");
}
// switch to env's address space
lcr3(PADDR(e->env_pgdir));
// load each program segment
struct Proghdr *ph = (struct Proghdr *)(binary + elfhr->e_phoff);
struct Proghdr *eph = ph + elfhr->e_phnum;
for (; ph < eph; ph++) {
if (ph->p_type != ELF_PROG_LOAD)
continue;
region_alloc(e, (uint8_t *)ph->p_va, ph->p_memsz);
memcpy((uint8_t *)ph->p_va, binary + ph->p_offset, ph->p_filesz);
memset((uint8_t *)ph->p_va + ph->p_filesz, 0, ph->p_memsz - ph->p_filesz);
}
// make eip points to the entry point
e->env_tf.tf_eip = elfhr->e_entry;
// Now map one page for the program's initial stack
// at virtual address USTACKTOP - PGSIZE.
// LAB 3: Your code here.
region_alloc(e, (void *)(USTACKTOP - PGSIZE), PGSIZE);
// switch back to kernel address space
lcr3(PADDR(kern_pgdir));
env_create()
struct Env *env;
int r;
if((r = env_alloc(&env, 0)) != 0)
panic("env_create: %e", r);
load_icode(env, binary);
env->env_type = type;
env_run()
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);
Handling Interrupts and Exceptions
Basics of Protected Control Transfer
Interrupt Descriptor Table
Task State Segment
Types of Exceptions and Interrupts
0-31:synchronous exceptions
32-255:software interrupts(int)、asynchronous hardware interrupts
48:software interrupts(int)
An Example
Nested Exceptions and Interrupts
在内核模式中发生异常或中断
Setting Up the IDT
Exercise4
/*
* Lab 3: Your code here for generating entry points for the different traps.
*/
TRAPHANDLER_NOEC(th_divide, T_DIVIDE)
TRAPHANDLER_NOEC(th_debug, T_DEBUG)
TRAPHANDLER_NOEC(th_nmi, T_NMI)
TRAPHANDLER_NOEC(th_brkpt, T_BRKPT)
TRAPHANDLER_NOEC(th_oflow, T_OFLOW)
TRAPHANDLER_NOEC(th_bound, T_BOUND)
TRAPHANDLER_NOEC(th_illop, T_ILLOP)
TRAPHANDLER_NOEC(th_device, T_DEVICE)
TRAPHANDLER(th_dblflt, T_DBLFLT)
TRAPHANDLER(th_tss, T_TSS)
TRAPHANDLER(th_segnp, T_SEGNP)
TRAPHANDLER(th_stack, T_STACK)
TRAPHANDLER(th_gpflt, T_GPFLT)
TRAPHANDLER(th_pgflt, T_PGFLT)
TRAPHANDLER_NOEC(th_fperr, T_FPERR)
TRAPHANDLER(th_align, T_ALIGN)
TRAPHANDLER_NOEC(th_mchk, T_MCHK)
TRAPHANDLER_NOEC(th_simderr, T_SIMDERR)
/*
* Lab 3: Your code here for _alltraps
*/
_alltraps:
pushl %ds
pushl %es
pushal
movw $GD_KD, %ax
movw %ax, %ds
movw %ax, %es
pushl %esp
call trap
void th_divide();
void th_debug();
void th_nmi();
void th_brkpt();
void th_oflow();
void th_bound();
void th_illop();
void th_device();
void th_dblflt();
void th_tss();
void th_segnp();
void th_stack();
void th_gpflt();
void th_pgflt();
void th_fperr();
void th_align();
void th_mchk();
void th_simderr();
SETGATE(idt[T_DIVIDE], 0, GD_KT, &th_divide, 0);
SETGATE(idt[T_DEBUG], 0, GD_KT, &th_debug, 0);
SETGATE(idt[T_NMI], 0, GD_KT, &th_nmi, 0);
SETGATE(idt[T_BRKPT], 0, GD_KT, &th_brkpt, 0);
SETGATE(idt[T_OFLOW], 0, GD_KT, &th_oflow, 0);
SETGATE(idt[T_BOUND], 0, GD_KT, &th_bound, 0);
SETGATE(idt[T_ILLOP], 0, GD_KT, &th_illop, 0);
SETGATE(idt[T_DEVICE], 0, GD_KT, &th_device, 0);
SETGATE(idt[T_DBLFLT], 0, GD_KT, &th_dblflt, 0);
SETGATE(idt[T_TSS], 0, GD_KT, &th_tss, 0);
SETGATE(idt[T_SEGNP], 0, GD_KT, &th_segnp, 0);
SETGATE(idt[T_STACK], 0, GD_KT, &th_stack, 0);
SETGATE(idt[T_GPFLT], 0, GD_KT, &th_gpflt, 0);
SETGATE(idt[T_PGFLT], 0, GD_KT, &th_pgflt, 0);
SETGATE(idt[T_FPERR], 0, GD_KT, &th_fperr, 0);
SETGATE(idt[T_ALIGN], 0, GD_KT, &th_align, 0);
SETGATE(idt[T_MCHK], 0, GD_KT, &th_mchk, 0);
SETGATE(idt[T_SIMDERR], 0, GD_KT, &th_simderr, 0);
Question1
-
To push the corresponding error code onto the stack. This is used for the codes going to handle it further like
trap_dispatch()
to distinguish the interrupts. -
To provide permission control or isolation. For each standalone interrupt handler, we can define it whether can be triggered by a user program or not. By putting such limits on interrupt handlers, we can ensure user programs would not interfere with the kernel, corrupt the kernel or even take control of the whole computer.
Question2
I didn’t have to do anything extra to do. It triggers an Interrupt 13 because only the kernel running in Ring 0 can trigger the handler of page fault as we defined above. This meets the “Executing the INT n instruction when the CPL is greater than the DPL of the referenced interrupt, trap, or task gate.” condition, so the processor triggers a General Protection Exception (Interrupt 13)
If we allow a page fault to be triggered by a user program like softint
. It can manipulate virtual memory and may cause serious security issues.
Part B: Page Faults, Breakpoints Exceptions, and System Calls
Handling Page Faults
Exercise5
switch (tf->tf_trapno) {
case T_PGFLT:
page_fault_handler(tf);
return;
default:
// 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;
}
}
The Breakpoint Exception
Exercise6
在trap_dispatch()
中增加case调用kern/monitor
中的monitor()
case T_BRKPT:
monitor(tf);
return;
因为现在Breakpoint Exception只能在内核模式下调用,所以kern/trap.c
中trap_init()
里的
SETGATE(idt[T_BRKPT], 0, GD_KT, &th_brkpt, 0);
应该更改为:
SETGATE(idt[T_BRKPT], 0, GD_KT, &th_brkpt, 3);
Question3
导致产生general protection fault
的几种情况:
- Accessing a gate that contains a null segment selector.
- Executing the INT n instruction when the CPL is greater than the DPL of the referenced interrupt, trap, or task gate.
- The segment selector in a call, interrupt, or trap gate does not point to a code segment.
Question4
保护机制
System calls
Exercise7
kern/trapentry.S
TRAPHANDLER_NOEC(th_syscall, T_SYSCALL)
kern/trap.c:trap_init()
void th_syscall();
SETGATE(idt[T_SYSCALL], 0, GD_KT, &th_syscall, 3);
kern/trap.c:trap_dispatch()
case T_SYSCALL:
tf->tf_regs.reg_eax = 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);
return;
kern/syscall.c:syscall()
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;
}
User-mode startup
Exercise8
thisenv = &envs[ENVX(sys_getenvid())];
Page faults and memory protection
Exercise9&10
kern/trap.c:page_fault_handler()
if ((tf->tf_cs & 0x3) == 0) {
panic("page_fault_handler: page fault in kernel mode");
}
kern/pmap.c:user_mem_check()
int
user_mem_check(struct Env *env, const void *va, size_t len, int perm)
{
// LAB 3: Your code here.
uint32_t addr = (uint32_t)va;
uint32_t begin = ROUNDDOWN(addr, PGSIZE);
uint32_t end = ROUNDUP(addr + len, PGSIZE);
while (begin < end) {
pte_t *pte = pgdir_walk(env->env_pgdir, (void *)begin, 0);
// Thanks @trace-andreason for telling me the mistake on next line
if (begin >= ULIM || pte == NULL || !(*pte & PTE_P) || (*pte & perm) != perm) {
user_mem_check_addr = (begin < addr) ? addr : begin;
return -E_FAULT;
}
begin += PGSIZE;
}
return 0;
}
kern/syscall.c:sys_cputs()
user_mem_assert(curenv, s, len, PTE_U);
kern/kdebug.c:debuginfo_eip()
if (user_mem_check(curenv, usd, sizeof(struct UserStabData), PTE_U) < 0) {
return -1;
}
...
if (user_mem_check(curenv, stabs, stab_end - stabs, PTE_U) < 0) {
return -1;
}
if (user_mem_check(curenv, stabstr, stabstr_end - stabstr, PTE_U) < 0) {
return -1;
}