Page Faults, Breakpoints Exceptions, and System Calls

Exercise 5,6,7

在trap_dispath中添加代码,能够传到相应的处理函数执行。

static void
trap_dispatch(struct Trapframe *tf)
{
  if (tf->tf_trapno == T_PGFLT) {
    page_fault_handler(tf);
    return ;
  }

  if (tf->tf_trapno == T_BRKPT) {
    break_point_handler(tf);
    return ;
  }

  if (tf->tf_trapno == T_SYSCALL) {
    system_call_handler(tf);
    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;
  }
}

添加system_call 参数传递代码。注意系统调用对应的中断的 DPL = 3。

void
system_call_handler(struct Trapframe *tf)
{
  uint32_t syscallno, a1, a2, a3, a4, a5;
  syscallno = (tf->tf_regs).reg_eax;
  a1 = (tf->tf_regs).reg_edx;
  a2 = (tf->tf_regs).reg_ecx;
  a3 = (tf->tf_regs).reg_ebx;
  a4 = (tf->tf_regs).reg_edi;
  a5 = (tf->tf_regs).reg_esi;
  (tf->tf_regs).reg_eax = (uint32_t)syscall(syscallno, a1, a2, a3, a4, a5);
}

添加dispatch system_call 的代码:

// 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)
{
  uint32_t ret = 0;
  switch (syscallno) {
  case SYS_cputs : 
    sys_cputs((char *)a1, (size_t)a2);
    break;

  case SYS_cgetc :
    ret = (uint32_t)sys_cgetc();
    break;

  case SYS_getenvid :
    ret = (envid_t)sys_getenvid();
    break;

  case SYS_env_destroy :
    ret = (uint32_t)sys_env_destroy((envid_t)a1);
    break;
  
  default :
    ret = -E_INVAL;
    break;
  }

  return ret;
}

Exercise 8,9,10

在libmain.c 中添加代码,初始化 thisenv,调用sys_getenvid()获取当前运行进程的envid,然后根据ENVX(envid)得出当前env在envs数组中的偏移量,获取thisenv。 进程退出的时候,exit() 函数调用系统调用sys_env_destory(0), 0 表示当前的env。

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];

  // call user main routine
  umain(argc, argv);

  // exit gracefully
  exit();
}

在lib/entry.S 中已经初始化了envs,指向系统的envs数组,所以envs可以直接 extern 之后使用。

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

处理PageFault。对于这个中断,如果发生在kernel处理自己的数据结构的时候,那么说明kernel bug了,这时候就应该把kernel panic 掉。如果发生在kernel 去引用user的一个指针的时候(一个指针就是一个虚拟地址),如kernel 访问 user 的一个地址,出现了PGFLT,那么就需要把 user 的这个env panic 掉。因为kernel有更多的权限,所以在处理PGFLT的时候,应该小心。 为了在kernel态区分这两种PGFLT,user_mem_check()这个函数就是判断这个env是否有访问一个虚拟地址va的权限的。

int
user_mem_check(struct Env *env, const void *va, size_t len, int perm)
{
  void * va_end = (void*)ROUNDUP(va + len, PGSIZE);
  pte_t * pte;
  // 注意这里 第一次的va不用ROUND,之后的都需要
  // 如果 va = va + PGSIZE,那么在 buggyhello2 就过不了
  // 囧,虽然我觉得功能上都一样
  for ( ; va < va_end ; va = ROUNDUP(va + PGSIZE, PGSIZE)) {
    pte = pgdir_walk(env->env_pgdir, va, 0);
    if (!pte || ( (*pte & (perm | PTE_P)) != (perm | PTE_P) )) {
      user_mem_check_addr = (uintptr_t)va;
      return  -E_FAULT;
    }
  }

  return 0;
}

在PGFLT中添加判断,如果是kernel在处理自己的数据结构的时候产生的,那么就直接panic掉。因为tf->cs保存的是中断之前的cs,所以通过判断cs的CPL就能够判断出是user的还是kernel的。通过error code 的 U/S 字段也一样。

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.
  if ((tf->tf_cs & 3 ) == 0){ 
    panic("page_fault_halder : page fault in kernel mode.\n");
    return ;
  }


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

user发出系统调用,kernel在访问user的地址空间的时候都需要做检查,在系统调用中添加mem检查,下面的const char *s 就是用户的一个地址空间。

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, (void*)s, len, PTE_W | PTE_U);

  // Print the string supplied by the user.
  cprintf("%.*s", len, s);
}

在kern/kdebug.c 中添加对 user 地址的检查。

int
debuginfo_eip(uintptr_t addr, struct Eipdebuginfo *info)
{
  ... 
  ...
  // 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, usd, sizeof(struct UserStabData), PTE_U) < 0)
    return -1;
  
  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.
  if (user_mem_check(curenv, stabs, stab_end - stabs, PTE_U) < 0 ||
      user_mem_check(curenv, stabstr, stabstr_end - stabstr, PTE_U) < 0)
    return -1;
  ...
  ...
}

– EOF –