Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Surface Language

Reference for the bpftrace surface as whistler bpftrace accepts it. Existing bpftrace scripts mostly run unchanged.

Probes

A probe is one or more attach specs, an optional filter predicate, and a body block:

PROBE-TYPE:TARGET [, PROBE-TYPE:TARGET ...] [/predicate/] { body }
ProbeTargetNotes
BEGINFires once at attach.
ENDFires once at exit.
kprobe:FUNCkernel functionWildcards allowed: kprobe:tcp_*.
kretprobe:FUNCkernel functionSame.
kfunc:FUNCkernel functionBTF-trampoline fentry — cheaper than kprobe.
kretfunc:FUNCkernel functionBTF-trampoline fexit.
uprobe:PATH:SYMuser function
uretprobe:PATH:SYMuser function
tracepoint:CAT:EVENTtracepointargs->FIELD reads from the format file.
profile:hz:NPeriodic, per-CPU.
interval:s:N / :ms:N / :us:N / :hz:NPeriodic, single-CPU.

Several specs can share one body:

kprobe:vfs_read,kprobe:vfs_write { @[probe] = count(); }

Wildcard targets attach to every matching kernel function:

kprobe:tcp_* { @ = count(); }

On kernels ≥ 5.18 these will eventually use BPF_TRACE_KPROBE_MULTI; see issue #39. Today they fall back to one perf_event_open per match, which is slower but correct.

Maps and aggregations

Map references start with @. The unnamed @ is fine; named maps use @foo. Keys go in brackets:

@              = …    # global scalar
@foo           = …    # named scalar
@[pid]         = …    # keyed
@[pid, ustack] = …    # composite key

The right-hand side is either a value or an aggregation:

RHSStorageOutput
scalar (5, nsecs)u64bare number
count()u64 counterbare number
sum(x)per-CPU u64sum across CPUs
avg(x)per-CPU (count, sum)computed average
min(x) / max(x)per-CPU (count, value)min / max across CPUs
stats(x)per-CPU (count, sum)count N, average A, total T
hist(x)per-CPU log2 bucketsASCII histogram
lhist(x, lo, hi, step)per-CPU linear bucketslinear histogram with [a, b) labels

String-typed composite-key slots are 16 bytes for comm and whatever size you ask for with str(ptr [, n]) / kstr(ptr [, n]) (default 64).

Built-in variables

BuiltinReturns
pidu32
tidu32
uidu32
gidu32
nsecsu64; CLOCK_BOOTTIME, matching bpftrace
cpuu32
cgroupu64
comm16-byte char[]
retvalu64; kretprobe / kretfunc / uretprobe only
argstracepoint args struct, used with ->field
arg0..arg9nth function arg (kprobe / uprobe / kfunc)
curtaskstruct task_struct *, used with ->field
probestring; the current probe's section name
funcstring; the current probed function name
kstack / ustackstack-id, formatted at print time
$namelocal variable ($name = … to assign)

Functions and async actions

Output

FormEffect
printf(FMT, args…)C-style format. Supports %d / %u / %x / %X / %p / %c / %s / %lld / %%, the - (left-align) and 0 (zero-pad) flags, and decimal width.
print(@m [, top [, div]])Dump map @m from userspace. With top, only the largest N entries are shown. With div, integer values are divided by div before rendering — matches bpftrace's print(@m, 10, 1000) ns → µs idiom.
time()Print the current time.
exit()Raise the exit flag; the runtime stops at the next tick.
clear(@m)Empty map @m.
zero(@m)No-op for now.
delete(@m[k])Remove a key.

Memory and addresses

FormReturnsHelper
str(ptr [, n])string slot (default 64 B)bpf_probe_read_user_str
kstr(ptr [, n])string slotbpf_probe_read_kernel_str
ksym(addr)resolved nameuserspace /proc/kallsyms
usym(addr)resolved nameuserspace symbolizer
ntop([af,] addr)IPv4 / IPv6 stringuserspace format
reg("ip"|"sp"|"di"|…)u64 registerdirect pt_regs read
syscall_name(id)syscall name stringbaked arch table (x86-64, arm64)
signal_name(N)signal name string (SIGTERM, SIGKILL, …)POSIX 1-31 baked in; SIG<N> fallback

CLI parameters

FormReturnsSource
getopt(NAME, DEFAULT)DEFAULT's typewhistler bpftrace script.bt -- --NAME[=VALUE]

Bool defaults: bare --NAME → 1, --NAME=true/--NAME=1 → 1, --NAME=false/--NAME=0 → 0. Int defaults: --NAME=N parses as an integer. Missing flag → DEFAULT.

Script configuration

A top-level config = { KEY=VALUE; KEY=VALUE } block (before any probe) sets runtime knobs. BPFTRACE_FOO, FOO, and foo are equivalent. Currently honored:

KeyEffect
print_maps_on_exitWhen 0 / false, suppresses the session-teardown auto-dump of every map.
max_strlenDefault buffer size for str(ptr) without an explicit length. Default 64.
max_map_keysDefault max-entries for hash maps. Default 1024.
on_stack_limitstruct-alloc requests larger than this spill to the per-CPU scratch map instead of the BPF stack. Default 32.
missing_probeswarn (default) logs failed attaches and continues; ignore is silent; error aborts the session.
str_trunc_trailerAppended to str() output when the buffer filled with no terminating NUL. Default empty.
stack_modebpftrace (default — indented symbol-only), perf (HEX_ADDR SYMBOL), or raw (hex addresses only).

Aggregations

count, sum, avg, min, max, stats, hist, lhist. See the Maps and aggregations table above. All of these must appear on the right-hand side of @m = ….

Operators

C-style: + - * / % == != < > <= >= && || ! & | ^ << >> ~. Compound assigns: += -= *= /= %= &= |= ^=. Pre/post increment and decrement: ++ --.

Casts and struct access

((struct task_struct *)curtask)->pid
((struct sock_common *)arg0)->skc_family

A cast tags the inner expression with a struct type; the subsequent ->FIELD is resolved against the kernel's BTF for that struct. Scalar fields up to 8 bytes are supported. Nested pointer chasing (curtask->mm->mmap_lock) is not yet wired up — see the Limits table.

Control flow

if (cond) { … } else { … }

while ($i < 10) { $i += 1; }

cond ? a : b

kprobe:foo /pid == 1234/ { … }     # filter predicate

while lowers to a bounded dotimes (currently 64 iterations) with the body short-circuiting once the condition goes false. The BPF verifier requires a static upper bound on every loop, so this is unavoidable without bpf_loop() helper plumbing.

Symbolic constants

Identifiers like AF_INET, O_RDONLY, IPPROTO_TCP resolve at codegen from two sources, in order:

  1. Kernel BTF BTF_KIND_ENUM / ENUM64 members. Free — Whistler harvests them once per session.
  2. A small curated table of POSIX/Linux #define constants BTF doesn't carry (socket families, mode bits, open flags).

The curated entries override BTF on conflict so a kernel renaming an enum can't silently change script semantics. No #include, no C parser.

User-defined functions

fn dub($x) { return $x * 2; }

kprobe:vfs_read { @ = dub(arg2); }

Functions are inlined at the AST-to-IR boundary: each call site substitutes the body, with parameters textually replaced by the argument expressions. A few caveats follow from that approach:

  • Side-effecting arguments evaluate at every reference. Don't pass nsecs into a parameter you read twice if you need it stable.
  • Recursion isn't blocked; the inliner will loop forever.
  • No type or return-type annotations — every value is u64.

Limits

The gaps versus upstream bpftrace, in case you hit one:

FeatureStatus
for ($k : @m) { … }Not wired up; needs bpf_for_each_map_elem.
break / continueNot wired up.
Chained pointer struct access (curtask->mm->mmap_lock)Single level only.
raw_tracepoint, software, hardware, watchpointNot wired up.
system() async actionNot wired up.
C++ symbol demanglingIntentionally skipped.
#include of C headersNot planned. Use symbolic constants instead.