终于进入第二章了 Operating system organization
请在开始第二章配套实验前,务必阅读 教材 book-riscv-rev3.pdf 第二章全文,第四章的 4.3 4.4两小节,以及教材中提到的相关代码(务必要跟着书中的代码走,否则就是走马观花
),这章呢主要是让你对Xv6是什么,以及Xv6是如何做的有个粗略的概念,比如进程啊、中断啊、堆栈啊、页表啊等等,后面的章节会分别对这些概念进行展开。
另外教材中推荐了自家的 Using the GNU Debugger,介绍了一些基本用法,我也额外推荐一个指南 Quick Guide to gdb: The GNU Debugger , 涵盖了更多的命令和用法,甚至教你如何在gdb界面中单步调试源码,同时显示寄存器和汇编等。
Using gdb
实验介绍如下,这个实验没要求,就是让你熟悉起来gdb (原文链接) :
In many cases, print statements will be sufficient to debug your kernel, but sometimes being able to single step through some assembly code or inspecting the variables on the stack is helpful.
To help you become familiar with gdb, run make qemu-gdb and then fire up gdb in another window (see the gdb bullet on the guidance page).
话不多说开始~
-
首先在项目目录下,一个终端中输入
make qemu-gdb *** Now run 'gdb' in another window. qemu-system-riscv64 -machine virt -bios none -kernel kernel/kernel -m 128M -smp 3 -nographic -global virtio-mmio.force-legacy=false -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 -S -gdb tcp::25501
-
然后另起一个终端输入
gdb GNU gdb (GDB) 16.2 Copyright (C) 2024 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "--host=aarch64-apple-darwin24.2.0 --target=x86_64-apple-darwin20". Type "show configuration" for configuration details. For bug reporting instructions, please see: <https://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word". The target architecture is set to "riscv:rv64". warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x0000000000001000 in ?? () (gdb)
macos 安装 gdb 参考 gdb — Homebrew Formulae
感兴趣可以看一下
Makefile: 312
和 项目中的.gdbinit
文件,问一下AI看看是如何把gdb挂载到qemu运行的xv6系统中的。 -
对 syscall 设置断点,在 gdb 窗口中输入
b syscall Breakpoint 1 at 0x8000208e: file kernel/syscall.c, line 133.
-
恢复运行~,在 gdb 窗口中输入
c Continuing. [Switching to Thread 1.3] Thread 3 hit Breakpoint 1, syscall () at kernel/syscall.c:133 133 { (gdb)
可以看到,断点已经触发了,源码位于
kernel/syscall.c:133
-
输入
layout src
,查看对应的源码文件layout src
界面变得花里胡哨了起来
-
输入
backtrace
查看调用栈backtrace #0 syscall () at kernel/syscall.c:133 #1 0x0000000080001dca in usertrap () at kernel/trap.c:67 #2 0x0505050505050505 in ?? ()
-
然后不停的输入
n
,直到struct proc *p = myproc();
这行代码执行完毕,就两次 -
输入
p/x *p
,以16进制打印指针p
所指向的对象struct proc
的内容p/x *p $1 = {lock = {locked = 0x0, name = 0x800081b8, cpu = 0x0}, state = 0x4, chan = 0x0, killed = 0x0, xstate = 0x0, pid = 0x1, parent = 0x0, kstack = 0x3fffffd000, sz = 0x1000, pagetable = 0x87f73000, trapframe = 0x87f74000, context = {ra = 0x80001514, sp = 0x3fffffde90, s0 = 0x3fffffdec0, s1 = 0x80008d50, s2 = 0x80008920, s3 = 0x1, s4 = 0x3, s5 = 0x800199f0, s6 = 0x1, s7 = 0x4, s8 = 0xffffffffffffffff, s9 = 0x0, s10 = 0x0, s11 = 0x0}, ofile = {0x0 <repeats 16 times>}, cwd = 0x80016e60, name = {0x69, 0x6e, 0x69, 0x74, 0x63, 0x6f, 0x64, 0x65, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}
-
查看
p->trapframe->a7
的值,并与user/initcode.S
对照p/o p->trapframe->a7 $3 = 07
# Initial process that execs /init. # This code runs in user space. #include "syscall.h" # exec(init, argv) .globl start start: la a0, init la a1, argv li a7, SYS_exec ecall # for(;;) exit(); exit: li a7, SYS_exit ecall jal exit # char init[] = "/init\0"; init: .string "/init\0" # char *argv[] = { init, 0 }; .p2align 2 argv: .long init .long 0
可以看到啊,寄存器
a7
的值对应SYS_exec
,查看kernel/syscall.h: 8
,刚好是 7// System call numbers #define SYS_fork 1 #define SYS_exit 2 #define SYS_wait 3 #define SYS_pipe 4 #define SYS_read 5 #define SYS_kill 6 #define SYS_exec 7 #define SYS_fstat 8 #define SYS_chdir 9 #define SYS_dup 10 #define SYS_getpid 11 #define SYS_sbrk 12 #define SYS_sleep 13 #define SYS_uptime 14 #define SYS_open 15 #define SYS_write 16 #define SYS_mknod 17 #define SYS_unlink 18 #define SYS_link 19 #define SYS_mkdir 20 #define SYS_close 21
-
查看寄存器
sstatus
的值,它的说明在 RISC-V privileged instructions中p /x $sstatus $2 = 0x200000022
-
干点坏事,教材说了,在以后的实验中呢,不可避免的会发生内核级的异常,怎么办呢,我们先尝试人为的做点坏事,修改
kernel/syscall.c : 137
为void syscall(void) { int num; struct proc *p = myproc(); // num = p->trapframe->a7; num = * (int *) 0; if(num > 0 && num < NELEM(syscalls) && syscalls[num]) { // Use num to lookup the system call function for num, call it, // and store its return value in p->trapframe->a0 p->trapframe->a0 = syscalls[num](); } else { printf("%d %s: unknown sys call %d\n", p->pid, p->name, num); p->trapframe->a0 = -1; } }
然后重新运行~ 会发现系统启动后就直接抛出异常了(
sepc 值和教材不一样,因为他们更新了代码,教材还没更新
)xv6 kernel is booting hart 2 starting hart 1 starting scause 0x000000000000000d sepc=0x00000000800020a2 stval=0x0000000000000000 panic: kerneltrap
这种情况怎么办呢,可以对异常的
sepc
直接下断点b *0x00000000800020a2 Breakpoint 2 at 0x800020a2: file kernel/syscall.c, line 138.
然后打开汇编窗口继续运行
layout asm c Continuing. Thread 1 hit Breakpoint 2, syscall () at kernel/syscall.c:138
断点触发,这一条指令的汇编代码
0x800020a2 <syscall+20> lw a3,0(zero) # 0x0
这是一个典型的内存访问问题,然后呢你这时候大概率想知道当前运行的进程是什么,就可以输入
p p->name $3 = "initcode\000\000\000\000\000\000\000"
好了结束。