6.S081 Lab - System Calls
实验指导:Lab - System Calls
实验任务
Using gdb
Looking at the backtrace output, which function called syscall?

bt 之后可以看出是 usertrap()。
What is the value of p->trapframe->a7 and what does that value represent?
- 在
initCode.S
中可以看到li a7, SYS_exec
。a7
寄存器中存放的是 system call 的 ID。 - 在执行完
*p = myproc()
之后p /x $a7
打印出来的值是 0x7,查看系统调用表,它代表SYS_fstat
。
1 | static uint64 (*syscalls[])(void) = { |
What was the previous mode that the CPU was in?
RISC-V privileged instructions 表示 「When a trap is taken, SPP is
set to 0 if the trap originated from user mode, or 1
otherwise」。于是我们要看 SPP 位,SPP 位是 sstatus
寄存器的第八位。p /x $sstatus1
的结果是
0x20000022
,第八个比特位是 0,于是可以确认是 user
mode。
Write down the assembly instruction the kernel is panicing at. Which register corresponds to the variable num?
在 kernel.asm
中可以看到是这条:80001c6c: 00002683 lw a3,0(zero) # 0 <_entry-0x80000000>
。指令是
lw
,寄存器是 a3。
Why does the kernel crash? Hint: look at figure 3-3 in the text; is address 0 mapped in the kernel address space? Is that confirmed by the value in scause above?
VMA 0x0 (PMA 0x80000000) 在内核空间。scause = 0xd 是 Load page fault。应该是因为在用户态不能 dereference 内核空间的地址。

What is the name of the process that was running when the kernel paniced? What is its process id (pid)?
PID 是 0x1,进程名是 initcode。

System call tracing
这个 task 要求实现一个 syscall 来 trace 给定的 syscall。例如
trace 32 grep hello README
可以打印出
grep hello README
的进程和它所有的子进程的
read
调用,32 是 read
的掩码。这里的掩码(mask)可以用一个 32 位的数表示,每一位代表一个
syscall。xv6 总共的 syscall 的数量只有不到 32 个。
整理一下 xv6 syscall 的流程:
- 用户程序调用
user/user.h
中的 syscall wrapper。这些 wrapper 的实现代码在user/usys.S
中,这个汇编代码由user/usys.pl
生成。 user/usys.pl
生成代码用ecall
指令系统调用。$stvec
寄存器中保存着响应代码的地址,当ecall
被触发后跳转到$stvec
指向的代码,即tampoline.S
。这一步是由硬件来完成的。在真正跳转到$stvec
之前硬件会自动保存一些硬件上下文,例如$sepc
/$sstatus
/$scause
。- trampoline 的代码会保存用户上下文,然后跳转到
kernel/trap.c:usertrap
。 kernel/trap.c:usertrap
调用kernel/syscall.c:syscall
函数。这也验证了我们在第一个任务中看到的。kernel/syscall.c:syscall
通过查表来确定和调用系统调用函数。kernel/syscall.c:syscall
使用kernel/syscall.c:usertrapret
结束调用,跳转到 trampoline 中的userret
。userret
恢复用户状态,调用sret
恢复硬件上下文($sepc
/$sstatus
/$scause
)并跳转到用户代码。
1 | user/user.h:syscall_wrapper_func |
所以,要给 xv6 加一个 trace 的 system call 需要首先在
user/user.h
中加入一个 wrapper 函数声明:
1 | int trace(int); |
接着在 user/usys.pl
中加入一个入口点,让构建程序可以生成这个 wrapper 函数的定义:
1 | entry("trace"); |
编译之后可以看到 user/usys.S
中生成了函数的定义:
1 | trace: |
对于 trampoline 和 usertrap 我们无需修改,下一步我们考虑让
kernel/syscall.c
支持 SYS_trace
。首先在
kernel/syscall.h
中定义 SYS_trace
:
1 |
然后在系统调用表中加入对这个函数的支持:
1 | extern uint64 sys_trace(void); |
下面来实现这个 syscall 的功能。这个 syscall 需要把 mask 放入 proc 结构中,好让其他 syscall 发生的时候查询 proc 结构中的 mask,来决定是否要 log 这个 syscall。
1 | struct proc { |
1 | uint64 |
接下来考虑 fork 的情况,让子进程也支持 trace 给定的 syscall,于是我们要在 fork 的时候把 mask 复制给子进程的 proc 结构。
1 | int |
最后在 kernel/syscall.c:syscall
中根据条件来打印
trace:
1 | static char* syscalls_name[] = { |
搞定!

Attack xv6
// 未完待续...