Files
reverse-engineering-anything/docs/apple-ips.md
781760a195 feat(apple): inspect IPS crash reports (#6511)
* feat(apple): inspect IPS crash reports

Add inspect_apple_ips for caller-selected Apple .ips files. Parse the
first-line metadata object and, when bug_type is the string 309, the
crash body: process identity, exception and termination facts, threads,
usedImages, lossless integer registers, and documented classification
rules. Other bug types including 288 stackshots return no crash result.
Opened Mach-O and dSYM UUID joins remain unobserved.

Related to #4297

* fix(apple): keep IPS register literals exact

Use the arm64e fixture integer that matches 0x8527000193ed4944 instead
of an IEEE-754-rounded decimal, and count the lossless JSON example
length from the source text.

* fix(apple): budget IPS parsing and keep typed presence

Parse the IPS metadata and crash body through a projection of the
documented members. Members the report does not interpret are
syntax-checked without being materialized, and thread, frame, image and
the new max_registers budgets run before each row is parsed, so a large
selected file fails with a typed resource error instead of first
expanding every row. Fixed allowances bound JSON nesting, unlisted key
names and termination reasons.

Present values whose JSON type differs from the documented one are no
longer read as absence: they are listed in coverage.type_mismatches and
coverage becomes partial, including wrong-type or missing threads and
usedImages collections. osVersion keeps the producer dictionary or the
string form. Frame roles follow physical frame depth, so the top frame
of a non-faulting thread is current_instruction and inlined rows share
the following frame's role. SIGKILL classes compare the canonical hex
once, so 0xdeadd0cc no longer matches dead10cc. Invalid UTF-8 is refused
instead of being replaced, and JSON strings are decoded by copying
unescaped spans.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ra7LM2NyQ7DumdssxbUq5g

* fix(apple): bound integer text expansion and reconcile coverage

* refactor(apple): retain image metadata at its owning row

* refactor(apple): validate row capacity through reconciled coverage

* test(apple): verify resource recovery at the public error boundary

* docs(apple): record complete IPS task and corpus measurements

* docs(apple): record integrated checks and stdio corpus evidence

* docs(apple): record PE integration and baseline cleanup controls

* fix(native): bound IPS string decoding and preserve invalid identities

* refactor(native): use exact optional IPS source evidence

* docs(native): verify IPS escape capacity and complete-task costs

* test(native): verify IPS through production stdio MCP

* style(tests): format IPS advertised schema bindings

* fix(native): preserve unresolved IPS references and recorded frame semantics

* docs(native): record IPS reference review evidence

* fix(apple-ips): enforce JSON and report consistency

* docs(apple-ips): record final consistency verification

---------

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
2026-10-24 16:02:59 +08:00

106 lines
5.6 KiB
Markdown

# Apple IPS crash reports
`inspect_apple_ips` / `inspect-apple-ips` answers whether one selected local
Apple `.ips` file is a crash report (`bug_type` string `"309"`), which process,
exception, and termination facts that report records, which threads and
`usedImages` rows it lists, and which documented crash classes its strings
match. It reads the file without executing a target and does not require Xcode,
atos, a disassembler, or pwntools.
```bash
rea inspect-apple-ips /absolute/path/to/report.ips --json
```
The CLI resolves relative paths against the caller's working directory. MCP
requires an absolute filesystem path and preserves that exact selection:
```json
{
"name": "inspect_apple_ips",
"arguments": { "path": "/artifacts/report.ips" }
}
```
## Observations
The inline Evidence result contains the artifact path, SHA-256, and byte length.
`metadata` is the first-line JSON object. `bug_type` stays a string. The crash
body is parsed only when that string is `"309"`. Other types, including `"288"`
stackshots, return no crash result.
`process`, `exception`, `termination`, `faulting_thread`, `threads`, and
`images` reproduce documented body fields when present. Integers keep their
decimal literals and a hexadecimal form, including arm64e registers above
2^53. Frame roles follow the recorded frame index: row zero of the resolved
faulting thread is `faulting_instruction`, row zero of other threads is
`current_instruction`, and later rows are `return_address`. Each runtime
address uses that row's own image base and offset. The recorded `inline` flag
does not supply a missing offset or authorize borrowing another row's address.
When `faultingThread` is missing or does not resolve to a recorded thread
object, row-zero roles are `unknown`; the recorded lookup remains available.
`process.os_version` keeps the producer's dictionary (`train`, `build`,
`release_type`, `is_embedded`) or the older string form. Image UUIDs are uppercase 8-4-4-4-12; an all-zero UUID
or a missing `arch` is `uuid_unknown`. Image `source` letters keep their
documented meaning (`P` process, `S` shared cache, `C` shared cache library,
`K` kernel, `U` kernel cache, `T` kernel text exec, `A` absolute). Keys the
documentation does not list remain in `unlisted_keys` or `unlisted_body_keys`.
Each frame's `image_index` refers to its entry in `images`. Image architecture,
UUID and `uuid_unknown` are retained once on that image. Resolve the reference
when using those facts; an absent or out-of-range index leaves the image
unknown. This avoids repeating image text for every stack frame.
A present value whose JSON type differs from the documented one is never read
as absent: it is listed in `coverage.type_mismatches` with its path, the
expected type, and the observed type, and `coverage.status` becomes
`partial`. A crash body without `threads` or `usedImages` is also `partial`.
Malformed image UUID strings likewise make coverage partial; their exact spelling
is retained in the mismatch's `recorded_value`, while the canonical image UUID
remains unknown. Both 32 hexadecimal digits and canonical hyphenated UUIDs are accepted.
Unresolved thread/image references also make coverage partial. A rejected
integer lookup keeps its exact decimal literal in `recorded_integer`, including
negative values and values above uint32. Valid uint32 lookup values remain in
`faulting_thread` or `image_index` even when the referenced row is unavailable.
`classification` matches only documented SIGKILL codes (compared by their
canonical hexadecimal form), `EXC_GUARD` subtypes,
pointer-authentication suffixes, and dyld missing-library strings. A report
that matches none of those rules is `unclassified`.
Opened Mach-O and dSYM UUID joins remain unobserved. Symbol names printed
inside the report are recorded claims and are not verified. Linux ELF cores
use the separate [recorded crash](recorded-crashes.md) workflow.
## Limits and failures
The selected file is limited to 1 MiB by default (16 MiB maximum). At most 256
threads, 8192 frames, 1024 `usedImages` rows, and 16384 `threadState`
register slots are examined by default (4096 / 65536 / 4096 / 262144
maximum). Budgets are checked before each row is parsed, and body members the
report does not interpret are syntax-checked without being kept. Fixed
allowances bound JSON nesting (64 levels), unlisted key names per object
(1024), and `termination.reasons` entries (256). Malformed metadata, a
truncated crash body, invalid UTF-8, a NUL in the text, and exhausted budgets
fail without partial success.
`max_integer_text_bytes` (`--max-integer-text-bytes` in the CLI) separately
bounds retained decimal and hexadecimal integer text, including every derived
runtime address and rejected lookup literal. Its default is 8 MiB of UTF-16 text storage and its maximum is
64 MiB. Each appearance counts, even when an object could be shared in memory
but would be serialized more than once. The parser reserves a conservative
upper bound before numeric conversion or address expansion, then releases the
unused reservation after each value. It reuses the parsed source integer when
the same image base contributes to many frames.
Raise this per-call allowance on the next call when more room is needed; no
restart is required. File-size and row budgets do not raise it. This allowance
is separate from JSON wire size, MCP response limits and the client's receive
buffer. An exhausted allowance reports a typed resource error without partial
success or truncated integer facts.
```bash
rea inspect-apple-ips /absolute/path/to/report.ips --max-integer-text-bytes 16777216 --json
```
This workflow does not execute a target, launch atos, or search for dSYMs.