| name | annotations |
| description | Apply persistent ghidrasql annotations such as names, comments, signatures, and local-variable edits. |
| allowed-tools | ["Bash","Read","Glob","Grep"] |
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:
decompiler to inspect locals and pseudocode before editing
types when a mutation depends on declarations or type imports
re-source when 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, addr).
- 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 save policy; 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() returning 1 is 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_comments on the same host can deadlock on a fair ReentrantReadWriteLock. 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, prototype | – | – |
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 | name (function rows only), prototype | – | – |
breakpoints | enabled, type, size, condition, group | yes | yes |
bytes | value (single-byte patch) | – | – |
segments | start_addr (rebase/move block) | yes | yes |
memory_blocks | start_addr (rebase/move block) | yes | yes |
segments/memory_blocks are the writable memory map (#42): INSERT a region
(start_addr, end_addr [, name, perm]), UPDATE start_addr to rebase/move
it, DELETE to remove it. Both expose the same map — see the data skill.
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',
prototype = 'bool parseConfig(const char *path, Config *out)'
WHERE addr = 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, '<local_id>', 'configFile');
SELECT set_local_type(0x4011F0, '<local_id>', '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 = '<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 (addr, 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 (addr, 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, addr, comment, source)
VALUES (0x4011F0, 0x4012A0, 'fail path: invalid magic', 'pre');
Bookmark (defaults to type='Analysis' if not specified):
INSERT INTO bookmarks (addr, 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
Verification Pattern
SELECT text FROM pseudocode WHERE func_addr = 0x4011F0;
SELECT local_id, name, type FROM decomp_lvars WHERE func_addr = 0x4011F0;
SELECT func_addr, local_id, name, type FROM decomp_lvars WHERE func_name = 'main';
SELECT ordinal, param_name, param_type FROM function_params WHERE func_addr = 0x4011F0;
SELECT save_database();
Name-scoped decomp_lvars reads are useful for discovery, but writes should still use the resolved func_addr plus the exact local_id returned by the read.
Failure and Recovery
- Mutation did not show up: query the exact target table again, re-read
pseudocode, verify you used the canonical local_id / ordinal / addr. If you're inside a batched script and the row is correct in decomp_lvars but pseudocode looks stale, SELECT cache_invalidate('pseudocode'); SELECT text FROM pseudocode WHERE func_addr = 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