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.
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.
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.
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:
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)..gdbinit
file automatically by adding set auto-load safe-path /
to your
~/.gdbinit
.gdb -x .gdbinit
every time.This page provides some additional information on debugging with GDB and Qemu.
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 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.