| name | debugger |
| description | Manage breakpoints and patch bytes through ghidrasql — the breakpoints table and bytes single-byte UPDATE. |
| allowed-tools | ["Bash","Read","Glob","Grep"] |
Debugger
ghidrasql exposes Ghidra's breakpoint metadata table and a single-byte patch path through bytes.value. This is not an active debugger control plane — Ghidra itself is not a runtime debugger in the JTAG sense — but it covers static breakpoint definition for tooling that consumes the project (e.g. running it under a real debugger later) and direct byte patching.
Trigger Intents
Use this skill when the user asks to:
- list, add, modify, or delete breakpoints
- patch bytes at an address
- inspect or audit existing patches
- review
bookmarks (lightweight notes that often double as patch markers)
Route to:
disassembly for context around a breakpoint or patch site
annotations to document a patch via a comment or bookmark
analysis for triage that uncovered the patch
What ghidrasql Does NOT Provide
- No live debugger control. There is no
step, no continue, no register read/write.
- No watchpoint. The closest analogue is a
breakpoints.type value reflecting the kind, but ghidrasql does not exercise it.
- No multi-byte atomic patch.
bytes writes one byte at a time. For multi-byte patches, batch UPDATEs in a single transaction-like script.
Breakpoints
breakpoints is a writable virtual table. 11 columns:
| Column | Type | Writable? |
|---|
addr | int | – (key) |
enabled | int | UPDATE |
type | int | UPDATE |
type_name | text | – (computed from type) |
size | int | UPDATE |
flags | int | – |
pass_count | int | – |
condition | text | UPDATE |
group | text | UPDATE |
loc_type | int | – |
loc_type_name | text | – (computed from loc_type) |
INSERT signature (positional argv):
INSERT INTO breakpoints (addr, enabled, type, size, condition, "group")
VALUES (0x401234, 1, 0, 1, '', 'review');
Required: addr. Defaults: enabled = 1, type = 0, size = 1, condition = '', group = ''.
Common queries:
SELECT printf('0x%X', addr) AS at, enabled, type_name, size, condition, "group", loc_type_name
FROM breakpoints
ORDER BY addr;
UPDATE breakpoints SET enabled = 0 WHERE addr = 0x401234;
UPDATE breakpoints
SET condition = 'EAX == 0',
"group" = 'crashes'
WHERE addr = 0x401234;
DELETE FROM breakpoints WHERE addr = 0x401234;
To discover the actual type and loc_type integer values used by Ghidra on this binary, consult type_name / loc_type_name:
SELECT DISTINCT type, type_name FROM breakpoints;
SELECT DISTINCT loc_type, loc_type_name FROM breakpoints;
Empty until at least one breakpoint exists — set one through the GUI or via INSERT first.
Byte Patching via bytes
bytes.value is the single writable byte column. Each UPDATE patches one address:
UPDATE bytes SET value = 0x90 WHERE addr = 0x401234;
SELECT printf('0x%X', addr) AS at, value, ascii, is_printable
FROM bytes
WHERE addr = 0x401234;
Multi-byte patch (NOP a 5-byte instruction):
UPDATE bytes SET value = 0x90 WHERE addr = 0x401234;
UPDATE bytes SET value = 0x90 WHERE addr = 0x401235;
UPDATE bytes SET value = 0x90 WHERE addr = 0x401236;
UPDATE bytes SET value = 0x90 WHERE addr = 0x401237;
UPDATE bytes SET value = 0x90 WHERE addr = 0x401238;
SELECT save_database();
There is no separate WriteBytes / PatchBytes SQL surface — bytes.value is the path.
Only initialized bytes are patchable. bytes now surfaces every mapped
byte, including uninitialized-block bytes where value is NULL and
is_initialized = 0; an UPDATE against such a byte is rejected (no backing
storage until the block is initialized). Patch targets should carry
is_initialized = 1.
Patch Inventory
To find bytes that have been patched (i.e. differ from the original image), join bytes against itself by source_kind:
SELECT DISTINCT source_kind FROM bytes WHERE source_kind IS NOT NULL LIMIT 10;
Document each patch with a bookmark so the patch survives rediscovery:
INSERT INTO bookmarks (addr, type, category, comment)
VALUES (0x401234, 'Note', 'patch', 'NOPed magic check at the entry to authValidate');
Performance and Safety
bytes is a streaming windowed generator. Keep the guard WHERE addr = X (or a tight range) on every UPDATE so only the requested window is streamed; an unconstrained scan still visits every mapped byte.
- Patches are persisted only when you call
SELECT save_database(); (or run with --shutdown save, the default in managed mode).
- For read-only audit sessions, run the host with
--readonly --shutdown discard so accidental UPDATEs cannot persist.
Failure and Recovery
UPDATE bytes returned without error but the byte didn't change. Re-read the same address and check program_revision() / cache_stats(). Libghidra live sources refresh automatically when Ghidra's native modification number or program identity changes; inside a batched script, drop the cache first (SELECT cache_invalidate('bytes');) and then re-read. If the address is in a non-writable segment (e.g. .rdata mapped read-only), Ghidra accepts the write into its program model but a runtime debugger may refuse to apply it.
- Breakpoint did not appear. Check that the addr is inside a known function (
SELECT * FROM funcs WHERE addr <= 0xN AND end_addr > 0xN;); breakpoints set on data are often dropped by Ghidra.