How to Use QEMUs “debugcon”-device (and Write Debug Information to the Terminal or a File)

Published by Philipp Schuster on

Update 2024-01-30: The same underlying mechanism was just merged in Cloud Hypervisor as well! It is exposed as --debug-console <target>. This article mostly applies to Cloud Hypervisor as well.

Problem: Getting Early Debug Output as Kernel Developer

QEMU is a VMM often used for low-level OS/kernel development and testing. When you develop your own kernel or firmware, things can get really hard and difficult to debug sometimes. Especially when something badly fails in early boot phases, you usually can’t write to the file system or the screen, as the relevant sub system is not initialized yet. So how can you see what happens inside the guest and find out why it fails?

Solution: The debugcon device

The QEMU debugcon device is a very easy solution to that problem! At least, when you are using QEMU with x86/x86_64, as this feature is limited to x86. “debugcon” can also be read as “debug console” or “debug connection”.

It is a part of the virtual hardware a guest sees, just like the other devices (LAPIC, PIT, PIC, serial device). Guests interact with it no different from other hardware. The debugcon device is only exposed via port I/O, not vie MMIO.

QEMU exposes the debugcon device at I/O port 0xe9 to a guest. It needs zero configuration from a guest, and you can directly start writing ASCII, UTF-8, or whatever data you like to it. On the VMM side, one can connect the I/O port to the terminal, a file, or a device, such as -debugcon stdio, -debugcon file:debugcon.txt, or -debugcon /dev/<device-file>.

It is similar to the -serial device, but needs no configuration and its throughput is not artificially limited by the (emulated) baud rate. But the serial device also accepts input (VMM to guest), whereas the debugcon device only outputs data.

Write to debugcon Device from a Guest

You need the outb instruction of x86. If you are not writing a program in pure assembly, you have to use inline assembly or use a library, such as the x86 crate.

Your code might look like this:

// The check for QEMU is not necessarily needed. On most or even all platforms,
// writes to unknown ports should be ignored.
if runs_inside_qemu() {
    unsafe {
        x86::io::outb(0xe9, b'H');
        x86::io::outb(0xe9, b'e');
        x86::io::outb(0xe9, b'l');
        x86::io::outb(0xe9, b'l');
        x86::io::outb(0xe9, b'o');
        x86::io::outb(0xe9, b'\n');
    }
}

In this blog post, you can find a more comprehensive version of how to write to the device from code.

Example QEMU config

The following snippet shows how a working QEMU configuration. ./kernel is a bootable kernel that prints to the debugcon device:

$ qemu-system-x86_64 -kernel ./kernel -debugcon stdio

Trivia

As many things in low-level space, it is poorly documented IMHO, but it is really handy to debug things as it can be used in the earliest phases of the boot already.

From my short research, the debug console originates from Bochs, a x86 emulator, but QEMU has it for some time now. In my opinion, QEMU is not documented very well and especially this feature is hard to use from the official documentation. After some research on GitHub, I found out, how others solved it. Quite easy in the end, once you are faimilar with it!


Philipp Schuster

Hi, I'm Philipp and interested in Computer Science. I especially like low level development, making ugly things nice, and de-mystify "low level magic".

0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *