[PR #4892] fix(ghidra): treat demangled C++ runtime imports as returning #6460

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

📋 Pull Request Information

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

Base: main ← Head: fix/ghidra-cxx-runtime-returning-imports


📝 Commits (1)

  • cb55afb fix(ghidra): treat demangled C++ runtime imports as returning

📊 Changes

7 files changed (+69 additions, -17 deletions)

View changed files

📝 bridge/ghidra/ReaGhidraNoReturnFix.java (+18 -2)
📝 docs/ghidra-provider.md (+8 -2)
📝 scripts/verify-real-ghidra-noreturn.mjs (+5 -1)
📝 src/ghidra/GhidraAnalysisProfile.ts (+2 -2)
📝 tests/conformance/ghidra/README.md (+2 -1)
📝 tests/conformance/ghidra/ReaNoReturnProbe.java (+20 -9)
📝 tests/conformance/ghidra/no-return.c (+14 -0)

📄 Description

Summary

On a GCC 11/libstdc++ x86-64 shared library, Ghidra's default auto-analysis flags the PLT imports of operator new, operator delete and __cxa_guard_acquire as non-returning. Every caller is truncated after its first such call, so pseudocode, blocks and references are cut off. The existing no-return repair (#1419) clears wrong flags on known returning imports, but matches the external's current name; Ghidra demangles C++ imports (_Znwm becomes operator.new, shared by every overload), so they never match. No existing issue; found while analyzing a third-party 17.5 MB C++ shared library.

Problem and expected behavior

Observed on current main with default analyzers (sizes in bytes; ELF symbol size from nm -S):

Function origin/main body ELF symbol size This branch
A 2,077 4,644 4,583
B 2,126 3,104 3,080
C 426 1,533 1,519
D 276 1,309 1,279

Each truncated function's limitations name terminal calls to <EXTERNAL>::operator.new, operator.delete or __cxa_guard_acquire with hasNoReturn=true. After this change those warnings are gone. The branch's bodies equal those obtained by disabling the "Non-Returning Functions - Discovered" and "GCC Exception Handlers" analyzers, without disabling anything.

Change and scope

ReaGhidraNoReturnFix matches each external's original imported name (ExternalLocation.getOriginalImportedName()), falling back to its current name, and adds the Itanium C++ ABI entry points that return to their caller:

  • operator new/new[] and operator delete/delete[] in plain, nothrow, sized and aligned forms (64- and 32-bit size_t manglings);
  • __cxa_guard_acquire/release/abort, __cxa_atexit, __cxa_allocate_exception, __cxa_begin_catch/__cxa_end_catch.

operator new may throw; that unwinds and does not make the call non-returning. __cxa_throw, __cxa_rethrow and _Unwind_Resume are deliberately absent. no_return_repair moves to returning-imports-decoded-return-v3.

The no-return lane also needed a repair to run on current main: ReaNoReturnProbe.java uses functionBoundaryLimitations(Function, Iterable) (renamed in 225f26a5f) and unwraps the procedureReferences observation envelope. The lane's asynchronous installation inspection is handled upstream by 7d79a6a85, so this PR no longer changes it.

Contract and boundary impact

  • Semantic owner and earliest changed stage: Ghidra pre-serving no-return repair.
  • Contribution scope fit and linked maintainer decision: disassembly/decompilation correctness; not applicable.
  • CLI and MCP/tool-catalog contract: no schema change. Function bodies, pseudocode and references become complete where these imports were flagged.
  • Provider, bridge, target-format, or platform compatibility: Ghidra on Linux/macOS. Windows P0 is unchanged (repair disabled).
  • Evidence, artifact, provenance, or reconstruction contract: analysis profile marker no_return_repair v2 → v3 (older snapshots refused).
  • Target selection, authority, upstream behavior, and cleanup details: ephemeral database edits only, as before.
  • Generated metadata, package, or installation impact: none.

Expected effects:

  • Read existing files or captured data.
  • Launch or attach to a process. (Verification lane only.)
  • 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.
  • Not sure yet; investigation or maintainer guidance needed.

Evidence and regression coverage

  • Tests added or updated: tests/conformance/ghidra/no-return.c gains C-linkage callers of _Znwm and __cxa_guard_acquire (Ghidra demangles the first). ReaNoReturnProbe.java injects wrong flags on both and requires returning_import__Znwm, recovered_body__Znwm, returning_import___cxa_guard_acquire and recovered_body___cxa_guard_acquire. The lane asserts all four.
  • Base reproduction or other evidence: with the repair change reverted, the probe fails with No-return assertion failed: returning_import__Znwm.
  • User-visible CLI/MCP output: on the large library, procedure_info bodies grow as in the table, and the dossier's terminal-call limitations for the C++ runtime imports disappear.
  • Remaining proof gaps: Mach-O C++ imports go through the same original-name path with the existing underscore normalization, but this run was Linux only. MSVC C++ runtime names are out of scope.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • node scripts/verify-real-ghidra-noreturn.mjs (Linux x64, GCC 15.2, Ghidra 12.1.4, JDK 21.0.12) — passed with the new checks, verified cleanup; fails with the repair change reverted.
  • MCP session against the 17.5 MB GCC 11 library with default analyzers — four sampled functions now cover 97.7–99.2% of their ELF symbol size (21–68% on origin/main).
  • npm run verify:ghidra:bridge-sources — passed.
  • npm run check:changed — 8 test files, 276 passed.
  • All Ghidra-related test files (132) — 2601 passed, 16 skipped.
  • npm run docs:check — passed.
  • Rebased onto origin/main 23f984b1a. The only conflict was in scripts/verify-real-ghidra-noreturn.mjs: upstream's inspectVerifierGhidraInstallation (7d79a6a85) replaces this PR's earlier inspection repair. Then re-ran node scripts/verify-real-ghidra-noreturn.mjs (passed with all four C++ checks, verified cleanup), npm run verify:ghidra:bridge-sources, npm run check:changed (276 passed) and npm run docs:check — all passed. The large-library MCP session above used base f5fcf5216; no upstream commit since touches src/ghidra/, bridge/ghidra/ or tests/conformance/ghidra/.

Compatibility, safety, and release

  • Breaking changes or migration steps: older Ghidra analysis snapshots are refused because the repair marker changed.
  • Real Hopper/Ghidra, browser, or OS coverage: real Ghidra 12.1.4 on Linux x64; macOS unverified for this change.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: same ephemeral flag repair; no new process or file effects.

Author readiness

Human owner:

  • 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.

🤖 Generated with Claude Code


🔄 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/4892 **Author:** [@robotaro](https://github.com/robotaro) **Created:** 10/17/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `fix/ghidra-cxx-runtime-returning-imports` --- ### 📝 Commits (1) - [`cb55afb`](https://github.com/morluto/rea/commit/cb55afbb182fba7467f7075ad06a5cbe6052528f) fix(ghidra): treat demangled C++ runtime imports as returning ### 📊 Changes **7 files changed** (+69 additions, -17 deletions) <details> <summary>View changed files</summary> 📝 `bridge/ghidra/ReaGhidraNoReturnFix.java` (+18 -2) 📝 `docs/ghidra-provider.md` (+8 -2) 📝 `scripts/verify-real-ghidra-noreturn.mjs` (+5 -1) 📝 `src/ghidra/GhidraAnalysisProfile.ts` (+2 -2) 📝 `tests/conformance/ghidra/README.md` (+2 -1) 📝 `tests/conformance/ghidra/ReaNoReturnProbe.java` (+20 -9) 📝 `tests/conformance/ghidra/no-return.c` (+14 -0) </details> ### 📄 Description ## Summary On a GCC 11/libstdc++ x86-64 shared library, Ghidra's default auto-analysis flags the PLT imports of `operator new`, `operator delete` and `__cxa_guard_acquire` as non-returning. Every caller is truncated after its first such call, so pseudocode, blocks and references are cut off. The existing no-return repair (#1419) clears wrong flags on known returning imports, but matches the external's current name; Ghidra demangles C++ imports (`_Znwm` becomes `operator.new`, shared by every overload), so they never match. No existing issue; found while analyzing a third-party 17.5 MB C++ shared library. ## Problem and expected behavior Observed on current main with default analyzers (sizes in bytes; ELF symbol size from `nm -S`): | Function | origin/main body | ELF symbol size | This branch | |---|---|---|---| | A | 2,077 | 4,644 | 4,583 | | B | 2,126 | 3,104 | 3,080 | | C | 426 | 1,533 | 1,519 | | D | 276 | 1,309 | 1,279 | Each truncated function's limitations name terminal calls to `<EXTERNAL>::operator.new`, `operator.delete` or `__cxa_guard_acquire` with `hasNoReturn=true`. After this change those warnings are gone. The branch's bodies equal those obtained by disabling the "Non-Returning Functions - Discovered" and "GCC Exception Handlers" analyzers, without disabling anything. ## Change and scope `ReaGhidraNoReturnFix` matches each external's original imported name (`ExternalLocation.getOriginalImportedName()`), falling back to its current name, and adds the Itanium C++ ABI entry points that return to their caller: - `operator new`/`new[]` and `operator delete`/`delete[]` in plain, nothrow, sized and aligned forms (64- and 32-bit `size_t` manglings); - `__cxa_guard_acquire`/`release`/`abort`, `__cxa_atexit`, `__cxa_allocate_exception`, `__cxa_begin_catch`/`__cxa_end_catch`. `operator new` may throw; that unwinds and does not make the call non-returning. `__cxa_throw`, `__cxa_rethrow` and `_Unwind_Resume` are deliberately absent. `no_return_repair` moves to `returning-imports-decoded-return-v3`. The no-return lane also needed a repair to run on current main: `ReaNoReturnProbe.java` uses `functionBoundaryLimitations(Function, Iterable)` (renamed in `225f26a5f`) and unwraps the `procedureReferences` observation envelope. The lane's asynchronous installation inspection is handled upstream by `7d79a6a85`, so this PR no longer changes it. ## Contract and boundary impact - Semantic owner and earliest changed stage: Ghidra pre-serving no-return repair. - Contribution scope fit and linked maintainer decision: disassembly/decompilation correctness; not applicable. - CLI and MCP/tool-catalog contract: no schema change. Function bodies, pseudocode and references become complete where these imports were flagged. - Provider, bridge, target-format, or platform compatibility: Ghidra on Linux/macOS. Windows P0 is unchanged (repair disabled). - Evidence, artifact, provenance, or reconstruction contract: analysis profile marker `no_return_repair` v2 → v3 (older snapshots refused). - Target selection, authority, upstream behavior, and cleanup details: ephemeral database edits only, as before. - Generated metadata, package, or installation impact: none. Expected effects: - [x] Read existing files or captured data. - [x] Launch or attach to a process. (Verification lane only.) - [ ] 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. - [ ] Not sure yet; investigation or maintainer guidance needed. ## Evidence and regression coverage - Tests added or updated: `tests/conformance/ghidra/no-return.c` gains C-linkage callers of `_Znwm` and `__cxa_guard_acquire` (Ghidra demangles the first). `ReaNoReturnProbe.java` injects wrong flags on both and requires `returning_import__Znwm`, `recovered_body__Znwm`, `returning_import___cxa_guard_acquire` and `recovered_body___cxa_guard_acquire`. The lane asserts all four. - Base reproduction or other evidence: with the repair change reverted, the probe fails with `No-return assertion failed: returning_import__Znwm`. - User-visible CLI/MCP output: on the large library, `procedure_info` bodies grow as in the table, and the dossier's terminal-call limitations for the C++ runtime imports disappear. - Remaining proof gaps: Mach-O C++ imports go through the same original-name path with the existing underscore normalization, but this run was Linux only. MSVC C++ runtime names are out of scope. For evidence-bearing changes: - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed - `node scripts/verify-real-ghidra-noreturn.mjs` (Linux x64, GCC 15.2, Ghidra 12.1.4, JDK 21.0.12) — passed with the new checks, verified cleanup; fails with the repair change reverted. - MCP session against the 17.5 MB GCC 11 library with default analyzers — four sampled functions now cover 97.7–99.2% of their ELF symbol size (21–68% on origin/main). - `npm run verify:ghidra:bridge-sources` — passed. - `npm run check:changed` — 8 test files, 276 passed. - All Ghidra-related test files (132) — 2601 passed, 16 skipped. - `npm run docs:check` — passed. - Rebased onto origin/main `23f984b1a`. The only conflict was in `scripts/verify-real-ghidra-noreturn.mjs`: upstream's `inspectVerifierGhidraInstallation` (`7d79a6a85`) replaces this PR's earlier inspection repair. Then re-ran `node scripts/verify-real-ghidra-noreturn.mjs` (passed with all four C++ checks, verified cleanup), `npm run verify:ghidra:bridge-sources`, `npm run check:changed` (276 passed) and `npm run docs:check` — all passed. The large-library MCP session above used base `f5fcf5216`; no upstream commit since touches `src/ghidra/`, `bridge/ghidra/` or `tests/conformance/ghidra/`. ## Compatibility, safety, and release - Breaking changes or migration steps: older Ghidra analysis snapshots are refused because the repair marker changed. - Real Hopper/Ghidra, browser, or OS coverage: real Ghidra 12.1.4 on Linux x64; macOS unverified for this change. - Package or release metadata impact: none. - Security, privacy, process, or containment review: same ephemeral flag repair; no new process or file effects. ## Author readiness Human owner: <!-- @GitHub-handle --> - [ ] 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. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <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#6460
No description provided.