Edit radare2 annotations — comments, flag/function names, local variables, bookmarks — via r2xsql. Use when asked to add comments, rename functions/flags, rename or retype locals, or persist analysis state.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Edit radare2 annotations — comments, flag/function names, local variables, bookmarks — via r2xsql. Use when asked to add comments, rename functions/flags, rename or retype locals, or persist analysis state.
allowed-tools
["Bash","Read"]
When to use
Pick this skill any time the task is a mutation: adding/editing/
removing a comment, renaming a function, deleting a flag, persisting
session state. For read-only annotation lookups, use data or
disassembly.
Required: write mode + project
Mutations only land in r2's session memory. To persist them
across reopens you need both:
-w (or --write) when opening the session.
--project NAME so r2xsql can call Ps NAME on shutdown.
r2xsql -w --project demo -s ./malware.exe -q "
UPDATE comments SET text = 'crypto init' WHERE addr = 0x401000;
UPDATE flags SET name = 'aes_init' WHERE addr = 0x401000;
"# reopen — the comment + rename are still there
r2xsql --project demo -s ./malware.exe -q "
SELECT text FROM comments WHERE addr = 0x401000;
"
Without --project, mutations are still visible in the current
session but vanish on exit.
td "<kind> <name> {};" (empty struct/union/enum shell only; use r2xsql_type_define for a full declaration)
—
t- <name>
projects
— (use r2xsql_project_save)
—
P- <name>
Rename functions: UPDATE funcs SET name='…' WHERE addr=… (issues afn).
flags also renames (UPDATE flags SET name='…' WHERE addr=…), always via
fr <old> <new> — radare2's generic in-place rename, regardless of which
flagspace the flag lives in. (This does NOT also drive afn: that command
only renames a flag already parked in the functions flagspace, and
otherwise creates a second, duplicate flag instead — to also rename a
function's own analysis-tracked name, use the funcs table.)
Set a function prototype: UPDATE funcs SET prototype='int parse(hdr_t *h, int len)' WHERE addr=… applies a C signature via afs (set-signature). A trailing ; is
stripped (r2's command separator); an empty/NULL prototype is rejected (r2 has no
clear-signature primitive). The read comes from aflj's signature field.
Set a function's calling convention: UPDATE funcs SET calltype='ms' WHERE addr=… issues afc <cc> (set-calling-convention). cc must be one of
the names SELECT name FROM calling_conventions reports for this session's
(arch,bits) — checked before the command runs, because afc itself gives no
usable signal on a rejected name: its textual output is empty on both a
successful set and an unknown convention. The read comes from aflj's
calltype field; the write is confirmed by reading afij back afterward.
Every funcs write (rename, set-signature, set-calltype, undefine, define) is
address-scoped, so
on the in-process (libr) backend each one also uses the same structural
command-dispatch path comments writes use — never evaluating r2's own command
separators at all, as a second, independent layer beneath the name/signature
validation above. The pipe backend is unaffected: it always uses the plain command
form.
flags' INSERT (f) and DELETE (f-) use the same structural dispatch (address-
scoped, like funcs/comments). Its rename does not issue fr at all on the
in-process (r2xsql-full) flavor — it calls radare2's rename function directly
(no address needed for that call either), measured substantially faster on a
bulk-rename workload with identical results. The pipe-only flavor still issues
, seeked to the flag's own address (a harmless anchor never consults).
Persistence functions
SELECT r2xsql_project_save('triage1'); -- Ps: save the session as a projectSELECT name FROM projects; -- Plj: list saved projectsSELECT r2xsql_project_open('triage1'); -- P: load a project mid-sessionSELECT r2xsql_type_define('struct hdr { int magic; int size; }'); -- td
Common operations
-- rename a function (the dummy fcn.<addr> names)UPDATE funcs SET name ='aes_decrypt'WHERE addr =0x401000;
-- rename / retype a local variable or argument (keyed on func_addr + name)UPDATE locals SET name ='key_len'WHERE func_addr =0x401000AND name ='var_20h';
UPDATE locals SET type ='int'WHERE func_addr =0x401000AND name ='key_len';
-- add / edit / remove a commentINSERT INTO comments(addr, text) VALUES (0x401000, 'crypto init');
UPDATE comments SET text ='crypto init v2'WHERE addr =0x401000;
DELETEFROM comments WHERE addr =0x401000;
-- add a flag / remove oneINSERT INTO flags(addr, name) VALUES (0x401000, 'aes_sbox');
DELETE flags addr ;
bookmarks (addr, name) (, );
bookmarks name addr ;
bookmarks addr ;
Bulk operations
UPDATE / DELETE happily take any SQL predicate, so bulk operations
just work:
-- mark every function whose name contains 'init' as analyzedUPDATE comments SET text ='TODO: review init logic'WHERE addr IN (SELECT addr FROM funcs WHERE name LIKE'%init%');
-- delete every comment in a specific code sectionDELETEFROM comments
WHERE addr IN (
SELECT addr FROM funcs
WHERE addr BETWEEN0x401000AND0x402000
);
Caveats
comments, flags, and bookmarks support INSERT. Use INSERT
when adding a new address/name pair; UPDATE only affects rows that
already match the predicate.
Changes are sequenced — r2xsql issues one r2 command per affected
row. For thousands of rows this is slow; chunk the predicate.
fr
fr
bookmarks' INSERT (f) and DELETE (f-) use the same structural dispatch
(address-scoped) inside the pre-existing flagspace save/select/restore below.
Its rename (fr) is addr-less in the same way flags' is, and routes through
the dispatch the same way — seeked to the bookmark's own address, which fr
never consults, needing no new primitive. The pipe backend is unaffected on all
three writes.
types' DELETE (t-) and INSERT (td, including the general
r2xsql_type_define('<C decl>')) also use the structural dispatch, but
neither command has ANY address in its own grammar at all (a type
definition isn't tied to any address) — unlike every table above, there is
no row address to seek to, so both are seeked to a fixed anchor address (0)
instead, which neither command ever consults. The pipe backend is
unaffected on both.
Rename / retype locals & args: UPDATE locals SET name='…' or
SET type='…', keyed on func_addr + the current name. Locals
are analysis-derived — no INSERT/DELETE. The type is passed verbatim (r2 takes
a bare int / char *; do NOT quote it), validated to [A-Za-z0-9_.* []].
The pipe-only flavor issues afvn/afvt; r2xsql-full calls radare2's
rename/retype functions directly instead — same results, measured
substantially faster on a bulk rename/retype workload. On the in-process
(libr) backend, whenever that direct call is unavailable, the command-path
fallback (afvn/afvt, address-scoped like funcs's writes) also uses
the same structural command-dispatch path — never evaluating r2's own
command separators at all, as a second, independent layer beneath the
name/type validation above. The pipe backend is unaffected: it always uses
the plain command form.
Comments are passed as base64: so spaces, quotes, and @ are stored
verbatim and can't inject extra r2 commands, on the pipe-only flavor.
r2xsql-full sets/updates a comment by calling radare2's own comment-write
function directly instead of building that command — no encoding needed for
that call — while reproducing one existing behavior byte-for-byte: an
UPDATE whose new text is already contained inside the EXISTING comment is a
silent no-op on both flavors (this is radare2's own long-standing
dedup-on-write behavior, not something either flavor invented). On the
in-process (libr) backend, comment writes that DO fall back to the command
form also use a structural command-dispatch path that never evaluates r2's
own command separators at all, as a second, independent layer beneath the
encoding above.
Reading comments on the in-process (libr) backend reads radare2's
meta store directly instead of parsing CCj's JSON — same rows, faster,
unrelated to the write-path note above. comments.type always reads back
the literal string CCu (radare2's own comment-type tag, not a description
of the comment); comments.text reads back with a newline/tab/quote/
backslash or non-ASCII byte escaped (e.g. a real newline as the two
characters \n) — this is radare2's own command-path behavior on both
backends, not something either producer invents.
Names must be [A-Za-z0-9._$] (no spaces / r2 metacharacters) — enforced.
bookmarks writes are scoped to the bookmarks flagspace (saved/selected/
restored around each write); id is synthetic and ignored.