xv6 学习笔记:Lab System calls
这个 lab 与上一个正好互补。上一个 lab 中,我们通过预定义的几个系统调用编写了一些用户态程序,在这个 lab 中,我们要编写一个系统调用。
学习
- xv6 book 第 2 章,以及第 4 章的 4.3 和 4.4 节,第 3 章也先看一下
- RISC-V privileged instructions
GDB
做操作系统实验需要用 gdb 调试程序。需要调试程序的时候,打开一个终端窗口,在 xv6 目录下输入make qemu-gdb。然后打开另一个终端窗口,在 xv6 目录下,输入gdb。gdb 窗口可能会给出这样的警告:
⚠️ warning: File "/home/xxx/project/xv6-labs-2026/.gdbinit" auto-loading has been declined by your `auto-load safe-path' set to "$debugdir:$datadir/auto-load".
To enable execution of this file add
add-auto-load-safe-path /home/xxx/project/xv6-labs-2026/.gdbinit
line to your configuration file "/home/xxx/.config/gdb/gdbinit".
这是 gdb 的安全警告,只需要在/home/xxx/.config/gdb/gdbinit中写入add-auto-load-safe-path /home/xxx/project/xv6-labs-2026/.gdbinit,然后重新打开 gdb 即可。
gdb 常用命令:
layout src # c源代码视图
layout asm # 汇编视图
layout split # c 和汇编视图
backtrace # 打印函数调用链
c # continue, 继续运行程序直到下一个断点
n # next, 单步执行,不进入函数
s # step, 单步执行并进入函数
ni # 单步执行一条汇编指令
finish # 执行到函数返回点
# 断点
b *0x80001d20 # 在虚拟地址 0x80001d20 处断点
b syscall # 在 syscall 函数开始处断点
b syscall.c:146 # 在 syscall.c 的第 146 行处断点
b <位置> if <条件表达式> # 满足条件表达式时才停下来
info break # 列出所有断点
info registers # 查看寄存器的值
delete 断点标号 # 删除断点
# 查看变量
p xxx # 查看变量值
p /x xxx # 以十六进制查看变量值
p /t xxx # 以二进制查看变量值
p $rrr # 查看 rrr 寄存器的值
默认调试的是内核。如果要调试用户程序,执行file user/_xxx。
riscv
riscv 有三种权限等级: machine-mode (M-mode), supervisor-mode (S-mode), user-mode (U-mode)。M-mode 有最高权限。S-mode 通常用于运行操作系统,能访问物理内存地址和处理设备中断。U-mode 权限最低,用来运行用户程序。
riscv 机器开机时,用 M-mode 运行。然后运行操作系统代码,切换成 S-mode。此后操作系统就一直在 S-mode 和 U-mode 中交替运行。
常用的几个寄存器:
sstatus (supervisor status) 寄存器记录了处理器当前的操作状态。SPP 位指示了这个 hart (hardware thread,逻辑线程) 在进入 S-mode 之前的特权模式。当 trap 发生的时候,如果 trap 来自 U-mode,SPP 设成 0,否则为 1,并控制 sret 指令返回到什么模式。SIE 位控制设备中断是否被启用。

sepc (supervisor exception program counter) 寄存器记录了陷入 S-mode 时发生异常的指令所处的虚拟地址。
scause (supervisor cause) 寄存器记录了陷入 S-mode 的原因。


stval (supervisor trap value) 寄存器提供了特定于异常的信息来辅助软件处理 S-mode 陷入。

kernel panic
把kernel/syscall.c中函数syscall的num = p->trapframe->a7替换成num = * (int *) 0,然后运行 xv6,会发现 xv6 报错,发生了 kerneltrap,并给出了 3 个寄存器的值:
scause=0xd sepc=0x80001d20 stval=0x0
panic: kerneltrap
通过上面的 scause 表格,可以发现发生 panic 的原因是 load page fault。没有额外的异常信息。产生异常的指令位于虚拟地址 0x80001d20。可以用 gdb 打断点定位到相关代码处,也可以通过下面的方式:
kernel/kernel.asm 是通过 objdump -S kernel/kernel 反汇编出来的内核代码。拿到上面的 sepc 寄存器值后,可以在 kernel.asm 里搜索这个地址,定位到相关代码。
练习
Sandbox a command
编写一个系统调用 interpose。按照提示,并且参考其他系统调用的实现即可。
值得注意的是,由于我们在编写操作系统代码,而且写的是沙箱,目的是阻止进程执行相应的系统调用。所以这个沙箱不能让进程通过某种方式又能执行被阻止的系统调用。为此,一旦把一个进程的某个系统调用的 mask 位设成了 1,这个进程(以及所有子进程)以后都不能再把这个 mask 位设置成 0。不然的话,恭喜你发明了一个 CVE。
Sandbox with allowed pathnames
和上一个练习类似,提示也很仔细。同样类似的,已经设置过 pathname 就不能再被设置。
Attack xv6
这个练习字很多,并且要阅读很多代码。但是有简单的解法:按照和 secret 程序相同的方式多增长点堆内存,然后暴力搜索字符串即可。