This repository is an experimental bridge between a RISC-V bare-metal guest, QEMU, and the Tracy profiler. The guest emits non-trapping RISC-V HINT instructions. The modified QEMU decodes those hints and publishes events, ranges, interrupt activity, and CPU-state plots to Tracy using the guest's virtual clock.
The project was built to inspect the VIMIX hobby OS, but the transport is not tied to its kernel architecture. Another RISC-V firmware, kernel, or bare-metal program can use it by providing the shared protocol definitions and emitting the same HINT encodings.
Note: This tools was AI coded, use at your own risk.
qemu/is the modified QEMU submodule. Itsfeatqtracybranch contains the RISC-V decoder hooks and Tracy client integration.tracy/is the modified Tracy submodule. Itsfeatqtracybranch adds APIs for events with caller-supplied timestamps.build.shconfigures and builds QEMU plus Tracy's profiler, capture tool, and CSV exporter.docs/technical-details.mddescribes the instruction protocol, time model, QEMU and Tracy changes, range handling, and current limitations.
This is a local experiment build by AI and is not intended as an upstream QEMU or Tracy change.
Use a Linux development environment capable of building QEMU and Tracy. At a minimum, the build expects:
- a C and C++ toolchain,
make, andpkg-config; - Python and QEMU's normal Meson/Ninja build dependencies;
- CMake 3.25 or newer;
- the development libraries required by the Tracy GUI on the host; and
- Git with submodule support.
The configured QEMU target is riscv64-softmmu. QEMU's virt machine, TCG,
virtio block support, and the bundled generic RISC-V OpenSBI firmware are part
of that build, so this repository does not build OpenSBI separately.
Initialize both modified upstream projects after cloning the superproject:
git submodule update --init --recursiveThe build needs a guest/QEMU protocol header named hint_defs.h. Supply its
absolute path as the first argument:
./build.sh "$(pwd)/path/to/hint_defs.h"For the original VIMIX layout, invoking ./build.sh without an argument uses:
../vimixos/kernel/include/kernel/hint_defs.h
Set JOBS to override the detected parallelism:
JOBS=8 ./build.sh "$(pwd)/path/to/hint_defs.h"The script configures missing build directories and then rebuilds all four targets. It does not build the instrumented guest. Outputs are kept outside the submodules:
build/qtracy/qemu-system-riscv64
build/qtracy-profiler/tracy-profiler
build/qtracy-capture/tracy-capture
build/qtracy-csvexport/tracy-csvexport
The selected header path is stored in build/qtracy/qtracy-hint-defs-path.
Changing the path reconfigures QEMU; changing the header contents causes
system/qtracy.c to be recompiled. Use an absolute path because the value is
passed to the compiler as QTRACY_HINT_DEFS_HEADER.
The header is the protocol definition shared by the guest and QEMU. It defines
the 17-bit packet layout, packet and control enums, the bounded data payload,
and X-macro tables for named events and range classes. It must also define the
three range symbols that currently receive special hart-local treatment:
QTRACY_RANGE_PROCESS, QTRACY_RANGE_SCHEDULER, and QTRACY_RANGE_WFI.
Keep the header usable by both environments: avoid guest-only includes and
types unless they are conditional. Numeric IDs must be nonzero, no greater than
QTRACY_PACKET_ID_MAX (currently 16383), and unique within the relevant event
or range namespace. QEMU consumes these definition tables at compile time, so
rebuild it after every change.
The complete required interface and a description of each definition table are in Custom hint definitions.
Start the Tracy GUI, then launch the guest with the modified QEMU using the usual arguments for that guest image:
build/qtracy-profiler/tracy-profiler
build/qtracy/qemu-system-riscv64 -machine virt [guest options]The QEMU client is built in Tracy's on-demand mode and appears as a profiling target while it runs. Automatic host sampling, context-switch tracing, callstacks, crash handling, frame images, system tracing, and vsync capture are disabled so the capture primarily contains guest-originated data.
Short-lived guests can finish before Tracy completes its connection handshake.
Start QEMU with -S, connect the profiler, and resume the virtual CPUs from the
QEMU monitor in that case (or from an attached gdb).
A headless capture can be recorded and exported with:
build/qtracy-capture/tracy-capture -o guest.tracy -s 10
build/qtracy-csvexport/tracy-csvexport --messages guest.tracyUse --unwrap for individual zones and combine it with --plot to export plot
data. Run either tool with --help for all options.
Guest instrumentation has two pieces:
- Share a compatible
hint_defs.hwith this build. - Emit
SLTI x0, rs1, imm12HINT words with the packet split across the instruction's 12-bit immediate and five-bitrs1field. Data-bearing packets place their XLEN-sized value or guest physical address ina0.
These instructions behave as no-ops on an implementation that does not
recognize this private protocol because their destination is x0. The guest
can therefore compile instrumentation into an image without requiring every
RISC-V implementation to support qtracy. The current QEMU integration is in
the TCG decoder; it does not add KVM support.
See Guest packet transport before implementing the guest-side macros, especially the physical-address, range-nesting, and execution-context rules.
