CS372H lab tools guide
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.
Jump: JOS makefile, GDB
commands, QEMU commands
JOS makefile
The JOS GNUmakefile includes a number of phony targets for running JOS
in various ways:
- make qemu
- Build everything and start qemu with the VGA console in a new
window and the serial console in your terminal. To exit, either
close the VGA window or press Ctrl-c or Ctrl-a x
in your terminal.
- make qemu-nox
- Like make qemu, but run with only the serial console.
To exit, press Ctrl-a x. This is particularly useful over
SSH connections to the UTCS machines because the VGA window consumes a
lot of bandwidth.
- make qemu-gdb
- Like make qemu, but pauses at the first machine
instruction and waits for a GDB connection. Run gdb from
your lab directory to connect to QEMU. We provide a
.gdbinit file that should automatically point GDB at QEMU
and switch between 16-bit and 32-bit mode as appropriate. Exiting
GDB will shut down QEMU.
- make qemu-nox-gdb
- A combination of the qemu-nox and qemu-gdb
targets.
- make run-name
- (Lab 3+) Run user program name. For example, make
run-hello runs user/hello.c.
- make run-name-nox,
run-name-gdb,
run-name-gdb-nox,
- (Lab 3+) Variants of run-name that correspond to
the variants of the qemu target.
Make usually prints out every command being executed. JOS suppresses
this, but if make is giving you trouble, you can revert to its usual
behavior using make V=. Similarly, the grade scripts have
a verbose mode, which you can invoke using sh
./grade-labN.sh -v.
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.
- Assembly code listings for the bootloader, kernel, and user
programs.
- obj/kern/kernel, obj/user/hello, etc
- Linked ELF images of the kernel and user programs. These
contain symbol information that can be used by GDB.
GDB commands
See the GDB
manual for a full guide to GDB commands. Here are some
particularly useful commands for CS372H, some of which don't typically
come up outside of OS development.
- Ctrl-c
- Halt the machine and break in to GDB at the current
instruction.
- c (or continue)
- Continue execution until the next breakpoint or Ctrl-c.
- si (or stepi)
- Execute one machine instruction.
- b *addr (or breakpoint)
- Set a breakpoint at the EIP addr. Used without the
*, b expects the name of a function, or a
file:line to place a breakpoint on.
- set print pretty
- Enable pretty-printing of arrays and structs.
- info registers
- Print the general purpose registers, eip,
eflags, and the segment selectors. For a much more
thorough dump of the machine register state, see QEMU's own info
registers command.
- x/Nx addr
- Display a hex dump of N words starting at virtual address
addr. If N is omitted, it defaults to 1. addr
can be any expression.
- x/Ni addr
- Display the N assembly instructions starting at
addr. Using $eip as addr will display the
instructions at the current instruction pointer.
- symbol-file file
- (Lab 3+) Switch to symbol file file. When GDB attaches
to QEMU, it has no notion of the process boundaries within the
virtual machine, so we have to tell it which symbols to use. By
default, we configure GDB to use the kernel symbol file,
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.
- add-symbol-file file load_addr
- (Lab 3+) Add the symbols in the symbol file file. Sometimes
it is useful to tell GDB about user code symbols while keeping the
kernel symbols. This command tells GDB to read the new symbol file, but
still keep the old symbols from obj/kern/kernel. Since GDB does
not know where file is loaded, you must specify the load address
(most likely 0x800020) yourself. For example, if you are
running hello.c, you would use
add-symbol-file obj/user/hello 0x800020.
QEMU commands
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:
- xp/Nx paddr
- Display a hex dump of N words starting at physical
address paddr. If N is omitted, it defaults to 1.
This is the physical memory analogue of GDB's x
command.
- info registers
- Display a full dump of the machine's internal register state.
In particular, this includes the machine's hidden segment
state for the segment selectors and the local, global, and interrupt
descriptor tables, plus the task register. This hidden state is the
information the virtual CPU read from the GDT/LDT when the segment
selector was loaded. Here's the CS when running in the JOS kernel
in lab 1 and the meaning of each field:
CS =0008 10000000 ffffffff 10cf9a00 DPL=0 CS32 [-R-]
- CS =0008
- The visible part of the code selector. We're using segment
0x8. This also tells us we're referring to the global descriptor
table (0x8&4=0), and our CPL (current privilege level) is
0x8&3=0.
- 10000000
- The base of this segment. Linear address = logical address +
0x10000000.
- ffffffff
- The limit of this segment. Linear addresses above 0xffffffff
will result in segment violation exceptions.
- 10cf9a00
- The raw flags of this segment, which QEMU helpfully decodes
for us in the next few fields.
- DPL=0
- The privilege level of this segment. Only code running with
privilege level 0 can load this segment.
- CS32
- This is a 32-bit code segment. Other values include
DS for data segments (not to be confused with the DS
register), and LDT for local descriptor tables.
- [-R-]
- This segment is read-only.
- info mem
- (Lab 2+) Display mapped virtual memory and permissions. For
example,
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 pg
- (Lab 2+) Display the current page table structure. The output
is similar to info mem, but distinguishes page directory
entries and page table entries and gives the permissions for each
separately. Unbroken sequences of PDE's or PTE's with identical
permissions are compressed into a single line, where the number in
parenthesis gives the number of PDE's or PTE's in hex. For example,
PDE(001) 00000000-00400000 00400000 urw
|-- PTE(000008) 00200000-00208000 00008000 urw
PDE(001) 00800000-00c00000 00400000 urw
|-- PTE(000006) 00800000-00806000 00006000 urw
This shows two page directory entries, spanning 0x00000000 to
0x00400000 and 0x00800000 to 0x00c00000, respectively. The first PDE
contains a sequence of 0x8 PTE's spanning 0x00008000 bytes of virtual
memory from 0x00200000 to 0x00208000.