Debugging
Serial console, QEMU debugging, GDB integration, crash analysis, and self-healing diagnostics.
Debug Architecture
Helix provides a comprehensive debugging infrastructure that works from early boot through full runtime operation. The debug stack spans hardware (serial port, GDB stub), kernel (logging macros, panic handler), and tooling (QEMU, GDB, objdump).
Debug Build
Build the kernel with full debug information:
# Debug build (opt-level 0, full debug symbols)
make build-debug
# Or directly with cargo
cargo build --target x86_64-unknown-none
Debug Profile Settings
[profile.dev]
opt-level = 0 # No optimization — code maps directly to source
lto = false # No link-time optimization
debug = true # Full DWARF debug info
panic = "abort" # No unwinding
codegen-units = 256 # Fast compilation
Release + Debug Symbols
For debugging performance issues in optimized code:
[profile.release-debug]
inherits = "release"
debug = true # Keep debug symbols with release optimizations
# Build with release-debug profile
cargo build --profile release-debug --target x86_64-unknown-none
Serial Console
The serial console is the primary debug output channel. It's available from the earliest boot stage.
Hardware Setup
| Parameter | Value |
|---|---|
| Port | COM1 (0x3F8) on x86_64 |
| Baud rate | 115200 |
| Data bits | 8 |
| Parity | None |
| Stop bits | 1 |
| Flow control | None |
Kernel Logging Macros
Each macro automatically includes:
- Timestamp (ticks since boot)
- Source location (file:line) in debug builds
- Log level prefix (
[DBG],[INF],[WRN],[ERR])
Example Output
[ 0.000] [INF] Helix OS v0.1.0
[ 0.001] [INF] CPU: x86_64, 4 cores
[ 0.002] [INF] Memory: 128 MB total, 124 MB free
[ 0.003] [DBG] Bitmap allocator: 32768 frames tracked
[ 0.005] [INF] IDT: 256 vectors configured
[ 0.006] [INF] APIC: Local APIC at 0xFEE00000
[ 0.010] [INF] Scheduler: Round-robin, 10ms quantum
[ 0.015] [INF] HelixFS: mounted at /
[ 0.020] [INF] Modules: 3 loaded (round_robin, nexus, benchmarks)
[ 0.025] [INF] Userspace: shell ready
helix>
QEMU Debugging
QEMU is the primary testing environment for Helix. The build system provides several debugging options.
Launch Commands
# Normal run — serial output to terminal
make qemu
# Debug mode — GDB server on port 1234, waits for connection
make qemu-debug
# Manual QEMU command with full options
qemu-system-x86_64 \
-kernel build/output/helix-kernel \
-serial stdio \
-m 128M \
-smp 4 \
-no-reboot \
-no-shutdown \
-d int,cpu_reset \
-D build/logs/qemu.log
QEMU Flags Reference
| Flag | Purpose |
|---|---|
-kernel <path> | Boot directly from kernel ELF (no bootloader) |
-serial stdio | Redirect serial port to terminal |
-m 128M | Guest RAM size |
-smp 4 | Number of CPU cores |
-no-reboot | Don't reboot on triple fault — halt instead |
-no-shutdown | Keep QEMU open after guest shutdown |
-s | Start GDB server on port 1234 |
-S | Freeze CPU at startup (wait for GDB continue) |
-d int,cpu_reset | Log interrupts and CPU resets |
-D <logfile> | Write QEMU debug output to file |
-monitor stdio | QEMU monitor console (instead of serial) |
-display none | Headless mode (serial only) |
-enable-kvm | Hardware acceleration (Linux host) |
QEMU Monitor Commands
Access the QEMU monitor with Ctrl+A, C:
| Command | Purpose |
|---|---|
info registers | Dump all CPU registers |
info mem | Show page table mappings |
info mtree | Memory region tree |
info cpus | CPU state (running/halted) |
xp /16xg 0xFFFFFFFF80000000 | Examine memory (16 giant words, hex) |
gdbserver | Start GDB server if not already running |
quit | Exit QEMU |
GDB Integration
GDB connects to QEMU's GDB stub for full source-level debugging.
Setup
# Terminal 1: Start QEMU with GDB server
make qemu-debug
# Terminal 2: Connect GDB
gdb build/output/helix-kernel
(gdb) target remote :1234
(gdb) break kernel_main
(gdb) continue
VS Code Launch Configuration
The workspace includes a VS Code debug configuration:
{
"name": "Debug Kernel (QEMU)",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/output/helix-kernel",
"miDebuggerServerAddress": "localhost:1234",
"miDebuggerPath": "gdb",
"setupCommands": [
{ "text": "set architecture i386:x86-64" },
{ "text": "symbol-file ${workspaceFolder}/build/output/helix-kernel" }
]
}
Useful GDB Commands
| Command | Purpose |
|---|---|
break kernel_main | Break at kernel entry |
break *0xFFFFFFFF80001234 | Break at address |
break hal/src/x86_64/idt.rs:42 | Break at source line |
info registers | Show CPU registers |
x/16xg $rsp | Examine stack (16 giant words) |
bt | Backtrace |
frame 3 | Select stack frame |
print variable_name | Print variable value |
watch *0xFFFFFFFF80010000 | Hardware watchpoint |
layout src | TUI source view |
layout asm | TUI assembly view |
stepi | Step one instruction |
nexti | Step over one instruction |
Conditional Breakpoints
# Break on page fault
break hal/src/x86_64/idt.rs:page_fault_handler
condition 1 error_code & 0x1 == 0 # Only on not-present faults
# Break when thread ID matches
break execution/src/scheduler.rs:next_thread
condition 2 current_thread.id == 42
Crash Analysis
When the kernel panics, the panic handler provides structured crash information.
Panic Output Format
Analyzing a Crash
- Read the panic message — usually tells you exactly what went wrong
- Check the location — file:line:column of the panic
- Examine the stack trace — trace the call chain back to the root cause
- Check registers — RAX often contains the return value, RIP is the instruction pointer
- Use addr2line for addresses without symbols:
addr2line -e build/output/helix-kernel 0xFFFFFFFF80023456
# Output: core/src/orchestrator.rs:142
Common Panic Causes
| Symptom | Likely Cause | Fix |
|---|---|---|
| Page fault in kernel | Null pointer dereference or invalid address | Check pointer validity |
| Double fault | Stack overflow | Increase kernel stack size (16 KiB default) |
| General protection fault | Misaligned access or privilege violation | Check memory alignment |
| Triple fault (reboot) | IDT not set up or corrupt | Verify GDT/IDT initialization |
| Assertion failure | Logic error | Read the assertion message |
Memory Debugging
Guard Pages
Kernel stacks are surrounded by unmapped guard pages. A stack overflow triggers a page fault on the guard page instead of silently corrupting adjacent memory:
Memory Leak Detection
In debug builds, the heap allocator tracks all allocations:
Allocation Poisoning
In debug builds:
- Freed memory is filled with
0xDEADBEEF— use-after-free shows up as obvious bad data - Uninitialized memory is filled with
0xCDCDCDCD— uninitialized reads show up clearly - Guard bytes surround allocations — buffer overflows corrupt the guard bytes
Debug Tools
Binary Analysis
# Show kernel binary size breakdown
make size
# Output:
# text data bss dec hex filename
# 245760 16384 32768 294912 48000 build/output/helix-kernel
# Disassemble the kernel
objdump -d build/output/helix-kernel | less
# Disassemble a specific function
objdump -d build/output/helix-kernel | grep -A 50 '<kernel_main>:'
# Show all symbols sorted by size
nm --size-sort build/output/helix-kernel | tail -20
# Show section sizes
size -A build/output/helix-kernel
QEMU Trace Events
# Trace all interrupt-related events
qemu-system-x86_64 ... -d int -D qemu_trace.log
# Trace CPU exceptions
qemu-system-x86_64 ... -d cpu_reset,int -D qemu_trace.log
# Trace memory accesses (very verbose)
qemu-system-x86_64 ... -d mmu -D qemu_trace.log
# Trace specific QEMU trace events
qemu-system-x86_64 ... -trace 'apic_*' -D qemu_trace.log
Kernel Log Levels
| Level | Macro | Compiled In | Description |
|---|---|---|---|
| Error | kerror! | Always | Unrecoverable or severe errors |
| Warn | kwarn! | Always | Potential problems |
| Info | kinfo! | Always | Significant events |
| Debug | kdebug! | Debug only | Detailed diagnostics |
| Trace | ktrace! | Debug only | Very verbose, per-operation |
In release builds, kdebug! and ktrace! compile to no-ops (zero overhead).
Performance Profiling
# Show time spent in each function (requires debug symbols)
# Use QEMU's -plugin mechanism:
qemu-system-x86_64 ... -plugin libinsn.so -d plugin -D profile.log
# Or use the built-in benchmark system:
helix> bench context_switch
helix> bench memory_alloc
helix> stats