Skip to content

P0: Explicit save & durable persistence for the decompiler CLI#204

Merged
mahaloz merged 1 commit into
mainfrom
feat/cli-persistence
Jul 17, 2026
Merged

P0: Explicit save & durable persistence for the decompiler CLI#204
mahaloz merged 1 commit into
mainfrom
feat/cli-persistence

Conversation

@mahaloz

@mahaloz mahaloz commented Jul 17, 2026

Copy link
Copy Markdown
Member

What & why

Work done through DecLib (renames, retypes, comments) could disappear after stop + reload — IDA closed its database with close_database(False), discarding everything. This is the P0 persistence gap. This PR adds a first-class, cross-backend save so agents can produce durable .i64 / Ghidra-project / .bndb artifacts.

New capabilities

  • decompiler save [--path] — flush the backend's analysis to disk.
  • decompiler stop --save | --discard — persist before shutdown, or explicitly drop unsaved edits (mutually exclusive).
  • DecompilerInterface.save() + persist_on_close control, implemented per backend:
    Backend Behavior
    IDA idc.save_database; reopens an existing .i64/.idb directly on reload (no re-analysis, no headless overwrite-prompt hang)
    Ghidra GhidraProject.save(program); teardown honors persist_on_close so --discard truly reverts
    Binary Ninja writes/snapshots a .bndb (best-effort)
    angr in-memory → NotImplementedError
  • Clean "not implemented" UX: NotImplementedError now crosses the client/server wire intact and the CLI prints not implemented: … with a distinct exit code 2, so scripts can tell "unsupported on this backend" apart from a real failure.

Example

$ decompiler load ./fauxware --backend ida --project-dir ./proj
$ decompiler rename func authenticate auth_check
$ decompiler save
saved: True
path: None
$ decompiler stop --id <id>
$ decompiler load ./fauxware --backend ida --project-dir ./proj   # reopens saved .i64
$ decompiler list_functions --filter auth_check
0x664        184      auth_check          # the rename survived

$ decompiler save --backend angr
not implemented: this backend has no persistent database to save (e.g. angr is purely in-memory).
$ echo $?
2

Tests

Backend-parametrized tests/test_decompiler_cli.py:

  • test_save_command_persists_rename_across_reload — save → stop → reload, rename intact (IDA + Ghidra, both pass).
  • test_stop_save_persists_rename_across_reloadstop --save path (IDA + Ghidra pass).
  • test_save_unsupported_on_inmemory_backend — angr save exits 2.
  • test_stop_save_and_discard_are_mutually_exclusive.

Run with the declib env against IDA 9.2 (idalib), Ghidra 12.1 (pyghidra), and angr. Binary Ninja code paths are implemented but not exercised here (module not installed in the test env).


First PR in a series closing the IDALib-parity gaps; subsequent PRs (comments, globals/signatures, typed reads, search, repair, patching, scripting) stack on this branch.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HfZHdprXg38re1XyKxzGg5

Agents renamed/retyped through DecLib used to vanish on stop/reload because
IDA closed its database with close_database(False). This adds first-class
persistence so edits become durable artifacts across backends.

- New DecompilerInterface.save(path=None) + persist_on_close control.
  - IDA: idc.save_database via compat.save_database; reopen an existing
    .i64/.idb directly (no re-analysis, no headless overwrite-prompt hang).
  - Ghidra: GhidraProject.save(program); close honors persist_on_close so
    --discard actually reverts.
  - Binary Ninja: writes/snapshots a .bndb (best-effort).
  - angr: in-memory, raises NotImplementedError.
- CLI: `decompiler save [--path]` and `decompiler stop --save|--discard`.
- CLI: NotImplementedError now surfaces as a clean "not implemented"
  message with a distinct exit code (2) instead of a generic error.
- Client proxies save()/set_persist_on_close() and reconstructs
  NotImplementedError across the wire.
- Reopen-and-verify tests for IDA + Ghidra; unsupported-path test for angr.
- SKILL.md / docs / CLI docstring updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HfZHdprXg38re1XyKxzGg5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant