A minimal, high-bandwidth binary kernel tracing system for bare-metal kernels running under QEMU. Zero dependencies. Zero kernel patches required.
┌─────────────────────────────────────────────────────────────────┐ │ Bare-metal kernel (x86_64 or AArch64) │ │ │ │ ktrace_core::transport::write_bytes(&record) │ │ │ │ │ │ x86_64 │ outb 0xe9 AArch64 │ HLT #0xF000 │ └──────────┼──────────────────────────────┼───────────────────────┘ │ │ ▼ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ QEMU -chardev file,id=ktrace,path=ktrace.bin │ │ ├── -device isa-debugcon,chardev=ktrace (x86_64) │ │ └── -semihosting-config ...,chardev=ktrace (AArch64) │ └───────────────────────────────────────┬─────────────────────────┘ │ ▼ ktrace.bin (KTRX v1 binary) │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ ktrace-decode.py ── text timeline · Perfetto JSON │ └─────────────────────────────────────────────────────────────────┘
Most kernel tracers (LTTng, Ftrace, perf) depend on a running Linux kernel. ktrace was designed for kernels that are replacing Linux — it works from bare metal, before any userspace exists, through whatever low-level channel QEMU provides.
One outb per byte on x86_64. One HLT trap per record on AArch64. No interrupts, no DMA.
The ktrace-core crate has zero dependencies and compiles for *-unknown-none targets out of the box.
32-byte fixed-size records with a packed bitfield header. A full CPU ring is 262 KB at 8192 entries.
Decode to a Perfetto JSON trace and open it in your browser at ui.perfetto.dev for a full timeline.
Both transports write to the same chardev — ktrace.bin is architecture-agnostic.
| Architecture | Mechanism | QEMU device | Cargo feature |
|---|---|---|---|
| x86_64 | ISA debugcon outb 0xe9 |
-device isa-debugcon,chardev=ktrace |
transport-x86-64 |
| AArch64 | ARM semihosting HLT #0xF000 |
-semihosting-config enable=on,...,chardev=ktrace |
transport-arm64 |
qemu-system-x86_64 \ -chardev file,id=ktrace,path=ktrace.bin \ -device isa-debugcon,chardev=ktrace,iobase=0xe9 \ -kernel kernel.elf
qemu-system-aarch64 \ -chardev file,id=ktrace,path=ktrace.bin \ -semihosting-config enable=on,target=native,chardev=ktrace \ -kernel kernel.elf
Magic KTRX · little-endian · stable.
A dump is a 64-byte header followed by N per-CPU ring buffers of 32-byte records.
DumpHeader (64 bytes)
| Offset | Size | Field | Notes |
|---|---|---|---|
| 0 | 4 | magic | Always KTRX |
| 4 | 4 | version | Currently 1 |
| 8 | 8 | tsc_freq_hz | TSC / CNTFRQ_EL0 in Hz |
| 16 | 4 | num_cpus | Per-CPU ring count |
| 20 | 4 | ring_size | Entries per ring (power of two) |
| 24 | 4 | entry_size | 32 bytes |
| 28 | 4 | flags | Reserved; 0 |
| 32 | 32 | _reserved | Zero-padded |
TraceRecord (32 bytes, align 32)
| Offset | Size | Field | Notes |
|---|---|---|---|
| 0 | 8 | tsc | Raw architecture counter; 0 = empty slot |
| 8 | 4 | header | [flags:8 | pid:11 | cpu:3 | event_type:10] |
| 12 | 20 | data[5] | Event-specific payload (5 × u32) |
Well-known event types
| # | Name | data[0] | data[1] |
|---|---|---|---|
| 0 | SYSCALL_ENTER | syscall nr | arg1 lo |
| 1 | SYSCALL_EXIT | syscall nr | ret lo |
| 5 | CTX_SWITCH | prev pid | next pid |
| 10 | PAGE_FAULT | addr lo | error code |
| 70 | WAITQ_SLEEP | queue id | — |
| 71 | WAITQ_WAKE | queue id | woken pid |
| 193 | NET_CONNECT | remote ip | port |
| 197–202 | NET_SEND/RECV/… | len | proto |
git clone https://github.com/levkropp/ktrace.git cd ktrace
In your bare-metal kernel's Cargo.toml:
# x86_64 kernel [dependencies] ktrace-core = { path = "tools/ktrace/ktrace-core", features = ["transport-x86-64"] } # AArch64 kernel ktrace-core = { path = "tools/ktrace/ktrace-core", features = ["transport-arm64"] }
use ktrace_core::{TraceRecord, EventType, transport}; let record = TraceRecord { tsc: read_tsc(), header: TraceRecord::pack_header(EventType::SYSCALL_ENTER, cpu, pid, 0), data: [syscall_nr, arg1 as u32, (arg1 >> 32) as u32, 0, 0], }; transport::write_bytes(unsafe { core::slice::from_raw_parts( &record as *const _ as *const u8, core::mem::size_of::<TraceRecord>(), )});
# Text timeline python3 decode/ktrace-decode.py ktrace.bin # Summary statistics python3 decode/ktrace-decode.py ktrace.bin --summary # Perfetto JSON → open at https://ui.perfetto.dev python3 decode/ktrace-decode.py ktrace.bin --perfetto trace.json