Install
$ agentstack add skill-0xeb-ghidrasql-skills-annotations ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Annotations
Trigger Intents
Use this skill when the user asks to:
- rename a function, symbol, parameter, or local
- add comments
- apply or adjust a function signature
- tag functions
- apply types via the
apply_type_*views - persist cleanup into the Ghidra database
Route to:
decompilerto inspect locals and pseudocode before editingtypeswhen a mutation depends on declarations or type importsre-sourcewhen the campaign expands beyond a single function
Mandatory Mutation Loop
- Read the current function or target rows.
- Resolve exact write coordinates (
func_addr,local_id,ordinal,address). - Batch the writes.
- Re-read the affected surfaces.
SELECT save_database();once at the end.- To end the session:
curl -X POST http://127.0.0.1:8081/shutdown(managed mode applies the launch-time--shutdown savepolicy; proxy mode leaves the upstream host running).
There is no SQL shutdown(...) function.
Two save-time gotchas to know about
- Verify saves explicitly.
save_database()returning1is not always proof the change persisted — in some configurations the save is silently dropped on reopen. For critical edits, save → shut the host down → reconnect with--readonly --no-analyze→ re-query the affected rows and compare against the post-write values. See the connect skill's mutation-loop section for the full recipe. - GUI host stalls under concurrent decompiler reads. Two ghidrasql clients simultaneously hitting
pseudocode/decomp_lvars/decomp_commentson the same host can deadlock on a fairReentrantReadWriteLock. Serialise decompiler-backed work — do not run two parallel annotation pipelines against one host.
Writable Surface (verified per-table)
| Table | UPDATE columns | INSERT | DELETE | |---|---|---|---| | funcs | name, signature | – | – | | names | name | yes | yes | | comments | comment, repeatable, source | yes | yes | | data_items | name, data_type | yes | yes | | bookmarks | type, category, comment | yes | yes | | decomp_lvars | name, type | – | – | | decomp_comments | comment, source | yes | yes | | function_params | param_name, param_type | – | – | | function_tags | comment | yes | yes | | function_tag_mappings | – | yes | yes | | signatures | prototype only (not name) | – | – | | breakpoints | enabled, type, size, condition, group | yes | yes | | memory_bytes | value (single-byte patch) | – | – |
Type tables (types, type_members, type_enums, type_enum_members, type_unions, type_aliases) support INSERT and DELETE; UPDATE is mostly limited to name (full per-table matrix in the types skill).
INSERT-only views: apply_type_data, apply_type_param, apply_type_local — INSTEAD OF INSERT triggers that wrap the underlying type write.
Canonical Mutation Surfaces
Function rename and signature:
UPDATE funcs
SET name = 'parseConfig',
signature = 'bool parseConfig(const char *path, Config *out)'
WHERE address = 0x4011F0;
Parameter rename or type:
UPDATE function_params
SET param_name = 'path',
param_type = 'const char *'
WHERE func_addr = 0x4011F0 AND ordinal = 0;
Decompiler local rename or type — prefer the SQL helpers (direct RPC, no re-decompilation between statements):
SELECT rename_local(0x4011F0, '', 'configFile');
SELECT set_local_type(0x4011F0, '', 'FILE *');
Alternative via UPDATE (works for the name; type rewrites can fail on array locals — see Gotchas):
UPDATE decomp_lvars
SET name = 'configFile'
WHERE func_addr = 0x4011F0 AND local_id = '';
Apply a type via the dedicated view (compositional — works even when the agent only knows the type name):
INSERT INTO apply_type_data (address, type_name) VALUES (0x404000, 'IMAGE_DOS_HEADER');
INSERT INTO apply_type_param (func_addr, ordinal, type_name) VALUES (0x4011F0, 0, 'const char *');
INSERT INTO apply_type_local (func_addr, local_id, type_name) VALUES (0x4011F0, 'arg2', 'FILE *');
Comment insertion (the source column controls the comment kind: 'plate', 'pre', 'post', 'eol', 'repeatable'):
INSERT INTO comments (address, comment, source)
VALUES (0x4011F0, 'Reads and parses the configuration file.', 'plate');
Decompiler-attached comments (anchored at decompiler tokens, not raw addresses):
INSERT INTO decomp_comments (func_addr, address, comment, source)
VALUES (0x4011F0, 0x4012A0, 'fail path: invalid magic', 'pre');
Bookmark (defaults to type='Analysis' if not specified):
INSERT INTO bookmarks (address, type, category, comment)
VALUES (0x4011F0, 'Note', 'review', 'check overflow handling');
Function tags (catalog first, then map):
INSERT INTO function_tags (name, comment) VALUES ('reviewed', 'manually reviewed');
INSERT INTO function_tag_mappings (func_addr, tag_name) VALUES (0x4011F0, 'reviewed');
Tag deletion cascades through both tables: DELETE FROM function_tags WHERE name = 'reviewed'; removes every mapping.
Gotchas
signaturesis read-mostly. Onlyprototypeis writable. To rename the function itself,UPDATE funcs SET name = .... To change the calling convention, write a full prototype string into eitherfuncs.signatureorsignatures.prototype.__cdeclin a prototype string can fail with a vague diagnostic. Ifset_function_signature at 0xNerrors without a parse detail, drop the calling-convention prefix and retry — the signature without__cdecltypically applies cleanly.- Renaming an array local via
UPDATE decomp_lvars SET name = ...may fail with "unable to resolve writable data type 'wchar_t[128]'". The UPDATE path re-applies the current type during the rename, and Ghidra refuses arrays in some contexts. Fall back to:
``sql SELECT rename_local(0x4011F0, '', 'configFile'); `` which goes through a direct RPC and ignores the type.
- Unsigned C type aliases (
uint8_t/uint16_t/uint32_t/unsigned/unsigned int) are mis-mapped in older builds ofparse_decls(). Current builds resolve them correctly; if you hit a width mismatch, suspect an outdated build. - Caches are freshness-token scoped on libghidra live sources. One-shot reads can reuse materialised tables while the native token is unchanged. Writes through ghidrasql and external Ghidra/libghidra edits change Ghidra's modification number, while program switches change
program_id, so the next query refreshes automatically. Inside a-fscript or multi-statement REPL input, drop just the table you wrote to before reading it again when you need to force a rebuild:
``sql SELECT cache_invalidate('decomp_lvars'); ` Cheaper than refresh_database()` when only one surface is stale.
- Pointer-return signature parser is whitespace-sensitive. Ghidra's CParser tokenises
*fn(...)as a name*fnrather than a return type. Use* fn(...)form when authoring prototypes —char* foo,void** bar,int* baz. Same trap applies to multi-statement parameter lists. Verify the apply landed by re-readingfuncs.signatureorfunction_params. - Type apply can over-propagate through reused decompiler temporaries. Ghidra shares storage between source-level variables that happen to use the same register/stack slot. Applying a type via
apply_type_local(or a direct UPDATE ondecomp_lvars) at one site can change unrelated variables. Mitigation: keep the recovered type on prototypes/parameters, back the local out to a neutral type (char *,int) at the questionable site, and verify each step inpseudocodebefore continuing.
Verification Pattern
SELECT text FROM pseudocode WHERE func_addr = 0x4011F0;
SELECT local_id, name, type FROM decomp_lvars WHERE func_addr = 0x4011F0;
SELECT ordinal, param_name, param_type FROM function_params WHERE func_addr = 0x4011F0;
SELECT save_database();
Failure and Recovery
- Mutation did not show up: query the exact target table again, re-read
pseudocode, verify you used the canonicallocal_id/ordinal/address. If you're inside a batched script and the row is correct indecomp_lvarsbutpseudocodelooks stale,SELECT cache_invalidate('pseudocode'); SELECT decompile(0xX);. - Multiple changes needed: put them in one script and
save_database()once at the end. - Save reported success but the change is gone after reopen: the save was silently dropped. Re-apply the mutation, run the save → shutdown → reconnect verification recipe again, and confirm the post-reopen value matches before continuing.
Additional Resources
- Write workflow and supported surfaces: [references/write-workflow.md](references/write-workflow.md)
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: 0xeb
- Source: 0xeb/ghidrasql-skills
- License: MPL-2.0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.