Skip to content

Debugging SlayerOS ​

This guide covers techniques and tools for debugging SlayerOS. Effective debugging is essential for kernel development, where traditional debugging methods may not be available.

Debugging Approaches ​

SlayerOS supports several debugging approaches:

  1. Serial Port Logging: Output debug messages via serial port
  2. GDB Remote Debugging: Connect GDB to QEMU for source-level debugging
  3. Core Dumps: Analyze memory dumps after crashes
  4. Assertions: Catch programming errors at runtime

Serial Port Debugging ​

Serial port debugging is the most basic and reliable method for kernel debugging.

Enabling Serial Output ​

Serial output is enabled by default in limine.conf:

SERIAL=yes

Logging Functions ​

The kernel provides several logging functions in dbg/log.h:

cpp
// Log levels
enum LogLevel {
    LOG_DEBUG,
    LOG_INFO,
    LOG_WARN,
    LOG_ERROR,
    LOG_FATAL
};

// Logging functions
void log_debug(const char* fmt, ...);
void log_info(const char* fmt, ...);
void log_warn(const char* fmt, ...);
void log_error(const char* fmt, ...);
void log_fatal(const char* fmt, ...);

Example usage:

cpp
#include <dbg/log.h>

void initialize_device() {
    log_debug("Initializing device at 0x%x", device_addr);
    
    if (!device_present()) {
        log_error("Device not found!");
        return;
    }
    
    log_info("Device initialized successfully");
}

Viewing Serial Output ​

When running in QEMU, use the -serial stdio option to redirect serial output to the terminal:

bash
make run

The Makefile already includes this option.

GDB Remote Debugging ​

GDB allows source-level debugging of the kernel running in QEMU.

Starting a Debug Session ​

  1. Start QEMU with GDB server:
bash
make debug
  1. In another terminal, start GDB:
bash
make gdb

This connects GDB to the QEMU GDB server.

Common GDB Commands ​

(gdb) break _start                # Set breakpoint at kernel entry
(gdb) break kernel.cxx:42         # Set breakpoint at line 42 in kernel.cxx
(gdb) continue                    # Continue execution
(gdb) step                        # Step into function
(gdb) next                        # Step over function
(gdb) print variable              # Print variable value
(gdb) info registers              # Show CPU registers
(gdb) x/10x 0x1000                # Examine 10 hex words at address 0x1000
(gdb) backtrace                   # Show call stack

Debugging with Symbols ​

For effective debugging, ensure debug symbols are enabled:

bash
make clean
make DEBUG=1

Assertions ​

Assertions help catch programming errors early by checking conditions that should always be true.

Using Assertions ​

cpp
#include <libc/assert.h>

void process_data(void* data, size_t size) {
    // Verify preconditions
    assert(data != nullptr, "Data pointer cannot be null");
    assert(size > 0, "Size must be positive");
    
    // Process data...
}

When an assertion fails, it prints the error message and halts the kernel.

Memory Debugging ​

Heap Corruption ​

To debug heap corruption:

  1. Enable heap validation in mem/heap.cxx:
cpp
#define HEAP_VALIDATE
  1. This adds checks before and after each allocation to detect buffer overflows.

Memory Leaks ​

Track memory allocations by implementing a simple memory tracker:

cpp
struct allocation_info {
    void* ptr;
    size_t size;
    const char* file;
    int line;
};

// In a debug build, modify kmalloc/kfree to track allocations

ELF and Symbol Debugging ​

SlayerOS includes ELF parsing capabilities that can be used for debugging:

Symbol Resolution ​

The err/meta_resolver.cxx file contains functions to resolve addresses to symbol names:

cpp
const char* resolve_symbol(uintptr_t addr);

This is used for generating meaningful backtraces.

Debugging Boot Issues ​

Boot problems can be particularly challenging to debug:

  1. Enable verbose boot: Add VERBOSE=yes to limine.conf
  2. Check memory map: Use log_debug to print the memory map early in boot
  3. Verify bootloader requests: Ensure all Limine requests are properly set up
  4. Single-step through initialization: Use GDB to step through the boot process

Hardware Debugging ​

For debugging hardware interaction:

  1. I/O Port Monitoring: Use QEMU's -d ioport option
  2. Instruction Tracing: Use QEMU's -d in_asm option
  3. Memory Tracing: Use QEMU's -d page option

Example:

bash
qemu-system-x86_64 -cdrom build/slay.iso -serial stdio -d ioport,in_asm

Best Practices ​

  1. Log liberally: Add detailed logging, especially for initialization code
  2. Use assertions: Check preconditions and invariants
  3. Isolate problems: Test components individually when possible
  4. Incremental changes: Make small, testable changes
  5. Version control: Commit working versions before major changes

Debugging Tools Reference ​

ToolPurposeCommand
GDBSource-level debuggingmake gdb
QEMU MonitorVM control and inspectionPress Ctrl+Alt+2 in QEMU
Serial ConsoleKernel outputBuilt into make run
ObjdumpDisassemble kernelobjdump -d build/slay.kernel
ReadelfExamine ELF structurereadelf -a build/slay.kernel

SlayerOS