// 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到UENVS处:
1 2 3 4 5 6 7 8 9
// 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. n = ROUNDUP(NENV*sizeof(struct Env), PGSIZE); for (i = 0; i < n; i+=PGSIZE) page_insert(kern_pgdir, pa2page(PADDR(envs) + i), (void *)(UENVS + i), PTE_U | PTE_P);
// 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. memset(envs, 0, sizeof(struct Env) * NENV); int i; env_free_list = envs; for(i = 1; i < NENV; ++i) envs[i-1].env_link = envs + i;
// Per-CPU part of the initialization env_init_percpu(); }
// // 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. // staticint env_setup_vm(struct Env *e) { int i; structPageInfo *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.
// // 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. // staticvoid 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!) void *start_va = ROUNDDOWN(va, PGSIZE); void *end_va = ROUNDUP(va + len, PGSIZE); void *cur_va; for(cur_va = start_va; cur_va < end_va; cur_va += PGSIZE) { structPageInfo * pp = page_alloc(0); if(!pp) panic("region_alloc: Out of memory!\n"); page_insert(e->env_pgdir, pp, (void *)cur_va, PTE_U | PTE_W); } }
// read 1st page off disk readseg((uint32_t) ELFHDR, SECTSIZE*8, 0);
// is this a valid ELF? if (ELFHDR->e_magic != ELF_MAGIC) goto bad;
// load each program segment (ignores ph flags) ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff); eph = ph + ELFHDR->e_phnum; for (; ph < eph; ph++) // p_pa is the load address of this segment (as well // as the physical address) readseg(ph->p_pa, ph->p_memsz, ph->p_offset);
// call the entry point from the ELF header // note: does not return! ((void (*)(void)) (ELFHDR->e_entry))();
bad: outw(0x8A00, 0x8A00); outw(0x8A00, 0x8E00); while (1) /* do nothing */; }
// // 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? // staticvoid 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.)
// // 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. structEnv * e; if(env_alloc(&e, 0)) panic("env_create: env alloc failed!\n"); load_icode(e, binary); e->env_type = type; }
// // 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.
(gdb) b *0x800a33 Breakpoint 2 at 0x800a33 (gdb) c Continuing. => 0x800a33: int $0x30
Breakpoint 2, 0x00800a33 in ?? ()
发现确实执行到了这一条指令,以上的实现应该是没有问题。
Exercise 3
内容为阅读Chapter 9,是关于Exceptions和Interrupts的内容。
Exercise 4
从inc/trap.h当中可以发现,TrapFrame有着如下的结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
structTrapframe { structPushRegstf_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));
Divide error 0 No Debug exceptions 1 No Breakpoint 3 No Overflow 4 No Bounds check 5 No Invalid opcode 6 No Coprocessor not available 7 No System error 8 Yes (always 0) Coprocessor Segment Overrun 9 No Invalid TSS 10 Yes Segment not present 11 Yes Stack exception 12 Yes General protection fault 13 Yes Page fault 14 Yes Coprocessor error 16 No Two-byte SW interrupt 0-255 No
// Set up a normal interrupt/trap gate descriptor. // - istrap: 1 for a trap (= exception) gate, 0 for an interrupt gate. // see section 9.6.1.3 of the i386 reference: "The difference between // an interrupt gate and a trap gate is in the effect on IF (the // interrupt-enable flag). An interrupt that vectors through an // interrupt gate resets IF, thereby preventing other interrupts from // interfering with the current interrupt handler. A subsequent IRET // instruction restores IF to the value in the EFLAGS image on the // stack. An interrupt through a trap gate does not change IF." // - sel: Code segment selector for interrupt/trap handler // - off: Offset in code segment for interrupt/trap handler // - dpl: Descriptor Privilege Level - // the privilege level required for software to invoke // this interrupt/trap gate explicitly using an int instruction. #define SETGATE(gate, istrap, sel, off, dpl) \ { \ (gate).gd_off_15_0 = (uint32_t) (off) & 0xffff; \ (gate).gd_sel = (sel); \ (gate).gd_args = 0; \ (gate).gd_rsv1 = 0; \ (gate).gd_type = (istrap) ? STS_TG32 : STS_IG32; \ (gate).gd_s = 0; \ (gate).gd_dpl = (dpl); \ (gate).gd_p = 1; \ (gate).gd_off_31_16 = (uint32_t) (off) >> 16; \ }
// 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) { case SYS_cputs: sys_cputs((constchar *)a1, (size_t)a2); return0;
case SYS_cgetc: return sys_cgetc();
case SYS_getenvid: return sys_getenvid();
case SYS_env_destroy: return sys_env_destroy((envid_t)a1);
case NSYSCALLS: return0;
default: return -E_INVAL; } }
Exercise 8
在lib/libmain.c当中,进行env_id的指定:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
void libmain(int argc, char **argv) { // set thisenv to point at our Env structure in envs[]. // LAB 3: Your code here. envid_t envid = sys_getenvid(); thisenv = &envs[ENVX(envid)];
// save the name of the program so that panic() can use it if (argc > 0) binaryname = argv[0];
// // 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, constvoid *va, size_t len, int perm) { // LAB 3: Your code here. int newperm = perm | PTE_P; uint32_t cur_addr; pte_t * pte; for(cur_addr = (uint32_t)va; cur_addr < (uint32_t)(va + len); cur_addr = ROUNDDOWN((cur_addr+PGSIZE),PGSIZE)) { if(cur_addr >= ULIM) { user_mem_check_addr = cur_addr; return -E_FAULT; } pte = pgdir_walk(env->env_pgdir, (void *)cur_addr, 0); if((!pte) || ((*pte) & newperm) != newperm){ user_mem_check_addr = cur_addr; return -E_FAULT; } } return0; }
需要注意的是要在kern/syscall.c当中需要填充上有关检查的部分!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// Print a string to the system console. // The string is exactly 'len' characters long. // Destroys the environment on memory errors. staticvoid sys_cputs(constchar *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. user_mem_assert(curenv, s, len, PTE_W);
// Print the string supplied by the user. cprintf("%.*s", len, s); }
else { // 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. conststruct UserStabData *usd = (conststruct UserStabData *) USTABDATA;
// Make sure this memory is valid. // Return -1 if it is not. Hint: Call user_mem_check. // LAB 3: Your code here. if(user_mem_check(curenv, (void *)usd, sizeof(struct UserStabData), PTE_U)) return-1;
[00000000] new env 00001000 Incoming TRAP frame at 0xefffffbc Incoming TRAP frame at 0xefffffbc [00001000] user_mem_check assertion failure for va f010000c [00001000] free env 00001000 Destroyed the only environment - nothing more to do! Welcome to the JOS kernel monitor!
用户环境被销毁了,并且kernel没有panic,说明行为符合预期。
使用make grade命令可以得到如下结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
divzero: OK (1.4s) softint: OK (1.4s) badsegment: OK (2.0s) Part A score: 30/30
faultread: OK (1.9s) faultreadkernel: OK (1.6s) faultwrite: OK (0.9s) faultwritekernel: OK (1.6s) breakpoint: OK (2.0s) testbss: OK (2.1s) hello: OK (1.8s) buggyhello: OK (1.7s) buggyhello2: OK (0.8s) evilhello: OK (1.6s) Part B score: 50/50
divzero: OK (1.4s) softint: OK (1.4s) badsegment: OK (1.6s) Part A score: 30/30
faultread: OK (0.9s) faultreadkernel: OK (1.5s) faultwrite: OK (2.0s) faultwritekernel: OK (1.6s) breakpoint: OK (0.9s) (Old jos.out.breakpoint failure log removed) testbss: OK (1.5s) hello: OK (1.6s) buggyhello: OK (1.0s) buggyhello2: OK (1.4s) evilhello: OK (1.6s) Part B score: 50/50
Score: 80/80
Challenge 2
Intel手册中12.3.1.4节为关于单步调试的相关内容:
This debug condition occurs at the end of an instruction if the trap flag (TF) of the flags register held the value one at the beginning of that instruction. Note that the exception does not occur at the end of an instruction that sets TF. For example, if POPF is used to set TF, a single-step trap does not occur until after the instruction that follows POPF.
[00000000] new env 00001000 Incoming TRAP frame at 0xefffffbc Incoming TRAP frame at 0xefffffbc Welcome to the JOS kernel monitor! Type 'help' for a list of commands. TRAP frame at 0xf0228000 edi 0x00000000 esi 0x00000000 ebp 0xeebfdfd0 oesp 0xefffffdc ebx 0x00000000 edx 0x00000000 ecx 0x00000000 eax 0xeec00000 es 0x----0023 ds 0x----0023 trap 0x00000003 Breakpoint err 0x00000000 eip 0x00800038 cs 0x----001b flag 0x00000046 esp 0xeebfdfd0 ss 0x----0023 K> continue Incoming TRAP frame at 0xefffffbc [00001000] exiting gracefully [00001000] free env 00001000 Destroyed the only environment - nothing more to do! Welcome to the JOS kernel monitor! Type 'help' for a list of commands. K>
#define wrmsr(msr,val1,val2) \ __asm__ __volatile__("wrmsr" \ : /* no outputs */ \ : "c" (msr), "a" (val1), "d" (val2))
从IA32的手册当中可以找到在使用SYSENTER之前所需要设置的相关内容:
IA32_SYSENTER_CS (MSR address 174H) — The lower 16 bits of this MSR are the segment selector for the privilege level 0 code segment. This value is also used to determine the segment selector of the privilege level 0 stack segment (see the Operation section). This value cannot indicate a null selector.
IA32_SYSENTER_EIP (MSR address 176H) — The value of this MSR is loaded into RIP (thus, this value references the first instruction of the selected operating procedure or routine). In protected mode, only bits 31:0 are loaded.
IA32_SYSENTER_ESP (MSR address 175H) — The value of this MSR is loaded into RSP (thus, this value contains the stack pointer for the privilege level 0 stack). This value cannot represent a non-canonical address. In protected mode, only bits 31:0 are loaded.
[00000000] new env 00001000 Incoming TRAP frame at 0xefffffbc hello, world i am environment 00001000 Incoming TRAP frame at 0xefffffbc [00001000] exiting gracefully [00001000] free env 00001000 Destroyed the only environment - nothing more to do!
对比原来的输出:
1 2 3 4 5 6 7 8 9 10
[00000000] new env 00001000 Incoming TRAP frame at 0xefffffbc Incoming TRAP frame at 0xefffffbc hello, world Incoming TRAP frame at 0xefffffbc i am environment 00001000 Incoming TRAP frame at 0xefffffbc [00001000] exiting gracefully [00001000] free env 00001000 Destroyed the only environment - nothing more to do!
可以发现由于在进行系统调用的时候没有采用int 0x30,所以这里在每次输出前并没有都进入trap()函数,使得少去了两行Incoming TRAP frame at ....的输出。