Timeline

2025-11-23

  1. init

Environment

1
2
3
4
5
6
wget https://download.qemu.org/qemu-10.1.2.tar.xz
tar xvJf qemu-10.1.2.tar.xz
cd qemu-10.1.2
mkdir -p output
./configure --prefix=$PWD/output --target-list=aarch64-softmmu,riscv64-softmmu --enable-debug
bear -- make -j$(nproc)

Create .clangd:

1
2
3
CompileFlags:
Add: -Wno-unknown-warning-option
Remove: [-m*, -f*]

Source-Level Debugging

Unlike debugging a kernel (which requires starting first, then attaching via gdb), QEMU can be debugged directly since it runs natively on the host. (Make sure to compile with --enable-debug.)

gdb

Direct gdb debugging:

1
2
$ gdb --args ./build/riscv64-softmmu/qemu-system-riscv64 \
-M virt -kernel Image -nographic

VSCode

.vscode/launch.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
"version": "0.2.0",
"configurations": [
{
"name": "qemu-system-riscv",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/qemu-system-riscv64",
"args": [
"-device", "edu,id=edu1"
],
"stopAtEntry": true,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set Disassembly Flavor to Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]
},
]
}

Remote Debugging

QEMU has a built-in gdbserver for controlling the guest processor. Any gdb client can connect.

1
$QEMU $QEMU_ARGS -s -S
  • -s: Start gdbstub on port 1234.
  • -S: Halt QEMU at the guest’s first instruction, waiting for a gdb client.

To specify a custom port:

1
$QEMU $QEMU_ARGS -gdb tcp::<your-port> -S

Connect with the appropriate architecture’s gdb:

1
$ARCH-gdb $BINARY -ex "target remote localhost:1234"

Log Debugging

QEMU has a flexible logging system for observing guest state (instruction flow, interrupts, exceptions, syscalls).

1
$QEMU $QEMU_ARGS -d <log-type,...> -D <log-file-name>
  • -d: Specify log types (comma-separated).
  • -D: Specify output file path (default: stdout).

View supported log types:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$QEMU -d ?
Log items (comma separated):
out_asm show generated host assembly code for each compiled TB
in_asm show target assembly code for each compiled TB
op show micro ops for each compiled TB
op_opt show micro ops after optimization
op_ind show micro ops before indirect lowering
int show interrupts/exceptions in short format
exec show trace before each executed TB (lots of logs)
cpu show CPU registers before entering a TB (lots of logs)
fpu include FPU registers in the 'cpu' logging
mmu log MMU-related activities
guest_errors log when the guest OS does something invalid
strace log every user-mode syscall, its input, and its result
...

Common combinations:

  • Observe TCG instruction translation:
1
$QEMU $QEMU_ARGS -d in_asm,op,out_asm -D tcg.log
  • Observe CPU state (registers, interrupts/exceptions):
1
$QEMU $QEMU_ARGS -d exec,cpu,int -D cpu.log
  • Precise instruction trace (one instruction per TB):
1
$QEMU $QEMU_ARGS --accel tcg,one-insn-per-tb=on -d exec,cpu,int -D cpu.log

Trace Events

Reference:

QEMU has a powerful tracing tool for tracking internal function execution and performance tuning.

Quick Start

Trace memory region access events:

1
2
3
4
$ qemu-system-riscv64 -M virt --trace "memory_region_ops_*"
...
719585@1608130130.441188:memory_region_ops_read cpu 0 mr 0x562fdfbb3820 addr 0x3cc value 0x67 size 1
719585@1608130130.441190:memory_region_ops_write cpu 0 mr 0x562fdfbd2f00 addr 0x3d4 value 0x70e size 2

Multiple trace events and file output:

1
2
echo "memory_region_ops_*" >/tmp/events
qemu-system-riscv64 -M --trace events=/tmp/events,file=/tmp/event.log ...

Dynamic enabling via QEMU monitor:

1
2
3
$ qemu-system-riscv64 -M virt -monitor stdio -S -display none
(qemu) trace-event memory_region_ops_read on
(qemu) c

Use info trace-events to list supported events.

Adding New Trace Events

Two steps:

  • Declare the trace-event in the corresponding directory’s trace-events file.
  • Add the event function call in the target source code.

Example format:

1
2
qemu_vmalloc(size_t size, void *ptr) "size %zu ptr %p"
qemu_vfree(void *ptr) "ptr %p"

Each event declaration starts with the event name, followed by parameters, and a format string for pretty-printing.

Usage in source:

1
2
3
4
5
6
7
8
9
#include "trace.h"
void *qemu_vmalloc(size_t size)
{
void *ptr;
// ...
ptr = qemu_memalign(align, size);
trace_qemu_vmalloc(size, ptr);
return ptr;
}

Trace Backends

QEMU tracing uses a frontend/backend separation design, supporting multiple backends: log, simple, ftrace, dtrace.

Enable specific backends:

1
./configure --enable-trace-backends=simple,dtrace

The simple backend writes binary trace logs to a file via a separate thread, with lower overhead than the log backend.

Analyzing Trace Files

Format with simpletrace.py:

1
./scripts/simpletrace.py <trace-events-all> <trace-log>

Ensure the trace-events-all file matches the one generated when QEMU was built.

Reference: