aboutsummaryrefslogtreecommitdiffstats
path: root/idatui/edit_ctl.py
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-08-07 22:55:27 +0200
committerblasty <blasty@local>2026-08-07 22:55:27 +0200
commit2eb2a0a8cff586fffecfcb068c53b65e8f6f9839 (patch)
tree82ccb8e8fa9608c41862a13d04827d7c76ac99ee /idatui/edit_ctl.py
parentSPEED.md: the 85ms keypress, and what settle() still cannot see (diff)
downloadida-tui-2eb2a0a8cff586fffecfcb068c53b65e8f6f9839.tar.gz
ida-tui-2eb2a0a8cff586fffecfcb068c53b65e8f6f9839.tar.xz
ida-tui-2eb2a0a8cff586fffecfcb068c53b65e8f6f9839.zip
Export findings as markdown (Ctrl+E), and the journal that makes it true
The output of an RE session is what you worked out, and it was locked in a .i64 that only IDA can read. Ctrl+E (or `drive export`, or the `export` RPC verb) writes it out: your comments grouped by function with the line each annotates, the names and prototypes you set, the types you declared. **The hard part was provenance, and it needed a mechanism, not a filter.** A database does not record WHO wrote a comment or a name. IDA's analyzer sets `; switch 73 cases` and `; s1` with the same `set_cmt` a person uses, and the ELF loader sets `elf_gnu_hash_nbuckets` and `File class: 64-bit` the same way. Four probes, all negative: the FF_COMM flag is identical, `get_cmt` returns them all, `generate_disasm_line` tags every one of them COLOR_REGCMT (not COLOR_AUTOCMT), and they survive with auto-comments switched off. A first cut filtered by shape and produced a report whose first screen was ELF header trivia and `; jumptable ... case 99`. So idatui journals its own edits (idatui/journal.py) into a netnode in the database: it rides along in the .i64, it is still there next session, and the report is then exactly what was done here -- 2 findings out of a database carrying 693 other annotations. Recorded at the choke points in edit_ctl (rename, name-address, comment, retype) and in the struct editor; flushed on save, on export and on quit, so no edit pays a round trip. Without a journal (a database worked on in the IDA GUI, or predating this) the report falls back to filtering by shape -- dummy names, imports, loader segments, the analyzer's stereotyped switch/jumptable strings -- and says so in the document rather than claiming authorship it cannot prove. idatui/findings.py splits gather (needs IDA) from render (does not), so the formatting, grouping, sorting, escaping and the empty cases are tested offline: tests/test_findings.py, 32 checks, no worker, 0.1s. The pilot scenario covers the round trip that matters -- edit through the UI, export, find it in the file, and reload the journal from the .i64. Full suite: 842 passed, 0 failed, 51.2s.
Diffstat (limited to 'idatui/edit_ctl.py')
-rw-r--r--idatui/edit_ctl.py5
1 files changed, 5 insertions, 0 deletions
diff --git a/idatui/edit_ctl.py b/idatui/edit_ctl.py
index 80e1109..52566ca 100644
--- a/idatui/edit_ctl.py
+++ b/idatui/edit_ctl.py
@@ -266,6 +266,7 @@ class EditController:
if kind == "func" and addr is not None:
self._rename_index(addr, new)
app._dirty = True
+ app.journal.record("rename", addr, f"{old} → {new}", {"kind": kind})
app._status(f"renamed {old} → {new} (Ctrl+S to save)")
def do_name_addr(self, addr: int, name: str) -> None: # worker context
@@ -317,6 +318,7 @@ class EditController:
self._rename_index(addr, name)
app._open_at(addr, label, idx, False, -1, 0, True)
app._dirty = True
+ app.journal.record("rename", addr, f"→ {name}")
app._status(f"named {addr:#x} → {name} (Ctrl+S to save)")
# -- comments (IDA ';') ------------------------------------------------- #
@@ -382,6 +384,7 @@ class EditController:
app.program.bump_names()
self.reload_active_code()
app._dirty = True
+ app.journal.record("comment" if text else "uncomment", ea, text)
verb = "cleared comment" if not text else "commented"
app._status(f"{verb} @ {ea:#x} (Ctrl+S to save)")
@@ -470,6 +473,8 @@ class EditController:
app.program.bump_names()
self.reload_active_code()
app._dirty = True
+ app.journal.record("retype", getattr(app._cur, "ea", None), word,
+ {"kind": kind})
what = "prototype" if kind == "func" else f"'{word}'"
app._status(f"retyped {what} (Ctrl+S to save)")