MIT 6.828 学习笔记5 Lab3实验报告

来源:互联网 发布:指纹校准软件 编辑:程序博客网 时间:2024/05/19 22:50

Lab3 实验报告

Exercise 1

  • Modify mem_init() in kern/pmap.c to allocate and map the envs array.
// mem_int()// 第一处envs = (struct Env *) boot_alloc(NENV * sizeof(struct Env));memset(pages, 0, NENV * sizeof(struct Env));// 第二处boot_map_region(kern_pgdir, UENVS, PTSIZE, PADDR(envs), PTE_U | PTE_P);

这里仿照上一个实验可以比较轻松的写出来,注意,由于内核已用内存多出了 NENV 这一段,所以还需要修改 page_init 函数内的上限,参照 LAB 2 实验报告的代码,修改如下

// page_init()size_t right_i = PGNUM(PADDR(envs + NENV));

Exercise 2

  • In the file env.c, finish coding the following functions.
// env_init()env_free_list = envs;struct Env *pre = envs;for (int i = 1; i != NENV; ++i) {    pre -> env_link = envs + i;    pre = envs + i;}

这里是初始化 envs 数组,然后插入到空闲链表中,由注释可知,需要确保分配 env 的时候是从 env[0] 开始分配的,也就是使用尾接法建链表,这和之前的 pages 不同

// env_setup_vm()p->pp_ref++;e->env_pgdir = page2kva(p);memcpy(e->env_pgdir, kern_pgdir, PGSIZE);

初始化用户地址空间中内核部分的虚拟内存并设置一级页表,可以将之前的 kern_pgdir 复制过去

// region_alloc()uintptr_t low = ROUNDDOWN((uintptr_t) va, PGSIZE);uintptr_t high = ROUNDUP((uintptr_t) va + len, PGSIZE);if (high > UTOP) {    panic("allocation fails");}while (low < high) {    struct PageInfo *pp = page_alloc(ALLOC_ZERO);    if (pp == NULL) {        panic("allocation falis");    }    pp->pp_ref++;    int r = page_insert(e->env_pgdir, pp, (void *) low, PTE_P | PTE_U | PTE_W);    if (r != 0) {        panic("region_alloc: %e", r);           }    low += PGSIZE;}

env 分配物理内存,接着映射到虚拟内存,注意需要对齐以及考虑边界情况

// load_icode()// 第一处struct Elf *elf = (struct Elf *) binary;struct Proghdr *ph = (struct Proghdr *) (binary + elf->e_phoff);struct Proghdr *eph = ph + elf->e_phnum;lcr3(PADDR(e->env_pgdir));while (ph < eph) {    if (ph->p_type == ELF_PROG_LOAD) {        region_alloc(e, (void *) ph->p_va, ph->p_memsz);        memcpy((void *) ph->p_va, binary + ph->p_offset, ph->p_filesz);    }    ph++;}lcr3(PADDR(kern_pgdir));e->env_tf.tf_eip = elf->e_entry;// 第二处region_alloc(e, (void *) (USTACKTOP - PGSIZE), PGSIZE);

elf 二进制文件读入用户地址空间中,仿照 bootloadermain.c 的做法,需要了解 elf 文件的结构,这个之前学习过,需要注意的是,为了读入用户地址空间,需要切换到用户的页表,还有记得设置 eip 指向入口处

// env_create()struct Env *e;int r = env_alloc(&e, 0);if (r < 0) {    panic("env_alloc: %e", r);}load_icode(e, binary);e->env_type = type;

利用之前的函数创建 env ,对于 parent_id 由于分配页面的时候,把页面都清 0 了,所以这里不显式设置也行

// env_run()if (curenv && curenv->env_status == ENV_RUNNING) {    curenv->env_status = ENV_RUNNABLE;}curenv = e;curenv->env_status = ENV_RUNNING;lcr3(PADDR(curenv->env_pgdir));env_pop_tf(&(curenv->env_tf));

运行 env ,需要设置状态并切换页表,这里起到关键作用的函数是 env_pop_tf ,通过查看代码可以知道,它将 trapframe 里保存的信息 pop 到了对应的寄存器上,注意在 load_icode 函数的最后一句把 trapframe 中的 eip 设置到了二进制文件的入口,所以在执行完 env_pop_tf 后,下一条指令的地址将会是二进制文件的入口,因此达到了切换的目的

Exercise 3

  • Read Chapter 9, Exceptions and Interrupts in the 80386 Programmer’s Manual.

这个需要读一读,不然后面可能会遇到一些困难,不过我也没认真读完,主要是英文看起来好累……过程中到网上查一些相关的中文资料,收获还是挺大的

Exercise 4

  • Edit trapentry.S and trap.c and implement the features described above.
// trapentry.S// 第一处TRAPHANDLER_NOEC(divide_error, T_DIVIDE)TRAPHANDLER_NOEC(debug_exception, T_DEBUG)TRAPHANDLER_NOEC(non_maskable_interrupt, T_NMI)TRAPHANDLER_NOEC(break_point, T_BRKPT)  // 注意这个地方千万不能用 breakpoint 作为函数名TRAPHANDLER_NOEC(overflow, T_OFLOW)TRAPHANDLER_NOEC(bounds_check, T_BOUND)TRAPHANDLER_NOEC(illegal_opcode, T_ILLOP)TRAPHANDLER_NOEC(device_not_available, T_DEVICE)TRAPHANDLER(double_fault, T_DBLFLT)TRAPHANDLER(invalid_task_switch_segment, T_TSS)TRAPHANDLER(segment_not_present, T_SEGNP)TRAPHANDLER(stack_fault, T_STACK)TRAPHANDLER(general_protection_fault, T_GPFLT)TRAPHANDLER(page_fault, T_PGFLT)TRAPHANDLER_NOEC(floating_point_error, T_FPERR)TRAPHANDLER(alignment_check, T_ALIGN)TRAPHANDLER_NOEC(machine_check, T_MCHK)TRAPHANDLER_NOEC(SIMD_floating_point_exception, T_SIMDERR)

使用上面给的宏定义来设置处理 trap 的函数,这里的函数名可以自己取,但是要注意, T_BRKPT 的函数名不能是 breakpoint ,因为 inc/x86.h 中含有同名函数,系统在调用时会调用 inc/x86.h 里的那个函数,关于是否需要 errorcode 可以查看 LEC 8handouts ,里面含有相关信息

// trapentry.S// 第二处_alltraps:  pushl %ds  pushl %es  pushal  movl $GD_KD, %eax  movw %ax, %ds  movw %ax, %es  pushl %esp  call trap  popal  popl %es  popl %ds  addl $8, %esp   iret

根据之前所说,在引发异常时 CPU 会把 SS 寄存器到 EIP 寄存器压入栈中,如果需要 error code 的话也会压入,而在上面宏定义的函数中,trapno 也被压入了,所以这里只需要 push 余下的寄存器,注意需要根据 trapframe 的结构倒序压入, pushal 指令会按顺序将 eaxedi 压入栈中,call 之后的指令是当 call trap 失败时可以还原相关寄存器

// trap.c// trap_init()extern void divide_error();extern void debug_exception();extern void non_maskable_interrupt();extern void break_point();extern void overflow();extern void bounds_check();extern void illegal_opcode();extern void device_not_available();extern void double_fault();extern void invalid_task_switch_segment();extern void segment_not_present();extern void stack_fault();extern void general_protection_fault();extern void page_fault();extern void floating_point_error();extern void alignment_check();extern void machine_check();extern void SIMD_floating_point_exception();SETGATE(idt[T_DIVIDE], 0, GD_KT, divide_error, 0);SETGATE(idt[T_DEBUG], 0, GD_KT, debug_exception, 0);SETGATE(idt[T_NMI], 0, GD_KT, non_maskable_interrupt, 0);SETGATE(idt[T_BRKPT], 0, GD_KT, break_point, 3);SETGATE(idt[T_OFLOW], 0, GD_KT, overflow, 0);SETGATE(idt[T_BOUND], 0, GD_KT, bounds_check, 0);SETGATE(idt[T_ILLOP], 0, GD_KT, illegal_opcode, 0);SETGATE(idt[T_DEVICE], 0, GD_KT, device_not_available, 0);SETGATE(idt[T_DBLFLT], 0, GD_KT, double_fault, 0);SETGATE(idt[T_TSS], 0, GD_KT, invalid_task_switch_segment, 0);SETGATE(idt[T_SEGNP], 0, GD_KT, segment_not_present, 0);SETGATE(idt[T_STACK], 0, GD_KT, stack_fault, 0);SETGATE(idt[T_GPFLT], 0, GD_KT, general_protection_fault, 0);SETGATE(idt[T_PGFLT], 0, GD_KT, page_fault, 0);SETGATE(idt[T_FPERR], 0, GD_KT, floating_point_error, 0);SETGATE(idt[T_ALIGN], 0, GD_KT, alignment_check, 0);SETGATE(idt[T_MCHK], 0, GD_KT, machine_check, 0);SETGATE(idt[T_SIMDERR], 0, GD_KT, SIMD_floating_point_exception, 0);

设置 IDT ,需要先声明函数,需要注意,由于 break_point 普通用户也可以使用,所以 DPL = 3SETGATE 的定义在 inc/mmu.h 之中

Questions

  • What is the purpose of having an individual handler function for each exception/interrupt? (i.e., if all exceptions/interrupts were delivered to the same handler, what feature that exists in the current implementation could not be provided?)

不同异常或中断的处理方式与结果不相同,例如是否可以恢复或从哪里恢复,条件也不一定相同,例如对权限等级与 errorcode 等参数的要求不同,所以需要拥有不同的处理函数

  • Did you have to do anything to make the user/softint program behave correctly? The grade script expects it to produce a general protection fault (trap 13), but softint’s code says int $14. Why should this produce interrupt vector 13? What happens if the kernel actually allows softint’s int $14 instruction to invoke the kernel’s page fault handler (which is interrupt vector 14)?

由于 trap 14IDT 内描述符的 DPL = 0 ,而此时 CPL = 3 即权限不足,所以执行这条指令会引发 trap 13

Exercise 5

  • Modify trap_dispatch() to dispatch page fault exceptions to page_fault_handler().
// trap.c// trap_init()switch (tf->tf_trapno) {    case T_PGFLT:        page_fault_handler(tf);        break;    default:        print_trapframe(tf);        if (tf->tf_cs == GD_KT)            panic("unhandled trap in kernel");        else {            env_destroy(curenv);            return;        }}

根据 trapno 判断异常的类型,然后分配给相应函数

Exercise 6

  • Modify trap_dispatch() to make breakpoint exceptions invoke the kernel monitor.
// trap_init()switch (tf->tf_trapno) {    case T_PGFLT:        page_fault_handler(tf);        break;    case T_BRKPT:        monitor(tf);        break;    default:        print_trapframe(tf);        if (tf->tf_cs == GD_KT)            panic("unhandled trap in kernel");        else {            env_destroy(curenv);            return;        }}

简单加上相应分支即可

Questions

  • The break point test case will either generate a break point exception or a general protection fault depending on how you initialized the break point entry in the IDT (i.e., your call to SETGATE from trap_init). Why? How do you need to set it up in order to get the breakpoint exception to work as specified above and what incorrect setup would cause it to trigger a general protection fault?

这个和上一个问题类似,如果设置 break pointDPL = 0 则会引发权限错误,由于这里设置的 DPL = 3 ,所以会引发断点

  • What do you think is the point of these mechanisms, particularly in light of what the user/softint test program does?

这个机制有效地防止了一些程序恶意任意调用指令,引发一些危险的错误,所以我认为这个粒度的权限机制时十分必要的

Exercise 7

  • Add a handler in the kernel for interrupt vector T_SYSCALL.
// trap.c// trap_dispatch()struct PushRegs regs = tf->tf_regs;switch (tf->tf_trapno) {    case T_PGFLT:        page_fault_handler(tf);        break;    case T_BRKPT:        monitor(tf);        break;    case T_SYSCALL:        tf->tf_regs.reg_eax = syscall(regs.reg_eax, regs.reg_edx, regs.reg_ecx, regs.reg_ebx, regs.reg_edi, regs.reg_esi);        break;    default:        print_trapframe(tf);        if (tf->tf_cs == GD_KT)            panic("unhandled trap in kernel");        else {            env_destroy(curenv);            return;        }}

还是和之前一样,分配相关的异常处理函数,这里需要将结果保存到 eax 寄存器中,记得在 IDT 中增加相应表项

// trapentry.STRAPHANDLER_NOEC(system_call, T_SYSCALL)// trap.c// trap_init()extern void system_call();SETGATE(idt[T_SYSCALL], 0, GD_KT, system_call, 3);

这里需要设置 syscallDPL = 3 ,接下来是 syscall.c 的部分

// 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_NO_SYS;}

只需简单地根据 syscallno 调用不同的函数即可

Exercise 8

  • Add the required code to the user library, then boot your kernel.
// libmain.c// libmain()thisenv = &envs[ENVX(sys_getenvid())];

由于之前没有设置 thisenv 的值,所以运行到 hello 的第二句时会出现错误,这里根据 id 取出索引,然后找到相应 env

Exercise 9

  • Change kern/trap.c to panic if a page fault happens in kernel mode. Implement user_mem_check in that same file.Change kern/syscall.c to sanity check arguments to system calls.
// trap.c// page_fault_handler()if ((tf->tf_cs & 3) == 0) {    panic("page fault in kern");}

由于 cs 寄存器的低 2 位的值与 CPL 相等,所以可以根据 cs 寄存器判断是否在内核态

// pmap.c// user_mem_check()uintptr_t high = ROUNDUP((uintptr_t) va + len, PGSIZE);for (uintptr_t low = (uintptr_t) va; low < high; low = ROUNDUP(low + 1, PGSIZE)) {    pte_t *pte = pgdir_walk(env->env_pgdir, (void *) low, false);    if (!pte || (~(*pte) & perm) || low >= ULIM) {        user_mem_check_addr = low;        return -E_FAULT;    }}return 0;

通过页表找到相应的 pte ,然后判断是否具有权限,这里需要记录第一个出错的虚拟地址,所以一开始不能将 va 对齐,还有这种写法的 high 不能向下对齐,因为结果可能会比 low 还小

// syscall.c// sys_cputs()user_mem_assert(curenv, s, len, 0);

利用刚才的函数检查这一段地址

// kdebug.c// debuginfo_eip()// 第一处if (user_mem_check(curenv, (void *) USTABDATA, sizeof(struct UserStabData), 0) < 0) {    return -1;}// 第二处if (user_mem_check(curenv, (void *) stabs, stab_end - stabs, 0) < 0 || user_mem_check(curenv, (void *) stabstr, stabstr_end - stabstr, 0) < 0) {    return -1;}

同理,检查相应的地址
关于最后一个问题,在调用 backtrace 后显示如下

K> backtraceStack backtrace: ebp efffff20 eip f0100c79 args 00000001 efffff38 f0198000 00000000 f0175840     kern/monitor.c:217: monitor+276 ebp efffff90 eip f01039a0 args f0198000 efffffbc 00000000 00000082 00000000     kern/trap.c:192: trap+199 ebp efffffb0 eip f0103aa8 args efffffbc 00000000 00000000 eebfdfd0 efffffdc     kern/trapentry.S:80: <unknown>+0 ebp eebfdfd0 eip 00800073 args 00000000 00000000 eebfdff0 00800049 00000000     lib/libmain.c:26: libmain+58 ebp eebfdff0 eip 00800031 args 00000000 00000000Incoming TRAP frame at 0xeffffea4kernel panic at kern/trap.c:261: page fault in kern

这里引发页错误的原因是访问到了用户栈顶以上,可以看到, libmain 的两个参数都是 0 ,回想一下,在 lib/entry.S 中,系统在 USTACKTOP 执行了两次 pushl $0 ,所以当往上找第三个参数时就到达了上面的空内存,所以引发了页错误

Exercise 10

  • Boot your kernel, running user/evilhello. The environment should be destroyed, and the kernel should not panic.

这里只要完成了 Exercise 9 就可以通过这题,即 make grade 全部通过

0 0
原创粉丝点击