aboutsummaryrefslogtreecommitdiffstats
path: root/idatui/trace.py (follow)
Commit message (Collapse)AuthorAgeFilesLines
* trace: memory at time T — stack in the dock, live bytes in hexblasty31 hours1-2/+113
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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.
* trace: paint the execution trail — including on the decompilerblasty3 days1-0/+39
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Both code views now show where you came from and where you're going: the instruction you're on ('now'), the ~96 steps behind it ('past', warm) and the ~96 ahead ('future', cool). A trail, not all of history. Painting every address the trace ever touched says almost nothing on a loop-heavy program; the last and next few dozen steps say how you GOT here. Where an address appears on both sides — a loop body, which is most of them — the nearer side wins, because that's the one explaining the step you just took or are about to. **The pseudocode is painted too**, which is the reason to build this here rather than use Tenet. A trace records instructions, so that's what Tenet paints. We already have decomp_map from the split-view work, saying which instructions each C line covers, so the same trail lands on the decompilation: line 46 now | v3 = getenv("POSIXLY_CORRECT"); line 47 future | v4 = (__int64)*a2; line 49 future | if ( v3 ) A C line covers many instructions, so it takes the strongest kind present: now beats past beats future — if the instruction you're standing on belongs to this line, this line is where you are. Two things kept cheap: the trail is recomputed per SEEK rather than per repaint (~200 lookups, and repaints vastly outnumber steps), and decomp_map is cached per function because it's an RPC and stepping is interactive. The colours sit deliberately under the code palette — the trail says "you came through here", the text still has to read as code. tests: +8 UI (21) — the listing carries now/past/future and it reaches the screen; pseudocode is painted; exactly ONE C line is 'now' and it is the line covering the current instruction (not merely some executed line, which is the mistake this check exists to catch). 209/0 scenarios, 27/0 model, 10/0 diff.
* trace: read Tenet execution traces (model layer)blasty3 days1-0/+345
First slice of the execution-trace viewer: parse, index and query a Tenet trace. No UI yet — this is the layer everything else stands on, and its shape decides whether decompiler painting is cheap later. A Tenet trace is a line-per-instruction delta log: registers that changed, the PC every line, and each memory access WITH its bytes. That's enough to reconstruct any register or address at any point in time, in either direction. Our own reader, not a port. The reference (tenet-original) packs traces into segments with compressed address/mask tables, which earns its keep for its Qt timeline; we need different queries and would rather own ~400 lines than inherit 3700. Indexed around the query the UI actually asks, which the reference answers one address at a time: WHICH TIMESTAMPS EXECUTED THIS SET OF ADDRESSES. A listing row is one address, but a pseudocode line covers many (we already have decomp_map for that), so by_ip maps address -> timestamps and hits() takes a set. Painting a pseudocode line will be one call, not one per instruction. Registers are stored as per-register change points, so a value at time t is a bisect, and "which instruction set this register?" (last_write) is the same lookup — that being the question a trace explorer exists to answer. Rebasing is not optional: our echo trace runs at 0x7ffff6faa000 while the database has that code at 0x2490. Page offsets survive relocation, so the low 12 bits of an instruction address are invariant; bucket the database's addresses by those bits and take the slide the most trace addresses agree on. Verified against a real IDB: slide -0x7ffff6fa8000, and it picks out the 12 functions the trace actually entered (main, start, ...) from 128. Performance: 176k instructions parse in 350ms (~500k lines/s), so a 10M-line trace is ~20s and wants a progress callback, which load() takes. FOUND A BUG IN THE REFERENCE while building the differential test. A register written on the LAST line of a 65535-line segment is missing from the next segment's base state, so Tenet returns a stale value until that register is written again — measured: wrong for all 179 timestamps of one such window. It survives in the reference because it only shows when the register isn't rewritten immediately. That changed how the test works. Rather than "must agree with the reference", it ARBITRATES with the raw text when they differ: if the text backs us it's reported and allowed, if the text backs them it fails. Blanket agreement would have made us copy their bug to stay green. tests: test_trace.py (27, pure stdlib) covers reconstruction, the set queries, rebasing (including that a lone agreeing address is not enough, and that matching is on page offsets rather than addresses looking plausible) and malformed input; test_trace_vs_tenet.py (10) diffs against the reference on real traces.