This article covers the fundamentals of compiling and debugging the Linux kernel using QEMU and GDB. The goal is to build a debug-friendly kernel image, run it in a virtual machine, and attach GDB to inspect boot flow, crashes, and kernel state.
Compiling Linux Kernel for debugging
- Clone the Linux kernel source:
git clone --branch v7.0 --depth 1 https://github.com/torvalds/linux.git
- Use your current running kernel config as a starting point. This preserves driver and subsystem selections from your host environment.
cd linux
cp /boot/config-6.17.12-300.fc43.x86_64 .config
- Update the configuration and select debugging options:
make olddefconfig
make menuconfig
make olddefconfig loads .config and fills any missing symbols with default values. Then use make menuconfig to inspect and enable debug options under the Kernel hacking menu.
- Useful debug configuration options
CONFIG_DEBUG_INFO=y: Generate DWARF debug symbols for GDB.CONFIG_DEBUG_INFO_DWARF5=y: Use DWARF version 5 when supported by your toolchain.CONFIG_DEBUG_KERNEL=y: Enable kernel debug checks and diagnostics.CONFIG_FRAME_POINTER=y: Preserve frame pointers for reliable stack traces.CONFIG_CC_OPTIMIZE_FOR_DEBUG=y: Reduce compiler optimizations to make code easier to step through.CONFIG_GDB_SCRIPTS=y: Install helper scripts for kernel debugging in GDB.CONFIG_DEBUG_FS=y: Enable the debug filesystem for runtime diagnostics.CONFIG_KASAN=y: Enable Kernel Address Sanitizer for memory error detection.CONFIG_LOCKDEP=y: Enable lock dependency tracking for deadlock debugging.CONFIG_KGDB=y: Support the kernel’s own remote debugger backend.CONFIG_EARLY_PRINTK=y: Enable early printk output before normal console initialization.
For subsystem-specific bugs, enable additional options such as CONFIG_DEBUG_SLAB, CONFIG_SCHED_DEBUG, or the relevant driver debug options.
- Build the kernel and keep the uncompressed ELF for debugging.
make -j$(nproc)
The bootable image appears at arch/x86/boot/bzImage. The debug binary vmlinux is the uncompressed ELF file that GDB loads.
Running the kernel in QEMU
- Start QEMU with a GDB server and serial console:
qemu-system-x86_64 -kernel arch/x86/boot/bzImage -m 6G -smp 4 -s -S -append "console=ttyS0 nokaslr earlyprintk=serial,ttyS0,115200 panic=-1" -nographic
QEMU options explained:
-kernel arch/x86/boot/bzImage: Boot this kernel image directly.-m 6G: Allocate 6 GiB of RAM.-smp 4: Use 4 virtual CPUs.-s: Start a GDB server on TCP port1234.-S: Pause execution at startup until GDB connects.-append "...": Pass kernel command-line parameters.console=ttyS0: Send kernel messages to the serial port.nokaslr: Disable kernel address-space layout randomization.earlyprintk=serial,ttyS0,115200: Enable very early serial logging.panic=-1: Prevent the kernel from rebooting after a panic.-nographic: Disable the graphical display and use the terminal for serial I/O.
Alternative options:
-gdb tcp::1234: Explicitly set the GDB server port.-display none: Disable any video output.-serial mon:stdio: Combine the QEMU monitor and serial console on the same terminal.
- In another terminal, start GDB with the uncompressed kernel image:
gdb vmlinux
- Connect to QEMU and set breakpoints:
(gdb) target remote :1234
(gdb) symbol-file vmlinux
(gdb) break start_kernel
(gdb) break panic
(gdb) continue
If QEMU is already waiting from -S, continue tells it to resume until a breakpoint is hit.
Useful GDB commands for kernel debugging
info registers: Show CPU register contents.bt: Print the current call stack.thread apply all bt: Backtrace all threads.info threads: List threads visible to GDB.disassemble /m start_kernel: Show both source and assembly forstart_kernel.list start_kernel: Display source around the function.print variable_name: Inspect kernel variables or structures.set $pc = start_kernel: Jump execution to a new instruction address.stepi: Single-step one machine instruction.continue: Resume execution until the next breakpoint.
Practical debugging examples
Early boot debugging
Use -S to stop execution immediately on startup and break on early boot functions:
(gdb) break start_kernel
(gdb) break setup_arch
(gdb) continue
This lets you inspect architecture setup, memory initialization, and early device probing.
Panic and oops debugging
To catch a panic or kernel oops, set breakpoints and disable auto-reboot:
(gdb) break panic
(gdb) break oops_end
(gdb) continue
When the panic triggers, inspect registers and the stack to determine the root cause.
Debugging a driver or module
For subsystem debugging, break on driver entry points or allocation functions:
(gdb) break my_driver_init
(gdb) break __kmalloc
(gdb) break do_IRQ
(gdb) continue
Notes on build artifacts
bzImage: Bootable compressed kernel image used by QEMU.vmlinux: Uncompressed ELF binary containing symbols and debug info.System.map: Symbol table mapping kernel addresses to symbol names..config: The kernel build configuration file.
References
- https://docs.kernel.org/process/index.html#dealing-with-bugs
- https://www.youtube.com/watch\?v=NDXYpR_m1CU
- https://www.youtube.com/watch\?v=l3h7F9za_pc
- https://www.youtube.com/watch\?v=7QiR3TOYajY
- https://people.cs.vt.edu/huaicheng/lkp-sp26/resources/debugging/#2-gdb–qemu-debugging
- https://lkp.pierreolivier.eu/slides/12_Qemu.pdf