Skip to main content
The QEMU target launches a GDB stub that lets you connect gdb-multiarch, set breakpoints, inspect process control blocks, and single-step through both OS and user-process code without any extra hardware. The BeagleBone target does not expose a GDB server by default, so all interactive debugging is done against QEMU.
QEMU’s -nographic flag routes the UART to stdout in the same terminal session. To exit QEMU press Ctrl+A then X. Closing the terminal or sending Ctrl+C will kill the GDB session but may leave QEMU running in the background.
1

Build with debug symbols

Build for the QEMU target:
make TARGET=qemu
When TARGET=qemu the Makefile selects these compiler and assembler flags:
CFLAGS  = $(BASE_CFLAGS) -O0 -g3 $(PLATFORM_FLAGS)
ASFLAGS = $(BASE_ASFLAGS) -g
-O0 disables all optimisations so variables are never elided and the source-to-instruction correspondence is 1:1. -g3 emits the maximum DWARF debug information, including macro definitions. The BeagleBone target uses -O2 with no -g flag, so release builds produce no debug symbols and cannot be introspected with source-level GDB.The output ELF is written to bin/program.elf.
2

Launch QEMU with the GDB server

Run QEMU manually, or use the make qemu convenience target. The manual command is:
qemu-system-arm \
    -M versatilepb \
    -cpu cortex-a8 \
    -kernel bin/program.elf \
    -nographic \
    -S -s
FlagEffect
-M versatilepbEmulate the Versatile/PB baseboard, which is what the QEMU linker script targets.
-cpu cortex-a8Match the Cortex-A8 used by the BeagleBone Black and the compiler’s -mcpu=cortex-a8 flag.
-nographicRoute UART0 (base 0x101f1000 in QEMU) to the terminal’s stdout/stdin.
-SFreeze the CPU at reset and wait for a GDB continue before executing any instructions.
-sOpen a GDB remote stub on TCP port 1234 (shorthand for -gdb tcp::1234).
QEMU will appear to hang — this is expected. The CPU is halted at the reset vector waiting for GDB.
make qemu builds the ELF and then launches the same QEMU command. It also prints the exact GDB commands needed to connect, which matches what is shown in the next step.
3

Connect GDB

In a second terminal, start gdb-multiarch and connect to the stub:
$ gdb-multiarch bin/program.elf
(gdb) set architecture arm
(gdb) target remote :1234
(gdb) file bin/program.elf
(gdb) break main
(gdb) continue
  • set architecture arm — tells GDB to use the 32-bit ARM instruction set decoder. Without this, GDB may misidentify the architecture from the ELF header.
  • target remote :1234 — connects to the QEMU GDB stub on localhost port 1234.
  • file bin/program.elf — (re-)loads the symbol table. If you already passed the ELF on the command line this is redundant, but it is harmless and ensures symbols are loaded after the remote is connected.
  • break main — sets a breakpoint at the start of main() in OS/os.c.
  • continue — releases the CPU from its initial halt; execution runs until the breakpoint is hit.
4

Useful GDB commands for OS debugging

Registers and disassembly
(gdb) info registers
(gdb) x/10i $pc
info registers prints all 16 ARM core registers (r0r15) plus cpsr and spsr. x/10i $pc disassembles 10 instructions starting at the current program counter — useful after a crash when source correlation is lost.Reading CPSR mode bitsThe bottom 5 bits of cpsr encode the current ARM processor mode:
Bits [4:0]Mode
0x10User
0x12IRQ
0x13Supervisor (SVC)
(gdb) p/x $cpsr & 0x1f
A value of 0x13 means the CPU is in SVC mode (where main and schedule run). A value of 0x12 means it is inside the IRQ handler.Inspecting the current process PCBCurrProcess is a global Process * in OS/os.c:
(gdb) p *CurrProcess
This prints the full Process struct defined in OS/process.h:
$1 = {
  pid   = 1,
  r     = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  sp    = 0x82111000,
  lr    = 0,
  pc    = 0x82100000,
  spsr  = 0x13,
  state = PROCESS_RUNNING,
  next  = 0x20008060
}
Inspecting the scheduler queue
(gdb) p ready_queue
This prints the ProcessQueue struct, which holds a tail pointer and a count:
$2 = {tail = 0x20008060, count = 2}
Follow tail->next manually to walk the circular list, or dereference individual process variables:
(gdb) p p1
(gdb) p p2
Process state valuesThe ProcessState enum (OS/process.h) maps to integers as follows:
ValueName
0PROCESS_RUNNING
1PROCESS_READY
2PROCESS_WAITING
3PROCESS_SLEEPING
4PROCESS_TERMINATED
Breakpoints for scheduler and IRQ events
(gdb) break schedule
(gdb) break irq_handler
break schedule stops execution at the top of schedule() in OS/os.c on every context switch triggered by the timer IRQ. break irq_handler stops on every timer interrupt before the scheduler is called — useful for checking which process was preempted.
5

Inspecting process state in detail

Print each field of a process PCB individually for a cleaner view:
(gdb) p CurrProcess->pid
(gdb) p/x CurrProcess->pc
(gdb) p/x CurrProcess->sp
(gdb) p/x CurrProcess->spsr
(gdb) p CurrProcess->state
spsr holds the CPSR value that was in effect when the process was last preempted. process_init initialises it to 0x00000013 (SVC mode, all interrupt flags clear, ARM instruction set), so a freshly created process that has never been context-switched will show this value.To examine the saved stack of a process that is not currently running, use its sp field as the base address:
(gdb) x/16x p1.sp
This dumps 16 words from the top of P1’s stack, which is where the exception entry code saved the register frame before calling schedule().
GDB’s TUI mode gives a split view with source or assembly alongside the command prompt. Use layout src to show C source (requires DWARF info from -g3) or layout asm to show the disassembly window. Press Ctrl+X A to toggle TUI mode on and off.

Build docs developers (and LLMs) love