QEMU is a hypervisor often used for low-level OS/kernel development and testing. Especially when you write your own OS/kernel, things can get really hard and difficult to debug. Furthermore, there are not many debug capabilities in the sense, that you can’t write to files or screen in early phases of the boot process due to the lack of an initialized environment with drivers etc. For this case, QEMU has a featured called debugcon. It is barely documented but really handy to debug things and can be used in the earliest phases of the boot already.
What is “debugcon”
The debug connection allows you to write to the x86 I/O-port 0xe9. Practically this means that you can transfer for example ASCII, UTF-8, UTF-16, or UTF-32 via this interface. On the QEMU side, one can connect the I/O port to either a file or a device, like /dev/stdout. When QEMU runs and the binary writes data to the file, you can open it afterwards with an editor of text viewer of your choice.
Due to my research, this feature comes originally 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.
How to Activate it
To connect -debugcon
with stdout
(the terminal that started your QEMU process), pass -debugcon /dev/stdout
to QEMU. To redirect the data to a file, pass -debugcon file:qemu/debugcon.txt
to it.
You can use any absolute or relative path behind the :
. The file will be created automatically (if QEMU has permission to do that in the given directory).
How to Use in Code
You need the outb instruction from x86. If you are not writing a program in pure assembly, I recommend inline assembly in C or Rust. For Rust, there is a nice abstraction, which can be found in the x86-crate.
Code might look like this:
// Only do this if the app runs in QEMU (or Bochs), otherwise bad things might happen).
// Or to be more specific, only do this, if I/O port 0xe9 is valid.
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');
}
}
Example QEMU config
The following snippet content from a sh
-file, that runs QEMU with the debugcon
feature. It’s taken from one of my projects.
QEMU_ARGS=(
# Disable default devices
# QEMU by default enables a ton of devices which slow down boot.
"-nodefaults"
# Use a standard VGA for graphics
"-vga"
"std"
# Use a modern machine, with acceleration if possible.
"-machine"
# "q35" # also works, but slower
# Interesting to see how this changes CPU-ID
# Without KVM the Hypervisor is QEMU, else its KVM
"q35,accel=kvm:tcg"
# Allocate some memory
"-m"
"128M"
# Set up OVMF
"-drive"
"if=pflash,format=raw,readonly,file=${OVMF_FW_PATH}"
"-drive"
"if=pflash,format=raw,file=${OVMF_VARS_PATH}"
# Mount a local directory as a FAT partition
"-drive"
"format=raw,file=fat:rw:${QEMU_VOLUME_DIR}"
# Enable serial
#
# Connect the serial port to the host. OVMF is kind enough to connect
# the UEFI stdout and stdin to that port too.
"-serial"
"stdio"
# https://qemu-project.gitlab.io/qemu/system/invocation.html
# using this, the program can write to X86 I/O port 0xe9 and talk
# to qemu => debug output
"-debugcon"
# or "/dev/stdout" => it appears in terminal window
# this is poorly documented! I found out by coincidence, that I can use a file like this
"file:qemu/debugcon.txt"
# Setup monitor
"-monitor"
"vc:1024x768"
)
echo "Executing: qemu-system-x86_64 " "${QEMU_ARGS[@]}"
qemu-system-x86_64 "${QEMU_ARGS[@]}