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.
Build with debug symbols
Build for the QEMU target:When
TARGET=qemu the Makefile selects these compiler and assembler flags:-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.Launch QEMU with the GDB server
Run QEMU manually, or use the
QEMU will appear to hang — this is expected. The CPU is halted at the reset vector waiting for GDB.
make qemu convenience target. The manual command is:| Flag | Effect |
|---|---|
-M versatilepb | Emulate the Versatile/PB baseboard, which is what the QEMU linker script targets. |
-cpu cortex-a8 | Match the Cortex-A8 used by the BeagleBone Black and the compiler’s -mcpu=cortex-a8 flag. |
-nographic | Route UART0 (base 0x101f1000 in QEMU) to the terminal’s stdout/stdin. |
-S | Freeze the CPU at reset and wait for a GDB continue before executing any instructions. |
-s | Open a GDB remote stub on TCP port 1234 (shorthand for -gdb tcp::1234). |
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.Connect GDB
In a second terminal, start
gdb-multiarch and connect to the stub: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 ofmain()inOS/os.c.continue— releases the CPU from its initial halt; execution runs until the breakpoint is hit.
Useful GDB commands for OS debugging
Registers and disassembly
A value of This prints the full Inspecting the scheduler queueThis prints the Follow Process state valuesThe
Breakpoints for scheduler and IRQ events
info registers prints all 16 ARM core registers (r0–r15) 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 |
|---|---|
0x10 | User |
0x12 | IRQ |
0x13 | Supervisor (SVC) |
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:Process struct defined in OS/process.h:ProcessQueue struct, which holds a tail pointer and a count:tail->next manually to walk the circular list, or dereference individual process variables:ProcessState enum (OS/process.h) maps to integers as follows:| Value | Name |
|---|---|
0 | PROCESS_RUNNING |
1 | PROCESS_READY |
2 | PROCESS_WAITING |
3 | PROCESS_SLEEPING |
4 | PROCESS_TERMINATED |
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.Inspecting process state in detail
Print each field of a process PCB individually for a cleaner view:This dumps 16 words from the top of P1’s stack, which is where the exception entry code saved the register frame before calling
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:schedule().