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 ./ -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.

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.
The base of this segment. Linear address = logical address + 0x10000000.
The limit of this segment. Linear addresses above 0xffffffff will result in segment violation exceptions.
The raw flags of this segment, which QEMU helpfully decodes for us in the next few fields.
The privilege level of this segment. Only code running with privilege level 0 can load this segment.
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.
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.