connect
Connect to ghidrasql sources, verify live access, and route to the right analysis skill.
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Menú
Connect to ghidrasql sources, verify live access, and route to the right analysis skill.
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Basado en la clasificación ocupacional SOC
Analyze binaries with ghidrasql using safe, high-signal query patterns.
Apply persistent ghidrasql annotations such as names, comments, signatures, and local-variable edits.
Query strings, bytes, data items, memory blocks, and relocations through ghidrasql.
Manage breakpoints and patch bytes through ghidrasql — the breakpoints table and bytes single-byte UPDATE.
Decompile functions with ghidrasql and work with pseudocode, locals, parameters, and ctree pattern views safely.
Inspect code structure through ghidrasql tables for functions, instructions, blocks, CFG, loops, switches, dominators, and tail calls.
| name | connect |
| description | Connect to ghidrasql sources, verify live access, and route to the right analysis skill. |
| allowed-tools | ["Bash","Read","Glob","Grep"] |
Entry point for every ghidrasql session. Bootstrap the source, verify the program is bound, then hand off to the right domain skill.
Use this skill when the user wants to:
ghidrasql sessionLibGhidraHostOnce binary and funcs > 0 come back, route by user intent:
| Intent | Skill | First query |
|---|---|---|
| triage / "what does this binary do?" | analysis | SELECT * FROM binary; SELECT name, size FROM funcs ORDER BY size DESC LIMIT 20; |
| disassembly / blocks / CFG | disassembly | SELECT start_addr, end_addr FROM blocks WHERE func_addr = 0xX; |
| callers / callees / call graph / xrefs | xrefs | SELECT * FROM callgraph_edges WHERE src_func_addr = 0xX; |
| find by name pattern | grep | SELECT name, addr FROM funcs WHERE name LIKE '%X%'; |
| strings / bytes / data items / hexdump | data | SELECT * FROM strings WHERE content LIKE '%X%' LIMIT 20; |
| decompile / pseudocode / locals / params | decompiler | SELECT text FROM pseudocode WHERE func_addr = 0xX; |
| ctree / AST patterns | decompiler | SELECT * FROM ctree_v_calls WHERE func_addr = 0xX; |
| low-level P-code / IR / dataflow | decompiler | SELECT seq, op, addr FROM ir_ops WHERE func_addr = 0xX ORDER BY seq; |
| rename / comment / signature / lvars | annotations | READ first, then UPDATE, then SELECT save_database(); |
| struct / enum / typedef / parse C | types | SELECT parse_decls('...'); SELECT * FROM type_members WHERE parent_type_name = 'X'; |
| breakpoints / patch bytes | debugger | SELECT * FROM breakpoints; |
| recursive bottom-up source recovery | re-source | starts with leaf callees and iterates |
| runtime knobs / query timeout | connect | SELECT key, value FROM runtime_settings; UPDATE runtime_settings SET value='5000' WHERE key='query_timeout_ms'; |
| SQL helper lookup | functions | reference catalog (no warm-start) |
These five contracts apply across every ghidrasql skill. Skip them at your peril — they prevent the most common failure modes.
Every mutation starts with a SELECT that yields the exact write coordinates. Resolve func_addr, local_id, addr from a query, never from guesswork.
SELECT local_id, name, type FROM decomp_lvars WHERE func_addr = 0x401000;
SELECT func_addr, local_id, name, type FROM decomp_lvars WHERE func_name = 'main';
-- now use the literal local_id from the result, not a guess
Use .tables, .schema <table>, and PRAGMA table_xinfo(<table>) before issuing uncertain queries. ghidrasql exposes 65 public virtual tables, 81 views, and 21 SQL functions — when a query fails, introspect first, then retry.
SELECT type, name, ncol FROM pragma_table_list WHERE schema='main';
PRAGMA table_xinfo('decomp_lvars');
SELECT name, narg FROM pragma_function_list WHERE name LIKE 'search_%';
Read → Write → SELECT save_database(); → re-read to verify.
SELECT name, prototype FROM funcs WHERE addr = 0x4011F0; -- read
UPDATE funcs SET name = 'parseConfig' WHERE addr = 0x4011F0; -- write
SELECT save_database(); -- commit
SELECT name FROM funcs WHERE addr = 0x4011F0; -- verify
Caveat — verify saves explicitly: save_database() returning 1 does not always mean the change persisted (in some configurations the save is silently dropped on reopen). To prove a critical write survived, run this loop:
SELECT save_database();
-- shut the host down (POST /shutdown for the SQL server, or exit the REPL)
-- reconnect: ghidrasql --ghidra ... --readonly --no-analyze ...
SELECT name FROM funcs WHERE addr = 0xX; -- compare against the post-write value
If the post-reopen value matches, the save is durable. If it reverts, re-apply the mutation and try again before continuing.
| Surface | Mandatory predicate | If you skip it |
|---|---|---|
pseudocode, decomp_lvars | WHERE func_addr = X or exact WHERE func_name = 'name' | Decompiles every function. At ~500 ms per function, a binary with thousands of functions takes many minutes per query |
decomp_tokens, decomp_comments | WHERE func_addr = X | Same |
blocks, cfg_edges | WHERE func_addr = X | Per-function CFG built for every function |
instructions, instruction_operands | WHERE func_addr = X or exact WHERE addr = X (pushed down) | A range or mnemonic-only predicate scans the whole corresponding table |
xrefs, xref_index | WHERE from_addr = X or to_addr = X (post-filter scan) | Cache build over every xref edge |
bytes | addr = X or range (streamed window) | Streams every mapped byte — bounded memory, but time proportional to the whole image |
comments | addr = X or range (post-filter scan) | Whole-comment-table scan |
pseudocode/decomp_lvars/blocks/cfg_edges/instructions/instruction_operands push the function filter (func_addr) into the source — without it, you trigger work proportional to the whole program. For pseudocode and decomp_lvars, exact func_name = ... is also pushed down; instructions and instruction_operands additionally push exact addr. An instruction address range and instructions.mnemonic are client-side scans, not bounded source reads. xrefs/comments are post-filter scans — the filter narrows the result set but not the work. bytes is a streaming windowed generator: a point (addr =) or range predicate streams only the requested window, while an unconstrained scan streams the whole image (bounded memory, but time proportional to every mapped byte). When the question is function-scoped, prefer the narrow entry-point views (disasm_calls, disasm_blocks, function_calls, string_refs, callgraph_edges) over raw tables.
See references/cost-model.md for the full table with file:line citations.
| Symptom | Fix |
|---|---|
funcs returns 0 rows | Program isn't bound — pass --program <name> (or --binary to import) and restart the host |
| Imported PE's external imports show as ordinals, or library symbols unresolved | Imports skip loading external system libraries by default. Re-import with --load-libraries (or POST /project/import {"load_libraries": true}) to load/link kernel32/CRT (slower) |
| Need to see every program in a project | Run ghidrasql ... --list-project-programs, or query SELECT path,name FROM project_programs ORDER BY path; |
--url rejected with "mutually exclusive" | unset GHIDRA_INSTALL_DIR (or env -u GHIDRA_INSTALL_DIR ghidrasql --url ...) and retry |
analyzeHeadless never prints LIBGHIDRA_HEADLESS_READY | Check the log for an exception or unsupported binary format. CLI-spawned hosts now bind an auto (ephemeral) RPC port, so several instances no longer collide there; the SQL HTTP port (default 8081) can still conflict. Pass --rpc-port N only if you need to pin the RPC port |
Stale *.lock / *.lock~ after force-kill | Confirm java.exe is gone, then delete both lock files |
| GUI host stalls under concurrent decompiler reads | Overlapping requests against pseudocode/decomp_lvars/decomp_comments can deadlock the host on a fair ReentrantReadWriteLock. Workaround: serialise decompiler-backed queries against the same host; do not run two ghidrasql clients hitting decompiler tables in parallel |
| Query returns stale rows after a write or external edit | Check SELECT program_revision() and SELECT cache_stats();. Libghidra live sources refresh automatically when the native freshness token changes; use SELECT cache_invalidate('<table>'); or refresh_database() to force a rebuild, especially inside batched scripts |
Host hangs after a wide comments range query | getComments() over wide ranges can hang the host. Use exact-addr WHERE addr = X instead of range scans |
/query HTTP layer wedges while /health still answers | Recovery path: spawn a fresh ghidrasql --url http://127.0.0.1:<rpc-port> directly against the LibGhidraHost RPC port (unset GHIDRA_INSTALL_DIR first if set). The new process bypasses the wedged HTTP layer and can run SELECT save_database() to commit pending state before the orphaned tree is killed |
| Individual RPC wedges (e.g. a pathological signature parse) tie up the worker | Lower the per-RPC timeout via --rpc-timeout-ms <N> (default 0 → libghidra's 120 s). Wedged calls fail with a timeout error and the worker frees up; observable via /health/deep |
| Local rename / type apply over-propagates through reused decompiler temporaries | The decompiler shares storage between source-level variables that happen to use the same register/stack slot, so apply_type_local (or a direct decomp_lvars UPDATE) on one site can change unrelated locals. Mitigation: back the local out to a neutral type (e.g. char *) and keep the recovered struct on prototypes; verify in pseudocode after each edit |
Pointer-return signature update fails with Can't parse name: *fn | Ghidra's CParser tokenises <type> *fn(...) differently from <type>* fn(...). Workaround: use char* fn form (asterisk on the type, not the name). Same applies to void **, int *, etc. |
main, 135 bytes"); show supporting rows only when they help the user verify; don't dump full tables unprompted; never surface data fetched only as an intermediate reasoning step./query response is a JSON envelope ({success, results:[{columns,rows,...}]}). Consume it directly and render in your reply. Do not pipe responses through python/jq to pre-render a table — that discards the success/elapsed_ms/error fields and makes you reason over a lossy view. The CLI (-q/-f) already prints a table. Reserve jq/python for extracting a value to feed a later query. (For direct terminal/pipe use the server can emit ?format=text|csv|tsv; as an agent, consume json.)If GHIDRA_INSTALL_DIR is set in the environment, the CLI auto-fills --ghidra from it and rejects --url as conflicting. For any --url-mode invocation either prefix with unset GHIDRA_INSTALL_DIR; or use env -u GHIDRA_INSTALL_DIR ghidrasql --url ....
Prove the source is alive and learn the current program:
ghidrasql --url http://127.0.0.1:18080 -q "SELECT * FROM binary;"
ghidrasql --url http://127.0.0.1:18080 -q "SELECT COUNT(*) AS n FROM funcs;"
Treat funcs > 0 as the real "program is bound" signal. binary.program_name may legitimately read "active-program" (host placeholder) — that alone is not a failure.
Three mutually exclusive modes. Pick one.
--url)ghidrasql --url http://127.0.0.1:18080 # REPL
ghidrasql --url http://127.0.0.1:18080 -q "<sql>" # one-shot
ghidrasql --url http://127.0.0.1:18080 -f script.sql # script
--ghidra)ghidrasql --ghidra <ghidra_dist> \
--project <project_dir> --project-name <name> \
--program <prog>.exe --no-analyze \
-q "SELECT name FROM funcs LIMIT 5;"
Use Ghidra domain paths for multi-program projects:
ghidrasql --ghidra <ghidra_dist> \
--project <project_dir> --project-name <name> \
--program /samples/payload.exe --no-analyze \
-q "SELECT * FROM binary;"
Seed the session (managed vs one-shot). Headless launch requires only
--project+--project-name; a seed (--binaryto import or--programto reopen) is not enforced. A one-shot-qwith no seed does not error — it attaches to the project with no active program and every analysis table (funcs,pseudocode,xrefs, …) returns 0 rows. This is intentional for managed sessions that attach to an already-open program (e.g. a live Ghidra GUI host). For a self-contained one-shot, always pass--binary/--programso the tables are populated.
Library loading.
--binaryimports skip loading external system libraries by default (kernel32, CRT, …) to keep imports fast. Imports referenced by name still resolve; only imports-by-ordinal show as ordinals instead of names. Pass--load-librariesto load and link them (slower, and pulls the libraries into the project). This applies to the CLI flag and thePOST /project/importbody field"load_libraries": true.
--binary and --program are repeatable. A host still has one active program; use --program to choose the active project domain path after imports:
ghidrasql --ghidra <ghidra_dist> \
--project <project_dir> --project-name <name> \
--binary loader.dll --binary payload.exe \
--program /payload.exe --http
List project programs without changing the program-scoped SQL model:
ghidrasql --url http://127.0.0.1:18080 --list-project-programs
ghidrasql --url http://127.0.0.1:18080 \
-q "SELECT path,name,folder_path FROM project_programs ORDER BY path;"
All analysis tables (funcs, pseudocode, xrefs, etc.) describe the active program only. To switch in HTTP mode, save if needed, then call POST /project/open with {"program_path":"/path/in/project"}; for one-shot/REPL mode, re-invoke ghidrasql with another --program.
ghidrasql --ghidra <ghidra_dist> \
--project <project_dir> --project-name <name> \
--program <prog>.exe --no-analyze \
--http --port 8081 --max-runtime 0 &
--http serve mode already defaults to no auto-exit timer (--max-runtime 0), so it runs until you stop it (Ctrl-C or POST /shutdown) — the explicit --max-runtime 0 above is redundant but harmless. The SQL endpoint becomes POST http://127.0.0.1:8081/query (default port). For read-only experiments add --readonly, which implies --shutdown discard and prevents accidental mutations being saved.
| URL | What it speaks | Who talks to it |
|---|---|---|
http://127.0.0.1:18080 | LibGhidraHost protobuf RPC | ghidrasql only |
http://127.0.0.1:8081/query | raw SQL (POST body) | any HTTP client |
18080 is the conventional port for a standalone LibGhidraHost you start yourself and connect to with --url. A host spawned by the CLI (--ghidra) binds an auto-assigned (ephemeral) RPC port by default (no fixed-18080 collisions across concurrent instances); pin it with --rpc-port N if you need a known port.
Wrong: curl -X POST http://127.0.0.1:18080/query ... — the upstream port speaks RPC, not SQL.
/query Response Envelope (canonical, single = array of one)All /query responses use the same shape. Single-statement bodies come back as an array of one entry:
{
"success": true,
"statement_count": <N>,
"results": [
{ "statement_index": 0, "success": true,
"columns": [...], "rows": [...], "row_count": <N>,
"elapsed_ms": <ms>, "error": null },
...
],
"row_count_total": <N>,
"elapsed_ms_total": <ms>,
"first_error_index": null
}
results[] stops at the failure; first_error_index points at it. The remaining statements are NOT executed.continue_on_error=1 runs every statement; each gets its own success / error entry in results[].include_sql=1 echoes each statement's SQL back in its results[i].sql field — useful for long scripts./query?continue_on_error=1&include_sql=1, body = raw SQL) or in a JSON request body: {"sql": "...", "continue_on_error": true, "include_sql": true}. With Content-Type: application/json the body must be a JSON object carrying a string sql — a malformed or sql-less body is rejected with 400 (it is not silently run as raw SQL). A body sent without that content type is treated as raw SQL; a valid leading-{ JSON-with-sql body is still accepted as a compatibility fallback, but prefer sending Content-Type: application/json for JSON requests.success:false, statement_count:0, results:[], plus a top-level parse_error string.The SQL HTTP server exposes project-control endpoints in managed mode, in addition to SQL/query lifecycle endpoints:
| Method | Path | Purpose |
|---|---|---|
| GET | / | Welcome + brief help |
| GET | /help | Endpoint documentation |
| POST | /query | Execute SQL (body = raw SQL or JSON {sql,continue_on_error,include_sql}, single or semicolon-separated script, response = canonical envelope) |
| GET | /status | Server status + program info |
| GET | /health | Liveness probe ({"status":"ok"}) — does not probe the query worker |
| GET | /health/deep | Readiness probe — reflects query-worker state, returns 503 when the oldest in-flight query exceeds the configured threshold |
| GET | /shutdown/status | Lifecycle observability — phase (idle / http_stopping / java_exiting / complete / force_killed), listener_running. Useful for polling during managed-mode shutdowns |
| POST | /refresh | Drop ghidrasql caches and reload |
| GET | /project/programs | List project programs when ghidrasql owns a project host |
| GET | /project/active | Show the active project program |
| POST | /project/import | Import a binary into the live project ({"source_path":"...","analyze":true,"load_libraries":false}). load_libraries (default false) loads/links external system libraries |
| POST | /project/open | Switch the active program ({"program_path":"/domain/path"}) |
| POST | /project/close | Close the active program (`{"shutdown_policy":"save |
| POST | /shutdown | Stop the SQL server (see Session End for managed-vs-proxy semantics) |
Host build pairing (no capability negotiation). The
/project/*routes and the writable memory-map surface ride newer libghidra client RPCs (ImportProgram/OpenProgram/CloseProgram/ListProjectFiles/GetRevisionand the memory-block RPCsCreateMemoryBlock/RemoveMemoryBlock/MoveMemory Block, plusLaunchHeadlessProject). There is no version handshake: against an olderLibGhidraHost/libghidrathat predates these RPCs, those calls fail with an RPC error (surfaced assuccess:false, no crash) rather than degrading. Buildghidrasqland theLibGhidraHostextension from the same libghidra revision so the client and host RPC sets match. If/project/*or a memory-map write returns an unexpected RPC error, suspect a stale host build first.
Save before stopping (skip this if you started with --readonly):
SELECT save_database();
Stop the HTTP server:
curl -X POST http://127.0.0.1:8081/shutdown
/shutdown returns {"success":true} once the listener is stopping. What happens next depends on launch mode:
--ghidra ... --http): ghidrasql owns both the SQL server and the upstream headless Java host. After the SQL server stops, the headless host is closed using the launch-time --shutdown save|discard|none policy (default save). For large pending state this can take many seconds. Wait for both java.exe and ghidrasql.exe to actually exit before reusing the project directory.--url ... --http): ghidrasql owns only the SQL proxy. /shutdown stops the proxy. The upstream LibGhidraHost is untouched — it keeps running. Stop it via its own RPC or kill the Java process if you own it.Never taskkill /F Java without saving first — that discards changes since the last save_database() and leaves orphaned *.lock / *.lock~ files in the project dir.
These exist in adjacent tooling (idasql) but are explicit non-features here:
idapython() analog. Java-side scripting is not exposed through ghidrasql.ui-context() function. The GUI plugin does not export selection / widget / cursor state.shutdown() SQL function. Exit policy is fixed at launch (--shutdown save|discard|none); trigger the actual stop with POST /shutdown.decompile SQL helper. Use pseudocode for full-function text. Each /query invocation refreshes relevant tables as needed; inside a batched script, call SELECT cache_invalidate('pseudocode'); before re-reading SELECT text FROM pseudocode WHERE func_addr = ... if the first read materialised the cache.ghidrasql --url http://127.0.0.1:18080 (or --ghidra ...) drops you into an interactive REPL. Nine dot-commands:
| Command | What |
|---|---|
.help / .h | Help text |
.tables | List virtual tables |
.schema <table> | Show columns for one table |
.info | Server + program metadata (same as /status) |
.save | save_database() shortcut |
.discard | discard_changes() shortcut |
.refresh | refresh_database() shortcut |
.http start / .http stop | Start / stop the embedded SQL HTTP server |
.quit / .exit / .q | Leave the REPL |
# CLI one-shot
ghidrasql --url http://127.0.0.1:18080 \
-q "SELECT name, printf('0x%X', addr) AS addr FROM funcs LIMIT 10;"
# HTTP one-shot (requires --http server already running)
curl -X POST http://127.0.0.1:8081/query \
--data "SELECT name, prototype FROM funcs LIMIT 5;"
# Read-only experiment (no save, host discards on exit)
ghidrasql --ghidra "$GHIDRA_INSTALL_DIR" \
--project /path/to/proj --project-name myproj --program sample.exe \
--no-analyze --readonly --http --port 8081 --max-runtime 0 &