* fix(process): report the effective regex worker heap Node gives --max-old-space-size, including NODE_OPTIONS, precedence over the worker's 128 MiB resourceLimits request. Name that effective old-generation heap and stop saying the request has no runtime override. The matching deadline stays a fixed cap. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(process): keep SIGXCPU and SIGXFSZ retries uncertain The signal does not prove the retained rlimit was exhausted, so an unchanged retry may succeed. Project that retry as possible instead of saying it fails the same way. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(process): leave percentage heap overrides unestablished --max-old-space-size-percentage takes precedence over the absolute flag in either order. Node computes that MiB from startup memory, which a later read does not recover, so the effective heap stays unknown instead of the 128 MiB request or an absolute flag. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(process): read launch NODE_OPTIONS, not process.env A preload can rewrite process.env.NODE_OPTIONS after V8 has applied the original flags, and those flags are not copied into execArgv. Use the initial environment block when it can be read, and leave the effective heap unestablished when that provenance cannot be recovered. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(process): leave env-file heap overrides unestablished --env-file and --env-file-if-exists can supply NODE_OPTIONS when that variable was absent from the initial environment. The file is not in /proc/self/environ, so a missing entry is not the 128 MiB request. An initial NODE_OPTIONS is still parsed, because Node does not let the file replace it. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(process): leave --max-heap-size old generation unestablished A positive --max-heap-size or --max_heap_size sets the total heap, not the old generation, and can stay below the 128 MiB worker request when an old-space flag is also present. The effective old generation stays unestablished while that value is in effect. Zero or empty clears it. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(process): leave config-file heap options unestablished --experimental-config-file and --experimental-default-config-file can apply a heap percentage from a file that is not launch provenance. The effective old generation stays unestablished when either flag is present, including beside an absolute --max-old-space-size. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(process): read launch exec argv from the command line process.execArgv can be cleared after V8 applies --max-old-space-size. Linux recovers those tokens from /proc/self/cmdline and leaves the heap unestablished when that vector cannot be separated from the program entry. Heap probes compare pinned absolute limits instead of a percentage of host memory. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(process): leave a title-overwritten command line unestablished process.title and --title replace /proc/self/cmdline with the title. A buffer without a program entry is not an empty option list, so the heap stays unknown instead of the 128 MiB request. The command-line probe does not read /proc off Linux. Co-authored-by: morluto <morluto@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: morluto <morluto@users.noreply.github.com>
819 lines
53 KiB
Markdown
819 lines
53 KiB
Markdown
<div id="native-ui-and-dispatch-investigation"></div>
|
|
|
|
# Native binary investigation
|
|
|
|
Use a native provider to discover functions, inspect instructions and pseudocode,
|
|
follow references, and examine database types. Static resources and runtime
|
|
observation complement that analysis when they answer a specific question.
|
|
|
|
## Start with a function
|
|
|
|
Choose an installed provider from [setup](installation.md#hopper). For Ghidra:
|
|
|
|
```bash
|
|
rea inspect /absolute/path/to/program --provider ghidra --json
|
|
rea search /absolute/path/to/program entry --kind procedures --provider ghidra --json
|
|
```
|
|
|
|
Select an exact name or address from the returned inventory, then use it with
|
|
`rea function PATH PROCEDURE --provider ghidra --json` for a function dossier,
|
|
`rea instructions` for assembly, or `rea decompile` for pseudocode. Do not assume
|
|
that every target names its entry function `entry`. MCP uses `open_binary` and
|
|
focused tools such as `list_procedures` and `analyze_function` on the same session.
|
|
|
|
| Question | Section |
|
|
| ------------------------------------------------------------ | -------------------------------------------------------------------------------------- |
|
|
| Which instructions, calls, references, or types are present? | [Native primitives](#native-instruction-call-and-type-primitives) |
|
|
| How can I select a name beginning with a dash? | [CLI selectors](#exact-cli-selectors) |
|
|
| What static path connects these functions? | [Bounded traces](#bounded-static-traces) |
|
|
| Which UI resources or dispatch metadata are present? | [Static inspection](#static-inspection) |
|
|
| What did the declared runtime scenario observe? | [Desktop](#native-desktop-observation) or [call observation](#native-call-observation) |
|
|
| What can this provider and host establish? | [Provider boundaries](#provider-and-verification-boundaries) |
|
|
|
|
## Exact CLI selectors
|
|
|
|
`function`, `instructions` and `decompile` accept `--procedure=<name-or-address>`;
|
|
`xrefs` accepts `--address=<name-or-address>`. `search` accepts `--pattern=<text>`
|
|
and `trace` accepts `--query=<text>`. These alternatives preserve names and text
|
|
that begin with a dash, including Objective-C names such as
|
|
`rea function ./app '--procedure=-[REAWidget delegate]' --provider hopper --format json`.
|
|
Use the equals form when the value resembles a CLI flag, for example
|
|
`rea trace ./app --query=--help --provider hopper --format json`.
|
|
The existing positional forms remain available. Missing selectors and conflicting
|
|
positional/named values fail before analysis starts; identical selections agree.
|
|
|
|
## Native instruction, call and type primitives
|
|
|
|
Ghidra supplies three exact-object operations:
|
|
|
|
Ghidra accepts hexadecimal addresses with or without a `0x` prefix, in either
|
|
letter case, and explicit address-space coordinates such as `EXTERNAL:0x1`.
|
|
Results use canonical lowercase hexadecimal offsets. For a procedure identifier,
|
|
an explicit `0x` or address-space prefix selects an address; otherwise an exact
|
|
database symbol name takes precedence over a bare hexadecimal address. Thus a
|
|
function renamed to `dead` remains selectable by name. Use the names returned
|
|
by the inventory, including their namespaces and any platform symbol prefix.
|
|
Ghidra also accepts secondary symbols at a function entry, such as an imported
|
|
`_main` label. A label inside a function is not a procedure-name selector; use
|
|
its address when selecting the containing function.
|
|
Overloads can share a fully qualified name; ambiguity errors return every
|
|
matching entry address so the caller can select the intended function directly.
|
|
|
|
`resolve_containing_procedure` also resolves an exact external entry. Its empty
|
|
body remains explicit; nearby external addresses do not inherit that identity.
|
|
|
|
Ghidra function references cover the complete function-body AddressSet and its
|
|
exact entry, including references into instruction interiors and references
|
|
from embedded data. Addresses inside an enclosing span but outside the owned
|
|
body are excluded. `procedure_references` returns internal and external edges;
|
|
`analyze_function` omits incoming edges whose sources belong to the same function.
|
|
The reference collector's semantics participate in the analysis profile, so
|
|
older snapshots cannot replay the former incomplete results under this profile.
|
|
|
|
Ghidra regex searches use Java Pattern semantics. If compiling or matching a
|
|
pattern exhausts the engine's stack, REA returns a resource constraint and
|
|
preserves the active database and annotations. Retry in literal mode or simplify
|
|
the regex; successful literal searches retain complete matching strings.
|
|
Cancelling an active request terminates the ephemeral database and discards its
|
|
annotations. REA retains the selected target and imports the original artifact
|
|
again on the next Ghidra query.
|
|
|
|
Opening the same target and profile again retains the live database, including
|
|
annotations. It does not make those edits eligible for an immutable snapshot.
|
|
Snapshot closes drain earlier provider requests before saving and keep later
|
|
requests waiting until the save and close finish. A concurrent successful edit
|
|
therefore rejects the snapshot save and leaves the edited session open.
|
|
After metadata edits, snapshot saves and imports remain unavailable in that
|
|
session. Use `export_evidence_bundle` to retain the observations, close without
|
|
`snapshot_path`, and reopen the target before importing or saving a snapshot.
|
|
|
|
Ghidra checks its private import copy against the artifact digest selected by
|
|
`open_binary`. If the source changes before that copy is acquired, the query
|
|
reports `artifact_changed` with the selected path and both digests. Reopen the
|
|
stable target to acquire its current identity. An already imported database
|
|
continues to describe its captured bytes even if the original path is changed
|
|
or removed; this identity failure does not mark the Ghidra installation unavailable.
|
|
On Linux and macOS, an unreadable source reports `access_denied`; a removed or
|
|
nonregular replacement reports `artifact_changed`. Source copying is cancellable
|
|
and never replaces an existing private snapshot.
|
|
|
|
```bash
|
|
rea inspect-native-instruction <binary> <address> --provider ghidra
|
|
rea resolve-native-call-targets <binary> <call-site> --provider ghidra
|
|
rea inspect-native-data-type <binary> --type /MyStruct --provider ghidra
|
|
rea inspect-native-data-type <binary> --address <typed-data-address> --provider ghidra
|
|
```
|
|
|
|
Instruction facts include bytes, length, decoder text, ordered register/scalar/
|
|
address tokens, typed references and flow destinations. Mid-instruction, data,
|
|
outside-memory and undecodable addresses have separate outcomes. Effective
|
|
memory base/index/displacement roles remain unavailable. Ghidra reports its program
|
|
language variant in `mode`; other providers retain their own mode label. Ghidra
|
|
MIPS results additionally expose `decoder_context`
|
|
with the observed `ISA_MODE` register at the queried address and its provider
|
|
source. A null value is unavailable, not mode zero; an empty array reports no
|
|
available context register, and omission preserves older captures or providers
|
|
that do not expose this facet. The context is an analysis observation, not proof
|
|
of a runtime processor state.
|
|
|
|
`inspect_native_instruction` also returns `incoming_references` to the exact
|
|
queried address, including data, interior-instruction and undecodable addresses.
|
|
Ghidra enumerates its ReferenceManager, excludes synthetic entry-point
|
|
references, and returns deterministic source-address ordering while retaining
|
|
distinct operand references from the same source. `[]` means that enumeration
|
|
found no references; omission or `null` means this facet is unavailable in an
|
|
older capture or another provider. Empty static references do not prove that
|
|
the address is unused at runtime. The existing `references` field still holds
|
|
outgoing instruction references, and `xrefs` still returns bare source addresses.
|
|
The Ghidra analysis profile versions this incoming-reference coverage. Reopen
|
|
the target without an older snapshot and save a fresh snapshot to acquire it;
|
|
the older snapshot's Evidence bundle remains readable with this facet unavailable.
|
|
|
|
Compact incoming, outgoing instruction and call-target references preserve the source
|
|
address, operand index, DATA/READ/WRITE classification, primary status, provider
|
|
`provenance`, and reference `source` (for Ghidra: `analysis`, `default`, `imported`
|
|
or `user_defined`). Primary status reports the provider's chosen reference; it
|
|
does not establish literal addressing. Analyzer-inferred indexed targets remain
|
|
static references, not observed runtime reads or writes. Older evidence may omit
|
|
the additive fields, and null or omitted facts mean unavailable rather than false.
|
|
|
|
Ghidra distinguishes a return using decoded instruction p-code's `RETURN`
|
|
operation. `flow.classification_evidence` preserves the Listing flow type and
|
|
the observed return evidence; mnemonic spelling alone is not the classifier.
|
|
Other terminal or trap flows retain the provider's classification, and this
|
|
inspection does not change function extents or prove a calling convention.
|
|
The analysis profile commits this decoder behavior. Snapshots from the earlier
|
|
profile produce a profile mismatch rather than replaying the former generic
|
|
terminal result. The old snapshot's `evidence_bundle` remains valid retained
|
|
Evidence: copy that object to a separate JSON file and use `rea evidence-import`
|
|
to validate it. Run analysis without the old snapshot and save a fresh one
|
|
under the current profile. Other providers and older captures may omit the
|
|
classification evidence field.
|
|
|
|
Call resolution reports direct, resolved indirect, ambiguous, unresolved and
|
|
non-call outcomes from static call references. It does not establish runtime
|
|
execution or classify Objective-C/Swift/vtable/closure mechanisms from names.
|
|
|
|
`batch_decompile` keeps each caller-selected symbol or address in `items[].address`.
|
|
The separate `procedure` field reports the canonical entry and the provider's
|
|
observed entry label. An interior address therefore remains visible without
|
|
being mistaken for the function entry. Identity failures are reported as
|
|
`procedure.status: "unknown"`; label failures leave a null name and `name_error`.
|
|
Neither failure discards usable pseudocode, and names are not parsed from it.
|
|
Resolved entries also appear in the composed Evidence locations. New batch
|
|
Evidence commits the revised workflow profile; retained historical Evidence
|
|
remains valid. This tool
|
|
has no dedicated CLI command; the existing `decompile` command remains the
|
|
single-procedure CLI operation.
|
|
|
|
Type inspection selects one exact database pathname or typed data address.
|
|
Struct/union fields, enums, pointers, arrays, typedefs, size, alignment, packing
|
|
and bitfields retain database authority. Child types use exact IDs for further
|
|
inspection, including recursive layouts. Source/debug authority and flexible
|
|
array semantics are not inferred. The real lane uses a native DWARF 4 object
|
|
with a struct containing a union, enum and recursive pointer; a stripped linked
|
|
binary may have no corresponding recovered layout.
|
|
|
|
Ghidra serializes at most 20,000 fields or enum members per inspected type.
|
|
Check `total_fields` and `truncated` before treating the returned layout as
|
|
complete. These fields describe omissions from the response, independently of
|
|
whether Ghidra recovered the original source layout.
|
|
|
|
### Hopper analysis behavior
|
|
|
|
Cursor navigation returns the observed object start when Hopper snaps an interior
|
|
address; adjacent-object navigation rejects unmapped inputs and document ends.
|
|
Native API text rejects NUL characters and unpaired Unicode surrogates before
|
|
annotation changes. Renames preserve unselected label owners; use a batch with
|
|
all affected addresses to move or swap existing labels explicitly. Every rename
|
|
destination must be mapped, and native symbol names must fit Hopper's 1024 UTF-16
|
|
code-unit limit. Oversized names fail before any batch edits; bookmarks and
|
|
literal string results are not subject to that symbol-name limit. Rename success
|
|
requires exact final readback. New bookmarks must point into mapped memory;
|
|
existing legacy bookmarks outside it can still be removed.
|
|
String results read each native typed object's complete bytes, retain the original
|
|
provider display in `provider_value`, and report its encoding, byte length, and
|
|
termination. `encoding_status: inferred` distinguishes REA's decoding from an
|
|
observed source encoding. Hopper can split long literals into adjacent
|
|
unterminated objects; search matches each object's decoded bytes independently.
|
|
Undecodable objects retain native display text with `decoding.available: false`
|
|
and a reason, so one uncertain object does not block unrelated inspections.
|
|
Function dossiers retain this same string evidence. Native call edges retain
|
|
Hopper's partial `CallReference` classification and exact endpoints; detailed
|
|
reference flags remain unavailable rather than being invented.
|
|
Regex searches use ECMAScript Unicode syntax in a cancellable worker with a
|
|
five-second matching deadline and a worker old-generation heap. Deadline or
|
|
cancellation stops matching while leaving the Hopper API available. This
|
|
release has no runtime override for that deadline. The worker requests 128
|
|
MiB. A positive Node.js `--max-old-space-size` on the process that starts
|
|
REA, including `NODE_OPTIONS`, overrides that request and sets the effective
|
|
heap. `--max-old-space-size-percentage` takes precedence over that flag in
|
|
either order. REA does not turn the percentage into a MiB, so the effective
|
|
heap stays unestablished instead of the 128 MiB request or the absolute flag.
|
|
The reported `NODE_OPTIONS` value is the process initial environment when that
|
|
block can be read. A later edit to the environment is not the value Node
|
|
applied, and an unreadable initial value leaves the effective heap
|
|
unestablished. The launch Node options are the initial command line on Linux
|
|
when that buffer still has a program entry, not a later edit to
|
|
`process.execArgv`. A preload can clear that array after V8 has applied the
|
|
flags. Setting `process.title` or `--title` overwrites the command line, so a
|
|
buffer without a program entry leaves the effective heap unestablished.
|
|
`--env-file` or `--env-file-if-exists` can supply `NODE_OPTIONS`
|
|
when the initial environment has none. That file is not part of the initial
|
|
environment block, so the effective heap stays unestablished instead of the
|
|
128 MiB request. An initial `NODE_OPTIONS` is still used, because Node does
|
|
not let the file replace it. A positive `--max-heap-size`, including the
|
|
underscore form and launch `NODE_OPTIONS` on Node versions that allow it,
|
|
sets the total heap rather than the old generation and can be lower than the
|
|
128 MiB request. The effective old generation stays unestablished while that
|
|
value is in effect. A zero or empty value clears it.
|
|
`--experimental-config-file` or `--experimental-default-config-file` can set
|
|
`nodeOptions`, including a heap percentage, from a file that is not read
|
|
back. The effective heap stays unestablished when either flag is present,
|
|
including when an absolute `--max-old-space-size` is also on the command
|
|
line or in the recovered `NODE_OPTIONS`. Node accepts the config path as
|
|
`=` or the next argument.
|
|
Literal mode retains Unicode casefold matching.
|
|
|
|
## Bounded static traces
|
|
|
|
`trace_native_ui_action` / `rea trace-native-ui-action <target> <seed>` accepts
|
|
one authored selector/object ID, native symbol or exact function address. It
|
|
joins UI wiring to uniquely matched class/selector implementations and follows
|
|
bounded typed call references. Direct references, resolved indirect references,
|
|
ambiguous candidates, inferred untyped provider callees and targetless call
|
|
sites remain distinct. A static route does not establish runtime reachability.
|
|
Swift/closure dispatch requires resolved static references; unavailable ABI
|
|
forms remain unknown.
|
|
|
|
Incomplete reference kinds do not discard known typed calls or partial
|
|
provider call references. A `provider_call` with `observed` resolution retains
|
|
the provider-reported call site and endpoint without claiming generic direct,
|
|
indirect or Objective-C dispatch semantics. Its `call_reference` evidence
|
|
preserves the original endpoints, classification, raw provider type and
|
|
provenance, even when the target node is a containing procedure. Hopper's
|
|
`none`, `unknown`, `direct` and `objective_c` labels stay provider-scoped;
|
|
unknown numeric types remain available in the raw type field. Target-only
|
|
callee fallback uses `provider_call` with `inferred` resolution.
|
|
|
|
Callee fallback adds inferred targets while each retained route keeps its
|
|
original provider and call site. If enumeration fails, known routes remain alongside an unresolved
|
|
failure; cancellation still terminates the request. A node-limit omission keeps
|
|
the route's known relation while marking its target unresolved. Distinct call
|
|
sites to the same omitted target retain separate evidence.
|
|
Reports sharing a call site and containing target procedure retain all their
|
|
original endpoint evidence on the shared graph edge. Different reported
|
|
relations retain separate edges, independent of enumeration order.
|
|
|
|
Session discovery describes two automatic source routes for this trace:
|
|
`authored_ui_dispatch` needs Interface Builder decoding and dispatch metadata;
|
|
`native_seed_call_trace` needs native address resolution, dispatch metadata and
|
|
a call reader. Metadata can come from direct inspection or `list_names`;
|
|
the call reader can supply typed `procedure_references` or inferred
|
|
`procedure_callees`. An authored UI-to-handler edge remains useful without
|
|
call expansion, and native-seed tracing can proceed without UI decoding.
|
|
These routes add no request option or default mode, so discovery omits
|
|
`default_mode_available`. Availability describes source prerequisites;
|
|
the result reports the query's actual matches, coverage and failures.
|
|
|
|
`trace_native_values` / `rea trace-native-values <binary> <procedure> --provider
|
|
ghidra` composes high-p-code def-use graphs across statically resolved calls.
|
|
Logical CALL arguments bind to recovered parameter ordinals; recovered RETURN
|
|
values bind to CALL outputs. These edges are decompiler-derived. Constants,
|
|
operators, LOAD/STORE and branch operands are included inline. Alias effects,
|
|
persistent state, RNG roles, missing/variadic bindings and ambiguous destinations
|
|
remain unknown. Budgets control depth, decompilations, call-site resolutions,
|
|
nodes and edges. Node payloads are bounded to 8 MiB and serialized results to
|
|
32 MiB. Pagination returns edges whose source is on the node page and preserves
|
|
stable IDs for endpoints outside that page.
|
|
|
|
Within one value trace, each exact procedure selector is attempted at most once,
|
|
including failed analyses. Failures remain unknown observations and do not
|
|
establish canonical function identity. A new trace can retry those selectors.
|
|
|
|
The raw Java `is_dead` flag can remain set after Ghidra decodes a live block;
|
|
`block_membership` reports actual syntax-tree membership separately.
|
|
|
|
The provider dossier itself retains up to 3,000 p-code operations, 64 inputs per
|
|
operation and 12,000 def-use edges, with explicit omitted counts. A `STORE`
|
|
may describe stack memory and does not itself prove persistent state.
|
|
|
|
Jump-table evidence keeps numeric `mappings`, explicit `default_targets`, and
|
|
backing `data_sources` separate. A null case value denotes an unresolved
|
|
case, never a known default. Ghidra pairs typed case/default tokens with the
|
|
recovered block entry and its unique indirect dispatch predecessor; it does
|
|
not zip unequal label and destination arrays. Shared case bodies retain each
|
|
label. Negative numeric tokens are checked against their encoded magnitude;
|
|
nonliteral, ambiguous, or nonexact JSON integer labels remain unknown.
|
|
Unknown-label diagnostics retain the token text, encoded unsigned magnitude,
|
|
target and dispatch so callers can inspect the original observations.
|
|
Decompiler load-table metadata supplies observed entry sizes and counts
|
|
without assigning every backing table to each mapping. These observations
|
|
describe the decompiler's recovered switch, not guaranteed original source.
|
|
Legacy records without `default_targets` normalize to an empty array and
|
|
retain their existing unresolved mappings.
|
|
|
|
AArch64 jump-table recovery additionally verifies byte and halfword relative
|
|
forms from unsigned bounds, register definitions, table loads, branch bases,
|
|
scaled ADD/BR instructions and the recovered target set. It reads exactly the
|
|
proven count and preserves unknowns for other forms. Real ELF and host ARM64
|
|
Mach-O fixtures check each case against source-owned return values.
|
|
|
|
## Static inspection
|
|
|
|
`inspect_plist.format` describes the selected source, not plutil's conversion
|
|
format. Sources explicitly identified as XML, binary or JSON retain those
|
|
labels. For generic text, a bounded source-prefix probe recognizes leading XML
|
|
markup, including UTF-8 and BOM-marked UTF-16 sources. Unidentified text and
|
|
OpenStep plists, including a data-only value such as `<deadbeef>`, report
|
|
`unknown` even when plutil decodes their values through an XML conversion.
|
|
|
|
Binary plist inspection reads the original bytes through an owned file descriptor,
|
|
so conversion cannot erase control characters, fractional dates, negative zero,
|
|
or the high word of a 128-bit integer. It accepts at most 64 MiB of input and
|
|
reserves an estimated 64 MiB decode budget before parsing; this estimate is not
|
|
a hard process-memory bound. A refused input reports non-retryable
|
|
`resource_constraint` with `reported_limits.maximum_input_bytes`,
|
|
`maximum_decoded_bytes` or, for nesting deeper than 128 levels,
|
|
`maximum_nesting_depth`. These are fixed allowances: worker limits and host
|
|
memory do not raise them, and the unchanged request fails the same way.
|
|
Binary-source provenance records the `file` classification without claiming a
|
|
`plutil` conversion.
|
|
|
|
A plist explicitly selected through the path input or as the active plist target
|
|
reports an unreadable or malformed selection as `invalid_request`. A missing or
|
|
unreadable default plist of another target reports `capability_unavailable`.
|
|
Permission failures remain `access_denied`; output and cleanup failures retain
|
|
their own error categories.
|
|
|
|
A conversion refusal includes plutil's diagnostic from either output stream.
|
|
`details.captured_output` retains bounded stdout/stderr excerpts, observed byte
|
|
totals and exit status. `truncated: true` means those excerpts are incomplete.
|
|
|
|
`inspect_macho.selected_architecture` identifies the exact selected lipo slice
|
|
and its `container_file_offset` in the input file. A thin Mach-O has a known
|
|
offset of zero; an unknown universal-slice offset stays null. The Evidence
|
|
subject format describes the input container: `mach-o-universal` for FAT files,
|
|
including those with one slice. The normalized result describes the selected
|
|
Mach-O image. Architecture
|
|
`file_offset` values are container-relative. Entrypoint, segment and section
|
|
`file_offset` values retain their source meaning and are relative to the
|
|
selected Mach-O slice. Raw load-command fields retain their original values.
|
|
A field otool prints more than once, such as each `LC_BUILD_VERSION` tool, is
|
|
keyed `name #n` in print order. Each `LC_LINKER_OPTION` string is keyed
|
|
`string #n` and checked against the declared `count`. Evidence locations map the LC_MAIN entrypoint and nonempty file-backed segment
|
|
ranges to container offsets by adding the observed slice offset. Zero-fill
|
|
segments do not produce file ranges. When the selected slice's container offset
|
|
is unknown, those mapped locations are omitted with an explicit limitation;
|
|
architecture inventory locations remain available.
|
|
|
|
Hopper's `address_to_file_offset` combines the native image-relative mapping with
|
|
original-file coordinates only after checking the source against the session's
|
|
SHA-256. FAT tables and embedded Mach-O headers come from that same hashing pass.
|
|
A changed or nonregular source reports `artifact_changed`; an unreadable or
|
|
missing source reports its access or filesystem failure. Those errors retain the native offset,
|
|
loaded header and provider source path in `details.partial_observation`, with the
|
|
original-file coordinate explicitly unavailable. Restore the original bytes or
|
|
reopen the changed executable to obtain verified coordinates. Snapshot replay
|
|
remains historical evidence for its recorded artifact identity.
|
|
|
|
A selected Hopper database's digest identifies the database rather than its
|
|
original executable. Original-file mapping is unavailable for that selection;
|
|
native mapping facts remain in partial evidence, and `read_bytes` still reads
|
|
the loaded database. Open the original executable to verify source coordinates.
|
|
|
|
Native Xcode command execution uses shared process supervision with a 60-second
|
|
deadline and a 64 MiB aggregate stdout/stderr budget. Timeout, cancellation,
|
|
stream failure, and output exhaustion retain the captured output, exit details,
|
|
and cleanup outcome in the error result. Truncated output is never parsed as a
|
|
complete observation.
|
|
|
|
- `trace_dylib_resolution` / `rea trace-dylib-resolution <app-or-mach-o>`
|
|
parses Mach-O load commands in TypeScript and follows dyld's path expansion
|
|
for every executable in an app bundle, or for one Mach-O within its directory.
|
|
It reports each `@rpath`, `@loader_path` and `@executable_path` candidate with
|
|
its outcome. Paths outside the analyzed root, including shared-cache system
|
|
libraries, stay undetermined. See
|
|
[Apple application analysis](apple-application-analysis.md#dylib-load-resolution).
|
|
- `inspect_asset_catalog` / `rea inspect-asset-catalog <app>` reads compiled
|
|
`Assets.car` metadata through macOS `assetutil --info`. Catalog digests, raw
|
|
rendition fields, pagination and exact UI resource-name matches are returned.
|
|
Image extraction and undocumented rendition interpretation are unsupported.
|
|
- `inspect_keyed_archive` / `rea inspect-keyed-archive <plist>` reads XML or
|
|
binary Foundation keyed archives without instantiating classes. For an active
|
|
app, provide its relative archive path as the second CLI argument or MCP
|
|
`path`. Results retain original `$objects` indices, named `$top` roots,
|
|
shared/cyclic references, class descriptors, raw serialized fields and
|
|
malformed/unresolved/nil references. Missing fields stay absent; unknown
|
|
classes are preserved. Select `root`, `offset` and `limit` explicitly.
|
|
Object `kind` describes the decoded archive value: data, dates and numbers
|
|
remain `scalar` when their JSON value uses a `$plist_type` wrapper. An actual
|
|
dictionary with the same fields remains `dictionary`.
|
|
XML must be well-formed. Data literals use
|
|
[CoreFoundation's raw ASCII quartet rules](https://github.com/swiftlang/swift-corelibs-foundation/blob/384ff680be7c7445fee6ce0c55e9ae11a46e07c5/Sources/CoreFoundation/CFPropertyList.c#L1588):
|
|
non-alphabet ASCII characters are ignored and an incomplete final quartet
|
|
emits no bytes. Character references retain their source spelling for data
|
|
decoding. Non-ASCII content, child markup and self-closing `<data/>` are format
|
|
failures; explicit `<data></data>` represents empty data.
|
|
UID 0 cannot distinguish conditional nil from ordinary nil. Limits are
|
|
64 MiB of input, 200,000 objects/references and 128 nesting levels.
|
|
XML dictionary entries decoded as `__proto__` are omitted with an explicit
|
|
count because REA results cannot represent that key. XML tag whitespace and
|
|
comments do not change that handling. XML key names combine their text and
|
|
CDATA segments without trimming or expanding literal CDATA entities. Other
|
|
values and the original archive digest are preserved.
|
|
- `decode_interface_builder` / `rea decode-interface-builder <app>` reads
|
|
compiled storyboard and nib resources into objects, outlets, actions,
|
|
connections and controller/class names. A full app graph is often several
|
|
megabytes (XLD.app: 6.7 MB, about 2M tokens, which also exceeds the default
|
|
MCP response budget), so MCP callers should pass `detail: "summary"`. The
|
|
summary keeps every decoded document and node/edge/coverage counts and
|
|
retains the complete Evidence; page rows with `inspect_analysis_view`
|
|
`view: {kind: "interface-builder", collection: "nodes", document:
|
|
"Contents/Resources/Base.lproj/MainMenu.nib", offset: 0, limit: 64}`.
|
|
`document` selects rows whose evidence names that exact
|
|
`documents[].relative_path` (nodes and edges only); `source_truncated` keeps
|
|
graph incompleteness visible on every page. `verify:interface-builder` compiles
|
|
a source-owned AppKit XIB with `ibtool`. Storyboards require an installed
|
|
iOS platform; unsupported archive forms remain explicit.
|
|
XML plist archives accept UTF-8 and BOM-marked UTF-16 in either byte order.
|
|
App bundle `Info.plist` executable resolution uses the same decoder as keyed
|
|
archives and Interface Builder. The decoder consumes an initial
|
|
BOM and rejects malformed byte sequences, unsupported encoding declarations,
|
|
and declarations that disagree with the detected encoding. It never substitutes
|
|
UTF-8 for a declared encoding it cannot process. Archive and evidence digests
|
|
still identify the original serialized bytes. Bundle resolution reports
|
|
malformed bytes and unsupported encodings against the original plist path
|
|
before looking up the executable. Interface Builder decoding
|
|
limits aggregate input archives to 32 MiB.
|
|
Before decoding, it also bounds the serialized structure: expanded binary
|
|
plist references, NIB table records, XML elements and text, plus their
|
|
projected representations must fit the 256 MiB aggregate decode budget.
|
|
NIB string values, data, property names and class names are charged at each
|
|
projected JSON occurrence, including when the archive reuses one source
|
|
value. Binary plist data projection also accounts for the temporary byte
|
|
copy made by the pinned decoder projection. These are format-derived
|
|
representation reservations, not exact heap measurements or a process-wide
|
|
heap ceiling.
|
|
Archives beyond either aggregate budget are omitted with their paths and an
|
|
explicitly partial `archive_decode` facet; previously decoded documents and
|
|
their identities remain available.
|
|
- `inspect_native_dispatch_metadata` /
|
|
`rea inspect-native-dispatch-metadata <app-or-binary>` prefers a validated macOS Mach-O byte reader. It decodes
|
|
64-bit little-endian Objective-C class/metaclass records, superclass pointers,
|
|
absolute/relative method entries, ivar offsets/sizes/alignment and protocol
|
|
declarations. It also decodes simple Swift conformances, static synchronous
|
|
witness slots, signed-relative pointers and non-generic, non-resilient class
|
|
vtable descriptors. Records include exact virtual addresses/file offsets and
|
|
artifact evidence. Vtable indexes are metadata word offsets; witness indexes
|
|
start after the conformance header. Names unavailable in metadata remain null.
|
|
The `swift_class_vtable_descriptors` coverage counters count examined vtable
|
|
slots and successfully decoded slots, rather than type-section entries.
|
|
The `objc_class_method_ivar_metadata` counters count admitted class and
|
|
metaclass objects and successfully decoded objects.
|
|
Pointers are decoded through the image's fixups:
|
|
- `LC_DYLD_CHAINED_FIXUPS` rebases and binds, including the authenticated
|
|
arm64e formats;
|
|
- legacy `LC_DYLD_INFO` rebase and bind opcodes.
|
|
|
|
A Swift conformance whose type or protocol reference is an external bind
|
|
keeps its name as `null` and carries the raw dyld identity in
|
|
`type_binding` or `protocol_binding`; that record and its facet coverage are
|
|
partial. Consumers of this output must handle nullable conformance names
|
|
and use the typed binding fields instead of substituting a symbol as a
|
|
display name. Indirect relative-pointer records also retain their binding
|
|
identity. A decoded rebase to virtual address zero remains an address; only
|
|
an unfixed zero in a nullable ABI field is treated as null. Incomplete or
|
|
conflicting fixup streams keep affected pointers unresolved; coverage reports
|
|
parsing and resource limits while retaining validated prefix facts.
|
|
Conformance records preserve the raw three-bit `type_reference_kind`. Kinds
|
|
0 and 1 identify direct and indirect nominal descriptors; kind 2 preserves
|
|
the inline Objective-C class name and its source location. Kind 3 preserves
|
|
an imported class binding or the local class-object address when available,
|
|
but class metadata/name recovery requiring the Objective-C runtime remains
|
|
partial. Unknown kinds retain the raw kind and reference location without
|
|
being treated as absent or assigned a guessed type name.
|
|
|
|
So an external superclass resolves from its `_OBJC_CLASS_$_` bind (for
|
|
example `NSObject`). The `pointer_fixups` coverage facet names the mechanism.
|
|
Class properties preserve each encoded attribute as a raw `{name, value}`
|
|
pair, for example `{ name: "N", value: "" }`, and report atomicity on the
|
|
property: `N` means `nonatomic`; a complete typed attribute string without
|
|
`N` means the Objective-C default `atomic`;
|
|
missing or incomplete attribute metadata leaves `atomicity` and any
|
|
unestablished read-only value null. A type attribute whose quoted class name
|
|
never closes is incomplete and marks `objc_properties_categories` partial
|
|
with `property_attributes_incomplete`. A property record that cannot be
|
|
decoded, such as one with an empty or unmapped name, is reported as
|
|
`property_decode_failed` at its entry address while sibling properties are
|
|
retained. `is_meta_class`, `is_root_class`, and
|
|
`ivar_count` are also nullable when the selected provider exposes only symbol
|
|
names. Class implementation methods have null `is_required` and `is_optional`
|
|
fields because those facts apply to protocol declarations; decoded protocol
|
|
method lists retain their observed required/optional values. Class properties
|
|
and `__objc_catlist` categories are also decoded. Categories record the
|
|
extended class, local or external, plus
|
|
their methods, protocols and properties; category methods appear as
|
|
implementations with a `category`. Swift field-offset globals that are only
|
|
initialized at runtime stay unresolved. Generic, resilient, async and
|
|
coroutine tables, and inherited overrides, have explicit unsupported or
|
|
partial coverage. Other providers retain the
|
|
existing symbol-based inventory with its narrower coverage.
|
|
Symbols without a reported location remain in that inventory: Swift symbol
|
|
`address` and Objective-C `implementation_address` are null. An empty provider
|
|
address is retained as `reported_address` in the symbol's evidence, whose
|
|
location also remains unknown. Nonempty provider address syntax is preserved.
|
|
|
|
Session discovery reports this workflow available when the bound provider
|
|
offers direct metadata inspection or a `list_names` symbol inventory. The
|
|
workflow tries direct metadata first and uses the symbol fallback only when
|
|
that operation is unavailable; runtime failures remain failures rather than
|
|
being replaced by symbol-only results.
|
|
|
|
`get_objc_classes` keeps names from the provider inventory. It coalesces rows
|
|
only when their exact selected-image pointer slots decode to the same class
|
|
import, including its binding stream, raw library ordinal, resolved library, weak status, and addend. A literal filter is
|
|
applied to the original names before that join; if the filter selects only an
|
|
alias, the output keeps that exact row. `identity_coverage` distinguishes a
|
|
complete selected-inventory scan from partial, unsupported, and unknown cases.
|
|
Recognized Mach-O formats outside the native metadata reader's support retain
|
|
the provider's class labels with unknown import identity; malformed headers and
|
|
read failures remain errors.
|
|
The join also requires matching whole-artifact digests and matching provider-
|
|
owned selected-image facts: container, byte range, full raw CPU type/subtype,
|
|
and FAT table digest when present. A missing or overridden loader selection
|
|
remains unknown; the result describes agreement on the selected artifact image,
|
|
not an atomic observation of a running process or whole-program class coverage.
|
|
|
|
## Native desktop observation
|
|
|
|
`observe_native_ui` captures one explicitly selected existing PID/window ID.
|
|
Screenshots use a selected-window ScreenCaptureKit
|
|
filter on macOS 14+; accessibility reads stay within that window. Missing Screen
|
|
Recording/Accessibility permissions produce actionable errors without broad
|
|
capture or automatic permission prompts. Executable bytes and process launch
|
|
time guard against a different target or PID reuse. AX selection requires one
|
|
unique geometry match; ambiguity fails closed.
|
|
|
|
`capture_native_ui_scenario` takes AX child-index paths for press, increment/
|
|
decrement scrolling and text-value entry, or bounded waits. No global event
|
|
injection is used. Unsupported AX actions fail explicitly. The result preserves
|
|
ordered before/after captures and gaps; an action may have occurred before a
|
|
post-action capture fails. Application state is left as-is; REA does not attempt
|
|
to restore it. Failed or cancelled scenarios return a typed error with the
|
|
collected report in `partial_observation`, including ordered step outcomes.
|
|
Cancellation preserves the caller's reason and remains observable through helper
|
|
cleanup. A later abort does not replace an earlier helper or output failure.
|
|
Cleanup failures retain their runtime owner until a confirmed cleanup retry.
|
|
|
|
Scenarios accept caller-selected action lists and accessibility node counts.
|
|
One wait step stays capped at 180000 ms. That cap has no runtime override.
|
|
The operation deadline is `native_ui_deadline_ms` (omitted means 180000 ms)
|
|
and does not raise the wait-step cap. The complete scenario result has a
|
|
64 MiB output budget. Screenshots are scaled to
|
|
at most 2,048 pixels and captured only for the selected window. REA compiles one
|
|
owned helper per scenario, removes its temporary compiler cache and stops its
|
|
helper on cancellation. It does not launch or own the selected application. UI
|
|
actions may change application data or trigger network activity.
|
|
|
|
## Native call observation
|
|
|
|
`observe_native_calls` / `rea observe-native-calls <app-or-mach-o> <input-json>`
|
|
launches the active Mach-O as a new, owned process under LLDB. It stops at
|
|
caller-selected entries, records them and continues:
|
|
|
|
- functions by exact symbol name, optionally limited to one image;
|
|
- Objective-C methods by selector, optionally limited to one class and to
|
|
instance or class methods.
|
|
|
|
```sh
|
|
rea observe-native-calls /Applications/Example.app '{"breakpoints":[{"kind":"objc-method","class_name":"NSURLSession","selector":"dataTaskWithRequest:completionHandler:"},{"kind":"function","name":"open","module":"libsystem_kernel.dylib"}],"duration_ms":5000,"backtrace_frames":4}' --json
|
|
```
|
|
|
|
Each event is an observation: thread, image, symbol, load and file address,
|
|
the raw integer argument registers, optional caller frames and, for an
|
|
Objective-C method, the selector read from `_cmd` and the receiver's dynamic
|
|
class. Breakpoints stop at the symbol itself, so the registers hold the ABI
|
|
arguments. The receiver class comes from LLDB's Objective-C runtime reader
|
|
without running target code; no expression is evaluated, and REA never
|
|
attaches to a process it did not launch.
|
|
|
|
The run ends when the process exits, `max_events` entries are recorded,
|
|
`duration_ms` elapses, or the aggregate trace resource budget is exhausted.
|
|
Bounded termination reports partial coverage. REA checks process termination
|
|
using a launch-time OS start identity; if ownership or cleanup cannot be
|
|
verified, the result or error preserves that uncertainty. The result reports:
|
|
Launch errors keep the fact that LLDB created a process separate from whether
|
|
it remains alive. An exited `SBProcess` is reported as exited only after LLDB
|
|
confirms that state; unresolved ownership remains retained for cleanup retry.
|
|
Cancellation observed while output and Evidence are finalized returns partial
|
|
cancellation Evidence, even if the target has already exited.
|
|
|
|
- the outcome, exit status and captured stdout/stderr (1 MiB kept per stream
|
|
while excess bytes are drained and counted without growing capture files).
|
|
Each stream reports `complete`; when draining could not finish, its byte
|
|
count is an observed lower bound and truncation is explicit;
|
|
- caller-selected arguments, working directory, and environment overrides;
|
|
- `selected_file_sha256` and LLDB module identity observations. These checks do
|
|
not pin the executable against concurrent mutation; `loaded_image_sha256`
|
|
remains unknown;
|
|
- signal or exception stops;
|
|
- every breakpoint's resolved locations; a breakpoint that matched no loaded
|
|
code makes coverage partial;
|
|
- limitations.
|
|
|
|
The bridge limits retained event JSON to 8 MiB and caller frames to 65,536
|
|
across the run. It checks admission before retaining another complete event;
|
|
`resource-limit` reports which observations could not be completed without
|
|
silently changing the requested event or frame settings.
|
|
Resolved breakpoint locations have a separate aggregate 8 MiB metadata budget.
|
|
If their details exceed it, `location_count` retains the full observed match
|
|
count, coverage is partial, and `breakpoint_locations_truncated` is true. This
|
|
post-run metadata limit does not imply that the target was killed.
|
|
|
|
Accepted entries and signal stops are also flushed to a bounded 8 MiB journal.
|
|
If cancellation, timeout, capture failure, or a later bridge error prevents a
|
|
full result, the error retains this evidence in `details.partial_observation`
|
|
with partial coverage. Available output prefixes remain inline; their byte
|
|
counts are observed lower bounds when final drain counters are unavailable.
|
|
The original failure classification and cleanup uncertainty remain separate.
|
|
An interrupted journal row is ignored with an explicit limitation.
|
|
If the trace completed before cancellation or runtime-directory cleanup failed,
|
|
the retained observation also keeps its exit status, termination proof, launch
|
|
identity, breakpoint results and final stream counters. Those fields remain
|
|
absent when the interrupted producer did not report them; partial coverage alone
|
|
does not mean that every retained fact is incomplete.
|
|
|
|
Only entries are observed: return values, floating-point and stack arguments,
|
|
and inlined or `objc_direct` calls are not. The target runs with the current
|
|
user's permissions and may change files, show UI or use the network.
|
|
|
|
Debugging must be allowed for the user (Developer Tools access). A
|
|
hardened-runtime target without `com.apple.security.get-task-allow` cannot be
|
|
debugged; `inspect_signature` reports the signature's `hardened_runtime` value
|
|
and its `entitlements`, including `com.apple.security.get-task-allow` when it
|
|
is present. A denied trace reports the `debugger-attach-denied` reason.
|
|
|
|
## Provider and verification boundaries
|
|
|
|
Install a Ghidra 12.1.x release and the 64-bit full JDK it declares, then configure REA to
|
|
use them. Ghidra analysis supports Linux x64/arm64 and macOS x64/arm64; each installation requires
|
|
the matching native decompiler. Experimental Windows x64 P0 admits native
|
|
x86, x86-64, ARMNT (Thumb-2) and ARM64 PE applications and DLLs, plus explicitly selected
|
|
[raw NMOS 6502 images](raw-binary.md#nmos-6502) with a caller-supplied load address,
|
|
on fixed local NTFS volumes. Other raw CPU families remain unsupported on Windows.
|
|
The boundary uses bundled Job Object ownership, protected runtime DACLs, and
|
|
handle-based path admission. See [Windows Ghidra P0](windows-ghidra-p0.md) and
|
|
[issue #527](https://github.com/morluto/rea/issues/527).
|
|
On Linux and macOS, `annotate_native_function` atomically edits a function name
|
|
and entry comments in the ephemeral database, returning refreshed analysis
|
|
without changing executable bytes. Annotation text must contain no NUL or
|
|
unpaired Unicode surrogate; a rejection identifies the field and UTF-16 index
|
|
and leaves every annotation unchanged. CRLF, supplementary Unicode characters,
|
|
and combining characters are preserved. Windows P0 remains read-only. Ghidra has no
|
|
GUI authority, and REA never falls back automatically to Hopper.
|
|
`set_address_name` and `set_addresses_names` give Ghidra sessions the same
|
|
compact naming results as Hopper: a local function entry renames that function,
|
|
and any other mapped address, such as a global, receives a new or renamed primary
|
|
label. A batch applies each address separately and reports one success boolean
|
|
per address. Use them to name many globals and helpers cheaply; use
|
|
`annotate_native_function` when you also want comments and a refreshed dossier.
|
|
Ghidra name edits preserve the existing namespace. Supply either a leaf name
|
|
such as `renamed` or a fully qualified name in that namespace, such as
|
|
`alpha::renamed`. The returned qualified name can be reused as an idempotent
|
|
rename input. Edits retain the existing namespace; other namespace-like text
|
|
remains literal leaf-name text. A qualified name with an empty leaf is rejected
|
|
before any comment or name is changed.
|
|
|
|
- `npm run verify:ghidra`: host-native debug/stripped targets, native type layout,
|
|
instruction/call facts, value dependencies and process/project cleanup.
|
|
- `npm run verify:ghidra:aarch64-jump-table`: optimized ELF and byte/halfword
|
|
relative tables, plus ARM64 Mach-O on an ARM64 macOS host.
|
|
- `npm run verify:apple-dispatch`: source-built Objective-C protocols, classes,
|
|
properties and categories, and Swift conformances and vtables. Each is linked
|
|
with legacy `LC_DYLD_INFO` and with chained fixups (and as arm64e on Apple
|
|
silicon), then repeated after stripping local symbols.
|
|
- `npm run verify:native-ui`: one source-owned fixture window, successful
|
|
selected-window capture and actions, changed-target rejection, and cleanup.
|
|
Missing OS permissions fail this lane.
|
|
- `npm run verify:native-ui:permissions`: permits a permission-denial result and
|
|
reports `positive_e2e: false` when capture is denied. That result verifies the
|
|
OS permission boundary, not successful UI capture or actions.
|
|
|
|
macOS ARM64 is the real host verified during this implementation. Admission of
|
|
macOS Intel does not claim an Intel verification run. Unsupported metadata and
|
|
unresolved runtime/value semantics remain visible in results.
|
|
|
|
## Interface Builder hierarchy coverage
|
|
|
|
A document's `hierarchy_complete` flag follows its own hierarchy coverage.
|
|
If graph limits omit a hierarchy source or destination, or prevent a hierarchy
|
|
edge or traversal from being retained, that document reports partial hierarchy
|
|
coverage. Retained unresolved edges remain in the graph and do not add to the
|
|
omitted-edge count. Coverage is evaluated separately for each document.
|
|
|
|
A keyed-archive hierarchy UID without an object-table entry marks the hierarchy
|
|
as partial while preserving decoded objects and known links. UID 0 remains
|
|
archived nil and does not count as a missing reference. Archive and evidence
|
|
digests identify the original serialized bytes.
|
|
|
|
## Keyed archive integer precision
|
|
|
|
The archive reader observes XML number element types and binary number markers.
|
|
XML values and their number facts are interpreted together from the validated
|
|
DOM and its original scalar source spans. Hexadecimal `<integer>` text, including
|
|
an optional sign, retains its value;
|
|
`<real>` text and CDATA segments are combined without inferring text from nested
|
|
elements. Real character references expand outside CDATA; integer and date
|
|
literals use raw source text.
|
|
Integers beyond the exact range of a JSON number are reported with the original
|
|
decimal text as `{ "$plist_type": "integer", "decimal": "<exact digits>" }` when
|
|
that source value can be associated unambiguously. Integral real values remain
|
|
numbers. Colliding integer/real values, unclassified numbers, and malformed UID
|
|
markers remain decoded with an explicit precision limitation. The limitation
|
|
also counts observed unsafe integer literals, including observations outside the
|
|
selected object page. Incomplete supplemental metadata produces an explicit
|
|
precision note without rejecting an archive accepted by the byte decoder.
|
|
Malformed XML scalar content becomes an explicit unknown rather than a prefix
|
|
number, guessed timestamp, or tag-only boolean. These unknowns do not disable
|
|
exact association for unrelated observed numbers. Nonnumeric
|
|
`UID` and `CF$UID` dictionary values remain ordinary data and are projected
|
|
recursively; numeric reference markers retain their original representation.
|
|
|
|
Value projection preserves serialized node kinds, IDs, references and pagination.
|
|
Exact and ambiguous value counts describe emitted root/object value instances;
|
|
a root and an object may contain the same value. Archive digests identify the
|
|
original bytes.
|
|
|
|
Interface Builder's XML and binary plist archives use the same number metadata
|
|
and exact-decimal representation for emitted node attributes. Projection follows
|
|
object and connection classification, so archive IDs, UID links and graph counts
|
|
keep their meaning. Precision limitations name the source document. Unsafe
|
|
integer-literal counts cover the archive, including fields not emitted as node
|
|
attributes; exact and ambiguous projection counts cover emitted attribute values.
|
|
Integral reals stay numbers, and ambiguous or incompletely observed values retain
|
|
explicit precision limitations. Before plist decoding, the existing aggregate
|
|
decode budget reserves estimated room for number metadata and attribute
|
|
projection; these estimates are not a hard process-memory bound. Native
|
|
NIBArchive decoding is unchanged by this plist-specific representation.
|
|
|
|
Dates are reported as `{ "$plist_type": "date", "iso8601": "<timestamp>" }`.
|
|
XML dates require a complete UTC spelling with fixed two-digit month, day and
|
|
time fields; short, negative and extended years are interpreted as UTC year
|
|
fields within JavaScript's date range. Unsupported calendar fields (including
|
|
leap seconds) and out-of-range dates remain explicit unknowns. REA does not
|
|
emulate CoreFoundation's normalization of overflowing calendar fields. Invalid
|
|
dates returned by other decoders retain `{ "$plist_type": "date", "iso8601": null }`
|
|
with a counted limitation. `inspect_plist` and Interface Builder decoding use
|
|
the same projection. Binary plist decoding refuses a reachable date whose marker
|
|
is not `0x33`; date and numeric payloads must end before the offset table.
|
|
Valid overlapping object records retain their independent source values. A valid `0x33` date is
|
|
reported with an ISO-8601 timestamp only when its float64 seconds are exactly
|
|
the canonical encoding of that millisecond instant. Any other finite binary
|
|
date uses `{ "$plist_type": "date", "iso8601": null }` and the counted
|
|
limitation; the archive bytes remain the source of the instant.
|
|
|
|
A negative-zero real is reported as `{ "$plist_type": "real", "value": "-0" }`
|
|
because a JSON number cannot carry the sign of zero. `inspect_plist` and
|
|
`inspect_keyed_archive` count these values in a limitation, and Interface
|
|
Builder node attributes use the same value. Positive zero and integer zero,
|
|
including an `<integer>-0</integer>` literal, remain the number `0`.
|
|
|
|
Known non-finite reals are reported as
|
|
`{ "$plist_type": "real", "value": "nan" | "+infinity" | "-infinity" }`.
|
|
These are canonical numeric labels, not a claim about original source spelling.
|
|
Binary IEEE reals retain NaN versus signed infinity; NaN payload bits and sign
|
|
are not represented in the JSON value, and the original bytes remain the source
|
|
for those details. XML CFPropertyList literals (`nan`, `+infinity`, `-infinity`,
|
|
`infinity`, `inf`, `+inf`, `-inf`, case-insensitively) decode to the same known
|
|
values. Case and aliases normalize; original archive bytes retain source text.
|
|
|
|
XML scalars whose identity cannot be established use
|
|
`{ "$plist_type": "unknown", "element": "integer" | "real" | "date" | "true" | "false", "literal": "<original content>", "reason": "invalid-literal" | "unsupported-markup" | "unrepresentable" }`.
|
|
A counted limitation accompanies these wrappers. Literal content retains its
|
|
original entity spelling, markup and line endings. Whole XML documents must
|
|
still pass strict well-formedness checks. Empty integer, real and date elements
|
|
are unknown scalars; boolean elements must be empty. Complete decimal/exponent
|
|
reals and signed decimal/hexadecimal integers accept their respective native
|
|
leading whitespace but reject trailing text. Arbitrary-precision integers
|
|
remain an intentional REA extension beyond CoreFoundation's integer range.
|
|
Finite real spellings that overflow the numeric representation remain unknown,
|
|
rather than becoming observed infinities. Known non-finite and unresolved scalar
|
|
values have separate counts. This replaces the former prefix-number and
|
|
incomplete-metadata fallback for malformed XML numeric content. Native
|
|
`inspect_plist` uses the same projection for plutil's XML conversion.
|
|
|
|
## Locate a byte signature
|
|
|
|
Use `search_bytes` after opening a target with Ghidra to locate a known constant
|
|
or instruction sequence. For example, `{"pattern":"48 8B ?? ?? 89","limit":100}`
|
|
returns ordered addresses with segments, source file offsets and containing
|
|
procedures. The CLI equivalent is
|
|
`rea search-bytes ./program '48 8B ?? ?? 89' --provider ghidra`.
|
|
Use `start`/`end` to constrain the inclusive address window, and continue with
|
|
`next_cursor` until `complete` is true. Inspect `searched` for page coverage and
|
|
skipped uninitialized blocks. A missing match does not establish absence from
|
|
uninitialized memory or across block boundaries. See
|
|
[Ghidra byte search](ghidra-provider.md#masked-byte-search) for mask and coverage
|
|
semantics. This query never executes or modifies target bytes.
|