From 636a6cb34e83523c3c513fb62ca39e0f6c10098f Mon Sep 17 00:00:00 2001 From: blasty Date: Fri, 10 Jul 2026 14:03:16 +0200 Subject: hex view: raw image bytes (VA-addressed), synced to the code cursor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New HexView: a line-virtualized 16-bytes/row dump of the whole loaded image, addressed by virtual address. Format-agnostic — the range/segments come from IDA (image_range over sections()), not any file header; gaps read back as zeros. Backslash toggles it, synced to the address under the disasm/pseudocode cursor (see the naked bytes of some code/data). In hex: hjkl/paging navigate a byte cursor (status shows the section), Enter jumps the code view to the byte, Tab/Esc/backslash return to the preferred code view. A third _active state; block -cached + prefetched like the disasm model. domain: HexModel + Program.hex_model/read_bytes/image_range (get_bytes regions). Fixed a self-deadlock (hex_model held _lock while sections() re-locked). Pilot: sync, actual bytes, byte-step, return. full suite 98/98. --- tests/test_tui.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/test_tui.py b/tests/test_tui.py index 84037a2..6d77fc2 100644 --- a/tests/test_tui.py +++ b/tests/test_tui.py @@ -19,8 +19,8 @@ import sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from idatui.app import ( # noqa: E402 - ConfirmScreen, DecompView, DisasmView, FunctionsPanel, IdaTui, StructEditor, - SymbolPalette, XrefsScreen, + ConfirmScreen, DecompView, DisasmView, FunctionsPanel, HexView, IdaTui, + StructEditor, SymbolPalette, XrefsScreen, ) from textual.widgets import DataTable, Input, OptionList, Static, TextArea # noqa: E402 from rich.text import Text # noqa: E402 @@ -310,6 +310,32 @@ async def run(db): check("goto-bottom lands near end", view.cursor >= view.total - 1, f"cursor={view.cursor}/{view.total}") + # Hex view: backslash shows the raw image bytes synced to the code cursor. + view.focus() + code_ea = view._cursor_ea() + await pilot.press("backslash") + await wait_until(pilot, lambda: app._active == "hex", 10) + hx = app.query_one(HexView) + await wait_until( + pilot, lambda: hx.model is not None + and hx.model.row(hx.cursor // 16)[1] is not None, 20) + check("backslash opens the hex view synced to the code cursor", + app._active == "hex" and code_ea is not None and hx.cursor_va() == code_ea, + f"active={app._active} hexva={hx.cursor_va():#x} ea={code_ea}") + want = app.program.read_bytes(code_ea, 1) + _, rb = hx.model.row(hx.cursor // 16) + check("hex shows the actual byte at that address", + rb is not None and rb[hx.cursor % 16] == want[0], + f"got={rb[hx.cursor % 16] if rb else None} want={want[0]}") + await pilot.press("l") + await pilot.pause(0.1) + check("hex cursor steps one byte", hx.cursor_va() == code_ea + 1, + f"va={hx.cursor_va():#x}") + await pilot.press("backslash") # back to the code view + await wait_until(pilot, lambda: app._active != "hex", 10) + check("backslash returns from hex to the code view", + app._active == "disasm", f"active={app._active}") + # Filter round-trip. '/' is context-sensitive: it filters when the # function table is focused (and searches when a code view is focused), # so focus the table first. -- cgit v1.3.1-sl0p