[PR #1961] fix(gdb)!: preserve interrupt and provider cleanup ownership #4938

Open
opened 2026-10-31 00:57:10 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/1961
Author: @dandgabr
Created: 10/11/2026
Status: 🔄 Open

Base: main ← Head: feat/gdb-debugger-integration


📝 Commits (10+)

  • c322afa fix(gdb): report decoding loss in failed startup evidence
  • 01541c2 fix(rizin): scope decoding loss to collected command frames
  • da4d7d8 test(mcp): cover snapshot retirement in reverse tools CI
  • f92d5ca fix(debuggers): keep verified detach on exit retries and resume Windows cleanup polling
  • e30ddea fix(gdb): keep unrelated closes from clearing cleanup owners and harden digest open
  • a73fd54 fix(gdb): keep MI3 startup evidence on fallback and reject background one-shot commands
  • 99a5a9e merge: sync PR 1959 with upstream main
  • 745a236 merge: sync PR 1961 with the reconciled #2799 lineage
  • 17f267e merge: reconcile PR 1961 with the merged #2799 GDB implementation
  • 702e73d fix(gdb): keep MI3 startup evidence on fallback and bound the artifact digest open

📊 Changes

96 files changed (+12324 additions, -1194 deletions)

View changed files

📝 .github/workflows/ci.yml (+9 -0)
➕ .github/workflows/real-reverse-providers.yml (+67 -0)
📝 .gitignore (+1 -0)
📝 docs/reverse-engineering-tools.md (+37 -8)
📝 docs/testing.md (+24 -0)
📝 package.json (+1 -0)
📝 scripts/ci/plan.mjs (+11 -0)
📝 scripts/ci/scopes.mjs (+33 -0)
📝 scripts/lib/reverse-tools-mutation-targets.mjs (+13 -10)
➕ scripts/verify-inferior-tty.d.mts (+12 -0)
➕ scripts/verify-inferior-tty.mjs (+86 -0)
➕ scripts/verify-real-reverse-providers.mjs (+595 -0)
📝 src/application/binary/BinarySession.ts (+187 -38)
📝 src/application/binary/BinarySessionBundleResources.test.ts (+44 -0)
📝 src/application/binary/BinarySessionOpen.ts (+5 -0)
📝 src/application/binary/BinarySessionPort.ts (+17 -8)
📝 src/application/reverse/ReverseEngineeringCommandPort.ts (+12 -2)
📝 src/application/reverse/ReverseEngineeringService.ts (+422 -109)
📝 src/browser/CdpEndpoint.ts (+5 -1)
📝 src/browser/WebBundleAnalysisWorker.test.ts (+3 -2)

...and 76 more files

📄 Description

Summary

Fix persistent GDB control and cleanup ownership: interrupt can reach an admitted run, cancelled runs retain their mutation barrier, and interrupted artifact reads return bounded failures without losing descriptor ownership. Synchronize the shared lifecycle corrections from #1959/#1960 and merge current upstream/main (9af0b6113). Related scope discussion: #3656; this PR originated in the split of closed #1279.

Problem and expected behavior

A run waiting for *stopped also blocked its queued interrupt; cancellation could instead detach a running writer and release admission. An interrupted digest could then hang on FileHandle.close(), or hide that cleanup behind an earlier provider timeout. Commands now preserve the exact execution/cleanup owner until correlated protocol settlement and confirmed cleanup, while returning partial observations and typed failures.

Change and scope

  • Separate target-running state and interrupted-token synchronization from command serialization. Only exact argument-free interrupt gets one bounded control slot under the original run's admission. Real GDB console execution blocks MI input, so delivery queues MI and sends SIGINT only through the retained owned debugger handle, then awaits correlated MI/async stop.
  • Normalize shutdown failures at the GDB owner; keep failed close/cancel errors bound to that same owner. Preserve delayed startup stderr.
  • Hash through the canonical descriptor chunk reader and OwnedFileHandle; bounded late-open/fstat/read/close settlement retains a retryable cleanup owner. Compose secondary cleanup through the existing error algebra without losing the primary failure.
  • Carry shared preflight/admission, fixed-source, Windows lookup, inferior-input, caller-token and CLI recovery corrections. Close retries retained writers within the same session transition, preserves snapshot receipts, and composes every independent cleanup owner (including typed callback rejections). CLI admission conflicts report retryable capacity. Failed terminal-helper readiness now closes/awaits its own child; real-provider CI follows ownership primitives and checks actual MI result classes and SIGINT.
  • Disclose the reserved REA_PROCESS_RUN_ID override in static Rizin/plugins. This is documentation of existing behavior; environment equivalence is not claimed and ambient inheritance does not authorize persistence.

No new target-execution authority is added by these corrections. Maintainer scope approval for the overall debugger integration remains unknown/pending in #3656; passing checks do not authorize integration.

Contract and boundary impact

  • Semantic owner and earliest changed stage: provider command preparation, GDB MI/running state, descriptor read/close ownership, and shared binary mutation admission.
  • Contribution scope fit and linked maintainer decision: routine corrections within the existing proposed integration; the final integration decision is still pending in #3656.
  • CLI and MCP/tool-catalog contract: existing tool names/schema shapes; exact interrupt control behavior and truthful Rizin environment description. Fixed analysis sources with no mutation lifecycle no longer advertise unrestricted Rizin.
  • Provider, bridge, target-format, or platform compatibility: caller-supplied tools; GDB live-inferior verification remains Linux-only. Windows lookup is cancellation-aware; actual Windows/macOS runtime behavior was not executed here.
  • Evidence, artifact, provenance, or reconstruction contract: retained partial output/primary failures and live cleanup resources; actual interrupt delivery is recorded. No ambient environment snapshot is persisted.
  • Target selection, authority, upstream behavior, and cleanup details: caller-selected commands/targets; only the retained debugger handle receives interrupt signals, never an inferior PID or group. Unknown cleanup remains owned and blocks unsafe replacement.
  • Generated metadata, package, or installation impact: canonical description regenerated/validated through docs checks; ignored catalogs/build outputs are not committed. No provider installation.

Expected effects:

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code. (existing unrestricted debugger/Rizin commands)
  • Change process memory, registers, files, or configuration. (existing caller-selected debugger authority)
  • Contact network services. (possible through caller-selected commands/plugins)
  • 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: GDB provider/MCP run interruption, cancellation/timeout settlement, rejected shutdown, startup pipe ordering; digest late-open/fstat/stream-close ownership and composed failures; helper startup failure, CI selection, shared mutation/preflight/CLI recovery.
  • Base reproduction or other evidence: observed failing regressions before fixes; independent review replayed the hidden cancellation-stop and primary-timeout/secondary-cleanup failures and confirmed recovery. Real GDB exposed the synchronous-console MI limitation before the retained-handle delivery correction.
  • User-visible CLI/MCP output: exact interrupt yields complete correlated MI and the run retains a *stopped record with reason="signal-received",signal-name="SIGINT"; incomplete cleanup remains a typed error with live resources.
  • Remaining proof gaps: no Windows/macOS execution, no full arbitrary-plugin environment equivalence or containment of unrestricted persistent shell background writers. Rizin was deliberately omitted for the skip-path check; the host does have Rizin.

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

Current head db95b1fdc; shared close/admission follow-ups b69d50fbb, fa41732bc, db95b1fdc were independently reviewed and imported from #1960. Pinned Node 24.18.0 / npm 11.16.0.

Fresh verification on this head: four focused files (registerCloseLifecycleTool, binarySession, sessionMcp, reverseEngineeringCommands), 80/80 passed; check:fast passed. Independent production-adapter probe: 17/17 passed, covering all cleanup-owner combinations, both settlement orders, resource unions and exactly-once typed synchronous failure. No provider backend/protocol implementation changed in this follow-up, so the real-provider matrix was not rerun.

The following earlier broader evidence is pinned to executable source f9d47d8ec; documentation-only follow-ups 89d4a53c7 and 98690fc05 (Rizin terminal-input help) were separately checked:

  • npm run test:focused with 21 selected provider, MCP, composition, CLI and CI files: 697 passed, 1 platform skip.
  • npm run check:fast: passed, including the post-commit/pre-push gates.
  • npm run docs:check and npm run verify:test-discovery: passed; docs/contract follow-up also passed its fast/docs checks. Built debug-with-rizin --help now explicitly reports stdin/stdout/stderr and possible consumption of terminal input.
  • npm run verify:reverse-providers -- objdump gdb rizin: 48/48 assertions passed on final executable source, real GNU GDB 17.2, objdump 2.46.1 and Rizin 0.7.4 on Linux x86-64. Checks include explicit SIGINT stop, register read, artifact digest preservation, MI rejection negative control and owned debugger count 0 before/after.
  • node scripts/verify-real-reverse-providers.mjs objdump gdb '--skipped=rizin:deliberately omitted to verify multiword skip reporting on a host with Rizin installed': 45/45 passed; exact multiword skip reason and status: skipped independently asserted in the receipt.

The final full command including build took 28.81 s elapsed, 61.14 s user / 8.93 s system CPU; GNU time reported maximum RSS 2,768,948 KiB. These are shared-host observations, not a speedup claim; no equivalent successful before measurement, token totals or aggregate process-family peak memory were measured. The provider-only full run separately took 19.21 s elapsed.

Compatibility, safety, and release

  • Breaking changes or migration steps: fixed analysis sources must use a session-backed server with mutation admission for unrestricted Rizin. Hosts lacking ownership baselines reject required token-sweep launches before execution; use a supported ownership host. These narrow previously unsafe acceptance and cannot be preserved through deprecation without losing the cleanup boundary.
  • Real Hopper/Ghidra, browser, or OS coverage: real GNU providers on Linux only; no new Hopper/Ghidra/browser or Windows/macOS runtime claim.
  • Package or release metadata impact: generated canonical contract description only; no binaries/installers or generated outputs committed.
  • Security, privacy, process, or containment review: exact retained resource owners; incomplete cleanup remains visible. Persistent raw commands/plugins retain their documented host/target/filesystem/network effects. REA_PROCESS_RUN_ID stays reserved inside static Rizin/plugins; caller-value restoration is established separately for debugger inferiors. Overall integration still requires the maintainer decision in #3656.

Author readiness

Human owner: @dandgabr

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

🔄 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/1961 **Author:** [@dandgabr](https://github.com/dandgabr) **Created:** 10/11/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `feat/gdb-debugger-integration` --- ### 📝 Commits (10+) - [`c322afa`](https://github.com/morluto/rea/commit/c322afae47816a03079aeb1571ccb357b4572696) fix(gdb): report decoding loss in failed startup evidence - [`01541c2`](https://github.com/morluto/rea/commit/01541c2d504cd2f70322aa79bcee3f96dcd1be69) fix(rizin): scope decoding loss to collected command frames - [`da4d7d8`](https://github.com/morluto/rea/commit/da4d7d84773fadb68585ec9a12f0679b5c9a2799) test(mcp): cover snapshot retirement in reverse tools CI - [`f92d5ca`](https://github.com/morluto/rea/commit/f92d5ca78d6f5fccec764be880d1cb129be29d0f) fix(debuggers): keep verified detach on exit retries and resume Windows cleanup polling - [`e30ddea`](https://github.com/morluto/rea/commit/e30ddeac570c64db89669a3939a9999e231d466d) fix(gdb): keep unrelated closes from clearing cleanup owners and harden digest open - [`a73fd54`](https://github.com/morluto/rea/commit/a73fd54922bcc7a971d9949d44740458679072d0) fix(gdb): keep MI3 startup evidence on fallback and reject background one-shot commands - [`99a5a9e`](https://github.com/morluto/rea/commit/99a5a9eb34062034f97f6305a06c1aa36a612471) merge: sync PR 1959 with upstream main - [`745a236`](https://github.com/morluto/rea/commit/745a236d0d9402765b1a73f2e5a225de92fc9ace) merge: sync PR 1961 with the reconciled #2799 lineage - [`17f267e`](https://github.com/morluto/rea/commit/17f267e588264e7129d746e2a94cc1cae4b3ce7d) merge: reconcile PR 1961 with the merged #2799 GDB implementation - [`702e73d`](https://github.com/morluto/rea/commit/702e73d0b3a9757136fd6cd944d9f3ef5f1ac1eb) fix(gdb): keep MI3 startup evidence on fallback and bound the artifact digest open ### 📊 Changes **96 files changed** (+12324 additions, -1194 deletions) <details> <summary>View changed files</summary> 📝 `.github/workflows/ci.yml` (+9 -0) ➕ `.github/workflows/real-reverse-providers.yml` (+67 -0) 📝 `.gitignore` (+1 -0) 📝 `docs/reverse-engineering-tools.md` (+37 -8) 📝 `docs/testing.md` (+24 -0) 📝 `package.json` (+1 -0) 📝 `scripts/ci/plan.mjs` (+11 -0) 📝 `scripts/ci/scopes.mjs` (+33 -0) 📝 `scripts/lib/reverse-tools-mutation-targets.mjs` (+13 -10) ➕ `scripts/verify-inferior-tty.d.mts` (+12 -0) ➕ `scripts/verify-inferior-tty.mjs` (+86 -0) ➕ `scripts/verify-real-reverse-providers.mjs` (+595 -0) 📝 `src/application/binary/BinarySession.ts` (+187 -38) 📝 `src/application/binary/BinarySessionBundleResources.test.ts` (+44 -0) 📝 `src/application/binary/BinarySessionOpen.ts` (+5 -0) 📝 `src/application/binary/BinarySessionPort.ts` (+17 -8) 📝 `src/application/reverse/ReverseEngineeringCommandPort.ts` (+12 -2) 📝 `src/application/reverse/ReverseEngineeringService.ts` (+422 -109) 📝 `src/browser/CdpEndpoint.ts` (+5 -1) 📝 `src/browser/WebBundleAnalysisWorker.test.ts` (+3 -2) _...and 76 more files_ </details> ### 📄 Description ## Summary Fix persistent GDB control and cleanup ownership: `interrupt` can reach an admitted run, cancelled runs retain their mutation barrier, and interrupted artifact reads return bounded failures without losing descriptor ownership. Synchronize the shared lifecycle corrections from #1959/#1960 and merge current `upstream/main` (`9af0b6113`). Related scope discussion: #3656; this PR originated in the split of closed #1279. ## Problem and expected behavior A run waiting for `*stopped` also blocked its queued `interrupt`; cancellation could instead detach a running writer and release admission. An interrupted digest could then hang on `FileHandle.close()`, or hide that cleanup behind an earlier provider timeout. Commands now preserve the exact execution/cleanup owner until correlated protocol settlement and confirmed cleanup, while returning partial observations and typed failures. ## Change and scope - Separate target-running state and interrupted-token synchronization from command serialization. Only exact argument-free `interrupt` gets one bounded control slot under the original run's admission. Real GDB console execution blocks MI input, so delivery queues MI and sends `SIGINT` only through the retained owned debugger handle, then awaits correlated MI/async stop. - Normalize shutdown failures at the GDB owner; keep failed close/cancel errors bound to that same owner. Preserve delayed startup stderr. - Hash through the canonical descriptor chunk reader and `OwnedFileHandle`; bounded late-open/fstat/read/close settlement retains a retryable cleanup owner. Compose secondary cleanup through the existing error algebra without losing the primary failure. - Carry shared preflight/admission, fixed-source, Windows lookup, inferior-input, caller-token and CLI recovery corrections. Close retries retained writers within the same session transition, preserves snapshot receipts, and composes every independent cleanup owner (including typed callback rejections). CLI admission conflicts report retryable capacity. Failed terminal-helper readiness now closes/awaits its own child; real-provider CI follows ownership primitives and checks actual MI result classes and SIGINT. - Disclose the reserved `REA_PROCESS_RUN_ID` override in static Rizin/plugins. This is documentation of existing behavior; environment equivalence is not claimed and ambient inheritance does not authorize persistence. No new target-execution authority is added by these corrections. Maintainer scope approval for the overall debugger integration remains unknown/pending in #3656; passing checks do not authorize integration. ## Contract and boundary impact - Semantic owner and earliest changed stage: provider command preparation, GDB MI/running state, descriptor read/close ownership, and shared binary mutation admission. - Contribution scope fit and linked maintainer decision: routine corrections within the existing proposed integration; the final integration decision is still pending in [#3656](https://github.com/morluto/rea/issues/3656). - CLI and MCP/tool-catalog contract: existing tool names/schema shapes; exact `interrupt` control behavior and truthful Rizin environment description. Fixed analysis sources with no mutation lifecycle no longer advertise unrestricted Rizin. - Provider, bridge, target-format, or platform compatibility: caller-supplied tools; GDB live-inferior verification remains Linux-only. Windows lookup is cancellation-aware; actual Windows/macOS runtime behavior was not executed here. - Evidence, artifact, provenance, or reconstruction contract: retained partial output/primary failures and live cleanup resources; actual interrupt delivery is recorded. No ambient environment snapshot is persisted. - Target selection, authority, upstream behavior, and cleanup details: caller-selected commands/targets; only the retained debugger handle receives interrupt signals, never an inferior PID or group. Unknown cleanup remains owned and blocks unsafe replacement. - Generated metadata, package, or installation impact: canonical description regenerated/validated through docs checks; ignored catalogs/build outputs are not committed. No provider installation. Expected effects: - [x] Read existing files or captured data. - [x] Launch or attach to a process. - [x] Inject instrumentation or run scripts/code. (existing unrestricted debugger/Rizin commands) - [x] Change process memory, registers, files, or configuration. (existing caller-selected debugger authority) - [x] Contact network services. (possible through caller-selected commands/plugins) - [ ] 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: GDB provider/MCP run interruption, cancellation/timeout settlement, rejected shutdown, startup pipe ordering; digest late-open/fstat/stream-close ownership and composed failures; helper startup failure, CI selection, shared mutation/preflight/CLI recovery. - Base reproduction or other evidence: observed failing regressions before fixes; independent review replayed the hidden cancellation-stop and primary-timeout/secondary-cleanup failures and confirmed recovery. Real GDB exposed the synchronous-console MI limitation before the retained-handle delivery correction. - User-visible CLI/MCP output: exact `interrupt` yields complete correlated MI and the run retains a `*stopped` record with `reason="signal-received",signal-name="SIGINT"`; incomplete cleanup remains a typed error with live resources. - Remaining proof gaps: no Windows/macOS execution, no full arbitrary-plugin environment equivalence or containment of unrestricted persistent shell background writers. Rizin was deliberately omitted for the skip-path check; the host does have Rizin. 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 Current head `db95b1fdc`; shared close/admission follow-ups `b69d50fbb`, `fa41732bc`, `db95b1fdc` were independently reviewed and imported from #1960. Pinned Node 24.18.0 / npm 11.16.0. Fresh verification on this head: four focused files (`registerCloseLifecycleTool`, `binarySession`, `sessionMcp`, `reverseEngineeringCommands`), **80/80 passed**; `check:fast` passed. Independent production-adapter probe: **17/17 passed**, covering all cleanup-owner combinations, both settlement orders, resource unions and exactly-once typed synchronous failure. No provider backend/protocol implementation changed in this follow-up, so the real-provider matrix was not rerun. The following earlier broader evidence is pinned to executable source `f9d47d8ec`; documentation-only follow-ups `89d4a53c7` and `98690fc05` (Rizin terminal-input help) were separately checked: - `npm run test:focused` with 21 selected provider, MCP, composition, CLI and CI files: **697 passed, 1 platform skip**. - `npm run check:fast`: passed, including the post-commit/pre-push gates. - `npm run docs:check` and `npm run verify:test-discovery`: passed; docs/contract follow-up also passed its fast/docs checks. Built `debug-with-rizin --help` now explicitly reports stdin/stdout/stderr and possible consumption of terminal input. - `npm run verify:reverse-providers -- objdump gdb rizin`: **48/48 assertions passed** on final executable source, real GNU GDB 17.2, objdump 2.46.1 and Rizin 0.7.4 on Linux x86-64. Checks include explicit SIGINT stop, register read, artifact digest preservation, MI rejection negative control and owned debugger count 0 before/after. - `node scripts/verify-real-reverse-providers.mjs objdump gdb '--skipped=rizin:deliberately omitted to verify multiword skip reporting on a host with Rizin installed'`: **45/45 passed**; exact multiword skip reason and `status: skipped` independently asserted in the receipt. The final full command including build took 28.81 s elapsed, 61.14 s user / 8.93 s system CPU; GNU time reported maximum RSS 2,768,948 KiB. These are shared-host observations, not a speedup claim; no equivalent successful before measurement, token totals or aggregate process-family peak memory were measured. The provider-only full run separately took 19.21 s elapsed. ## Compatibility, safety, and release - Breaking changes or migration steps: fixed analysis sources must use a session-backed server with mutation admission for unrestricted Rizin. Hosts lacking ownership baselines reject required token-sweep launches before execution; use a supported ownership host. These narrow previously unsafe acceptance and cannot be preserved through deprecation without losing the cleanup boundary. - Real Hopper/Ghidra, browser, or OS coverage: real GNU providers on Linux only; no new Hopper/Ghidra/browser or Windows/macOS runtime claim. - Package or release metadata impact: generated canonical contract description only; no binaries/installers or generated outputs committed. - Security, privacy, process, or containment review: exact retained resource owners; incomplete cleanup remains visible. Persistent raw commands/plugins retain their documented host/target/filesystem/network effects. `REA_PROCESS_RUN_ID` stays reserved inside static Rizin/plugins; caller-value restoration is established separately for debugger inferiors. Overall integration still requires the maintainer decision in #3656. ## Author readiness Human owner: @dandgabr - [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. - [ ] 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. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. --- <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#4938
No description provided.