Familiarity with your environment is crucial for productive development and debugging. This page gives a brief overview of the JOS environment and useful GDB and QEMU commands. Don't take our word for it, though. Read the GDB and QEMU manuals. These are powerful tools that are worth knowing how to use.
Debugging tips: | Kernel User environments |
---|---|
Reference: | JOS makefile JOS obj/ GDB QEMU Emulator |
All of the tools that you need to execute and complete the labs are installed in your course virtual machine.
If you wish to compile and run the tools on your own machine, the information that you need is as follows. Note that we cannot guarantee that these tools will run on your computer, and we cannot support these tools on your own computer. However, the tools should run on recent versions of Linux. The tools should also run under Windows with the help of Cygwin. Install cygwin, and be sure to install the flex and bison packages (they are under the development header).
Most modern Linux distributions and BSDs have an ELF toolchain compatible with
the JOS labs. That is, the system-standard gcc
,
as
, ld
and objdump
should just work. The lab
Makefile should automatically detect this. If
the makefile fails to detect your build tools,
you can specify their location by adding the following line to
conf/env.mk
:
GCCPREFIX=
Older versions of gdb do not correctly handle the transition to long mode during JOS boot, yielding a "Packet too long" error. This has been fixed in the version that ships with Ubuntu 22.04 (gdb 12.1), possibly older versions. If you see this error, upgrade your development environment to a newer gdb.
QEMU is a modern and fast PC emulator.
Unfortunately, QEMU's debugging facilities, while powerful, are
somewhat immature, so we highly recommend you use the MIT patched version
of QEMU instead of the stock version that may come with your
distribution. The version installed on walter.cs.unc.edu
is already patched.
If you want to install the patched version of qemu on your laptop, you may either use these included debian packages (for Ubuntu 22.04):
To build your own patched version of QEMU, you may download the source from https://github.com/donporter/qemu and build it.
GDB is your friend. Use the qemu-gdb target (or its qemu-gdb-nox
variant) to make
QEMU wait for GDB to attach. See the GDB reference
below for some commands that are useful when debugging kernels.
If you're getting unexpected interrupts, exceptions, or triple faults, you can ask QEMU to generate a detailed log of interrupts using the -d argument.
To debug virtual memory issues, try the QEMU monitor commands info mem (for a high-level overview) or info pg (for lots of detail). Note that these commands only display the current page table.
(Lab 4+) To debug multiple CPUs, use GDB's thread-related commands like thread and info threads.
GDB also lets you debug user environments, but there are a few things you need to watch out for, since GDB doesn't know that there's a distinction between multiple user environments, or between user and kernel.
You can start JOS with a specific user environment using make run-name (or you can edit
kern/init.c
directly). To make QEMU wait for GDB to attach,
use the run-name-gdb
variant.
You can symbolically debug user code, just like you can kernel
code, but you have to tell GDB which symbol
table to use with the symbol-file command, since it
can only use one symbol table at a time. The provided
.gdbinit
loads the kernel symbol table,
obj/kern/kernel
. The symbol table for a user environment is
in its ELF binary, so you can load it using symbol-file
obj/user/name. Don't load symbols from any
.o
files, as those haven't been relocated by the linker
(libraries are statically linked into JOS user binaries, so those
symbols are already included in each user binary). Make sure you get
the right user binary; library functions will be linked at
different EIPs in different binaries and GDB won't know any
better!
(Lab 4+) Since GDB is attached to the virtual machine as a whole, it sees clock interrupts as just another control transfer. This makes it basically impossible to step through user code because a clock interrupt is virtually guaranteed the moment you let the VM run again. The stepi command works because it suppresses interrupts, but it only steps one assembly instruction. Breakpoints generally work, but watch out because you can hit the same EIP in a different environment (indeed, a different binary altogether!).
*-gdb
targets also wait for this
connection). To start once QEMU is running, simply run gdb
from your lab directory. We provide a .gdbinit
file that
automatically points GDB at QEMU, loads the kernel symbol file, and
switches between 16-bit and 32-bit mode. Exiting GDB will shut down
QEMU.
Ctrl-c
or Ctrl-a x
in your terminal.make qemu
, but run with only the serial console.
To exit, press Ctrl-a x
. This is particularly useful over
SSH connections to Athena dialups because the VGA window consumes a
lot of bandwidth.make qemu
, but rather than passively accepting GDB
connections at any time, this pauses at the first machine
instruction and waits for a GDB connection.qemu-nox
and qemu-gdb
targets.make
run-hello
runs user/hello.c
.run-name
that correspond to
the variants of the qemu
target.jos.out
for inspection.When building JOS, the makefile also produces some additional output files that may prove useful while debugging:
obj/boot/boot.asm
,
obj/kern/kernel.asm
, obj/user/hello.asm
, etc.obj/kern/kernel.sym
,
obj/user/hello.sym
, etc.obj/boot/boot.out
, obj/kern/kernel
,
obj/user/hello
, etcSee the GDB manual for a full guide to GDB commands. Here are some particularly useful commands for COMP 630, some of which don't typically come up outside of OS development.
Ctrl-c
.eip
,
eflags
, and the segment selectors. For a much more
thorough dump of the machine register state, see QEMU's own info
registers
command.$eip
as addr will display the
instructions at the current instruction pointer.obj/kern/kernel
. If the machine is running user code, say
hello.c
, you can switch to the hello symbol file using
symbol-file obj/user/hello
.QEMU represents each virtual CPU as a thread in GDB, so you can use all of GDB's thread-related commands to view or manipulate QEMU's virtual CPUs.
QEMU includes a built-in monitor that can inspect and modify the machine state in useful ways. To enter the monitor, press Ctrl-a c in the terminal running QEMU. Press Ctrl-a c again to switch back to the serial console.
For a complete reference to the monitor commands, see the QEMU manual. Here are some particularly useful commands:
x
command.CS =0008 10000000 ffffffff 10cf9a00 DPL=0 CS32 [-R-]
CS =0008
10000000
ffffffff
10cf9a00
DPL=0
CS32
DS
for data segments (not to be confused with the DS
register), and LDT
for local descriptor tables.[-R-]
ef7c0000-ef800000 00040000 urw
efbf8000-efc00000 00008000 -rw
tells us that the 0x00040000 bytes of memory from 0xef7c0000 to
0xef800000 are mapped read/write and user-accessible, while the
memory from 0xefbf8000 to 0xefc00000 is mapped read/write, but only
kernel-accessible.
info mem
, but distinguishes page directory
entries and page table entries and gives the permissions for each
separately. Repeated PTE's and entire page tables are folded up
into a single line. For example,
VPN range Entry Flags Physical page
[00000-003ff] PDE[000] -------UWP
[00200-00233] PTE[200-233] -------U-P 00380 0037e 0037d 0037c 0037b 0037a ..
[00800-00bff] PDE[002] ----A--UWP
[00800-00801] PTE[000-001] ----A--U-P 0034b 00349
[00802-00802] PTE[002] -------U-P 00348
This shows two page directory entries, spanning virtual addresses
0x00000000 to 0x003fffff and 0x00800000 to 0x00bfffff, respectively.
Both PDE's are present, writable, and user and the second PDE is also
accessed. The second of these page tables maps three pages, spanning
virtual addresses 0x00800000 through 0x00802fff, of which the first
two are present, user, and accessed and the third is only present and
user. The first of these PTE's maps physical page 0x34b.
qemu.log
. You can ignore the first two log entries, "SMM:
enter" and "SMM: after RMS", as these are generated before entering
the boot loader. After this, log entries look like
4: v=30 e=0000 i=1 cpl=3 IP=001b:00800e2e pc=00800e2e SP=0023:eebfdf28 EAX=00000005
EAX=00000005 EBX=00001002 ECX=00200000 EDX=00000000
ESI=00000805 EDI=00200000 EBP=eebfdf60 ESP=eebfdf28
...
The first line describes the interrupt. The 4:
is just a
log record counter. v
gives the vector number in hex.
e
gives the error code. i=1
indicates that this
was produced by an int
instruction (versus a hardware
interrupt). The rest of the line should be self-explanatory. See
info registers for a description
of the register dump that follows.
/tmp
instead of the current directory.Last updated: 2025-04-24 13:28:25 -0400 [validate xhtml]