[PR #4843] fix(domain): admit SPARC V9 (EM_SPARCV9, 43) ELF binaries #6439

Open
opened 2026-10-31 01:14:28 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/4843
Author: @Zhouyang66639
Created: 10/17/2026
Status: 🔄 Open

Base: main ← Head: admit-sparcv9-elf


📝 Commits (2)

  • af4df27 fix(domain): admit SPARC V9 (EM_SPARCV9, 43) ELF binaries
  • 629291a fix(domain): scope SPARC to BE64 and keep adapter policy out of domain

📊 Changes

13 files changed (+251 additions, -30 deletions)

View changed files

➕ src/domain/binaryTarget.sparc.test.ts (+31 -0)
📝 src/domain/binaryTarget.test.ts (+1 -0)
📝 src/domain/binaryTarget.ts (+14 -0)
➕ src/domain/binaryTargetTypes.test.ts (+32 -0)
📝 src/domain/binaryTargetTypes.ts (+26 -1)
📝 src/domain/elfFixedHeader.ts (+7 -1)
📝 src/ghidra/GhidraProvider.behavior.test.ts (+34 -0)
📝 src/ghidra/GhidraProvider.ts (+1 -0)
📝 src/hopper/HopperAnalysisProfile.ts (+28 -12)
📝 src/hopper/HopperProvider.test.ts (+43 -0)
📝 src/hopper/HopperProvider.ts (+3 -8)
📝 src/ida/IdaProvider.test.ts (+19 -0)
📝 src/ida/IdaProvider.ts (+12 -8)

📄 Description

Summary

Adds the missing elfArchitecture() case for e_machine = 43 (EM_SPARCV9) so open_binary admits 64-bit SPARC ELF objects through the existing header-driven Ghidra lane instead of refusing them at admission. Fixes #4842.

Problem and expected behavior

Smallest trigger: an ELF64 big-endian object with e_machine = 43. Violated invariant: src/domain/elfFixedHeader.ts classifies only eight ELF machine values (3, 8, 20, 40, 62, 164, 183, 243), so parseElf returns err("unsupported ELF architecture") and the target never reaches a provider — even though the Ghidra version REA pins already ships the processor. At tag Ghidra_12.1.4_build, Ghidra/Processors/Sparc/data/languages/Sparc.opinion binds ELF primary 43 to processor="Sparc" endian="big" size="64", and SparcV9.ldefs offers exactly two ids (sparc:BE:32:default, sparc:BE:64:default), so 43 resolves to one language with no caller choice.

Expected behavior after this change: REA records the sparc family, the generic Ghidra lane imports it with language_id: "auto-from-header", and Hopper and IDA decline it naming the family — the same observable contract #4819 established for hexagon.

Change and scope

Commit af4df27 admits the family; commit 629291a answers the first review round (see the reply threads).

  • src/domain/elfFixedHeader.ts: elfArchitecture() now takes the parsed elfClass and byte order, and admits EM_SPARCV9 (43) only under ELF64 big-endian commitments. Those are exactly the header facts Ghidra binds to one language (Sparc.opinion at tag Ghidra_12.1.4_build: 43 -> endian="big" size="64", and SparcV9.ldefs offers only sparc:BE:32:default / sparc:BE:64:default), so a 32-bit-class or little-endian header with machine 43 stays unclassified instead of reaching an import that has no matching language.
  • src/domain/binaryTargetTypes.ts: sparc joins BINARY_ARCHITECTURES, plus a provider-neutral partition ELF_ONLY_ARCHITECTURES = ["hexagon", "sparc"] with isElfOnlyArchitecture — families no PE COFF machine or Mach-O CPU type can declare (src/domain/peMachine.ts maps only 0x014c, 0x8664, 0x01c0/0x01c2/0x01c4, 0xaa64). HeaderArchitecture is derived from the metadata, raw-only, and ELF-only partitions, and BinaryTarget gains a format: "elf"-only sparc member, so sparc cannot appear as a PE or raw-binary family.
  • src/domain/binaryTarget.ts: ExecutableMetadata gains the sparc member; parseElf admits it without an ELF metadata member, alongside hexagon.
  • src/ghidra/GhidraProvider.ts: sparc joins SUPPORTED_ARCHITECTURES; the profile stays loader/language_id = auto-from-header.
  • src/hopper/ and src/ida/: each adapter keeps its own family label table and declines the metadata families and the ELF-only families. Hopper's advice is now conditional on which thing actually exists: a metadata family is pointed at its verified pinned Ghidra profile, an ELF-only family at Ghidra's header-driven import.
  • Tests: SPARC classification row, a dedicated src/domain/binaryTarget.sparc.test.ts (accept plus three rejects), the Ghidra admit/Windows-decline behavior block, Hopper and IDA decline wording for both advice arms, and the ELF-only partition table.

Intentionally not included:

  • EM_SPARC (2) and EM_SPARC32PLUS (18), now asserted rather than only stated: machine 2 covers pre-V9 V8 objects and 18 covers V9 in 32-bit mode, so the family name plus class does not establish the ISA. That needs the ELF-metadata plus pinned-profile route (mips, riscv, powerpc) with its own verified lane.
  • No GhidraSparcProfile, no verify:ghidra:sparc lane, no SPARC conformance fixture. This machine has no SPARC cross target (Apple clang 17 ships none, and there is no lld or llvm-readelf) and no SPARC object, so a lane claiming verified decompiler results could not be run and is not claimed.
  • No real Ghidra import was executed. See the reply on the real-provider thread for the scope question this raises.

Contract and boundary impact

  • Semantic owner and earliest changed stage: src/domain/elfFixedHeader.ts for machine-to-family, src/domain/binaryTargetTypes.ts for the partition; earliest stage is open_binary admission.
  • Contribution scope fit and linked maintainer decision: ordinary fix inside the existing admission and provider boundaries; maintainer scope decision not applicable.
  • CLI and MCP/tool-catalog contract: sparc joins the advertised architecture enum, because src/contracts/toolOutputSchemaGroups.ts and src/domain/evidence.ts both build it from BINARY_ARCHITECTURES. Tool output schemas and the Evidence schema therefore gain one enum value. Catalog digests are recorded from the compiled contracts during npm run compile and are not committed, so no generated file changes here.
  • Provider, bridge, target-format, or platform compatibility: unchanged for every existing family. Ghidra admits the new family on the platforms its ELF lane already supports; the added behavior test records that a sparc ELF target is still declined on win32 with target_format_unsupported. Hopper and IDA decline by name.
  • Evidence, artifact, provenance, or reconstruction contract: no change beyond the enum member.
  • Target selection, authority, upstream behavior, and cleanup details: not applicable; no new process, transport, or authority surface.
  • Generated metadata, package, or installation impact: none committed.

Expected effects.

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.

Evidence and regression coverage

  • Tests added or updated:
    • src/domain/binaryTarget.sparc.test.ts — new; admits elf(2, 2, 43) and declines elf(1, 2, 43), elf(2, 1, 43), plus machine values 2 and 18.
    • src/domain/binaryTargetTypes.test.ts — new; pins the ELF-only partition to hexagon and sparc and lists every family outside it.
    • src/domain/binaryTarget.test.ts — classification row for elf(2, 2, 43).
    • src/ghidra/GhidraProvider.behavior.test.ts — its own describe: SPARC target supported, profile parameters stay auto-from-header, declined on win32. Separate block because describe("Ghidra provider") already occupies 149 of the 150 lines max-lines-per-function allows.
    • src/hopper/HopperProvider.test.ts — SPARC and Hexagon declines assert header-driven and assert the reason does not say verified; MIPS still asserts verified Ghidra MIPS profile.
    • src/ida/IdaProvider.test.ts — SPARC decline names the family.
  • Base reproduction: reverting only the source files and keeping these tests turns npm run test:focused on the affected files into failures (10 failed / 165 passed for the first commit's set). Two targeted mutations on the final tree: replacing the BE64 guard with an unconditional "sparc" fails 2 of the 4 SPARC header tests; collapsing Hopper's two advice arms into one fails 2 Hopper tests.
  • User-visible CLI/MCP output: not executed. Before the change open_binary returns unsupported ELF architecture; the post-change import result is source-derived from Ghidra's Sparc.opinion, not observed.
  • Remaining proof gaps: no real Ghidra import of a SPARC64 binary, no rea doctor run, no Hopper or IDA run, no SPARC fixture, and no 32-bit SPARC admission.

For evidence-bearing changes: the agent left these unchecked because they are claims about human review of the evidence, not facts it can assert.

Validation performed

All results below are from the tree at head 629291a (main 7016951 plus these two commits), run locally on macOS arm64 with Node 26.11.1.

  • npm run test:fast (source group) — 608 test files, 9680 passed, 13 skipped, 0 failed.
  • npm run test:focused -- the eight domain, Ghidra, Hopper and IDA files above — 9 files, 196 passed.
  • npm run typecheck — clean.
  • npm run verify:architecture-guards — passed across 1494 source files.
  • npx oxfmt --check src/domain src/hopper src/ida — all correctly formatted.
  • npx oxlint on the changed files — 42 warnings, 0 errors, and no finding at a line this PR added.
  • Mutation runs recorded above.
  • npm run check:ci — stops at format:check on exactly one file, docs/readme/README_fr.md, which is not in this diff; that failure is on main independently of this change.
  • Unavailable: the verify:ghidra* real-provider lanes, any SPARC cross-toolchain, and a SPARC target binary.

Compatibility, safety, and release

  • Breaking changes or migration steps: none. No existing classification, format, or provider decision changes; one enum member is added.
  • Real Hopper/Ghidra, browser, or OS coverage: none executed in this PR; see the recorded gaps.
  • Package or release metadata impact: none committed.
  • Security, privacy, process, or containment review: no new process launch, network contact, or authority change; the existing read-only Ghidra import path is untouched.

Author readiness

Human owner: @Zhouyang66639

The change was prepared by an AI agent on behalf of @Zhouyang66639. The human owner reviewed this description and the changed-file list in this session and instructed the agent to fill these declarations and mark the PR ready for review; every evidence line below is a command the agent actually ran, and the recorded gaps stay as written.

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

Fixes #4842


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/4843 **Author:** [@Zhouyang66639](https://github.com/Zhouyang66639) **Created:** 10/17/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `admit-sparcv9-elf` --- ### 📝 Commits (2) - [`af4df27`](https://github.com/morluto/rea/commit/af4df27ce7ca592e70f338b91ec1d7734d05b80c) fix(domain): admit SPARC V9 (EM_SPARCV9, 43) ELF binaries - [`629291a`](https://github.com/morluto/rea/commit/629291a5450d60e168ca70e0f7f87d1849348c87) fix(domain): scope SPARC to BE64 and keep adapter policy out of domain ### 📊 Changes **13 files changed** (+251 additions, -30 deletions) <details> <summary>View changed files</summary> ➕ `src/domain/binaryTarget.sparc.test.ts` (+31 -0) 📝 `src/domain/binaryTarget.test.ts` (+1 -0) 📝 `src/domain/binaryTarget.ts` (+14 -0) ➕ `src/domain/binaryTargetTypes.test.ts` (+32 -0) 📝 `src/domain/binaryTargetTypes.ts` (+26 -1) 📝 `src/domain/elfFixedHeader.ts` (+7 -1) 📝 `src/ghidra/GhidraProvider.behavior.test.ts` (+34 -0) 📝 `src/ghidra/GhidraProvider.ts` (+1 -0) 📝 `src/hopper/HopperAnalysisProfile.ts` (+28 -12) 📝 `src/hopper/HopperProvider.test.ts` (+43 -0) 📝 `src/hopper/HopperProvider.ts` (+3 -8) 📝 `src/ida/IdaProvider.test.ts` (+19 -0) 📝 `src/ida/IdaProvider.ts` (+12 -8) </details> ### 📄 Description ## Summary Adds the missing `elfArchitecture()` case for `e_machine = 43` (`EM_SPARCV9`) so `open_binary` admits 64-bit SPARC ELF objects through the existing header-driven Ghidra lane instead of refusing them at admission. Fixes #4842. ## Problem and expected behavior Smallest trigger: an ELF64 big-endian object with `e_machine = 43`. Violated invariant: `src/domain/elfFixedHeader.ts` classifies only eight ELF machine values (3, 8, 20, 40, 62, 164, 183, 243), so `parseElf` returns `err("unsupported ELF architecture")` and the target never reaches a provider — even though the Ghidra version REA pins already ships the processor. At tag `Ghidra_12.1.4_build`, `Ghidra/Processors/Sparc/data/languages/Sparc.opinion` binds ELF `primary` 43 to `processor="Sparc" endian="big" size="64"`, and `SparcV9.ldefs` offers exactly two ids (`sparc:BE:32:default`, `sparc:BE:64:default`), so 43 resolves to one language with no caller choice. Expected behavior after this change: REA records the `sparc` family, the generic Ghidra lane imports it with `language_id: "auto-from-header"`, and Hopper and IDA decline it naming the family — the same observable contract #4819 established for `hexagon`. ## Change and scope Commit `af4df27` admits the family; commit `629291a` answers the first review round (see the reply threads). - `src/domain/elfFixedHeader.ts`: `elfArchitecture()` now takes the parsed `elfClass` and byte order, and admits `EM_SPARCV9` (43) only under ELF64 big-endian commitments. Those are exactly the header facts Ghidra binds to one language (`Sparc.opinion` at tag `Ghidra_12.1.4_build`: 43 -> `endian="big" size="64"`, and `SparcV9.ldefs` offers only `sparc:BE:32:default` / `sparc:BE:64:default`), so a 32-bit-class or little-endian header with machine 43 stays unclassified instead of reaching an import that has no matching language. - `src/domain/binaryTargetTypes.ts`: `sparc` joins `BINARY_ARCHITECTURES`, plus a provider-neutral partition `ELF_ONLY_ARCHITECTURES = ["hexagon", "sparc"]` with `isElfOnlyArchitecture` — families no PE COFF machine or Mach-O CPU type can declare (`src/domain/peMachine.ts` maps only 0x014c, 0x8664, 0x01c0/0x01c2/0x01c4, 0xaa64). `HeaderArchitecture` is derived from the metadata, raw-only, and ELF-only partitions, and `BinaryTarget` gains a `format: "elf"`-only `sparc` member, so `sparc` cannot appear as a PE or raw-binary family. - `src/domain/binaryTarget.ts`: `ExecutableMetadata` gains the `sparc` member; `parseElf` admits it without an ELF metadata member, alongside `hexagon`. - `src/ghidra/GhidraProvider.ts`: `sparc` joins `SUPPORTED_ARCHITECTURES`; the profile stays `loader`/`language_id` = `auto-from-header`. - `src/hopper/` and `src/ida/`: each adapter keeps its own family label table and declines the metadata families and the ELF-only families. Hopper's advice is now conditional on which thing actually exists: a metadata family is pointed at its verified pinned Ghidra profile, an ELF-only family at Ghidra's header-driven import. - Tests: SPARC classification row, a dedicated `src/domain/binaryTarget.sparc.test.ts` (accept plus three rejects), the Ghidra admit/Windows-decline behavior block, Hopper and IDA decline wording for both advice arms, and the ELF-only partition table. Intentionally not included: - `EM_SPARC` (2) and `EM_SPARC32PLUS` (18), now asserted rather than only stated: machine 2 covers pre-V9 V8 objects and 18 covers V9 in 32-bit mode, so the family name plus class does not establish the ISA. That needs the ELF-metadata plus pinned-profile route (`mips`, `riscv`, `powerpc`) with its own verified lane. - No `GhidraSparcProfile`, no `verify:ghidra:sparc` lane, no SPARC conformance fixture. This machine has no SPARC cross target (Apple clang 17 ships none, and there is no `lld` or `llvm-readelf`) and no SPARC object, so a lane claiming verified decompiler results could not be run and is not claimed. - No real Ghidra import was executed. See the reply on the real-provider thread for the scope question this raises. ## Contract and boundary impact - Semantic owner and earliest changed stage: `src/domain/elfFixedHeader.ts` for machine-to-family, `src/domain/binaryTargetTypes.ts` for the partition; earliest stage is `open_binary` admission. - Contribution scope fit and linked maintainer decision: ordinary fix inside the existing admission and provider boundaries; maintainer scope decision not applicable. - CLI and MCP/tool-catalog contract: `sparc` joins the advertised `architecture` enum, because `src/contracts/toolOutputSchemaGroups.ts` and `src/domain/evidence.ts` both build it from `BINARY_ARCHITECTURES`. Tool output schemas and the Evidence schema therefore gain one enum value. Catalog digests are recorded from the compiled contracts during `npm run compile` and are not committed, so no generated file changes here. - Provider, bridge, target-format, or platform compatibility: unchanged for every existing family. Ghidra admits the new family on the platforms its ELF lane already supports; the added behavior test records that a `sparc` ELF target is still declined on `win32` with `target_format_unsupported`. Hopper and IDA decline by name. - Evidence, artifact, provenance, or reconstruction contract: no change beyond the enum member. - Target selection, authority, upstream behavior, and cleanup details: not applicable; no new process, transport, or authority surface. - Generated metadata, package, or installation impact: none committed. Expected effects. - [x] Read existing files or captured data. - [ ] Launch or attach to a process. - [ ] Inject instrumentation or run scripts/code. - [ ] Change process memory, registers, files, or configuration. - [ ] Contact network services. - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. ## Evidence and regression coverage - Tests added or updated: - `src/domain/binaryTarget.sparc.test.ts` — new; admits `elf(2, 2, 43)` and declines `elf(1, 2, 43)`, `elf(2, 1, 43)`, plus machine values 2 and 18. - `src/domain/binaryTargetTypes.test.ts` — new; pins the ELF-only partition to `hexagon` and `sparc` and lists every family outside it. - `src/domain/binaryTarget.test.ts` — classification row for `elf(2, 2, 43)`. - `src/ghidra/GhidraProvider.behavior.test.ts` — its own `describe`: SPARC target supported, profile parameters stay `auto-from-header`, declined on `win32`. Separate block because `describe("Ghidra provider")` already occupies 149 of the 150 lines `max-lines-per-function` allows. - `src/hopper/HopperProvider.test.ts` — SPARC and Hexagon declines assert `header-driven` and assert the reason does **not** say `verified`; MIPS still asserts `verified Ghidra MIPS profile`. - `src/ida/IdaProvider.test.ts` — SPARC decline names the family. - Base reproduction: reverting only the source files and keeping these tests turns `npm run test:focused` on the affected files into failures (10 failed / 165 passed for the first commit's set). Two targeted mutations on the final tree: replacing the BE64 guard with an unconditional `"sparc"` fails 2 of the 4 SPARC header tests; collapsing Hopper's two advice arms into one fails 2 Hopper tests. - User-visible CLI/MCP output: not executed. Before the change `open_binary` returns `unsupported ELF architecture`; the post-change import result is **source-derived from Ghidra's `Sparc.opinion`, not observed**. - Remaining proof gaps: no real Ghidra import of a SPARC64 binary, no `rea doctor` run, no Hopper or IDA run, no SPARC fixture, and no 32-bit SPARC admission. For evidence-bearing changes: the agent left these unchecked because they are claims about human review of the evidence, not facts it can assert. ## Validation performed All results below are from the tree at head `629291a` (main `7016951` plus these two commits), run locally on macOS arm64 with Node 26.11.1. - `npm run test:fast` (source group) — 608 test files, 9680 passed, 13 skipped, 0 failed. - `npm run test:focused --` the eight domain, Ghidra, Hopper and IDA files above — 9 files, 196 passed. - `npm run typecheck` — clean. - `npm run verify:architecture-guards` — passed across 1494 source files. - `npx oxfmt --check src/domain src/hopper src/ida` — all correctly formatted. - `npx oxlint` on the changed files — 42 warnings, 0 errors, and no finding at a line this PR added. - Mutation runs recorded above. - `npm run check:ci` — stops at `format:check` on exactly one file, `docs/readme/README_fr.md`, which is not in this diff; that failure is on `main` independently of this change. - Unavailable: the `verify:ghidra*` real-provider lanes, any SPARC cross-toolchain, and a SPARC target binary. ## Compatibility, safety, and release - Breaking changes or migration steps: none. No existing classification, format, or provider decision changes; one enum member is added. - Real Hopper/Ghidra, browser, or OS coverage: none executed in this PR; see the recorded gaps. - Package or release metadata impact: none committed. - Security, privacy, process, or containment review: no new process launch, network contact, or authority change; the existing read-only Ghidra import path is untouched. ## Author readiness Human owner: @Zhouyang66639 The change was prepared by an AI agent on behalf of @Zhouyang66639. The human owner reviewed this description and the changed-file list in this session and instructed the agent to fill these declarations and mark the PR ready for review; every evidence line below is a command the agent actually ran, and the recorded gaps stay as written. - [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [ ] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. Fixes #4842 --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#6439
No description provided.