xv6 Project Instructions

Your Repository

Create a git repository for your code on gitlab, and add antoinek as a member to your project with at least reporter permissions. guest is not sufficient because it does not allow us to pull your code.

Pull the xv6 code from here:

https://github.com/xiw/xv6.git

and push it to your repo.

Tools

You can work on attu (ssh attu.cs.washington.edu) and use our pre-configured toolchain by adding the directory to your $PATH environment variable:

$ export PATH=/cse/courses/cse451/16au/bin:$PATH

You either have to run this command manually in every session, or add it to your shell’s initialization file (e.g. ~/.bashrc).

You can also use your own machine, in which case you need the following tools:

For Linux you should be able to use the versions that ship with your distribution. On some 64-bit systems you might need to install 32-bit libraries for gcc (e.g. gcc-multilib for Debian/Ubuntu). For Qemu, however, we provide a separate version with additional debugging capabilities (mostly related to virtual memory). So you do not need the modified version for the assignments, but they might be useful if you get stuck debugging virtual memory.

If you want to try to compile the modified Qemu version from source, you can find instructions here. For those of you interested in building a compiler toolchain from source (e.g. if you are on a non-ELF system, or just for fun) there are instructions here.

If you are running OS X there is instructions here on how to install the necessary tools.

Building and Running

You can compile xv6 by simply running:

$ make

And all generated files can be removed again, e.g. for recompiling, by running:

$ make clean

While xv6 does not take a lot of time to compile, you can still speed up compilation a bit by telling make to run multiple jobs in parallel, e.g. to run 4 parallel jobs:

$ make -j4

The Makefile provides targets to run xv6 in the Qemu emulator:

$ make qemu
$ make qemu-nox

The former runs the graphical version of qemu, while the latter runs the console version. To exit the console version use Ctrl + a x.

Qemu lets you control the number of CPUs it emulates. We recommend that you start implementing and debugging with only one CPU (the default is 2), and then move on to more than one, since concurrency issues can make debugging more complicated. To control the number of CPU set the CPUS make variable:

$ make qemu-nox CPUS=1

You can also modify the Makefile to change the default.

Debugging

Qemu can be configured to work with GDB for debugging. The xv6 Makefile takes care of this if you add -gdb to the qemu targets:

$ make qemu-gdb
$ make qemu-nox-gdb

Qemu will then wait for GDB to connect before it starts executing. To make things easier, the Makefile generates a .gdbinit file that contains the necessary commands for GDB to connect to QEMU. Then while leaving qemu running launch GDB in another shell (new terminal, separate ssh connection, new tmux window:

$ gdb

Recent GDB versions, including the one on attu, will not automatically load .gdbinit files anymore for security reasons. You have four choices to proceed:

  1. Add add-auto-load-safe-path /absolute/path/to/.gdbinit to your ~/.gdbinit to enable autoloading of only this file (good if you use GDB in untrusted codebases).
  2. You can completely disable the security check and tell GDB to load any .gdbinit file automatically by adding set auto-load safe-path / to your ~/.gdbinit.
  3. You can explicitly tell GDB to load the file by running gdb -x .gdbinit every time.
  4. You can manually run the commands in .gdbinit every time.

This page provides some additional information on debugging with GDB and Qemu.

User Space

GDB can also be used to debug user space code with Qemu, but there are some caveats and this sometimes gets a little messy.

GDB has no information about which process is currently executing or even if user space or kernel code is being executed when running with Qemu. Confusion between user space and kernel space is less of an issue since the kernel generally executes in a part of the address space that’s disjoint from user space so things like break points do not get confused since their addresses are not overlapping with user space applications.

For user space things get confusing if there is more than one process running, since those processes will share the same address space (in terms of virtual addresses, and virtual addresses are all that GDB deals with). So if you set a break point on a particular function for one process, this break point might be triggered by an instruction at the same address in another process, i.e. you might get spurious breaks. In GDB you can try telling them apart by looking at the current instruction at the break point that GDB prints, and matching them against the instruction you would expect there if you look at the assembly of the application (objdump -d FILE), but if you are unlucky this might still be ambiguous. The definite way to tell is by switching to the Qemu debug console with Ctrl + a c and doing info registers there and looking at the value of the cr3 register. cr3 contains the physical address of the page directory and is specific to each process. To give you something to match against you might want to add a debug print to your kernel code that prints out the address of the page directories as processes are created.

To start debugging a user space application, start Qemu and GDB as described above for kernel code. Next you need to let GDB know which binary to use to look up symbols and debug information. So let’s say we are debugging ls:

(gdb) symbol-file _ls
Load new symbol table from "_ls"? (y or n) y
Reading symbols from _ls...done.

Now we can refer to symbols in the _ls binary, e.g. by setting a break point on the fmtname function:

(gdb) break fmtname
Breakpoint 1 at 0x60: file ls.c, line 8.

Now if we launch ls from the shell GDB will stop at the break point. You can reduce some spurious breaks by setting break points only right before you need them. In this example you could wait until the shell is initialized, then hit Ctrl + c in GDB, set the break point, continue and then launch ls in the shell.

Qemu Console

Qemu itself also provides some additional debug features beyond what is accessible through GDB. We will add additional info here as this might be useful for problem sets.

For debugging issues relating to traps Qemu can provide logs of each trap that occurs including the full CPU state at the moment it occurs. This is especially useful if you are debugging some crashes associated with traps, since the Qemu logs will always be correct and available, which might not be the case with information you print out in the kernel code. The logs can be enabled by adding additional command line flags, e.g.:

make QEMUEXTRA='-d int -D qemu.log' qemu-nox

This page provides additional details on some other Qemu debug features.