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 over slow link 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 our course, 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.
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.