The labs in general require not many lines of code (tens to a few hundred lines), but the code is conceptually complicated and often details matter a lot. So, make sure you do the assigned reading for the labs, read the relevant files through, consult the documentation (the RISC-V manuals etc. are on the readings page) before you write any code. Only when you have a firm grasp of the assignment and solution, then start coding. When you start coding, implement your solution in small steps (the assignments often suggest how to break the problem down in smaller steps) and test whether each steps works before proceeding to the next one.

Although most C programs never need to cast between pointers and integers, operating systems frequently do. Whenever you see an addition involving a memory address, ask yourself whether it is an integer addition or pointer addition and make sure the value being added is appropriately multiplied or not. A few pointer common idioms are in particular worth remembering:

Debugging

If you fail a test, make sure you understand why your code fails the test. Insert print statements until you understand what is going on.

You may find that your print statements may produce much output that you would like to search through; one way to do that is to run make qemu inside of script (run man script on your machine), which logs all console output to a file, which you can then search. Don’t forget to exit script.

In many cases, print statements will be sufficient, but sometimes being able to single step through some assembly code or inspecting the variables on the stack is helpful. To use gdb with xv6, you need to first do the following steps:

  xv6@946090421a81:~/xv6-riscv$ make qemu-gdb -j32
  *** Now run 'make gdb' in another window.
  qemu-system-riscv64 -machine virt -global virtio-mmio.force-legacy=false -bios none -kernel kernel/kernel -m 128M -smp 3 -nographic -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 -no-user-config -device virtio-net-device,bus=virtio-mmio-bus.1,netdev=en0 -object filter-dump,id=f0,netdev=en0,file=en0.pcap -netdev type=user,id=en0 -S -gdb tcp::26000
  
  --Type <RET> for more, q to quit, c to continue without paging--
  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 ?? ()
  

There are also several other tips for debugging xv6 system.

If you want to see what the assembly is that the compiler generates for the kernel or to find out what the instruction is at a particular kernel address, see the file kernel.asm, which the Makefile produces when it compiles the kernel. (The Makefile also produces .asm for all user programs.)

If the kernel panics, it will print an error message listing the value of the program counter when it crashed; you can search kernel.asm to find out in which function the program counter was when it crashed, or you can run addr2line -e kernel/kernel pc-value (run man addr2line for details). If you want to get backtrace, restart using gdb: set breakpoint in panic (b panic) when gdb-multiarch starts, followed by c (continue). When the kernel hits the break point, type bt to get a backtrace.

If your kernel hangs (e.g., due to a deadlock) or cannot execute further (e.g., due to a page fault when executing a kernel instruction), you can use gdb to find out where it is hanging. When the kernel appears to hang hit Ctrl-C in the gdb-multiarch window and type bt to get a backtrace.

QEMU has a “monitor” that lets you query the state of the emulated machine. You can get at it by typing Ctrl-a c (the “c” is for console). A particularly useful monitor command is info mem to print the page table. You may need to use the cpu command to select which core info mem looks at, or you could start qemu with make CPUS=1 qemu to cause there to be just one core.