diff options
| author | blasty <blasty@local> | 2026-07-29 18:15:57 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-29 18:15:57 +0200 |
| commit | b06884d91216497fab92685cd1530b4ca6b3c506 (patch) | |
| tree | 0c23eb7c97ad3f3c8c5d1398071d9b6c93a49cf3 /tests/test_trace_vs_tenet.py | |
| parent | app: one instruction map for the decompiled function, not two (diff) | |
| download | ida-tui-b06884d91216497fab92685cd1530b4ca6b3c506.tar.gz ida-tui-b06884d91216497fab92685cd1530b4ca6b3c506.tar.xz ida-tui-b06884d91216497fab92685cd1530b4ca6b3c506.zip | |
trace: memory at time T — stack in the dock, live bytes in hex
M2. Trace.memory(addr, length, idx) reconstructs what memory held at a moment,
returning the bytes AND a per-byte "known" mask. The mask is the point: a trace
knows what it observed and nothing else, so a byte nobody read or wrote is
genuinely unknown and must not be drawn as zero. That distinction is the whole
reason to read memory from a trace instead of the database — the database has
the file's bytes, the trace has what was actually there.
Reads count as evidence, not just writes: an instruction reading a byte reveals
what it held then.
Indexed by ADDRESS (sorted once, bisect per query), because the question is
"what was in this window at time t" and the accesses that matter are the few
touching that window, not the tens of thousands in the trace.
Where the memory actually is: measured, 0% of accesses in either real trace fall
inside the image — every one is stack or heap. So the primary view is the STACK,
in the dock, anchored at SP:
stack (rsp)
▸7ffff6f99470 ????????????????
7ffff6f99478 00007ffff6fb0b00
7ffff6f99488 00007ffff6fa94e5
The hex view overlays trace bytes on the file's contents (green = the trace saw
this byte at this timestamp, grey = still the file's idea). Correct, and it will
matter for a program that writes globals, but on these traces it shows nothing —
which is why the stack pane is the deliverable and not a nice-to-have.
One bug the work surfaced: MemOp.addr was having the image slide applied to it,
which is nonsense for a stack address — it produced -0xc838. The slide relocates
the IMAGE; stack and heap have no database counterpart. Memory op addresses now
stay in trace space, and memory_raw() queries there, while memory() takes
database addresses for the hex view.
tests: +9 model (35) covering the known-mask, reads-as-evidence, partial
coverage and the writers/accessors queries; +1 differential (12) checking
reconstructed memory state against Tenet's own get_memory at sampled timestamps;
+5 UI (30) for the stack pane — present, anchored at SP, marks unseen bytes,
follows time. 212/0 scenarios.
Diffstat (limited to 'tests/test_trace_vs_tenet.py')
| -rw-r--r-- | tests/test_trace_vs_tenet.py | 21 |
1 files changed, 21 insertions, 0 deletions
diff --git a/tests/test_trace_vs_tenet.py b/tests/test_trace_vs_tenet.py index 6c78c95..aa6002a 100644 --- a/tests/test_trace_vs_tenet.py +++ b/tests/test_trace_vs_tenet.py @@ -134,6 +134,27 @@ def compare(path, ref_cls, arch, dctx, samples=200): check(f"{name}: same execution timestamps for the hottest addresses", not ex_bad, f"{ex_bad[:3]}") + # Memory STATE at a timestamp — reconstructed from the deltas, which is the + # hard part and the whole point of reading memory from a trace. + mem_bad, mem_checked = [], 0 + for i in idxs[::4]: + for op in ours.memory_ops(i)[:2]: + n = min(len(op.data), 8) + mine, known = ours.memory(op.addr, n, i) + ref = theirs.get_memory(op.addr, n, i) + if ref is None: + continue + refb = bytes(ref.data) + # Compare only the bytes we claim to know; the reference reports its + # own coverage separately and a byte neither has seen is not a + # disagreement. + for j in range(n): + if known[j] and refb[j:j + 1] and mine[j] != refb[j]: + mem_bad.append((i, hex(op.addr + j), mine[j], refb[j])) + mem_checked += 1 + check(f"{name}: memory state at a timestamp matches the reference", + not mem_bad, f"{mem_bad[:3]}") + # Memory: the bytes an instruction touched, and which way. with_mem = [i for i in idxs if ours.memory_ops(i)][:40] mem_bad = [] |
