idasql-inspired analysis layer: SQLite index, SQL query, Ghidra daemon#19
Open
Night1099 wants to merge 21 commits into
Open
idasql-inspired analysis layer: SQLite index, SQL query, Ghidra daemon#19Night1099 wants to merge 21 commits into
Night1099 wants to merge 21 commits into
Conversation
Create new kb.py module with single source of truth for parsing kb.h knowledge-base files. Provides: - parse_kb(text_or_path) -> Kb with functions, globals, typedefs - KbFunction, KbGlobal, Kb dataclasses - extract_function_name() for signature parsing - read_existing_addresses() for bootstrap dedup Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implement GameIndex writer with schema for per-game analysis results. Provides transactional replace-by-source, WAL with DELETE fallback, schema-version guard, and read-only connections. Skips addresses with bit 63 set (out of signed-64 range) with stderr warning. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…ce overwrite 1. Extend _is_addr_col to include from_func column (stores function-start addresses in xrefs that must be range-checked like other address columns). 2. Change INSERT to INSERT OR REPLACE to allow authoritative Ghidra rows (source='ghidra') to overwrite provisional bootstrap rows (source='bootstrap') at overlapping addresses. 3. Update replace() docstring to document last-writer-wins cross-source overwrite. Add two new tests: - test_cross_source_overwrite: verify bootstrap row replaced by ghidra at same address - test_from_func_high_bit_skipped: verify from_func column bit-63 guard works Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Applies kb.h function names, prototypes, global labels, and typedefs into an analyzed Ghidra program in one transaction via _kb_apply_program.
Adds ghidra_client.py/ghidra_server.py cloning the livetools daemon pattern on port 27043 for a warm per-project Ghidra program, so repeat decompiles skip the ~3s JVM cold start. pyghidra_backend's decompile/ export/kb_apply now route through a live daemon transparently when one is running for the project, falling back to the existing cold in-process path otherwise (RETOOLS_GHIDRA_COLD=1 / --cold forces cold).
…ong-binary reuse, cover routing branch
Add retools.kb, retools.index, retools.query, retools.ghidra_server, and retools.ghidra_client to the import check module list. All five modules import cleanly without Ghidra installed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ends Documents the Task 1-10 additions (retools.index, retools.query, pyghidra_backend export/kb-apply, ghidra_server/ghidra_client daemon) across all four IDE trees, and reframes decompiler backend guidance from "two peer backends" to "Ghidra primary (indexed, daemon-backed, kb-applied); r2ghidra zero-setup fallback and second opinion." Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…dra daemon state file
…aden query errors
Addresses the findings from the branch-wide review and a real bug found running the full Ghidra path on a real binary (Warband mb_warband.exe). Correctness - ghidra daemon: reject cross-project routing. is_daemon_alive checks the recorded pid before the shared port, and the server validates the game per command (wrong_project -> client cold-falls-back); dropped SO_REUSEADDR so a second daemon can't silently bind an occupied port. - pyghidra export: default --db derives from --project, not the binary stem (was writing patches/<stem>/index.db); _iter_blocks keys blocks by the containing function entry, not the block's own start. - _route_daemon: a socket.timeout now propagates instead of silently cold- retrying into a daemon mid-operation (no double-execution); sends absolute binary/db/kb paths so the daemon can't resolve them against a different cwd. - context.assemble: verify the queried start is a known function before trusting an empty callee result, fall back to the scan on a schemaless index.db (was crashing), recover indirect-call markers, and let a fresh kb.h name outrank the exported Ghidra name. - index.db path anchoring: bootstrap (writer) and context (reader) now resolve the same <project>/index.db from the same --project. - index schema-version: repair a missing version row on open instead of leaving the DB permanently versionless after an interrupted first open. - decompiler.py: put repo root on sys.path so `python retools/decompiler.py` resolves its package imports (script-mode invocation was broken). - kb-apply (found on real Ghidra): remove the manual program.save() that fails with "Unable to lock due to active transaction" inside pyghidra's open_program context; persist via pyghidra's project.save on context exit (cold) / by closing the program (daemon). @ entries at data addresses (RTTI vtables, string refs) now become labels, not bogus functions. Cleanup / conventions - index: add ix_xrefs_from_func; source-priority upsert so re-running bootstrap can't downgrade authoritative Ghidra funcs; narrow catch-alls in GameIndex.close and ghidra_client.read_state. - daemon: single-threaded accept loop (commands already serialize), removing the lock, the close-variant split, the unbounded thread list, and the idle self-connect; reuse ghidra_client's framing; answer malformed frames with an error instead of a silent pass; cache the DecompInterface for warm repeats. - context: parse kb.h once per assemble. - query/index: share resolve_db so both CLIs print the same guidance. - bootstrap: one string sweep feeds both the error-string KB seed and the index seed. - extract _kb_apply_txn shared by cold + daemon paths. Tests: 123 passed / 1 skipped across all changed modules, including new coverage for the daemon lifecycle, identity handshake, source-priority, empty-xrefs fallback, and the kb-apply label path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an idasql-inspired analysis layer to the RE toolkit: a per-game SQLite
index, a read-only SQL front-end, a consolidated
kb.hparser, and a warmper-project Ghidra daemon with transparent routing. Static findings that
used to live only in a flat
kb.hare now also queryable as structured data,and repeat Ghidra work (decompile / export / kb-apply) skips the ~3s JVM cold
start.
The whole branch was reviewed end-to-end and then validated on a real binary
(Mount & Blade: Warband
mb_warband.exe, 32-bit DX9) — which surfaced and fixeda real
kb-applytransaction bug that only appears against actual Ghidra.What's new
retools/index.py— per-game SQLite index (GameIndex)callers/callees/grepviews.bootstrap rows, and re-running bootstrap can never downgrade them back.
index status <Game>CLI.retools/query.py— read-only SQL front-endquery <Game> "SELECT …"overindex.dbvia afile:…?mode=roconnection(can never mutate the index). Text +
--jsonenvelope,--list-tables,--schema TABLE.retools/kb.py— onekb.hparserkb.hgrammar; the three former duplicateparsers (decompiler, context, bootstrap) now delegate to it.
retools/pyghidra_backend.py—export+kb-applyexportseedsindex.dbwith authoritativesource='ghidra'funcs/names/xrefs/blocks.
kb-applypusheskb.hnames/labels/prototypes into the Ghidra project.retools/ghidra_server.py+ghidra_client.py— per-project daemondecompile,export, andkb-applyroute through a live daemon automatically;RETOOLS_GHIDRA_COLD=1/--coldforces cold. Tracks itself inpatches/<Game>/ghidra/.state.json.bootstrap.py/context.pyindex.db(source='bootstrap') from pefile data.context assembleresolves callees fromindex.dbwhen present, fallingback to the disassembly scan otherwise.
Real-world validation (Warband)
Ran the full pipeline on
mb_warband.exe:index.db(9,648 funcs, 35,201 strings, 376 imports) +kb.hanalyzeexport(via daemon)kb-applykb.h, persisted + re-exportedcontext assemblemaster vs this branch, same task: master reaches the same answer but with
16 modules (no
index/query/ghidra_server), a bootstrap that produces noindex.db, ~4s cold decompiles with no warm path, andcontextcalleeslimited to
edx: indirect callplaceholders. The improvements don't change theanswer — they make the toolkit queryable and fast to iterate (SQL over
220k xrefs, 0.15s warm decompiles, named cross-references).
Review findings fixed
Correctness
server-side game-identity handshake + dropped
SO_REUSEADDR.exportdefault--dbderives from--project(was the binary stem →wrong dir);
_iter_blockskeys blocks by containing-function entry._route_daemonpropagatessocket.timeoutinstead of silently cold-retryinginto a mid-operation daemon; sends absolute paths (cwd-independent).
context.assemble: verifies the start is a known function before trusting anempty callee result, falls back on a schemaless
index.db(was crashing),recovers indirect-call markers, and prefers fresh
kb.hnames.index.dbpath anchoring unified; schema-version repaired on interruptedfirst open;
decompiler.pyscript-mode import fixed.kb-applytransaction bug (found on real Ghidra): removed the manualprogram.save()that fails with "Unable to lock due to active transaction"inside pyghidra's
open_programcontext — persist viaproject.saveon exit(cold) / program close (daemon);
@entries at data addresses become labels,not bogus functions.
Cleanup / conventions
ix_xrefs_from_funcindex; narrowed catch-all excepts; single-threaded daemonloop (removes the lock, close-variant split, thread list, idle self-connect);
reused framing; error response on malformed frames; cached DecompInterface;
parse
kb.honce per assemble; sharedresolve_db; one bootstrap stringsweep; extracted shared
_kb_apply_txn.Tests
123 passed / 1 skipped across all changed modules, including new coverage for
the daemon lifecycle + identity handshake, source-priority, empty-xrefs
fallback, and the kb-apply label path.
Still needs manual verification on a Ghidra-equipped host
The real Ghidra
export/kb-applyclass paths and the daemon warm-programlifecycle were exercised on Warband in this branch; a second independent host
run is worthwhile before merge.
🤖 Generated with Claude Code