KTRX v1 · no_std · bare-metal

ktrace

A minimal, high-bandwidth binary kernel tracing system for bare-metal kernels running under QEMU. Zero dependencies. Zero kernel patches required.

Get Started View on GitHub
┌─────────────────────────────────────────────────────────────────┐
  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         
└─────────────────────────────────────────────────────────────────┘
Motivation

Built for kernels that replace Linux

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.

Zero-overhead transport

One outb per byte on x86_64. One HLT trap per record on AArch64. No interrupts, no DMA.

🔩

no_std & no_alloc

The ktrace-core crate has zero dependencies and compiles for *-unknown-none targets out of the box.

🏗

Compact wire format

32-byte fixed-size records with a packed bitfield header. A full CPU ring is 262 KB at 8192 entries.

🔭

Perfetto export

Decode to a Perfetto JSON trace and open it in your browser at ui.perfetto.dev for a full timeline.

Architecture Support

QEMU transports

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
shell — x86_64
qemu-system-x86_64 \
  -chardev file,id=ktrace,path=ktrace.bin \
  -device isa-debugcon,chardev=ktrace,iobase=0xe9 \
  -kernel kernel.elf
shell — AArch64
qemu-system-aarch64 \
  -chardev file,id=ktrace,path=ktrace.bin \
  -semihosting-config enable=on,target=native,chardev=ktrace \
  -kernel kernel.elf
Protocol

KTRX v1 wire format

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)

OffsetSizeFieldNotes
04magicAlways KTRX
44versionCurrently 1
88tsc_freq_hzTSC / CNTFRQ_EL0 in Hz
164num_cpusPer-CPU ring count
204ring_sizeEntries per ring (power of two)
244entry_size32 bytes
284flagsReserved; 0
3232_reservedZero-padded

TraceRecord (32 bytes, align 32)

OffsetSizeFieldNotes
08tscRaw architecture counter; 0 = empty slot
84header[flags:8 | pid:11 | cpu:3 | event_type:10]
1220data[5]Event-specific payload (5 × u32)

Well-known event types

#Namedata[0]data[1]
0SYSCALL_ENTERsyscall nrarg1 lo
1SYSCALL_EXITsyscall nrret lo
5CTX_SWITCHprev pidnext pid
10PAGE_FAULTaddr loerror code
70WAITQ_SLEEPqueue id
71WAITQ_WAKEqueue idwoken pid
193NET_CONNECTremote ipport
197–202NET_SEND/RECV/…lenproto
Getting Started

Install

1

Clone the repository

shell
git clone https://github.com/levkropp/ktrace.git
cd ktrace
2

Add ktrace-core to your kernel

In your bare-metal kernel's Cargo.toml:

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"] }
3

Emit trace events from your kernel

rust
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>(),
)});
4

Decode the trace on your host

shell
# 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
✓ CI passing no_std x86_64 · AArch64 MIT OR Apache-2.0 OR BSD-2-Clause KTRX v1