Files
rea/docs/hex-records.md
T
Aaditya c36971953c feat(artifacts): inventory and extract Intel HEX and Motorola S-record regions (#7136)
* feat(artifacts): inventory and extract Intel HEX and Motorola S-record regions

open_binary refused Intel HEX and S-record programming files as an
unsupported format, so inspect_artifact and extract_artifact could not
answer which bytes load at which addresses.

Recognize both encodings by a fully valid first record and decode them
statically into address-contiguous regions, each an inventory entry named
0x<start>-0x<end>.bin and extractable as raw bytes. Root format_metadata
records the encoding and variant, record counts, unique and duplicate
bytes, address bounds, termination, declared start address, S0 header and
validated S5/S6 count. Gaps are never filled; start addresses are declared
facts, not proven entry points.

Checksum, record-type, byte-count, line-length, overlap-conflict, address
wrap and post-termination errors fail closed with the line number; a
missing termination record leaves coverage unknown. Decoding is bounded by
a 128 MiB file ceiling (decoded bytes are at most half the file and are
reserved after the size check) and 65,536 address-discontiguous runs.

Refs #7129

* fix(artifacts): refuse Intel HEX extended address records with a non-zero address

* fix(artifacts): bound HEX region copies and retain stream cancellation

---------

Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
2026-10-28 12:35:55 +08:00

9.2 KiB
Raw Blame History

Intel HEX and S-record images

REA inventories Intel HEX (I8HEX, I16HEX, I32HEX) and Motorola S-record (S19, S28, S37) programming files through inspect-artifact / inspect_artifact, and materializes their decoded bytes through extract-artifact / extract_artifact. Decoding is static and needs no external tool.

rea inspect-artifact ./firmware.hex --json
rea inspect-artifact ./firmware.s37 --detail summary --evidence_output evidence.json --json
rea extract-artifact ./firmware.hex --json

For MCP, open the file with open_binary, then call inspect_artifact. Summary delivery retains the complete Evidence; use parent_evidence_id with inspect_analysis_view to read selected regions and fields. extract_artifact returns a fresh output_root containing one raw file per region.

Recognition

A file is recognized only when its first line, starting at byte 0, is a fully valid record: correct start character, hex digits, byte count, checksum, known record type and fixed length. The file name does not matter. A byte-order mark, a leading blank line or an invalid first record leaves the file unrecognized, so it falls through to plain file classification. Recognition never searches for a record later in the file. The container formats are intel-hex and srec.

Regions and metadata

Each inventory entry is one address-contiguous region of decoded bytes, named 0x<start>-0x<end>.bin with an inclusive end address of eight lowercase hex digits, for example 0x08000000-0x0800001f.bin. Regions are listed in ascending address order. Out-of-order records are sorted into regions; gaps separate regions and are never filled.

Region format_metadata (hex-record-region) records start_address, end_address, length, record_count, and first_line/last_line: the 1-based line span of the contributing data records.

Root format_metadata (hex-records) records:

Field Meaning
encoding, variant intel-hex or srec; the variant derived from the record types seen, or mixed
line_count, record_count, data_record_count Lines read, non-blank records, and data records
data_bytes, duplicate_bytes, region_count Unique decoded bytes, bytes rewritten with identical values, and regions
lowest_address, highest_address Address bounds of the decoded bytes, or null without data
termination Whether Intel 01 or S-record S7/S8/S9 was present
start_address, start_address_record The Intel 05 EIP or the S7/S8/S9 address, with its record type
start_segment_cs, start_segment_ip The Intel 03 CS:IP pair
header_hex, header_text S-record S0 data as hex, and as text only when every byte is printable ASCII
declared_data_record_count The last S5/S6 count, validated against the data records before it

variant is null for an S-record file in which no data or termination record establishes an address width. Start addresses are declared facts, not proven entry points.

Facts, limitations and failures

Situation Outcome
Gaps between regions Separate regions; never zero-filled
Out-of-order records Sorted into regions
An address written twice with the same value Accepted and counted in duplicate_bytes
An address written twice with different values Fails, naming the address and the line spans of both writes
Checksum mismatch, odd hex length, non-hex character, byte count disagreeing with the line Fails with the line, declared and computed values, and record type
Line longer than 521 (Intel) or 514 (S-record) characters, excluding the line ending Fails before the line is decoded
Intel type 06 or higher, S4, wrong fixed length for Intel 01–05 or S5–S9 Fails
Missing Intel 01 or S7–S9 Regions are returned; coverage is unknown and the file may be truncated
A non-blank record after termination, or a second S0 Fails; blank lines are allowed anywhere
S5/S6 count different from the data records before it Fails
Termination type not matching the data address width Reported as a limitation; the declared fact is kept
Conflicting repeated 03/05 start records Fails; identical repeats are allowed
An Intel data record whose 16-bit offset wraps past 0xFFFF Fails: producers disagree on whether the address wraps or continues linearly
I16HEX data above 0xFFFFF, or data crossing the S1/S2/S3 address width Fails

Lowercase hex, LF and CRLF line endings, and trailing spaces or tabs are accepted. A CR not followed by LF is an invalid character. Failures apply to the whole file and return no partial regions.

Limits

The file must be at most 128 MiB. Every data byte needs two hex digits, so the decoder reserves at most half the file size for decoded bytes before reading, and stores only written addresses. The decoder retains at most 65,536 address-discontiguous record runs before merging them into regions. A run is a sequence of consecutive records whose addresses continue each other. Both are fixed resource bounds, not format limits, and fail as limits (file-size and capacity) with no runtime override. Configurable ceilings are tracked in #6176. The line-length limits are the one-byte record count: Intel HEX allows 255 data bytes, while S-record counts include address and checksum bytes.

Decoded regions are cached only for the reader's lifetime. Each open checks its admitted carrier identity again. Region streams copy at most 64 KiB per chunk, so consumers cannot mutate cached bytes and opening a large region does not copy it in full. Caller cancellation keeps its exact reason, and closing the reader waits for active region streams to close.

Analyzing a region

Inventory does not establish the CPU, byte order or memory map, or that a declared start address is executable. To disassemble a region, extract it and open the region file with open_binary using format raw-binary, an architecture you choose, and entry_point set to the region start address; raw-binary maps file offset 0 at entry_point. Each region is opened separately, so references between regions are not resolved. A caller-selected Ghidra language and base address for raw-binary imports is tracked in #4246; Thumb-only Cortex-M images may be refused or decoded in ARM state until #5744 is resolved.

Verification

Fixtures with computed checksums cover supported record types, checksums, termination, address gaps and overlaps, resource refusals, inventory, extraction and reader lifecycle. CLI and stdio MCP tests exercise the same artifact provider and advertised schemas, including retained Evidence views.

GNU objcopy 2.40 output was also checked on Linux: the system true ELF was converted to Intel HEX and S-record, with additional address-rebased I32HEX and S37 cases. Each yielded 13 regions. A 19,124,817-byte Intel HEX conversion of the system Python 3.11 ELF yielded 11 regions containing 6,799,261 decoded bytes. Public CLI inspection and extraction matched GNU BFD address ranges and independently exported section bytes. Adjacent GNU sections were joined without filling address gaps. These are real producer outputs from system applications, not vendor-firmware or target-execution verification; srec_cat and other producers remain unverified. No general speedup is claimed.