Trace Domain v1 format reference
Use Creating custom Trace Domains for the complete tutorial, downloadable examples and offline validation with the ViewAlyzer CLI. This reference covers all fields in the version-one format.
File format and compatibility
Save the descriptor as UTF-8 JSON with the .vadomain extension. Use valid JSON: no comments or trailing commas. A package is a folder containing the descriptor and any supporting files, such as an external instrument recorder.
domain_api: 1 selects the complete version-one format described here: sampled sources, pointer/member/array traversal, derived channels, findings, cards and tables, graph choices, RAM read groups, typed actions and instrument declarations. Omitting domain_api also selects version one. Always include it in shared files so the required format is explicit.
version is your domain's own release string. It is independent of domain_api, the SDK package version and the applications' version numbers. Increment your domain version when you publish changes. Change domain_api only when adopting a new documented format supported by the reader. Do not change the number to bypass a compatibility error.
A descriptor is rejected if it uses an unsupported API version, an unknown field, or an invalid value. Check the error shown when installing the file, or run viewalyzer-cli domains to inspect loading errors. For a version error, use a compatible application version or a descriptor supplied for your installed version.
Top level
This is a complete minimal descriptor. Add the sections your domain needs; omit unused sections.
{
"id": "com.example.device",
"name": "Device Monitor",
"version": "1.0.0",
"domain_api": 1,
"publisher": "Example"
}
| FIELD | REQUIRED | MEANING |
|---|---|---|
id | Yes | Stable domain identifier. Use a reverse-DNS name such as com.example.device. ViewAlyzer uses it to identify installed versions and overrides. |
name | Yes | Display name in domain lists and findings. |
version | Yes | Your descriptor's version string, such as 1.0.0. |
domain_api | No; defaults to 1 | Descriptor format version. |
publisher | No | Author or organization shown in the domain list. |
channels | No | Rules for channel units, grouping, names, and display style. |
sampled_state | No | Registers and firmware values to observe through the debug probe. |
derived | No | Channels calculated from observed values. |
verdicts | No | Conditions that produce findings with explanations and evidence. |
presentation | No | Collapsible cards or tables grouping sampled and derived values. |
actions | No | Explicit typed operations, executable only in BKPT Debug. |
instrument | No | External instrument recorder to run with a ViewAlyzer capture. |
series_kinds | No | Additional event kinds whose numeric values should appear as series. |
namespaces | No | Additional namespaces whose objects should be treated as data channels. |
Channel display rules
Each entry in channels selects channels by namespace and name. The first matching rule wins, so put exact-name rules before broader prefix rules. Styling applies to the channel's presentation; recorded sample values are preserved.
This rule displays a sampled ring occupancy as a level bar:
{
"match": { "ns": "poll", "name": "eth.rx_ring" },
"unit": "desc",
"display": "level",
"graph": true,
"group": "DMA",
"min": 0,
"max": 4,
"warn": 3,
"interpolation": "step"
}
| FIELD | MEANING |
|---|---|
match.ns | Channel namespace; see the table below. |
match.name | Exact channel name. Takes precedence over name_prefix within the same rule. |
match.name_prefix | Selects names beginning with this text. For example, eth. selects Ethernet channels named with that prefix. Omit both name fields to select the whole namespace. |
unit | Suffix shown beside values, such as pkts, Hz, or m/s2. |
display | Display style: graph, bar, level, scatter, impulse, enum, gauge, counter, table, histogram, toggle, register, angle, task, or isr. Overrides the firmware's display hint. |
graph | Optional boolean default Graph choice. A saved user choice takes precedence. Does not affect sampling or history. |
group | String label grouping charts. Distinct from a presentation field's nested group array. |
min, max, warn | Fixed scale and warning threshold for a level bar. |
names | Value labels, such as {"0": "Stopped", "1": "Running"}. Per-channel labels chosen in the app take precedence. |
interpolation | step or linear. Use step for sampled states and counters so the chart holds each observed value until the next sample. A per-chart selection takes precedence. |
| NAMESPACE | VALUES |
|---|---|
poll | Domain inputs and manually polled firmware symbols. |
derived | Values calculated by the derived section. |
user_trace | User traces emitted by the firmware recorder. |
external | Imported or captured instrument samples. |
Graph defaults
In both apps, each value's Graph control chooses whether to display its history in Trace/Traces. It stays available in cards/tables either way. The first matching channel rule wins; if that rule omits graph, use the automatic default rather than searching later rules.
With presentation, declared counters, ring occupancy and derived rates start graphed; other fields start in cards/tables. Without presentation, the legacy enabled graph default is preserved. Use explicit graph: true for motion or other useful signals, and graph: false for addresses, configuration codes or duplicate representations. Users can override any default. Reset graphs restores these defaults without changing expansion or sampling settings. Preferences are separate in each app; BKPT Debug also scopes them to the workspace.
Sampled inputs
sampled_state tells the debug probe which target values to read while the firmware runs. Register sources use the device's SVD file. Firmware symbols, structure members, and rings use the ELF from the firmware build. Domains that only read firmware memory need no SVD unless they also declare register preconditions.
For desktop domain-only capture, enable an applicable sampled domain and turn Software Trace off. No recorder control block is required. Enable Software Trace for combined recorder capture. RAM snapshots still require recorder firmware.
This section observes an existing firmware counter without adding logging calls:
{
"sampled_state": {
"rate_hz": 20,
"channels": [
{
"name": "sensor.samples",
"symbol": "imu_sample_count",
"ty": "u32",
"counter": "wrapping"
}
]
}
}
| FIELD | MEANING |
|---|---|
svd_device | Device SVD name, without .svd. ViewAlyzer searches VA_SVD_DIR, its svd/ folder, and STM32CubeCLT installations. Use the exact peripheral and register names in that file. |
target_device_prefix | List of target-name prefixes to which the domain applies, such as ["STM32U575"]. An empty list permits any target. A mismatch skips sampling with a reason. |
rate_hz | Requested samples per second; defaults to 20. Must be positive and no greater than 1000. Actual acquisition can be slower. |
coalesce_gap_bytes | Maximum gap between registers that may be included in one combined read. Defaults to 0, allowing only adjacent registers to be combined. A larger value permits reading intervening bytes; use it only when those bytes are safe to read. |
precondition | Register condition required for every input in this section. |
channels | List of sampled inputs. |
Input fields
Give each input a unique name and one source: a peripheral/register pair, a symbol, a ring, or a structured path.
| FIELD | MEANING |
|---|---|
name | Channel name used by display rules, derived channels, and findings. |
peripheral, register | Exact names in the selected SVD. Supply both for a register source. |
symbol | Global symbol in the matching firmware ELF. |
ty | Scalar type: u8, u16, u32, i8, i16, i32, or f32. Defaults to u32. |
offset_bytes | Byte offset within a symbol; defaults to 0. The selected value must fit within that symbol's size. |
ring | Descriptor-ring source whose value is the number of matching entries. See Rings below. |
structured | Named structure members, explicit pointer steps and fixed-array indices. |
counter | monotonic, wrapping, or clear_on_read. Omit for values that are not counters. |
safety | Read policy; defaults to read-safe. See Read policies below. |
precondition | Additional register condition required for this input. |
reason | Explanation shown when an input cannot be sampled. |
note | Context for interpreting the value, such as cache behavior or counter reset conditions. |
Use monotonic for an increasing total. Use wrapping for a counter that rolls over at the width of ty, so its rollover can be accounted for in comparisons. With clear_on_read, each sample is the increment since the previous read; use value_at_or_above to detect a nonzero interval. Reading a clear-on-read counter consumes its value, so only observe it when doing so is compatible with the firmware's use of that counter.
For a fixed member offset, verify the layout against the exact firmware build. This example reads a 16-bit member two bytes into lwip_stats:
{
"name": "lwip.link_recv",
"symbol": "lwip_stats",
"offset_bytes": 2,
"ty": "u16",
"counter": "wrapping"
}
Preconditions
A precondition tests (register & mask) == equals. Both mask and equals are hexadecimal or decimal strings. Use a condition to check, for example, that a peripheral clock is enabled before reading its registers.
{
"peripheral": "RCC_S",
"register": "RCC_AHB5ENR",
"mask": "0x02000000",
"equals": "0x02000000",
"description": "Ethernet peripheral clock is disabled"
}
The register names and mask above are device-specific. Replace them with values from your device's SVD and reference manual. A failed condition makes the affected inputs unavailable and displays description.
In ViewAlyzer captures, preconditions are evaluated at capture start. Start capture after the required peripheral initialization; a condition that fails at startup disables its inputs for that capture.
Read policies
| SAFETY | BEHAVIOR |
|---|---|
read-safe or safe | Read when applicable preconditions pass. |
conditional | Read only with a declared precondition, either on the input or on sampled_state. |
intrusive | Not sampled. Use reason to explain how a read would disturb the target. |
unsafe | Not sampled. Use reason to explain the risk to target state. |
unsupported | Not sampled. Use reason to explain why the value is inaccessible through the debug port. |
Sampling never executes a write action. Typed actions are a separate, explicit BKPT Debug operation; desktop views remain read-only. Reads can still have device-specific effects; choose sources and conditions using the device documentation and the firmware's ownership of each resource.
Rings
A ring source reads a fixed-size array located by an ELF symbol and counts entries whose ownership bits match the requested state.
{
"name": "eth.rx_ring",
"ring": {
"symbol": "DMARxDscrTab",
"entries": 4,
"stride_bytes": 24,
"own_word_offset": 12,
"own_mask": "0x80000000",
"count_set": false
},
"note": "Receive descriptors awaiting reclamation by the application"
}
entries is the array length; stride_bytes is the size of each entry. own_word_offset locates the ownership word within each entry. count_set: true counts entries where word & own_mask is nonzero; false counts entries where it is zero. The example's layout and ownership convention must match your firmware.
The ring requires a matching ELF, and its total size must fit within 65,535 bytes. Set the entry count, stride, offset, and mask to match the application's actual array layout.
Structured sources
Use structured to select members by name and follow pointers explicitly. This avoids hard-coding member offsets. The matching firmware ELF must contain debug information describing the types.
This input follows the global pointer device_root and reads its state member:
{
"name": "device.state",
"ty": "u32",
"structured": {
"symbol": "device_root",
"steps": [
{ "op": "deref" },
{ "op": "member", "name": "state" }
]
}
}
member selects a struct or union member; deref follows a pointer. For a global struct instead of a pointer, start with member. Paths contain 1–16 steps. Use the input's ty to match the final scalar type.
A path also accepts {"op":"index","index":1} for a fixed-size C array. Bounds and element sizes come from DWARF. Pointer indexing, variable-length/multidimensional arrays and out-of-bounds indices are rejected. Use an explicit deref step when an array element is a pointer.
Structured sources require a 32-bit little-endian ELF with matching type information. Reads must remain within writable allocated ELF sections. Missing symbols or types, unsupported bitfields, mismatched scalar types, null pointers, out-of-range addresses, and failed reads make the input unavailable with a reason. Structured sources do not support clear_on_read.
Pointers and the selected value are read again for each observation. A detected pointer change produces a gap, so rates and checks do not span two different object instances. Separately read fields are not an atomic snapshot, and pointer checks cannot detect every change between reads, including reuse at the same address. Keep the selected ELF matched to the flashed firmware.
Optional RAM read groups
sampled_state.ram_read_groups declares bounded opportunities to read related RAM fields together. It is an acquisition hint: a consumer may keep separate reads. It is not a promise of higher achieved rate or an atomic snapshot.
{
"id": "queue_header",
"channels": ["queue.used", "queue.capacity"],
"max_bytes": 64,
"allow_padding": true
}
| FIELD | REQUIRED | MEANING |
|---|---|---|
id | Yes | Nonempty group ID, unique within ram_read_groups |
channels | Yes | 2–32 distinct declared sampled inputs; each input may belong to only one group |
max_bytes | Yes | Maximum aligned read span, 4–4096 bytes and a multiple of four |
allow_padding | No; default false | Explicitly permits reading padding and unselected RAM fields inside the bounded span |
At most 32 groups are allowed. Members must be readable symbol or structured inputs; registers, rings, clear_on_read, unsafe, intrusive and unsupported inputs cannot belong. ELF-derived bounds still apply. A group cannot authorize reading MMIO or memory outside supported allocations. Without allow_padding, every byte in the aligned span must belong to a selected field.
Selecting fewer than two members allows individual reads; selecting none reads nothing for that group. Unselected fields never become observations merely because their bytes were inside a permitted read. Groups do not change dependency requirements for derived values or findings. Pointer validation and failed-read handling still apply.
Derived channels
Each entry in derived calculates a channel from a sampled or recorded value. Derived channels use the derived namespace and can be displayed, queried, or used by findings. Put dependencies before the derived channels that use them.
This array extracts a named state from a register and calculates a counter's rate:
[
{
"type": "field",
"name": "eth.rx_dma_state",
"source": "eth.dma_debug",
"shift": 8,
"mask": "0xF",
"values": { "0": "Stopped", "4": "Suspended", "7": "Transferring" }
},
{
"type": "rate",
"name": "eth.tx_rate",
"source": "eth.tx_good",
"unit": "pkts/s"
}
]
| TYPE | FIELDS | RESULT |
|---|---|---|
field | name, source, shift, mask, optional values | (value >> shift) & mask, retains observation cadence for sampled domains; recorded on change for externally recorded channels. values maps numeric values to state labels. |
rate | name, source, optional unit | Counter advance per second between adjacent samples. Negative deltas are clamped to zero. |
map | name, source, table, optional unit | Numeric lookup, such as {"4": 104, "5": 208} to convert configuration codes to Hz. Unmapped values pass through. Sampled domains retain observation cadence; externally recorded values are recorded on change. |
For a domain with sampled_state, every source must refer to a declared input or an earlier derived channel. Names must be unique. Derived channels are available during live observation in BKPT Debug and when ViewAlyzer finalizes or opens a recording.
Findings and verdicts
Each entry in verdicts defines a condition, severity, explanation, and supporting channels. ViewAlyzer shows the resulting findings in the Analyzer. CLI users can retrieve them with query verdicts. BKPT Debug shows them in its Trace Domains view.
{
"id": "app-not-draining",
"name": "Application not draining",
"severity": "warning",
"when": {
"type": "counter_stalled",
"channel": "lwip.link_recv",
"while_advances": "eth.rx_good",
"window_ms": 250
},
"explain": "Frames continued arriving while the stack stopped accepting them. Check the receive task and buffer pool.",
"evidence": ["lwip.link_recv", "eth.rx_good", "eth.rx_ring"]
}
| FIELD | MEANING |
|---|---|
id | Unique, stable identifier for the rule. |
name | Short finding title. |
severity | error, warning (default), suspicious, flag, or info. |
when | Detector and its inputs, from the table below. |
explain | Explanation shown with the finding. Describe what happened and what to check next. |
evidence | Channels to include with their values at the finding's onset and end. Named states use their labels. |
Detectors
| WHEN.TYPE | FIELDS | CONDITION |
|---|---|---|
sustained_at_or_above | channel, value, sustained_ms | Observed value stays at or above the threshold for the duration. |
sustained_at_or_below | channel, value, sustained_ms | Observed value stays at or below the threshold for the duration. |
counter_advanced | channel | Counter increases. Use for cumulative counts, such as an error total. Wrapping counters are compared using their unwrapped total. |
value_at_or_above | channel, value | A sample reaches the threshold. Useful for clear_on_read counters, where each sample already represents an interval. |
value_in | channel, values | An observed value matches one of the listed discrete codes. Repeated observations form intervals, not counts of operations. |
counter_stalled | channel, while_advances, window_ms, optional min_advance | One counter stops while the other advances over the window. min_advance defaults to 1 and sets the required advance of while_advances. |
ratio_below | channel, reference, ratio, sustained_ms | The channel stays below reference * ratio for the duration while both values are available. |
In a domain with sampled inputs, declare every channel used by a detector or listed as evidence. Choose the observation and duration to suit the expected behavior: a 20 Hz poll may miss a state that changes and returns between two samples. A cumulative counter can preserve evidence of those short events. The absence of a finding does not establish that no brief event occurred.
Cards and tables
presentation.sections contains 1–12 sections, each with 1–32 fields.
| SECTION FIELD | MEANING |
|---|---|
title | Required nonempty heading |
view | Required cards or table |
description | Optional explanatory text |
collapsed | Optional initial state. Omitted: tables closed, cards open. Saved expansion choices take precedence |
fields | Required array of fields below |
checks | Optional verdict IDs associated with this section |
| PRESENTATION FIELD | MEANING |
|---|---|
channel | Required declared sampled or derived channel name |
label | Required nonempty display label |
group | Optional array of up to four nonempty labels, each at most 128 UTF-8 bytes. Omitted/empty means directly in the section |
reference | Optional channel supplying positive capacity/total |
unit | Optional display unit |
action | Optional declared action ID; editable only in BKPT Debug |
For example, a field with "group": ["Characteristic", "Identity"] appears under those two nested disclosures. Groups start closed. Direct fields precede child groups, and sibling groups follow their first appearance in the fields array. The app does not infer hierarchy from channel names or device type.
Expansion is remembered separately from Graph choices. Closing a section or group does not stop reads, hide history, disable checks or change Graph choices. Expand all/Collapse all change expansion; Reset graphs restores only the graph defaults. Each app keeps its own preferences. Renaming a section or group can reset that branch's expansion choice.
For graph defaults, the first matching channel rule is decisive. If it omits graph, use the automatic fallback, not a later rule: with presentation, declared counters, rings and derived rates start graphed; other channels do not. Without presentation, channels retain the legacy enabled default. Put exact rules before broad prefixes when they need different defaults.
Fields may add reference (another channel containing a positive capacity/total) and unit, for example {"channel":"queue.used","reference":"queue.capacity","label":"Control queue","unit":"messages"}. Both observations must be available; missing or invalid capacity does not render as a successful empty queue. Sections may add checks: ["verdict-id"] to surface associated observations and retained incident history in Debug. These are observed incidents, not declarations that a fault remains active. Replay uses the history available at the selected time. The desktop analyzer continues to provide its full findings report when capture stops.
{"presentation":{"sections":[{"title":"Device","view":"cards","description":"Current sampled state","fields":[{"channel":"device.state","label":"State"}]}]}}
Each section uses cards or table; 1–12 sections and 1–32 fields per section. Fields reference declared input or derived channels and use their existing enum names. There is no HTML, script or arbitrary layout execution. Both BKPT Debug and ViewAlyzer render these same sections with neutral rectangular observations. Debug shows live values alongside checks and retained findings. ViewAlyzer shows live values while capturing. Its dedicated domain view follows the hovered or pinned trace cursor in saved recordings, or the recording end when no cursor is selected. Existing trace views retain histories. Samples older than three requested periods (at least 250 ms) display as unavailable in snapshots.
Typed actions
In BKPT Debug, a presentation field can reference a declared action, for example {"channel":"device.state","label":"State","action":"set_state"}. Editing a draft never writes: the user must explicitly select Send. ViewAlyzer keeps all observations read-only, even when the descriptor declares an action. Actions have a unique identifier id, a visible label, input, target and mandatory completion. Unknown properties, target kinds and policies are rejected. At most 32 actions are accepted per domain. No scripts, addresses, expressions or function calls can be submitted by the editor.
Inputs and encoding
| INPUT.TY | ENCODING AND BOUNDS |
|---|---|
u8, u16, u32, i8, i16, i32 | Little-endian integer; decimal or nonnegative 0x hexadecimal; optional min/max within the type |
f32 | Finite little-endian float32; optional min/max; normal float32 rounding, overflow and underflow-to-zero rejected |
bool | true or false, one byte 1 or 0 |
bytes | Complete hex byte pairs, contiguous or separated by whitespace; mandatory max_bytes in 1–256 |
utf8 | UTF-8 text; mandatory max_bytes in 1–256 and termination: "none" or "nul"; bound includes the terminator; embedded NUL rejected |
Optional choices is a list of up to 64 { "value": "1", "label": "Enabled" } entries. Values must pass the same encoder and be distinct after encoding. With choices present, only those encoded values are accepted. Inputs are never silently truncated or wrapped. The same type, range and encoding rules apply when validating a draft and when sending it. The editor sends strings, not encoded bytes.
RAM symbols and members
{"id":"set_limit","label":"Limit","input":{"ty":"u16","min":0,"max":500},
"target":{"kind":"ram","source":{"symbol":"settings","steps":[{"op":"member","name":"limit"}]},"execution":"running_scalar"},
"completion":"readback"}
The matching ELF/DWARF determines the exact type, size, member/array offsets and writable allocated SRAM bounds. It must match the input type; integer widths or signedness are not reinterpreted. Const, union, bitfield and pointer-valued leaves are rejected. Buffers require an exactly sized one-dimensional byte/char array. A shorter buffer value is zero-padded to that storage capacity. Completion reports fresh readback, including a mismatch if firmware overwrote or did not retain it.
execution defaults to halted. Pointer traversal is halted-only, bounded to ELF allocations, read fresh and rechecked before writing; null/changing pointers fail. running_scalar explicitly permits aligned 1/2/4-byte scalar edits at fixed paths without dereferencing pointers. This is a background debug-bus operation, not a transaction with firmware. The application owns concurrency with interrupts, DMA and other cores; halting one core does not stop those other bus masters.
SVD registers and fields
{"id":"clear_flags","label":"Clear flags","input":{"ty":"u32"},
"target":{"kind":"register","peripheral":"PORT","register":"STATUS","field":"READY","execution":"running_scalar"},
"completion":"transport"}
BKPT Debug resolves names and access policies from the selected SVD. Only unsigned integer/bool inputs and unambiguous 8/16/32-bit register layouts are supported. Register/field access rights, writable masks, reserved bits, numeric/enumerated write constraints and read effects are enforced. Normal partial writes preserve unrelated bits with read-modify-write and require a halted core and side-effect-free readable register. Running actions allow direct writes only. Homogeneous oneToClear, oneToSet and oneToToggle effects write zero to untouched flags; mixed effects, zero-to effects, write-once, writeAsRead, ambiguous derived/alias layouts and missing field masks are refused.
completion: "readback" requires a safe read. "transport" reports the probe's write acknowledgement only, with no claim about the resulting register value. A descriptor cannot override SVD restrictions. SVD accuracy remains a prerequisite.
Application mailbox
{"id":"set_state","label":"State",
"input":{"ty":"u8","choices":[{"value":"0","label":"Off"},{"value":"1","label":"On"}]},
"target":{"kind":"mailbox","symbol":"domain_command","operation":1,"timeout_ms":3000},
"completion":"application"}
Mailbox commands require running firmware. The firmware callback runs in its appropriate application context, validates operation/length/value independently, and invokes the real application API. Observation mirrors are not command targets. Operation mappings and payload interpretation belong to the descriptor/firmware. Timeout is 100–5000 ms and the maximum payload is 64 bytes.
Command ABI 1 is a word-aligned 104-byte object with these exact DWARF member names and offsets. Every integer is uint32_t, little-endian; payload is uint8_t[64].
| OFFSET | MEMBER | OWNER |
|---|---|---|
| 0, 4, 8 | magic = 0x56414331, abi = 1, capacity = 64 | Firmware initialization |
| 12 | request_seq | Host publishes last |
| 16, 20, 24 | request_nonce, operation, length | Host stages |
| 28 | payload | Host stages |
| 92, 96, 100 | response_seq, response_nonce, status | Firmware completion |
An idle mailbox has equal request/response sequences. The host stages and verifies the body before publishing a new nonzero sequence. Firmware consumes only a new sequence, copies and validates the bounded request, then publishes status and nonce before response sequence using appropriate memory barriers. Status 0 means accepted; other values are application errors. Host completion requires both sequence and nonce to match. One host owns a mailbox; concurrent independent writers are unsupported.
Host lifecycle and compatibility
BKPT Debug keeps drafts separate from sampled values and writes only on explicit Send. It shows pending, accepted, rejected and unknown completion separately. Overlapping destinations are serialized by refusing concurrent requests. A timed-out or disconnected write may have executed: it is never retried automatically. A busy mailbox refuses another command until firmware acknowledges the previous sequence.
Send is bound to the loaded descriptor revision, debug session, ELF, target state and destination resolution. The host rechecks these at queued IO dispatch, validates the ELF file hash for DWARF targets, and compares sampled ELF load-image bytes with target memory. A mismatch or unavailable signature blocks writes. This detects ordinary stale firmware; sparse signatures are not full-image attestation. Modified RAM load images or patched code may also fail the signature check. Keep the matching firmware/ELF/source set. The host never silently halts, resumes, resets or reflashes.
History, recordings and disconnected/saved-task views are read-only. Changing the domain or session discards drafts and cannot replay a prior Send. The desktop renders observations with a visible read-only limitation; its analyzer does not execute actions. No action is executed while parsing, recording, restoring a layout or applying a descriptor to a recording.
External instrument recorders
An instrument section configures a separate recorder program that runs during a ViewAlyzer capture. The program streams the external-series format; its samples are aligned with and merged into the recording. Include the program and its required assets in the package folder. Instrument recording is configured in ViewAlyzer.
{
"instrument": {
"cmd": "\"{dir}/bin/recorder\" --stream --rate {param.rate}",
"warmup_s": 4,
"sync_name": "jsync",
"params": [
{
"key": "rate",
"label": "Sample rate",
"type": "number",
"default": "1000",
"min": 1,
"max": 10000
}
]
}
}
| FIELD | MEANING |
|---|---|
cmd | Recorder command. {dir} expands to the descriptor's folder; {param.key} expands to a parameter value. |
warmup_s | Seconds to wait after launching the recorder before target capture starts; default 2, constrained to 0–30 at runtime. |
sync_name | Name of the recording's synchronization channel; default jsync. |
params | Editable parameter fields shown beneath the domain in the sidebar. Each has a key, optional label, type (number or string, default string), and a string default (default empty). Numeric fields may include min and max. |
Set the parameters and enable Record with capture in the sidebar before capturing. See Instrument time sync for synchronization setup and alignment quality.
Worked example
This complete descriptor observes a firmware counter, calculates its rate, and raises a warning when the observed rate stays at or below 10 samples per second for at least one second. It requires a matching ELF containing a 32-bit unsigned global named imu_sample_count that increments once per sample. Change the symbol and threshold to match your application.
Save the following as sensor-monitor.vadomain, then install it using Load Domain File in ViewAlyzer or Install custom trace domain… in BKPT Debug.
{
"id": "com.example.sensor-monitor",
"name": "Sensor Monitor",
"version": "1.0.0",
"domain_api": 1,
"sampled_state": {
"rate_hz": 20,
"channels": [
{
"name": "sensor.samples",
"symbol": "imu_sample_count",
"ty": "u32",
"counter": "wrapping"
}
]
},
"derived": [
{
"type": "rate",
"name": "sensor.sample_rate",
"source": "sensor.samples",
"unit": "samples/s"
}
],
"verdicts": [
{
"id": "low-sample-rate",
"name": "Sensor sample rate is low",
"severity": "warning",
"when": {
"type": "sustained_at_or_below",
"channel": "sensor.sample_rate",
"value": 10,
"sustained_ms": 1000
},
"explain": "The observed sample rate stayed at or below 10 samples/s for at least one second. Check the sensor configuration and acquisition task.",
"evidence": ["sensor.samples", "sensor.sample_rate"]
}
],
"channels": [
{
"match": { "ns": "poll", "name": "sensor.samples" },
"unit": "samples",
"display": "counter",
"group": "Sensor"
},
{
"match": { "ns": "derived", "name": "sensor.sample_rate" },
"unit": "samples/s",
"display": "graph",
"group": "Sensor",
"interpolation": "step"
}
]
}
Enable the domain, select the matching ELF, and capture while the firmware runs. In ViewAlyzer, stop capture to inspect the derived rate in Trace and any findings in Analyzer. In BKPT Debug, follow the channels in Traces and findings in Trace Domains.
Troubleshooting
| SYMPTOM | WHAT TO CHECK |
|---|---|
| Descriptor will not load | Check JSON syntax, field names, display names, and domain_api. Omit unused sections instead of supplying incomplete objects. |
| Unknown input or evidence channel | Match names exactly and define each derived dependency before its consumer. |
| Register input is unavailable | Check the selected SVD, peripheral/register names, target match, peripheral clock, and precondition message. |
| Firmware input is unavailable | Select the ELF from the flashed build. Check symbol names, type information, member layout, and pointer availability. |
| A finding does not appear | Check that the domain and its required inputs are enabled, valid samples exist, and enough time has elapsed for the rule's duration. Review the actual sampling rate. |
| Channel styling is missing | Check match.ns, spelling, and rule order. Put exact matches before prefix matches. |
For a hardware example with controller registers, structured sources, derived fields, and presentation sections, use the downloadable domain in the USBX on ThreadX walkthrough.
Validation boundaries
The file is checked in stages. An offline pass establishes that the descriptor has the supported shape and internally consistent references. The application later resolves real hardware and firmware information.
| CHECK | OFFLINE CLI | APPLICATION OR TARGET REQUIRED |
|---|---|---|
| JSON syntax, types, unknown properties, supported format | Yes | No |
| Source exclusivity, limits, names, derived ordering and references | Yes | No |
| Action input bounds, choice encodings and completion policies | Yes | No |
| Whether a selected SVD contains the named register or field | No | Selected device SVD |
| Register read/write effects and actual access restrictions | No | Correct SVD and hardware context |
| Whether symbols, members, array indices and scalar types match | No | Matching ELF with supported debug information |
| Runtime pointer validity and current values | No | Live target |
| Achieved polling rate, cache coherence and firmware concurrency | No | Live target and application-specific evaluation |
| Whether diagnostic thresholds describe a real fault | No | Domain expertise and representative tests |
| Instrument executable, stream and synchronization | No | Instrument and recorder program |
Numeric and structural limits
The following details matter when generating files programmatically. Integers must be JSON integers, not strings, booleans or decimal tokens such as 1.0, except fields explicitly described as numeric strings.
| FIELD OR OBJECT | CONSTRAINT |
|---|---|
domain_api | Current supported format, 1; omit to use the default |
sampled_state.rate_hz | Finite number greater than zero and at most 1000; default 20 |
coalesce_gap_bytes | Unsigned 64-bit integer; default zero |
offset_bytes | Unsigned 32-bit integer; default zero; nonzero only with symbol |
Precondition mask / equals | Unsigned decimal or 0x strings; mask fits 32 bits; all set bits in equals must be inside the mask |
Ring entries, stride_bytes, own_word_offset | Unsigned 16-bit integers; entries positive; stride at least four; ownership offset plus four fits the stride; entries times stride at most 65,535 |
Ring own_mask / sampled type | Nonzero unsigned 32-bit decimal/hex string; ring channel uses u32 |
Sampled structured.symbol / member names | ASCII C identifiers, at most 256 bytes; 1–16 steps |
Structured index | Unsigned 32-bit integer; actual array bounds checked against the ELF |
Derived field shift / mask | Shift 0–63; unsigned 64-bit decimal/hex mask applied after shifting |
Derived map table | Nonempty object with signed 64-bit decimal integer keys and finite numeric values |
| Duration values | Unsigned 64-bit integer milliseconds; window_ms positive; sustained_ms may be zero |
min_advance / ratio | Finite and positive / finite and nonnegative, respectively |
value_in.values | 1–256 finite numbers |
| Presentation | 1–12 sections, 1–32 fields each; nonempty titles and labels; up to four group levels with nonblank labels no larger than 128 UTF-8 bytes |
| Action ID and target C identifiers | At most 128 ASCII bytes; action IDs unique within the domain |
| RAM action path | Zero to 16 steps; zero selects the root scalar or buffer, unlike a sampled structured source |
| Actions and choices | At most 32 actions; at most 64 choices per input; choice labels nonempty and encoded choice values unique |
| Buffer capacity | max_bytes 1–256 for bytes/utf8; mailbox payload at most 64 bytes |
| Mailbox | Nonzero unsigned 32-bit operation; unsigned integer timeout 100–5000 ms |
| Instrument parameter key | One or more ASCII letters, digits, underscores or hyphens |
The sampled types are u8, u16, u32, i8, i16, i32 and f32; the default is u32. bool is an action input type, not a sampled type. Unknown enum values are errors. Channel names and identifiers are case-sensitive. Sampled and derived names must be unique within their domain, and verdict IDs must be unique within their own list. An action ID uses a C identifier, so use set_limit, not set-limit.
Arrays default to empty when optional. Optional text such as publisher, explanation and section description defaults to empty. Fields represented as optional values may accept null, including the top-level sampled_state, presentation and instrument, source alternatives, notes, channel-style options and action bounds. A required scalar or list does not become optional by assigning null.
The parser checks format validity, not editorial quality. Give domains, channels and findings useful names even where the format allows an empty string. Use step or linear for interpolation. Include a reason for every deliberately unavailable input. A reverse-DNS identifier should belong to a namespace you can maintain; the parser's basic identifier check does not prove ownership.