asmspy metrics and observability¶
Every number, token, and field asmspy reports — what it measures,
what values it can take, and which architectures can observe it at all.
Use this page when you are consuming asmspy output rather than reading it: piping
--json to jq, parsing an .asmtrace recording, wiring a dashboard, or
deciding whether a metric you need exists on the host you have.
Read the asmspy guide first for what each view is. This page is the field-by-field reference behind it.
The three output channels¶
Every headless view writes the same measurements through up to three channels,
and the field names differ between them — the human line is for a terminal,
--json is a single object shaped for jq, and the .asmtrace NDJSON is the
recording contract shared with the desktop GUI and the corpus recorder.
Channel |
How |
Shape |
Numbers rendered as |
|---|---|---|---|
Human text |
default |
one line per event / a rendered table |
decimal counts, |
|
|
one JSON object on stdout |
addresses as |
|
|
NDJSON: a header line, event lines, an |
addresses as decimal u64 numbers |
Concretely: a --graph --json node carries "addr":"0x401136" while the same
node in a --record file carries "addr":4198710. A --sample --json edge names
its endpoints from/from_name; the recorded survey event names them
from_addr/from. Neither is a bug — they are two contracts — but a consumer
must pick one and not assume the other.
--json is available on --info, --log, --stream, --graph, --tree,
--procs, --sample, --watch and --dataflow. --dot (Graphviz) is
available on --graph, --tree and --procs. --record=<f> works on
every headless view.
Fidelity metrics — the ones that ride every view¶
These are not per-view measurements; they are asmspy’s statement about how much
to trust the measurements. They appear in the .asmtrace header and end
footer (and, for the statistical sampler, in --json too).
Metric |
Values |
Meaning |
|---|---|---|
|
|
Which engine produced the file. |
|
|
|
|
|
The tier word for the same fact. |
|
|
Present when the capture was scoped to a region ( |
|
|
Present when the run skipped: the positive skip code plus the measured reason string. A skipped run still writes a closed, valid file. |
|
|
The recording host’s architecture. asmspy traces same-machine processes, so this is also the tracee’s. |
|
|
Routine identity — a SHA-256 of the region’s live bytes. Emitted by |
|
u64 |
Event lines written before the footer. |
|
|
Something was dropped: a ring overflowed, a line did not fit, or emission was paused. |
|
u64 |
Samples the kernel dropped ( |
|
|
The kernel throttled the sample rate ( |
|
u64 |
Steps the value trace saw, counting past the ring cap — the M in “N of M”. |
A file with no end line is a torn recording. There is no atexit rescue;
a reader must say “torn” rather than present a prefix as complete.
Skip codes¶
A positive return is a fact about the target or the host; a negative one is a failure of the tracer. They are never mixed.
Code |
Name |
Meaning |
Exit status |
|---|---|---|---|
1 |
|
The region was watched and did not execute in the window (not “never runs”). |
1 |
2 |
|
AMD IBS-Op unavailable, or perf refused the open. |
0 ( |
3 |
|
The value producer is not built here (off Linux x86-64, or no Capstone). |
0 ( |
4 |
|
No hardware data watchpoint: wrong architecture, or arming refused. |
0 ( |
5 |
|
The tracee is a 32-bit (i386) process — refused before any attach. |
1 |
The # SKIP line carries the measured reason, not a list of suspects — for a
refused watchpoint it distinguishes “regset absent” from “zero slots” from “slots
present but unreservable”, because those three send you to three different places.
Per-view metrics¶
--list / the TUI process picker¶
Pure /proc; no attach, no ptrace. Identical on every architecture asmspy builds
for.
Metric |
Column |
Values |
Meaning |
|---|---|---|---|
pid |
|
int |
Process id. |
string density |
|
0–1000 (per-mille) |
Alphanumeric byte fraction of a sample of the process’s readable, non-code mappings. Ranks “string-rich” processes first. |
cpu |
|
u64 jiffies |
CPU time (utime + stime) consumed during a 150 ms sampling window — a delta, not a total. |
user |
|
string |
Owner username, or the numeric uid when unresolvable. |
attachable |
|
|
Same euid as you (or you are root). The TUI marks non-attachable rows |
command |
|
string |
|
runtime badge |
(TUI only) |
|
Cheap badge from argv0/comm plus the presence of a perf-map. The headless |
--syms¶
Metric |
Values |
Meaning |
|---|---|---|
addr |
u64, printed 12-hex |
Runtime address in the target (module load bias already applied). |
size |
u64 bytes |
Symbol extent; |
name |
string |
|
module |
string |
Basename of the backing ELF, or |
Reverse lookup is exact-extent only: an address in the gap between two functions resolves to nothing rather than to a confidently wrong neighbour.
Process details (TUI mode 8)¶
A no-attach fingerprint from /proc plus the mapped ELF — it works where ptrace
would be denied. Every field is architecture-neutral.
Metric |
Values |
|---|---|
|
|
|
what identified it, e.g. |
|
bool — |
|
int, from |
|
KiB, from |
|
pid — |
|
|
|
|
|
bool / bool / loader basename |
|
up to 6 distinct per-task |
|
up to 10 notable mapped library basenames, with a “more were dropped” bit |
--log — syscalls¶
Metric |
Channel |
Values |
Meaning |
|---|---|---|---|
syscall name |
all |
string |
From the compiling host’s own |
arity |
human |
exact for shaped calls |
49 syscalls have a declared argument shape; an unmodelled call prints its first three raw words followed by |
return value |
human |
signed long |
|
|
|
string |
The payload-free line: same name, fds, flag words, counts and return, with content replaced by |
|
|
string, ≤ 200 bytes decoded |
The decoded string the call carried. Present only when there was one — which is what lets a reader redact content without losing the call. |
|
human |
int |
Present once more than one thread is followed. |
The 25 argument classes that decode precisely: raw word, signed int, size, fd
(resolved to its endpoint), dirfd (AT_FDCWD), path, open flags, octal mode,
mmap prot, mmap flags, clone flags, signal number, sigset bitmask,
rt_sigprocmask how, iovec contents, timespec, lseek whence, socket family,
sockaddr in/out contents, ioctl request, fcntl command, futex op,
stat result contents, statx result contents.
An fd resolves through /proc/<pid>/fd to a path, or through the target’s own
network namespace to TCP 127.0.0.1:40730->127.0.0.1:54681 /
TCP LISTEN 127.0.0.1:8080 / unix:/tmp/foo.sock. A pipe stays pipe:[inode].
--trace — region samples¶
Metric |
Channel |
Values |
Meaning |
|---|---|---|---|
|
human |
1-based |
Which captured invocation this is. |
|
human |
signed long |
The region’s return value ( |
insns recorded |
human |
u64 |
Distinct instruction offsets recorded. |
insns executed |
human |
u64 |
Total instructions retired in the region — the loop-inclusive count. |
blocks |
human, |
u64 offsets |
Distinct basic-block entry offsets. |
|
|
u64 |
Totals the engine counted, including anything the arrays could not hold. |
per-instruction heat |
human |
u32 |
How many times that offset executed. Capped at 512 displayed offsets. |
per-callee count |
human |
u32 |
Calls made to that callee, ranked most-called first. Capped at 256 edges. |
call site |
human |
u64 |
Lowest observed call-site offset for that callee. |
|
both |
bool |
The trace or the call record overflowed. |
|
|
|
Offsets are relative to the region base. Mandatory — a reader may never default it. |
|
|
u64 / string |
One executed instruction. |
--stream — live instruction stream¶
Text only: function+0xoff [module] <disasm>, prefixed [tid] once more than
one thread is followed. The .asmtrace stream event carries a single text
field — the engine hands the front end a formatted line and nothing else, so the
recording states that faithfully rather than inventing fields it never measured.
--graph — whole-process call graph¶
Per node:
Metric |
|
|
Values |
|---|---|---|---|
entry address |
|
|
u64 |
name |
|
|
resolved symbol, demangled, or |
module |
|
|
basename, |
class |
|
|
|
times called |
|
|
u64 |
calls made |
|
|
u64 |
distinct callees |
|
|
u32 |
Per edge: {caller, callee, count} in --json (addresses as "0x…"),
{from, to, count} in the recording (decimal). Edges are keyed by entry
address, not node index, so a consumer may re-sort or filter the node array
without invalidating them.
The human row renders as [int]/[EXT]/[JIT]/[?] plus
inv=… calls=… fanout=… [module]. --sort takes invocations or fanout
(functions-called is a synonym).
--tree — live call tree¶
Metric |
|
|
Values |
|---|---|---|---|
emission order |
|
(line order) |
0-based |
thread |
|
|
int |
call depth |
|
|
int, 0 = top |
callee entry |
|
|
u64 |
name / module |
|
|
string |
depth is the height of a real per-thread return-address stack keyed on the
stack pointer, not a push/pop counter — so a ret, a longjmp over ten frames
and a C++ unwind all pop correctly, and a signal handler (which runs below the
interrupted frame) leaves the frames beneath it intact. Under --focus=<sym> the
depth is re-based so the focused function sits at 0.
Filters (--depth, --focus, --module) bound what is printed, never what
is tracked — so depths stay true and n counts surviving lines. The --dot
export additionally aggregates a per-node entered= count and per-edge call
counts.
--procs — process/thread topology¶
Metric |
Values |
Meaning |
|---|---|---|
|
int |
Task, thread-group (process), parent process. |
|
bool |
|
|
string |
|
|
string |
Process exe basename (leader tasks only). |
|
u64 |
The count whose meaning switches — see |
|
|
What |
--count=syscalls (default) runs near full speed and is safe on any target;
--count=calls single-steps, so the whole tree crawls.
The exports carry the flat task list, not a rendered tree: the forest is
derivable from tgid + ppid, and exporting box-drawing glyphs would throw
information away.
--sample — statistical hot edges (AMD IBS-Op)¶
Per edge:
Metric |
|
|
Values |
|---|---|---|---|
source address |
|
|
u64 |
target address |
|
|
u64 |
source name |
|
|
|
target name |
|
|
same |
samples on this edge |
|
|
u64 |
mispredicted |
|
|
u32, ≤ |
retired a return |
|
|
u32, ≤ |
Provenance, emitted once per window:
Metric |
Values |
Meaning |
|---|---|---|
|
u64 |
Total IBS-Op samples drained. |
|
u64 |
Of those, retired taken branches — the ones that became edges. |
|
u64 |
Samples the kernel dropped. |
|
bool |
The kernel throttled the sampling rate: lengthen |
|
|
Which sampler ran. |
Derived in the human view: [misp N%] is mispred * 100 / count; [ret] marks
is_return. Sorting is by count (default) or mispred (TUI Tab).
This view is always exact:false. It proves an edge was taken; absence
proves nothing, and cold code may be missing from a short window.
--watch — hardware data watchpoint¶
Metric |
|
|
Values |
|---|---|---|---|
hit index |
|
|
1-based |
thread |
|
|
int |
program counter |
|
|
u64 — see the per-arch note below |
watched address |
(top-level |
|
u64 |
direction |
|
|
Tri-state. |
value |
|
|
The watched bytes read back after the access, host-endian. |
width |
|
|
1, 2, 4 or 8 |
location |
|
same |
Resolved through the ELF symtab then the JIT perf-map. |
session scope |
top-level |
header |
What was armed. |
Between hits the target runs at native speed — no single-stepping, no code patching.
--dataflow — scoped value trace + def-use¶
Top-level (--json):
Metric |
Values |
Meaning |
|---|---|---|
|
string / |
The captured region. |
|
signed long |
Return value of the invocation. |
|
size |
In-region instructions captured. |
|
size |
Operand records across all steps. |
|
bool |
|
Per step (trace[] in --json, df_step in a recording):
Metric |
Values |
Meaning |
|---|---|---|
|
0-based |
Step ordinal within this invocation. |
|
u64 |
Offset from the region base — always region-relative, and it carries no |
|
u64, optional |
The absolute base |
|
u64, optional |
The |
|
string |
Present only when Capstone is linked. |
|
see below |
The operand records for this step. |
Per operand:
Metric |
|
|
Values |
|---|---|---|---|
direction |
|
|
Read set vs write set. |
location class |
|
|
Register, absolute memory, or region-relative memory. |
register id |
|
|
Capstone register id — an architecture-specific namespace. x86-64 and AArch64 ids overlap numerically and mean different registers; resolve against the recording’s |
addressing terms |
|
|
Memory operands only; omitted for a register operand. |
width |
|
|
bytes |
value |
|
|
Omitted when not captured. |
wide |
|
|
|
Def-use (L1): defuse[] in --json is {from, to} step pairs; the recording’s
df_edge additionally carries loc — the consumer’s read record, in the same
operand shape — so an edge names which value flowed.
Opt-in event streams (all absent by default, all --record/--serve only):
Flag |
Kind |
Fields |
Notes |
|---|---|---|---|
|
|
|
Pre-state register file per step, 1:1 with |
|
|
|
One |
|
|
|
A projection of the memory operands |
|
|
|
Backward def-use cone, including the sink. |
|
|
|
Register delta vs the previous held step. |
|
|
|
Delimits each re-armed pass; every pass restarts |
|
|
|
The ordered instruction stream the session already single-stepped through. Off by default so existing recordings are unchanged; a divergence pair needs it, because it is what lets two dataflow recordings be aligned. |
Serve-session events¶
A --serve session emits two streams no headless --record carries:
codeimage (versioned, JIT-safe code bytes — the when timestamp above) and
vmmap — the target’s address-space map (/proc/<pid>/maps parsed, ranked
and encoded), emitted at session start and re-emitted only when the map
changes (a digest gates re-emission). vmmap is what lets a consumer name
address regions; the desktop’s 3D plane reads it for its region naming and
roster.
--auto — what the picker chose, and on what evidence¶
--auto replaces the region argument with an out-of-band sample. Its decision is
itself reported (on stderr headless, as a session state:"pick" event under
--serve):
Metric |
Values |
Meaning |
|---|---|---|
|
|
Which sampler actually ran after |
|
|
The load-bearing field. |
|
string, u64, u64 |
The region handed to the capture. |
|
u64 |
Entry samples ( |
|
u32 |
Distinct call sites observed arriving. |
|
int / int |
1-based candidate index and how many are ranked — the |
window |
ms |
|
An idle target gets a genuine refusal (no function was observed being ENTERED),
never a guess.
Observability by architecture¶
asmspy builds on Linux only, on x86-64 and AArch64. There is no macOS,
BSD or Windows body, and no 32-bit body — a 32-bit tracee is refused at attach
(ASMSPY_ETRACEE_I386) rather than decoded against the wrong syscall table.
View |
x86-64 (Intel) |
x86-64 (AMD Zen) |
AArch64 |
Gated by |
|---|---|---|---|---|
|
✅ |
✅ |
✅ |
|
|
✅ |
✅ |
✅ |
host syscall table (see below) |
|
✅ |
✅ |
✅ |
ptrace single-step |
|
✅ |
✅ |
✅ |
ptrace single-step |
|
✅ |
✅ |
✅ |
|
|
✅ |
✅ |
✅ |
software breakpoint + single-step |
|
✅ DR0–3 |
✅ DR0–3 |
✅ |
a free per-thread hardware execution breakpoint slot |
|
✅ DR0–3 |
✅ DR0–3 |
✅ |
a free hardware data watchpoint slot |
|
❌ |
✅ |
❌ |
AMD IBS-Op |
|
✅ |
✅ |
❌ |
live value producer: Linux x86-64 + Capstone |
|
❌ |
✅ |
❌ |
AMD IBS-Op |
|
✅ |
✅ |
❌ |
|
|
✅ |
✅ |
❌ |
ptrace only — opens no perf event |
|
✅ |
✅ |
✅ |
the underlying view |
Every ❌ above is a clean self-skip: # SKIP with the measured reason and
exit status 0 for --sample, --dataflow and --watch. None of them is a crash,
and none of them silently reports zeros.
What differs between x86-64 and AArch64 even where both work¶
These do not change a metric’s name or meaning, but they change what you can conclude from it.
Metric / behaviour |
x86-64 |
AArch64 |
|---|---|---|
syscall number source |
|
|
syscall argument registers |
|
|
syscall name set |
the x86-64 table |
the AArch64 table — calls that do not exist there ( |
return value ( |
|
|
region entry breakpoint |
|
|
call-frame identity ( |
return address pushed on the stack; the frame is |
|
PLT stub naming |
|
|
single-step teardown |
|
no user-writable step bit; the engine must drain a queued step trap and skip threads parked inside a syscall, or the trap outlives the detach and kills the target seconds later |
watchpoint |
the |
the exception fires on the accessing load/store, so |
watchpoint encoding |
DR0–3 + DR7 |
|
|
Capstone x86 ids |
n/a — the live value producer does not run here |
disassembly ( |
x86-64 syntax |
AArch64 syntax; the same field, decoded for the host arch |
Both architectures require a --watch address aligned to --len, which also
satisfies the AArch64 no-crossing rule for every legal length.
Host gates that are not architecture¶
An architecture ✅ above still needs the host to cooperate.
Gate |
Affects |
Requirement |
|---|---|---|
|
every view except |
same-uid and |
kernel floor |
attach |
Linux 3.4+ for |
|
|
Linux 5.3+; below that it falls back to an entry/exit toggle, which can desync when a thread is seized mid-syscall |
|
|
sw` |
IBS |
|
Linux ≳ 6.2 — the user-only filter that makes IBS unprivileged |
Capstone |
|
linked at build time; without it offsets and counts are still exact, the text is simply absent |
soft-dirty / |
|
Linux ≥ 6.7. Where absent, the session emits the measured reason as a |
free debug-register slots |
|
qemu-user exposes none; some hypervisors report slots and then refuse to reserve one ( |
W^X JIT pages |
|
a genuinely W^X-enforced page refuses the entry breakpoint and self-skips as exactly that, not as “never executed” |