快乐的春饼

xv6 学习笔记:Lab System calls

Lab System calls

这个 lab 与上一个正好互补。上一个 lab 中,我们通过预定义的几个系统调用编写了一些用户态程序,在这个 lab 中,我们要编写一个系统调用。

学习

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 位控制设备中断是否被启用。

sstatus 布局

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

scause (supervisor cause) 寄存器记录了陷入 S-mode 的原因。

scause 布局

scause 值

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

stval 布局

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 程序相同的方式多增长点堆内存,然后暴力搜索字符串即可。

我的代码