aboutsummaryrefslogtreecommitdiffstats
path: root/.auto
diff options
context:
space:
mode:
Diffstat (limited to '.auto')
-rw-r--r--.auto/bench.py633
-rw-r--r--.auto/cfg-corpus.json1
-rw-r--r--.auto/check_edit.py126
-rw-r--r--.auto/check_rename.py120
-rw-r--r--.auto/check_search.py148
-rwxr-xr-x.auto/checks.sh81
-rw-r--r--.auto/diff_spans.py116
-rw-r--r--.auto/ideas.md331
-rw-r--r--.auto/log.jsonl53
-rwxr-xr-x.auto/measure.sh14
-rw-r--r--.auto/parked/check_decomp.py110
-rw-r--r--.auto/parked/fast_decompile.patch122
-rw-r--r--.auto/parked/fast_pc_nums.patch52
-rw-r--r--.auto/prompt.md275
-rw-r--r--.auto/wip-chunk.patch53
-rw-r--r--.auto/wip-decompmap.patch49
-rw-r--r--.auto/wip-digest.patch162
-rw-r--r--.auto/wip-renamekeep.patch211
18 files changed, 0 insertions, 2657 deletions
diff --git a/.auto/bench.py b/.auto/bench.py
deleted file mode 100644
index b4f5793..0000000
--- a/.auto/bench.py
+++ /dev/null
@@ -1,633 +0,0 @@
-#!/usr/bin/env python3
-"""ida-tui performance benchmark.
-
-Boots the real Textual app over a real idalib worker (a staged copy of a target,
-seeded from a pristine .i64 so auto-analysis is not paid per run) and times a
-scripted, deterministic set of the things a user actually waits on:
-
- boot worker spawn + db open + function index complete + usable
- listing sweeping the unified listing (cold pages, then warm)
- render render_line throughput on a warm listing
- decomp F5 -> decompile -> highlight -> paint, per function
- graph flowchart -> Sugiyama layout -> paint, per function
- search incremental search over the whole segment
- index function index load_all (3 filters) + fuzzy palette filter
- hex hex view scroll sweep
- pure_graph offline layout over a frozen CFG corpus (no IDA at all)
-
-Two targets are benched so nothing tunes for one scale: a small binary (echo,
-~128 funcs) and a big one (bash, ~2100 funcs).
-
-Every phase is deterministic: the function set is "largest by size, ties by
-address, skipping any the graph view refuses" -- never "whatever is biggest
-right now", which drifts with analysis.
-
-Prints `METRIC name=value` lines; the primary metric is `total_ms`. Exits
-non-zero if any phase failed to do its work (a phase that silently no-ops would
-otherwise look like a huge speedup).
-
- ~/ida-venv/bin/python .auto/bench.py
-"""
-from __future__ import annotations
-
-import argparse
-import asyncio
-import json
-import os
-import shutil
-import statistics
-import sys
-import tempfile
-import time
-
-HERE = os.path.dirname(os.path.abspath(__file__))
-ROOT = os.path.dirname(HERE)
-sys.path.insert(0, ROOT)
-sys.path.insert(0, os.path.join(ROOT, "tests"))
-
-from idatui.app import ( # noqa: E402
- DecompView, GraphView, HexView, IdaTui, ListingView,
-)
-from idatui._sync import wait_for # noqa: E402
-
-SIZE = (140, 44)
-TIMES: dict[str, list[float]] = {}
-NOTES: dict[str, object] = {}
-FAILS: list[str] = []
-PREFIX = ""
-
-
-class _T:
- def __init__(self, key):
- self.key = PREFIX + key
-
- def __enter__(self):
- self.t = time.perf_counter()
- return self
-
- def __exit__(self, *a):
- TIMES.setdefault(self.key, []).append((time.perf_counter() - self.t) * 1000)
- return False
-
-
-def note(k, v):
- NOTES[PREFIX + k] = v
-
-
-def fail(msg):
- FAILS.append(PREFIX + msg)
-
-
-#: Poll interval for every "has it landed yet" wait. The phases time the wait,
-#: so the interval is measurement overhead: at the 10ms this used to use, a
-#: graph open that really took 30ms was charged up to 40, and the graph phase
-#: (24 timed waits) carried ~12% of pure quantisation.
-_STEP = 0.002
-
-
-async def _wait(pilot, pred, t=60.0, step=_STEP):
- return await wait_for(pred, pilot.pause, t, step)
-
-
-def _paint(view) -> int:
- """Render every visible row; returns total cells painted."""
- n = 0
- for y in range(view.size.height):
- n += len(view.render_line(y).text)
- return n
-
-
-# --------------------------------------------------------------------------- #
-# phases
-# --------------------------------------------------------------------------- #
-async def phase_nav(app, pilot, funcs):
- """Jump to each function's entry in the unified listing, cold.
-
- This is `g <addr>`, follow, and every xref landing: the listing model has to
- have walked heads as far as the target before the cursor can sit on it. The
- order deliberately alternates far and near (last, first, second-last, ...)
- so it measures random access, not one tidy forward sweep.
- """
- lst = app.query_one(ListingView)
- order = []
- lo, hi = 0, len(funcs) - 1
- while lo <= hi:
- order.append(funcs[hi])
- if lo != hi:
- order.append(funcs[lo])
- lo, hi = lo + 1, hi - 1
- worst = 0.0
- with _T("nav_ms"):
- for fn in order:
- t0 = time.perf_counter()
- app._open_function(fn.addr, fn.name)
- if not await _wait(pilot, lambda fn=fn: app._cur is not None
- and app._cur.ea == fn.addr and lst.total > 0
- and lst._cursor_ea() == fn.addr, 120):
- fail(f"nav:{fn.name}")
- worst = max(worst, (time.perf_counter() - t0) * 1000)
- note("nav_worst_ms", round(worst, 1))
- note("nav_rows", lst.total)
-
-
-async def phase_listing(app, pilot, funcs, pages, first):
- """Sweep the unified listing, painting each viewport. The first pass pulls
- cold pages over the worker; the second is all-cached, i.e. pure python."""
- lst = app.query_one(ListingView)
- fn = funcs[0]
- app._open_function(fn.addr, fn.name)
- if not await _wait(pilot, lambda: app._cur is not None and app._cur.ea == fn.addr
- and lst.total > 0, 60):
- fail("listing:open")
- return
- app._active = "listing"
- app._show_active()
- lst.focus()
- await pilot.pause(0.02)
- height = max(lst.size.height, 1)
-
- async def sweep():
- cells = 0
- for i in range(pages):
- lst.model.ensure(i * height + height + 1)
- lst.cursor = min(max(lst.total - 1, 0), i * height)
- lst._scroll_cursor_into_view()
- await pilot.pause(0)
- cells += _paint(lst)
- return cells
-
- if first:
- # Only on the first repetition: after it the pages are cached and this
- # would be a second warm sweep wearing the cold sweep's name.
- with _T("listing_cold_ms"):
- c = await sweep()
- note("listing_rows", lst.total)
- note("listing_cells", c)
- else:
- await sweep()
- with _T("listing_warm_ms"):
- await sweep()
-
-
-async def phase_render(app, pilot, funcs, frames):
- """render_line throughput on a warm listing with the cursor moving every
- frame -- the steady state of holding down j."""
- lst = app.query_one(ListingView)
- app._active = "listing"
- app._show_active()
- lst.focus()
- await pilot.pause(0.02)
- n = 0
- with _T("render_ms"):
- for i in range(frames):
- lst.cursor = (i * 7) % max(lst.total, 1)
- for y in range(lst.size.height):
- n += len(lst.render_line(y).text)
- note("render_cells", n)
-
-
-async def phase_decomp(app, pilot, funcs):
- """F5 out of the listing into the pseudocode, then read down it.
-
- Navigation is done OUTSIDE the timer -- phase_nav already charges for that,
- and counting it twice would let a nav win look like a decompiler win.
- """
- dv = app.query_one(DecompView)
- lst = app.query_one(ListingView)
- ok = lines = 0
- spent = 0.0
- for fn in funcs:
- app._open_function(fn.addr, fn.name)
- if not await _wait(pilot, lambda fn=fn: app._cur is not None
- and app._cur.ea == fn.addr and lst.total > 0
- and lst._cursor_ea() == fn.addr, 120):
- fail(f"decomp:open:{fn.name}")
- continue
- app._active = "listing"
- app._show_active()
- lst.focus()
- await pilot.pause(0.02)
- t0 = time.perf_counter()
- app.action_toggle_view()
- if not await _wait(pilot, lambda fn=fn: dv.loaded_ea == fn.addr, 60):
- fail(f"decomp:{fn.name}")
- continue
- app._active = "decomp"
- app._show_active()
- await pilot.pause(0)
- for _ in range(4): # scroll the body, painting each page
- _paint(dv)
- dv.cursor = min(max(dv.total - 1, 0), dv.cursor + dv.size.height)
- dv._scroll_cursor_into_view()
- await pilot.pause(0)
- spent += (time.perf_counter() - t0) * 1000
- ok += 1
- lines += dv.total
- TIMES.setdefault(PREFIX + "decomp_ms", []).append(spent)
- app._active = "listing"
- app._show_active()
- note("decomp_ok", ok)
- note("decomp_lines", lines)
-
-
-async def phase_graph(app, pilot, funcs):
- gv = app.query_one(GraphView)
- ok = blocks = 0
- spent = 0.0
- # Space on a function you have not graphed before is the case that hurts:
- # it pays the flowchart tool, a heads walk over the function's extent, and
- # the layout. Program caches flowcharts per function, so without this the
- # phase would time a dict lookup.
- app.program._flowcharts.clear()
- for fn in funcs:
- app._open_function(fn.addr, fn.name)
- if not await _wait(pilot, lambda fn=fn: app._cur is not None
- and app._cur.ea == fn.addr, 120):
- fail(f"graph:open:{fn.name}")
- continue
- app._graph_sticky = True
- gv.fc = None
- gv.lay = None
- t0 = time.perf_counter()
- app._load_graph(fn.addr, fn.addr)
- got = await _wait(pilot, lambda fn=fn: gv.fc is not None
- and gv.fc.func_ea == fn.addr and gv.lay is not None, 60)
- app._graph_sticky = False
- if not got:
- fail(f"graph:{fn.name}")
- continue
- app._active = "graph"
- app._show_active()
- await pilot.pause(0)
- _paint(gv)
- for _ in range(3): # pan the canvas, painting each frame
- gv.scroll_to(y=min(gv.lay.height, round(gv.scroll_offset.y)
- + gv.size.height), animate=False)
- await pilot.pause(0)
- _paint(gv)
- spent += (time.perf_counter() - t0) * 1000
- ok += 1
- blocks += len(gv.lay.nodes)
- TIMES.setdefault(PREFIX + "graph_ms", []).append(spent)
- app._active = "listing"
- app._show_active()
- note("graph_ok", ok)
- note("graph_blocks", blocks)
-
-
-async def phase_split(app, pilot, funcs):
- """`s` — listing and pseudocode side by side, cursor-linked.
-
- The link is driven by ``decomp_map``: for every pseudocode line, the set of
- instructions the decompiler attributes to it. That is the whole cost of the
- feature and the bench did not cover it at all.
- """
- dv = app.query_one(DecompView)
- lst = app.query_one(ListingView)
- ok = mapped = 0
- spent = 0.0
- for fn in funcs:
- app._open_function(fn.addr, fn.name)
- if not await _wait(pilot, lambda fn=fn: app._cur is not None
- and app._cur.ea == fn.addr and lst.total > 0
- and lst._cursor_ea() == fn.addr, 120):
- fail(f"split:open:{fn.name}")
- continue
- app._active = "listing"
- app._show_active()
- lst.focus()
- await pilot.pause(0.02)
- t0 = time.perf_counter()
- app.action_toggle_split()
- got = await _wait(pilot, lambda fn=fn: app._split and lst.display
- and dv.display and dv.loaded_ea == fn.addr, 120)
- # The region map is what the split view is FOR; wait for it, not just
- # for two panes to appear.
- m = app.program.decomp_map(fn.addr)
- spent += (time.perf_counter() - t0) * 1000
- if not got:
- fail(f"split:{fn.name}")
- else:
- ok += 1
- mapped += sum(1 for eas in m if eas)
- _paint(lst)
- _paint(dv)
- if app._split:
- app.action_toggle_split()
- await _wait(pilot, lambda: not app._split, 30)
- TIMES.setdefault(PREFIX + "split_ms", []).append(spent)
- app._active = "listing"
- app._show_active()
- note("split_ok", ok)
- note("split_mapped_lines", mapped)
-
-
-async def phase_rename(app, pilot, funcs):
- """Rename a function and get the listing back — the commonest RE operation.
-
- A rename invalidates cached names everywhere, and the question this measures
- is what the *listing* then costs: the cursor has to land back on the same row
- with the new name showing. Renames are undone afterwards so the .i64 the
- bench stages from is never left edited.
- """
- lst = app.query_one(ListingView)
- ed = app.program
- done = 0
- spent = 0.0
- tag = f"_bench_{os.getpid()}"
- for k, fn in enumerate(funcs[:6]):
- app._open_function(fn.addr, fn.name)
- if not await _wait(pilot, lambda fn=fn: app._cur is not None
- and app._cur.ea == fn.addr and lst.total > 0
- and lst._cursor_ea() == fn.addr, 120):
- fail(f"rename:open:{fn.name}")
- continue
- row = lst.cursor
- new = f"{tag}_{k}"
- t0 = time.perf_counter()
- try:
- ed.client.call("rename",
- batch={"func": [{"addr": hex(fn.addr), "name": new}]})
- except Exception: # noqa: BLE001
- fail(f"rename:call:{fn.name}")
- continue
- ed.bump_names()
- # What the app does next: reopen the listing where it was and paint it.
- lm = ed.listing(fn.addr)
- idx = max(lm.ensure_ea(fn.addr), 0) if lm is not None else 0
- lst.load(lm, new, cursor=idx, scroll_y=max(idx - 6, 0))
- got = await _wait(pilot, lambda fn=fn: lst.total > 0
- and lst._cursor_ea() == fn.addr, 120)
- await pilot.pause(0)
- _paint(lst)
- spent += (time.perf_counter() - t0) * 1000
- # The name lives on the `proc` banner row, a few rows above the code row
- # ensure_ea lands on (banner rows aren't address-indexed), so look at the
- # window rather than the single row.
- shows_new = False
- if lm is not None:
- for k2 in range(max(idx - 4, 0), idx + 2):
- h = lm.get(k2)
- if h is not None and (new in (h.text or "")
- or new == (h.name or "")):
- shows_new = True
- break
- if not (got and idx == row and shows_new):
- fail(f"rename:{fn.name}:row={idx}/{row} shows_new={shows_new}")
- else:
- done += 1
- try: # put it back, whatever happened above
- ed.client.call("rename",
- batch={"func": [{"addr": hex(fn.addr),
- "name": fn.name}]})
- ed.bump_names()
- except Exception: # noqa: BLE001
- fail(f"rename:undo:{fn.name}")
- TIMES.setdefault(PREFIX + "rename_ms", []).append(spent)
- note("rename_ok", done)
-
-
-async def phase_search(app, pilot, funcs, terms):
- """The real incremental-search path: search_begin, then one search_update per
- typed character, exactly as the Input's on_changed drives it."""
- lst = app.query_one(ListingView)
- app._active = "listing"
- app._show_active()
- lst.focus()
- await pilot.pause(0.02)
- hits = 0
- with _T("search_ms"):
- for term in terms:
- lst.search_begin(1)
- for i in range(1, len(term) + 1):
- lst.search_update(term[:i])
- if not await _wait(pilot, lambda: bool(lst._matches), 180):
- fail(f"search:{term}")
- hits += len(lst._matches)
- lst.search_cancel()
- await pilot.pause(0)
- note("search_hits", hits)
-
-
-async def phase_index(app, pilot, funcs):
- from idatui.app import _fuzzy # noqa: PLC0415
- prog = app.program
- with _T("index_ms"):
- for filt in (None, "sub_*", "*a*"):
- prog.functions(filt).load_all()
- n = len(prog.functions())
- note("index_n", n)
- if n == 0:
- fail("index:empty")
- names = [f.name for f in prog.functions().all_loaded()]
- with _T("palette_ms"):
- tot = 0
- for q in ("m", "mn", "sub", "prnt", "ab", "str"):
- for nm in names:
- tot += 1 if _fuzzy(nm, q) else 0
- note("palette_hits", tot)
-
-
-async def phase_hex(app, pilot, funcs, frames):
- hx = app.query_one(HexView)
- fn = funcs[0]
- app._open_function(fn.addr, fn.name)
- await _wait(pilot, lambda: app._cur is not None and app._cur.ea == fn.addr, 60)
- app.action_hex()
- if not await _wait(pilot, lambda: app._active == "hex" and hx.model is not None, 60):
- fail("hex:open")
- return
- await pilot.pause(0.05)
- height = max(hx.size.height, 1)
- with _T("hex_ms"):
- for i in range(frames):
- row = (i * height) % max(hx.total, 1)
- hx.cursor = row * 16
- hx.model.ensure(row, height)
- hx._apply_scroll(row)
- await pilot.pause(0)
- _paint(hx)
- app._active = "listing"
- app._show_active()
-
-
-def phase_pure_graph(corpus):
- from idatui import graph as G # noqa: PLC0415
- with open(corpus) as fh:
- recs = json.load(fh)
- n = 0
- with _T("pure_graph_ms"): # called three times; the median is taken
- for rec in recs:
- texts = {b["id"]: list(b["lines"]) for b in rec["blocks"]}
- blocks = [G.Block(id=b["id"], start=b["start"], end=b["end"],
- succs=[(d, k) for d, k in b["succs"]])
- for b in rec["blocks"]]
-
- def sizer(b, texts=texts):
- lines = texts[b.id]
- label = f"loc_{b.start:X}"
- widest = max([len(label) + 4] + [len(ln) for ln in lines] or [4])
- return (widest + 4, max(len(lines), 1) + 2)
-
- lay = G.layout(blocks, sizer)
- n += len(lay.nodes)
- NOTES["pure_graph_nodes"] = n
- if n == 0:
- FAILS.append("pure_graph:empty")
-
-
-# --------------------------------------------------------------------------- #
-def stage(binary):
- """A temp-dir copy of ``binary`` seeded from its pre-analysed database.
-
- Prefers ``<bin>.pristine.i64`` (what tests/_fixtures builds) and falls back
- to a tracked ``<bin>.i64``. Never touches either.
- """
- d = tempfile.mkdtemp(prefix="idatui-bench-")
- tgt = os.path.join(d, os.path.basename(binary))
- shutil.copy2(binary, tgt)
- for cand in (binary + ".pristine.i64", binary + ".i64"):
- if os.path.exists(cand):
- shutil.copy2(cand, tgt + ".i64")
- break
- else:
- raise SystemExit(f"{binary}: no pre-analysed .i64 -- bench would time "
- f"auto-analysis, not ida-tui")
- return d, tgt
-
-
-async def run_target(binary, reps, nfuncs, pages, frames, terms, skip=0):
- d, target = stage(binary)
- try:
- app = IdaTui(open_path=target, keepalive=False)
- t0 = time.perf_counter()
- async with app.run_test(size=SIZE) as pilot:
- # Usable = index complete AND the loading overlay handed the screen
- # back (auto-land fired). Benching with the modal still up silently
- # disables every priority binding, so nothing a phase presses lands.
- if not await _wait(pilot, lambda: app._func_index is not None
- and app._func_index.complete, 600):
- fail("boot:index")
- return
- await _wait(pilot, lambda: app._loading_screen is None
- and len(app.screen_stack) == 1, 120)
- TIMES.setdefault(PREFIX + "boot_ms", []).append(
- (time.perf_counter() - t0) * 1000)
- idx = app._func_index
- idx.load_all()
- allf = idx.all_loaded()
- note("funcs", len(allf))
- # Largest by size, ties by address, skipping any function the graph
- # view refuses (>GRAPH_MAX_BLOCKS) -- otherwise the graph phase sits
- # in a timeout instead of doing work.
- # ``skip`` drops the very largest: on a big binary the top few are
- # multi-thousand-line monsters whose Hex-Rays cost is 30s of pure
- # backend time, which would swamp every phase we can actually make
- # faster. A band just below the top is still large and realistic.
- big = []
- for f in sorted(allf, key=lambda f: (-f.size, f.addr))[skip:]:
- if len(big) >= nfuncs:
- break
- try:
- # The raw tool, NOT Program.flowchart: the latter caches, and
- # picking the fixtures through it left phase_graph measuring
- # cache hits instead of the work a user waits for.
- payload = app.program.client.call("flowchart",
- addr=hex(f.addr))
- except Exception: # noqa: BLE001
- continue
- if not isinstance(payload, dict) or payload.get("error"):
- continue
- nblocks = len(payload.get("blocks") or [])
- if not nblocks or nblocks > IdaTui.GRAPH_MAX_BLOCKS:
- continue
- big.append(f)
- if len(big) < nfuncs:
- fail(f"fixture:only {len(big)}/{nfuncs} functions")
- big = sorted(big, key=lambda f: f.addr)
- note("fixed_set", [f.name for f in big])
-
- # Cold, once: the listing model starts empty and nav is the thing
- # that has to walk it. Repeating it would only measure a warm cache.
- await phase_nav(app, pilot, big)
-
- # Phases split two ways. The repeatable ones (paint throughput, and
- # the graph, which clears its own cache) are run every repetition so
- # the median settles. The cold-sensitive ones -- decompile, search
- # and the function index all cache their answer, and re-running them
- # would report a dict lookup under the name of the thing a user
- # waits for -- are run ONCE.
- for rep in range(reps):
- first = rep == 0
- await phase_listing(app, pilot, big, pages, first)
- await phase_render(app, pilot, big, frames)
- if first:
- await phase_decomp(app, pilot, big)
- await phase_graph(app, pilot, big)
- if first:
- await phase_rename(app, pilot, big)
- await phase_split(app, pilot, big)
- await phase_search(app, pilot, big, terms)
- await phase_index(app, pilot, big)
- await phase_hex(app, pilot, big, frames // 5)
- app.exit()
- finally:
- shutil.rmtree(d, ignore_errors=True)
-
-
-def main():
- global PREFIX
- ap = argparse.ArgumentParser()
- ap.add_argument("--corpus", default=os.path.join(HERE, "cfg-corpus.json"))
- ap.add_argument("--only", default="", help="only this target tag")
- ap.add_argument("--reps", type=int, default=0, help="override reps")
- a = ap.parse_args()
-
- plan = [
- # tag, binary, reps, nfuncs, pages, frames, terms, skip
- ("sm_", "targets/echo", 2, 12, 40, 300, ("mov", "call", "lea"), 0),
- # Two reps on the big target too: one sample of a 2.5s Hex-Rays phase
- # swings by 10%, which was drowning changes worth more than that.
- # (phase_nav stays single -- it is a COLD walk by definition.)
- ("lg_", "targets/bash", 2, 12, 60, 300, ("mov", "call"), 120),
- ]
- if a.only:
- plan = [p for p in plan if p[0].startswith(a.only)]
-
- async def go():
- global PREFIX
- for tag, binary, reps, nfuncs, pages, frames, terms, skip in plan:
- PREFIX = tag
- await run_target(os.path.join(ROOT, binary), a.reps or reps,
- nfuncs, pages, frames, terms, skip)
- PREFIX = ""
-
- asyncio.run(go())
- if os.path.exists(a.corpus):
- # Three times, for the median. This phase is pure CPU with no I/O and
- # was the single jumpiest number in the suite (239 <-> 524 with
- # identical code) purely from being descheduled -- which is noise the
- # primary metric was carrying for no reason.
- for _ in range(3):
- phase_pure_graph(a.corpus)
- else:
- FAILS.append("pure_graph:no corpus")
-
- out = {}
- for k, v in TIMES.items():
- out[k] = statistics.median(v) if len(v) > 2 else min(v)
- total = sum(out.values())
- print()
- for k in sorted(out):
- print(f"METRIC {k}={out[k]:.1f}")
- print(f"METRIC total_ms={total:.1f}")
- print(f"METRIC fails={len(FAILS)}")
- print("NOTES " + json.dumps(NOTES)[:3000])
- if FAILS:
- print("FAILS " + "; ".join(FAILS[:20]))
- return 1
- return 0
-
-
-if __name__ == "__main__":
- sys.exit(main())
diff --git a/.auto/cfg-corpus.json b/.auto/cfg-corpus.json
deleted file mode 100644
index cf747f9..0000000
--- a/.auto/cfg-corpus.json
+++ /dev/null
@@ -1 +0,0 @@
-[{"name": "sub_3720", "ea": 14112, "blocks": [{"id": 0, "start": 14112, "end": 14208, "lines": ["push rbp", "mov rbp, rsp", "push r15", "mov r15, rsi", "push r14", "push r13", "push r12", "push rbx", "mov rbx, rdi", "sub rsp, 0B8h", "mov rax, [rbp+arg_0]", "mov [rbp+var_58], rdx", "mov [rbp+var_E0], rdi", "mov r13, [rbp+arg_8]", "mov [rbp+var_7C], r8d", "mov [rbp+var_9C], r9d", "mov [rbp+var_70], rax", "mov r14, fs:28h", "mov [rbp+var_38], r14", "mov r14, [rbp+arg_10]", "mov [rbp+var_D8], r13", "mov [rbp+s], r14", "mov r14, rcx", "xchg ax, ax"], "succs": [[1, "fall"]]}, {"id": 1, "start": 14208, "end": 14285, "lines": ["call cs:__ctype_get_mb_cur_max_ptr", "mov [rbp+var_63], 1", "mov r8, r14", "mov r12, rbx", "mov edx, [rbp+var_9C]", "mov [rbp+var_B0], rax", "mov [rbp+var_7E], 0", "shr edx, 1", "mov [rbp+var_65], 0", "mov eax, edx", "mov [rbp+n], 0", "mov [rbp+s2], 0", "and eax, 1", "mov [rbp+var_61], al", "mov [rbp+var_A8], 0"], "succs": [[2, "fall"]]}, {"id": 2, "start": 14285, "end": 14297, "lines": ["mov eax, [rbp+var_7C]", "cmp eax, 0Ah; switch 11 cases", "ja def_37E7; jumptable 00000000000037E7 default case"], "succs": [[3, "fall"], [423, "jump"]]}, {"id": 3, "start": 14297, "end": 14314, "lines": ["lea rdi, jpt_37E7", "movsxd rax, ds:(jpt_37E7 - 8C00h)[rdi+rax*4]", "add rax, rdi", "jmp rax; switch jump"], "succs": [[4, "switch"], [118, "switch"], [119, "switch"], [121, "switch"], [122, "switch"], [126, "switch"], [127, "switch"], [131, "switch"], [133, "switch"]]}, {"id": 4, "start": 14320, "end": 14328, "lines": ["mov ebx, [rbp+var_7C]; jumptable 00000000000037E7 cases 8-10", "cmp ebx, 0Ah", "jz short loc_382A"], "succs": [[5, "fall"], [6, "jump"]]}, {"id": 5, "start": 14328, "end": 14378, "lines": ["mov esi, ebx", "lea rdi, asc_8348+4; msgid", "mov [rbp+var_60], r8", "call sub_3640", "mov esi, ebx", "lea rdi, asc_8348+2; msgid", "mov [rbp+var_D8], rax", "call sub_3640", "mov r8, [rbp+var_60]", "mov [rbp+s], rax"], "succs": [[6, "fall"]]}, {"id": 6, "start": 14378, "end": 14390, "lines": ["xor ebx, ebx", "cmp [rbp+var_61], 0", "jz loc_4DA5"], "succs": [[7, "fall"], [394, "jump"]]}, {"id": 7, "start": 14390, "end": 14464, "lines": ["mov r14, [rbp+s]", "mov [rbp+var_60], r8", "mov rdi, r14; s", "call cs:strlen_ptr", "mov [rbp+s2], r14", "mov r8, [rbp+var_60]", "cmp rax, 1", "mov [rbp+n], rax", "mov [rbp+var_66], 0", "mov [rbp+var_67], 0", "mov [rbp+var_64], 0", "mov [rbp+var_9D], 1", "mov [rbp+var_65], 1", "setnz [rbp+var_7D]", "test rax, rax", "setnz [rbp+var_62]", "nop"], "succs": [[8, "fall"]]}, {"id": 8, "start": 14464, "end": 14470, "lines": ["xor r13d, r13d", "xor r11d, r11d"], "succs": [[9, "fall"]]}, {"id": 9, "start": 14470, "end": 14487, "lines": ["cmp r8, r13", "setnz r14b", "cmp r8, 0FFFFFFFFFFFFFFFFh", "jz loc_39B3"], "succs": [[10, "fall"], [39, "jump"]]}, {"id": 10, "start": 14487, "end": 14496, "lines": ["nop word ptr [rax+rax+00000000h]"], "succs": [[11, "fall"]]}, {"id": 11, "start": 14496, "end": 14505, "lines": ["test r14b, r14b", "jz loc_39D0"], "succs": [[12, "fall"], [40, "jump"]]}, {"id": 12, "start": 14505, "end": 14523, "lines": ["mov rax, [rbp+var_58]", "lea r10, [rax+r13]", "cmp [rbp+var_62], 0", "jnz loc_3FB0"], "succs": [[13, "fall"], [134, "jump"]]}, {"id": 13, "start": 14523, "end": 14537, "lines": ["movzx r9d, byte ptr [r10]", "cmp r9b, 3Fh ; '?'", "jg loc_43C0"], "succs": [[14, "fall"], [191, "jump"]]}, {"id": 14, "start": 14537, "end": 14546, "lines": ["test r9b, r9b", "js def_38EE; jumptable 00000000000038EE default case, cases 1-6,14-31"], "succs": [[15, "fall"], [47, "jump"]]}, {"id": 15, "start": 14546, "end": 14556, "lines": ["cmp r9b, 3Fh; switch 64 cases", "ja def_38EE; jumptable 00000000000038EE default case, cases 1-6,14-31"], "succs": [[16, "fall"], [47, "jump"]]}, {"id": 16, "start": 14556, "end": 14577, "lines": ["lea rsi, jpt_38EE", "movzx eax, r9b", "movsxd rax, ds:(jpt_38EE - 8C2Ch)[rsi+rax*4]", "add rax, rsi", "jmp rax; switch jump"], "succs": [[17, "switch"], [47, "switch"], [63, "switch"], [68, "switch"], [73, "switch"], [74, "switch"], [75, "switch"], [77, "switch"], [82, "switch"], [86, "switch"], [88, "switch"], [108, "switch"], [109, "switch"], [112, "switch"], [114, "switch"]]}, {"id": 17, "start": 14584, "end": 14594, "lines": ["cmp [rbp+var_66], 0; jumptable 00000000000038EE cases 37,43-58", "jnz loc_46B0"], "succs": [[18, "fall"], [253, "jump"]]}, {"id": 18, "start": 14594, "end": 14605, "lines": ["cmp [rbp+var_70], 0", "jz loc_4600"], "succs": [[19, "fall"], [237, "jump"]]}, {"id": 19, "start": 14605, "end": 14612, "lines": ["mov byte ptr [rbp+var_60], 0", "mov edx, r14d"], "succs": [[20, "fall"]]}, {"id": 20, "start": 14612, "end": 14631, "lines": ["mov eax, r9d", "mov esi, r9d", "shr al, 5", "and esi, 1Fh", "movzx eax, al", "shl rax, 2"], "succs": [[21, "fall"]]}, {"id": 21, "start": 14631, "end": 14647, "lines": ["mov rdi, [rbp+var_70]", "mov eax, [rdi+rax]", "bt eax, esi", "jnb loc_3AB8"], "succs": [[22, "fall"], [54, "jump"]]}, {"id": 22, "start": 14647, "end": 14657, "lines": ["cmp [rbp+var_61], 0", "jnz loc_45EA"], "succs": [[23, "fall"], [236, "jump"]]}, {"id": 23, "start": 14657, "end": 14661, "lines": ["lea rcx, [r13+1]"], "succs": [[24, "fall"]]}, {"id": 24, "start": 14661, "end": 14676, "lines": ["and [rbp+var_63], dl", "mov eax, r11d", "mov r13, rcx", "xor eax, 1", "and al, [rbp+var_64]"], "succs": [[25, "fall"]]}, {"id": 25, "start": 14676, "end": 14680, "lines": ["test al, al", "jz short loc_3987"], "succs": [[26, "fall"], [33, "jump"]]}, {"id": 26, "start": 14680, "end": 14685, "lines": ["cmp rbx, r15", "jnb short loc_3962"], "succs": [[27, "fall"], [28, "jump"]]}, {"id": 27, "start": 14685, "end": 14690, "lines": ["mov byte ptr [r12+rbx], 27h ; '''"], "succs": [[28, "fall"]]}, {"id": 28, "start": 14690, "end": 14699, "lines": ["lea rax, [rbx+1]", "cmp rax, r15", "jnb short loc_3971"], "succs": [[29, "fall"], [30, "jump"]]}, {"id": 29, "start": 14699, "end": 14705, "lines": ["mov byte ptr [r12+rbx+1], 24h ; '$'"], "succs": [[30, "fall"]]}, {"id": 30, "start": 14705, "end": 14714, "lines": ["lea rax, [rbx+2]", "cmp rax, r15", "jnb short loc_3980"], "succs": [[31, "fall"], [32, "jump"]]}, {"id": 31, "start": 14714, "end": 14720, "lines": ["mov byte ptr [r12+rbx+2], 27h ; '''"], "succs": [[32, "fall"]]}, {"id": 32, "start": 14720, "end": 14727, "lines": ["add rbx, 3", "mov r11d, r14d"], "succs": [[33, "fall"]]}, {"id": 33, "start": 14727, "end": 14732, "lines": ["cmp rbx, r15", "jnb short loc_3991"], "succs": [[34, "fall"], [35, "jump"]]}, {"id": 34, "start": 14732, "end": 14737, "lines": ["mov byte ptr [r12+rbx], 5Ch ; '\\'"], "succs": [[35, "fall"]]}, {"id": 35, "start": 14737, "end": 14741, "lines": ["add rbx, 1"], "succs": [[36, "fall"]]}, {"id": 36, "start": 14741, "end": 14746, "lines": ["cmp rbx, r15", "jnb short loc_399E"], "succs": [[37, "fall"], [38, "jump"]]}, {"id": 37, "start": 14746, "end": 14750, "lines": ["mov [r12+rbx], r9b"], "succs": [[38, "fall"]]}, {"id": 38, "start": 14750, "end": 14771, "lines": ["add rbx, 1", "cmp r8, r13", "setnz r14b", "cmp r8, 0FFFFFFFFFFFFFFFFh", "jnz loc_38A0"], "succs": [[11, "jump"], [39, "fall"]]}, {"id": 39, "start": 14771, "end": 14800, "lines": ["mov rax, [rbp+var_58]", "mov r8, 0FFFFFFFFFFFFFFFFh", "cmp byte ptr [rax+r13], 0", "setnz r14b", "test r14b, r14b", "jnz loc_38A9"], "succs": [[12, "jump"], [40, "fall"]]}, {"id": 40, "start": 14800, "end": 14809, "lines": ["test rbx, rbx", "jnz loc_4950"], "succs": [[41, "fall"], [307, "jump"]]}, {"id": 41, "start": 14809, "end": 14819, "lines": ["cmp [rbp+var_64], 0", "jz loc_4950"], "succs": [[42, "fall"], [307, "jump"]]}, {"id": 42, "start": 14819, "end": 14829, "lines": ["cmp [rbp+var_61], 0", "jnz loc_3B0C"], "succs": [[43, "fall"], [65, "jump"]]}, {"id": 43, "start": 14829, "end": 14839, "lines": ["cmp [rbp+var_7E], 0", "jz loc_4EAF"], "succs": [[44, "fall"], [414, "jump"]]}, {"id": 44, "start": 14839, "end": 14849, "lines": ["cmp [rbp+var_63], 0", "jnz loc_4E96"], "succs": [[45, "fall"], [413, "jump"]]}, {"id": 45, "start": 14849, "end": 14874, "lines": ["test r15, r15", "setz al", "cmp [rbp+var_A8], 0", "setnz dl", "and al, dl", "jz loc_4E8A"], "succs": [[46, "fall"], [412, "jump"]]}, {"id": 46, "start": 14874, "end": 14893, "lines": ["mov [rbp+var_7E], al", "mov r15, [rbp+var_A8]", "mov [rbp+var_61], 0", "jmp loc_37CD"], "succs": [[2, "jump"]]}, {"id": 47, "start": 14896, "end": 14900, "lines": ["mov byte ptr [rbp+var_60], 0; jumptable 00000000000038EE default case, cases 1-6,14-31"], "succs": [[48, "fall"]]}, {"id": 48, "start": 14900, "end": 14914, "lines": ["cmp [rbp+var_B0], 1", "jnz loc_40BD"], "succs": [[49, "fall"], [148, "jump"]]}, {"id": 49, "start": 14914, "end": 14998, "lines": ["mov [rbp+var_B8], r8", "mov byte ptr [rbp+var_90], r11b", "mov byte ptr [rbp+var_88], r9b", "call cs:__ctype_b_loc_ptr", "movzx r11d, byte ptr [rbp+var_90]", "mov ecx, 1", "mov r8, [rbp+var_B8]", "mov rdx, rax", "movzx eax, byte ptr [rbp+var_88]", "mov rdx, [rdx]", "mov r9, rax", "movzx edx, word ptr [rdx+rax*2]", "and dx, 4000h", "shr dx, 0Eh", "mov eax, edx", "xor eax, 1", "and al, [rbp+var_65]"], "succs": [[50, "fall"]]}, {"id": 50, "start": 14998, "end": 15006, "lines": ["test al, al", "jnz loc_47C0"], "succs": [[51, "fall"], [273, "jump"]]}, {"id": 51, "start": 15006, "end": 15008, "lines": ["xchg ax, ax"], "succs": [[52, "fall"]]}, {"id": 52, "start": 15008, "end": 15021, "lines": ["mov ecx, r9d", "cmp [rbp+var_66], 0", "jnz loc_3B6C"], "succs": [[53, "fall"], [70, "jump"]]}, {"id": 53, "start": 15021, "end": 15032, "lines": ["cmp [rbp+var_70], 0", "jnz loc_3914"], "succs": [[20, "jump"], [54, "fall"]]}, {"id": 54, "start": 15032, "end": 15034, "lines": ["xor eax, eax"], "succs": [[55, "fall"]]}, {"id": 55, "start": 15034, "end": 15044, "lines": ["cmp byte ptr [rbp+var_60], 0", "jnz loc_3937"], "succs": [[22, "jump"], [56, "fall"]]}, {"id": 56, "start": 15044, "end": 15057, "lines": ["and [rbp+var_63], dl", "xor eax, 1", "add r13, 1", "and eax, r11d"], "succs": [[57, "fall"]]}, {"id": 57, "start": 15057, "end": 15065, "lines": ["test al, al", "jz loc_3995"], "succs": [[36, "jump"], [58, "fall"]]}, {"id": 58, "start": 15065, "end": 15070, "lines": ["cmp rbx, r15", "jnb short loc_3AE3"], "succs": [[59, "fall"], [60, "jump"]]}, {"id": 59, "start": 15070, "end": 15075, "lines": ["mov byte ptr [r12+rbx], 27h ; '''"], "succs": [[60, "fall"]]}, {"id": 60, "start": 15075, "end": 15084, "lines": ["lea rax, [rbx+1]", "cmp rax, r15", "jnb short loc_3AF2"], "succs": [[61, "fall"], [62, "jump"]]}, {"id": 61, "start": 15084, "end": 15090, "lines": ["mov byte ptr [r12+rbx+1], 27h ; '''"], "succs": [[62, "fall"]]}, {"id": 62, "start": 15090, "end": 15102, "lines": ["add rbx, 2", "xor r11d, r11d", "jmp loc_3995"], "succs": [[36, "jump"]]}, {"id": 63, "start": 15104, "end": 15110, "lines": ["mov byte ptr [rbp+var_60], 0; jumptable 00000000000038EE cases 33,34,36,38,40-42,59-62", "xor edx, edx"], "succs": [[64, "fall"]]}, {"id": 64, "start": 15110, "end": 15116, "lines": ["cmp [rbp+var_67], 0", "jz short loc_3AA0"], "succs": [[52, "jump"], [65, "fall"]]}, {"id": 65, "start": 15116, "end": 15136, "lines": ["mov [rbp+var_7C], 2", "mov r14, r8", "mov rbx, r12", "nop dword ptr [rax+00000000h]"], "succs": [[66, "fall"]]}, {"id": 66, "start": 15136, "end": 15152, "lines": ["cmp [rbp+var_65], 0", "mov eax, 4", "cmovz eax, [rbp+var_7C]", "mov [rbp+var_7C], eax"], "succs": [[67, "fall"]]}, {"id": 67, "start": 15152, "end": 15172, "lines": ["and [rbp+var_9C], 0FFFFFFFDh", "mov [rbp+var_70], 0", "jmp loc_3780"], "succs": [[1, "jump"]]}, {"id": 68, "start": 15176, "end": 15200, "lines": ["mov byte ptr [rbp+var_60], 0; jumptable 00000000000038EE case 12", "mov ecx, 0Ch", "mov r9d, 66h ; 'f'", "nop word ptr [rax+rax+00000000h]"], "succs": [[69, "fall"]]}, {"id": 69, "start": 15200, "end": 15212, "lines": ["xor edx, edx", "cmp [rbp+var_65], 0", "jnz loc_3937"], "succs": [[22, "jump"], [70, "fall"]]}, {"id": 70, "start": 15212, "end": 15222, "lines": ["cmp [rbp+var_61], 0", "jz loc_4300"], "succs": [[71, "fall"], [178, "jump"]]}, {"id": 71, "start": 15222, "end": 15233, "lines": ["cmp [rbp+var_70], 0", "jz loc_45E0"], "succs": [[72, "fall"], [235, "jump"]]}, {"id": 72, "start": 15233, "end": 15258, "lines": ["mov eax, ecx", "mov esi, ecx", "mov r9d, ecx", "shr al, 5", "and esi, 1Fh", "movzx eax, al", "shl rax, 2", "jmp loc_3927"], "succs": [[21, "jump"]]}, {"id": 73, "start": 15264, "end": 15281, "lines": ["mov byte ptr [rbp+var_60], 0; jumptable 00000000000038EE case 11", "mov ecx, 0Bh", "mov r9d, 76h ; 'v'", "jmp short loc_3B60"], "succs": [[69, "jump"]]}, {"id": 74, "start": 15288, "end": 15305, "lines": ["mov byte ptr [rbp+var_60], 0; jumptable 00000000000038EE case 8", "mov ecx, 8", "mov r9d, 62h ; 'b'", "jmp short loc_3B60"], "succs": [[69, "jump"]]}, {"id": 75, "start": 15312, "end": 15322, "lines": ["cmp [rbp+var_7C], 2; jumptable 00000000000038EE case 39", "jz loc_46F0"], "succs": [[76, "fall"], [259, "jump"]]}, {"id": 76, "start": 15322, "end": 15344, "lines": ["mov [rbp+var_7E], r14b", "mov edx, r14d", "mov r9d, 27h ; '''", "mov byte ptr [rbp+var_60], 0", "jmp loc_3AA0"], "succs": [[52, "jump"]]}, {"id": 77, "start": 15344, "end": 15354, "lines": ["cmp [rbp+var_65], 0; jumptable 00000000000038EE case 0", "jnz loc_44D8; jumptable 000000000000446B case 0"], "succs": [[78, "fall"], [212, "jump"]]}, {"id": 78, "start": 15354, "end": 15367, "lines": ["test byte ptr [rbp+var_9C], 1", "jnz loc_4A0B"], "succs": [[79, "fall"], [324, "jump"]]}, {"id": 79, "start": 15367, "end": 15377, "lines": ["cmp [rbp+var_61], 0", "jz loc_4A2F"], "succs": [[80, "fall"], [327, "jump"]]}, {"id": 80, "start": 15377, "end": 15388, "lines": ["cmp [rbp+var_70], 0", "jz loc_4BCB"], "succs": [[81, "fall"], [354, "jump"]]}, {"id": 81, "start": 15388, "end": 15401, "lines": ["mov byte ptr [rbp+var_60], 0", "xor edx, edx", "xor ecx, ecx", "jmp loc_3B81"], "succs": [[72, "jump"]]}, {"id": 82, "start": 15408, "end": 15412, "lines": ["mov byte ptr [rbp+var_60], 0; jumptable 00000000000038EE case 13"], "succs": [[83, "fall"]]}, {"id": 83, "start": 15412, "end": 15423, "lines": ["mov ecx, 0Dh", "mov r9d, 72h ; 'r'"], "succs": [[84, "fall"]]}, {"id": 84, "start": 15423, "end": 15433, "lines": ["cmp [rbp+var_67], 0", "jz loc_3B60"], "succs": [[69, "jump"], [85, "fall"]]}, {"id": 85, "start": 15433, "end": 15438, "lines": ["jmp loc_3B0C"], "succs": [[65, "jump"]]}, {"id": 86, "start": 15440, "end": 15444, "lines": ["mov byte ptr [rbp+var_60], 0; jumptable 00000000000038EE case 9"], "succs": [[87, "fall"]]}, {"id": 87, "start": 15444, "end": 15457, "lines": ["mov ecx, 9", "mov r9d, 74h ; 't'", "jmp short loc_3C3F"], "succs": [[84, "jump"]]}, {"id": 88, "start": 15464, "end": 15478, "lines": ["mov byte ptr [rbp+var_60], 0; jumptable 00000000000038EE case 63", "cmp [rbp+var_7C], 2", "jz loc_4B6E"], "succs": [[89, "fall"], [350, "jump"]]}, {"id": 89, "start": 15478, "end": 15488, "lines": ["cmp [rbp+var_7C], 5", "jnz loc_4640"], "succs": [[90, "fall"], [244, "jump"]]}, {"id": 90, "start": 15488, "end": 15501, "lines": ["test byte ptr [rbp+var_9C], 4", "jz loc_4640"], "succs": [[91, "fall"], [244, "jump"]]}, {"id": 91, "start": 15501, "end": 15522, "lines": ["lea rsi, [r13+2]", "mov r9d, 3Fh ; '?'", "xor edx, edx", "cmp rsi, r8", "jnb loc_3AA0"], "succs": [[52, "jump"], [92, "fall"]]}, {"id": 92, "start": 15522, "end": 15538, "lines": ["mov rax, [rbp+var_58]", "cmp byte ptr [rax+r13+1], 3Fh ; '?'", "jnz loc_3AA0"], "succs": [[52, "jump"], [93, "fall"]]}, {"id": 93, "start": 15538, "end": 15555, "lines": ["movzx ecx, byte ptr [rax+r13+2]", "lea eax, [rcx-21h]", "cmp al, 1Dh", "ja loc_3AA0"], "succs": [[52, "jump"], [94, "fall"]]}, {"id": 94, "start": 15555, "end": 15575, "lines": ["mov edx, 380051C1h", "bt rdx, rax", "setb dl", "test dl, dl", "jz loc_3AA0"], "succs": [[52, "jump"], [95, "fall"]]}, {"id": 95, "start": 15575, "end": 15585, "lines": ["cmp [rbp+var_61], 0", "jnz loc_4427"], "succs": [[96, "fall"], [199, "jump"]]}, {"id": 96, "start": 15585, "end": 15590, "lines": ["cmp rbx, r15", "jnb short loc_3CEB"], "succs": [[97, "fall"], [98, "jump"]]}, {"id": 97, "start": 15590, "end": 15595, "lines": ["mov byte ptr [r12+rbx], 3Fh ; '?'"], "succs": [[98, "fall"]]}, {"id": 98, "start": 15595, "end": 15604, "lines": ["lea rax, [rbx+1]", "cmp rax, r15", "jnb short loc_3CFA"], "succs": [[99, "fall"], [100, "jump"]]}, {"id": 99, "start": 15604, "end": 15610, "lines": ["mov byte ptr [r12+rbx+1], 22h ; '\"'"], "succs": [[100, "fall"]]}, {"id": 100, "start": 15610, "end": 15619, "lines": ["lea rax, [rbx+2]", "cmp rax, r15", "jnb short loc_3D09"], "succs": [[101, "fall"], [102, "jump"]]}, {"id": 101, "start": 15619, "end": 15625, "lines": ["mov byte ptr [r12+rbx+2], 22h ; '\"'"], "succs": [[102, "fall"]]}, {"id": 102, "start": 15625, "end": 15634, "lines": ["lea rax, [rbx+3]", "cmp rax, r15", "jnb short loc_3D18"], "succs": [[103, "fall"], [104, "jump"]]}, {"id": 103, "start": 15634, "end": 15640, "lines": ["mov byte ptr [r12+rbx+3], 3Fh ; '?'"], "succs": [[104, "fall"]]}, {"id": 104, "start": 15640, "end": 15654, "lines": ["add rbx, 4", "cmp [rbp+var_65], 0", "jnz loc_4E3B"], "succs": [[105, "fall"], [406, "jump"]]}, {"id": 105, "start": 15654, "end": 15677, "lines": ["mov r13, rsi", "mov [rbp+var_63], 0", "xor eax, eax", "add r13, 1", "cmp byte ptr [rbp+var_60], 0", "jnz loc_4313"], "succs": [[106, "fall"], [180, "jump"]]}, {"id": 106, "start": 15677, "end": 15680, "lines": ["nop dword ptr [rax]"], "succs": [[107, "fall"]]}, {"id": 107, "start": 15680, "end": 15694, "lines": ["xor eax, 1", "mov r9d, ecx", "and eax, r11d", "jmp loc_3AD1"], "succs": [[57, "jump"]]}, {"id": 108, "start": 15694, "end": 15714, "lines": ["mov byte ptr [rbp+var_60], 0; jumptable 00000000000038EE case 10", "mov ecx, 0Ah", "mov r9d, 6Eh ; 'n'", "jmp loc_3C3F"], "succs": [[84, "jump"]]}, {"id": 109, "start": 15714, "end": 15728, "lines": ["mov byte ptr [rbp+var_60], 0; jumptable 00000000000038EE case 35", "mov r9d, 23h ; '#'", "nop dword ptr [rax+00h]"], "succs": [[110, "fall"]]}, {"id": 110, "start": 15728, "end": 15733, "lines": ["test r13, r13", "jz short loc_3D90"], "succs": [[111, "fall"], [113, "jump"]]}, {"id": 111, "start": 15733, "end": 15740, "lines": ["xor edx, edx", "jmp loc_3AA0"], "succs": [[52, "jump"]]}, {"id": 112, "start": 15740, "end": 15760, "lines": ["mov byte ptr [rbp+var_60], 0; jumptable 00000000000038EE case 32", "mov r9d, 20h ; ' '", "nop word ptr [rax+rax+00000000h]"], "succs": [[113, "fall"]]}, {"id": 113, "start": 15760, "end": 15768, "lines": ["mov edx, r14d", "jmp loc_3B06"], "succs": [[64, "jump"]]}, {"id": 114, "start": 15768, "end": 15778, "lines": ["cmp [rbp+var_65], 0; jumptable 00000000000038EE case 7", "jnz loc_4BE1"], "succs": [[115, "fall"], [355, "jump"]]}, {"id": 115, "start": 15778, "end": 15788, "lines": ["cmp [rbp+var_61], 0", "jz loc_4BAE"], "succs": [[116, "fall"], [353, "jump"]]}, {"id": 116, "start": 15788, "end": 15799, "lines": ["cmp [rbp+var_70], 0", "jz loc_4DFC"], "succs": [[117, "fall"], [402, "jump"]]}, {"id": 117, "start": 15799, "end": 15815, "lines": ["mov byte ptr [rbp+var_60], 0", "xor edx, edx", "mov ecx, 7", "jmp loc_3B81"], "succs": [[72, "jump"]]}, {"id": 118, "start": 15815, "end": 15886, "lines": ["lea rax, asc_8348; jumptable 00000000000037E7 case 6", "mov [rbp+var_62], 1", "xor ebx, ebx", "mov [rbp+var_66], 0", "mov [rbp+var_67], 0", "mov [rbp+var_64], 0", "mov [rbp+var_7D], 0", "mov [rbp+var_9D], 1", "mov [rbp+var_61], 1", "mov [rbp+var_65], 1", "mov [rbp+n], 1", "mov [rbp+s2], rax", "mov [rbp+var_7C], 5", "jmp loc_3880"], "succs": [[8, "jump"]]}, {"id": 119, "start": 15886, "end": 15917, "lines": ["mov [rbp+var_62], 0; jumptable 00000000000037E7 case 1", "mov [rbp+var_66], 1", "mov [rbp+var_67], 1", "mov [rbp+var_64], 1", "mov [rbp+var_7D], 0", "mov [rbp+var_9D], 0", "mov [rbp+var_61], 1"], "succs": [[120, "fall"]]}, {"id": 120, "start": 15917, "end": 15953, "lines": ["lea rax, asc_8348+2; \"'`\"", "mov [rbp+n], 1", "xor ebx, ebx", "mov [rbp+s2], rax", "mov [rbp+var_7C], 2", "jmp loc_3880"], "succs": [[8, "jump"]]}, {"id": 121, "start": 15953, "end": 16013, "lines": ["movzx ecx, [rbp+var_65]; jumptable 00000000000037E7 case 0", "mov rdi, [rbp+n]", "mov [rbp+var_67], 0", "mov [rbp+var_64], 0", "cmp rdi, 1", "mov eax, ecx", "mov [rbp+var_9D], cl", "setnz [rbp+var_7D]", "xor eax, 1", "test rdi, rdi", "mov [rbp+var_66], al", "setnz al", "xor ebx, ebx", "and eax, ecx", "mov [rbp+var_61], 0", "mov [rbp+var_62], al", "jmp loc_3880"], "succs": [[8, "jump"]]}, {"id": 122, "start": 16013, "end": 16023, "lines": ["cmp [rbp+var_61], 0; jumptable 00000000000037E7 case 2", "jnz loc_4CA2"], "succs": [[123, "fall"], [372, "jump"]]}, {"id": 123, "start": 16023, "end": 16028, "lines": ["test r15, r15", "jz short loc_3EA1"], "succs": [[124, "fall"], [125, "jump"]]}, {"id": 124, "start": 16028, "end": 16033, "lines": ["mov byte ptr [r12], 27h ; '''"], "succs": [[125, "fall"]]}, {"id": 125, "start": 16033, "end": 16103, "lines": ["lea rax, asc_8348+2; \"'`\"", "mov [rbp+var_62], 0", "mov ebx, 1", "mov [rbp+var_66], 1", "mov [rbp+var_67], 0", "mov [rbp+var_64], 1", "mov [rbp+var_7D], 0", "mov [rbp+var_9D], 0", "mov [rbp+var_61], 0", "mov [rbp+n], 1", "mov [rbp+s2], rax", "mov [rbp+var_7C], 2", "jmp loc_3880"], "succs": [[8, "jump"]]}, {"id": 126, "start": 16103, "end": 16156, "lines": ["mov rax, [rbp+n]; jumptable 00000000000037E7 case 7", "mov [rbp+var_66], 0", "mov [rbp+var_67], 0", "cmp rax, 1", "mov [rbp+var_64], 0", "setnz [rbp+var_7D]", "test rax, rax", "setnz [rbp+var_62]", "xor ebx, ebx", "mov [rbp+var_9D], 1", "mov [rbp+var_61], 0", "mov [rbp+var_65], 1", "jmp loc_3880"], "succs": [[8, "jump"]]}, {"id": 127, "start": 16156, "end": 16166, "lines": ["cmp [rbp+var_61], 0; jumptable 00000000000037E7 case 5", "jnz loc_3DC7; jumptable 00000000000037E7 case 6"], "succs": [[118, "jump"], [128, "fall"]]}, {"id": 128, "start": 16166, "end": 16171, "lines": ["test r15, r15", "jz short loc_3F30"], "succs": [[129, "fall"], [130, "jump"]]}, {"id": 129, "start": 16171, "end": 16176, "lines": ["mov byte ptr [r12], 22h ; '\"'"], "succs": [[130, "fall"]]}, {"id": 130, "start": 16176, "end": 16239, "lines": ["lea rax, asc_8348; \"\\\"'`\"", "mov [rbp+var_66], 0", "mov ebx, 1", "mov [rbp+var_67], 0", "mov [rbp+var_64], 0", "mov [rbp+var_7D], 0", "mov [rbp+var_62], 1", "mov [rbp+var_9D], 1", "mov [rbp+var_65], 1", "mov [rbp+n], 1", "mov [rbp+s2], rax", "jmp loc_3880"], "succs": [[8, "jump"]]}, {"id": 131, "start": 16239, "end": 16249, "lines": ["cmp [rbp+var_61], 0; jumptable 00000000000037E7 case 4", "jnz loc_3E0E; jumptable 00000000000037E7 case 1"], "succs": [[119, "jump"], [132, "fall"]]}, {"id": 132, "start": 16249, "end": 16258, "lines": ["mov [rbp+var_65], 1", "jmp loc_3E97"], "succs": [[123, "jump"]]}, {"id": 133, "start": 16258, "end": 16298, "lines": ["mov [rbp+var_62], 0; jumptable 00000000000037E7 case 3", "mov [rbp+var_66], 1", "mov [rbp+var_67], 1", "mov [rbp+var_64], 1", "mov [rbp+var_7D], 0", "mov [rbp+var_9D], 0", "mov [rbp+var_61], 1", "mov [rbp+var_65], 1", "jmp loc_3E2D"], "succs": [[120, "jump"]]}, {"id": 134, "start": 16304, "end": 16318, "lines": ["mov rax, [rbp+n]", "lea rdx, [rax+r13]", "cmp r8, 0FFFFFFFFFFFFFFFFh", "jnz short loc_3FF6"], "succs": [[135, "fall"], [137, "jump"]]}, {"id": 135, "start": 16318, "end": 16324, "lines": ["cmp [rbp+var_7D], 0", "jz short loc_3FF6"], "succs": [[136, "fall"], [137, "jump"]]}, {"id": 136, "start": 16324, "end": 16374, "lines": ["mov byte ptr [rbp+var_90], r11b", "mov rdi, [rbp+var_58]; s", "mov [rbp+var_88], r10", "mov [rbp+var_60], rdx", "call cs:strlen_ptr", "mov r10, [rbp+var_88]", "mov rdx, [rbp+var_60]", "movzx r11d, byte ptr [rbp+var_90]", "mov r8, rax"], "succs": [[137, "fall"]]}, {"id": 137, "start": 16374, "end": 16383, "lines": ["cmp r8, rdx", "jb loc_4438"], "succs": [[138, "fall"], [200, "jump"]]}, {"id": 138, "start": 16383, "end": 16448, "lines": ["mov [rbp+var_90], r8", "mov rdi, r10; s1", "mov rdx, [rbp+n]; n", "mov byte ptr [rbp+var_88], r11b", "mov rsi, [rbp+s2]; s2", "mov [rbp+var_60], r10", "call cs:memcmp_ptr", "mov r10, [rbp+var_60]", "movzx r11d, byte ptr [rbp+var_88]", "test eax, eax", "mov r8, [rbp+var_90]", "jnz loc_4470"], "succs": [[139, "fall"], [204, "jump"]]}, {"id": 139, "start": 16448, "end": 16458, "lines": ["cmp [rbp+var_61], 0", "jnz loc_4A49"], "succs": [[140, "fall"], [328, "jump"]]}, {"id": 140, "start": 16458, "end": 16472, "lines": ["movzx r9d, byte ptr [r10]", "cmp r9b, 3Fh ; '?'", "jg loc_48A0"], "succs": [[141, "fall"], [296, "jump"]]}, {"id": 141, "start": 16472, "end": 16477, "lines": ["test r9b, r9b", "js short def_4075; jumptable 0000000000004075 default case, cases 1-6,14-31"], "succs": [[142, "fall"], [147, "jump"]]}, {"id": 142, "start": 16477, "end": 16483, "lines": ["cmp r9b, 3Fh; switch 64 cases", "ja short def_4075; jumptable 0000000000004075 default case, cases 1-6,14-31"], "succs": [[143, "fall"], [147, "jump"]]}, {"id": 143, "start": 16483, "end": 16504, "lines": ["lea rdx, jpt_4075", "movzx eax, r9b", "movsxd rax, ds:(jpt_4075 - 8D2Ch)[rdx+rax*4]", "add rax, rdx", "jmp rax; switch jump"], "succs": [[144, "switch"], [147, "switch"], [158, "switch"], [160, "switch"], [161, "switch"], [162, "switch"], [163, "switch"], [164, "switch"], [165, "switch"], [176, "switch"], [181, "switch"], [183, "switch"], [185, "switch"], [187, "switch"], [189, "switch"]]}, {"id": 144, "start": 16512, "end": 16522, "lines": ["cmp [rbp+var_66], 0; jumptable 0000000000004075 cases 37,43-58", "jnz loc_4D99"], "succs": [[145, "fall"], [393, "jump"]]}, {"id": 145, "start": 16522, "end": 16537, "lines": ["movzx edx, [rbp+var_62]", "cmp [rbp+var_70], 0", "jz loc_3941"], "succs": [[23, "jump"], [146, "fall"]]}, {"id": 146, "start": 16537, "end": 16545, "lines": ["mov byte ptr [rbp+var_60], dl", "jmp loc_3914"], "succs": [[20, "jump"]]}, {"id": 147, "start": 16552, "end": 16573, "lines": ["movzx eax, [rbp+var_62]; jumptable 0000000000004075 default case, cases 1-6,14-31", "mov byte ptr [rbp+var_60], al", "cmp [rbp+var_B0], 1", "jz loc_3A42"], "succs": [[49, "jump"], [148, "fall"]]}, {"id": 148, "start": 16573, "end": 16591, "lines": ["mov [rbp+var_40], 0", "lea rcx, [rbp+var_40]", "cmp r8, 0FFFFFFFFFFFFFFFFh", "jnz short loc_4116"], "succs": [[149, "fall"], [150, "jump"]]}, {"id": 149, "start": 16591, "end": 16662, "lines": ["mov byte ptr [rbp+var_C0], r11b", "mov rdi, [rbp+var_58]; s", "mov [rbp+var_B8], rcx", "mov [rbp+var_90], r10", "mov byte ptr [rbp+var_88], r9b", "call cs:strlen_ptr", "movzx r11d, byte ptr [rbp+var_C0]", "mov rcx, [rbp+var_B8]", "mov r10, [rbp+var_90]", "movzx r9d, byte ptr [rbp+var_88]", "mov r8, rax"], "succs": [[150, "fall"]]}, {"id": 150, "start": 16662, "end": 16754, "lines": ["mov rax, r8", "mov byte ptr [rbp+var_B8], r11b", "lea rdi, [rbp+wc]; pc32", "mov rsi, r10; s", "sub rax, r13", "mov byte ptr [rbp+var_90], r9b", "mov [rbp+var_88], r8", "mov rdx, rax", "mov [rbp+var_C8], rax", "mov [rbp+var_C0], r10", "call sub_69C0", "mov r8, [rbp+var_88]", "movzx r9d, byte ptr [rbp+var_90]", "test rax, rax", "movzx r11d, byte ptr [rbp+var_B8]", "mov rcx, rax", "jz loc_41F0"], "succs": [[151, "fall"], [157, "jump"]]}, {"id": 151, "start": 16754, "end": 16771, "lines": ["cmp rax, 0FFFFFFFFFFFFFFFFh", "mov r10, [rbp+var_C0]", "jz loc_4B1A"], "succs": [[152, "fall"], [342, "jump"]]}, {"id": 152, "start": 16771, "end": 16781, "lines": ["cmp rax, 0FFFFFFFFFFFFFFFEh", "jz loc_4B27"], "succs": [[153, "fall"], [343, "jump"]]}, {"id": 153, "start": 16781, "end": 16791, "lines": ["cmp [rbp+var_67], 0", "jnz loc_4C5D"], "succs": [[154, "fall"], [366, "jump"]]}, {"id": 154, "start": 16791, "end": 16866, "lines": ["mov [rbp+var_C0], r8", "mov edi, [rbp+wc]; wc", "mov byte ptr [rbp+var_B8], r11b", "mov byte ptr [rbp+var_90], r9b", "mov [rbp+var_88], rax", "call cs:iswprint_ptr", "mov rcx, [rbp+var_88]", "movzx r9d, byte ptr [rbp+var_90]", "test eax, eax", "movzx r11d, byte ptr [rbp+var_B8]", "mov r8, [rbp+var_C0]", "jz loc_4B50"], "succs": [[155, "fall"], [347, "jump"]]}, {"id": 155, "start": 16866, "end": 16876, "lines": ["cmp rcx, 1", "jnz loc_4E34"], "succs": [[156, "fall"], [405, "jump"]]}, {"id": 156, "start": 16876, "end": 16880, "lines": ["nop dword ptr [rax+00h]"], "succs": [[157, "fall"]]}, {"id": 157, "start": 16880, "end": 16888, "lines": ["mov edx, r14d", "jmp loc_3AA0"], "succs": [[52, "jump"]]}, {"id": 158, "start": 16888, "end": 16895, "lines": ["movzx eax, [rbp+var_62]; jumptable 0000000000004075 cases 33,34,36,38,40-42,59-62", "mov byte ptr [rbp+var_60], al"], "succs": [[159, "fall"]]}, {"id": 159, "start": 16895, "end": 16902, "lines": ["xor edx, edx", "jmp loc_3B06"], "succs": [[64, "jump"]]}, {"id": 160, "start": 16902, "end": 16920, "lines": ["movzx eax, [rbp+var_62]; jumptable 0000000000004075 case 35", "mov r9d, 23h ; '#'", "mov byte ptr [rbp+var_60], al", "jmp loc_3D70"], "succs": [[110, "jump"]]}, {"id": 161, "start": 16920, "end": 16941, "lines": ["movzx eax, [rbp+var_62]; jumptable 0000000000004075 case 32", "mov r9d, 20h ; ' '", "mov edx, r14d", "mov byte ptr [rbp+var_60], al", "jmp loc_3B06"], "succs": [[64, "jump"]]}, {"id": 162, "start": 16941, "end": 16953, "lines": ["movzx eax, [rbp+var_62]; jumptable 0000000000004075 case 13", "mov byte ptr [rbp+var_60], al", "jmp loc_3C34"], "succs": [[83, "jump"]]}, {"id": 163, "start": 16953, "end": 16976, "lines": ["movzx eax, [rbp+var_62]; jumptable 0000000000004075 case 10", "mov ecx, 0Ah", "mov r9d, 6Eh ; 'n'", "mov byte ptr [rbp+var_60], al", "jmp loc_3C3F"], "succs": [[84, "jump"]]}, {"id": 164, "start": 16976, "end": 16988, "lines": ["movzx eax, [rbp+var_62]; jumptable 0000000000004075 case 9", "mov byte ptr [rbp+var_60], al", "jmp loc_3C54"], "succs": [[87, "jump"]]}, {"id": 165, "start": 16988, "end": 17010, "lines": ["movzx eax, [rbp+var_62]; jumptable 0000000000004075 case 0", "mov byte ptr [rbp+var_60], al", "mov eax, r11d", "xor eax, 1", "and al, [rbp+var_64]", "jz loc_44F5"], "succs": [[166, "fall"], [214, "jump"]]}, {"id": 166, "start": 17010, "end": 17024, "lines": ["nop dword ptr [rax]", "nop word ptr [rax+rax+00000000h]"], "succs": [[167, "fall"]]}, {"id": 167, "start": 17024, "end": 17029, "lines": ["cmp rbx, r15", "jnb short loc_428A"], "succs": [[168, "fall"], [169, "jump"]]}, {"id": 168, "start": 17029, "end": 17034, "lines": ["mov byte ptr [r12+rbx], 27h ; '''"], "succs": [[169, "fall"]]}, {"id": 169, "start": 17034, "end": 17043, "lines": ["lea rdx, [rbx+1]", "cmp rdx, r15", "jnb short loc_4299"], "succs": [[170, "fall"], [171, "jump"]]}, {"id": 170, "start": 17043, "end": 17049, "lines": ["mov byte ptr [r12+rbx+1], 24h ; '$'"], "succs": [[171, "fall"]]}, {"id": 171, "start": 17049, "end": 17058, "lines": ["lea rdx, [rbx+2]", "cmp rdx, r15", "jnb short loc_42A8"], "succs": [[172, "fall"], [173, "jump"]]}, {"id": 172, "start": 17058, "end": 17064, "lines": ["mov byte ptr [r12+rbx+2], 27h ; '''"], "succs": [[173, "fall"]]}, {"id": 173, "start": 17064, "end": 17081, "lines": ["lea rdx, [rbx+3]", "add rbx, 4", "cmp rdx, r15", "jnb loc_4A5B"], "succs": [[174, "fall"], [329, "jump"]]}, {"id": 174, "start": 17081, "end": 17100, "lines": ["mov byte ptr [r12+rdx], 5Ch ; '\\'", "add r13, 1", "cmp byte ptr [rbp+var_60], 0", "jz loc_4BEE"], "succs": [[175, "fall"], [356, "jump"]]}, {"id": 175, "start": 17100, "end": 17120, "lines": ["mov [rbp+var_63], 0", "movzx r11d, byte ptr [rbp+var_60]", "mov r9d, 30h ; '0'", "jmp loc_3987"], "succs": [[33, "jump"]]}, {"id": 176, "start": 17120, "end": 17130, "lines": ["cmp [rbp+var_65], 0; jumptable 0000000000004075 case 12", "jnz loc_4EEB"], "succs": [[177, "fall"], [418, "jump"]]}, {"id": 177, "start": 17130, "end": 17152, "lines": ["movzx eax, [rbp+var_62]", "xor edx, edx", "mov ecx, 0Ch", "mov byte ptr [rbp+var_60], al", "nop dword ptr [rax+rax+00000000h]"], "succs": [[178, "fall"]]}, {"id": 178, "start": 17152, "end": 17154, "lines": ["xor eax, eax"], "succs": [[179, "fall"]]}, {"id": 179, "start": 17154, "end": 17171, "lines": ["and [rbp+var_63], dl", "add r13, 1", "cmp byte ptr [rbp+var_60], 0", "jz loc_3D40"], "succs": [[107, "jump"], [180, "fall"]]}, {"id": 180, "start": 17171, "end": 17188, "lines": ["mov eax, r11d", "mov r9d, ecx", "xor eax, 1", "and al, [rbp+var_64]", "jmp loc_3954"], "succs": [[25, "jump"]]}, {"id": 181, "start": 17188, "end": 17198, "lines": ["cmp [rbp+var_65], 0; jumptable 0000000000004075 case 11", "jnz loc_4EBC"], "succs": [[182, "fall"], [415, "jump"]]}, {"id": 182, "start": 17198, "end": 17214, "lines": ["movzx eax, [rbp+var_62]", "xor edx, edx", "mov ecx, 0Bh", "mov byte ptr [rbp+var_60], al", "jmp short loc_4300"], "succs": [[178, "jump"]]}, {"id": 183, "start": 17214, "end": 17231, "lines": ["movzx eax, [rbp+var_62]; jumptable 0000000000004075 case 63", "mov byte ptr [rbp+var_60], al", "cmp [rbp+var_7C], 2", "jnz loc_3C76"], "succs": [[89, "jump"], [184, "fall"]]}, {"id": 184, "start": 17231, "end": 17246, "lines": ["mov [rbp+var_63], 0", "add r13, 1", "mov ecx, 3Fh ; '?'", "jmp short loc_4313"], "succs": [[180, "jump"]]}, {"id": 185, "start": 17246, "end": 17260, "lines": ["movzx eax, [rbp+var_62]; jumptable 0000000000004075 case 39", "cmp [rbp+var_7C], 2", "jz loc_4E63"], "succs": [[186, "fall"], [409, "jump"]]}, {"id": 186, "start": 17260, "end": 17279, "lines": ["mov byte ptr [rbp+var_60], al", "mov edx, eax", "mov r9d, 27h ; '''", "mov [rbp+var_7E], al", "jmp loc_3AA0"], "succs": [[52, "jump"]]}, {"id": 187, "start": 17279, "end": 17289, "lines": ["cmp [rbp+var_65], 0; jumptable 0000000000004075 case 8", "jnz loc_4ECD"], "succs": [[188, "fall"], [416, "jump"]]}, {"id": 188, "start": 17289, "end": 17308, "lines": ["movzx eax, [rbp+var_62]", "xor edx, edx", "mov ecx, 8", "mov byte ptr [rbp+var_60], al", "jmp loc_4300"], "succs": [[178, "jump"]]}, {"id": 189, "start": 17308, "end": 17318, "lines": ["cmp [rbp+var_65], 0; jumptable 0000000000004075 case 7", "jnz loc_4EDE"], "succs": [[190, "fall"], [417, "jump"]]}, {"id": 190, "start": 17318, "end": 17336, "lines": ["mov [rbp+var_63], 0", "add r13, 1", "mov ecx, 7", "jmp loc_4313"], "succs": [[180, "jump"]]}, {"id": 191, "start": 17344, "end": 17354, "lines": ["cmp r9b, 7Ah ; 'z'", "jg loc_45A0"], "succs": [[192, "fall"], [228, "jump"]]}, {"id": 192, "start": 17354, "end": 17364, "lines": ["cmp r9b, 40h ; '@'", "jz def_38EE; jumptable 00000000000038EE default case, cases 1-6,14-31"], "succs": [[47, "jump"], [193, "fall"]]}, {"id": 193, "start": 17364, "end": 17395, "lines": ["lea ecx, [r9-41h]", "mov eax, 1", "mov rdx, 3FFFFFF53FFFFFFh", "shl rax, cl", "test rax, rdx", "jnz loc_38F8; jumptable 00000000000038EE cases 37,43-58"], "succs": [[17, "jump"], [194, "fall"]]}, {"id": 194, "start": 17395, "end": 17406, "lines": ["test eax, 0A4000000h", "jnz loc_3B00; jumptable 00000000000038EE cases 33,34,36,38,40-42,59-62"], "succs": [[63, "jump"], [195, "fall"]]}, {"id": 195, "start": 17406, "end": 17416, "lines": ["cmp [rbp+var_7C], 2", "jz loc_4650"], "succs": [[196, "fall"], [245, "jump"]]}, {"id": 196, "start": 17416, "end": 17426, "lines": ["cmp [rbp+var_65], 0", "jz loc_4917"], "succs": [[197, "fall"], [304, "jump"]]}, {"id": 197, "start": 17426, "end": 17436, "lines": ["cmp [rbp+var_61], 0", "jz loc_4AA7"], "succs": [[198, "fall"], [335, "jump"]]}, {"id": 198, "start": 17436, "end": 17447, "lines": ["cmp [rbp+n], 0", "jnz loc_465A"], "succs": [[199, "fall"], [246, "jump"]]}, {"id": 199, "start": 17447, "end": 17458, "lines": ["mov r14, r8", "mov rbx, r12", "jmp loc_3B30"], "succs": [[67, "jump"]]}, {"id": 200, "start": 17464, "end": 17478, "lines": ["movzx r9d, byte ptr [r10]", "cmp r9b, 3Fh ; '?'", "jg loc_4610"], "succs": [[201, "fall"], [238, "jump"]]}, {"id": 201, "start": 17478, "end": 17487, "lines": ["test r9b, r9b", "js def_38EE; jumptable 00000000000038EE default case, cases 1-6,14-31"], "succs": [[47, "jump"], [202, "fall"]]}, {"id": 202, "start": 17487, "end": 17497, "lines": ["cmp r9b, 3Fh; switch 64 cases", "ja def_38EE; jumptable 00000000000038EE default case, cases 1-6,14-31"], "succs": [[47, "jump"], [203, "fall"]]}, {"id": 203, "start": 17497, "end": 17518, "lines": ["lea rcx, jpt_446B", "movzx eax, r9b", "movsxd rax, ds:(jpt_446B - 8E2Ch)[rcx+rax*4]", "add rax, rcx", "jmp rax; switch jump"], "succs": [[47, "switch"], [63, "switch"], [68, "switch"], [73, "switch"], [74, "switch"], [75, "switch"], [82, "switch"], [86, "switch"], [88, "switch"], [209, "switch"], [212, "switch"], [223, "switch"], [224, "switch"], [225, "switch"], [226, "switch"]]}, {"id": 204, "start": 17520, "end": 17530, "lines": ["movzx r9d, byte ptr [r10]", "cmp r9b, 3Fh ; '?'", "jle short loc_4446"], "succs": [[201, "jump"], [205, "fall"]]}, {"id": 205, "start": 17530, "end": 17540, "lines": ["cmp r9b, 7Ah ; 'z'", "jg loc_467D"], "succs": [[206, "fall"], [249, "jump"]]}, {"id": 206, "start": 17540, "end": 17550, "lines": ["cmp r9b, 40h ; '@'", "jz def_38EE; jumptable 00000000000038EE default case, cases 1-6,14-31"], "succs": [[47, "jump"], [207, "fall"]]}, {"id": 207, "start": 17550, "end": 17581, "lines": ["lea ecx, [r9-41h]", "mov eax, 1", "shl rax, cl", "mov rcx, 3FFFFFF53FFFFFFh", "test rax, rcx", "jz loc_43F3"], "succs": [[194, "jump"], [208, "fall"]]}, {"id": 208, "start": 17581, "end": 17584, "lines": ["nop dword ptr [rax]"], "succs": [[209, "fall"]]}, {"id": 209, "start": 17584, "end": 17594, "lines": ["cmp [rbp+var_66], 0; jumptable 000000000000446B cases 37,43-58", "jnz loc_4AD8"], "succs": [[210, "fall"], [337, "jump"]]}, {"id": 210, "start": 17594, "end": 17605, "lines": ["cmp [rbp+var_70], 0", "jz loc_4928"], "succs": [[211, "fall"], [305, "jump"]]}, {"id": 211, "start": 17605, "end": 17618, "lines": ["mov byte ptr [rbp+var_60], 0", "movzx edx, [rbp+var_62]", "jmp loc_3914"], "succs": [[20, "jump"]]}, {"id": 212, "start": 17624, "end": 17634, "lines": ["cmp [rbp+var_61], 0; jumptable 000000000000446B case 0", "jnz loc_4A49"], "succs": [[213, "fall"], [328, "jump"]]}, {"id": 213, "start": 17634, "end": 17653, "lines": ["mov eax, r11d", "mov byte ptr [rbp+var_60], 0", "xor eax, 1", "and al, [rbp+var_64]", "jnz loc_4280"], "succs": [[167, "jump"], [214, "fall"]]}, {"id": 214, "start": 17653, "end": 17658, "lines": ["cmp rbx, r15", "jnb short loc_44FF"], "succs": [[215, "fall"], [216, "jump"]]}, {"id": 215, "start": 17658, "end": 17663, "lines": ["mov byte ptr [r12+rbx], 5Ch ; '\\'"], "succs": [[216, "fall"]]}, {"id": 216, "start": 17663, "end": 17684, "lines": ["lea rsi, [rbx+1]", "lea rcx, [r13+1]", "cmp [rbp+var_9D], 0", "jz loc_4E6B"], "succs": [[217, "fall"], [410, "jump"]]}, {"id": 217, "start": 17684, "end": 17689, "lines": ["cmp rcx, r8", "jnb short loc_4534"], "succs": [[218, "fall"], [219, "jump"]]}, {"id": 218, "start": 17689, "end": 17716, "lines": ["mov rax, [rbp+var_58]", "movzx eax, byte ptr [rax+r13+1]", "mov byte ptr [rbp+var_88], al", "sub eax, 30h ; '0'", "cmp al, 9", "jbe loc_4C00"], "succs": [[219, "fall"], [357, "jump"]]}, {"id": 219, "start": 17716, "end": 17729, "lines": ["mov rax, [rbp+var_70]", "test rax, rax", "jz loc_4AFB"], "succs": [[220, "fall"], [340, "jump"]]}, {"id": 220, "start": 17729, "end": 17741, "lines": ["xor edx, edx", "test byte ptr [rax+6], 1", "jz loc_4AC3"], "succs": [[221, "fall"], [336, "jump"]]}, {"id": 221, "start": 17741, "end": 17744, "lines": ["mov rbx, rsi"], "succs": [[222, "fall"]]}, {"id": 222, "start": 17744, "end": 17755, "lines": ["mov r9d, 30h ; '0'", "jmp loc_3945"], "succs": [[24, "jump"]]}, {"id": 223, "start": 17755, "end": 17764, "lines": ["mov byte ptr [rbp+var_60], 0; jumptable 000000000000446B case 35", "jmp loc_3D70"], "succs": [[110, "jump"]]}, {"id": 224, "start": 17764, "end": 17776, "lines": ["mov byte ptr [rbp+var_60], 0; jumptable 000000000000446B case 32", "mov edx, r14d", "jmp loc_3B06"], "succs": [[64, "jump"]]}, {"id": 225, "start": 17776, "end": 17794, "lines": ["mov ecx, r9d; jumptable 000000000000446B case 10", "mov byte ptr [rbp+var_60], 0", "mov r9d, 6Eh ; 'n'", "jmp loc_3C3F"], "succs": [[84, "jump"]]}, {"id": 226, "start": 17794, "end": 17804, "lines": ["cmp [rbp+var_65], 0; jumptable 000000000000446B case 7", "jz loc_3DA2"], "succs": [[115, "jump"], [227, "fall"]]}, {"id": 227, "start": 17804, "end": 17817, "lines": ["mov r9d, 61h ; 'a'", "xor edx, edx", "jmp loc_3937"], "succs": [[22, "jump"]]}, {"id": 228, "start": 17824, "end": 17834, "lines": ["cmp r9b, 7Dh ; '}'", "jz loc_4690"], "succs": [[229, "fall"], [251, "jump"]]}, {"id": 229, "start": 17834, "end": 17844, "lines": ["mov byte ptr [rbp+var_60], 0", "jg loc_4630"], "succs": [[230, "fall"], [242, "jump"]]}, {"id": 230, "start": 17844, "end": 17854, "lines": ["cmp r9b, 7Bh ; '{'", "jnz loc_41FF"], "succs": [[159, "jump"], [231, "fall"]]}, {"id": 231, "start": 17854, "end": 17864, "lines": ["cmp r8, 0FFFFFFFFFFFFFFFFh", "jz loc_469E"], "succs": [[232, "fall"], [252, "jump"]]}, {"id": 232, "start": 17864, "end": 17871, "lines": ["cmp r8, 1", "setnz al"], "succs": [[233, "fall"]]}, {"id": 233, "start": 17871, "end": 17879, "lines": ["test al, al", "jz loc_3D70"], "succs": [[110, "jump"], [234, "fall"]]}, {"id": 234, "start": 17879, "end": 17886, "lines": ["xor edx, edx", "jmp loc_3AA0"], "succs": [[52, "jump"]]}, {"id": 235, "start": 17888, "end": 17898, "lines": ["cmp byte ptr [rbp+var_60], 0", "jz loc_46D6"], "succs": [[236, "fall"], [258, "jump"]]}, {"id": 236, "start": 17898, "end": 17916, "lines": ["movzx ecx, [rbp+var_64]", "mov r14, r8", "and [rbp+var_65], cl", "mov rbx, r12", "jmp loc_3B20"], "succs": [[66, "jump"]]}, {"id": 237, "start": 17920, "end": 17930, "lines": ["mov edx, r14d", "xor eax, eax", "jmp loc_3AC4"], "succs": [[56, "jump"]]}, {"id": 238, "start": 17936, "end": 17946, "lines": ["cmp r9b, 7Ah ; 'z'", "jle loc_4484"], "succs": [[206, "jump"], [239, "fall"]]}, {"id": 239, "start": 17946, "end": 17956, "lines": ["cmp r9b, 7Dh ; '}'", "jz loc_4B65"], "succs": [[240, "fall"], [349, "jump"]]}, {"id": 240, "start": 17956, "end": 17966, "lines": ["mov byte ptr [rbp+var_60], 0", "cmp r9b, 7Dh ; '}'", "jle short loc_45B4"], "succs": [[230, "jump"], [241, "fall"]]}, {"id": 241, "start": 17966, "end": 17968, "lines": ["xchg ax, ax"], "succs": [[242, "fall"]]}, {"id": 242, "start": 17968, "end": 17978, "lines": ["cmp r9b, 7Eh ; '~'", "jz loc_3D70"], "succs": [[110, "jump"], [243, "fall"]]}, {"id": 243, "start": 17978, "end": 17983, "lines": ["jmp loc_3A34"], "succs": [[48, "jump"]]}, {"id": 244, "start": 17984, "end": 17997, "lines": ["mov r9d, 3Fh ; '?'", "xor edx, edx", "jmp loc_3AA0"], "succs": [[52, "jump"]]}, {"id": 245, "start": 18000, "end": 18010, "lines": ["cmp [rbp+var_61], 0", "jnz loc_475F"], "succs": [[246, "fall"], [270, "jump"]]}, {"id": 246, "start": 18010, "end": 18023, "lines": ["add r13, 1", "test r11b, r11b", "jnz loc_4908"], "succs": [[247, "fall"], [303, "jump"]]}, {"id": 247, "start": 18023, "end": 18032, "lines": ["cmp rbx, r15", "jb loc_4A6A"], "succs": [[248, "fall"], [330, "jump"]]}, {"id": 248, "start": 18032, "end": 18045, "lines": ["mov [rbp+var_63], 0", "add rbx, 1", "jmp loc_3886"], "succs": [[9, "jump"]]}, {"id": 249, "start": 18045, "end": 18051, "lines": ["cmp r9b, 7Dh ; '}'", "jnz short loc_4624"], "succs": [[240, "jump"], [250, "fall"]]}, {"id": 250, "start": 18051, "end": 18064, "lines": ["xchg ax, ax", "nop word ptr [rax+rax+00000000h]"], "succs": [[251, "fall"]]}, {"id": 251, "start": 18064, "end": 18078, "lines": ["mov byte ptr [rbp+var_60], 0", "cmp r8, 0FFFFFFFFFFFFFFFFh", "jnz loc_45C8"], "succs": [[232, "jump"], [252, "fall"]]}, {"id": 252, "start": 18078, "end": 18094, "lines": ["mov rax, [rbp+var_58]", "cmp byte ptr [rax+1], 0", "setnz al", "jmp loc_45CF"], "succs": [[233, "jump"]]}, {"id": 253, "start": 18096, "end": 18108, "lines": ["movzx edx, [rbp+var_61]", "test dl, dl", "jz loc_4938"], "succs": [[254, "fall"], [306, "jump"]]}, {"id": 254, "start": 18108, "end": 18115, "lines": ["cmp [rbp+var_70], 0", "jz short loc_46CF"], "succs": [[255, "fall"], [256, "jump"]]}, {"id": 255, "start": 18115, "end": 18127, "lines": ["mov byte ptr [rbp+var_60], 0", "mov ecx, r9d", "jmp loc_3B81"], "succs": [[72, "jump"]]}, {"id": 256, "start": 18127, "end": 18130, "lines": ["mov ecx, r9d"], "succs": [[257, "fall"]]}, {"id": 257, "start": 18130, "end": 18134, "lines": ["movzx edx, [rbp+var_61]"], "succs": [[258, "fall"]]}, {"id": 258, "start": 18134, "end": 18152, "lines": ["and [rbp+var_63], dl", "add r13, 1", "mov eax, r11d", "mov r9d, ecx", "jmp loc_3AD1"], "succs": [[57, "jump"]]}, {"id": 259, "start": 18160, "end": 18166, "lines": ["cmp [rbp+var_61], 0", "jnz short loc_475F"], "succs": [[260, "fall"], [270, "jump"]]}, {"id": 260, "start": 18166, "end": 18170, "lines": ["mov [rbp+var_7E], 0"], "succs": [[261, "fall"]]}, {"id": 261, "start": 18170, "end": 18195, "lines": ["test r15, r15", "setnz al", "cmp [rbp+var_A8], 0", "setz dl", "and al, dl", "jnz loc_49DF"], "succs": [[262, "fall"], [322, "jump"]]}, {"id": 262, "start": 18195, "end": 18200, "lines": ["cmp rbx, r15", "jnb short loc_471D"], "succs": [[263, "fall"], [264, "jump"]]}, {"id": 263, "start": 18200, "end": 18205, "lines": ["mov byte ptr [r12+rbx], 27h ; '''"], "succs": [[264, "fall"]]}, {"id": 264, "start": 18205, "end": 18214, "lines": ["lea rax, [rbx+1]", "cmp rax, r15", "jnb short loc_472C"], "succs": [[265, "fall"], [266, "jump"]]}, {"id": 265, "start": 18214, "end": 18220, "lines": ["mov byte ptr [r12+rbx+1], 5Ch ; '\\'"], "succs": [[266, "fall"]]}, {"id": 266, "start": 18220, "end": 18229, "lines": ["lea rax, [rbx+2]", "cmp rax, r15", "jnb short loc_473B"], "succs": [[267, "fall"], [268, "jump"]]}, {"id": 267, "start": 18229, "end": 18235, "lines": ["mov byte ptr [r12+rbx+2], 27h ; '''"], "succs": [[268, "fall"]]}, {"id": 268, "start": 18235, "end": 18259, "lines": ["add rbx, 3", "add r13, 1", "mov r9d, 27h ; '''", "cmp [rbp+var_7E], 0", "jnz loc_3958"], "succs": [[26, "jump"], [269, "fall"]]}, {"id": 269, "start": 18259, "end": 18271, "lines": ["mov [rbp+var_7E], r14b", "xor r11d, r11d", "jmp loc_3995"], "succs": [[36, "jump"]]}, {"id": 270, "start": 18271, "end": 18282, "lines": ["mov r14, r8", "mov rbx, r12", "jmp loc_3B20"], "succs": [[66, "jump"]]}, {"id": 271, "start": 18282, "end": 18357, "lines": ["mov [rbp+var_C0], r8", "mov edi, [rbp+wc]; wc", "mov byte ptr [rbp+var_B8], r11b", "mov byte ptr [rbp+var_90], r9b", "mov [rbp+var_88], rcx", "call cs:iswprint_ptr", "mov rcx, [rbp+var_88]", "movzx r9d, byte ptr [rbp+var_90]", "test eax, eax", "movzx r11d, byte ptr [rbp+var_B8]", "mov r8, [rbp+var_C0]", "jnz loc_4E34"], "succs": [[272, "fall"], [405, "jump"]]}, {"id": 272, "start": 18357, "end": 18368, "lines": ["nop word ptr [rax+rax+00000000h]"], "succs": [[273, "fall"]]}, {"id": 273, "start": 18368, "end": 18375, "lines": ["movzx eax, [rbp+var_65]", "xor r14d, r14d"], "succs": [[274, "fall"]]}, {"id": 274, "start": 18375, "end": 18391, "lines": ["lea rdx, [rcx+r13]", "add r13, 1", "test al, al", "jz loc_4CDB"], "succs": [[275, "fall"], [373, "jump"]]}, {"id": 275, "start": 18391, "end": 18401, "lines": ["cmp [rbp+var_61], 0", "jnz loc_4A49"], "succs": [[276, "fall"], [328, "jump"]]}, {"id": 276, "start": 18401, "end": 18411, "lines": ["movzx ecx, [rbp+var_64]", "mov rsi, [rbp+var_58]", "jmp short loc_4806"], "succs": [[280, "jump"]]}, {"id": 277, "start": 18416, "end": 18421, "lines": ["cmp rax, r15", "jnb short loc_47F9"], "succs": [[278, "fall"], [279, "jump"]]}, {"id": 278, "start": 18421, "end": 18425, "lines": ["mov [r12+rax], r9b"], "succs": [[279, "fall"]]}, {"id": 279, "start": 18425, "end": 18438, "lines": ["movzx r9d, byte ptr [rsi+r13]", "add rbx, 4", "add r13, 1"], "succs": [[280, "fall"]]}, {"id": 280, "start": 18438, "end": 18448, "lines": ["mov eax, r11d", "xor eax, 1", "and al, cl", "jz short loc_483F"], "succs": [[281, "fall"], [288, "jump"]]}, {"id": 281, "start": 18448, "end": 18453, "lines": ["cmp rbx, r15", "jnb short loc_481A"], "succs": [[282, "fall"], [283, "jump"]]}, {"id": 282, "start": 18453, "end": 18458, "lines": ["mov byte ptr [r12+rbx], 27h ; '''"], "succs": [[283, "fall"]]}, {"id": 283, "start": 18458, "end": 18467, "lines": ["lea rdi, [rbx+1]", "cmp rdi, r15", "jnb short loc_4829"], "succs": [[284, "fall"], [285, "jump"]]}, {"id": 284, "start": 18467, "end": 18473, "lines": ["mov byte ptr [r12+rbx+1], 24h ; '$'"], "succs": [[285, "fall"]]}, {"id": 285, "start": 18473, "end": 18482, "lines": ["lea rdi, [rbx+2]", "cmp rdi, r15", "jnb short loc_4838"], "succs": [[286, "fall"], [287, "jump"]]}, {"id": 286, "start": 18482, "end": 18488, "lines": ["mov byte ptr [r12+rbx+2], 27h ; '''"], "succs": [[287, "fall"]]}, {"id": 287, "start": 18488, "end": 18495, "lines": ["add rbx, 3", "mov r11d, eax"], "succs": [[288, "fall"]]}, {"id": 288, "start": 18495, "end": 18500, "lines": ["cmp rbx, r15", "jnb short loc_4849"], "succs": [[289, "fall"], [290, "jump"]]}, {"id": 289, "start": 18500, "end": 18505, "lines": ["mov byte ptr [r12+rbx], 5Ch ; '\\'"], "succs": [[290, "fall"]]}, {"id": 290, "start": 18505, "end": 18514, "lines": ["lea rax, [rbx+1]", "cmp rax, r15", "jnb short loc_4860"], "succs": [[291, "fall"], [292, "jump"]]}, {"id": 291, "start": 18514, "end": 18528, "lines": ["mov eax, r9d", "shr al, 6", "add eax, 30h ; '0'", "mov [r12+rbx+1], al"], "succs": [[292, "fall"]]}, {"id": 292, "start": 18528, "end": 18537, "lines": ["lea rax, [rbx+2]", "cmp rax, r15", "jnb short loc_487A"], "succs": [[293, "fall"], [294, "jump"]]}, {"id": 293, "start": 18537, "end": 18554, "lines": ["mov eax, r9d", "shr al, 3", "and eax, 7", "add eax, 30h ; '0'", "mov [r12+rbx+2], al"], "succs": [[294, "fall"]]}, {"id": 294, "start": 18554, "end": 18575, "lines": ["and r9d, 7", "lea rax, [rbx+3]", "add r9d, 30h ; '0'", "cmp r13, rdx", "jb loc_47F0"], "succs": [[277, "jump"], [295, "fall"]]}, {"id": 295, "start": 18575, "end": 18587, "lines": ["mov [rbp+var_63], 0", "mov rbx, rax", "jmp loc_3995"], "succs": [[36, "jump"]]}, {"id": 296, "start": 18592, "end": 18602, "lines": ["cmp r9b, 7Ah ; 'z'", "jg loc_4A79"], "succs": [[297, "fall"], [331, "jump"]]}, {"id": 297, "start": 18602, "end": 18612, "lines": ["cmp r9b, 40h ; '@'", "jz def_4075; jumptable 0000000000004075 default case, cases 1-6,14-31"], "succs": [[147, "jump"], [298, "fall"]]}, {"id": 298, "start": 18612, "end": 18643, "lines": ["lea ecx, [r9-41h]", "mov eax, 1", "shl rax, cl", "mov rcx, 3FFFFFF53FFFFFFh", "test rax, rcx", "jnz loc_4080; jumptable 0000000000004075 cases 37,43-58"], "succs": [[144, "jump"], [299, "fall"]]}, {"id": 299, "start": 18643, "end": 18654, "lines": ["test eax, 0A4000000h", "jnz loc_41F8; jumptable 0000000000004075 cases 33,34,36,38,40-42,59-62"], "succs": [[158, "jump"], [300, "fall"]]}, {"id": 300, "start": 18654, "end": 18664, "lines": ["cmp [rbp+var_7C], 2", "jz loc_465A"], "succs": [[246, "jump"], [301, "fall"]]}, {"id": 301, "start": 18664, "end": 18674, "lines": ["cmp [rbp+var_65], 0", "jnz loc_4AA7"], "succs": [[302, "fall"], [335, "jump"]]}, {"id": 302, "start": 18674, "end": 18693, "lines": ["movzx eax, [rbp+var_62]", "mov ecx, r9d", "xor edx, edx", "mov byte ptr [rbp+var_60], al", "xor eax, eax", "jmp loc_4302"], "succs": [[179, "jump"]]}, {"id": 303, "start": 18696, "end": 18711, "lines": ["mov [rbp+var_63], 0", "mov r9d, 5Ch ; '\\'", "jmp loc_3AD9"], "succs": [[58, "jump"]]}, {"id": 304, "start": 18711, "end": 18725, "lines": ["mov byte ptr [rbp+var_60], 0", "mov ecx, r9d", "xor edx, edx", "jmp loc_3B6C"], "succs": [[70, "jump"]]}, {"id": 305, "start": 18728, "end": 18739, "lines": ["movzx edx, [rbp+var_62]", "xor eax, eax", "jmp loc_3AC4"], "succs": [[56, "jump"]]}, {"id": 306, "start": 18744, "end": 18760, "lines": ["mov byte ptr [rbp+var_60], 0", "movzx edx, [rbp+var_66]", "mov ecx, r9d", "jmp loc_4300"], "succs": [[178, "jump"]]}, {"id": 307, "start": 18768, "end": 18777, "lines": ["movzx edx, [rbp+var_61]", "xor dl, 1", "jz short loc_4963"], "succs": [[308, "fall"], [309, "jump"]]}, {"id": 308, "start": 18777, "end": 18787, "lines": ["cmp [rbp+var_64], 0", "jnz loc_39ED"], "succs": [[43, "jump"], [309, "fall"]]}, {"id": 309, "start": 18787, "end": 18792, "lines": ["mov eax, edx", "mov r11, rbx"], "succs": [[310, "fall"]]}, {"id": 310, "start": 18792, "end": 18804, "lines": ["mov rdi, [rbp+s2]", "test rdi, rdi", "jz short loc_49A6"], "succs": [[311, "fall"], [318, "jump"]]}, {"id": 311, "start": 18804, "end": 18808, "lines": ["test al, al", "jz short loc_49A6"], "succs": [[312, "fall"], [318, "jump"]]}, {"id": 312, "start": 18808, "end": 18818, "lines": ["movzx ecx, byte ptr [rdi]", "mov rax, rdi", "test cl, cl", "jz short loc_49A6"], "succs": [[313, "fall"], [318, "jump"]]}, {"id": 313, "start": 18818, "end": 18831, "lines": ["mov rsi, [rbp+var_E0]", "mov rdx, r11", "sub rax, r11"], "succs": [[314, "fall"]]}, {"id": 314, "start": 18831, "end": 18836, "lines": ["cmp rdx, r15", "jnb short loc_4997"], "succs": [[315, "fall"], [316, "jump"]]}, {"id": 315, "start": 18836, "end": 18839, "lines": ["mov [rsi+rdx], cl"], "succs": [[316, "fall"]]}, {"id": 316, "start": 18839, "end": 18851, "lines": ["add rdx, 1", "movzx ecx, byte ptr [rax+rdx]", "test cl, cl", "jnz short loc_498F"], "succs": [[314, "jump"], [317, "fall"]]}, {"id": 317, "start": 18851, "end": 18854, "lines": ["mov r11, rdx"], "succs": [[318, "fall"]]}, {"id": 318, "start": 18854, "end": 18859, "lines": ["cmp r11, r15", "jnb short loc_49B7"], "succs": [[319, "fall"], [320, "jump"]]}, {"id": 319, "start": 18859, "end": 18871, "lines": ["mov rax, [rbp+var_E0]", "mov byte ptr [rax+r11], 0"], "succs": [[320, "fall"]]}, {"id": 320, "start": 18871, "end": 18890, "lines": ["mov rax, [rbp+var_38]", "sub rax, fs:28h", "jnz loc_4F5D"], "succs": [[321, "fall"], [422, "jump"]]}, {"id": 321, "start": 18890, "end": 18911, "lines": ["add rsp, 0B8h", "mov rax, r11", "pop rbx", "pop r12", "pop r13", "pop r14", "pop r15", "pop rbp", "retn"], "succs": []}, {"id": 322, "start": 18911, "end": 18925, "lines": ["add r13, 1", "cmp [rbp+var_7E], 0", "jz loc_4B95"], "succs": [[323, "fall"], [352, "jump"]]}, {"id": 323, "start": 18925, "end": 18955, "lines": ["mov [rbp+var_A8], r15", "movzx r11d, [rbp+var_7E]", "add rbx, 6", "xor r15d, r15d", "mov r9d, 27h ; '''", "jmp loc_3991"], "succs": [[35, "jump"]]}, {"id": 324, "start": 18955, "end": 18969, "lines": ["add r13, 1", "cmp r8, 0FFFFFFFFFFFFFFFFh", "jz loc_39B3"], "succs": [[39, "jump"], [325, "fall"]]}, {"id": 325, "start": 18969, "end": 18978, "lines": ["cmp r13, r8", "jz loc_39D0"], "succs": [[40, "jump"], [326, "fall"]]}, {"id": 326, "start": 18978, "end": 18991, "lines": ["mov rax, [rbp+var_58]", "lea r10, [rax+r13]", "jmp loc_38BB"], "succs": [[13, "jump"]]}, {"id": 327, "start": 18991, "end": 19017, "lines": ["xor eax, eax", "xor ecx, ecx", "mov [rbp+var_63], 0", "add r13, 1", "xor eax, 1", "mov r9d, ecx", "and eax, r11d", "jmp loc_3AD1"], "succs": [[57, "jump"]]}, {"id": 328, "start": 19017, "end": 19035, "lines": ["movzx eax, [rbp+var_64]", "mov r14, r8", "mov rbx, r12", "mov [rbp+var_65], al", "jmp loc_3B20"], "succs": [[66, "jump"]]}, {"id": 329, "start": 19035, "end": 19050, "lines": ["mov r11d, eax", "mov ecx, 30h ; '0'", "xor edx, edx", "jmp loc_4302"], "succs": [[179, "jump"]]}, {"id": 330, "start": 19050, "end": 19065, "lines": ["mov [rbp+var_63], 0", "mov r9d, 5Ch ; '\\'", "jmp loc_399A"], "succs": [[37, "jump"]]}, {"id": 331, "start": 19065, "end": 19082, "lines": ["movzx eax, [rbp+var_62]", "mov byte ptr [rbp+var_60], al", "cmp r9b, 7Dh ; '}'", "jz loc_45BE"], "succs": [[231, "jump"], [332, "fall"]]}, {"id": 332, "start": 19082, "end": 19088, "lines": ["jg loc_4C48"], "succs": [[333, "fall"], [364, "jump"]]}, {"id": 333, "start": 19088, "end": 19098, "lines": ["cmp r9b, 7Bh ; '{'", "jz loc_45BE"], "succs": [[231, "jump"], [334, "fall"]]}, {"id": 334, "start": 19098, "end": 19111, "lines": ["xor edx, edx", "mov r9d, 7Ch ; '|'", "jmp loc_3B06"], "succs": [[64, "jump"]]}, {"id": 335, "start": 19111, "end": 19139, "lines": ["mov eax, r11d", "mov [rbp+var_63], 0", "add r13, 1", "mov r9d, 5Ch ; '\\'", "xor eax, 1", "and al, [rbp+var_64]", "jmp loc_3954"], "succs": [[25, "jump"]]}, {"id": 336, "start": 19139, "end": 19160, "lines": ["movzx eax, [rbp+var_9D]", "mov rbx, rsi", "mov r9d, 30h ; '0'", "jmp loc_3ABA"], "succs": [[55, "jump"]]}, {"id": 337, "start": 19160, "end": 19172, "lines": ["movzx edx, [rbp+var_61]", "test dl, dl", "jz loc_4938"], "succs": [[306, "jump"], [338, "fall"]]}, {"id": 338, "start": 19172, "end": 19186, "lines": ["mov ecx, r9d", "cmp [rbp+var_70], 0", "jz loc_46D2"], "succs": [[257, "jump"], [339, "fall"]]}, {"id": 339, "start": 19186, "end": 19195, "lines": ["mov byte ptr [rbp+var_60], 0", "jmp loc_3B81"], "succs": [[72, "jump"]]}, {"id": 340, "start": 19195, "end": 19205, "lines": ["cmp byte ptr [rbp+var_60], 0", "jz loc_4D84"], "succs": [[341, "fall"], [392, "jump"]]}, {"id": 341, "start": 19205, "end": 19226, "lines": ["mov [rbp+var_63], 0", "mov r13, rcx", "mov rbx, rsi", "mov r9d, 30h ; '0'", "jmp loc_3987"], "succs": [[33, "jump"]]}, {"id": 342, "start": 19226, "end": 19239, "lines": ["movzx eax, [rbp+var_65]", "xor edx, edx", "xor ecx, ecx", "jmp loc_3A96"], "succs": [[50, "jump"]]}, {"id": 343, "start": 19239, "end": 19253, "lines": ["mov rax, [rbp+var_C8]", "xor ecx, ecx", "cmp r13, r8", "jb short loc_4B49"], "succs": [[344, "fall"], [346, "jump"]]}, {"id": 344, "start": 19253, "end": 19258, "lines": ["jmp loc_4F52"], "succs": [[421, "jump"]]}, {"id": 345, "start": 19264, "end": 19273, "lines": ["add rcx, 1", "cmp rax, rcx", "jz short loc_4B50"], "succs": [[346, "fall"], [347, "jump"]]}, {"id": 346, "start": 19273, "end": 19280, "lines": ["cmp byte ptr [r10+rcx], 0", "jnz short loc_4B40"], "succs": [[345, "jump"], [347, "fall"]]}, {"id": 347, "start": 19280, "end": 19294, "lines": ["movzx eax, [rbp+var_65]", "cmp rcx, 1", "ja loc_4DF4"], "succs": [[348, "fall"], [401, "jump"]]}, {"id": 348, "start": 19294, "end": 19301, "lines": ["xor edx, edx", "jmp loc_3A96"], "succs": [[50, "jump"]]}, {"id": 349, "start": 19301, "end": 19310, "lines": ["mov byte ptr [rbp+var_60], 0", "jmp loc_45C8"], "succs": [[232, "jump"]]}, {"id": 350, "start": 19310, "end": 19320, "lines": ["cmp [rbp+var_61], 0", "jnz loc_475F"], "succs": [[270, "jump"], [351, "fall"]]}, {"id": 351, "start": 19320, "end": 19349, "lines": ["xor eax, eax", "mov ecx, 3Fh ; '?'", "mov [rbp+var_63], 0", "add r13, 1", "xor eax, 1", "mov r9d, ecx", "and eax, r11d", "jmp loc_3AD1"], "succs": [[57, "jump"]]}, {"id": 352, "start": 19349, "end": 19374, "lines": ["mov [rbp+var_A8], r15", "add rbx, 3", "xor r11d, r11d", "xor r15d, r15d", "mov [rbp+var_7E], al", "jmp loc_399E"], "succs": [[38, "jump"]]}, {"id": 353, "start": 19374, "end": 19403, "lines": ["xor eax, eax", "mov ecx, 7", "mov [rbp+var_63], 0", "add r13, 1", "xor eax, 1", "mov r9d, ecx", "and eax, r11d", "jmp loc_3AD1"], "succs": [[57, "jump"]]}, {"id": 354, "start": 19403, "end": 19425, "lines": ["xor ecx, ecx", "xor edx, edx", "add r13, 1", "and [rbp+var_63], dl", "mov eax, r11d", "mov r9d, ecx", "jmp loc_3AD1"], "succs": [[57, "jump"]]}, {"id": 355, "start": 19425, "end": 19438, "lines": ["xor edx, edx", "mov r9d, 61h ; 'a'", "jmp loc_3937"], "succs": [[22, "jump"]]}, {"id": 356, "start": 19438, "end": 19456, "lines": ["mov [rbp+var_63], 0", "mov r11d, eax", "mov r9d, 30h ; '0'", "jmp loc_3995"], "succs": [[36, "jump"]]}, {"id": 357, "start": 19456, "end": 19461, "lines": ["cmp rsi, r15", "jnb short loc_4C0A"], "succs": [[358, "fall"], [359, "jump"]]}, {"id": 358, "start": 19461, "end": 19466, "lines": ["mov byte ptr [r12+rsi], 30h ; '0'"], "succs": [[359, "fall"]]}, {"id": 359, "start": 19466, "end": 19475, "lines": ["lea rax, [rbx+2]", "cmp rax, r15", "jnb short loc_4C19"], "succs": [[360, "fall"], [361, "jump"]]}, {"id": 360, "start": 19475, "end": 19481, "lines": ["mov byte ptr [r12+rbx+2], 30h ; '0'"], "succs": [[361, "fall"]]}, {"id": 361, "start": 19481, "end": 19498, "lines": ["mov rax, [rbp+var_70]", "add rbx, 3", "test rax, rax", "jz loc_4E15"], "succs": [[362, "fall"], [403, "jump"]]}, {"id": 362, "start": 19498, "end": 19510, "lines": ["xor edx, edx", "test byte ptr [rax+6], 1", "jnz loc_4550"], "succs": [[222, "jump"], [363, "fall"]]}, {"id": 363, "start": 19510, "end": 19528, "lines": ["movzx eax, [rbp+var_9D]", "mov r9d, 30h ; '0'", "jmp loc_3ABA"], "succs": [[55, "jump"]]}, {"id": 364, "start": 19528, "end": 19538, "lines": ["cmp r9b, 7Eh ; '~'", "jz loc_3D70"], "succs": [[110, "jump"], [365, "fall"]]}, {"id": 365, "start": 19538, "end": 19549, "lines": ["mov r9d, 7Fh", "jmp loc_3A34"], "succs": [[48, "jump"]]}, {"id": 366, "start": 19549, "end": 19559, "lines": ["cmp rax, 1", "jz loc_4EFC"], "succs": [[367, "fall"], [419, "jump"]]}, {"id": 367, "start": 19559, "end": 19583, "lines": ["mov rax, [rbp+var_58]", "add r10, rcx", "mov rsi, 20000002Bh", "lea rdx, [rax+r13+1]", "jmp short loc_4C8D"], "succs": [[369, "jump"]]}, {"id": 368, "start": 19584, "end": 19597, "lines": ["add rdx, 1", "cmp rdx, r10", "jz loc_476A"], "succs": [[271, "jump"], [369, "fall"]]}, {"id": 369, "start": 19597, "end": 19607, "lines": ["movzx eax, byte ptr [rdx]", "sub eax, 5Bh ; '['", "cmp al, 21h ; '!'", "ja short loc_4C80"], "succs": [[368, "jump"], [370, "fall"]]}, {"id": 370, "start": 19607, "end": 19613, "lines": ["bt rsi, rax", "jnb short loc_4C80"], "succs": [[368, "jump"], [371, "fall"]]}, {"id": 371, "start": 19613, "end": 19618, "lines": ["jmp loc_3B0C"], "succs": [[65, "jump"]]}, {"id": 372, "start": 19618, "end": 19675, "lines": ["movzx eax, [rbp+var_61]", "mov [rbp+var_62], 0", "xor ebx, ebx", "mov [rbp+var_7D], 0", "mov [rbp+var_66], al", "mov [rbp+var_67], al", "mov [rbp+var_64], al", "lea rax, asc_8348+2; \"'`\"", "mov [rbp+var_9D], 0", "mov [rbp+n], 1", "mov [rbp+s2], rax", "jmp loc_3880"], "succs": [[8, "jump"]]}, {"id": 373, "start": 19675, "end": 19684, "lines": ["mov eax, r11d", "cmp byte ptr [rbp+var_60], 0", "jz short loc_4CF2"], "succs": [[374, "fall"], [377, "jump"]]}, {"id": 374, "start": 19684, "end": 19689, "lines": ["cmp rbx, r15", "jnb short loc_4CEE"], "succs": [[375, "fall"], [376, "jump"]]}, {"id": 375, "start": 19689, "end": 19694, "lines": ["mov byte ptr [r12+rbx], 5Ch ; '\\'"], "succs": [[376, "fall"]]}, {"id": 376, "start": 19694, "end": 19698, "lines": ["add rbx, 1"], "succs": [[377, "fall"]]}, {"id": 377, "start": 19698, "end": 19703, "lines": ["cmp r13, rdx", "jnb short loc_4D54"], "succs": [[378, "fall"], [388, "jump"]]}, {"id": 378, "start": 19703, "end": 19707, "lines": ["test al, al", "jz short loc_4D35"], "succs": [[379, "fall"], [386, "jump"]]}, {"id": 379, "start": 19707, "end": 19712, "lines": ["cmp rbx, r15", "jnb short loc_4D05"], "succs": [[380, "fall"], [381, "jump"]]}, {"id": 380, "start": 19712, "end": 19717, "lines": ["mov byte ptr [r12+rbx], 27h ; '''"], "succs": [[381, "fall"]]}, {"id": 381, "start": 19717, "end": 19726, "lines": ["lea rax, [rbx+1]", "cmp rax, r15", "jnb short loc_4D14"], "succs": [[382, "fall"], [383, "jump"]]}, {"id": 382, "start": 19726, "end": 19732, "lines": ["mov byte ptr [r12+rbx+1], 27h ; '''"], "succs": [[383, "fall"]]}, {"id": 383, "start": 19732, "end": 19741, "lines": ["lea rax, [rbx+2]", "cmp rax, r15", "jb short loc_4D60"], "succs": [[384, "fall"], [390, "jump"]]}, {"id": 384, "start": 19741, "end": 19763, "lines": ["mov rax, [rbp+var_58]", "add rbx, 3", "movzx r9d, byte ptr [rax+r13]", "add r13, 1", "cmp r13, rdx", "jnb short loc_4D7D"], "succs": [[385, "fall"], [391, "jump"]]}, {"id": 385, "start": 19763, "end": 19765, "lines": ["xor eax, eax"], "succs": [[386, "fall"]]}, {"id": 386, "start": 19765, "end": 19774, "lines": ["cmp rbx, r15", "jb loc_4DDA"], "succs": [[387, "fall"], [400, "jump"]]}, {"id": 387, "start": 19774, "end": 19796, "lines": ["mov rcx, [rbp+var_58]", "add rbx, 1", "movzx r9d, byte ptr [rcx+r13]", "add r13, 1", "cmp r13, rdx", "jb short loc_4CF7"], "succs": [[378, "jump"], [388, "fall"]]}, {"id": 388, "start": 19796, "end": 19799, "lines": ["mov r11d, eax"], "succs": [[389, "fall"]]}, {"id": 389, "start": 19799, "end": 19808, "lines": ["and [rbp+var_63], r14b", "jmp loc_3AD1"], "succs": [[57, "jump"]]}, {"id": 390, "start": 19808, "end": 19837, "lines": ["mov rax, [rbp+var_58]", "mov [r12+rbx+2], r9b", "add rbx, 3", "movzx r9d, byte ptr [rax+r13]", "add r13, 1", "xor eax, eax", "jmp loc_4CF2"], "succs": [[377, "jump"]]}, {"id": 391, "start": 19837, "end": 19844, "lines": ["xor r11d, r11d", "xor eax, eax", "jmp short loc_4D57"], "succs": [[389, "jump"]]}, {"id": 392, "start": 19844, "end": 19865, "lines": ["mov [rbp+var_63], 0", "mov r13, rcx", "mov rbx, rsi", "mov r9d, 30h ; '0'", "jmp loc_3995"], "succs": [[36, "jump"]]}, {"id": 393, "start": 19865, "end": 19877, "lines": ["add r13, 1", "mov ecx, r9d", "jmp loc_4313"], "succs": [[180, "jump"]]}, {"id": 394, "start": 19877, "end": 19895, "lines": ["mov rdx, [rbp+var_D8]", "movzx eax, byte ptr [rdx]", "test al, al", "jz loc_3836"], "succs": [[7, "jump"], [395, "fall"]]}, {"id": 395, "start": 19895, "end": 19904, "lines": ["nop word ptr [rax+rax+00000000h]"], "succs": [[396, "fall"]]}, {"id": 396, "start": 19904, "end": 19909, "lines": ["cmp rbx, r15", "jnb short loc_4DC9"], "succs": [[397, "fall"], [398, "jump"]]}, {"id": 397, "start": 19909, "end": 19913, "lines": ["mov [r12+rbx], al"], "succs": [[398, "fall"]]}, {"id": 398, "start": 19913, "end": 19925, "lines": ["add rbx, 1", "movzx eax, byte ptr [rdx+rbx]", "test al, al", "jnz short loc_4DC0"], "succs": [[396, "jump"], [399, "fall"]]}, {"id": 399, "start": 19925, "end": 19930, "lines": ["jmp loc_3836"], "succs": [[7, "jump"]]}, {"id": 400, "start": 19930, "end": 19956, "lines": ["mov rdi, [rbp+var_58]", "mov [r12+rbx], r9b", "add rbx, 1", "movzx r9d, byte ptr [rdi+r13]", "add r13, 1", "jmp loc_4CF2"], "succs": [[377, "jump"]]}, {"id": 401, "start": 19956, "end": 19964, "lines": ["xor r14d, r14d", "jmp loc_47C7"], "succs": [[274, "jump"]]}, {"id": 402, "start": 19964, "end": 19989, "lines": ["mov ecx, 7", "xor edx, edx", "add r13, 1", "and [rbp+var_63], dl", "mov eax, r11d", "mov r9d, ecx", "jmp loc_3AD1"], "succs": [[57, "jump"]]}, {"id": 403, "start": 19989, "end": 20002, "lines": ["mov rsi, rbx", "cmp byte ptr [rbp+var_60], 0", "jnz loc_4B05"], "succs": [[341, "jump"], [404, "fall"]]}, {"id": 404, "start": 20002, "end": 20020, "lines": ["mov [rbp+var_63], 0", "mov r13, rcx", "mov r9d, 30h ; '0'", "jmp loc_3995"], "succs": [[36, "jump"]]}, {"id": 405, "start": 20020, "end": 20027, "lines": ["xor eax, eax", "jmp loc_47C7"], "succs": [[274, "jump"]]}, {"id": 406, "start": 20027, "end": 20046, "lines": ["mov r9d, ecx", "xor edx, edx", "mov r13, rsi", "cmp [rbp+var_70], 0", "jnz loc_3914"], "succs": [[20, "jump"], [407, "fall"]]}, {"id": 407, "start": 20046, "end": 20058, "lines": ["xor eax, eax", "cmp byte ptr [rbp+var_60], 0", "jz loc_3AC4"], "succs": [[56, "jump"], [408, "fall"]]}, {"id": 408, "start": 20058, "end": 20067, "lines": ["lea rcx, [rsi+1]", "jmp loc_3945"], "succs": [[24, "jump"]]}, {"id": 409, "start": 20067, "end": 20075, "lines": ["mov [rbp+var_7E], al", "jmp loc_46FA"], "succs": [[261, "jump"]]}, {"id": 410, "start": 20075, "end": 20101, "lines": ["cmp byte ptr [rbp+var_60], 0", "mov [rbp+var_63], 0", "mov r13, rcx", "mov rbx, rsi", "mov r9d, 30h ; '0'", "jnz loc_3987"], "succs": [[33, "jump"], [411, "fall"]]}, {"id": 411, "start": 20101, "end": 20106, "lines": ["jmp loc_3995"], "succs": [[36, "jump"]]}, {"id": 412, "start": 20106, "end": 20118, "lines": ["movzx eax, [rbp+var_7E]", "mov r11, rbx", "jmp loc_4968"], "succs": [[310, "jump"]]}, {"id": 413, "start": 20118, "end": 20143, "lines": ["mov [rbp+var_7C], 5", "mov r15, [rbp+var_A8]", "mov r14, r8", "mov rbx, r12", "jmp loc_3780"], "succs": [[1, "jump"]]}, {"id": 414, "start": 20143, "end": 20156, "lines": ["mov r11, rbx", "mov eax, 1", "jmp loc_4968"], "succs": [[310, "jump"]]}, {"id": 415, "start": 20156, "end": 20173, "lines": ["xor edx, edx", "mov r9d, 76h ; 'v'", "lea rcx, [r13+1]", "jmp loc_3945"], "succs": [[24, "jump"]]}, {"id": 416, "start": 20173, "end": 20190, "lines": ["xor edx, edx", "mov r9d, 62h ; 'b'", "lea rcx, [r13+1]", "jmp loc_3945"], "succs": [[24, "jump"]]}, {"id": 417, "start": 20190, "end": 20203, "lines": ["xor edx, edx", "mov r9d, 61h ; 'a'", "jmp loc_3941"], "succs": [[23, "jump"]]}, {"id": 418, "start": 20203, "end": 20220, "lines": ["xor edx, edx", "mov r9d, 66h ; 'f'", "lea rcx, [r13+1]", "jmp loc_3945"], "succs": [[24, "jump"]]}, {"id": 419, "start": 20220, "end": 20288, "lines": ["mov [rbp+var_C0], r8", "mov edi, [rbp+wc]; wc", "mov byte ptr [rbp+var_B8], r11b", "mov byte ptr [rbp+var_90], r9b", "mov [rbp+var_88], rax", "call cs:iswprint_ptr", "movzx r9d, byte ptr [rbp+var_90]", "movzx r11d, byte ptr [rbp+var_B8]", "test eax, eax", "mov r8, [rbp+var_C0]", "jnz loc_41F0"], "succs": [[157, "jump"], [420, "fall"]]}, {"id": 420, "start": 20288, "end": 20306, "lines": ["movzx eax, [rbp+var_65]", "mov rcx, [rbp+var_88]", "xor edx, edx", "jmp loc_3A96"], "succs": [[50, "jump"]]}, {"id": 421, "start": 20306, "end": 20317, "lines": ["movzx eax, [rbp+var_65]", "xor edx, edx", "jmp loc_3A96"], "succs": [[50, "jump"]]}, {"id": 422, "start": 20317, "end": 20323, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}, {"id": 423, "start": 9275, "end": 9281, "lines": ["call cs:abort_ptr; jumptable 00000000000037E7 default case"], "succs": []}]}, {"name": "sub_69C0", "ea": 27072, "blocks": [{"id": 0, "start": 27072, "end": 27115, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "push r14", "push r13", "push r12", "push rbx", "sub rsp, 20h", "mov rax, fs:28h", "mov [rbp+var_28], rax", "xor eax, eax", "test rsi, rsi", "jz loc_6C28"], "succs": [[1, "fall"], [41, "jump"]]}, {"id": 1, "start": 27115, "end": 27130, "lines": ["mov r12, rdi", "mov rbx, rsi", "test rdx, rdx", "jz loc_6D80"], "succs": [[2, "fall"], [65, "jump"]]}, {"id": 2, "start": 27130, "end": 27161, "lines": ["test rcx, rcx", "lea rax, unk_C278", "cmovnz rax, rcx", "mov r13, rax", "mov eax, cs:dword_C0A0", "test eax, eax", "js loc_6C40"], "succs": [[3, "fall"], [42, "jump"]]}, {"id": 3, "start": 27161, "end": 27169, "lines": ["test eax, eax", "jz loc_6C61"], "succs": [[4, "fall"], [44, "jump"]]}, {"id": 4, "start": 27169, "end": 27189, "lines": ["mov eax, [r13+0]", "mov r9d, eax", "mov r8d, eax", "and r9d, 7", "and r8d, 7", "jnz short loc_6A80"], "succs": [[5, "fall"], [12, "jump"]]}, {"id": 5, "start": 27189, "end": 27202, "lines": ["movsx ecx, byte ptr [rbx]", "mov eax, ecx", "test cl, cl", "jns loc_6D38"], "succs": [[6, "fall"], [58, "jump"]]}, {"id": 6, "start": 27202, "end": 27211, "lines": ["cmp cl, 0C1h", "jbe loc_6BE0"], "succs": [[7, "fall"], [37, "jump"]]}, {"id": 7, "start": 27211, "end": 27220, "lines": ["cmp cl, 0DFh", "ja loc_6B30"], "succs": [[8, "fall"], [20, "jump"]]}, {"id": 8, "start": 27220, "end": 27230, "lines": ["cmp rdx, 1", "jz loc_6EF0"], "succs": [[9, "fall"], [97, "jump"]]}, {"id": 9, "start": 27230, "end": 27246, "lines": ["movzx edx, byte ptr [rbx+1]", "add edx, 0FFFFFF80h", "cmp dl, 3Fh ; '?'", "ja loc_6BE0"], "succs": [[10, "fall"], [37, "jump"]]}, {"id": 10, "start": 27246, "end": 27255, "lines": ["test r12, r12", "jnz loc_6E00"], "succs": [[11, "fall"], [75, "jump"]]}, {"id": 11, "start": 27255, "end": 27260, "lines": ["jmp loc_6E11"], "succs": [[76, "jump"]]}, {"id": 12, "start": 27264, "end": 27283, "lines": ["mov r11d, eax", "sar r11d, 8", "movsxd rsi, r11d", "cmp r9, rsi", "jnb loc_6D20"], "succs": [[13, "fall"], [57, "jump"]]}, {"id": 13, "start": 27283, "end": 27293, "lines": ["cmp rsi, 4", "ja loc_6D20"], "succs": [[14, "fall"], [57, "jump"]]}, {"id": 14, "start": 27293, "end": 27339, "lines": ["mov r10d, [r13+4]", "lea ecx, [rsi+rsi*2-3]", "mov edi, 100h", "add ecx, ecx", "mov r14d, r10d", "shr r14d, cl", "mov ecx, r11d", "sar edi, cl", "neg edi", "or edi, r14d", "mov [rbp+var_2C], dil", "movzx edi, byte ptr [rbx]", "test al, 6", "jz loc_6D90"], "succs": [[15, "fall"], [66, "jump"]]}, {"id": 15, "start": 27339, "end": 27369, "lines": ["lea ecx, [rsi+rsi*2-6]", "mov eax, r10d", "add ecx, ecx", "shr eax, cl", "and eax, 3Fh", "or eax, 0FFFFFF80h", "mov [rbp+var_2B], al", "cmp r8d, 2", "jle loc_6D90"], "succs": [[16, "fall"], [66, "jump"]]}, {"id": 16, "start": 27369, "end": 27410, "lines": ["lea ecx, [rsi+rsi*2-9]", "add ecx, ecx", "shr r10d, cl", "and r10d, 3Fh", "or r10d, 0FFFFFF80h", "mov [rbp+var_2A], r10b", "mov [rbp+r9+var_2C], dil", "movzx eax, [rbp+var_2C]", "movsx ecx, al", "test al, al", "jns loc_6D38"], "succs": [[17, "fall"], [58, "jump"]]}, {"id": 17, "start": 27410, "end": 27418, "lines": ["cmp al, 0C1h", "jbe loc_6BE0"], "succs": [[18, "fall"], [37, "jump"]]}, {"id": 18, "start": 27418, "end": 27426, "lines": ["cmp al, 0DFh", "jbe loc_6EB4"], "succs": [[19, "fall"], [91, "jump"]]}, {"id": 19, "start": 27426, "end": 27440, "lines": ["lea rdx, [r9+1]", "lea rbx, [rbp+var_2C]", "nop word ptr [rax+rax+00h]"], "succs": [[20, "fall"]]}, {"id": 20, "start": 27440, "end": 27448, "lines": ["cmp al, 0EFh", "jbe loc_6E20"], "succs": [[21, "fall"], [77, "jump"]]}, {"id": 21, "start": 27448, "end": 27456, "lines": ["cmp al, 0F4h", "ja loc_6BE0"], "succs": [[22, "fall"], [37, "jump"]]}, {"id": 22, "start": 27456, "end": 27466, "lines": ["cmp rdx, 1", "jz loc_6F38"], "succs": [[23, "fall"], [102, "jump"]]}, {"id": 23, "start": 27466, "end": 27483, "lines": ["movzx ecx, byte ptr [rbx+1]", "lea esi, [rcx-80h]", "cmp sil, 3Fh ; '?'", "ja loc_6BE0"], "succs": [[24, "fall"], [37, "jump"]]}, {"id": 24, "start": 27483, "end": 27487, "lines": ["cmp al, 0F0h", "jnz short loc_6B64"], "succs": [[25, "fall"], [26, "jump"]]}, {"id": 25, "start": 27487, "end": 27492, "lines": ["cmp cl, 8Fh", "jbe short loc_6BE0"], "succs": [[26, "fall"], [37, "jump"]]}, {"id": 26, "start": 27492, "end": 27496, "lines": ["cmp al, 0F4h", "jnz short loc_6B6D"], "succs": [[27, "fall"], [28, "jump"]]}, {"id": 27, "start": 27496, "end": 27501, "lines": ["cmp cl, 8Fh", "ja short loc_6BE0"], "succs": [[28, "fall"], [37, "jump"]]}, {"id": 28, "start": 27501, "end": 27511, "lines": ["cmp rdx, 2", "jz loc_6FA4"], "succs": [[29, "fall"], [105, "jump"]]}, {"id": 29, "start": 27511, "end": 27523, "lines": ["movzx edi, byte ptr [rbx+2]", "lea ecx, [rdi-80h]", "cmp cl, 3Fh ; '?'", "ja short loc_6BE0"], "succs": [[30, "fall"], [37, "jump"]]}, {"id": 30, "start": 27523, "end": 27533, "lines": ["cmp rdx, 3", "jz loc_6F71"], "succs": [[31, "fall"], [104, "jump"]]}, {"id": 31, "start": 27533, "end": 27545, "lines": ["movzx edx, byte ptr [rbx+3]", "add edx, 0FFFFFF80h", "cmp dl, 3Fh ; '?'", "ja short loc_6BE0"], "succs": [[32, "fall"], [37, "jump"]]}, {"id": 32, "start": 27545, "end": 27550, "lines": ["test r12, r12", "jz short loc_6BC0"], "succs": [[33, "fall"], [34, "jump"]]}, {"id": 33, "start": 27550, "end": 27584, "lines": ["shl eax, 12h", "movsx edx, dl", "movsx esi, sil", "movsx ecx, cl", "shl esi, 0Ch", "and eax, 1C0000h", "shl ecx, 6", "or eax, edx", "or eax, esi", "or eax, ecx", "mov [r12], eax"], "succs": [[34, "fall"]]}, {"id": 34, "start": 27584, "end": 27599, "lines": ["mov eax, 4", "mov edx, 4", "jmp loc_6D4D"], "succs": [[61, "jump"]]}, {"id": 35, "start": 27600, "end": 27615, "lines": ["mov rdx, rsi", "lea rbx, [rbp+var_2C]", "cmp al, 0F4h", "jbe loc_6B4A"], "succs": [[23, "jump"], [36, "fall"]]}, {"id": 36, "start": 27615, "end": 27616, "lines": ["nop"], "succs": [[37, "fall"]]}, {"id": 37, "start": 27616, "end": 27628, "lines": ["call cs:__errno_location_ptr", "mov dword ptr [rax], 54h ; 'T'"], "succs": [[38, "fall"]]}, {"id": 38, "start": 27628, "end": 27648, "lines": ["mov rdx, 0FFFFFFFFFFFFFFFFh", "xchg ax, ax", "nop word ptr [rax+rax+00000000h]"], "succs": [[39, "fall"]]}, {"id": 39, "start": 27648, "end": 27667, "lines": ["mov rax, [rbp+var_28]", "sub rax, fs:28h", "jnz loc_6FD2"], "succs": [[40, "fall"], [107, "jump"]]}, {"id": 40, "start": 27667, "end": 27683, "lines": ["add rsp, 20h", "mov rax, rdx", "pop rbx", "pop r12", "pop r13", "pop r14", "pop rbp", "retn"], "succs": []}, {"id": 41, "start": 27688, "end": 27708, "lines": ["xor r12d, r12d", "mov edx, 1", "lea rbx, accept+3; \"\"", "jmp loc_69FA"], "succs": [[2, "jump"]]}, {"id": 42, "start": 27712, "end": 27735, "lines": ["mov [rbp+n], rdx", "call sub_70A0", "mov rdx, [rbp+n]; n", "cmp byte ptr [rax], 55h ; 'U'", "jz loc_6CE0"], "succs": [[43, "fall"], [52, "jump"]]}, {"id": 43, "start": 27735, "end": 27745, "lines": ["mov cs:dword_C0A0, 0"], "succs": [[44, "fall"]]}, {"id": 44, "start": 27745, "end": 27766, "lines": ["mov rcx, r13; p", "mov rsi, rbx; s", "mov rdi, r12; pc32", "call cs:mbrtoc32_ptr", "cmp rax, 0FFFFFFFFFFFFFFFCh", "jbe short loc_6CB0"], "succs": [[45, "fall"], [50, "jump"]]}, {"id": 45, "start": 27766, "end": 27776, "lines": ["cmp rax, 0FFFFFFFFFFFFFFFDh", "jz loc_2487"], "succs": [[46, "fall"], [108, "jump"]]}, {"id": 46, "start": 27776, "end": 27800, "lines": ["xor edi, edi", "mov [rbp+n], rax", "call sub_7020", "mov rdx, [rbp+n]", "test al, al", "jnz loc_6C00"], "succs": [[39, "jump"], [47, "fall"]]}, {"id": 47, "start": 27800, "end": 27805, "lines": ["test r12, r12", "jz short loc_6CA4"], "succs": [[48, "fall"], [49, "jump"]]}, {"id": 48, "start": 27805, "end": 27812, "lines": ["movzx eax, byte ptr [rbx]", "mov [r12], eax"], "succs": [[49, "fall"]]}, {"id": 49, "start": 27812, "end": 27822, "lines": ["mov edx, 1", "jmp loc_6C00"], "succs": [[39, "jump"]]}, {"id": 50, "start": 27824, "end": 27849, "lines": ["mov [rbp+n], rax", "mov rdi, r13; ps", "call cs:mbsinit_ptr", "mov rdx, [rbp+n]", "test eax, eax", "jnz loc_6C00"], "succs": [[39, "jump"], [51, "fall"]]}, {"id": 51, "start": 27849, "end": 27862, "lines": ["mov qword ptr [r13+0], 0", "jmp loc_6C00"], "succs": [[39, "jump"]]}, {"id": 52, "start": 27872, "end": 27882, "lines": ["cmp byte ptr [rax+1], 54h ; 'T'", "jnz loc_6C57"], "succs": [[43, "jump"], [53, "fall"]]}, {"id": 53, "start": 27882, "end": 27892, "lines": ["cmp byte ptr [rax+2], 46h ; 'F'", "jnz loc_6C57"], "succs": [[43, "jump"], [54, "fall"]]}, {"id": 54, "start": 27892, "end": 27902, "lines": ["cmp byte ptr [rax+3], 2Dh ; '-'", "jnz loc_6C57"], "succs": [[43, "jump"], [55, "fall"]]}, {"id": 55, "start": 27902, "end": 27912, "lines": ["cmp byte ptr [rax+4], 38h ; '8'", "jnz loc_6C57"], "succs": [[43, "jump"], [56, "fall"]]}, {"id": 56, "start": 27912, "end": 27933, "lines": ["cmp byte ptr [rax+5], 0", "setz al", "movzx eax, al", "mov cs:dword_C0A0, eax", "jmp loc_6A19"], "succs": [[3, "jump"]]}, {"id": 57, "start": 27936, "end": 27953, "lines": ["call cs:__errno_location_ptr", "mov dword ptr [rax], 16h", "jmp loc_6BEC"], "succs": [[38, "jump"]]}, {"id": 58, "start": 27960, "end": 27965, "lines": ["test r12, r12", "jz short loc_6D41"], "succs": [[59, "fall"], [60, "jump"]]}, {"id": 59, "start": 27965, "end": 27969, "lines": ["mov [r12], ecx"], "succs": [[60, "fall"]]}, {"id": 60, "start": 27969, "end": 27981, "lines": ["cmp al, 1", "mov eax, 1", "sbb edx, edx", "add edx, 1"], "succs": [[61, "fall"]]}, {"id": 61, "start": 27981, "end": 27990, "lines": ["cmp r9, rax", "jnb loc_2487"], "succs": [[62, "fall"], [108, "jump"]]}, {"id": 62, "start": 27990, "end": 28009, "lines": ["sub edx, r8d", "mov dword ptr [r13+0], 0", "movsxd rdx, edx", "jmp loc_6C00"], "succs": [[39, "jump"]]}, {"id": 63, "start": 28009, "end": 28025, "lines": ["mov dword ptr [r13+0], 301h", "shl eax, 0Ch", "and eax, 0F000h"], "succs": [[64, "fall"]]}, {"id": 64, "start": 28025, "end": 28032, "lines": ["mov [r13+4], eax", "nop dword ptr [rax]"], "succs": [[65, "fall"]]}, {"id": 65, "start": 28032, "end": 28044, "lines": ["mov rdx, 0FFFFFFFFFFFFFFFEh", "jmp loc_6C00"], "succs": [[39, "jump"]]}, {"id": 66, "start": 28048, "end": 28070, "lines": ["mov [rbp+r9+var_2C], dil", "movzx eax, [rbp+var_2C]", "movsx ecx, al", "cmp rdx, 1", "jz loc_6EC0"], "succs": [[67, "fall"], [92, "jump"]]}, {"id": 67, "start": 28070, "end": 28093, "lines": ["movzx edi, byte ptr [rbx+1]", "lea rsi, [r9+2]", "mov [rbp+r9+var_2B], dil", "cmp rdx, 2", "jz loc_6EA0"], "succs": [[68, "fall"], [88, "jump"]]}, {"id": 68, "start": 28093, "end": 28103, "lines": ["cmp rsi, 4", "jz loc_6EA0"], "succs": [[69, "fall"], [88, "jump"]]}, {"id": 69, "start": 28103, "end": 28118, "lines": ["movzx edx, byte ptr [rbx+2]", "mov [rbp+var_29], dl", "test al, al", "jns loc_6D38"], "succs": [[58, "jump"], [70, "fall"]]}, {"id": 70, "start": 28118, "end": 28126, "lines": ["cmp al, 0C1h", "jbe loc_6BE0"], "succs": [[37, "jump"], [71, "fall"]]}, {"id": 71, "start": 28126, "end": 28134, "lines": ["cmp al, 0DFh", "ja loc_6F10"], "succs": [[72, "fall"], [98, "jump"]]}, {"id": 72, "start": 28134, "end": 28150, "lines": ["movzx ebx, [rbp+var_2B]", "lea edx, [rbx-80h]", "cmp dl, 3Fh ; '?'", "ja loc_6BE0"], "succs": [[37, "jump"], [73, "fall"]]}, {"id": 73, "start": 28150, "end": 28159, "lines": ["test r12, r12", "jz loc_6FC8"], "succs": [[74, "fall"], [106, "jump"]]}, {"id": 74, "start": 28159, "end": 28160, "lines": ["nop"], "succs": [[75, "fall"]]}, {"id": 75, "start": 28160, "end": 28177, "lines": ["shl eax, 6", "movsx edx, dl", "and eax, 7C0h", "or eax, edx", "mov [r12], eax"], "succs": [[76, "fall"]]}, {"id": 76, "start": 28177, "end": 28192, "lines": ["mov eax, 2", "mov edx, 2", "jmp loc_6D4D"], "succs": [[61, "jump"]]}, {"id": 77, "start": 28192, "end": 28202, "lines": ["cmp rdx, 1", "jz loc_6D69"], "succs": [[63, "jump"], [78, "fall"]]}, {"id": 78, "start": 28202, "end": 28219, "lines": ["movzx ecx, byte ptr [rbx+1]", "lea esi, [rcx-80h]", "cmp sil, 3Fh ; '?'", "ja loc_6BE0"], "succs": [[37, "jump"], [79, "fall"]]}, {"id": 79, "start": 28219, "end": 28223, "lines": ["cmp al, 0E0h", "jnz short loc_6E48"], "succs": [[80, "fall"], [81, "jump"]]}, {"id": 80, "start": 28223, "end": 28232, "lines": ["cmp cl, 9Fh", "jbe loc_6BE0"], "succs": [[37, "jump"], [81, "fall"]]}, {"id": 81, "start": 28232, "end": 28236, "lines": ["cmp al, 0EDh", "jnz short loc_6E55"], "succs": [[82, "fall"], [83, "jump"]]}, {"id": 82, "start": 28236, "end": 28245, "lines": ["cmp cl, 9Fh", "ja loc_6BE0"], "succs": [[37, "jump"], [83, "fall"]]}, {"id": 83, "start": 28245, "end": 28255, "lines": ["cmp rdx, 2", "jz loc_6F4D"], "succs": [[84, "fall"], [103, "jump"]]}, {"id": 84, "start": 28255, "end": 28271, "lines": ["movzx edx, byte ptr [rbx+2]", "add edx, 0FFFFFF80h", "cmp dl, 3Fh ; '?'", "ja loc_6BE0"], "succs": [[37, "jump"], [85, "fall"]]}, {"id": 85, "start": 28271, "end": 28276, "lines": ["test r12, r12", "jz short loc_6E8E"], "succs": [[86, "fall"], [87, "jump"]]}, {"id": 86, "start": 28276, "end": 28302, "lines": ["shl eax, 0Ch", "movsx edx, dl", "movsx esi, sil", "and eax, 0F000h", "shl esi, 6", "or eax, edx", "or eax, esi", "mov [r12], eax"], "succs": [[87, "fall"]]}, {"id": 87, "start": 28302, "end": 28317, "lines": ["mov eax, 3", "mov edx, 3", "jmp loc_6D4D"], "succs": [[61, "jump"]]}, {"id": 88, "start": 28320, "end": 28328, "lines": ["test al, al", "jns loc_6D38"], "succs": [[58, "jump"], [89, "fall"]]}, {"id": 89, "start": 28328, "end": 28336, "lines": ["cmp al, 0C1h", "jbe loc_6BE0"], "succs": [[37, "jump"], [90, "fall"]]}, {"id": 90, "start": 28336, "end": 28340, "lines": ["cmp al, 0DFh", "ja short loc_6ED8"], "succs": [[91, "fall"], [95, "jump"]]}, {"id": 91, "start": 28340, "end": 28349, "lines": ["lea rbx, [rbp+var_2C]", "jmp loc_6A5E"], "succs": [[9, "jump"]]}, {"id": 92, "start": 28352, "end": 28360, "lines": ["test al, al", "jns loc_6D38"], "succs": [[58, "jump"], [93, "fall"]]}, {"id": 93, "start": 28360, "end": 28368, "lines": ["cmp al, 0C1h", "jbe loc_6BE0"], "succs": [[37, "jump"], [94, "fall"]]}, {"id": 94, "start": 28368, "end": 28376, "lines": ["lea rsi, [r9+1]", "cmp al, 0DFh", "jbe short loc_6EB4"], "succs": [[91, "jump"], [95, "fall"]]}, {"id": 95, "start": 28376, "end": 28384, "lines": ["cmp al, 0EFh", "ja loc_6BD0"], "succs": [[35, "jump"], [96, "fall"]]}, {"id": 96, "start": 28384, "end": 28396, "lines": ["mov rdx, rsi", "lea rbx, [rbp+var_2C]", "jmp loc_6E2A"], "succs": [[78, "jump"]]}, {"id": 97, "start": 28400, "end": 28426, "lines": ["shl ecx, 6", "mov dword ptr [r13+0], 201h", "and ecx, 7C0h", "mov [r13+4], ecx", "jmp loc_6D80"], "succs": [[65, "jump"]]}, {"id": 98, "start": 28432, "end": 28436, "lines": ["cmp al, 0EFh", "ja short loc_6F22"], "succs": [[99, "fall"], [100, "jump"]]}, {"id": 99, "start": 28436, "end": 28450, "lines": ["lea rbx, [rbp+var_2C]", "mov edx, 4", "jmp loc_6E2A"], "succs": [[78, "jump"]]}, {"id": 100, "start": 28450, "end": 28467, "lines": ["lea rbx, [rbp+var_2C]", "mov edx, 4", "cmp al, 0F4h", "jbe loc_6B4A"], "succs": [[23, "jump"], [101, "fall"]]}, {"id": 101, "start": 28467, "end": 28472, "lines": ["jmp loc_6BE0"], "succs": [[37, "jump"]]}, {"id": 102, "start": 28472, "end": 28493, "lines": ["shl eax, 12h", "mov dword ptr [r13+0], 401h", "and eax, 1C0000h", "jmp loc_6D79"], "succs": [[64, "jump"]]}, {"id": 103, "start": 28493, "end": 28529, "lines": ["mov dword ptr [r13+0], 302h", "movzx edx, byte ptr [rbx+1]", "shl eax, 0Ch", "and eax, 0F000h", "shl edx, 6", "and edx, 0FC0h", "or eax, edx", "jmp loc_6D79"], "succs": [[64, "jump"]]}, {"id": 104, "start": 28529, "end": 28580, "lines": ["mov dword ptr [r13+0], 403h", "movzx edx, byte ptr [rbx+1]", "shl eax, 12h", "movzx ecx, byte ptr [rbx+2]", "and eax, 1C0000h", "shl edx, 0Ch", "shl ecx, 6", "and edx, 3F000h", "and ecx, 0FC0h", "or edx, ecx", "or eax, edx", "jmp loc_6D79"], "succs": [[64, "jump"]]}, {"id": 105, "start": 28580, "end": 28616, "lines": ["mov dword ptr [r13+0], 402h", "movzx edx, byte ptr [rbx+1]", "shl eax, 12h", "and eax, 1C0000h", "shl edx, 0Ch", "and edx, 3F000h", "or eax, edx", "jmp loc_6D79"], "succs": [[64, "jump"]]}, {"id": 106, "start": 28616, "end": 28626, "lines": ["mov edx, 2", "jmp loc_6D56"], "succs": [[62, "jump"]]}, {"id": 107, "start": 28626, "end": 28632, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}, {"id": 108, "start": 9351, "end": 9357, "lines": ["call cs:abort_ptr"], "succs": []}]}, {"name": "main", "ea": 9360, "blocks": [{"id": 0, "start": 9360, "end": 9414, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "push r15", "push r14", "mov r14, rsi", "push r13", "push r12", "push rbx", "mov ebx, edi", "lea rdi, name; \"POSIXLY_CORRECT\"", "sub rsp, 18h", "call cs:getenv_ptr", "mov r13, [r14]", "mov r12, rax", "test rax, rax", "jz loc_2926"], "succs": [[1, "fall"], [69, "jump"]]}, {"id": 1, "start": 9414, "end": 9423, "lines": ["cmp ebx, 1", "jle loc_262F"], "succs": [[2, "fall"], [23, "jump"]]}, {"id": 2, "start": 9423, "end": 9525, "lines": ["mov rdi, [r14+8]; s1", "lea rsi, s2; \"-n\"", "call cs:strcmp_ptr", "mov rdi, r13", "mov r15d, eax", "call sub_3500", "lea rsi, accept+3; locale", "mov edi, 6; category", "call cs:setlocale_ptr", "lea rsi, dirname; \"/usr/share/locale\"", "lea rdi, aGnuCoreutils+4; domainname", "call cs:bindtextdomain_ptr", "lea rdi, aGnuCoreutils+4; domainname", "call cs:textdomain_ptr", "lea rdi, sub_3430", "call sub_71C0", "cmp ebx, 2", "jnz loc_26B8"], "succs": [[3, "fall"], [28, "jump"]]}, {"id": 3, "start": 9525, "end": 9534, "lines": ["test r15d, r15d", "jnz loc_26B8"], "succs": [[4, "fall"], [28, "jump"]]}, {"id": 4, "start": 9534, "end": 9562, "lines": ["mov rbx, [r14+8]", "lea rsi, aHelp; \"--help\"", "mov rdi, rbx; s1", "call cs:strcmp_ptr", "test eax, eax", "jz loc_2ACF"], "succs": [[5, "fall"], [86, "jump"]]}, {"id": 5, "start": 9562, "end": 9586, "lines": ["lea rsi, aVersion; \"--version\"", "mov rdi, rbx; s1", "call cs:strcmp_ptr", "test eax, eax", "jz loc_2A2C"], "succs": [[6, "fall"], [81, "jump"]]}, {"id": 6, "start": 9586, "end": 9596, "lines": ["add r14, 8", "mov r9d, 1"], "succs": [[7, "fall"]]}, {"id": 7, "start": 9596, "end": 9621, "lines": ["test r12, r12", "mov ebx, 1", "mov r8, 20100000001h", "setnz r11b", "xor r10d, r10d"], "succs": [[8, "fall"]]}, {"id": 8, "start": 9621, "end": 9633, "lines": ["mov rdi, [r14]", "cmp byte ptr [rdi], 2Dh ; '-'", "jnz loc_28C2"], "succs": [[9, "fall"], [62, "jump"]]}, {"id": 9, "start": 9633, "end": 9645, "lines": ["movzx ecx, byte ptr [rdi+1]", "test cl, cl", "jz loc_28C2"], "succs": [[10, "fall"], [62, "jump"]]}, {"id": 10, "start": 9645, "end": 9664, "lines": ["lea rdx, [rdi+2]", "mov eax, ecx", "xchg ax, ax", "nop word ptr [rax+rax+00000000h]"], "succs": [[11, "fall"]]}, {"id": 11, "start": 9664, "end": 9675, "lines": ["sub eax, 45h ; 'E'", "cmp al, 29h ; ')'", "ja loc_28C2"], "succs": [[12, "fall"], [62, "jump"]]}, {"id": 12, "start": 9675, "end": 9692, "lines": ["bt r8, rax", "setb sil", "test sil, sil", "jz loc_28C2"], "succs": [[13, "fall"], [62, "jump"]]}, {"id": 13, "start": 9692, "end": 9703, "lines": ["movzx eax, byte ptr [rdx]", "add rdx, 1", "test al, al", "jnz short loc_25C0"], "succs": [[11, "jump"], [14, "fall"]]}, {"id": 14, "start": 9703, "end": 9709, "lines": ["lea rax, [rdi+1]", "jmp short loc_2600"], "succs": [[17, "jump"]]}, {"id": 15, "start": 9712, "end": 9721, "lines": ["cmp cl, 45h ; 'E'", "setnz dl", "and r10d, edx"], "succs": [[16, "fall"]]}, {"id": 16, "start": 9721, "end": 9728, "lines": ["movzx ecx, byte ptr [rax]", "test cl, cl", "jz short loc_261B"], "succs": [[17, "fall"], [20, "jump"]]}, {"id": 17, "start": 9728, "end": 9741, "lines": ["add rax, 1", "cmp cl, 65h ; 'e'", "jz loc_26B0"], "succs": [[18, "fall"], [27, "jump"]]}, {"id": 18, "start": 9741, "end": 9746, "lines": ["cmp cl, 6Eh ; 'n'", "jnz short loc_25F0"], "succs": [[15, "jump"], [19, "fall"]]}, {"id": 19, "start": 9746, "end": 9755, "lines": ["movzx ecx, byte ptr [rax]", "xor ebx, ebx", "test cl, cl", "jnz short loc_2600"], "succs": [[17, "jump"], [20, "fall"]]}, {"id": 20, "start": 9755, "end": 9769, "lines": ["add r14, 8", "sub r9d, 1", "jnz loc_2595"], "succs": [[8, "jump"], [21, "fall"]]}, {"id": 21, "start": 9769, "end": 9773, "lines": ["test bl, bl", "jz short loc_2698; jumptable 000000000000275A case 99"], "succs": [[22, "fall"], [26, "jump"]]}, {"id": 22, "start": 9773, "end": 9775, "lines": ["jmp short loc_2678"], "succs": [[24, "jump"]]}, {"id": 23, "start": 9775, "end": 9848, "lines": ["mov rdi, r13", "call sub_3500", "lea rsi, accept+3; locale", "mov edi, 6; category", "call cs:setlocale_ptr", "lea rsi, dirname; \"/usr/share/locale\"", "lea rdi, aGnuCoreutils+4; domainname", "call cs:bindtextdomain_ptr", "lea rdi, aGnuCoreutils+4; domainname", "call cs:textdomain_ptr", "lea rdi, sub_3430", "call sub_71C0"], "succs": [[24, "fall"]]}, {"id": 24, "start": 9848, "end": 9869, "lines": ["mov rdi, cs:stdout; _IO_FILE *", "mov rax, [rdi+28h]", "cmp rax, [rdi+30h]", "jnb loc_2AB3"], "succs": [[25, "fall"], [84, "jump"]]}, {"id": 25, "start": 9869, "end": 9880, "lines": ["lea rdx, [rax+1]", "mov [rdi+28h], rdx", "mov byte ptr [rax], 0Ah"], "succs": [[26, "fall"]]}, {"id": 26, "start": 9880, "end": 9897, "lines": ["lea rsp, [rbp-28h]; jumptable 000000000000275A case 99", "xor eax, eax", "pop rbx", "pop r12", "pop r13", "pop r14", "pop r15", "pop rbp", "retn"], "succs": []}, {"id": 27, "start": 9904, "end": 9912, "lines": ["mov r10d, esi", "jmp loc_25F9"], "succs": [[16, "jump"]]}, {"id": 28, "start": 9912, "end": 9929, "lines": ["lea r9d, [rbx-1]", "add r14, 8", "test r15d, r15d", "jz loc_257C"], "succs": [[7, "jump"], [29, "fall"]]}, {"id": 29, "start": 9929, "end": 9934, "lines": ["mov ebx, 1"], "succs": [[30, "fall"]]}, {"id": 30, "start": 9934, "end": 9949, "lines": ["lea eax, [r9-1]", "lea r15, jpt_275A", "lea r12, [r14+rax*8]"], "succs": [[31, "fall"]]}, {"id": 31, "start": 9949, "end": 9965, "lines": ["mov r13, [r14]", "movzx eax, byte ptr [r13+0]", "lea rdx, [r13+1]", "test al, al", "jnz short loc_272E"], "succs": [[32, "fall"], [36, "jump"]]}, {"id": 32, "start": 9965, "end": 9970, "lines": ["jmp loc_2890"], "succs": [[59, "jump"]]}, {"id": 33, "start": 9976, "end": 9982, "lines": ["movzx ecx, al", "mov r13, rdx"], "succs": [[34, "fall"]]}, {"id": 34, "start": 9982, "end": 10003, "lines": ["mov rdi, cs:stdout; _IO_FILE *", "mov rdx, [rdi+28h]", "cmp rdx, [rdi+30h]", "jnb loc_2870"], "succs": [[35, "fall"], [57, "jump"]]}, {"id": 35, "start": 10003, "end": 10030, "lines": ["lea rcx, [rdx+1]", "mov [rdi+28h], rcx", "mov [rdx], al", "movzx eax, byte ptr [r13+0]", "lea rdx, [r13+1]", "test al, al", "jz loc_2890"], "succs": [[36, "fall"], [59, "jump"]]}, {"id": 36, "start": 10030, "end": 10034, "lines": ["cmp al, 5Ch ; '\\'", "jnz short loc_26F8"], "succs": [[33, "jump"], [37, "fall"]]}, {"id": 37, "start": 10034, "end": 10048, "lines": ["movzx r8d, byte ptr [r13+1]", "test r8b, r8b", "jz loc_2860"], "succs": [[38, "fall"], [56, "jump"]]}, {"id": 38, "start": 10048, "end": 10064, "lines": ["lea edx, [r8-30h]; switch 73 cases", "lea r9, [r13+2]", "mov ecx, r8d", "cmp dl, 48h", "ja short def_275A; jumptable 000000000000275A default case, cases 56-91,93-96,100,103-109,111-113,115,117,119"], "succs": [[39, "fall"], [42, "jump"]]}, {"id": 39, "start": 10064, "end": 10077, "lines": ["movzx edx, dl", "movsxd rdx, ds:(jpt_275A - 809Ch)[r15+rdx*4]", "add rdx, r15", "jmp rdx; switch jump"], "succs": [[26, "switch"], [40, "switch"], [42, "switch"], [45, "switch"], [47, "switch"], [49, "switch"], [50, "switch"], [51, "switch"], [52, "switch"], [53, "switch"], [54, "switch"], [55, "switch"], [78, "switch"], [79, "switch"]]}, {"id": 40, "start": 10077, "end": 10090, "lines": ["movzx eax, byte ptr [r13+2]; jumptable 000000000000275A case 120", "lea edx, [rax-30h]", "cmp dl, 36h ; '6'", "ja short def_275A; jumptable 000000000000275A default case, cases 56-91,93-96,100,103-109,111-113,115,117,119"], "succs": [[41, "fall"], [42, "jump"]]}, {"id": 41, "start": 10090, "end": 10110, "lines": ["mov rsi, 7E0000007E03FFh", "bt rsi, rdx", "jb loc_29B2"], "succs": [[42, "fall"], [74, "jump"]]}, {"id": 42, "start": 10110, "end": 10135, "lines": ["mov rdi, cs:stdout; jumptable 000000000000275A default case, cases 56-91,93-96,100,103-109,111-113,115,117,119", "movzx ecx, r8b", "mov rax, [rdi+28h]", "cmp rax, [rdi+30h]", "jnb loc_2A8C"], "succs": [[43, "fall"], [83, "jump"]]}, {"id": 43, "start": 10135, "end": 10146, "lines": ["lea rdx, [rax+1]", "mov [rdi+28h], rdx", "mov byte ptr [rax], 5Ch ; '\\'"], "succs": [[44, "fall"]]}, {"id": 44, "start": 10146, "end": 10157, "lines": ["mov eax, r8d", "mov r13, r9", "jmp loc_26FE"], "succs": [[34, "jump"]]}, {"id": 45, "start": 10157, "end": 10173, "lines": ["movzx ecx, byte ptr [r13+2]; jumptable 000000000000275A case 48", "lea eax, [rcx-30h]", "cmp al, 7", "ja loc_29F1"], "succs": [[46, "fall"], [77, "jump"]]}, {"id": 46, "start": 10173, "end": 10177, "lines": ["lea r9, [r13+3]"], "succs": [[47, "fall"]]}, {"id": 47, "start": 10177, "end": 10196, "lines": ["movzx edx, byte ptr [r9]; jumptable 000000000000275A cases 49-55", "lea eax, [rcx-30h]", "lea ecx, [rdx-30h]", "cmp cl, 7", "jbe loc_298E"], "succs": [[48, "fall"], [72, "jump"]]}, {"id": 48, "start": 10196, "end": 10207, "lines": ["movzx ecx, al", "mov r13, r9", "jmp loc_26FE"], "succs": [[34, "jump"]]}, {"id": 49, "start": 10207, "end": 10225, "lines": ["mov r13, r9; jumptable 000000000000275A case 101", "mov ecx, 1Bh", "mov eax, 1Bh", "jmp loc_26FE"], "succs": [[34, "jump"]]}, {"id": 50, "start": 10225, "end": 10243, "lines": ["mov r13, r9; jumptable 000000000000275A case 98", "mov ecx, 8", "mov eax, 8", "jmp loc_26FE"], "succs": [[34, "jump"]]}, {"id": 51, "start": 10243, "end": 10261, "lines": ["mov r13, r9; jumptable 000000000000275A case 116", "mov ecx, 9", "mov eax, 9", "jmp loc_26FE"], "succs": [[34, "jump"]]}, {"id": 52, "start": 10261, "end": 10279, "lines": ["mov r13, r9; jumptable 000000000000275A case 114", "mov ecx, 0Dh", "mov eax, 0Dh", "jmp loc_26FE"], "succs": [[34, "jump"]]}, {"id": 53, "start": 10279, "end": 10297, "lines": ["mov r13, r9; jumptable 000000000000275A case 110", "mov ecx, 0Ah", "mov eax, 0Ah", "jmp loc_26FE"], "succs": [[34, "jump"]]}, {"id": 54, "start": 10297, "end": 10315, "lines": ["mov r13, r9; jumptable 000000000000275A case 102", "mov ecx, 0Ch", "mov eax, 0Ch", "jmp loc_26FE"], "succs": [[34, "jump"]]}, {"id": 55, "start": 10315, "end": 10333, "lines": ["mov r13, r9; jumptable 000000000000275A case 118", "mov ecx, 0Bh", "mov eax, 0Bh", "jmp loc_26FE"], "succs": [[34, "jump"]]}, {"id": 56, "start": 10336, "end": 10349, "lines": ["mov r13, rdx", "mov ecx, 5Ch ; '\\'", "jmp loc_26FE"], "succs": [[34, "jump"]]}, {"id": 57, "start": 10352, "end": 10377, "lines": ["mov esi, ecx; int", "call cs:__overflow_ptr", "movzx eax, byte ptr [r13+0]", "lea rdx, [r13+1]", "test al, al", "jnz loc_272E"], "succs": [[36, "jump"], [58, "fall"]]}, {"id": 58, "start": 10377, "end": 10384, "lines": ["nop dword ptr [rax+00000000h]"], "succs": [[59, "fall"]]}, {"id": 59, "start": 10384, "end": 10393, "lines": ["cmp r14, r12", "jz loc_2629"], "succs": [[21, "jump"], [60, "fall"]]}, {"id": 60, "start": 10393, "end": 10418, "lines": ["mov rdi, cs:stdout; _IO_FILE *", "add r14, 8", "mov rax, [rdi+28h]", "cmp rax, [rdi+30h]", "jnb loc_2A1C"], "succs": [[61, "fall"], [80, "jump"]]}, {"id": 61, "start": 10418, "end": 10434, "lines": ["lea rdx, [rax+1]", "mov [rdi+28h], rdx", "mov byte ptr [rax], 20h ; ' '", "jmp loc_26DD"], "succs": [[31, "jump"]]}, {"id": 62, "start": 10434, "end": 10443, "lines": ["test r11b, r11b", "jnz loc_26CE"], "succs": [[30, "jump"], [63, "fall"]]}, {"id": 63, "start": 10443, "end": 10452, "lines": ["test r10b, r10b", "jnz loc_26CE"], "succs": [[30, "jump"], [64, "fall"]]}, {"id": 64, "start": 10452, "end": 10462, "lines": ["lea eax, [r9-1]", "lea r12, [r14+rax*8]", "jmp short loc_28EB"], "succs": [[66, "jump"]]}, {"id": 65, "start": 10464, "end": 10475, "lines": ["lea rdx, [rax+1]", "mov [rdi+28h], rdx", "mov byte ptr [rax], 20h ; ' '"], "succs": [[66, "fall"]]}, {"id": 66, "start": 10475, "end": 10500, "lines": ["mov rdi, [r14]; s", "mov rsi, cs:stdout; stream", "call cs:fputs_unlocked_ptr", "cmp r12, r14", "jz loc_2629"], "succs": [[21, "jump"], [67, "fall"]]}, {"id": 67, "start": 10500, "end": 10521, "lines": ["mov rdi, cs:stdout; _IO_FILE *", "add r14, 8", "mov rax, [rdi+28h]", "cmp rax, [rdi+30h]", "jb short loc_28E0"], "succs": [[65, "jump"], [68, "fall"]]}, {"id": 68, "start": 10521, "end": 10534, "lines": ["mov esi, 20h ; ' '; int", "call cs:__overflow_ptr", "jmp short loc_28EB"], "succs": [[66, "jump"]]}, {"id": 69, "start": 10534, "end": 10616, "lines": ["mov rdi, r13", "call sub_3500", "lea rsi, accept+3; locale", "mov edi, 6; category", "call cs:setlocale_ptr", "lea rsi, dirname; \"/usr/share/locale\"", "lea rdi, aGnuCoreutils+4; domainname", "call cs:bindtextdomain_ptr", "lea rdi, aGnuCoreutils+4; domainname", "call cs:textdomain_ptr", "lea rdi, sub_3430", "call sub_71C0", "cmp ebx, 2", "jz loc_253E"], "succs": [[4, "jump"], [70, "fall"]]}, {"id": 70, "start": 10616, "end": 10629, "lines": ["lea r9d, [rbx-1]", "test r9d, r9d", "jle loc_2678"], "succs": [[24, "jump"], [71, "fall"]]}, {"id": 71, "start": 10629, "end": 10638, "lines": ["add r14, 8", "jmp loc_257C"], "succs": [[7, "jump"]]}, {"id": 72, "start": 10638, "end": 10659, "lines": ["movzx esi, byte ptr [r9+1]", "lea eax, [rdx+rax*8-30h]", "lea edx, [rsi-30h]", "cmp dl, 7", "ja loc_2AC3"], "succs": [[73, "fall"], [85, "jump"]]}, {"id": 73, "start": 10659, "end": 10674, "lines": ["lea eax, [rdx+rax*8]", "lea r13, [r9+2]", "movzx ecx, al", "jmp loc_26FE"], "succs": [[34, "jump"]]}, {"id": 74, "start": 10674, "end": 10702, "lines": ["movsx edi, al", "call sub_2C00", "movzx edx, byte ptr [r13+3]", "mov ecx, eax", "lea edi, [rdx-30h]", "cmp dil, 36h ; '6'", "ja loc_2A83"], "succs": [[75, "fall"], [82, "jump"]]}, {"id": 75, "start": 10702, "end": 10712, "lines": ["bt rsi, rdi", "jnb loc_2A83"], "succs": [[76, "fall"], [82, "jump"]]}, {"id": 76, "start": 10712, "end": 10737, "lines": ["movsx edi, dl", "shl ecx, 4", "add r13, 4", "call sub_2C00", "add eax, ecx", "movzx ecx, al", "jmp loc_26FE"], "succs": [[34, "jump"]]}, {"id": 77, "start": 10737, "end": 10749, "lines": ["mov r13, r9", "xor ecx, ecx", "xor eax, eax", "jmp loc_26FE"], "succs": [[34, "jump"]]}, {"id": 78, "start": 10749, "end": 10767, "lines": ["mov r13, r9; jumptable 000000000000275A case 97", "mov ecx, 7", "mov eax, 7", "jmp loc_26FE"], "succs": [[34, "jump"]]}, {"id": 79, "start": 10767, "end": 10780, "lines": ["mov r13, r9; jumptable 000000000000275A case 92", "mov ecx, 5Ch ; '\\'", "jmp loc_26FE"], "succs": [[34, "jump"]]}, {"id": 80, "start": 10780, "end": 10796, "lines": ["mov esi, 20h ; ' '; int", "call cs:__overflow_ptr", "jmp loc_26DD"], "succs": [[31, "jump"]]}, {"id": 81, "start": 10796, "end": 10883, "lines": ["lea rsi, aChetRamey; \"Chet Ramey\"", "mov rdi, rsi; msgid", "call sub_3590", "lea rsi, aBrianFox; \"Brian Fox\"", "mov rdi, rsi; msgid", "mov rbx, rax", "call sub_3590", "lea rdx, aGnuCoreutils; \"GNU coreutils\"", "mov r9, rbx", "lea rsi, aEcho; \"echo\"", "mov r8, rax", "push rax", "xor eax, eax", "mov rdi, cs:stdout", "push 0", "mov rcx, cs:off_C018; \"9.11\"", "call sub_6280", "pop rdx", "pop rcx", "jmp loc_2698; jumptable 000000000000275A case 99"], "succs": [[26, "jump"]]}, {"id": 82, "start": 10883, "end": 10892, "lines": ["add r13, 3", "jmp loc_26FE"], "succs": [[34, "jump"]]}, {"id": 83, "start": 10892, "end": 10931, "lines": ["mov [rbp+var_40], r9", "mov esi, 5Ch ; '\\'; int", "mov [rbp+var_38], ecx", "mov [rbp+var_31], r8b", "call cs:__overflow_ptr", "mov r9, [rbp+var_40]", "mov ecx, [rbp+var_38]", "movzx r8d, [rbp+var_31]", "jmp loc_27A2"], "succs": [[44, "jump"]]}, {"id": 84, "start": 10931, "end": 10947, "lines": ["mov esi, 0Ah; int", "call cs:__overflow_ptr", "jmp loc_2698; jumptable 000000000000275A case 99"], "succs": [[26, "jump"]]}, {"id": 85, "start": 10947, "end": 10959, "lines": ["lea r13, [r9+1]", "movzx ecx, al", "jmp loc_26FE"], "succs": [[34, "jump"]]}, {"id": 86, "start": 10959, "end": 10966, "lines": ["xor edi, edi", "call sub_2FF0"], "succs": []}]}, {"name": "sub_2C90", "ea": 11408, "blocks": [{"id": 0, "start": 11408, "end": 11443, "lines": ["push rbp", "mov rbp, rsp", "push r15", "push r14", "push r13", "push r12", "push rbx", "mov rbx, rdi", "sub rsp, 28h", "mov eax, cs:dword_C010", "cmp eax, 0FFFFFFFFh", "jz loc_2F5E"], "succs": [[1, "fall"], [30, "jump"]]}, {"id": 1, "start": 11443, "end": 11462, "lines": ["mov rcx, cs:stdout", "mov [rbp+stream], rcx", "test eax, eax", "jnz loc_2FAD"], "succs": [[2, "fall"], [34, "jump"]]}, {"id": 2, "start": 11462, "end": 11512, "lines": ["lea rsi, accept; \" \\t\\n\"", "mov rdi, rbx; s", "call cs:strspn_ptr", "mov esi, 2Dh ; '-'; c", "mov rdi, rbx; s", "mov [rbp+n], rax", "lea r12, [rbx+rax]", "call cs:strchr_ptr", "mov r13, rax", "test rax, rax", "jz loc_2F53"], "succs": [[3, "fall"], [29, "jump"]]}, {"id": 3, "start": 11512, "end": 11517, "lines": ["cmp r12, rax", "jz short loc_2D74"], "succs": [[4, "fall"], [8, "jump"]]}, {"id": 4, "start": 11517, "end": 11519, "lines": ["jnb short loc_2D74"], "succs": [[5, "fall"], [8, "jump"]]}, {"id": 5, "start": 11519, "end": 11584, "lines": ["call cs:__ctype_b_loc_ptr", "mov rdx, r12", "xor esi, esi", "mov rdi, [rax]", "nop dword ptr [rax+00000000h]", "nop word ptr [rax+rax+00000000h]", "nop word ptr [rax+rax+00000000h]", "nop word ptr [rax+rax+00000000h]", "nop word ptr [rax+rax+00000000h]"], "succs": [[6, "fall"]]}, {"id": 6, "start": 11584, "end": 11626, "lines": ["movsx rax, byte ptr [rdx]", "add rdx, 1", "movzx eax, byte ptr [rdi+rax*2+1]", "shr al, 5", "and eax, 1", "add rsi, rax", "cmp rdx, r13", "setb cl", "cmp rsi, 2", "setnz al", "and cl, al", "mov r14d, ecx", "jnz short loc_2D40"], "succs": [[7, "fall"], [6, "jump"]]}, {"id": 7, "start": 11626, "end": 11636, "lines": ["cmp rsi, 2", "jz loc_2FE7"], "succs": [[8, "fall"], [36, "jump"]]}, {"id": 8, "start": 11636, "end": 11642, "lines": ["mov r14d, 1"], "succs": [[9, "fall"]]}, {"id": 9, "start": 11642, "end": 11675, "lines": ["lea rsi, reject; \",=[ \\n\"", "mov rdi, r13; s", "call cs:strcspn_ptr", "lea r15, [r13+rax+0]", "mov [rbp+var_48], rax", "movzx edx, byte ptr [r15]", "test dl, dl", "jz short loc_2E10"], "succs": [[10, "fall"], [22, "jump"]]}, {"id": 10, "start": 11675, "end": 11680, "lines": ["cmp dl, 0Ah", "jz short loc_2E10"], "succs": [[11, "fall"], [22, "jump"]]}, {"id": 11, "start": 11680, "end": 11698, "lines": ["mov [rbp+var_49], dl", "call cs:__ctype_b_loc_ptr", "movzx edx, [rbp+var_49]", "mov rsi, [rax]", "jmp short loc_2DD3"], "succs": [[16, "jump"]]}, {"id": 12, "start": 11704, "end": 11713, "lines": ["test ah, 20h", "jnz loc_2F40"], "succs": [[13, "fall"], [27, "jump"]]}, {"id": 13, "start": 11713, "end": 11718, "lines": ["movzx edx, byte ptr [r15+1]"], "succs": [[14, "fall"]]}, {"id": 14, "start": 11718, "end": 11726, "lines": ["add r15, 1", "test dl, dl", "jz short loc_2E10"], "succs": [[15, "fall"], [22, "jump"]]}, {"id": 15, "start": 11726, "end": 11731, "lines": ["cmp dl, 0Ah", "jz short loc_2E10"], "succs": [[16, "fall"], [22, "jump"]]}, {"id": 16, "start": 11731, "end": 11744, "lines": ["movsx rax, dl", "movzx eax, word ptr [rsi+rax*2]", "cmp dl, 2Dh ; '-'", "jnz short loc_2DB8"], "succs": [[12, "jump"], [17, "fall"]]}, {"id": 17, "start": 11744, "end": 11764, "lines": ["movzx edx, byte ptr [r15+1]", "cmp dl, 2Dh ; '-'", "setnz dil", "and r14d, edi", "test ah, 20h", "jz short loc_2DC6"], "succs": [[14, "jump"], [18, "fall"]]}, {"id": 18, "start": 11764, "end": 11775, "lines": ["movsx rax, dl", "test byte ptr [rsi+rax*2+1], 20h", "jnz short loc_2E10"], "succs": [[19, "fall"], [22, "jump"]]}, {"id": 19, "start": 11775, "end": 11780, "lines": ["test r14b, r14b", "jnz short loc_2DC6"], "succs": [[14, "jump"], [20, "fall"]]}, {"id": 20, "start": 11780, "end": 11785, "lines": ["cmp dl, 2Dh ; '-'", "jz short loc_2DC6"], "succs": [[14, "jump"], [21, "fall"]]}, {"id": 21, "start": 11785, "end": 11792, "lines": ["nop dword ptr [rax+00000000h]"], "succs": [[22, "fall"]]}, {"id": 22, "start": 11792, "end": 11839, "lines": ["mov rdx, [rbp+n]; n", "mov esi, 1; size", "mov rdi, rbx; ptr", "mov rcx, [rbp+stream]; stream", "call cs:fwrite_unlocked_ptr", "mov edx, 6; n", "lea rsi, aHelp; \"--help\"", "mov rdi, r13; s1", "call cs:strncmp_ptr", "test eax, eax", "jz short loc_2E5C"], "succs": [[23, "fall"], [24, "jump"]]}, {"id": 23, "start": 11839, "end": 11868, "lines": ["mov edx, 9; n", "lea rsi, aVersion; \"--version\"", "mov rdi, r13; s1", "call cs:strncmp_ptr", "test eax, eax", "jnz loc_2FB9"], "succs": [[24, "fall"], [35, "jump"]]}, {"id": 24, "start": 11868, "end": 11914, "lines": ["lea r8, aEcho; \"echo\"", "lea rdx, aHttpsWwwGnuOrg; \"https://www.gnu.org/software/coreutils/\"", "mov edi, 2", "push rax", "mov rcx, r8", "push r13", "lea rsi, a8SSSS; \"\\x1B]8;;%s%s#%s%.*s\"", "mov r9d, dword ptr [rbp+var_48]", "xor eax, eax", "call cs:__printf_chk_ptr", "pop rdx", "pop rcx"], "succs": [[25, "fall"]]}, {"id": 25, "start": 11914, "end": 12071, "lines": ["mov rcx, cs:stdout; stream", "mov edx, 2; n", "mov esi, 1; size", "lea rdi, a8+5; ptr", "call cs:fwrite_unlocked_ptr", "mov edx, 4; n", "mov rcx, cs:stdout; stream", "mov esi, 1; size", "lea rdi, a1m; \"\\x1B[1m\"", "call cs:fwrite_unlocked_ptr", "mov rdx, r15", "mov esi, 1; size", "mov rdi, r12; ptr", "mov rcx, cs:stdout; stream", "sub rdx, r12; n", "call cs:fwrite_unlocked_ptr", "mov edx, 4; n", "mov rcx, cs:stdout; stream", "mov esi, 1; size", "lea rdi, a0m; \"\\x1B[0m\"", "call cs:fwrite_unlocked_ptr", "mov esi, 1; size", "mov edx, 7; n", "lea rdi, a8; \"\\x1B]8;;\\x1B\\\\\"", "mov rcx, cs:stdout; stream", "call cs:fwrite_unlocked_ptr", "mov rsi, cs:stdout; stream", "mov rdi, r15; s"], "succs": [[26, "fall"]]}, {"id": 26, "start": 12071, "end": 12091, "lines": ["lea rsp, [rbp-28h]", "pop rbx", "pop r12", "pop r13", "pop r14", "pop r15", "pop rbp", "jmp cs:fputs_unlocked_ptr"], "succs": [[37, "jump"]]}, {"id": 27, "start": 12096, "end": 12105, "lines": ["cmp dl, 9", "jz loc_2E10"], "succs": [[22, "jump"], [28, "fall"]]}, {"id": 28, "start": 12105, "end": 12115, "lines": ["movzx edx, byte ptr [r15+1]", "jmp loc_2DF4"], "succs": [[18, "jump"]]}, {"id": 29, "start": 12115, "end": 12126, "lines": ["mov r13, r12", "xor r14d, r14d", "jmp loc_2D7A"], "succs": [[9, "jump"]]}, {"id": 30, "start": 12126, "end": 12144, "lines": ["lea rdi, aTerm; \"TERM\"", "call cs:getenv_ptr", "test rax, rax", "jz short loc_2F98"], "succs": [[31, "fall"], [33, "jump"]]}, {"id": 31, "start": 12144, "end": 12149, "lines": ["cmp byte ptr [rax], 0", "jz short loc_2F98"], "succs": [[32, "fall"], [33, "jump"]]}, {"id": 32, "start": 12149, "end": 12184, "lines": ["lea rsi, aDumb; \"dumb\"", "mov rdi, rax; s1", "call cs:strcmp_ptr", "test eax, eax", "setz al", "movzx eax, al", "mov cs:dword_C010, eax", "jmp loc_2CB3"], "succs": [[1, "jump"]]}, {"id": 33, "start": 12184, "end": 12205, "lines": ["mov rax, cs:stdout", "mov cs:dword_C010, 1", "mov [rbp+stream], rax"], "succs": [[34, "fall"]]}, {"id": 34, "start": 12205, "end": 12217, "lines": ["mov rsi, [rbp+stream]", "mov rdi, rbx", "jmp loc_2F27"], "succs": [[26, "jump"]]}, {"id": 35, "start": 12217, "end": 12263, "lines": ["mov r8d, dword ptr [rbp+var_48]", "mov r9, r13", "lea rcx, aEcho; \"echo\"", "xor eax, eax", "lea rdx, aHttpsWwwGnuOrg_0; \"https://www.gnu.org/software/coreutils/\"...", "lea rsi, a8SSS; \"\\x1B]8;;%s#%s%.*s\"", "mov edi, 2", "call cs:__printf_chk_ptr", "jmp loc_2E8A"], "succs": [[25, "jump"]]}, {"id": 36, "start": 12263, "end": 12271, "lines": ["mov r13, r12", "jmp loc_2D7A"], "succs": [[9, "jump"]]}, {"id": 37, "start": 50000, "end": 50000, "lines": [], "succs": []}]}, {"name": "sub_5CA0", "ea": 23712, "blocks": [{"id": 0, "start": 23712, "end": 23751, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "push r15", "push r14", "push r13", "mov r13, r9", "push r12", "mov r12, r8", "push rbx", "mov rbx, rdi", "sub rsp, 38h", "test rsi, rsi", "jz loc_5E30"], "succs": [[1, "fall"], [7, "jump"]]}, {"id": 1, "start": 23751, "end": 23780, "lines": ["mov r9, rcx", "mov r8, rdx", "mov rcx, rsi", "xor eax, eax", "lea rdx, aSSS; \"%s (%s) %s\\n\"", "mov esi, 2", "call cs:__fprintf_chk_ptr"], "succs": [[2, "fall"]]}, {"id": 2, "start": 23780, "end": 23926, "lines": ["mov edx, 5; category", "lea rsi, aC; \"(C)\"", "lea rdi, domainname; \"gnulib\"", "call cs:dcgettext_ptr", "mov r8d, 7EAh", "mov esi, 2", "mov rdi, rbx", "mov rcx, rax", "lea rdx, aCopyrightSDFre; \"Copyright %s %d Free Software Foundatio\"...", "xor eax, eax", "call cs:__fprintf_chk_ptr", "mov rsi, rbx; stream", "mov edi, 0Ah; c", "call cs:fputc_unlocked_ptr", "mov edx, 5; category", "lea rsi, aLicenseGplv3Gn; \"License GPLv3+: GNU GPL version 3 or la\"...", "lea rdi, domainname; \"gnulib\"", "call cs:dcgettext_ptr", "mov esi, 2", "mov rdi, rbx", "lea rcx, aHttpsGnuOrgLic; \"https://gnu.org/licenses/gpl.html\"", "mov rdx, rax", "xor eax, eax", "call cs:__fprintf_chk_ptr", "mov rsi, rbx; stream", "mov edi, 0Ah; c", "call cs:fputc_unlocked_ptr", "cmp r13, 9; switch 10 cases", "ja def_5D84; jumptable 0000000000005D84 default case"], "succs": [[3, "fall"], [19, "jump"]]}, {"id": 3, "start": 23926, "end": 23943, "lines": ["lea rdx, jpt_5D84", "movsxd rax, ds:(jpt_5D84 - 8F80h)[rdx+r13*4]", "add rax, rdx", "jmp rax; switch jump"], "succs": [[4, "switch"], [6, "switch"], [8, "switch"], [10, "switch"], [11, "switch"], [12, "switch"], [13, "switch"], [15, "switch"], [16, "switch"], [18, "switch"]]}, {"id": 4, "start": 23952, "end": 24065, "lines": ["mov r11, [r12+30h]; jumptable 0000000000005D84 case 8", "mov r10, [r12+28h]", "mov edx, 5; category", "lea rsi, aWrittenBySSSSS; \"Written by %s, %s, %s,\\n%s, %s, %s, %s,\"...", "mov r8, [r12+20h]", "mov r9, [r12+10h]", "lea rdi, domainname; \"gnulib\"", "mov r14, [r12+38h]", "mov r13, [r12+18h]", "mov [rbp+var_50], r11", "mov r15, [r12+8]", "mov r12, [r12]", "mov [rbp+var_48], r10", "mov [rbp+var_40], r8", "mov [rbp+var_38], r9", "call cs:dcgettext_ptr", "sub rsp, 8", "push r14", "mov r11, [rbp+var_50]", "push r11", "mov r10, [rbp+var_48]", "push r10", "mov r8, [rbp+var_40]", "push r8", "mov r8, r15", "push r13", "mov r9, [rbp+var_38]"], "succs": [[5, "fall"]]}, {"id": 5, "start": 24065, "end": 24091, "lines": ["mov rdx, rax", "mov rcx, r12", "mov esi, 2", "mov rdi, rbx", "xor eax, eax", "call cs:__fprintf_chk_ptr", "add rsp, 30h"], "succs": [[6, "fall"]]}, {"id": 6, "start": 24091, "end": 24106, "lines": ["lea rsp, [rbp-28h]; jumptable 0000000000005D84 case 0", "pop rbx", "pop r12", "pop r13", "pop r14", "pop r15", "pop rbp", "retn"], "succs": []}, {"id": 7, "start": 24112, "end": 24143, "lines": ["mov r8, rcx", "mov esi, 2", "mov rcx, rdx", "xor eax, eax", "lea rdx, aSS_0; \"%s %s\\n\"", "call cs:__fprintf_chk_ptr", "jmp loc_5CE4"], "succs": [[2, "jump"]]}, {"id": 8, "start": 24144, "end": 24220, "lines": ["mov rax, [r12+30h]; jumptable 0000000000005D84 case 9", "mov r11, [r12+28h]", "mov edx, 5; category", "lea rsi, aWrittenBySSSSS_0; \"Written by %s, %s, %s,\\n%s, %s, %s, %s,\"...", "mov r10, [r12+20h]", "mov r9, [r12+10h]", "mov r8, [r12+8]", "mov r15, [r12+40h]", "mov [rbp+var_38], rax", "mov r14, [r12+38h]", "mov r13, [r12+18h]", "mov [rbp+var_58], r11", "mov [rbp+var_50], r10", "mov r12, [r12]", "mov [rbp+var_48], r9", "mov [rbp+var_40], r8"], "succs": [[9, "fall"]]}, {"id": 9, "start": 24220, "end": 24267, "lines": ["lea rdi, domainname; \"gnulib\"", "call cs:dcgettext_ptr", "push r15", "push r14", "push [rbp+var_38]", "mov r11, [rbp+var_58]", "push r11", "mov r10, [rbp+var_50]", "push r10", "push r13", "mov r9, [rbp+var_48]", "mov r8, [rbp+var_40]", "jmp loc_5E01"], "succs": [[5, "jump"]]}, {"id": 10, "start": 24272, "end": 24337, "lines": ["mov r12, [r12]; jumptable 0000000000005D84 case 1", "mov edx, 5; category", "lea rsi, aWrittenByS; \"Written by %s.\\n\"", "lea rdi, domainname; \"gnulib\"", "call cs:dcgettext_ptr", "lea rsp, [rbp-28h]", "mov rdi, rbx", "mov esi, 2", "pop rbx", "mov rcx, r12", "mov rdx, rax", "pop r12", "xor eax, eax", "pop r13", "pop r14", "pop r15", "pop rbp", "jmp cs:__fprintf_chk_ptr"], "succs": [[20, "jump"]]}, {"id": 11, "start": 24344, "end": 24417, "lines": ["mov r13, [r12+8]; jumptable 0000000000005D84 case 2", "mov r12, [r12]", "mov edx, 5; category", "lea rsi, aWrittenBySAndS; \"Written by %s and %s.\\n\"", "lea rdi, domainname; \"gnulib\"", "call cs:dcgettext_ptr", "lea rsp, [rbp-28h]", "mov r8, r13", "mov rcx, r12", "mov rdx, rax", "mov rdi, rbx", "mov esi, 2", "pop rbx", "xor eax, eax", "pop r12", "pop r13", "pop r14", "pop r15", "pop rbp", "jmp cs:__fprintf_chk_ptr"], "succs": [[20, "jump"]]}, {"id": 12, "start": 24424, "end": 24510, "lines": ["mov r9, [r12+10h]; jumptable 0000000000005D84 case 3", "mov r13, [r12+8]", "mov edx, 5; category", "lea rsi, aWrittenBySSAnd; \"Written by %s, %s, and %s.\\n\"", "mov r12, [r12]", "lea rdi, domainname; \"gnulib\"", "mov [rbp+var_38], r9", "call cs:dcgettext_ptr", "mov r9, [rbp+var_38]", "lea rsp, [rbp-28h]", "mov r8, r13", "mov rcx, r12", "mov rdx, rax", "mov rdi, rbx", "mov esi, 2", "pop rbx", "xor eax, eax", "pop r12", "pop r13", "pop r14", "pop r15", "pop rbp", "jmp cs:__fprintf_chk_ptr"], "succs": [[20, "jump"]]}, {"id": 13, "start": 24512, "end": 24578, "lines": ["mov r9, [r12+10h]; jumptable 0000000000005D84 case 4", "mov r8, [r12+8]", "mov edx, 5; category", "lea rsi, aWrittenBySSSAn; \"Written by %s, %s, %s,\\nand %s.\\n\"", "mov r13, [r12+18h]", "lea rdi, domainname; \"gnulib\"", "mov r12, [r12]", "mov [rbp+var_40], r9", "mov [rbp+var_38], r8", "call cs:dcgettext_ptr", "sub rsp, 8", "push r13", "mov r9, [rbp+var_40]", "mov r8, [rbp+var_38]"], "succs": [[14, "fall"]]}, {"id": 14, "start": 24578, "end": 24617, "lines": ["mov rdx, rax", "mov rcx, r12", "mov rdi, rbx", "mov esi, 2", "xor eax, eax", "call cs:__fprintf_chk_ptr", "pop rax", "pop rdx", "lea rsp, [rbp-28h]", "pop rbx", "pop r12", "pop r13", "pop r14", "pop r15", "pop rbp", "retn"], "succs": []}, {"id": 15, "start": 24624, "end": 24698, "lines": ["mov r8, [r12+20h]; jumptable 0000000000005D84 case 5", "mov r9, [r12+10h]", "mov edx, 5; category", "lea rsi, aWrittenBySSSSA; \"Written by %s, %s, %s,\\n%s, and %s.\\n\"", "mov r13, [r12+18h]", "mov r14, [r12+8]", "lea rdi, domainname; \"gnulib\"", "mov [rbp+var_40], r8", "mov r12, [r12]", "mov [rbp+var_38], r9", "call cs:dcgettext_ptr", "mov r8, [rbp+var_40]", "push r8", "mov r8, r14", "push r13", "mov r9, [rbp+var_38]", "jmp short loc_6002"], "succs": [[14, "jump"]]}, {"id": 16, "start": 24704, "end": 24774, "lines": ["mov r10, [r12+28h]; jumptable 0000000000005D84 case 6", "mov r8, [r12+20h]", "mov edx, 5; category", "lea rsi, aWrittenBySSSSS_1; \"Written by %s, %s, %s,\\n%s, %s, and %s.\"...", "mov r9, [r12+10h]", "mov r13, [r12+18h]", "lea rdi, domainname; \"gnulib\"", "mov r14, [r12+8]", "mov r12, [r12]", "mov [rbp+var_48], r10", "mov [rbp+var_40], r8", "mov [rbp+var_38], r9", "call cs:dcgettext_ptr", "sub rsp, 8"], "succs": [[17, "fall"]]}, {"id": 17, "start": 24774, "end": 24836, "lines": ["mov r10, [rbp+var_48]", "mov rcx, r12", "mov rdx, rax", "mov rdi, rbx", "mov esi, 2", "xor eax, eax", "push r10", "mov r8, [rbp+var_40]", "push r8", "mov r8, r14", "push r13", "mov r9, [rbp+var_38]", "call cs:__fprintf_chk_ptr", "add rsp, 20h", "lea rsp, [rbp-28h]", "pop rbx", "pop r12", "pop r13", "pop r14", "pop r15", "pop rbp", "retn"], "succs": []}, {"id": 18, "start": 24840, "end": 24926, "lines": ["mov r11, [r12+30h]; jumptable 0000000000005D84 case 7", "mov r10, [r12+28h]", "mov edx, 5; category", "lea rsi, aWrittenBySSSSS_2; \"Written by %s, %s, %s,\\n%s, %s, %s, and\"...", "mov r8, [r12+20h]", "mov r9, [r12+10h]", "lea rdi, domainname; \"gnulib\"", "mov r13, [r12+18h]", "mov r14, [r12+8]", "mov [rbp+var_50], r11", "mov [rbp+var_48], r10", "mov r12, [r12]", "mov [rbp+var_40], r8", "mov [rbp+var_38], r9", "call cs:dcgettext_ptr", "mov r11, [rbp+var_50]", "push r11", "jmp loc_60C6"], "succs": [[17, "jump"]]}, {"id": 19, "start": 24926, "end": 25007, "lines": ["mov rax, [r12+30h]; jumptable 0000000000005D84 default case", "mov r11, [r12+28h]", "mov edx, 5", "lea rsi, aWrittenBySSSSS_3; \"Written by %s, %s, %s,\\n%s, %s, %s, %s,\"...", "mov r10, [r12+20h]", "mov r9, [r12+10h]", "mov r8, [r12+8]", "mov r15, [r12+40h]", "mov [rbp+var_38], rax", "mov r14, [r12+38h]", "mov r13, [r12+18h]", "mov [rbp+var_58], r11", "mov [rbp+var_50], r10", "mov r12, [r12]", "mov [rbp+var_48], r9", "mov [rbp+var_40], r8", "jmp loc_5E9C"], "succs": [[9, "jump"]]}, {"id": 20, "start": 50120, "end": 50120, "lines": [], "succs": []}]}, {"name": "sub_6670", "ea": 26224, "blocks": [{"id": 0, "start": 26224, "end": 26262, "lines": ["endbr64", "push rbp", "mov r10, rcx", "mov r9, rdx", "mov rbp, rsp", "push r12", "mov r12, rsi", "push rbx", "mov rcx, [rsi]", "mov rbx, rcx", "sar rbx, 1", "add rbx, rcx", "jo loc_6747"], "succs": [[1, "fall"], [17, "jump"]]}, {"id": 1, "start": 26262, "end": 26292, "lines": ["cmp rbx, r10", "mov rax, r10", "cmovle rax, rbx", "test r10, r10", "cmovns rbx, rax", "mov rax, rbx", "imul rax, r8", "jo loc_673B"], "succs": [[2, "fall"], [16, "jump"]]}, {"id": 2, "start": 26292, "end": 26298, "lines": ["cmp rax, 7Fh", "jle short loc_6710"], "succs": [[3, "fall"], [12, "jump"]]}, {"id": 3, "start": 26298, "end": 26303, "lines": ["test rdi, rdi", "jz short loc_672B"], "succs": [[4, "fall"], [14, "jump"]]}, {"id": 4, "start": 26303, "end": 26304, "lines": ["nop"], "succs": [[5, "fall"]]}, {"id": 5, "start": 26304, "end": 26315, "lines": ["mov rdx, rbx", "sub rdx, rcx", "cmp rdx, r9", "jge short loc_66E6"], "succs": [[6, "fall"], [10, "jump"]]}, {"id": 6, "start": 26315, "end": 26320, "lines": ["add rcx, r9", "jo short loc_6735"], "succs": [[7, "fall"], [15, "jump"]]}, {"id": 7, "start": 26320, "end": 26328, "lines": ["mov rbx, rcx", "test r10, r10", "js short loc_66DD"], "succs": [[8, "fall"], [9, "jump"]]}, {"id": 8, "start": 26328, "end": 26333, "lines": ["cmp rcx, r10", "jg short loc_6735"], "succs": [[9, "fall"], [15, "jump"]]}, {"id": 9, "start": 26333, "end": 26342, "lines": ["mov rax, rcx", "imul rax, r8", "jo short loc_6735"], "succs": [[10, "fall"], [15, "jump"]]}, {"id": 10, "start": 26342, "end": 26365, "lines": ["test rax, rax", "mov esi, 1", "cmovnz rsi, rax; size", "call cs:realloc_ptr", "test rax, rax", "jz short loc_6735"], "succs": [[11, "fall"], [15, "jump"]]}, {"id": 11, "start": 26365, "end": 26374, "lines": ["mov [r12], rbx", "pop rbx", "pop r12", "pop rbp", "retn"], "succs": []}, {"id": 12, "start": 26384, "end": 26389, "lines": ["mov esi, 80h"], "succs": [[13, "fall"]]}, {"id": 13, "start": 26389, "end": 26411, "lines": ["mov rax, rsi", "cqo", "idiv r8", "mov rbx, rax", "mov rax, rsi", "sub rax, rdx", "test rdi, rdi", "jnz short loc_66C0"], "succs": [[5, "jump"], [14, "fall"]]}, {"id": 14, "start": 26411, "end": 26421, "lines": ["mov qword ptr [r12], 0", "jmp short loc_66C0"], "succs": [[5, "jump"]]}, {"id": 15, "start": 26421, "end": 26427, "lines": ["call sub_6900"], "succs": []}, {"id": 16, "start": 26427, "end": 26439, "lines": ["mov rsi, 7FFFFFFFFFFFFFFFh", "jmp short loc_6715"], "succs": [[13, "jump"]]}, {"id": 17, "start": 26439, "end": 26454, "lines": ["mov rbx, 7FFFFFFFFFFFFFFFh", "jmp loc_6696"], "succs": [[1, "jump"]]}]}, {"name": "sub_2FF0", "ea": 12272, "blocks": [{"id": 0, "start": 12272, "end": 12371, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "push r12", "push rbx", "sub rsp, 0C0h", "movq xmm4, cs:off_BBE0; \"sha224sum\"", "movq xmm3, cs:off_BBE8; \"sha256sum\"", "mov rax, fs:28h", "mov [rbp+var_18], rax", "lea rax, aSha2Utilities; \"sha2 utilities\"", "movq xmm2, cs:off_BBF0; \"sha384sum\"", "movq xmm1, cs:off_BBF8; \"sha512sum\"", "movq xmm0, rax", "punpcklqdq xmm4, xmm0", "punpcklqdq xmm3, xmm0", "punpcklqdq xmm2, xmm0", "punpcklqdq xmm1, xmm0", "test edi, edi", "jnz loc_2040"], "succs": [[1, "fall"], [16, "jump"]]}, {"id": 1, "start": 12371, "end": 12960, "lines": ["mov rbx, cs:qword_C120", "movaps [rbp+var_D0], xmm1", "xor edi, edi; domainname", "mov edx, 5; category", "movaps [rbp+var_C0], xmm2", "lea rsi, aUsageSShortOpt; \"Usage: %s [SHORT-OPTION]... [STRING]...\"...", "movaps [rbp+var_B0], xmm3", "movaps [rbp+var_A0], xmm4", "call cs:dcgettext_ptr", "mov rcx, rbx", "mov rdx, rbx", "mov edi, 2", "mov rsi, rax", "xor eax, eax", "call cs:__printf_chk_ptr", "mov rbx, cs:stdout", "xor edi, edi; domainname", "mov edx, 5; category", "lea rsi, aEchoTheStringS; \"Echo the STRING(s) to standard output.\"...", "call cs:dcgettext_ptr", "mov rsi, rbx; stream", "mov rdi, rax; s", "call cs:fputs_unlocked_ptr", "mov edx, 5; category", "lea rsi, aNDoNotOutputTh; \" -n do not output the trailing new\"...", "xor edi, edi; domainname", "call cs:dcgettext_ptr", "mov rdi, rax; ptr", "call sub_2C90", "mov edx, 5; category", "lea rsi, aEEnableInterpr; \" -e enable interpretation of backs\"...", "xor edi, edi; domainname", "call cs:dcgettext_ptr", "mov rdi, rax; ptr", "call sub_2C90", "mov edx, 5; category", "lea rsi, aEDisableInterp; \" -E disable interpretation of back\"...", "xor edi, edi; domainname", "call cs:dcgettext_ptr", "mov rdi, rax; ptr", "call sub_2C90", "mov edx, 5; category", "lea rsi, aHelpDisplayThi; \" --help\\n display this hel\"...", "xor edi, edi; domainname", "call cs:dcgettext_ptr", "mov rdi, rax; ptr", "call sub_2C90", "mov edx, 5; category", "lea rsi, aVersionOutputV; \" --version\\n output versio\"...", "xor edi, edi; domainname", "call cs:dcgettext_ptr", "mov rdi, rax; ptr", "call sub_2C90", "mov rbx, cs:stdout", "xor edi, edi; domainname", "mov edx, 5; category", "lea rsi, aIfEIsInEffectT; \"\\nIf -e is in effect, the following seq\"...", "call cs:dcgettext_ptr", "mov rsi, rbx; stream", "mov rdi, rax; s", "call cs:fputs_unlocked_ptr", "mov rbx, cs:stdout", "xor edi, edi; domainname", "mov edx, 5; category", "lea rsi, aBackslashAAler; \" \\\\\\\\ backslash\\n \\\\a alert\"...", "call cs:dcgettext_ptr", "mov rsi, rbx; stream", "mov rdi, rax; s", "call cs:fputs_unlocked_ptr", "mov rbx, cs:stdout", "xor edi, edi; domainname", "mov edx, 5; category", "lea rsi, a0nnnByteWithOc; \" \\\\0NNN byte with octal value NNN (1\"...", "call cs:dcgettext_ptr", "mov rsi, rbx; stream", "mov rdi, rax; s", "call cs:fputs_unlocked_ptr", "mov edx, 5; category", "lea rsi, aYourShellMayHa; \"\\nYour shell may have its own version o\"...", "xor edi, edi; domainname", "call cs:dcgettext_ptr", "lea rdx, aEcho; \"echo\"", "mov edi, 2", "mov rsi, rax", "xor eax, eax", "call cs:__printf_chk_ptr", "mov rbx, cs:stdout", "mov edx, 5; category", "xor edi, edi; domainname", "lea rsi, aConsiderUsingT; \"\\nConsider using the printf(1) command \"...", "call cs:dcgettext_ptr", "mov rsi, rbx; stream", "lea rbx, [rbp+var_90]", "mov rdi, rax; s", "call cs:fputs_unlocked_ptr", "lea rax, aTestInvocation; \"test invocation\"", "movq xmm0, cs:off_BC00; \"[\"", "movdqa xmm4, [rbp+var_A0]", "movq xmm5, rax", "lea rax, aMultiCallInvoc; \"Multi-call invocation\"", "movdqa xmm3, [rbp+var_B0]", "movdqa xmm2, [rbp+var_C0]", "punpcklqdq xmm0, xmm5", "movq xmm6, rax", "movdqa xmm1, [rbp+var_D0]", "movaps [rbp+var_70], xmm4", "movaps [rbp+var_90], xmm0", "movq xmm0, cs:off_BC08; \"coreutils\"", "movaps [rbp+var_60], xmm3", "punpcklqdq xmm0, xmm6", "movaps [rbp+var_50], xmm2", "movaps [rbp+var_80], xmm0", "pxor xmm0, xmm0", "movaps [rbp+var_40], xmm1", "movaps [rbp+var_30], xmm0", "nop", "nop word ptr [rax+rax+00000000h]"], "succs": [[2, "fall"]]}, {"id": 2, "start": 12960, "end": 12973, "lines": ["mov rsi, [rbx+10h]; s2", "add rbx, 10h", "test rsi, rsi", "jz short loc_32BE"], "succs": [[3, "fall"], [4, "jump"]]}, {"id": 3, "start": 12973, "end": 12990, "lines": ["lea rdi, aEcho; \"echo\"", "call cs:strcmp_ptr", "test eax, eax", "jnz short loc_32A0"], "succs": [[2, "jump"], [4, "fall"]]}, {"id": 4, "start": 12990, "end": 13003, "lines": ["mov rbx, [rbx+8]", "test rbx, rbx", "jz loc_33A7"], "succs": [[5, "fall"], [13, "jump"]]}, {"id": 5, "start": 13003, "end": 13027, "lines": ["call sub_6360", "xor esi, esi; locale", "mov edi, 5; category", "call cs:setlocale_ptr", "test rax, rax", "jz short loc_3313"], "succs": [[6, "fall"], [8, "jump"]]}, {"id": 6, "start": 13027, "end": 13036, "lines": ["cmp byte ptr [rax], 65h ; 'e'", "jz loc_338E"], "succs": [[7, "fall"], [10, "jump"]]}, {"id": 7, "start": 13036, "end": 13075, "lines": ["mov r12, cs:stdout", "lea rsi, aReportAnyTrans; \"Report any translation bugs to <https:/\"...", "xor edi, edi; domainname", "mov edx, 5; category", "call cs:dcgettext_ptr", "mov rdi, rax; s", "mov rsi, r12; stream", "call cs:fputs_unlocked_ptr"], "succs": [[8, "fall"]]}, {"id": 8, "start": 13075, "end": 13148, "lines": ["mov edx, 5; category", "lea rsi, aFullDocumentat; \"Full documentation <%s%s>\\n\"", "xor edi, edi; domainname", "call cs:dcgettext_ptr", "lea rcx, aEcho; \"echo\"", "lea rdx, aHttpsWwwGnuOrg; \"https://www.gnu.org/software/coreutils/\"", "mov edi, 2", "mov rsi, rax", "xor eax, eax", "lea r12, accept+3; \"\"", "call cs:__printf_chk_ptr", "lea rax, aEcho; \"echo\"", "cmp rbx, rax", "jz loc_33FC"], "succs": [[9, "fall"], [15, "jump"]]}, {"id": 9, "start": 13148, "end": 13198, "lines": ["mov edx, 5; category", "lea rsi, aOrAvailableLoc; \"or available locally via: info '(coreut\"...", "xor edi, edi; domainname", "call cs:dcgettext_ptr", "mov edi, 2", "mov rcx, r12", "mov rdx, rbx", "mov rsi, rax", "xor eax, eax", "call cs:__printf_chk_ptr", "xor edi, edi; status", "call cs:exit_ptr"], "succs": []}, {"id": 10, "start": 13198, "end": 13208, "lines": ["cmp byte ptr [rax+1], 6Eh ; 'n'", "jnz loc_32EC"], "succs": [[7, "jump"], [11, "fall"]]}, {"id": 11, "start": 13208, "end": 13218, "lines": ["cmp byte ptr [rax+2], 5Fh ; '_'", "jz loc_3313"], "succs": [[8, "jump"], [12, "fall"]]}, {"id": 12, "start": 13218, "end": 13223, "lines": ["jmp loc_32EC"], "succs": [[7, "jump"]]}, {"id": 13, "start": 13223, "end": 13258, "lines": ["call sub_6360", "xor esi, esi; locale", "mov edi, 5; category", "lea rbx, aEcho; \"echo\"", "call cs:setlocale_ptr", "test rax, rax", "jnz loc_32E3"], "succs": [[6, "jump"], [14, "fall"]]}, {"id": 14, "start": 13258, "end": 13308, "lines": ["mov edx, 5; category", "lea rsi, aFullDocumentat; \"Full documentation <%s%s>\\n\"", "xor edi, edi; domainname", "call cs:dcgettext_ptr", "lea rcx, aEcho; \"echo\"", "mov edi, 2", "lea rdx, aHttpsWwwGnuOrg; \"https://www.gnu.org/software/coreutils/\"", "mov rsi, rax", "xor eax, eax", "call cs:__printf_chk_ptr"], "succs": [[15, "fall"]]}, {"id": 15, "start": 13308, "end": 13327, "lines": ["lea rbx, aEcho; \"echo\"", "lea r12, aMultiCallInvoc+0Ah; \" invocation\"", "jmp loc_335C"], "succs": [[9, "jump"]]}, {"id": 16, "start": 8256, "end": 8288, "lines": ["lea rcx, function; \"usage\"", "mov edx, 29h ; ')'; line", "lea rsi, file; \"src/echo.c\"", "lea rdi, assertion; \"status == 0\"", "call cs:__assert_fail_ptr"], "succs": []}]}, {"name": "sub_2297", "ea": 8855, "blocks": [{"id": 0, "start": 8855, "end": 8903, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "push r15", "mov r15, r9", "push r14", "mov r14, r8", "push r13", "mov r13d, esi", "push r12", "mov r12d, ecx", "push rbx", "mov rbx, rdx", "sub rsp, 18h", "cmp cs:error_one_per_line, 0", "mov [rbp+status], edi", "jz short loc_2308"], "succs": [[1, "fall"], [7, "jump"]]}, {"id": 1, "start": 8903, "end": 8911, "lines": ["cmp cs:dword_C118, ecx", "jnz short loc_22FA"], "succs": [[2, "fall"], [6, "jump"]]}, {"id": 2, "start": 8911, "end": 8927, "lines": ["mov rdi, cs:s1; s1", "cmp rdi, rdx", "jz loc_238D"], "succs": [[3, "fall"], [13, "jump"]]}, {"id": 3, "start": 8927, "end": 8932, "lines": ["test rdx, rdx", "jz short loc_22FA"], "succs": [[4, "fall"], [6, "jump"]]}, {"id": 4, "start": 8932, "end": 8937, "lines": ["test rdi, rdi", "jz short loc_22FA"], "succs": [[5, "fall"], [6, "jump"]]}, {"id": 5, "start": 8937, "end": 8954, "lines": ["mov rsi, rdx; s2", "call cs:strcmp_ptr", "test eax, eax", "jz loc_238D"], "succs": [[6, "fall"], [13, "jump"]]}, {"id": 6, "start": 8954, "end": 8968, "lines": ["mov cs:s1, rbx", "mov cs:dword_C118, r12d"], "succs": [[7, "fall"]]}, {"id": 7, "start": 8968, "end": 8985, "lines": ["call sub_2155", "mov rax, cs:error_print_progname", "test rax, rax", "jz short loc_231D"], "succs": [[8, "fall"], [9, "jump"]]}, {"id": 8, "start": 8985, "end": 8989, "lines": ["call rax ; error_print_progname", "jmp short loc_2341"], "succs": [[10, "jump"]]}, {"id": 9, "start": 8989, "end": 9025, "lines": ["call sub_34F0", "mov rdi, cs:stderr", "mov esi, 2", "lea rdx, aS; \"%s:\"", "mov rcx, rax", "xor eax, eax", "call cs:__fprintf_chk_ptr"], "succs": [[10, "fall"]]}, {"id": 10, "start": 9025, "end": 9037, "lines": ["lea rdx, aS_0+3; \" \"", "test rbx, rbx", "jz short loc_2354"], "succs": [[11, "fall"], [12, "jump"]]}, {"id": 11, "start": 9037, "end": 9044, "lines": ["lea rdx, aSU; \"%s:%u: \""], "succs": [[12, "fall"]]}, {"id": 12, "start": 9044, "end": 9101, "lines": ["mov rcx, rbx", "mov rdi, cs:stderr", "mov r8d, r12d", "xor eax, eax", "mov esi, 2", "call cs:__fprintf_chk_ptr", "mov edi, [rbp+status]; status", "add rsp, 18h", "mov rcx, r15", "pop rbx", "mov rdx, r14", "pop r12", "mov esi, r13d", "pop r13", "pop r14", "pop r15", "pop rbp", "jmp sub_20DD"], "succs": [[14, "jump"]]}, {"id": 13, "start": 9101, "end": 9116, "lines": ["add rsp, 18h", "pop rbx", "pop r12", "pop r13", "pop r14", "pop r15", "pop rbp", "retn"], "succs": []}, {"id": 14, "start": 8413, "end": 8413, "lines": [], "succs": []}]}, {"name": "sub_4F70", "ea": 20336, "blocks": [{"id": 0, "start": 20336, "end": 20413, "lines": ["push rbp", "mov rbp, rsp", "push r15", "push r14", "push r13", "push r12", "push rbx", "movsxd rbx, edi", "sub rsp, 38h", "mov [rbp+var_48], rsi", "mov [rbp+var_50], rdx", "mov r15, fs:28h", "mov [rbp+var_38], r15", "mov r15, rcx", "call cs:__errno_location_ptr", "mov r14, cs:ptr", "mov r12, rax", "mov eax, [rax]", "mov [rbp+var_54], eax", "cmp ebx, 7FFFFFFEh", "ja loc_2441"], "succs": [[1, "fall"], [13, "jump"]]}, {"id": 1, "start": 20413, "end": 20423, "lines": ["mov eax, cs:dword_C078", "cmp eax, ebx", "jg short loc_5035"], "succs": [[2, "fall"], [5, "jump"]]}, {"id": 2, "start": 20423, "end": 20456, "lines": ["movsxd rdx, eax", "mov [rbp+var_40], rdx", "mov edx, ebx", "sub edx, eax", "lea rax, xmmword_C090", "add edx, 1", "movsxd rdx, edx", "cmp r14, rax", "jz loc_5110"], "succs": [[3, "fall"], [11, "jump"]]}, {"id": 3, "start": 20456, "end": 20490, "lines": ["mov rdi, r14", "lea rsi, [rbp+var_40]", "mov r8d, 10h", "mov ecx, 7FFFFFFFh", "call sub_6670", "mov cs:ptr, rax", "mov r14, rax"], "succs": [[4, "fall"]]}, {"id": 4, "start": 20490, "end": 20533, "lines": ["movsxd rdi, cs:dword_C078", "mov rdx, [rbp+var_40]", "xor esi, esi; c", "sub rdx, rdi", "shl rdi, 4", "shl rdx, 4; n", "add rdi, r14; s", "call cs:memset_ptr", "mov rax, [rbp+var_40]", "mov cs:dword_C078, eax"], "succs": [[5, "fall"]]}, {"id": 5, "start": 20533, "end": 20611, "lines": ["mov eax, [r15+4]", "shl rbx, 4", "sub rsp, 8", "mov r8d, [r15]", "add rbx, r14", "lea r14, [r15+8]", "mov rcx, [rbp+var_50]", "mov rdx, [rbp+var_48]", "or eax, 1", "mov rsi, [rbx]", "mov r13, [rbx+8]", "mov [rbp+var_58], eax", "mov r9d, eax", "push qword ptr [r15+30h]", "mov rdi, r13", "push qword ptr [r15+28h]", "push r14", "mov [rbp+var_60], rsi", "call sub_3720", "add rsp, 20h", "cmp rax, [rbp+var_60]", "jb short loc_50E8"], "succs": [[6, "fall"], [9, "jump"]]}, {"id": 6, "start": 20611, "end": 20630, "lines": ["lea rsi, [rax+1]", "lea rax, unk_C140", "mov [rbx], rsi", "cmp r13, rax", "jz short loc_50A7"], "succs": [[7, "fall"], [8, "jump"]]}, {"id": 7, "start": 20630, "end": 20647, "lines": ["mov [rbp+var_60], rsi", "mov rdi, r13; ptr", "call cs:free_ptr", "mov rsi, [rbp+var_60]"], "succs": [[8, "fall"]]}, {"id": 8, "start": 20647, "end": 20712, "lines": ["mov rdi, rsi", "mov [rbp+var_60], rsi", "call sub_6480", "sub rsp, 8", "mov r9d, [rbp+var_58]", "mov r8d, [r15]", "mov [rbx+8], rax", "mov rcx, [rbp+var_50]", "mov rdi, rax", "mov r13, rax", "mov rdx, [rbp+var_48]", "mov rsi, [rbp+var_60]", "push qword ptr [r15+30h]", "push qword ptr [r15+28h]", "push r14", "call sub_3720", "add rsp, 20h"], "succs": [[9, "fall"]]}, {"id": 9, "start": 20712, "end": 20734, "lines": ["mov eax, [rbp+var_54]", "mov [r12], eax", "mov rax, [rbp+var_38]", "sub rax, fs:28h", "jnz short loc_5141"], "succs": [[10, "fall"], [12, "jump"]]}, {"id": 10, "start": 20734, "end": 20752, "lines": ["lea rsp, [rbp-28h]", "mov rax, r13", "pop rbx", "pop r12", "pop r13", "pop r14", "pop r15", "pop rbp", "retn"], "succs": []}, {"id": 11, "start": 20752, "end": 20801, "lines": ["lea rsi, [rbp+var_40]", "mov r8d, 10h", "mov ecx, 7FFFFFFFh", "xor edi, edi", "call sub_6670", "movdqa xmm0, cs:xmmword_C090", "mov cs:ptr, rax", "mov r14, rax", "movups xmmword ptr [rax], xmm0", "jmp loc_500A"], "succs": [[4, "jump"]]}, {"id": 12, "start": 20801, "end": 20807, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}, {"id": 13, "start": 9281, "end": 9287, "lines": ["call cs:abort_ptr"], "succs": []}]}, {"name": "sub_3500", "ea": 13568, "blocks": [{"id": 0, "start": 13568, "end": 13606, "lines": ["endbr64", "push rbp", "mov esi, 2Fh ; '/'; c", "mov rbp, rsp", "push rbx", "mov rbx, rdi", "sub rsp, 8", "call cs:strrchr_ptr", "mov rdx, rax", "mov rax, rbx", "test rdx, rdx", "jz short loc_353F"], "succs": [[1, "fall"], [3, "jump"]]}, {"id": 1, "start": 13606, "end": 13622, "lines": ["lea rax, [rdx+1]", "mov rcx, rax", "sub rcx, rbx", "cmp rcx, 6", "jle short loc_353F"], "succs": [[2, "fall"], [3, "jump"]]}, {"id": 2, "start": 13622, "end": 13631, "lines": ["cmp dword ptr [rdx-6], 696C2E2Fh", "jz short loc_3560"], "succs": [[3, "fall"], [4, "jump"]]}, {"id": 3, "start": 13631, "end": 13658, "lines": ["mov cs:qword_C120, rbx", "mov cs:__progname_full, rbx", "mov rbx, [rbp+var_8]", "mov cs:__progname, rax", "leave", "retn"], "succs": []}, {"id": 4, "start": 13664, "end": 13673, "lines": ["cmp dword ptr [rdx-3], 2F736269h", "jnz short loc_353F"], "succs": [[3, "jump"], [5, "fall"]]}, {"id": 5, "start": 13673, "end": 13679, "lines": ["cmp byte ptr [rdx+1], 6Ch ; 'l'", "jnz short loc_3588"], "succs": [[6, "fall"], [9, "jump"]]}, {"id": 6, "start": 13679, "end": 13685, "lines": ["cmp byte ptr [rax+1], 74h ; 't'", "jnz short loc_3588"], "succs": [[7, "fall"], [9, "jump"]]}, {"id": 7, "start": 13685, "end": 13691, "lines": ["cmp byte ptr [rax+2], 2Dh ; '-'", "jnz short loc_3588"], "succs": [[8, "fall"], [9, "jump"]]}, {"id": 8, "start": 13691, "end": 13700, "lines": ["lea rbx, [rdx+4]", "mov rax, rbx", "jmp short loc_353F"], "succs": [[3, "jump"]]}, {"id": 9, "start": 13704, "end": 13709, "lines": ["mov rbx, rax", "jmp short loc_353F"], "succs": [[3, "jump"]]}]}, {"name": "sub_7110", "ea": 28944, "blocks": [{"id": 0, "start": 28944, "end": 28980, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "push r13", "mov r13, rsi", "xor esi, esi; locale", "push r12", "mov r12, rdx", "push rbx", "sub rsp, 8", "call cs:setlocale_ptr", "test rax, rax", "jz short loc_71A0"], "succs": [[1, "fall"], [7, "jump"]]}, {"id": 1, "start": 28980, "end": 28997, "lines": ["mov rbx, rax", "mov rdi, rax; s", "call cs:strlen_ptr", "cmp rax, r12", "jb short loc_7160"], "succs": [[2, "fall"], [5, "jump"]]}, {"id": 2, "start": 28997, "end": 29002, "lines": ["test r12, r12", "jnz short loc_7180"], "succs": [[3, "fall"], [6, "jump"]]}, {"id": 3, "start": 29002, "end": 29007, "lines": ["mov eax, 22h ; '\"'"], "succs": [[4, "fall"]]}, {"id": 4, "start": 29007, "end": 29018, "lines": ["add rsp, 8", "pop rbx", "pop r12", "pop r13", "pop rbp", "retn"], "succs": []}, {"id": 5, "start": 29024, "end": 29053, "lines": ["mov rsi, rbx; src", "mov rdi, r13; dest", "lea rdx, [rax+1]; n", "call cs:memcpy_ptr", "add rsp, 8", "xor eax, eax", "pop rbx", "pop r12", "pop r13", "pop rbp", "retn"], "succs": []}, {"id": 6, "start": 29056, "end": 29081, "lines": ["lea rdx, [r12-1]; n", "mov rsi, rbx; src", "mov rdi, r13; dest", "call cs:memcpy_ptr", "mov byte ptr [r13+r12-1], 0", "jmp short loc_714A"], "succs": [[3, "jump"]]}, {"id": 7, "start": 29088, "end": 29093, "lines": ["test r12, r12", "jz short loc_71AA"], "succs": [[8, "fall"], [9, "jump"]]}, {"id": 8, "start": 29093, "end": 29098, "lines": ["mov byte ptr [r13+0], 0"], "succs": [[9, "fall"]]}, {"id": 9, "start": 29098, "end": 29105, "lines": ["mov eax, 16h", "jmp short loc_714F"], "succs": [[4, "jump"]]}]}, {"name": "sub_2C00", "ea": 11264, "blocks": [{"id": 0, "start": 11264, "end": 11271, "lines": ["lea eax, [rdi-41h]; switch 38 cases", "cmp al, 25h", "ja short def_2C18; jumptable 0000000000002C18 default case, cases 71-96"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 11271, "end": 11291, "lines": ["lea rdx, jpt_2C18", "movzx eax, al", "movsxd rax, ds:(jpt_2C18 - 8004h)[rdx+rax*4]", "add rax, rdx", "jmp rax; switch jump"], "succs": [[2, "switch"], [3, "switch"], [4, "switch"], [5, "switch"], [6, "switch"], [7, "switch"], [8, "switch"]]}, {"id": 2, "start": 11296, "end": 11304, "lines": ["movzx edi, dil; jumptable 0000000000002C18 default case, cases 71-96", "lea eax, [rdi-30h]", "retn"], "succs": []}, {"id": 3, "start": 11312, "end": 11318, "lines": ["mov eax, 0Ch; jumptable 0000000000002C18 cases 67,99", "retn"], "succs": []}, {"id": 4, "start": 11328, "end": 11334, "lines": ["mov eax, 0Dh; jumptable 0000000000002C18 cases 68,100", "retn"], "succs": []}, {"id": 5, "start": 11344, "end": 11350, "lines": ["mov eax, 0Eh; jumptable 0000000000002C18 cases 69,101", "retn"], "succs": []}, {"id": 6, "start": 11360, "end": 11366, "lines": ["mov eax, 0Fh; jumptable 0000000000002C18 cases 70,102", "retn"], "succs": []}, {"id": 7, "start": 11376, "end": 11382, "lines": ["mov eax, 0Bh; jumptable 0000000000002C18 cases 66,98", "retn"], "succs": []}, {"id": 8, "start": 11392, "end": 11398, "lines": ["mov eax, 0Ah; jumptable 0000000000002C18 cases 65,97", "retn"], "succs": []}]}, {"name": "sub_3430", "ea": 13360, "blocks": [{"id": 0, "start": 13360, "end": 13388, "lines": ["endbr64", "push rbp", "mov rdi, cs:stdout", "mov rbp, rsp", "push r14", "push rbx", "call sub_6950", "test eax, eax", "jz short loc_3460"], "succs": [[1, "fall"], [3, "jump"]]}, {"id": 1, "start": 13388, "end": 13397, "lines": ["cmp cs:byte_C0F0, 0", "jz short loc_3476"], "succs": [[2, "fall"], [5, "jump"]]}, {"id": 2, "start": 13397, "end": 13408, "lines": ["call cs:__errno_location_ptr", "cmp dword ptr [rax], 20h ; ' '", "jnz short loc_3476"], "succs": [[3, "fall"], [5, "jump"]]}, {"id": 3, "start": 13408, "end": 13425, "lines": ["mov rdi, cs:stderr", "call sub_6950", "test eax, eax", "jnz short loc_34C6"], "succs": [[4, "fall"], [7, "jump"]]}, {"id": 4, "start": 13425, "end": 13430, "lines": ["pop rbx", "pop r14", "pop rbp", "retn"], "succs": []}, {"id": 5, "start": 13430, "end": 13470, "lines": ["lea rdi, domainname; \"gnulib\"", "mov edx, 5; category", "lea rsi, aWriteError; \"write error\"", "call cs:dcgettext_ptr", "mov rdi, cs:qword_C0F8", "mov rbx, rax", "test rdi, rdi", "jz short loc_34D2"], "succs": [[6, "fall"], [8, "jump"]]}, {"id": 6, "start": 13470, "end": 13510, "lines": ["call sub_5820", "mov r14, rax", "call cs:__errno_location_ptr", "mov r8, rbx", "mov rcx, r14", "xor edi, edi", "mov esi, [rax]", "lea rdx, aSS; \"%s: %s\"", "xor eax, eax", "call error"], "succs": [[7, "fall"]]}, {"id": 7, "start": 13510, "end": 13522, "lines": ["mov edi, cs:status; status", "call cs:_exit_ptr"], "succs": []}, {"id": 8, "start": 13522, "end": 13552, "lines": ["call cs:__errno_location_ptr", "mov rcx, rbx", "lea rdx, aSS+4; \"%s\"", "xor edi, edi", "mov esi, [rax]", "xor eax, eax", "call error", "jmp short loc_34C6"], "succs": [[7, "jump"]]}]}, {"name": "sub_61D0", "ea": 25040, "blocks": [{"id": 0, "start": 25040, "end": 25090, "lines": ["endbr64", "push rbp", "mov r10, rsi", "mov r11, rdx", "xor r9d, r9d", "mov rbp, rsp", "push rbx", "mov rbx, rcx", "sub rsp, 68h", "mov rcx, fs:28h", "mov [rbp+var_18], rcx", "mov rcx, r8", "lea r8, [rbp+var_70]", "mov rsi, r8", "jmp short loc_622C"], "succs": [[3, "jump"]]}, {"id": 1, "start": 25096, "end": 25118, "lines": ["mov edx, eax", "add eax, 8", "add rdx, [rcx+10h]", "mov [rcx], eax", "mov rax, [rdx]", "mov [rsi], rax", "test rax, rax", "jz short loc_6250"], "succs": [[2, "fall"], [6, "jump"]]}, {"id": 2, "start": 25118, "end": 25132, "lines": ["add r9, 1", "add rsi, 8", "cmp r9, 0Ah", "jz short loc_6250"], "succs": [[3, "fall"], [6, "jump"]]}, {"id": 3, "start": 25132, "end": 25139, "lines": ["mov eax, [rcx]", "cmp eax, 2Fh ; '/'", "jbe short loc_6208"], "succs": [[1, "jump"], [4, "fall"]]}, {"id": 4, "start": 25139, "end": 25162, "lines": ["mov rdx, [rcx+8]", "lea rax, [rdx+8]", "mov [rcx+8], rax", "mov rax, [rdx]", "mov [rsi], rax", "test rax, rax", "jnz short loc_621E"], "succs": [[2, "jump"], [5, "fall"]]}, {"id": 5, "start": 25162, "end": 25168, "lines": ["nop word ptr [rax+rax+00h]"], "succs": [[6, "fall"]]}, {"id": 6, "start": 25168, "end": 25197, "lines": ["mov rcx, rbx", "mov rdx, r11", "mov rsi, r10", "call sub_5CA0", "mov rax, [rbp+var_18]", "sub rax, fs:28h", "jnz short loc_6273"], "succs": [[7, "fall"], [8, "jump"]]}, {"id": 7, "start": 25197, "end": 25203, "lines": ["mov rbx, [rbp+var_8]", "leave", "retn"], "succs": []}, {"id": 8, "start": 25203, "end": 25209, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}]}, {"name": "sub_6280", "ea": 25216, "blocks": [{"id": 0, "start": 25216, "end": 25320, "lines": ["endbr64", "push rbp", "mov r10, rsi", "mov r11, rdx", "mov rbp, rsp", "push rbx", "mov rbx, rcx", "mov ecx, 20h ; ' '", "lea rsi, [rbp+arg_0]", "sub rsp, 0B8h", "mov [rbp+var_20], r8", "lea r8, [rbp+var_A0]", "mov [rbp+var_18], r9", "xor r9d, r9d", "mov rax, fs:28h", "mov [rbp+var_48], rax", "xor eax, eax", "lea rax, [rbp+arg_0]", "mov [rbp+var_B8], 20h ; ' '", "mov [rbp+var_B0], rax", "lea rax, [rbp+var_40]", "mov [rbp+var_A8], rax", "mov rax, r8", "jmp short loc_6313"], "succs": [[3, "jump"]]}, {"id": 1, "start": 25328, "end": 25349, "lines": ["mov edx, ecx", "add ecx, 8", "lea rdx, [rbp+rdx+var_40]", "mov rdx, [rdx]", "mov [rax], rdx", "test rdx, rdx", "jz short loc_6330"], "succs": [[2, "fall"], [6, "jump"]]}, {"id": 2, "start": 25349, "end": 25363, "lines": ["add r9, 1", "add rax, 8", "cmp r9, 0Ah", "jz short loc_6330"], "succs": [[3, "fall"], [6, "jump"]]}, {"id": 3, "start": 25363, "end": 25368, "lines": ["cmp ecx, 2Fh ; '/'", "jbe short loc_62F0"], "succs": [[1, "jump"], [4, "fall"]]}, {"id": 4, "start": 25368, "end": 25386, "lines": ["mov rdx, rsi", "add rsi, 8", "mov rdx, [rdx]", "mov [rax], rdx", "test rdx, rdx", "jnz short loc_6305"], "succs": [[2, "jump"], [5, "fall"]]}, {"id": 5, "start": 25386, "end": 25392, "lines": ["nop word ptr [rax+rax+00h]"], "succs": [[6, "fall"]]}, {"id": 6, "start": 25392, "end": 25421, "lines": ["mov rcx, rbx", "mov rdx, r11", "mov rsi, r10", "call sub_5CA0", "mov rax, [rbp+var_48]", "sub rax, fs:28h", "jnz short loc_6353"], "succs": [[7, "fall"], [8, "jump"]]}, {"id": 7, "start": 25421, "end": 25427, "lines": ["mov rbx, [rbp+var_8]", "leave", "retn"], "succs": []}, {"id": 8, "start": 25427, "end": 25433, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}]}, {"name": "sub_7020", "ea": 28704, "blocks": [{"id": 0, "start": 28704, "end": 28759, "lines": ["endbr64", "push rbp", "mov edx, 101h", "mov rbp, rsp", "sub rsp, 110h", "mov rsi, fs:28h", "mov [rbp+var_8], rsi", "lea rsi, [rbp+var_110]", "call sub_70E0", "test eax, eax", "mov eax, 0", "jnz short loc_7072"], "succs": [[1, "fall"], [4, "jump"]]}, {"id": 1, "start": 28759, "end": 28769, "lines": ["cmp word ptr [rbp+var_110], 43h ; 'C'", "jz short loc_7072"], "succs": [[2, "fall"], [4, "jump"]]}, {"id": 2, "start": 28769, "end": 28781, "lines": ["cmp [rbp+var_110], 49534F50h", "jz short loc_7088"], "succs": [[3, "fall"], [6, "jump"]]}, {"id": 3, "start": 28781, "end": 28786, "lines": ["mov eax, 1"], "succs": [[4, "fall"]]}, {"id": 4, "start": 28786, "end": 28801, "lines": ["mov rdx, [rbp+var_8]", "sub rdx, fs:28h", "jnz short loc_7096"], "succs": [[5, "fall"], [8, "jump"]]}, {"id": 5, "start": 28801, "end": 28803, "lines": ["leave", "retn"], "succs": []}, {"id": 6, "start": 28808, "end": 28820, "lines": ["xor eax, eax", "cmp [rbp+var_10C], 58h ; 'X'", "jnz short loc_706D"], "succs": [[3, "jump"], [7, "fall"]]}, {"id": 7, "start": 28820, "end": 28822, "lines": ["jmp short loc_7072"], "succs": [[4, "jump"]]}, {"id": 8, "start": 28822, "end": 28828, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}]}, {"name": "sub_20DD", "ea": 8413, "blocks": [{"id": 0, "start": 8413, "end": 8454, "lines": ["push rbp", "mov rbp, rsp", "push r12", "mov r12d, esi", "mov esi, 2", "push rbx", "mov ebx, edi", "mov rdi, cs:stderr", "call cs:__vfprintf_chk_ptr", "inc cs:error_message_count", "test r12d, r12d", "jz short loc_210E"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 8454, "end": 8462, "lines": ["mov edi, r12d", "call sub_2060"], "succs": [[2, "fall"]]}, {"id": 2, "start": 8462, "end": 8479, "lines": ["mov rdi, cs:stderr; _IO_FILE *", "mov rax, [rdi+28h]", "cmp rax, [rdi+30h]", "jb short loc_212C"], "succs": [[3, "fall"], [4, "jump"]]}, {"id": 3, "start": 8479, "end": 8492, "lines": ["mov esi, 0Ah; int", "call cs:__overflow_ptr", "jmp short loc_2137"], "succs": [[5, "jump"]]}, {"id": 4, "start": 8492, "end": 8503, "lines": ["lea rdx, [rax+1]", "mov [rdi+28h], rdx", "mov byte ptr [rax], 0Ah"], "succs": [[5, "fall"]]}, {"id": 5, "start": 8503, "end": 8520, "lines": ["mov rdi, cs:stderr; stream", "call cs:fflush_unlocked_ptr", "test ebx, ebx", "jz short loc_2150"], "succs": [[6, "fall"], [7, "jump"]]}, {"id": 6, "start": 8520, "end": 8528, "lines": ["mov edi, ebx; status", "call cs:exit_ptr"], "succs": []}, {"id": 7, "start": 8528, "end": 8533, "lines": ["pop rbx", "pop r12", "pop rbp", "retn"], "succs": []}]}, {"name": "sub_3640", "ea": 13888, "blocks": [{"id": 0, "start": 13888, "end": 13947, "lines": ["push rbp", "mov edx, 5; category", "mov rbp, rsp", "push r14", "push rbx", "mov rbx, rdi", "sub rsp, 30h", "mov r14, fs:28h", "mov [rbp+var_18], r14", "mov r14d, esi", "mov rsi, rdi; msgid", "lea rdi, domainname; \"gnulib\"", "call cs:dcgettext_ptr", "mov r8, rax", "cmp rbx, rax", "jz short loc_36A0"], "succs": [[1, "fall"], [3, "jump"]]}, {"id": 1, "start": 13947, "end": 13966, "lines": ["mov rax, [rbp+var_18]", "sub rax, fs:28h", "jnz loc_3712"], "succs": [[2, "fall"], [7, "jump"]]}, {"id": 2, "start": 13966, "end": 13978, "lines": ["add rsp, 30h", "mov rax, r8", "pop rbx", "pop r14", "pop rbp", "retn"], "succs": []}, {"id": 3, "start": 13984, "end": 14032, "lines": ["mov [rbp+var_38], rax", "lea rcx, [rbp+var_20]", "lea rdi, [rbp+pc32]; pc32", "mov edx, 3", "mov [rbp+var_20], 0", "lea rsi, byte_8F30; s", "call sub_69C0", "mov r8, [rbp+var_38]", "cmp rax, 3", "jz short loc_36F0"], "succs": [[4, "fall"], [5, "jump"]]}, {"id": 4, "start": 14032, "end": 14056, "lines": ["lea r8, asc_8348+2; \"'`\"", "cmp r14d, 9", "lea rax, asc_8348; \"\\\"'`\"", "cmovz r8, rax", "jmp short loc_367B"], "succs": [[1, "jump"]]}, {"id": 5, "start": 14064, "end": 14073, "lines": ["cmp [rbp+pc32], 2018h", "jnz short loc_36D0"], "succs": [[4, "jump"], [6, "fall"]]}, {"id": 6, "start": 14073, "end": 14098, "lines": ["xor eax, eax", "cmp byte ptr [r8], 27h ; '''", "lea rcx, byte_8F30", "setz al", "lea r8, [rcx+rax*4]", "jmp loc_367B"], "succs": [[1, "jump"]]}, {"id": 7, "start": 14098, "end": 14104, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}]}, {"name": "sub_5400", "ea": 21504, "blocks": [{"id": 0, "start": 21504, "end": 21539, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "push r13", "push r12", "push rbx", "sub rsp, 8", "mov eax, cs:dword_C078", "mov r13, cs:ptr", "cmp eax, 1", "jle short loc_5452"], "succs": [[1, "fall"], [3, "jump"]]}, {"id": 1, "start": 21539, "end": 21568, "lines": ["sub eax, 2", "lea rbx, [r13+18h]", "shl rax, 4", "lea r12, [r13+rax+28h]", "xchg ax, ax", "nop word ptr [rax+rax+00000000h]"], "succs": [[2, "fall"]]}, {"id": 2, "start": 21568, "end": 21586, "lines": ["mov rdi, [rbx]; ptr", "add rbx, 10h", "call cs:free_ptr", "cmp rbx, r12", "jnz short loc_5440"], "succs": [[3, "fall"], [2, "jump"]]}, {"id": 3, "start": 21586, "end": 21602, "lines": ["mov rdi, [r13+8]; ptr", "lea rbx, unk_C140", "cmp rdi, rbx", "jz short loc_547A"], "succs": [[4, "fall"], [5, "jump"]]}, {"id": 4, "start": 21602, "end": 21626, "lines": ["call cs:free_ptr", "mov qword ptr cs:xmmword_C090+8, rbx", "mov qword ptr cs:xmmword_C090, 100h"], "succs": [[5, "fall"]]}, {"id": 5, "start": 21626, "end": 21638, "lines": ["lea rbx, xmmword_C090", "cmp r13, rbx", "jz short loc_5496"], "succs": [[6, "fall"], [7, "jump"]]}, {"id": 6, "start": 21638, "end": 21654, "lines": ["mov rdi, r13; ptr", "call cs:free_ptr", "mov cs:ptr, rbx"], "succs": [[7, "fall"]]}, {"id": 7, "start": 21654, "end": 21675, "lines": ["mov cs:dword_C078, 1", "add rsp, 8", "pop rbx", "pop r12", "pop r13", "pop rbp", "retn"], "succs": []}]}, {"name": "sub_6950", "ea": 26960, "blocks": [{"id": 0, "start": 26960, "end": 27007, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "push r14", "push rbx", "sub rsp, 10h", "mov [rbp+stream], rdi", "call cs:__fpending_ptr", "mov rdi, [rbp+stream]; stream", "mov r14, rax", "mov ebx, [rdi]", "and ebx, 20h", "call cs:fclose_ptr", "test ebx, ebx", "jnz short loc_69A8"], "succs": [[1, "fall"], [5, "jump"]]}, {"id": 1, "start": 27007, "end": 27011, "lines": ["test eax, eax", "jz short loc_6999"], "succs": [[2, "fall"], [4, "jump"]]}, {"id": 2, "start": 27011, "end": 27016, "lines": ["test r14, r14", "jnz short loc_69B8"], "succs": [[3, "fall"], [7, "jump"]]}, {"id": 3, "start": 27016, "end": 27033, "lines": ["call cs:__errno_location_ptr", "cmp dword ptr [rax], 9", "setnz al", "movzx eax, al", "neg eax"], "succs": [[4, "fall"]]}, {"id": 4, "start": 27033, "end": 27042, "lines": ["add rsp, 10h", "pop rbx", "pop r14", "pop rbp", "retn"], "succs": []}, {"id": 5, "start": 27048, "end": 27052, "lines": ["test eax, eax", "jnz short loc_69B8"], "succs": [[6, "fall"], [7, "jump"]]}, {"id": 6, "start": 27052, "end": 27064, "lines": ["call cs:__errno_location_ptr", "mov dword ptr [rax], 0"], "succs": [[7, "fall"]]}, {"id": 7, "start": 27064, "end": 27071, "lines": ["mov eax, 0FFFFFFFFh", "jmp short loc_6999"], "succs": [[4, "jump"]]}]}, {"name": "sub_6600", "ea": 26112, "blocks": [{"id": 0, "start": 26112, "end": 26137, "lines": ["endbr64", "push rbp", "mov rcx, rdx", "mov rbp, rsp", "push r12", "mov r12, rsi", "push rbx", "mov rbx, [rsi]", "test rdi, rdi", "jz short loc_6648"], "succs": [[1, "fall"], [4, "jump"]]}, {"id": 1, "start": 26137, "end": 26152, "lines": ["mov rax, rbx", "shr rax, 1", "add rax, 1", "add rbx, rax", "jb short loc_6669"], "succs": [[2, "fall"], [6, "jump"]]}, {"id": 2, "start": 26152, "end": 26169, "lines": ["mov rdx, rcx", "mov rsi, rbx", "call sub_6FE0", "test rax, rax", "jz short loc_6669"], "succs": [[3, "fall"], [6, "jump"]]}, {"id": 3, "start": 26169, "end": 26178, "lines": ["mov [r12], rbx", "pop rbx", "pop r12", "pop rbp", "retn"], "succs": []}, {"id": 4, "start": 26184, "end": 26189, "lines": ["test rbx, rbx", "jnz short loc_6628"], "succs": [[2, "jump"], [5, "fall"]]}, {"id": 5, "start": 26189, "end": 26217, "lines": ["xor edx, edx", "mov eax, 80h", "div rcx", "xor edx, edx", "cmp rcx, 80h", "setnbe dl", "lea rbx, [rax+rdx]", "jmp short loc_6628"], "succs": [[2, "jump"]]}, {"id": 6, "start": 26217, "end": 26223, "lines": ["call sub_6900"], "succs": []}]}, {"name": "sub_3590", "ea": 13712, "blocks": [{"id": 0, "start": 13712, "end": 13770, "lines": ["endbr64", "push rbp", "mov edx, 5; category", "mov rbp, rsp", "push r14", "push rbx", "mov rbx, rdi", "sub rsp, 30h", "mov r14, fs:28h", "mov [rbp+var_18], r14", "mov r14, rsi", "mov rsi, rdi; msgid", "xor edi, edi; domainname", "call cs:dcgettext_ptr", "mov r8, rax", "cmp rbx, rax", "jz short loc_35E8"], "succs": [[1, "fall"], [3, "jump"]]}, {"id": 1, "start": 13770, "end": 13785, "lines": ["mov rax, [rbp+var_18]", "sub rax, fs:28h", "jnz short loc_3625"], "succs": [[2, "fall"], [5, "jump"]]}, {"id": 2, "start": 13785, "end": 13797, "lines": ["add rsp, 30h", "mov rax, r8", "pop rbx", "pop r14", "pop rbp", "retn"], "succs": []}, {"id": 3, "start": 13800, "end": 13848, "lines": ["mov [rbp+var_38], rax", "lea rcx, [rbp+p]; p", "lea rdi, [rbp+pc32]; pc32", "mov edx, 2; n", "mov qword ptr [rbp+p.__count], 0", "lea rsi, s; s", "call cs:mbrtoc32_ptr", "mov r8, [rbp+var_38]", "cmp rax, 2", "jnz short loc_35CA"], "succs": [[1, "jump"], [4, "fall"]]}, {"id": 4, "start": 13848, "end": 13861, "lines": ["cmp [rbp+pc32], 7FFh", "cmovz r8, r14", "jmp short loc_35CA"], "succs": [[1, "jump"]]}, {"id": 5, "start": 13861, "end": 13867, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}]}, {"name": "sub_59B0", "ea": 22960, "blocks": [{"id": 0, "start": 22960, "end": 23051, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "sub rsp, 40h", "movdqa xmm0, cs:xmmword_C240", "mov rax, fs:28h", "mov [rbp+var_8], rax", "mov rax, rcx", "mov rcx, cs:qword_C270", "movaps [rbp+var_40], xmm0", "movdqa xmm0, cs:xmmword_C250", "mov [rbp+var_10], rcx", "movaps [rbp+var_30], xmm0", "movdqa xmm0, cs:xmmword_C260", "mov dword ptr [rbp+var_40], 0Ah", "movaps [rbp+var_20], xmm0", "test rsi, rsi", "jz loc_246F"], "succs": [[1, "fall"], [5, "jump"]]}, {"id": 1, "start": 23051, "end": 23060, "lines": ["test rdx, rdx", "jz loc_246F"], "succs": [[2, "fall"], [5, "jump"]]}, {"id": 2, "start": 23060, "end": 23102, "lines": ["mov qword ptr [rbp+var_20+8], rsi", "lea rcx, [rbp+var_40]", "mov rsi, rax", "mov [rbp+var_10], rdx", "mov rdx, 0FFFFFFFFFFFFFFFFh", "call sub_4F70", "mov rdx, [rbp+var_8]", "sub rdx, fs:28h", "jnz short loc_5A40"], "succs": [[3, "fall"], [4, "jump"]]}, {"id": 3, "start": 23102, "end": 23104, "lines": ["leave", "retn"], "succs": []}, {"id": 4, "start": 23104, "end": 23110, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}, {"id": 5, "start": 9327, "end": 9333, "lines": ["call cs:abort_ptr"], "succs": []}]}, {"name": "sub_5A50", "ea": 23120, "blocks": [{"id": 0, "start": 23120, "end": 23211, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "sub rsp, 40h", "movdqa xmm0, cs:xmmword_C240", "mov rax, fs:28h", "mov [rbp+var_8], rax", "mov rax, rcx", "mov rcx, cs:qword_C270", "movaps [rbp+var_40], xmm0", "movdqa xmm0, cs:xmmword_C250", "mov [rbp+var_10], rcx", "movaps [rbp+var_30], xmm0", "movdqa xmm0, cs:xmmword_C260", "mov dword ptr [rbp+var_40], 0Ah", "movaps [rbp+var_20], xmm0", "test rsi, rsi", "jz loc_2475"], "succs": [[1, "fall"], [5, "jump"]]}, {"id": 1, "start": 23211, "end": 23220, "lines": ["test rdx, rdx", "jz loc_2475"], "succs": [[2, "fall"], [5, "jump"]]}, {"id": 2, "start": 23220, "end": 23258, "lines": ["mov qword ptr [rbp+var_20+8], rsi", "lea rcx, [rbp+var_40]", "mov rsi, rax", "mov [rbp+var_10], rdx", "mov rdx, r8", "call sub_4F70", "mov rdx, [rbp+var_8]", "sub rdx, fs:28h", "jnz short loc_5ADC"], "succs": [[3, "fall"], [4, "jump"]]}, {"id": 3, "start": 23258, "end": 23260, "lines": ["leave", "retn"], "succs": []}, {"id": 4, "start": 23260, "end": 23266, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}, {"id": 5, "start": 9333, "end": 9339, "lines": ["call cs:abort_ptr"], "succs": []}]}, {"name": "sub_5AF0", "ea": 23280, "blocks": [{"id": 0, "start": 23280, "end": 23371, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "sub rsp, 40h", "movdqa xmm0, cs:xmmword_C240", "mov rax, fs:28h", "mov [rbp+var_8], rax", "mov rax, rdx", "mov rdx, cs:qword_C270", "movaps [rbp+var_40], xmm0", "movdqa xmm0, cs:xmmword_C250", "mov [rbp+var_10], rdx", "movaps [rbp+var_30], xmm0", "movdqa xmm0, cs:xmmword_C260", "mov dword ptr [rbp+var_40], 0Ah", "movaps [rbp+var_20], xmm0", "test rdi, rdi", "jz loc_247B"], "succs": [[1, "fall"], [5, "jump"]]}, {"id": 1, "start": 23371, "end": 23380, "lines": ["test rsi, rsi", "jz loc_247B"], "succs": [[2, "fall"], [5, "jump"]]}, {"id": 2, "start": 23380, "end": 23424, "lines": ["mov qword ptr [rbp+var_20+8], rdi", "mov rdx, 0FFFFFFFFFFFFFFFFh", "xor edi, edi", "lea rcx, [rbp+var_40]", "mov [rbp+var_10], rsi", "mov rsi, rax", "call sub_4F70", "mov rdx, [rbp+var_8]", "sub rdx, fs:28h", "jnz short loc_5B82"], "succs": [[3, "fall"], [4, "jump"]]}, {"id": 3, "start": 23424, "end": 23426, "lines": ["leave", "retn"], "succs": []}, {"id": 4, "start": 23426, "end": 23432, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}, {"id": 5, "start": 9339, "end": 9345, "lines": ["call cs:abort_ptr"], "succs": []}]}, {"name": "sub_5B90", "ea": 23440, "blocks": [{"id": 0, "start": 23440, "end": 23534, "lines": ["endbr64", "push rbp", "mov rax, rdx", "mov rbp, rsp", "sub rsp, 40h", "movdqa xmm0, cs:xmmword_C240", "mov rdx, fs:28h", "mov [rbp+var_8], rdx", "mov rdx, rcx", "mov rcx, cs:qword_C270", "movaps [rbp+var_40], xmm0", "movdqa xmm0, cs:xmmword_C250", "mov [rbp+var_10], rcx", "movaps [rbp+var_30], xmm0", "movdqa xmm0, cs:xmmword_C260", "mov dword ptr [rbp+var_40], 0Ah", "movaps [rbp+var_20], xmm0", "test rdi, rdi", "jz loc_2481"], "succs": [[1, "fall"], [5, "jump"]]}, {"id": 1, "start": 23534, "end": 23543, "lines": ["test rsi, rsi", "jz loc_2481"], "succs": [[2, "fall"], [5, "jump"]]}, {"id": 2, "start": 23543, "end": 23580, "lines": ["mov qword ptr [rbp+var_20+8], rdi", "lea rcx, [rbp+var_40]", "xor edi, edi", "mov [rbp+var_10], rsi", "mov rsi, rax", "call sub_4F70", "mov rdx, [rbp+var_8]", "sub rdx, fs:28h", "jnz short loc_5C1E"], "succs": [[3, "fall"], [4, "jump"]]}, {"id": 3, "start": 23580, "end": 23582, "lines": ["leave", "retn"], "succs": []}, {"id": 4, "start": 23582, "end": 23588, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}, {"id": 5, "start": 9345, "end": 9351, "lines": ["call cs:abort_ptr"], "succs": []}]}, {"name": "sub_65A0", "ea": 26016, "blocks": [{"id": 0, "start": 26016, "end": 26038, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "push r12", "mov r12, rsi", "push rbx", "mov rbx, [rsi]", "test rdi, rdi", "jz short loc_65E8"], "succs": [[1, "fall"], [4, "jump"]]}, {"id": 1, "start": 26038, "end": 26053, "lines": ["mov rax, rbx", "shr rax, 1", "add rax, 1", "add rbx, rax", "jb short loc_65F6"], "succs": [[2, "fall"], [5, "jump"]]}, {"id": 2, "start": 26053, "end": 26072, "lines": ["mov edx, 1", "mov rsi, rbx", "call sub_6FE0", "test rax, rax", "jz short loc_65F6"], "succs": [[3, "fall"], [5, "jump"]]}, {"id": 3, "start": 26072, "end": 26081, "lines": ["mov [r12], rbx", "pop rbx", "pop r12", "pop rbp", "retn"], "succs": []}, {"id": 4, "start": 26088, "end": 26102, "lines": ["test rbx, rbx", "mov eax, 80h", "cmovz rbx, rax", "jmp short loc_65C5"], "succs": [[2, "jump"]]}, {"id": 5, "start": 26102, "end": 26108, "lines": ["call sub_6900"], "succs": []}]}, {"name": "sub_2060", "ea": 8288, "blocks": [{"id": 0, "start": 8288, "end": 8335, "lines": ["push rbp", "mov edx, 400h; buflen", "mov rbp, rsp", "sub rsp, 410h", "mov rsi, fs:28h", "mov [rbp+var_8], rsi", "lea rsi, [rbp+buf]; buf", "call cs:strerror_r_ptr", "test rax, rax", "jnz short loc_20A8"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 8335, "end": 8360, "lines": ["mov edx, 5; category", "lea rsi, msgid; \"Unknown system error\"", "lea rdi, domainname; \"gnulib\"", "call cs:dcgettext_ptr"], "succs": [[2, "fall"]]}, {"id": 2, "start": 8360, "end": 8405, "lines": ["mov rcx, rax", "mov rdi, cs:stderr", "xor eax, eax", "lea rdx, aSS+2; \": %s\"", "mov esi, 2", "call cs:__fprintf_chk_ptr", "mov rax, [rbp+var_8]", "sub rax, fs:28h", "jz short locret_20DB"], "succs": [[3, "fall"], [4, "jump"]]}, {"id": 3, "start": 8405, "end": 8411, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}, {"id": 4, "start": 8411, "end": 8413, "lines": ["leave", "retn"], "succs": []}]}, {"name": "sub_217F", "ea": 8575, "blocks": [{"id": 0, "start": 8575, "end": 8618, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "push r14", "mov r14, rcx", "push r13", "mov r13, rdx", "push r12", "mov r12d, esi", "push rbx", "mov ebx, edi", "call sub_2155", "mov rax, cs:error_print_progname", "test rax, rax", "jz short loc_21AE"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 8618, "end": 8622, "lines": ["call rax ; error_print_progname", "jmp short loc_21D2"], "succs": [[3, "jump"]]}, {"id": 2, "start": 8622, "end": 8658, "lines": ["call sub_34F0", "mov rdi, cs:stderr", "mov esi, 2", "lea rdx, aS_0; \"%s: \"", "mov rcx, rax", "xor eax, eax", "call cs:__fprintf_chk_ptr"], "succs": [[3, "fall"]]}, {"id": 3, "start": 8658, "end": 8682, "lines": ["mov rcx, r14", "mov rdx, r13", "mov esi, r12d", "mov edi, ebx; status", "pop rbx", "pop r12", "pop r13", "pop r14", "pop rbp", "jmp sub_20DD"], "succs": [[4, "jump"]]}, {"id": 4, "start": 8413, "end": 8413, "lines": [], "succs": []}]}, {"name": "error", "ea": 8682, "blocks": [{"id": 0, "start": 8682, "end": 8722, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "sub rsp, 0D0h", "mov [rbp+var_98], rcx", "mov [rbp+var_90], r8", "mov [rbp+var_88], r9", "test al, al", "jz short loc_2232"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 8722, "end": 8754, "lines": ["movaps [rbp+var_80], xmm0", "movaps [rbp+var_70], xmm1", "movaps [rbp+var_60], xmm2", "movaps [rbp+var_50], xmm3", "movaps [rbp+var_40], xmm4", "movaps [rbp+var_30], xmm5", "movaps [rbp+var_20], xmm6", "movaps [rbp+var_10], xmm7"], "succs": [[2, "fall"]]}, {"id": 2, "start": 8754, "end": 8847, "lines": ["mov rax, fs:28h", "mov [rbp+var_B8], rax", "xor eax, eax", "lea rax, [rbp+arg_0]", "lea rcx, [rbp+var_D0]", "mov [rbp+var_D0], 18h", "mov [rbp+var_C8], rax", "lea rax, [rbp+var_B0]", "mov [rbp+var_C0], rax", "mov [rbp+var_CC], 30h ; '0'", "call sub_217F", "mov rax, [rbp+var_B8]", "sub rax, fs:28h", "jz short locret_2295"], "succs": [[3, "fall"], [4, "jump"]]}, {"id": 3, "start": 8847, "end": 8853, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}, {"id": 4, "start": 8853, "end": 8855, "lines": ["leave", "retn"], "succs": []}]}, {"name": "error_at_line", "ea": 9116, "blocks": [{"id": 0, "start": 9116, "end": 9142, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "sub rsp, 0D0h", "mov [rbp+var_88], r9", "test al, al", "jz short loc_23D6"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 9142, "end": 9174, "lines": ["movaps [rbp+var_80], xmm0", "movaps [rbp+var_70], xmm1", "movaps [rbp+var_60], xmm2", "movaps [rbp+var_50], xmm3", "movaps [rbp+var_40], xmm4", "movaps [rbp+var_30], xmm5", "movaps [rbp+var_20], xmm6", "movaps [rbp+var_10], xmm7"], "succs": [[2, "fall"]]}, {"id": 2, "start": 9174, "end": 9267, "lines": ["mov rax, fs:28h", "mov [rbp+var_B8], rax", "xor eax, eax", "lea rax, [rbp+arg_0]", "lea r9, [rbp+var_D0]", "mov [rbp+var_D0], 28h ; '('", "mov [rbp+var_C8], rax", "lea rax, [rbp+var_B0]", "mov [rbp+var_C0], rax", "mov [rbp+var_CC], 30h ; '0'", "call sub_2297", "mov rax, [rbp+var_B8]", "sub rax, fs:28h", "jz short locret_2439"], "succs": [[3, "fall"], [4, "jump"]]}, {"id": 3, "start": 9267, "end": 9273, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}, {"id": 4, "start": 9273, "end": 9275, "lines": ["leave", "retn"], "succs": []}]}, {"name": "sub_2B10", "ea": 11024, "blocks": [{"id": 0, "start": 11024, "end": 11043, "lines": ["lea rdi, unk_C0A8", "lea rax, unk_C0A8", "cmp rax, rdi", "jz short locret_2B38"], "succs": [[1, "fall"], [3, "jump"]]}, {"id": 1, "start": 11043, "end": 11055, "lines": ["mov rax, cs:_ITM_deregisterTMCloneTable_ptr", "test rax, rax", "jz short locret_2B38"], "succs": [[2, "fall"], [3, "jump"]]}, {"id": 2, "start": 11055, "end": 11057, "lines": ["jmp rax"], "succs": [[4, "jump"]]}, {"id": 3, "start": 11064, "end": 11065, "lines": ["retn"], "succs": []}, {"id": 4, "start": 50168, "end": 50168, "lines": [], "succs": []}]}, {"name": "sub_2B40", "ea": 11072, "blocks": [{"id": 0, "start": 11072, "end": 11108, "lines": ["lea rdi, unk_C0A8", "lea rsi, unk_C0A8", "sub rsi, rdi", "mov rax, rsi", "shr rsi, 3Fh", "sar rax, 3", "add rsi, rax", "sar rsi, 1", "jz short locret_2B78"], "succs": [[1, "fall"], [3, "jump"]]}, {"id": 1, "start": 11108, "end": 11120, "lines": ["mov rax, cs:_ITM_registerTMCloneTable_ptr", "test rax, rax", "jz short locret_2B78"], "succs": [[2, "fall"], [3, "jump"]]}, {"id": 2, "start": 11120, "end": 11122, "lines": ["jmp rax"], "succs": [[4, "jump"]]}, {"id": 3, "start": 11128, "end": 11129, "lines": ["retn"], "succs": []}, {"id": 4, "start": 50184, "end": 50184, "lines": [], "succs": []}]}, {"name": "sub_2B80", "ea": 11136, "blocks": [{"id": 0, "start": 11136, "end": 11149, "lines": ["endbr64", "cmp cs:byte_C0E8, 0", "jnz short locret_2BC0"], "succs": [[1, "fall"], [4, "jump"]]}, {"id": 1, "start": 11149, "end": 11163, "lines": ["push rbp", "cmp cs:__cxa_finalize_ptr, 0", "mov rbp, rsp", "jz short loc_2BA8"], "succs": [[2, "fall"], [3, "jump"]]}, {"id": 2, "start": 11163, "end": 11176, "lines": ["mov rdi, cs:lpdso_handle; void *", "call cs:__cxa_finalize_ptr"], "succs": [[3, "fall"]]}, {"id": 3, "start": 11176, "end": 11190, "lines": ["call sub_2B10", "mov cs:byte_C0E8, 1", "pop rbp", "retn"], "succs": []}, {"id": 4, "start": 11200, "end": 11201, "lines": ["retn"], "succs": []}]}, {"name": "sub_5520", "ea": 21792, "blocks": [{"id": 0, "start": 21792, "end": 21829, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "sub rsp, 40h", "mov rax, fs:28h", "mov [rbp+var_8], rax", "mov rax, rdx", "cmp esi, 0Ah", "jz loc_2451"], "succs": [[1, "fall"], [4, "jump"]]}, {"id": 1, "start": 21829, "end": 21893, "lines": ["pxor xmm0, xmm0", "mov [rbp+var_40], esi", "lea rcx, [rbp+var_40]", "mov rdx, 0FFFFFFFFFFFFFFFFh", "movups [rbp+var_38], xmm0", "mov rsi, rax", "movups [rbp+var_28], xmm0", "pxor xmm0, xmm0", "mov [rbp+var_3C], 0", "movups [rbp+var_18], xmm0", "call sub_4F70", "mov rdx, [rbp+var_8]", "sub rdx, fs:28h", "jnz short loc_5587"], "succs": [[2, "fall"], [3, "jump"]]}, {"id": 2, "start": 21893, "end": 21895, "lines": ["leave", "retn"], "succs": []}, {"id": 3, "start": 21895, "end": 21901, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}, {"id": 4, "start": 9297, "end": 9303, "lines": ["call cs:abort_ptr"], "succs": []}]}, {"name": "sub_5590", "ea": 21904, "blocks": [{"id": 0, "start": 21904, "end": 21944, "lines": ["endbr64", "push rbp", "mov rax, rdx", "mov rbp, rsp", "sub rsp, 40h", "mov rdx, fs:28h", "mov [rbp+var_8], rdx", "mov rdx, rcx", "cmp esi, 0Ah", "jz loc_2457"], "succs": [[1, "fall"], [4, "jump"]]}, {"id": 1, "start": 21944, "end": 22001, "lines": ["pxor xmm0, xmm0", "mov [rbp+var_40], esi", "lea rcx, [rbp+var_40]", "mov rsi, rax", "movups [rbp+var_38], xmm0", "movups [rbp+var_28], xmm0", "pxor xmm0, xmm0", "mov [rbp+var_3C], 0", "movups [rbp+var_18], xmm0", "call sub_4F70", "mov rdx, [rbp+var_8]", "sub rdx, fs:28h", "jnz short loc_55F3"], "succs": [[2, "fall"], [3, "jump"]]}, {"id": 2, "start": 22001, "end": 22003, "lines": ["leave", "retn"], "succs": []}, {"id": 3, "start": 22003, "end": 22009, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}, {"id": 4, "start": 9303, "end": 9309, "lines": ["call cs:abort_ptr"], "succs": []}]}, {"name": "sub_5600", "ea": 22016, "blocks": [{"id": 0, "start": 22016, "end": 22052, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "sub rsp, 40h", "mov rax, fs:28h", "mov [rbp+var_8], rax", "xor eax, eax", "cmp edi, 0Ah", "jz loc_245D"], "succs": [[1, "fall"], [4, "jump"]]}, {"id": 1, "start": 22052, "end": 22115, "lines": ["pxor xmm0, xmm0", "mov [rbp+var_40], edi", "lea rcx, [rbp+var_40]", "xor edi, edi", "movups [rbp+var_38], xmm0", "mov rdx, 0FFFFFFFFFFFFFFFFh", "movups [rbp+var_28], xmm0", "pxor xmm0, xmm0", "mov [rbp+var_3C], 0", "movups [rbp+var_18], xmm0", "call sub_4F70", "mov rdx, [rbp+var_8]", "sub rdx, fs:28h", "jnz short loc_5665"], "succs": [[2, "fall"], [3, "jump"]]}, {"id": 2, "start": 22115, "end": 22117, "lines": ["leave", "retn"], "succs": []}, {"id": 3, "start": 22117, "end": 22123, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}, {"id": 4, "start": 9309, "end": 9315, "lines": ["call cs:abort_ptr"], "succs": []}]}, {"name": "sub_5670", "ea": 22128, "blocks": [{"id": 0, "start": 22128, "end": 22164, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "sub rsp, 40h", "mov rax, fs:28h", "mov [rbp+var_8], rax", "xor eax, eax", "cmp edi, 0Ah", "jz loc_2463"], "succs": [[1, "fall"], [4, "jump"]]}, {"id": 1, "start": 22164, "end": 22220, "lines": ["pxor xmm0, xmm0", "mov [rbp+var_40], edi", "xor edi, edi", "lea rcx, [rbp+var_40]", "movups [rbp+var_38], xmm0", "movups [rbp+var_28], xmm0", "pxor xmm0, xmm0", "mov [rbp+var_3C], 0", "movups [rbp+var_18], xmm0", "call sub_4F70", "mov rdx, [rbp+var_8]", "sub rdx, fs:28h", "jnz short loc_56CE"], "succs": [[2, "fall"], [3, "jump"]]}, {"id": 2, "start": 22220, "end": 22222, "lines": ["leave", "retn"], "succs": []}, {"id": 3, "start": 22222, "end": 22228, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}, {"id": 4, "start": 9315, "end": 9321, "lines": ["call cs:abort_ptr"], "succs": []}]}, {"name": "sub_5930", "ea": 22832, "blocks": [{"id": 0, "start": 22832, "end": 22869, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "sub rsp, 40h", "mov rax, fs:28h", "mov [rbp+var_8], rax", "mov rax, rdx", "cmp esi, 0Ah", "jz loc_2469"], "succs": [[1, "fall"], [4, "jump"]]}, {"id": 1, "start": 22869, "end": 22941, "lines": ["pxor xmm0, xmm0", "mov [rbp+var_40], esi", "lea rcx, [rbp+var_40]", "mov rdx, 0FFFFFFFFFFFFFFFFh", "movups [rbp+var_18], xmm0", "movdqa xmm0, cs:xmmword_8F70", "mov rsi, rax", "mov [rbp+var_3C], 0", "movups [rbp+var_38], xmm0", "pxor xmm0, xmm0", "movups [rbp+var_28], xmm0", "call sub_4F70", "mov rdx, [rbp+var_8]", "sub rdx, fs:28h", "jnz short loc_599F"], "succs": [[2, "fall"], [3, "jump"]]}, {"id": 2, "start": 22941, "end": 22943, "lines": ["leave", "retn"], "succs": []}, {"id": 3, "start": 22943, "end": 22949, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}, {"id": 4, "start": 9321, "end": 9327, "lines": ["call cs:abort_ptr"], "succs": []}]}, {"name": "sub_2155", "ea": 8533, "blocks": [{"id": 0, "start": 8533, "end": 8559, "lines": ["push rbp", "xor eax, eax", "mov esi, 3; cmd", "mov edi, 1; fd", "mov rbp, rsp", "call cs:fcntl_ptr", "test eax, eax", "js short loc_217D"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 8559, "end": 8573, "lines": ["mov rdi, cs:stdout; stream", "pop rbp", "jmp cs:fflush_unlocked_ptr"], "succs": [[3, "jump"]]}, {"id": 2, "start": 8573, "end": 8575, "lines": ["pop rbp", "retn"], "succs": []}, {"id": 3, "start": 50128, "end": 50128, "lines": [], "succs": []}]}, {"name": "sub_67E0", "ea": 26592, "blocks": [{"id": 0, "start": 26592, "end": 26623, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "push r12", "mov r12, rdi", "mov rdi, rsi; size", "push rbx", "mov rbx, rsi", "call cs:malloc_ptr", "test rax, rax", "jz short loc_6815"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 26623, "end": 26645, "lines": ["mov rcx, rbx", "mov rdx, rbx", "mov rsi, r12", "pop rbx", "mov rdi, rax", "pop r12", "pop rbp", "jmp cs:__memcpy_chk_ptr"], "succs": [[3, "jump"]]}, {"id": 2, "start": 26645, "end": 26651, "lines": ["call sub_6900"], "succs": []}, {"id": 3, "start": 50032, "end": 50032, "lines": [], "succs": []}]}, {"name": "sub_6820", "ea": 26656, "blocks": [{"id": 0, "start": 26656, "end": 26687, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "push r12", "mov r12, rdi", "mov rdi, rsi; size", "push rbx", "mov rbx, rsi", "call cs:malloc_ptr", "test rax, rax", "jz short loc_6855"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 26687, "end": 26709, "lines": ["mov rcx, rbx", "mov rdx, rbx", "mov rsi, r12", "pop rbx", "mov rdi, rax", "pop r12", "pop rbp", "jmp cs:__memcpy_chk_ptr"], "succs": [[3, "jump"]]}, {"id": 2, "start": 26709, "end": 26715, "lines": ["call sub_6900"], "succs": []}, {"id": 3, "start": 50032, "end": 50032, "lines": [], "succs": []}]}, {"name": "sub_6860", "ea": 26720, "blocks": [{"id": 0, "start": 26720, "end": 26758, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "push r13", "mov r13, rdi", "lea rdi, [rsi+1]; size", "push r12", "push rbx", "mov rbx, rsi", "sub rsp, 8", "call cs:malloc_ptr", "test rax, rax", "jz short loc_68A7"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 26758, "end": 26791, "lines": ["mov byte ptr [rax+rbx], 0", "add rsp, 8", "lea rcx, [rbx+1]", "mov rdx, rbx", "mov rsi, r13", "pop rbx", "mov rdi, rax", "pop r12", "pop r13", "pop rbp", "jmp cs:__memcpy_chk_ptr"], "succs": [[3, "jump"]]}, {"id": 2, "start": 26791, "end": 26797, "lines": ["call sub_6900"], "succs": []}, {"id": 3, "start": 50032, "end": 50032, "lines": [], "succs": []}]}, {"name": "sub_68B0", "ea": 26800, "blocks": [{"id": 0, "start": 26800, "end": 26838, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "push r12", "mov r12, rdi", "push rbx", "call cs:strlen_ptr", "lea rbx, [rax+1]", "mov rdi, rbx; size", "call cs:malloc_ptr", "test rax, rax", "jz short loc_68EC"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 26838, "end": 26860, "lines": ["mov rcx, rbx", "mov rdx, rbx", "mov rsi, r12", "pop rbx", "mov rdi, rax", "pop r12", "pop rbp", "jmp cs:__memcpy_chk_ptr"], "succs": [[3, "jump"]]}, {"id": 2, "start": 26860, "end": 26866, "lines": ["call sub_6900"], "succs": []}, {"id": 3, "start": 50032, "end": 50032, "lines": [], "succs": []}]}, {"name": "sub_6FE0", "ea": 28640, "blocks": [{"id": 0, "start": 28640, "end": 28652, "lines": ["endbr64", "mov rax, rsi", "mul rdx", "jo short loc_7001"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 28652, "end": 28673, "lines": ["mov rsi, rax", "test rax, rax", "mov eax, 1", "cmovz rsi, rax; size", "jmp cs:realloc_ptr"], "succs": [[3, "jump"]]}, {"id": 2, "start": 28673, "end": 28693, "lines": ["push rbp", "mov rbp, rsp", "call cs:__errno_location_ptr", "mov dword ptr [rax], 0Ch", "xor eax, eax", "pop rbp", "retn"], "succs": []}, {"id": 3, "start": 50080, "end": 50080, "lines": [], "succs": []}]}, {"name": ".init_proc", "ea": 8192, "blocks": [{"id": 0, "start": 8192, "end": 8212, "lines": ["endbr64", "sub rsp, 8", "mov rax, cs:__gmon_start___ptr", "test rax, rax", "jz short loc_2016"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 8212, "end": 8214, "lines": ["call rax ; __gmon_start__"], "succs": [[2, "fall"]]}, {"id": 2, "start": 8214, "end": 8219, "lines": ["add rsp, 8", "retn"], "succs": []}]}, {"name": "sub_5300", "ea": 21248, "blocks": [{"id": 0, "start": 21248, "end": 21455, "lines": ["endbr64", "push rbp", "lea rax, xmmword_C240", "mov rbp, rsp", "push r15", "mov r15, rsi", "push r14", "mov r14, rdi", "push r13", "push r12", "mov r12, rdx", "push rbx", "sub rsp, 28h", "test rcx, rcx", "cmovnz rax, rcx", "mov rbx, rax", "call cs:__errno_location_ptr", "xor r9d, r9d", "test r12, r12", "lea r10, [rbx+8]", "mov r13, rax", "mov eax, [rax]", "setz r9b", "sub rsp, 8", "or r9d, [rbx+4]", "mov r8d, [rbx]", "mov rcx, r15", "mov rdx, r14", "mov [rbp+var_34], eax", "xor esi, esi", "xor edi, edi", "push qword ptr [rbx+30h]", "push qword ptr [rbx+28h]", "push r10", "mov [rbp+var_48], r10", "mov [rbp+var_38], r9d", "call sub_3720", "add rsp, 20h", "lea rsi, [rax+1]", "mov [rbp+var_50], rax", "mov rdi, rsi", "mov [rbp+var_40], rsi", "call sub_6480", "mov r10, [rbp+var_48]", "sub rsp, 8", "mov r8d, [rbx]", "push qword ptr [rbx+30h]", "mov rsi, [rbp+var_40]", "mov rdi, rax", "mov rcx, r15", "mov r9d, [rbp+var_38]", "push qword ptr [rbx+28h]", "mov rdx, r14", "push r10", "mov [rbp+var_40], rax", "call sub_3720", "mov eax, [rbp+var_34]", "add rsp, 20h", "test r12, r12", "mov rdi, [rbp+var_40]", "mov [r13+0], eax", "jz short loc_53D7"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 21455, "end": 21463, "lines": ["mov r11, [rbp+var_50]", "mov [r12], r11"], "succs": [[2, "fall"]]}, {"id": 2, "start": 21463, "end": 21481, "lines": ["lea rsp, [rbp-28h]", "mov rax, rdi", "pop rbx", "pop r12", "pop r13", "pop r14", "pop r15", "pop rbp", "retn"], "succs": []}]}, {"name": "sub_56E0", "ea": 22240, "blocks": [{"id": 0, "start": 22240, "end": 22381, "lines": ["endbr64", "push rbp", "mov ecx, edx", "and ecx, 1Fh", "mov rbp, rsp", "sub rsp, 40h", "movdqa xmm0, cs:xmmword_C240", "mov rax, cs:qword_C270", "mov r8, fs:28h", "mov [rbp+var_8], r8", "mov r8, rsi", "mov [rbp+var_10], rax", "mov eax, edx", "movaps [rbp+var_40], xmm0", "movdqa xmm0, cs:xmmword_C250", "shr al, 5", "movzx eax, al", "movaps [rbp+var_30], xmm0", "movdqa xmm0, cs:xmmword_C260", "lea rsi, [rbp+rax*4+var_40+8]", "xor eax, eax", "movaps [rbp+var_20], xmm0", "mov edx, [rsi]", "bt edx, ecx", "setnb al", "shl eax, cl", "lea rcx, [rbp+var_40]", "xor eax, edx", "mov rdx, r8", "mov [rsi], eax", "mov rsi, rdi", "xor edi, edi", "call sub_4F70", "mov rdx, [rbp+var_8]", "sub rdx, fs:28h", "jnz short loc_576F"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 22381, "end": 22383, "lines": ["leave", "retn"], "succs": []}, {"id": 2, "start": 22383, "end": 22389, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}]}, {"name": "sub_5780", "ea": 22400, "blocks": [{"id": 0, "start": 22400, "end": 22544, "lines": ["endbr64", "push rbp", "mov ecx, esi", "and ecx, 1Fh", "mov rbp, rsp", "sub rsp, 40h", "movdqa xmm0, cs:xmmword_C240", "mov rax, fs:28h", "mov [rbp+var_8], rax", "xor eax, eax", "mov rax, cs:qword_C270", "movaps [rbp+var_40], xmm0", "movdqa xmm0, cs:xmmword_C250", "mov [rbp+var_10], rax", "mov eax, esi", "shr al, 5", "movaps [rbp+var_30], xmm0", "movdqa xmm0, cs:xmmword_C260", "movzx eax, al", "lea rdx, [rbp+rax*4+var_40+8]", "movaps [rbp+var_20], xmm0", "xor eax, eax", "mov esi, [rdx]", "bt esi, ecx", "setnb al", "shl eax, cl", "lea rcx, [rbp+var_40]", "xor eax, esi", "mov rsi, rdi", "xor edi, edi", "mov [rdx], eax", "mov rdx, 0FFFFFFFFFFFFFFFFh", "call sub_4F70", "mov rdx, [rbp+var_8]", "sub rdx, fs:28h", "jnz short loc_5812"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 22544, "end": 22546, "lines": ["leave", "retn"], "succs": []}, {"id": 2, "start": 22546, "end": 22552, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}]}, {"name": "sub_5820", "ea": 22560, "blocks": [{"id": 0, "start": 22560, "end": 22682, "lines": ["endbr64", "push rbp", "mov rdx, 0FFFFFFFFFFFFFFFFh", "mov rbp, rsp", "sub rsp, 40h", "movdqa xmm0, cs:xmmword_C240", "mov rax, cs:qword_C270", "mov rsi, fs:28h", "mov [rbp+var_8], rsi", "mov rsi, rdi", "lea rcx, [rbp+var_40]", "xor edi, edi", "movaps [rbp+var_40], xmm0", "movdqa xmm0, cs:xmmword_C250", "mov [rbp+var_10], rax", "mov eax, dword ptr cs:xmmword_C240+0Ch", "movaps [rbp+var_30], xmm0", "movdqa xmm0, cs:xmmword_C260", "or eax, 4000000h", "mov dword ptr [rbp+var_40+0Ch], eax", "movaps [rbp+var_20], xmm0", "call sub_4F70", "mov rdx, [rbp+var_8]", "sub rdx, fs:28h", "jnz short loc_589C"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 22682, "end": 22684, "lines": ["leave", "retn"], "succs": []}, {"id": 2, "start": 22684, "end": 22690, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}]}, {"name": "sub_58B0", "ea": 22704, "blocks": [{"id": 0, "start": 22704, "end": 22822, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "sub rsp, 40h", "movdqa xmm0, cs:xmmword_C240", "mov rax, cs:qword_C270", "mov rdx, fs:28h", "mov [rbp+var_8], rdx", "mov rdx, rsi", "lea rcx, [rbp+var_40]", "mov rsi, rdi", "xor edi, edi", "movaps [rbp+var_40], xmm0", "movdqa xmm0, cs:xmmword_C250", "mov [rbp+var_10], rax", "mov eax, dword ptr cs:xmmword_C240+0Ch", "movaps [rbp+var_30], xmm0", "movdqa xmm0, cs:xmmword_C260", "or eax, 4000000h", "mov dword ptr [rbp+var_40+0Ch], eax", "movaps [rbp+var_20], xmm0", "call sub_4F70", "mov rdx, [rbp+var_8]", "sub rdx, fs:28h", "jnz short loc_5928"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 22822, "end": 22824, "lines": ["leave", "retn"], "succs": []}, {"id": 2, "start": 22824, "end": 22830, "lines": ["call cs:__stack_chk_fail_ptr"], "succs": []}]}, {"name": "sub_6420", "ea": 25632, "blocks": [{"id": 0, "start": 25632, "end": 25651, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "call sub_6FE0", "test rax, rax", "jz short loc_6435"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 25651, "end": 25653, "lines": ["pop rbp", "retn"], "succs": []}, {"id": 2, "start": 25653, "end": 25659, "lines": ["call sub_6900"], "succs": []}]}, {"name": "sub_6440", "ea": 25664, "blocks": [{"id": 0, "start": 25664, "end": 25683, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "call cs:malloc_ptr", "test rax, rax", "jz short loc_6455"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 25683, "end": 25685, "lines": ["pop rbp", "retn"], "succs": []}, {"id": 2, "start": 25685, "end": 25691, "lines": ["call sub_6900"], "succs": []}]}, {"name": "sub_6460", "ea": 25696, "blocks": [{"id": 0, "start": 25696, "end": 25715, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "call cs:malloc_ptr", "test rax, rax", "jz short loc_6475"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 25715, "end": 25717, "lines": ["pop rbp", "retn"], "succs": []}, {"id": 2, "start": 25717, "end": 25723, "lines": ["call sub_6900"], "succs": []}]}, {"name": "sub_6480", "ea": 25728, "blocks": [{"id": 0, "start": 25728, "end": 25747, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "call cs:malloc_ptr", "test rax, rax", "jz short loc_6495"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 25747, "end": 25749, "lines": ["pop rbp", "retn"], "succs": []}, {"id": 2, "start": 25749, "end": 25755, "lines": ["call sub_6900"], "succs": []}]}, {"name": "sub_64A0", "ea": 25760, "blocks": [{"id": 0, "start": 25760, "end": 25791, "lines": ["endbr64", "push rbp", "test rsi, rsi", "mov eax, 1", "cmovz rsi, rax; size", "mov rbp, rsp", "call cs:realloc_ptr", "test rax, rax", "jz short loc_64C1"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 25791, "end": 25793, "lines": ["pop rbp", "retn"], "succs": []}, {"id": 2, "start": 25793, "end": 25799, "lines": ["call sub_6900"], "succs": []}]}, {"name": "sub_64D0", "ea": 25808, "blocks": [{"id": 0, "start": 25808, "end": 25839, "lines": ["endbr64", "push rbp", "test rsi, rsi", "mov eax, 1", "cmovz rsi, rax; size", "mov rbp, rsp", "call cs:realloc_ptr", "test rax, rax", "jz short loc_64F1"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 25839, "end": 25841, "lines": ["pop rbp", "retn"], "succs": []}, {"id": 2, "start": 25841, "end": 25847, "lines": ["call sub_6900"], "succs": []}]}, {"name": "sub_6500", "ea": 25856, "blocks": [{"id": 0, "start": 25856, "end": 25875, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "call sub_6FE0", "test rax, rax", "jz short loc_6515"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 25875, "end": 25877, "lines": ["pop rbp", "retn"], "succs": []}, {"id": 2, "start": 25877, "end": 25883, "lines": ["call sub_6900"], "succs": []}]}, {"name": "sub_6520", "ea": 25888, "blocks": [{"id": 0, "start": 25888, "end": 25907, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "call sub_6FE0", "test rax, rax", "jz short loc_6535"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 25907, "end": 25909, "lines": ["pop rbp", "retn"], "succs": []}, {"id": 2, "start": 25909, "end": 25915, "lines": ["call sub_6900"], "succs": []}]}, {"name": "sub_6540", "ea": 25920, "blocks": [{"id": 0, "start": 25920, "end": 25947, "lines": ["endbr64", "push rbp", "mov rdx, rsi", "mov rsi, rdi", "xor edi, edi", "mov rbp, rsp", "call sub_6FE0", "test rax, rax", "jz short loc_655D"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 25947, "end": 25949, "lines": ["pop rbp", "retn"], "succs": []}, {"id": 2, "start": 25949, "end": 25955, "lines": ["call sub_6900"], "succs": []}]}, {"name": "sub_6570", "ea": 25968, "blocks": [{"id": 0, "start": 25968, "end": 25995, "lines": ["endbr64", "push rbp", "mov rdx, rsi", "mov rsi, rdi", "xor edi, edi", "mov rbp, rsp", "call sub_6FE0", "test rax, rax", "jz short loc_658D"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 25995, "end": 25997, "lines": ["pop rbp", "retn"], "succs": []}, {"id": 2, "start": 25997, "end": 26003, "lines": ["call sub_6900"], "succs": []}]}, {"name": "sub_6760", "ea": 26464, "blocks": [{"id": 0, "start": 26464, "end": 26488, "lines": ["endbr64", "push rbp", "mov esi, 1; size", "mov rbp, rsp", "call cs:calloc_ptr", "test rax, rax", "jz short loc_677A"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 26488, "end": 26490, "lines": ["pop rbp", "retn"], "succs": []}, {"id": 2, "start": 26490, "end": 26496, "lines": ["call sub_6900"], "succs": []}]}, {"name": "sub_6780", "ea": 26496, "blocks": [{"id": 0, "start": 26496, "end": 26520, "lines": ["endbr64", "push rbp", "mov esi, 1; size", "mov rbp, rsp", "call cs:calloc_ptr", "test rax, rax", "jz short loc_679A"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 26520, "end": 26522, "lines": ["pop rbp", "retn"], "succs": []}, {"id": 2, "start": 26522, "end": 26528, "lines": ["call sub_6900"], "succs": []}]}, {"name": "sub_67A0", "ea": 26528, "blocks": [{"id": 0, "start": 26528, "end": 26547, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "call cs:calloc_ptr", "test rax, rax", "jz short loc_67B5"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 26547, "end": 26549, "lines": ["pop rbp", "retn"], "succs": []}, {"id": 2, "start": 26549, "end": 26555, "lines": ["call sub_6900"], "succs": []}]}, {"name": "sub_67C0", "ea": 26560, "blocks": [{"id": 0, "start": 26560, "end": 26579, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "call cs:calloc_ptr", "test rax, rax", "jz short loc_67D5"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 26579, "end": 26581, "lines": ["pop rbp", "retn"], "succs": []}, {"id": 2, "start": 26581, "end": 26587, "lines": ["call sub_6900"], "succs": []}]}, {"name": "sub_70A0", "ea": 28832, "blocks": [{"id": 0, "start": 28832, "end": 28866, "lines": ["endbr64", "push rbp", "mov edi, 0Eh; item", "mov rbp, rsp", "call nl_langinfo", "mov rdx, rax", "lea rax, aAscii; \"ASCII\"", "test rdx, rdx", "jz short loc_70C9"], "succs": [[1, "fall"], [2, "jump"]]}, {"id": 1, "start": 28866, "end": 28873, "lines": ["cmp byte ptr [rdx], 0", "cmovnz rax, rdx"], "succs": [[2, "fall"]]}, {"id": 2, "start": 28873, "end": 28875, "lines": ["pop rbp", "retn"], "succs": []}]}, {"name": "sub_2BD0", "ea": 11216, "blocks": [{"id": 0, "start": 11216, "end": 11225, "lines": ["endbr64", "jmp sub_2B40"], "succs": [[1, "jump"]]}, {"id": 1, "start": 11072, "end": 11072, "lines": [], "succs": []}]}, {"name": "sub_6360", "ea": 25440, "blocks": [{"id": 0, "start": 25440, "end": 25618, "lines": ["endbr64", "push rbp", "mov rsi, cs:stdout; stream", "mov edi, 0Ah; c", "mov rbp, rsp", "call cs:fputc_unlocked_ptr", "mov edx, 5; category", "lea rsi, aReportBugsToS; \"Report bugs to: %s\\n\"", "lea rdi, domainname; \"gnulib\"", "call cs:dcgettext_ptr", "lea rdx, aBugCoreutilsGn; \"bug-coreutils@gnu.org\"", "mov edi, 2", "mov rsi, rax", "xor eax, eax", "call cs:__printf_chk_ptr", "mov edx, 5; category", "lea rsi, aSHomePageS; \"%s home page: <%s>\\n\"", "lea rdi, domainname; \"gnulib\"", "call cs:dcgettext_ptr", "lea rcx, aHttpsWwwGnuOrg; \"https://www.gnu.org/software/coreutils/\"", "mov edi, 2", "lea rdx, aGnuCoreutils; \"GNU coreutils\"", "mov rsi, rax", "xor eax, eax", "call cs:__printf_chk_ptr", "mov edx, 5; category", "lea rsi, aGeneralHelpUsi; \"General help using GNU software: <%s>\\n\"", "lea rdi, domainname; \"gnulib\"", "call cs:dcgettext_ptr", "lea rdx, aHttpsWwwGnuOrg_1; \"https://www.gnu.org/gethelp/\"", "mov edi, 2", "pop rbp", "mov rsi, rax", "xor eax, eax", "jmp cs:__printf_chk_ptr"], "succs": [[1, "jump"]]}, {"id": 1, "start": 50096, "end": 50096, "lines": [], "succs": []}]}, {"name": "nl_langinfo", "ea": 28880, "blocks": [{"id": 0, "start": 28880, "end": 28890, "lines": ["endbr64", "jmp cs:nl_langinfo_ptr"], "succs": [[1, "jump"]]}, {"id": 1, "start": 50064, "end": 50064, "lines": [], "succs": []}]}, {"name": "sub_70E0", "ea": 28896, "blocks": [{"id": 0, "start": 28896, "end": 28905, "lines": ["endbr64", "jmp sub_7110"], "succs": [[1, "jump"]]}, {"id": 1, "start": 28944, "end": 28944, "lines": [], "succs": []}]}, {"name": "sub_71C0", "ea": 29120, "blocks": [{"id": 0, "start": 29120, "end": 29139, "lines": ["endbr64", "mov rdx, cs:lpdso_handle; lpdso_handle", "xor esi, esi; obj", "jmp cs:__cxa_atexit_ptr"], "succs": [[1, "jump"]]}, {"id": 1, "start": 50104, "end": 50104, "lines": [], "succs": []}]}, {"name": "sub_2447", "ea": 9287, "blocks": [{"id": 0, "start": 9287, "end": 9297, "lines": ["push rbp", "mov rbp, rsp", "call cs:abort_ptr"], "succs": []}]}, {"name": "start", "ea": 10976, "blocks": [{"id": 0, "start": 10976, "end": 11014, "lines": ["endbr64", "xor ebp, ebp", "mov r9, rdx; rtld_fini", "pop rsi; argc", "mov rdx, rsp; ubp_av", "and rsp, 0FFFFFFFFFFFFFFF0h", "push rax", "push rsp; stack_end", "xor r8d, r8d; fini", "xor ecx, ecx; init", "lea rdi, main; main", "call cs:__libc_start_main_ptr", "hlt"], "succs": []}]}, {"name": "sub_34F0", "ea": 13552, "blocks": [{"id": 0, "start": 13552, "end": 13564, "lines": ["endbr64", "mov rax, cs:__progname", "retn"], "succs": []}]}, {"name": "sub_5150", "ea": 20816, "blocks": [{"id": 0, "start": 20816, "end": 20891, "lines": ["endbr64", "push rbp", "mov rbp, rsp", "push r13", "push r12", "push rbx", "mov rbx, rdi", "sub rsp, 8", "call cs:__errno_location_ptr", "test rbx, rbx", "mov esi, 38h ; '8'", "mov r13d, [rax]", "mov r12, rax", "lea rax, xmmword_C240", "cmovnz rax, rbx", "mov rdi, rax", "call sub_67E0", "mov [r12], r13d", "add rsp, 8", "pop rbx", "pop r12", "pop r13", "pop rbp", "retn"], "succs": []}]}, {"name": "sub_5280", "ea": 21120, "blocks": [{"id": 0, "start": 21120, "end": 21241, "lines": ["endbr64", "push rbp", "lea rax, xmmword_C240", "mov rbp, rsp", "push r13", "push r12", "push rbx", "sub rsp, 28h", "test r8, r8", "mov [rbp+var_40], rdi", "cmovnz rax, r8", "mov [rbp+var_38], rsi", "mov [rbp+var_30], rdx", "mov rbx, rax", "mov [rbp+var_28], rcx", "call cs:__errno_location_ptr", "add rbx, 8", "sub rsp, 8", "mov r9d, [rbx-4]", "mov r13d, [rax]", "mov r8d, [rbx-8]", "push qword ptr [rbx+28h]", "mov r12, rax", "mov rcx, [rbp+var_28]", "push qword ptr [rbx+20h]", "mov rdx, [rbp+var_30]", "push rbx", "mov rsi, [rbp+var_38]", "mov rdi, [rbp+var_40]", "call sub_3720", "mov [r12], r13d", "lea rsp, [rbp-18h]", "pop rbx", "pop r12", "pop r13", "pop rbp", "retn"], "succs": []}]}, {"name": "sub_6900", "ea": 26880, "blocks": [{"id": 0, "start": 26880, "end": 26952, "lines": ["endbr64", "push rbp", "mov edx, 5; category", "lea rsi, aMemoryExhauste; \"memory exhausted\"", "lea rdi, domainname; \"gnulib\"", "mov rbp, rsp", "push rbx", "sub rsp, 8", "mov ebx, cs:status", "call cs:dcgettext_ptr", "lea rdx, aSS+4; \"%s\"", "xor esi, esi", "mov rcx, rax", "mov edi, ebx", "xor eax, eax", "call error", "call cs:abort_ptr"], "succs": []}]}, {"name": ".term_proc", "ea": 29140, "blocks": [{"id": 0, "start": 29140, "end": 29153, "lines": ["endbr64", "sub rsp, 8", "add rsp, 8", "retn"], "succs": []}]}, {"name": "getenv", "ea": 49792, "blocks": [{"id": 0, "start": 49792, "end": 49800, "lines": ["extrn getenv:near"], "succs": []}]}, {"name": "free", "ea": 49800, "blocks": [{"id": 0, "start": 49800, "end": 49808, "lines": ["extrn free:near"], "succs": []}]}, {"name": "__vfprintf_chk", "ea": 49808, "blocks": [{"id": 0, "start": 49808, "end": 49816, "lines": ["extrn __vfprintf_chk:near"], "succs": []}]}, {"name": "__libc_start_main", "ea": 49816, "blocks": [{"id": 0, "start": 49816, "end": 49824, "lines": ["extrn __libc_start_main:near"], "succs": []}]}, {"name": "abort", "ea": 49824, "blocks": [{"id": 0, "start": 49824, "end": 49832, "lines": ["extrn abort:near"], "succs": []}]}, {"name": "__errno_location", "ea": 49832, "blocks": [{"id": 0, "start": 49832, "end": 49840, "lines": ["extrn __errno_location:near"], "succs": []}]}, {"name": "strncmp", "ea": 49840, "blocks": [{"id": 0, "start": 49840, "end": 49848, "lines": ["extrn strncmp:near"], "succs": []}]}, {"name": "_exit", "ea": 49848, "blocks": [{"id": 0, "start": 49848, "end": 49856, "lines": ["extrn _exit:near"], "succs": []}]}, {"name": "__fpending", "ea": 49856, "blocks": [{"id": 0, "start": 49856, "end": 49864, "lines": ["extrn __fpending:near"], "succs": []}]}, {"name": "fcntl", "ea": 49864, "blocks": [{"id": 0, "start": 49864, "end": 49872, "lines": ["extrn fcntl:near"], "succs": []}]}, {"name": "textdomain", "ea": 49872, "blocks": [{"id": 0, "start": 49872, "end": 49880, "lines": ["extrn textdomain:near"], "succs": []}]}, {"name": "fclose", "ea": 49880, "blocks": [{"id": 0, "start": 49880, "end": 49888, "lines": ["extrn fclose:near"], "succs": []}]}, {"name": "bindtextdomain", "ea": 49888, "blocks": [{"id": 0, "start": 49888, "end": 49896, "lines": ["extrn bindtextdomain:near"], "succs": []}]}, {"name": "dcgettext", "ea": 49896, "blocks": [{"id": 0, "start": 49896, "end": 49904, "lines": ["extrn dcgettext:near"], "succs": []}]}, {"name": "__ctype_get_mb_cur_max", "ea": 49904, "blocks": [{"id": 0, "start": 49904, "end": 49912, "lines": ["extrn __ctype_get_mb_cur_max:near"], "succs": []}]}, {"name": "strlen", "ea": 49912, "blocks": [{"id": 0, "start": 49912, "end": 49920, "lines": ["extrn strlen:near"], "succs": []}]}, {"name": "__stack_chk_fail", "ea": 49920, "blocks": [{"id": 0, "start": 49920, "end": 49928, "lines": ["extrn __stack_chk_fail:near"], "succs": []}]}, {"name": "strchr", "ea": 49928, "blocks": [{"id": 0, "start": 49928, "end": 49936, "lines": ["extrn strchr:near"], "succs": []}]}, {"name": "__overflow", "ea": 49936, "blocks": [{"id": 0, "start": 49936, "end": 49944, "lines": ["extrn __overflow:near"], "succs": []}]}, {"name": "strrchr", "ea": 49944, "blocks": [{"id": 0, "start": 49944, "end": 49952, "lines": ["extrn strrchr:near"], "succs": []}]}, {"name": "__assert_fail", "ea": 49952, "blocks": [{"id": 0, "start": 49952, "end": 49960, "lines": ["extrn __assert_fail:near"], "succs": []}]}, {"name": "memset", "ea": 49960, "blocks": [{"id": 0, "start": 49960, "end": 49968, "lines": ["extrn memset:near"], "succs": []}]}, {"name": "mbrtoc32", "ea": 49968, "blocks": [{"id": 0, "start": 49968, "end": 49976, "lines": ["extrn mbrtoc32:near"], "succs": []}]}, {"name": "strspn", "ea": 49976, "blocks": [{"id": 0, "start": 49976, "end": 49984, "lines": ["extrn strspn:near"], "succs": []}]}, {"name": "strcspn", "ea": 49984, "blocks": [{"id": 0, "start": 49984, "end": 49992, "lines": ["extrn strcspn:near"], "succs": []}]}, {"name": "memcmp", "ea": 49992, "blocks": [{"id": 0, "start": 49992, "end": 50000, "lines": ["extrn memcmp:near"], "succs": []}]}, {"name": "fputs_unlocked", "ea": 50000, "blocks": [{"id": 0, "start": 50000, "end": 50008, "lines": ["extrn fputs_unlocked:near"], "succs": []}]}, {"name": "calloc", "ea": 50008, "blocks": [{"id": 0, "start": 50008, "end": 50016, "lines": ["extrn calloc:near"], "succs": []}]}, {"name": "strcmp", "ea": 50016, "blocks": [{"id": 0, "start": 50016, "end": 50024, "lines": ["extrn strcmp:near"], "succs": []}]}, {"name": "fputc_unlocked", "ea": 50024, "blocks": [{"id": 0, "start": 50024, "end": 50032, "lines": ["extrn fputc_unlocked:near"], "succs": []}]}, {"name": "__memcpy_chk", "ea": 50032, "blocks": [{"id": 0, "start": 50032, "end": 50040, "lines": ["extrn __memcpy_chk:near"], "succs": []}]}, {"name": "memcpy", "ea": 50040, "blocks": [{"id": 0, "start": 50040, "end": 50048, "lines": ["extrn memcpy:near"], "succs": []}]}, {"name": "strerror_r", "ea": 50048, "blocks": [{"id": 0, "start": 50048, "end": 50056, "lines": ["extrn strerror_r:near"], "succs": []}]}, {"name": "malloc", "ea": 50056, "blocks": [{"id": 0, "start": 50056, "end": 50064, "lines": ["extrn malloc:near"], "succs": []}]}, {"name": "__imp_nl_langinfo", "ea": 50064, "blocks": [{"id": 0, "start": 50064, "end": 50072, "lines": ["extrn __imp_nl_langinfo:near"], "succs": []}]}, {"name": "fwrite_unlocked", "ea": 50072, "blocks": [{"id": 0, "start": 50072, "end": 50080, "lines": ["extrn fwrite_unlocked:near"], "succs": []}]}, {"name": "realloc", "ea": 50080, "blocks": [{"id": 0, "start": 50080, "end": 50088, "lines": ["extrn realloc:near"], "succs": []}]}, {"name": "setlocale", "ea": 50088, "blocks": [{"id": 0, "start": 50088, "end": 50096, "lines": ["extrn setlocale:near"], "succs": []}]}, {"name": "__printf_chk", "ea": 50096, "blocks": [{"id": 0, "start": 50096, "end": 50104, "lines": ["extrn __printf_chk:near"], "succs": []}]}, {"name": "__cxa_atexit", "ea": 50104, "blocks": [{"id": 0, "start": 50104, "end": 50112, "lines": ["extrn __cxa_atexit:near"], "succs": []}]}, {"name": "exit", "ea": 50112, "blocks": [{"id": 0, "start": 50112, "end": 50120, "lines": ["extrn exit:near"], "succs": []}]}, {"name": "__fprintf_chk", "ea": 50120, "blocks": [{"id": 0, "start": 50120, "end": 50128, "lines": ["extrn __fprintf_chk:near"], "succs": []}]}, {"name": "fflush_unlocked", "ea": 50128, "blocks": [{"id": 0, "start": 50128, "end": 50136, "lines": ["extrn fflush_unlocked:near"], "succs": []}]}, {"name": "mbsinit", "ea": 50136, "blocks": [{"id": 0, "start": 50136, "end": 50144, "lines": ["extrn mbsinit:near"], "succs": []}]}, {"name": "iswprint", "ea": 50144, "blocks": [{"id": 0, "start": 50144, "end": 50152, "lines": ["extrn iswprint:near"], "succs": []}]}, {"name": "__cxa_finalize", "ea": 50152, "blocks": [{"id": 0, "start": 50152, "end": 50160, "lines": ["extrn __cxa_finalize:near ; weak"], "succs": []}]}, {"name": "__ctype_b_loc", "ea": 50160, "blocks": [{"id": 0, "start": 50160, "end": 50168, "lines": ["extrn __ctype_b_loc:near"], "succs": []}]}, {"name": "_ITM_deregisterTMCloneTable", "ea": 50168, "blocks": [{"id": 0, "start": 50168, "end": 50176, "lines": ["extrn _ITM_deregisterTMCloneTable:near ; weak"], "succs": []}]}, {"name": "__gmon_start__", "ea": 50176, "blocks": [{"id": 0, "start": 50176, "end": 50184, "lines": ["extrn __gmon_start__:near ; weak"], "succs": []}]}, {"name": "_ITM_registerTMCloneTable", "ea": 50184, "blocks": [{"id": 0, "start": 50184, "end": 50192, "lines": ["extrn _ITM_registerTMCloneTable:near ; weak"], "succs": []}]}] \ No newline at end of file
diff --git a/.auto/check_edit.py b/.auto/check_edit.py
deleted file mode 100644
index 2472c84..0000000
--- a/.auto/check_edit.py
+++ /dev/null
@@ -1,126 +0,0 @@
-#!/usr/bin/env python3
-"""Correctness gate for the listing after an item edit (run by .auto/checks.sh).
-
-An item edit (`c`/`d`/`u`/`p`) changes structure, but only locally: every head
-in front of it keeps its address and its row number. So `Program.bump_items(ea)`
-keeps the walk up to there instead of discarding the model — worth 257x on a big
-binary (4.9s to make one byte into data, against 19ms).
-
-Keeping *anything* across a structural edit is the risky half of that, and it
-fails silently: the pane shows rows that are no longer what the database says.
-So this drives real edits and compares the kept model against one built from
-scratch, row for row — narrow reads (what painting does) and wide ones (what
-building the search body does).
-
-The staged database is a throwaway copy and is never saved, so the edits here do
-not need undoing and can be as destructive as they like.
-
- ~/ida-venv/bin/python .auto/check_edit.py [targets/echo]
-"""
-from __future__ import annotations
-
-import os
-import shutil
-import sys
-
-HERE = os.path.dirname(os.path.abspath(__file__))
-ROOT = os.path.dirname(HERE)
-sys.path.insert(0, ROOT)
-sys.path.insert(0, HERE)
-
-from bench import stage # noqa: E402
-from idatui.domain import Program # noqa: E402
-from idatui.worker_client import WorkerClient # noqa: E402
-
-
-def snapshot(model, total: int, wide: bool):
- if wide:
- rows = []
- for base in range(0, total, 4096):
- rows.extend(model.window(base, min(4096, total - base)))
- else:
- rows = [model.get(i) for i in range(total)]
- return [(h.ea, h.kind, h.size, h.text, h.name) if h else None for h in rows]
-
-
-def main() -> int:
- target = sys.argv[1] if len(sys.argv) > 1 else "targets/echo"
- d, path = stage(os.path.join(ROOT, target))
- fails: list[str] = []
- client = WorkerClient(path)
- try:
- prog = Program(client)
- idx = prog.functions()
- idx.load_all()
- funcs = sorted(idx.all_loaded(), key=lambda f: f.addr)
- if len(funcs) < 8:
- print(f"{target}: too few functions to check")
- return 1
- seg = funcs[len(funcs) // 2].addr
- model = prog.listing(seg)
- model.load_all()
- total = len(model)
-
- # Edits at a spread of positions: near the end (where truncation keeps
- # nearly everything), the middle, and early on (where it must give up).
- spots = [funcs[int(len(funcs) * f)].addr for f in (0.9, 0.5, 0.05)]
- for k, ea in enumerate(spots):
- # Undefine, at three sizes. This is the edit that can coalesce
- # BACKWARDS into the undefined run in front of it, which is the
- # reason truncate_from drops two pages rather than one.
- size = (1, 4, 16)[k % 3]
- kind = f"undefine {size}B"
- try:
- prog.undefine(ea, size)
- except Exception as e: # noqa: BLE001
- fails.append(f"{kind} at {ea:#x} failed: {e}")
- continue
- prog.bump_items(ea)
-
- kept = prog.listing(seg)
- kept_total = None
- got_wide = None
- if kept is not None:
- kept.load_all()
- kept_total = len(kept)
- got_wide = snapshot(kept, kept_total, wide=True)
- got_narrow = snapshot(kept, kept_total, wide=False)
-
- # ... against a model that knows nothing about what came before.
- prog._listings.clear()
- fresh = prog.listing(seg)
- fresh.load_all()
- want = snapshot(fresh, len(fresh), wide=False)
-
- if kept_total != len(fresh):
- fails.append(f"{kind} at {ea:#x}: kept model has {kept_total} "
- f"rows, a rebuild has {len(fresh)}")
- elif got_narrow != want or got_wide != want:
- which = "narrow" if got_narrow != want else "wide"
- bad = next((i for i, (a, b) in
- enumerate(zip(got_narrow if which == "narrow"
- else got_wide, want)) if a != b), None)
- fails.append(
- f"{kind} at {ea:#x}: {which} read differs from a rebuild at "
- f"row {bad}: {(got_narrow if which == 'narrow' else got_wide)[bad]}"
- f" vs {want[bad]}")
-
- model = prog.listing(seg)
- model.load_all()
- total = len(model)
-
- print(f"item edits: {len(spots)} edits checked against a rebuild "
- f"({total} rows), {len(fails)} problems")
- for f in fails:
- print(" FAIL", f)
- return 1 if fails else 0
- finally:
- try:
- client.close()
- except Exception: # noqa: BLE001
- pass
- shutil.rmtree(d, ignore_errors=True)
-
-
-if __name__ == "__main__":
- sys.exit(main())
diff --git a/.auto/check_rename.py b/.auto/check_rename.py
deleted file mode 100644
index c617aa6..0000000
--- a/.auto/check_rename.py
+++ /dev/null
@@ -1,120 +0,0 @@
-#!/usr/bin/env python3
-"""Correctness gate for the listing's rename handling (run by .auto/checks.sh).
-
-A rename does not move any listing row, so ``Program.bump_names`` keeps the
-segment's walk and only marks the rendered text stale; ``ListingModel`` re-renders
-a block at a time as rows are read. That is worth 500x on a big binary (the
-alternative re-walks the whole segment to find a row the cursor was already on),
-and it is exactly the kind of optimisation that fails *quietly*: the pane keeps
-showing the old name and nothing errors.
-
-Two things are checked, because they exercise different paths and only the first
-was ever caught by accident:
-
-* a NARROW read (``get`` per row, what painting does), and
-* a WIDE read (``window`` over thousands of rows, what search's body build does)
-
-both have to come back with the new name -- and the whole model has to match one
-built from scratch, row for row.
-
- ~/ida-venv/bin/python .auto/check_rename.py [targets/echo]
-"""
-from __future__ import annotations
-
-import os
-import shutil
-import sys
-
-HERE = os.path.dirname(os.path.abspath(__file__))
-ROOT = os.path.dirname(HERE)
-sys.path.insert(0, ROOT)
-sys.path.insert(0, HERE)
-
-from bench import stage # noqa: E402
-from idatui.domain import Program # noqa: E402
-from idatui.worker_client import WorkerClient # noqa: E402
-
-
-def rename(client, ea: int, name: str) -> None:
- client.call("rename", batch={"func": [{"addr": hex(ea), "name": name}]})
-
-
-def main() -> int:
- target = sys.argv[1] if len(sys.argv) > 1 else "targets/echo"
- d, path = stage(os.path.join(ROOT, target))
- fails: list[str] = []
- client = WorkerClient(path)
- try:
- prog = Program(client)
- idx = prog.functions()
- idx.load_all()
- funcs = idx.all_loaded()
- if len(funcs) < 4:
- print(f"{target}: too few functions to check")
- return 1
- seg = sorted(funcs, key=lambda f: f.addr)[len(funcs) // 2].addr
- model = prog.listing(seg)
- model.load_all()
- total = len(model)
-
- for k, victim in enumerate(sorted(funcs, key=lambda f: -f.size)[:2]):
- new = f"_check_rename_{os.getpid()}_{k}"
- rename(client, victim.addr, new)
- prog.bump_names()
-
- # WIDE read -- what building the search body does. This is the one
- # that used to come back with the old names: an oversized refetch
- # overflowed the heads tool's row cap, failed its sequence check and
- # left the block untouched.
- wide = []
- for base in range(0, total, 4096):
- wide.extend(model.window(base, min(4096, total - base)))
- # "Is the old name gone" is not a sound test -- `main` is a token of
- # `__libc_start_main` and can legitimately appear in a comment or a
- # string. That the NEW name arrived proves the refresh ran; that the
- # model matches a rebuild, below, proves it ran correctly.
- shown = sum(1 for h in wide if h is not None
- and (new in (h.text or "") or new == (h.name or "")))
- if not shown:
- fails.append(f"wide read after renaming {victim.name} -> {new}: "
- f"no row shows the new name")
-
- # NARROW read -- what painting does -- and the whole model against a
- # rebuild, row for row.
- kept = [(h.ea, h.kind, h.text, h.name) if h else None
- for h in (model.get(i) for i in range(total))]
- prog._listings.clear()
- fresh_model = prog.listing(seg)
- fresh_model.load_all()
- fresh = [(h.ea, h.kind, h.text, h.name) if h else None
- for h in (fresh_model.get(i) for i in range(len(fresh_model)))]
- if kept != fresh:
- bad = next((i for i, (a, b) in enumerate(zip(kept, fresh))
- if a != b), None)
- fails.append(f"kept model != rebuilt model after renaming "
- f"{victim.name}: first difference at row {bad}: "
- f"{kept[bad] if bad is not None else None} vs "
- f"{fresh[bad] if bad is not None else None}")
-
- rename(client, victim.addr, victim.name)
- prog.bump_names()
- prog._listings.clear()
- model = prog.listing(seg)
- model.load_all()
- total = len(model)
-
- print(f"rename handling: {total} rows checked wide and narrow, "
- f"{len(fails)} problems")
- for f in fails:
- print(" FAIL", f)
- return 1 if fails else 0
- finally:
- try:
- client.close()
- except Exception: # noqa: BLE001
- pass
- shutil.rmtree(d, ignore_errors=True)
-
-
-if __name__ == "__main__":
- sys.exit(main())
diff --git a/.auto/check_search.py b/.auto/check_search.py
deleted file mode 100644
index 74ce719..0000000
--- a/.auto/check_search.py
+++ /dev/null
@@ -1,148 +0,0 @@
-#!/usr/bin/env python3
-"""Correctness gate for the search fast paths (run by .auto/checks.sh).
-
-`SearchMixin` grew two optimisations that are invisible to the scenario suite
-because they produce the *same answer* when they work:
-
-* **prefix narrowing** — typing a character onto the term rescans only the
- previous hits, because a line holding "mov" holds "mo";
-* **the joined haystack** — the whole body is concatenated once so a term is
- found with a C-level `str.find` walk instead of a python loop over every row.
-
-Both are cache-shaped, so the way they break is *staleness*, not a crash. This
-drives the real `ListingView` and `DecompView` and asserts that, for every
-prefix of a set of terms, the fast path returns exactly the matches and
-highlight ranges the plain per-line loop does — including after the things that
-are meant to invalidate them (ending a search, toggling the opcode column,
-navigating).
-
- ~/ida-venv/bin/python .auto/check_search.py [targets/echo]
-"""
-from __future__ import annotations
-
-import asyncio
-import os
-import shutil
-import sys
-
-HERE = os.path.dirname(os.path.abspath(__file__))
-ROOT = os.path.dirname(HERE)
-sys.path.insert(0, ROOT)
-sys.path.insert(0, os.path.join(ROOT, "tests"))
-sys.path.insert(0, HERE)
-
-from bench import stage # noqa: E402
-from idatui._sync import wait_for # noqa: E402
-from idatui.app import DecompView, IdaTui, ListingView # noqa: E402
-
-TERMS = ("mov", "call", "rsp", "Mov", "1a", "push", "e", "lea", "sub_", "0",
- " ", "if", "v1", "]")
-
-
-def _reference(view, term: str):
- """(matches, ranges) from the plain per-line loop, with every cache off."""
- cls = view.__class__
- saved = cls._search_haystack
- cls._search_haystack = lambda self, c, s: None
- try:
- view._reset_search_cache(body=True)
- view._term = term
- view._ci = term.islower()
- view._compute_matches()
- return (list(view._matches),
- {k: list(v) for k, v in view._ranges.items()})
- finally:
- cls._search_haystack = saved
- view._reset_search_cache(body=True)
-
-
-def _fast(view, term: str):
- view._term = term
- view._ci = term.islower()
- view._compute_matches()
- return (list(view._matches), {k: list(v) for k, v in view._ranges.items()})
-
-
-def _check(view, name: str, fails: list) -> int:
- """Type every prefix of every term; compare the fast path to the loop."""
- checked = 0
- for word in TERMS:
- view.search_begin(1)
- for i in range(1, len(word) + 1):
- term = word[:i]
- got = _fast(view, term) # may narrow from the previous term
- want = _reference(view, term)
- checked += 1
- if got != want:
- a, b = set(got[0]), set(want[0])
- fails.append(
- f"{name} {term!r}: fast={len(got[0])} loop={len(want[0])} "
- f"only-fast={sorted(a - b)[:4]} only-loop={sorted(b - a)[:4]}")
- view.search_cancel()
- return checked
-
-
-async def main() -> int:
- target = sys.argv[1] if len(sys.argv) > 1 else "targets/echo"
- fails: list[str] = []
- d, path = stage(os.path.join(ROOT, target))
- app = IdaTui(open_path=path, keepalive=False)
- try:
- async with app.run_test(size=(140, 44)) as pilot:
- async def w(pred, t=300.0):
- return await wait_for(pred, pilot.pause, t, 0.02)
-
- if not await w(lambda: app._func_index is not None
- and app._func_index.complete):
- fails.append("boot: function index never completed")
- return 1
- await w(lambda: app._loading_screen is None
- and len(app.screen_stack) == 1, 120)
- funcs = app._func_index.all_loaded()
- lst = app.query_one(ListingView)
- fn = funcs[min(5, len(funcs) - 1)]
- app._open_function(fn.addr, fn.name)
- await w(lambda: lst.total > 0 and lst._cursor_ea() == fn.addr, 120)
- lst.model.load_all()
- lst.total = len(lst.model)
-
- n = _check(lst, "listing", fails)
-
- # The opcode-bytes column is searchable text and changes width
- # without changing the row count or the model -- the one thing the
- # haystack's key cannot see.
- app.action_toggle_view.__self__ # noqa: B018 - keep app referenced
- lst.action_toggle_opcodes()
- n += _check(lst, "listing/opcodes", fails)
- lst.action_toggle_opcodes()
-
- # Navigating inside the same segment must NOT invalidate the body,
- # and must not leave it stale either.
- other = funcs[min(9, len(funcs) - 1)]
- app._open_function(other.addr, other.name)
- await w(lambda: lst.total > 0
- and lst._cursor_ea() == other.addr, 120)
- lst.model.load_all()
- lst.total = len(lst.model)
- n += _check(lst, "listing/after-nav", fails)
-
- # The pseudocode view uses a different line source.
- app._active = "listing"
- app._show_active()
- lst.focus()
- await pilot.pause(0.05)
- app.action_toggle_view()
- dv = app.query_one(DecompView)
- if await w(lambda: dv.total > 0, 60):
- n += _check(dv, "decomp", fails)
- app.exit()
- print(f"search fast paths: {n} prefixes checked, {len(fails)} mismatches")
- for f in fails[:10]:
- print(" FAIL", f)
- return 1 if fails else 0
- finally:
- shutil.rmtree(d, ignore_errors=True)
-
-
-if __name__ == "__main__":
- sys.exit(asyncio.run(main()))
diff --git a/.auto/checks.sh b/.auto/checks.sh
deleted file mode 100755
index 4b9778a..0000000
--- a/.auto/checks.sh
+++ /dev/null
@@ -1,81 +0,0 @@
-#!/bin/bash
-# Correctness gate: no perf win is allowed to cost functionality.
-#
-# 1. .auto/check_search.py -- the search fast paths against the plain loop.
-# Caches that go stale still return AN answer, so no scenario test can see
-# them; this compares fast and slow directly, for every typed prefix.
-# 2. .auto/check_rename.py -- a rename keeps the listing's walk and only
-# re-renders its text. When that goes wrong the pane simply keeps showing
-# the old name, which nothing else notices. Checks a NARROW read (what
-# painting does) and a WIDE one (what building the search body does), and
-# the whole model against a rebuild.
-# 3. .auto/check_edit.py -- the listing after an ITEM edit. bump_items(ea)
-# keeps the walk in front of the edit rather than discarding it; keeping
-# anything across a structural change is the risky half and it fails
-# silently, so the kept model is compared against a rebuild row for row.
-# 4. tests/run.py -- the project's own front door: every suite, pure and IDA,
-# ~185s. It used to be only the scenario suite here, and that gap cost a
-# real regression: a faster worker connect left the loading overlay up a
-# moment longer relative to the index finishing, and project mode's first
-# keypress landed on the overlay. Only test_project_ui.py covers that, and
-# it was not being run.
-#
-# Only failures reach stdout: the agent sees the last 80 lines on failure, and a
-# wall of "ok" would push the actual break out of view. A file that fails is
-# re-run ALONE before it counts -- the IDA suites share a loaded box, and a
-# worker that got CPU-starved mid-analysis reads as a failure but is not one
-# (see the note in tests/run.py).
-set -euo pipefail
-cd "$(dirname "$0")/.."
-
-PY="${IDATUI_PYTHON:-$HOME/ida-venv/bin/python}"
-
-search=$("$PY" .auto/check_search.py targets/echo 2>&1) || {
- echo "--- search fast paths disagree with the plain loop ---"
- echo "$search" | tail -20
- exit 1
-}
-echo "$search" | tail -1
-
-rn=$("$PY" .auto/check_rename.py targets/echo 2>&1) || {
- echo "--- the listing is wrong after a rename ---"
- echo "$rn" | tail -20
- exit 1
-}
-echo "$rn" | tail -1
-
-ed=$("$PY" .auto/check_edit.py targets/echo 2>&1) || {
- echo "--- the listing is wrong after an item edit ---"
- echo "$ed" | tail -20
- exit 1
-}
-echo "$ed" | tail -1
-
-out=$(python3 tests/run.py 2>&1) || true
-echo "$out" | tail -2
-
-# Files run.py marked bad, e.g. " FAIL scenarios 301 passed, 2 failed"
-# run.py's summary says "N passed[, M failed]..."; no "failed" clause == green.
-if ! echo "$out" | tail -3 | grep -q "failed"; then
- exit 0
-fi
-
-echo "--- first pass failures ---"
-echo "$out" | grep -E "^ FAIL" | head -20
-# run.py names them itself: "failing files: formats scenarios" (colourised).
-files=$(echo "$out" | sed -e 's/\x1b\[[0-9;]*m//g' \
- | sed -n 's/^failing files: *//p' | tr '\n' ' ')
-if [ -z "$files" ]; then
- echo "--- could not identify the failing file; full tail ---"
- echo "$out" | tail -30
- exit 1
-fi
-echo "--- retrying alone: $files ---"
-retry=$(python3 tests/run.py $files 2>&1) || true
-if echo "$retry" | tail -3 | grep -q "failed"; then
- echo "--- REAL regression ---"
- echo "$retry" | grep -E "^ FAIL" | head -30
- echo "$retry" | tail -3
- exit 1
-fi
-echo "flake: [$files] pass in isolation"
diff --git a/.auto/diff_spans.py b/.auto/diff_spans.py
deleted file mode 100644
index aad4ebc..0000000
--- a/.auto/diff_spans.py
+++ /dev/null
@@ -1,116 +0,0 @@
-#!/usr/bin/env python3
-"""Differential check: the current `_idatui_spans` vs the one at a git ref.
-
-The span walker turns IDA's colour-tagged disassembly line into (spans, ops).
-It is on the hot path of every listing row, so it is worth optimising -- but its
-output drives highlighting, operand marking and the cursor's column arithmetic,
-so "faster" is only acceptable if it is byte-identical.
-
-This pulls both implementations out of `server/patch_server.py` (the current
-working tree, and whatever `--ref` names), runs them over every tagged line of
-a real binary, and reports the first disagreement.
-
- /usr/bin/python3 .auto/diff_spans.py [--ref HEAD] [--target targets/bash]
- [--limit 60000]
-"""
-from __future__ import annotations
-
-import argparse
-import os
-import shutil
-import subprocess
-import sys
-import tempfile
-
-ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-
-
-def load_impl(path: str, name: str):
- """Exec just the span-walker section of a patch_server.py's injected BODY.
-
- BODY is a normal (non-raw) triple-quoted string, so the escapes in it are
- resolved once by importing the module -- slicing the file text instead would
- compile the *undecoded* source and silently test a different program (an
- escaped backslash became a literal one, and every 's' in a comment turned
- into a space).
- """
- import importlib.util
- spec = importlib.util.spec_from_file_location(f"_ps_{name}", path)
- mod = importlib.util.module_from_spec(spec)
- spec.loader.exec_module(mod) # IDA-free at import time
- body = mod.BODY
- a = body.index("def _idatui_head_row")
- b = body.index("def _idatui_struct_member_rows")
- g = {"__name__": name}
- exec(compile(body[a:b], name, "exec"), g) # noqa: S102
- return g
-
-
-def main() -> int:
- ap = argparse.ArgumentParser()
- ap.add_argument("--ref", default="HEAD")
- ap.add_argument("--target", default="targets/bash")
- ap.add_argument("--limit", type=int, default=60000)
- a = ap.parse_args()
-
- d = tempfile.mkdtemp(prefix="diffspans-")
- old_path = os.path.join(d, "patch_server_old.py")
- with open(old_path, "w") as fh:
- fh.write(subprocess.run(
- ["git", "-C", ROOT, "show", f"{a.ref}:server/patch_server.py"],
- capture_output=True, text=True, check=True).stdout)
- gnew = load_impl(os.path.join(ROOT, "server", "patch_server.py"), "new")
- gold = load_impl(old_path, "old")
- new, old = gnew["_idatui_spans"], gold["_idatui_spans"]
- new_row, old_row = gnew["_idatui_head_row"], gold["_idatui_head_row"]
-
- binary = os.path.join(ROOT, a.target)
- tgt = os.path.join(d, os.path.basename(binary))
- shutil.copy2(binary, tgt)
- shutil.copy2(binary + ".i64", tgt + ".i64")
- try:
- import idapro
- if idapro.open_database(tgt, run_auto_analysis=False) != 0:
- raise SystemExit("could not open database")
- import ida_bytes
- import ida_lines
- import ida_segment
- import idaapi
-
- checked = bad = 0
- for si in range(ida_segment.get_segm_qty()):
- seg = ida_segment.getnseg(si)
- if seg is None:
- continue
- ea = seg.start_ea
- while ea < seg.end_ea and checked < a.limit:
- line = ida_lines.generate_disasm_line(ea, 0)
- if line:
- checked += 1
- ra, rb = old(line), new(line)
- if ra != rb:
- bad += 1
- if bad <= 3:
- print(f"SPAN MISMATCH @ {ea:#x}\n line={line!r}\n"
- f" old={ra!r}\n new={rb!r}")
- # The whole row, not just the spans: `text`, the spans/text
- # agreement guard and the name all moved around too.
- ra, rb = old_row(ea), new_row(ea)
- if ra != rb:
- bad += 1
- if bad <= 3:
- print(f"ROW MISMATCH @ {ea:#x}\n"
- f" old={ra!r}\n new={rb!r}")
- nxt = ida_bytes.get_item_end(ea)
- ea = nxt if nxt > ea else ea + 1
- if checked >= a.limit:
- break
- print(f"checked {checked} lines, {bad} mismatches")
- idapro.close_database(False)
- return 1 if bad else 0
- finally:
- shutil.rmtree(d, ignore_errors=True)
-
-
-if __name__ == "__main__":
- sys.exit(main())
diff --git a/.auto/ideas.md b/.auto/ideas.md
deleted file mode 100644
index 133993c..0000000
--- a/.auto/ideas.md
+++ /dev/null
@@ -1,331 +0,0 @@
-# Ideas backlog
-
-## Perf, not yet tried
-
-- **Skeleton walk for `ensure_ea`** — navigation only needs the *row index* of an
- address, yet `ListingModel` walks the segment loading fully-rendered rows.
- **Costed, and it only nets ~5%**: a text-free walk would be ~3 µs/row instead
- of ~23, but search then has to fetch the text anyway (it builds its haystack
- from `_line_plain`), so most of the saving moves rather than disappears.
- Worth it only if the goal changes from "total session time" to "no single
- foreground wait over 200 ms" — which is arguably the better goal for a TUI.
-- **`_grow` should start from where the user is**, not sweep from the segment
- start, so a jump into the middle doesn't wait behind everything before it.
-- **Prefetch the decompilation of adjacent/called functions** while the user
- reads the current one. The worker is idle then and `Program.decompile` caches.
-- **Persist the worker's `_idatui_line_parts` cache** — it is warm only within
- one worker, and the same binary is reopened constantly during a session.
-
-## Bugs found while optimising (not perf work)
-
-- **`idatui/graph.py` edge routing is non-deterministic.** Laying out the *same*
- function twice with the *unchanged* engine gives different
- `painting.vruns` for 71 of 128 corpus functions. Node placement is stable;
- only the routing moves. So a graph redraws differently when you reopen it, and
- any old-vs-new painting diff is worthless as a regression test (old-vs-old
- fails it too). Find the set/dict iteration or `id()`-keyed order behind it.
-- **Sticky graph mode makes a keypress ambiguous.** With `_graph_sticky` on, a
- navigation schedules the next function's graph asynchronously; until it lands
- the app is in the listing. So `space` right after a jump either enters or
- leaves the graph depending on which won. `graph_minimap` was silently relying
- on losing that race. A fix would be to enter graph mode immediately, with a
- loading state, when a sticky navigation starts.
-- **`domain.decomp_map` costs ~280 ms per function** — more than the decompile
- itself — and is on the split-view (`s`) path, which the bench doesn't cover.
-
-## Verification patterns that worked (reuse them)
-
-- **Differential against a git ref.** `.auto/diff_spans.py` loads the *current*
- and a *past* `server/patch_server.py`, execs the same slice of each `BODY`,
- and compares outputs over every disassembly line of a real binary. Catch:
- `BODY` is a normal triple-quoted string, so you must import the module and
- read `mod.BODY` — slicing the file text tests an undecoded program.
-- **Compare what reaches the screen, not the data structure.** Merging Segments
- is *supposed* to change the segments; expand both to `(char, style)` per cell
- and compare that (`/tmp/hexeq.py`, `/tmp/gveq.py`).
-- **Check the old code against itself first.** The graph routing diff looked
- like a regression until old-vs-old failed identically.
-- **`.auto/check_search.py`** is the permanent version of this idea and runs in
- `checks.sh`: it compares the search fast paths against the plain per-line loop
- for every typed prefix. Cache staleness returns a *plausible wrong answer*,
- which no scenario test can catch.
-- The bench's `NOTES` counters (`search_hits`, `graph_blocks`, `decomp_ok`,
- `render_cells`, `nav_rows`, `listing_cells`) are the standing guard against
- "faster because it did less".
-
-## Two "predict what changed" schemes, both measured and rejected
-
-Both would have made a rename nearly free. Both fail for the same reason: **IDA
-and Hex-Rays drift on their own**, so "what the edit changed" is not the same
-question as "what is different now".
-
-- **Listing rows, predicted from `xrefs_to` + the function's extent.**
- `/tmp/whatchanges.py` rebuilds the segment before and after and diffs every
- row. echo: 19/19 changed rows covered. ls_ttl: 53/54 — the miss was
- `lea rcx, unk_1D7A0` → `byte_1D7A0`, which the rename did not cause; IDA's own
- analysis defined that byte.
-- **Decompilations, predicted from "the old name appears in the cached text".**
- `/tmp/decchanges.py` decompiles 25 functions, renames one, recompiles all and
- diffs. **16 misses over 4 renames, every one of them Hex-Rays' type inference
- moving** — e.g. `unsigned __int64 f(..., unsigned int a4)` → `..., int a4)` in
- functions with no connection to the rename.
-
-The lesson generalises: predicting the effect of an edit on a database that has
-its own opinions is unsound. Verify instead — `heads(digest=True)` works because
-it asks what the row renders as *now*, not what should have changed.
-
-(This also explains the `lg_decomp_lines` drift blamed on CPU starvation in v5
-#4: it is probably the same Hex-Rays instability.)
-
-## Added late in the session
-
-- ~~**Refresh only the rows a rename actually changed** (xrefs-driven).~~
- **MEASURED AND REJECTED.** `/tmp/whatchanges.py` rebuilds the whole segment
- before and after a rename and diffs every row. echo: all 19 changed rows over
- 4 renames were covered by (function extent + `xrefs_to`). ls_ttl: 53 of 54 —
- and the one that was not, `lea rcx, unk_1D7A0` → `byte_1D7A0`, **was not
- caused by the rename at all**: IDA's own analysis defined that byte. Any
- address-predicted invalidation leaves such a row stale for good. Superseded by
- the digest scheme, which is exact because it looks at the rendered line.
-- **Features the bench still doesn't drive end to end**, in the order they seem
- worth probing: xrefs (`x`), the strings browser (`"`), literal formats (`o`),
- make-code/data/function edits, history (`back`), execution traces, the RPC
- layer. Use the `/tmp/featprobe.py` shape: call the domain API for each and look
- for a number that is absurd for the work done. That is how the flowchart hull,
- `decomp_map` and the rename walk were all found.
-
-## The UI is much slower while a big segment streams (measured, not fixed)
-
-Opening a big binary leaves `ListingView._grow` streaming the segment in the
-background for ~7s. During that window every UI action is several times slower:
-an xrefs dialog measured **691ms while streaming against 105ms after** on bash.
-Two mechanisms, both confirmed:
-
-* **`@work(exclusive=True)` does not stop a thread worker that is already
- running**, and navigating inside the same segment re-primes against the SAME
- model — so every jump leaves another streamer behind, all queueing on the
- model's load lock. Five jumps into bash meant five streamers.
-* Each streamer reports growth to the UI every four pages; a report is a thread
- hop, a `virtual_size` change and a full repaint.
-
-Two fixes were tried and **both measured worse**, so neither was kept:
-a time-based report throttle (10/s) made it worse because the count is per
-*streamer* and there are several; adding a token so only the newest streamer
-survives did not reduce the report count either, which means the retirement is
-not happening where it looks like it should — worth understanding before trying
-again. Probe: `/tmp/streamresp.py` (navigate + press `x` repeatedly while
-`lst.model.complete` is still False, counting `_grew` calls).
-
-Caveat: these numbers were taken on a loaded box and the probe re-primes the
-view on every iteration, which is itself what spawns the extra streamers. Build
-a cleaner probe first.
-
-## Pre-existing crash, unrelated to performance
-
-`StringsPalette.on_mount` calls `self._apply("")`, which does
-`query_one(OptionList)` before `compose`'s children are mounted:
-`NoMatches: No nodes match 'OptionList' on StringsPalette()`. Reproduces 3/3 on
-`targets/bash` with `/tmp/strcrash.py`, **and 3/3 on the pre-autoresearch commit
-2b0ae8d** — so it is not something this work introduced. `ProjectPalette` has the
-same shape and the same latent race.
-
-## Trace memory reads scale with trace LENGTH (measured, not fixed)
-
-`idatui/trace.py` loads linearly (36.6 / 73.3 / 143.9 / 280.7 ms for 20k / 40k /
-80k / 160k rows — x1.95 per doubling, exactly right) and `register_state` is
-effectively O(1). But `Trace.memory(addr, length, idx)` costs 16.3 / 31.7 / 63.4
-/ 125.8 ms for 200 calls over those same traces: **linear in trace length per
-call.**
-
-`_mem_index` sorts accesses by address and bisects to the window, which is the
-right idea — but it then iterates *every* access in that address window across
-all time, filtering by `t > idx`. A hot stack slot in a loop is written once per
-iteration, so the stack pane's cost grows with how long the trace ran. On a
-10M-instruction trace a single step could scan millions of entries.
-
-The fix is to find, per byte, the latest access with `t <= idx` rather than
-scanning them all. The sort is already stable, so entries within one address are
-in time order — but accesses have variable length and overlap, so grouping is
-not trivial. Probe: `/tmp/traceprof.py`.
-
-**Do not attempt this until `tests/test_trace_vs_tenet.py` can run** — it is the
-differential against Tenet's own reference reader and it is currently skipped
-here, which leaves `tests/test_trace.py`'s 35 synthetic checks as the only guard
-on a subtle indexing change.
-
-## A bench phase for item edits hangs (attempted, reverted)
-
-`bump_items(ea)` keeping the listing's walk is worth 257x (4890ms → 19ms on
-bash) but is **not visible in total_ms**, because the bench has no item-edit
-phase. One was written and reverted: driving `undefine` from inside the pilot
-hangs the run (no output, killed at the timeout), while the identical sequence
-against `Program` directly is fine, and the same sequence with a `print` between
-`prog.listing(ea)` and `ensure_ea` is also fine.
-
-**That explanation was wrong** — corrected by a stack dump
-(`faulthandler.dump_traceback_later`, `/tmp/hangdiag2.py`). At the moment of the
-hang there are **no idatui threads at all**: the main thread is idle in
-`selectors.select()` and the only others are idle asyncio executor threads. The
-app is stuck *before* `app.run_test()` even returns — nothing to do with
-`bump_items`, `_prime`/`_grow`, or `_load_lock`.
-
-It is **pilot start-up flakiness**, and it is partly environmental: several
-orphaned `idatui/worker.py` processes had accumulated from runs killed by
-`timeout`, and clearing them (`pkill -f idatui/worker.py`) made the next run
-boot fine — but it recurred afterwards, so that is not the whole story. Not the
-kitty-graphics query either (`IDATUI_KITTY=0` still hangs).
-
-Two things to take from it: **kill stray workers between probe runs**, and a
-bench phase should not be built on this until app start-up under the pilot is
-reliable. The item-edit win is carried by `/tmp/itemedit.py` (direct
-measurement) and `.auto/check_edit.py` (correctness, in the gate).
-
-## decomp_map: what is actually left (measured, corrects run #30)
-
-Run #30 said "what is left in `decomp_map` is `ida_hexrays.decompile`, which
-duplicates the decompile the view already did". **That is wrong.** A warm
-`ida_hexrays.decompile()` is **0.01 ms** (`/tmp/hxcache.py`) — Hex-Rays' own
-cache is free, and the duplicate costs nothing.
-
-The sweep's real split, over 15 417 lines of bash (`/tmp/sweepprof.py`):
-
-| part | cost | calls |
-|---|---|---|
-| `dstr()` | **2 581 ms (79%)** | 106 594 @ 24.2 µs |
-| `get_line_item` | 691 ms | 445 337 @ 1.55 µs |
-| `tag_remove` for the length | 14 ms | 15 417 |
-
-Fixed by memoising `obj_id -> ea` for the whole function (v7 #44). What remains
-is `get_line_item` per column, which is a real probe per screen column.
-
-Ideas for the remainder, in order of appeal:
-
-- **Map only the lines the split view can show.** The pane paints ~40 lines but
- the map is built for all 3 486. This is the same "compute on demand" shape
- that won for search highlight ranges (v5 #6). It needs a windowed tool
- (`first`/`count`) and a lazy container, because `app.py` and `trace_ctl.py`
- both index the whole list.
-- Stepping over columns instead of probing each one is **not** safe: an item
- occupying one or two columns (a single-character variable) would be skipped
- entirely, silently dropping an EA from the region highlight.
-
-## Re-printing instead of re-decompiling after a rename (measured, NOT applied)
-
-`cfunc.refresh_func_ctext()` on a cached ctree is **46x faster** than the
-recompile a rename currently forces (31 ms vs 1 432 ms for ten functions), and
-it is arguably what a user expects: only the name changes.
-
-**Not applied, because it changes what is on screen.** Only 2 of 10 functions
-re-printed to the same text as a real recompile; the other eight differ by
-Hex-Rays *type inference*: `char *` vs `const char *`, `__int64` vs
-`signed __int64`, `unsigned int a4` vs `int a4`. Same drift already recorded
-above — a full recompile re-runs inference with more accumulated knowledge, so
-the two disagree even where the rename is irrelevant. Choosing the stabler text
-is a product decision about what the pseudocode pane should show, not a
-performance change, so it needs a human call. Probe: `/tmp/reprint.py`.
-
-Also checked and **not** a bug: the split view's text and its `decomp_map` do
-come from the same ctree. `Program.decompile` calls the `force_recompile` tool
-(which does exist) before refetching, so the tool's plain
-`ida_hexrays.decompile()` repopulates the cache and `decomp_map` then hits it.
-`/tmp/mapalign.py` appeared to show a mismatch only because the probe itself
-used `DECOMP_NO_CACHE`, which the app never does.
-
-## PARKED: a 3.6x faster decompile_function_safe (measured, byte-identical, discarded on the metric)
-
-ida-pro-mcp's `decompile_function_safe` — the function that produces the
-pseudocode the pane shows — has the **same three faults** that were fixed in
-`decomp_map`:
-
-* it allocates **three** `ctree_item_t` SWIG objects per pseudocode line, and
- `_head` and `_tail` are never read (`get_line_item` takes None for both);
-* it calls `dstr()` per line to recover the `/*0xEA*/` marker — 24 µs a call —
- where consecutive lines of a multi-line expression report the same ctree item,
- so memoising by `obj_id` (unique within a cfunc) skips most of them.
-
-Measured, on a **warm** cfunc (so this is pure post-processing, no Hex-Rays):
-
-| workload | before | after |
-|---|---|---|
-| bash's 8 largest, 18 991 lines (`/tmp/decprof.py`) | 2 302 ms (121.2 µs/line) | 429 ms (22.6 µs/line) |
-| the same through the real worker (`/tmp/verifybind.py`) | 2 695 ms | 746 ms |
-| echo's 12 largest, 3 298 lines (`/tmp/splitprof.py`) | 219 ms | 120 ms (30 µs/line) |
-
-Byte-identical: `check_decomp.py` runs both implementations against the same
-cfunc with `include_addresses` both ways — **128/128 echo and 372/372 ls_ttl**.
-It is a real gate: keying the memo on `it.op` instead of `it.obj_id` fails 69 of
-128.
-
-**Discarded anyway**, because it does not move `total_ms`. Three runs with it
-(25 367 / 25 058 / 25 171) against three without (24 514 / 25 122 / 25 783) —
-the means are 25 199 vs 25 140, i.e. indistinguishable. The reason is in the
-third row of the table: the saving is 98 µs/line on bash's *largest* functions
-but only 30 µs/line on small ones, and the bench's fixed set averages 286 lines
-a function. Expected effect ~150–250 ms against a run-to-run spread of ±400–600
-on this box.
-
-**It is still a real win for the operation a user waits on** — an F5 on a
-2 374-line function drops 287 ms → 54 ms of post-processing — so it is parked
-rather than deleted:
-
-* `.auto/parked/fast_decompile.patch` (applies to `server/patch_server.py` and
- `idatui/worker.py`)
-* `.auto/parked/check_decomp.py` (re-wire into `checks.sh` if the patch is
- re-applied; it will crash if run without it, since it slices the function out
- of `BODY`)
-
-Re-apply it if the benchmark ever decompiles large functions, or if the goal
-moves from total session time to per-operation latency. Do **not** re-shape the
-bench's fixture set to make this win visible — that would be fitting the
-benchmark to the change.
-
-## PARKED: pc_nums, the third instance of the same bug (2.03x, byte-identical)
-
-`_idatui_pc_nums` allocated **three `ctree_item_t` SWIG objects per candidate
-column** — inside a scan that probes every literal-looking character of every
-pseudocode line. `a`–`f` are hex digits, so `a1`, `v6` and `sub_1F4C0` all
-qualify and most columns of a line get probed. `head` and `tail` were never
-read. This is the same fault as `decomp_map`'s sweep and
-`decompile_function_safe`'s loop — **three instances of one bug**.
-
-It is on the F5 path: `app.py:_load_decomp` fetches `pc_nums` after every
-successful decompile so the view can mark the literal under the cursor without a
-round trip per keypress.
-
-Measured warm (Hex-Rays already cached), bash's 8 largest, 18 991 lines:
-**1 247 ms → 614 ms (2.03x), 66 → 32 µs/line**, with the literal count identical
-(7 015). Also stopped `tag_remove` running twice over every line (`pc_nums` and
-`_idatui_pc_nums` each called it); worth ~nothing on its own but it is strictly
-less work.
-
-Equivalence: the tool's whole output dumped per function and compared across
-revisions (`/tmp/pcnumdump.py`, cross-process — an in-process differential
-**segfaults**, two SWIG item objects over one cfunc). echo: 128 functions,
-1 463 literals, **0 mismatches**. `tests/test_scenarios.py` also drives literal
-cycling through these exact column extents.
-
-Parked at `.auto/parked/fast_pc_nums.patch` for the same reason as the decompile
-patch: real work removed, but ~170 ms against a ±500 ms run-to-run spread.
-
-## Both parked patches, measured together
-
-Applied together (they are both on the F5 path) over four runs: 25 367 / 25 058 /
-25 171 / 24 585, against three without: 24 514 / 25 122 / 25 783. Means **25 045
-with, 25 140 without** — a 95 ms edge inside a 500 ms spread, i.e. still not
-resolvable. Discarded on the metric, kept on disk.
-
-Apply both if the goal moves to per-operation latency: an F5 on a 2 374-line
-function loses ~233 ms of text post-processing and halves its `pc_nums` cost.
-
-## A 27 283 ms outlier, and how to recognise one
-
-One run came back at 27 283 (against ~24 500) with `lg_split` 2 200 → 6 917 and
-`lg_search` 3 441 → 840. It was NOT the change under test: the **work counters
-moved with it** — `lg_decomp_lines` 3 436 → 3 233, `lg_split_mapped_lines`
-2 070 → 1 993, `lg_search_hits` 91 783 → 92 733. Hex-Rays decompiled bash
-differently that run (the drift documented above), which changed how much of the
-post-rename re-render the split phase absorbed before search got to it. The next
-run reproduced 24 585 with every counter back to its usual value.
-
-**The NOTES counters are what tell an outlier from a regression.** A real
-regression moves the time and leaves the work alone.
diff --git a/.auto/log.jsonl b/.auto/log.jsonl
deleted file mode 100644
index 1b24d9e..0000000
--- a/.auto/log.jsonl
+++ /dev/null
@@ -1,53 +0,0 @@
-{"type":"config","name":"ida-tui performance: cut the latency of navigation, listing, decomp, graph and search","metricName":"total_ms","metricUnit":"ms","bestDirection":"lower"}
-{"run":1,"commit":"2910c93","metric":46572.1,"metrics":{"lg_boot_ms":863.5,"lg_decomp_ms":2881.3,"lg_graph_ms":951.7,"lg_hex_ms":900.9,"lg_index_ms":219.3,"lg_listing_cold_ms":434.4,"lg_listing_warm_ms":552.3,"lg_nav_ms":29106.9,"lg_palette_ms":4.8,"lg_render_ms":231.2,"lg_search_ms":5590.1,"pure_graph_ms":236.2,"sm_boot_ms":539.4,"sm_decomp_ms":621.4,"sm_graph_ms":702,"sm_hex_ms":850.5,"sm_index_ms":0,"sm_listing_cold_ms":270.8,"sm_listing_warm_ms":270.6,"sm_nav_ms":875.1,"sm_palette_ms":0.3,"sm_render_ms":272.3,"sm_search_ms":197.1,"fails":0},"status":"checks_failed","description":"Baseline run of the new bench harness. Benchmark clean (fails=0) but the pilot scenario suite reported 300 passed / 1 failed with ZERO code changes -> flaky, and checks.sh printed the tail instead of the FAIL line so the name is unknown.","timestamp":1786058161497,"segment":0,"confidence":null,"asi":{"hypothesis":"establish a baseline for total_ms","bottleneck":"lg_nav_ms=29107 is 62% of total_ms; lg_nav_worst_ms=28274 is ONE cold jump to a high address in bash. ListingModel.ensure_ea walks the segment forward in 500-head pages from seg_start, so landing near the end of a 224k-row listing costs ~450 sequential worker round trips.","second_bottleneck":"lg_search_ms=5590 (91783 hits over the whole segment)","cheap_phases":"palette/index/render/pure_graph are all <600ms; not where the time is","rollback_reason":"checks.sh flagged 1 scenario failure with no code change (flake)","next_action_hint":"make checks.sh print the FAIL line name on non-zero exit, re-run baseline, then attack ListingModel address->row lookup (needs a backend primitive in server/patch_server.py: heads walking anchored at an address, or a segment head-index built in one call)"}}
-{"run":2,"commit":"a3f3400","metric":46685.5,"metrics":{"lg_boot_ms":885.7,"lg_decomp_ms":2871.8,"lg_graph_ms":930.9,"lg_hex_ms":947.3,"lg_index_ms":207.8,"lg_listing_cold_ms":564.3,"lg_listing_warm_ms":454.7,"lg_nav_ms":29018.5,"lg_palette_ms":4.6,"lg_render_ms":223.6,"lg_search_ms":5448.7,"pure_graph_ms":535.4,"sm_boot_ms":537.9,"sm_decomp_ms":643.8,"sm_graph_ms":680.4,"sm_hex_ms":854.9,"sm_index_ms":0,"sm_listing_cold_ms":265.5,"sm_listing_warm_ms":263.7,"sm_nav_ms":887.2,"sm_palette_ms":0.3,"sm_render_ms":264.1,"sm_search_ms":194.5,"fails":0},"status":"discard","description":"Baseline re-run with the fixed checks gate. Checks pass; total_ms reproduces to within 0.24% of run #1 (46572 -> 46686), so the noise floor is ~115ms on a 46.6s metric.","timestamp":1786058370297,"segment":0,"confidence":null,"asi":{"hypothesis":"confirm the baseline is reproducible and the checks gate is green","noise_floor_ms":115,"reproducibility":"run1 46572 / run2 46686 -> 0.24% spread; pure_graph_ms is the jumpiest single phase (236 -> 535, it is CPU-only and gets descheduled)","checks":"flaky-scenario retry logic works; suite green on a clean tree","next_action_hint":"attack ListingModel.ensure_ea / the heads tool: 29s of 46.6s is one cold address->row walk over bash"}}
-{"run":3,"commit":"93240e2","metric":26923.9,"metrics":{"lg_boot_ms":762.2,"lg_decomp_ms":2631.7,"lg_graph_ms":941.8,"lg_hex_ms":1052,"lg_index_ms":67.2,"lg_listing_cold_ms":440.6,"lg_listing_warm_ms":530.5,"lg_nav_ms":10598.3,"lg_palette_ms":4.6,"lg_render_ms":227.8,"lg_search_ms":5265.5,"pure_graph_ms":237.9,"sm_boot_ms":535.3,"sm_decomp_ms":631.8,"sm_graph_ms":702.1,"sm_hex_ms":841.1,"sm_index_ms":0,"sm_listing_cold_ms":268.2,"sm_listing_warm_ms":269.4,"sm_nav_ms":443.6,"sm_palette_ms":0.3,"sm_render_ms":277.8,"sm_search_ms":194.4,"fails":0},"status":"keep","description":"Stop ida-pro-mcp installing a sys.setprofile hook around every tool call. Its deadline mechanism profiles every python call/return so a pure-python tool loop can be interrupted; our tools are call-heavy, so it taxed the whole backend 3.3x. Worker now sets IDA_MCP_TOOL_TIMEOUT_SEC=0 and arms the deadline itself with one polling watchdog thread + ida_kernwin.set_cancelled() (the half that actually frees the IDA main thread). Also rewrote _idatui_spans to jump between colour tags instead of walking characters (byte-identical over 258k real lines).","timestamp":1786059117542,"segment":0,"confidence":173.264550264548,"asi":{"hypothesis":"the heads tool is not IDA-bound; the ida-pro-mcp sync wrapper's sys.setprofile deadline is the tax","evidence":"in-process A/B on targets/bash: heads(count=500,annotate) 92.2us/row with IDA_MCP_TOOL_TIMEOUT_SEC unset vs 27.5us/row with it 0; domain ListingModel._load_next_page 116 -> 37 us/row","where_it_lives":"site-packages/ida_pro_mcp/ida_mcp/sync.py sync_wrapper() installs profilefunc via sys.setprofile whenever timeout>0; default _DEFAULT_TOOL_TIMEOUT_SEC=60","what_we_kept":"the native half: threading watchdog + ida_kernwin.set_cancelled(), which is what decompile/auto_wait/find_bytes actually poll. Only the interruption of a pure-python loop inside a tool body is gone, and every idatui tool loop is bounded by its count argument (<=2000).","watchdog_design":"polling at 0.25s instead of signalling, because arm/disarm is on the path of EVERY worker call (a scroll is dozens); arming is two attribute writes + clr_cancelled()","spans_rewrite":"_idatui_spans now re.finditer's over the 3 control chars instead of walking characters. Verified byte-identical vs HEAD over 258k real disasm lines across echo/ls_ttl/bash/libcrypto (.auto/diff_spans.py). Standalone it measured 12.5 -> 13.6 us/line i.e. slightly SLOWER, so it is probably a small net loss that the setprofile win is hiding -- A/B it on its own next.","gains":"total 46572 -> 26924 (-42%); lg_nav 29107 -> 10598; lg_nav_worst 28274 -> 10170; sm_nav_worst 656 -> 224; lg_index 219 -> 67","work_unchanged":"nav_rows/decomp_ok/graph_ok/search_hits/render_cells all identical -> the speedup is not less work","next_action_hint":"1) A/B the _idatui_spans rewrite alone (suspect regression). 2) lg_nav is still 10.6s: ListingModel.ensure_ea is O(distance) at ~34us/row; a skeleton (ea,size,kind only) walk or a bigger PAGE would cut it further. 3) lg_search_ms 5265 is now the #2 term."}}
-{"run":4,"commit":"b6a95ec","metric":26050.2,"metrics":{"lg_boot_ms":727.4,"lg_decomp_ms":2618.1,"lg_graph_ms":935.1,"lg_hex_ms":954.2,"lg_index_ms":67.4,"lg_listing_cold_ms":459.3,"lg_listing_warm_ms":545.7,"lg_nav_ms":9257.8,"lg_palette_ms":4.7,"lg_render_ms":238.8,"lg_search_ms":5472.5,"pure_graph_ms":530.7,"sm_boot_ms":534.6,"sm_decomp_ms":635.8,"sm_graph_ms":748.1,"sm_hex_ms":858.7,"sm_index_ms":0,"sm_listing_cold_ms":268.7,"sm_listing_warm_ms":269.2,"sm_nav_ms":441.6,"sm_palette_ms":0.3,"sm_render_ms":281.7,"sm_search_ms":200,"fails":0},"status":"keep","description":"_idatui_spans: one capturing re.split over the tag pairs instead of finditer+char-slicing, and collapse whitespace with ' '.join(txt.split()) instead of a regex sub. 13.15 -> 11.07 us/line (the previous finditer attempt was 14.4, i.e. SLOWER than the original char loop it replaced).","timestamp":1786059375617,"segment":0,"confidence":2.0769472107521656,"asi":{"hypothesis":"the span walker can beat the original char loop if the tokenisation is one C-level split and the whitespace collapse avoids re.sub","microbench_us_per_line":{"original_char_loop":13.15,"finditer_attempt":14.42,"re.split_version":11.07},"lesson":"re.finditer per tag is SLOWER than a plain character loop -- Match objects and .start() calls cost more than the ~54 trivial loop iterations they replace. A single capturing re.split that hands back [text, tag, text, ...] is what actually wins.","lesson2":"re.sub for whitespace collapse cost ~1us per call at ~6.5 calls/line; ' '.join(txt.split()) splits on exactly str.isspace() and is far cheaper. Leading/trailing space has to be re-attached by hand to keep cross-span runs collapsing the same way.","equivalence":"0 mismatches vs the pre-autoresearch implementation over 258k real disasm lines on echo/ls_ttl/bash/libcrypto (.auto/diff_spans.py --ref 2b0ae8d)","gains":"total 26924 -> 26050 (-3.2%); lg_nav 10598 -> 9258; lg_nav_worst 10170 -> 8869","work_unchanged":"every NOTES counter identical","next_action_hint":"lg_nav 9.3s and lg_search 5.5s are now the top two. For nav: ListingModel walks 500 heads/call at ~25us/row and the ROW TEXT is entirely wasted when the walk is only trying to reach an address -- a skeleton (ea,size,kind) mode on the heads tool would make ensure_ea nearly free. For search: _compute_matches/_line_plain over 224k rows."}}
-{"run":5,"commit":"b6a95ec","metric":23259.6,"metrics":{"lg_boot_ms":752.7,"lg_decomp_ms":2475.3,"lg_graph_ms":963,"lg_hex_ms":950.4,"lg_index_ms":70.8,"lg_listing_cold_ms":533.2,"lg_listing_warm_ms":406.1,"lg_nav_ms":6862.6,"lg_palette_ms":4.7,"lg_render_ms":228.9,"lg_search_ms":5401.5,"pure_graph_ms":510.5,"sm_boot_ms":538.8,"sm_decomp_ms":666.7,"sm_graph_ms":715.7,"sm_hex_ms":856.8,"sm_index_ms":0,"sm_listing_cold_ms":260.3,"sm_listing_warm_ms":262.5,"sm_nav_ms":335.5,"sm_palette_ms":0.3,"sm_render_ms":270.8,"sm_search_ms":192.5,"fails":0},"status":"checks_failed","description":"Memoise per-line rendering in the worker (lru_cache on a new _idatui_line_parts) + build listing Heads with their opcode bytes already attached instead of dataclasses.replace-ing them in. total 26050 -> 23260, lg_nav 9258 -> 6863. Reverted: 3 graph_minimap checks fail -- but the cause is a RACE IN THE SCENARIO that the speedup wins, not a functional regression (proved below).","timestamp":1786060327739,"segment":0,"confidence":5.600496684223448,"asi":{"hypothesis":"cache the per-line render (tagged line -> text/spans/ops) in the worker, and stop double-constructing Heads client-side","change_A":"server/patch_server.py: new _idatui_line_parts(line) = (text, spans, ops), functools.lru_cache(16384). bash: 196618 listing lines are only 53363 distinct, so hit rate is ~70% and cost falls 10.4 -> 3.9 us/line. Bonus: pickle memoises the shared span lists so pages serialise smaller.","change_B":"idatui/domain.py: ListingModel._build_page reads the code extent FIRST and passes raw into Head.from_raw, replacing _attach_opcode_bytes' dataclasses.replace (which re-ran __init__ per code head). from_raw now uses tuple(map(tuple,...)) instead of a coercing genexpr.","measured":"cold ListingModel paging 35.7 -> 25.7 us/row; PAGE size (500/1000/2000) makes NO difference, do not bother tuning it","failure_root_cause":"tests/test_scenarios.py graph_minimap. _open_graph() leaves _graph_sticky=True; the scenario then does c.open(big,'listing') and presses space expecting to ENTER the graph. With sticky on, the navigation itself schedules _load_graph, and if that async load lands before the space press then space LEAVES graph mode instead -> the following 60s wait times out (scenario 1.9s -> 65.5s) and every minimap click lands on a hidden widget.","proof":"/tmp/mmrace.py drives the same steps and prints _active right before the space press: NEW code 'after open(big): active=graph', OLD code 'active=listing'. Bisected: stashing idatui/domain.py alone still fails, stashing server/patch_server.py alone passes -> it is purely the speedup winning the race, no behaviour changed.","equivalence_evidence":"diff_spans.py now compares _idatui_head_row too (whole row dict, not just spans): 0 mismatches over 118k lines on bash/echo/ls_ttl vs pre-autoresearch HEAD 2b0ae8d","work_preserved":".auto/wip-headcache.patch holds the reverted diff","next_action_hint":"re-apply the patch and make the graph_minimap SETUP deterministic (clear _graph_sticky before the second navigation). Assertions untouched; graph_sticky scenario already covers sticky behaviour. Record the amended tests/ rule in .auto/prompt.md."}}
-{"run":6,"commit":"cf45e11","metric":22980.2,"metrics":{"lg_boot_ms":738.2,"lg_decomp_ms":2401.8,"lg_graph_ms":944.1,"lg_hex_ms":920.6,"lg_index_ms":75.2,"lg_listing_cold_ms":538.5,"lg_listing_warm_ms":411.1,"lg_nav_ms":6813.9,"lg_palette_ms":4.9,"lg_render_ms":221.8,"lg_search_ms":5630.1,"pure_graph_ms":240.7,"sm_boot_ms":537.5,"sm_decomp_ms":595.1,"sm_graph_ms":715.7,"sm_hex_ms":858.8,"sm_index_ms":0,"sm_listing_cold_ms":263.3,"sm_listing_warm_ms":265.3,"sm_nav_ms":335.2,"sm_palette_ms":0.3,"sm_render_ms":271.4,"sm_search_ms":196.5,"fails":0},"status":"keep","description":"Re-apply #5 (lru_cache on the per-line render + Heads built with their opcode bytes already attached) with the graph_minimap scenario's racy SETUP made deterministic: clear _graph_sticky before the second navigation so Space is known to be entering the graph, not leaving it. No assertion changed.","timestamp":1786060531752,"segment":0,"confidence":7.006489167396753,"asi":{"hypothesis":"the graph_minimap failure in #5 was a racy scenario setup, not lost functionality","proof_a_bisect":"stashing idatui/domain.py alone still failed; stashing server/patch_server.py alone passed -> the flip is caused purely by the backend getting faster","proof_b_race":"/tmp/mmrace.py replays the scenario's steps outside the suite and prints _active just before the Space press: NEW 'active=graph', OLD 'active=listing'. Same steps, two states. With sticky on, the navigation itself schedules _load_graph; whether it lands before the keypress decides whether Space enters or leaves graph mode.","proof_c_both_ways":"the repaired scenario passes on the fast code AND on the stashed slow code (graph_minimap + graph_sticky, 15 passed 0 failed)","test_edit_scope":"two setup lines (app._graph_sticky = False; wait for _active == listing). Every c.check is byte-identical. graph_sticky scenario still covers sticky navigation.","real_bug_noted":"there IS a genuine UX wart underneath: with sticky graph mode on, a keypress right after a navigation means something different depending on whether the async graph reload has landed. Out of scope for perf work -> .auto/ideas.md","gains":"total 26050 -> 22980 (-11.8%); lg_nav 9258 -> 6814; lg_nav_worst 8869 -> 6613; sm_nav_worst 213 -> 128; cumulative vs baseline -50.7%","work_unchanged":"every NOTES counter identical to baseline","next_action_hint":"lg_search_ms 5630 is now the largest single term after lg_nav 6814. Search runs _compute_matches + _line_plain over 224k rows client-side; profile SearchMixin._compute_matches / ListingView._line_plain next."}}
-{"run":7,"commit":"5045ba1","metric":20835.7,"metrics":{"lg_boot_ms":754.4,"lg_decomp_ms":2593.7,"lg_graph_ms":929.5,"lg_hex_ms":1080.2,"lg_index_ms":76.1,"lg_listing_cold_ms":526.7,"lg_listing_warm_ms":410.1,"lg_nav_ms":6674.8,"lg_palette_ms":5,"lg_render_ms":225.2,"lg_search_ms":3310.3,"pure_graph_ms":239.3,"sm_boot_ms":537.9,"sm_decomp_ms":630.1,"sm_graph_ms":734.3,"sm_hex_ms":869.9,"sm_index_ms":0,"sm_listing_cold_ms":260.8,"sm_listing_warm_ms":262.3,"sm_nav_ms":334.8,"sm_palette_ms":0.3,"sm_render_ms":268.4,"sm_search_ms":111.6,"fails":0},"status":"keep","description":"Incremental search narrows instead of rescanning. Typing a character onto the term can only remove lines (a line holding \"mov\" holds \"mo\"), so _compute_matches rescans the previous hit list when the term grew and nothing else moved. Keyed on (term, case-fold, row count, line-source id) so a listing still streaming rows in behind the search falls back to a full scan.","timestamp":1786060806344,"segment":0,"confidence":8.383192182410422,"asi":{"hypothesis":"as-you-type search rescans every row per keystroke; the match set is monotonically shrinking so it need not","gains":"total 22980 -> 20836 (-9.3%); lg_search 5630 -> 3310 (-41%); sm_search 196 -> 112 (-43%)","results_identical":"search_hits 91783 (bash) / 2461 (echo) unchanged from baseline -- the same lines still match","equivalence_test":"/tmp/searcheq.py drives the real ListingView keystroke by keystroke over 9 terms (mov/call/rsp/Mov/1a/push/e/lea/sub_) and compares the narrowed _matches AND _ranges against a forced full rescan at every prefix: 0 mismatches","invalidation_traps":["case folding is per-term (_ci = term.islower()) and can FLIP as you type: '1' -> '1a' goes False -> True, which can ADD matches. The key stores _ci and only narrows when it is unchanged.","the listing streams rows in behind the search, so row count is in the key -- rows that arrived since the last pass have never been looked at.","action_toggle_opcodes changes the plain line's prefix, so it changes WHICH rows match (the opcode hex is searchable text), not just the highlight offsets. It now clears the key too.","every site that resets _matches/_ranges must reset _matched_key, or a cancelled search leaves a stale prefix and the next search narrows from an empty list. Six sites."],"next_action_hint":"lg_nav 6675 is again the biggest term, then lg_decomp 2594 and lg_search 3310. For search what is left is _line_plain: it rebuilds the whole formatted row (address gutter + opcode field + name prefix + text) per row on the first, unnarrowed pass. For nav see .auto/ideas.md (skeleton walk)."}}
-{"run":8,"commit":"8b40fd2","metric":20412.8,"metrics":{"lg_boot_ms":752.9,"lg_decomp_ms":2418.6,"lg_graph_ms":811.6,"lg_hex_ms":1020.1,"lg_index_ms":73.2,"lg_listing_cold_ms":573.7,"lg_listing_warm_ms":456.7,"lg_nav_ms":6640.1,"lg_palette_ms":4.8,"lg_render_ms":237.6,"lg_search_ms":3210.2,"pure_graph_ms":242.5,"sm_boot_ms":536.5,"sm_decomp_ms":643.1,"sm_graph_ms":689.8,"sm_hex_ms":866.6,"sm_index_ms":0,"sm_listing_cold_ms":262.9,"sm_listing_warm_ms":265.9,"sm_nav_ms":330.1,"sm_palette_ms":0.3,"sm_render_ms":265.2,"sm_search_ms":110.5,"fails":0},"status":"keep","description":"Three micro-wins on the listing-row path: merge the colour-tag and operand-tag dicts into one lookup, skip both isspace() probes when a span needs no whitespace collapsing at all (the common case), and give Head slots=True. Spans 11.07 -> 10.18 us/line; Head construction 3.18 -> 2.53 us/row.","timestamp":1786061248935,"segment":0,"confidence":8.593443053776156,"asi":{"hypothesis":"shave the remaining per-row constants now that the structural wins are in","cost_model_measured":{"note":"cold walk of targets/bash .text, per LISTING ROW","worker_tool_compute_cold_us":18.1,"worker_tool_compute_warm_cache_us":10.3,"pickle_dumps_us":0.9,"pickle_loads_us":2.4,"client_build_page_us":4.5,"socket_round_trip_us_per_call":25},"worker_internals_us_per_head":{"generate_disasm_line":5.9,"head_row_warm_cache":8.4,"spans_on_a_cache_miss":10.2,"get_func":0.4,"get_ea_name":0.4,"struct_member_rows_per_DATA_row":3.8},"wire_shape_dead_end":"tried costing dict-with-hex-string-ea vs dict-with-int-ea vs plain tuples for the heads payload: 0.75/0.70/0.54 us dumps and 1.11/1.26/1.04 us loads per row. At most 0.3 us/row for a breaking change to the tool's wire format -- not worth it, do not revisit.","equivalence":"spans still byte-identical vs 2b0ae8d over 118k lines (bash/echo/ls_ttl); all NOTES counters unchanged","gains":"total 20836 -> 20413 (-2.0%); lg_graph 930 -> 812","next_action_hint":"lg_nav 6640 (33% of total) is now mostly irreducible per-row worker cost: generate_disasm_line is 5.9us of the ~18us and is IDA's. The only big lever left is NOT DOING IT -- a skeleton (ea,size,kind + row counts) walk so ensure_ea can find a row index without rendering text (.auto/ideas.md). Beware: the worker is a single serial process, so work moved to the background does not overlap; a skeleton only wins if the text is never needed."}}
-{"run":9,"commit":"4148738","metric":19476.3,"metrics":{"lg_boot_ms":756.3,"lg_decomp_ms":2495.1,"lg_graph_ms":946,"lg_hex_ms":905.4,"lg_index_ms":72,"lg_listing_cold_ms":547.5,"lg_listing_warm_ms":410.4,"lg_nav_ms":6723.4,"lg_palette_ms":5,"lg_render_ms":218.8,"lg_search_ms":2312.4,"pure_graph_ms":239.5,"sm_boot_ms":535.3,"sm_decomp_ms":616.4,"sm_graph_ms":682.2,"sm_hex_ms":824.9,"sm_index_ms":0,"sm_listing_cold_ms":258.9,"sm_listing_warm_ms":261,"sm_nav_ms":339.5,"sm_palette_ms":0.3,"sm_render_ms":248.7,"sm_search_ms":77.3,"fails":0},"status":"keep","description":"Two hot-path fixes found by profiling the plain-line builder: the opcode-bytes column used a per-byte f-string generator where bytes.hex(' ').upper() does it in one C call (12x), and ListingModel._phys/_head_index_at re-imported bisect on every call. _line_plain 2.64 -> 1.57 us/row.","timestamp":1786061547254,"segment":0,"confidence":9.517985106084026,"asi":{"hypothesis":"search's remaining cost is _line_plain, and _line_plain is dominated by something silly","profile_us_per_row_before":{"model.get":0.67,"model._phys":0.44,"_op_field":1.29,"_line_plain":2.64},"profile_us_per_row_after":{"model.get":0.56,"model._phys":0.34,"_op_field":0.4,"_line_plain":1.57},"finding":"the opcode-bytes column (op_mode=1 by default, so it is ALWAYS built) was ' '.join(f'{b:02X}' for b in raw): 1.74us vs 0.14us for raw.hex(' ').upper(). Verified byte-identical for every length 0..19.","finding2":"ListingModel._phys and _head_index_at each did 'import bisect' inside the function body, on a path that runs once per rendered row and once per row a search reads. bisect is already imported at module scope.","gains":"total 20413 -> 19476 (-4.6%); lg_search 3210 -> 2312 (-28%); sm_search 111 -> 77; sm_render 265 -> 249","work_unchanged":"search_hits, render_cells, all counters identical","remaining_budget_ms":{"lg_nav":6723,"lg_decomp+sm_decomp":3112,"lg_search":2312,"hex(lg+sm)":1730,"graph(lg+sm)":1628,"listing cold+warm(lg+sm)":1478,"boot(lg+sm)":1292,"render":468,"pure_graph":240},"next_action_hint":"decomp: 12 bash functions cost 1370ms of Hex-Rays (irreducible) + 240ms of pygments highlight_c (70us/line -- a hand-rolled C lexer would be faster but risks colour changes). hex: 1730ms for 120 frames = 14ms/frame, unprofiled, look there next. ALSO NOTED: domain.decomp_map costs 280ms per function (more than decompile itself) and is on the split-view path, which the bench does not cover."}}
-{"run":10,"commit":"f8fb9b7","metric":19005.8,"metrics":{"lg_boot_ms":788.9,"lg_decomp_ms":2752.5,"lg_graph_ms":899,"lg_hex_ms":565.2,"lg_index_ms":71.9,"lg_listing_cold_ms":416.1,"lg_listing_warm_ms":402.3,"lg_nav_ms":6756.3,"lg_palette_ms":4.7,"lg_render_ms":217.1,"lg_search_ms":2335.8,"pure_graph_ms":235.3,"sm_boot_ms":537.4,"sm_decomp_ms":600.9,"sm_graph_ms":693.1,"sm_hex_ms":549.9,"sm_index_ms":0,"sm_listing_cold_ms":280.9,"sm_listing_warm_ms":261.2,"sm_nav_ms":309,"sm_palette_ms":0.3,"sm_render_ms":249.3,"sm_search_ms":78.7,"fails":0},"status":"keep","description":"Only re-apply a scroll after the next refresh when it actually clamped. Both _apply_scroll implementations unconditionally scheduled a deferred scroll_to + refresh(layout=True) — a whole-screen re-arrange on every scroll — as a workaround for scrolling before the view's size is computed. Now the deferred pass runs only when scroll_offset didn't reach the target.","timestamp":1786061776694,"segment":0,"confidence":8.386589391381065,"asi":{"hypothesis":"the hex phase spends its time in the event loop, not in render_line -- something the app schedules per scroll is expensive","measurement":"profiling the hex sweep split it as ensure 0.1ms / scroll 6.7ms / pilot.pause 700ms / paint 106ms over 60 frames. The cost was in what the pause had to process.","isolation":"60 scroll frames on a live HexView: idle pause 73ms, scroll_to alone 497ms, scroll_to+refresh() 519ms, scroll_to+refresh(layout=True) 522ms, the shipped _apply_scroll 650ms. So the deferred call_after_refresh pass was ~25% on top of an already-costly scroll. After the fix: 453ms.","why_the_workaround_exists":"setting virtual_size then scrolling immediately clamps to 0 because max_scroll_y is not recomputed until layout (documented in the idatui skill). Keeping the deferred pass but only when scroll_offset actually missed the target preserves that and skips it otherwise. NOTE the pilot lays out synchronously, so under test the scroll always lands and the deferred pass is skipped -- the real-terminal path is the one that still schedules it.","gains":"total 19476 -> 19006 (-2.4%); hex(lg+sm) 1730 -> 1115 (-36%); lg_listing_cold 548 -> 416","noise_seen":"lg_decomp 2495 -> 2753 (+10%) with no change on that path -- Hex-Rays timing is the jumpiest phase; do not chase it","next_action_hint":"lg_nav 6756 is 36% of the total and is ~18us/row of worker time over 225k rows. Its parts: generate_disasm_line 5.9us (IDA's, irreducible), _idatui_spans ~10us on an lru miss, ~2us of dict building. Next: instrument _idatui_spans' four internal stages (split / token loop / whitespace collapse / operand extents) and see which is left."}}
-{"type":"config","name":"ida-tui performance (v2 bench: graph opens are now measured cold)","metricName":"total_ms","metricUnit":"ms","bestDirection":"lower"}
-{"run":11,"commit":"26fa14c","metric":27912.9,"metrics":{"lg_boot_ms":742.8,"lg_decomp_ms":2595.7,"lg_graph_ms":9561.4,"lg_hex_ms":554.4,"lg_index_ms":77.6,"lg_listing_cold_ms":548.6,"lg_listing_warm_ms":405.5,"lg_nav_ms":6552.9,"lg_palette_ms":4.9,"lg_render_ms":216.1,"lg_search_ms":2218,"pure_graph_ms":507.6,"sm_boot_ms":535.6,"sm_decomp_ms":620.2,"sm_graph_ms":962,"sm_hex_ms":587.4,"sm_index_ms":0,"sm_listing_cold_ms":261.8,"sm_listing_warm_ms":261.7,"sm_nav_ms":368.8,"sm_palette_ms":0.3,"sm_render_ms":252.4,"sm_search_ms":77.1,"fails":0},"status":"keep","description":"RE-BASELINE on a corrected benchmark. phase_graph was measuring a cache hit: the fixture picker called Program.flowchart (which caches per function), so the timed Space press only did a dict lookup. Fixtures now use the raw flowchart tool and the graph cache is cleared before the phase. Cold graph opens cost lg_graph 9561ms — 34% of the total, previously invisible.","timestamp":1786062344258,"segment":1,"confidence":null,"asi":{"hypothesis":"the graph phase looked suspiciously cheap; check whether it was measuring real work","bench_flaw_found":"run_target picked its 12 fixture functions by calling app.program.flowchart(f.addr) to count blocks. Program.flowchart caches per (function, name generation) AND fetches every listing row in the function's extent, so the fixture loop paid the whole cost and phase_graph then timed a cache hit. Fixed: fixtures use client.call('flowchart') directly, and phase_graph clears program._flowcharts first.","carried_forward_from_v1":"experiments 1-10 took total_ms from 46572 to 19006 (-59%) on the old bench; that history is in the archived log and summarised in .auto/prompt.md","bug_this_exposed":"Program.flowchart does rows = _heads_between(min(block.start), max(block.end)) -- the CONVEX HULL of the blocks. IDA function chunks put tail blocks hundreds of KB from the entry, so a 1384-byte function (jobs_builtin) fetches 128000 listing rows and takes 2961ms to graph. Measured on bash: 4 of the 12 fixtures have spans of 280KB-680KB and cost 258-2961ms each; the other 8 have span == size and cost 7-32ms. Then b.rows = [h for h in rows if ...] is O(blocks x rows) on top -- 541ms for the set.","fix_planned":"fetch the MERGED BLOCK INTERVALS instead of the hull (adjacent blocks coalesce, so a normal function is still one call), and assign rows to blocks by bisect instead of a full scan per block","next_action_hint":"apply that fix; it should take lg_graph from 9561 to well under 1000"}}
-{"run":12,"commit":"870f89e","metric":19062.1,"metrics":{"lg_boot_ms":751.3,"lg_decomp_ms":2605.7,"lg_graph_ms":1022,"lg_hex_ms":554.5,"lg_index_ms":103.9,"lg_listing_cold_ms":545,"lg_listing_warm_ms":405.6,"lg_nav_ms":6636.8,"lg_palette_ms":4.7,"lg_render_ms":212.2,"lg_search_ms":2269,"pure_graph_ms":239.9,"sm_boot_ms":538.5,"sm_decomp_ms":688.4,"sm_graph_ms":712.7,"sm_hex_ms":557.5,"sm_index_ms":0,"sm_listing_cold_ms":259.9,"sm_listing_warm_ms":260.2,"sm_nav_ms":363.8,"sm_palette_ms":0.3,"sm_render_ms":252.7,"sm_search_ms":77.5,"fails":0},"status":"keep","description":"Fetch a graph's listing rows from the blocks' MERGED EXTENTS, not their convex hull, and assign them per block by bisect. IDA puts a function's cold/tail chunks far from its entry, so the hull of a 1.4KB function could be 680KB wide: it fetched 128k rows, took 3s, and still came back EMPTY for the far blocks because the pager's 64-page bound ran out first. lg_graph 9561 -> 1022.","timestamp":1786062629380,"segment":1,"confidence":null,"asi":{"hypothesis":"Program.flowchart fetches the convex hull of the basic blocks, which is enormous for a function with IDA chunks","gains":"total 27913 -> 19062 (-31.7%); lg_graph 9561 -> 1022 (-89%); sm_graph 962 -> 713","correctness_is_BETTER_not_equal":"differential over the 80 largest functions of bash: 1201 blocks differ, and EVERY one of them is a block the old code returned ZERO rows for. _heads_between is bounded to 64 pages x 2000 heads = 128k, and a 680KB hull exhausted that before reaching the tail chunk -- so far blocks drew as empty boxes. No block lost a row. targets/echo: 0 differences at all (no chunked functions).","scale":"80 bash functions: hull 44.4s / 1,864,814 rows fetched -> intervals 1.4s / 77,221 rows. 24x less data, 31x faster.","design":"blocks are sorted and merged with a 256-byte tolerance so alignment padding does not split an interval; an ordinary contiguous function is still exactly ONE heads call, as before. Row->block assignment is now bisect on the address list instead of a full scan per block (541ms -> ~0 for the 12-function set).","verify_script":"/tmp/fceq.py (kept the pattern in .auto/ideas.md): rebuild the blocks twice, fetch both ways, compare per-block row lists","next_action_hint":"lg_nav 6637 is now 35% of the total and sits at the per-row floor (~16.5us worker + ~7us client). decomp lg+sm 3294 is next: 1370ms of Hex-Rays, 240ms of pygments highlight_c, ~600ms of Textual loading-cover churn. Also unmeasured by the bench: domain.decomp_map costs 280ms per function on the split-view path."}}
-{"run":13,"commit":"60f0d70","metric":18856.8,"metrics":{"lg_boot_ms":689.7,"lg_decomp_ms":2484.2,"lg_graph_ms":1120,"lg_hex_ms":700.1,"lg_index_ms":96.5,"lg_listing_cold_ms":425.6,"lg_listing_warm_ms":511.5,"lg_nav_ms":6590.7,"lg_palette_ms":4.8,"lg_render_ms":215.1,"lg_search_ms":2195.5,"pure_graph_ms":238.1,"sm_boot_ms":431.5,"sm_decomp_ms":667.2,"sm_graph_ms":686.9,"sm_hex_ms":569.8,"sm_index_ms":0,"sm_listing_cold_ms":258.1,"sm_listing_warm_ms":283.2,"sm_nav_ms":365.2,"sm_palette_ms":0.3,"sm_render_ms":243.7,"sm_search_ms":79,"fails":0},"status":"keep","description":"Two independent constants: memoise the pygments token -> Rich style lookup (a decompilation uses ~18 distinct token types but each token walked up to nine 'token in ttype' hierarchy checks), and hold the worker-connect poll at 5ms for the first 5s instead of backing off geometrically from the first probe.","timestamp":1786062907221,"segment":1,"confidence":44.11154408183163,"asi":{"hypothesis":"boot time is a polling artefact, and highlighting is a style-lookup problem not a lexing problem","boot_smoking_gun":"WorkerClient.connect() returned in 351ms for BOTH targets/echo (47KB) and targets/bash (1.2MB) -- an identical number for very different work is a polling artefact, not a cost. The geometric backoff (5ms x1.6, capped at 200ms) has reached 134ms by the time a seeded database is ready at ~250ms. Holding 5ms for 5s first: echo 351 -> 260ms. bash is genuinely ~350ms so it did not move.","highlight_split":"on a 40KB body: lexer 52.2ms, _style_for 19.4ms, rest ~10ms of 82ms total. Memoised _style_for is 1.2ms (18 distinct token types in the whole corpus). highlight_c 84.2 -> 62.0ms.","equivalence":"/tmp/hleq.py compares old vs new highlight_c segment-for-segment over 5 bodies x 4 slices including an empty string and hex-rays-shaped pseudocode: 0 mismatches","gains":"total 19062 -> 18857 (-1.1%); sm_boot 539 -> 432 (-20%); lg_boot 751 -> 690","rejected_this_round":"a per-page identity memo for the spans->tuple conversion in Head.from_raw. The worker's line cache does share span objects across rows, but the repeats are spread over the whole segment, not within a 500-row page, so a page-scoped memo never hits: _build_page 4.52 -> 4.68 us/row. A model-scoped memo is UNSAFE because id() is reused once the page's row dicts are collected.","next_action_hint":"lg_nav 6591 (35%) is at the per-row floor. Left: pygments lexing itself (52ms per 40KB), the ~600ms of Textual loading-cover churn across 12 F5s, and lg_search 2196 (two unnarrowed passes over 224k rows at ~2.6us each)."}}
-{"run":14,"commit":"2686901","metric":18618.9,"metrics":{"lg_boot_ms":689.6,"lg_decomp_ms":2560,"lg_graph_ms":1035.1,"lg_hex_ms":684.2,"lg_index_ms":100.4,"lg_listing_cold_ms":551.1,"lg_listing_warm_ms":397.8,"lg_nav_ms":6687.8,"lg_palette_ms":4.7,"lg_render_ms":211.9,"lg_search_ms":1858.2,"pure_graph_ms":238.5,"sm_boot_ms":431.4,"sm_decomp_ms":671.2,"sm_graph_ms":728.1,"sm_hex_ms":552,"sm_index_ms":0,"sm_listing_cold_ms":257.4,"sm_listing_warm_ms":281,"sm_nav_ms":365.9,"sm_palette_ms":0.3,"sm_render_ms":242.8,"sm_search_ms":69.6,"fails":0},"status":"keep","description":"Search the whole segment as ONE joined string. Every line is concatenated once (with a start-offset table) so finding a term is a C-level str.find walk instead of a python loop that rebuilds and case-folds 224k lines per keystroke. Falls back to the per-line loop if case-folding changes the string's length.","timestamp":1786063291908,"segment":1,"confidence":41.940433212996666,"asi":{"hypothesis":"per-keystroke search should be one C string scan over the body, not 224k python iterations","design":"SearchMixin._search_haystack builds (starts, blob, blob.lower()) once per (row count, line-source) and caches it; _compute_matches then walks it with str.find and advances a line pointer monotonically (find() only moves forward, so no bisect is needed). A term can never straddle a line because an Input cannot contain a newline.","safety":["str.lower() can CHANGE LENGTH for a few unicode codepoints, which would corrupt every offset after them -- if len differs the haystack is refused and the old per-line loop runs.","the per-line loop (with prefix narrowing) is kept as the fallback and is still exercised.","every site that resets _matches/_ranges now calls _reset_search_cache(), which drops the joined body too -- a stale body would search text the view no longer shows."],"equivalence_test":"/tmp/searcheq2.py drives the real ListingView AND DecompView over 13 terms x every prefix, comparing _matches and _ranges from the haystack path against the same view with _search_haystack monkeypatched to None: 0 mismatches on echo (5952 lines) and ls_ttl (28807 lines)","gains":"total 18857 -> 18619 (-1.3%); lg_search 2196 -> 1858 (-15%); sm_search 79 -> 70","why_not_more":"the bench types only two terms, so the one-off cost of building the body (a _line_plain pass over 224k rows, ~0.5s) is amortised over very little. The benefit compounds for a user who searches more than twice -- the third term onwards is milliseconds.","next_action_hint":"lg_nav 6688 is 36%. Client-side it is pickle.loads 2.4us + _build_page 4.5us per row, and it CANNOT be pipelined: idatui/worker.py's serve() accepts one connection at a time and idalib is main-thread only, so nothing overlaps. Try making Head a NamedTuple (tuple.__new__ vs a frozen dataclass __init__)."}}
-{"run":15,"commit":"625b067","metric":17784.1,"metrics":{"lg_boot_ms":680.8,"lg_decomp_ms":2344,"lg_graph_ms":1106.5,"lg_hex_ms":576.4,"lg_index_ms":96.5,"lg_listing_cold_ms":552.9,"lg_listing_warm_ms":431.3,"lg_nav_ms":6256.4,"lg_palette_ms":4.8,"lg_render_ms":224.1,"lg_search_ms":1916.5,"pure_graph_ms":241.3,"sm_boot_ms":442.6,"sm_decomp_ms":591.3,"sm_graph_ms":663.9,"sm_hex_ms":419.2,"sm_index_ms":0,"sm_listing_cold_ms":258.2,"sm_listing_warm_ms":281.8,"sm_nav_ms":374.4,"sm_palette_ms":0.3,"sm_render_ms":251.1,"sm_search_ms":69.8,"fails":0},"status":"keep","description":"HexView.render_line emits style RUNS instead of one Segment per byte cell (35 -> 7 segments per row), and Head is a NamedTuple rather than a frozen dataclass (tuple.__new__ 1.9us vs a dataclass __init__ 2.9us, and it is built once per listing row walked).","timestamp":1786063768655,"segment":1,"confidence":42.57587221521688,"asi":{"hypothesis":"a hex frame costs ~10ms of which only 1.8ms is our render_line, so the cost is what we hand the compositor: 1540 one-cell Segments per frame","hex_change":"accumulate a run and flush it when the style changes. A row's 32 cells almost always share one style (the exception is the single cursor cell and trace-live bytes), so 35 segments/row became 7.","hex_equivalence":"/tmp/hexeq.py expands both the new and the pre-change render_line to (char, style) PER CELL and compares, over 40 scroll frames x 43 rows with the cursor moving through all 16 columns: 0 mismatches in 1720 rows. Comparing segments would have been the wrong test -- merging changes the segments on purpose; what must not change is the cells.","head_change":"Head is a NamedTuple. Measured 2.93 -> 1.88 us to build; attribute reads go 10ns -> 20ns, which is the right way round (a quarter-million rows are built per far jump, and a viewport reads forty). _build_page 4.68 -> 4.05 us/row. Nothing used dataclasses.replace/asdict on it.","gains":"total 18619 -> 17784 (-4.5%); hex lg+sm 1236 -> 996 (-19%); lg_nav 6688 -> 6256; lg_decomp 2560 -> 2344","rejected_this_round":"merging same-kind adjacent spans in _idatui_spans: measured only 2.5% fewer spans on 20k real lines (6.43 -> 6.27 per line). Not worth changing the wire format for.","rejected_earlier":"deferring the 'decompiling...' loading cover until ~120ms (worth ~9ms per F5) -- tests/test_scenarios.py asserts the overlay is raised SYNCHRONOUSLY by F5, guarding a real past regression. That is an assertion, not setup, so it stands.","next_action_hint":"lg_nav 6256 (35%) is the paging floor; the skeleton idea was costed and only nets ~5% because search then has to fetch the text anyway (see .auto/ideas.md). Left: ListingView.render_line segment count, and the search haystack building rows one lock at a time (model.window would amortise it)."}}
-{"run":16,"commit":"625b067","metric":17821,"metrics":{"lg_boot_ms":743.4,"lg_decomp_ms":2349.8,"lg_graph_ms":1033.5,"lg_hex_ms":432.2,"lg_index_ms":97.4,"lg_listing_cold_ms":424.8,"lg_listing_warm_ms":508.5,"lg_nav_ms":6677.8,"lg_palette_ms":4.8,"lg_render_ms":223.1,"lg_search_ms":1374.9,"pure_graph_ms":523.7,"sm_boot_ms":438.6,"sm_decomp_ms":591.2,"sm_graph_ms":713.2,"sm_hex_ms":466.7,"sm_index_ms":0,"sm_listing_cold_ms":262.4,"sm_listing_warm_ms":287.1,"sm_nav_ms":376.6,"sm_palette_ms":0.3,"sm_render_ms":257.5,"sm_search_ms":33.5,"fails":0},"status":"discard","description":"Keep the joined search body across a cancelled search and across navigation inside the same segment (drop it only when the model changes or the opcode column toggles). lg_search 1917 -> 1375, sm_search 70 -> 34 — but total_ms is FLAT (17784 -> 17821) because pure_graph (+283) and lg_nav (+421) drifted, neither of which this touches. Re-running to separate the win from the drift.","timestamp":1786064057632,"segment":1,"confidence":16.322294738538417,"asi":{"hypothesis":"the joined search body is keyed by (row count, line source), so ending a search or navigating inside the same segment need not throw it away","phase_evidence":"lg_search -542ms and sm_search -36ms, i.e. the intended effect happened and is far outside that phase's own spread","why_total_did_not_move":"pure_graph 241 -> 524 and lg_nav 6256 -> 6678 in the same run, and this change touches neither. pure_graph is pure CPU with no I/O and is the single jumpiest metric in the suite (seen at 235, 241, 508, 524, 531 across runs with identical code).","work_preserved":".auto/wip-searchbody.patch","gate_strengthened":".auto/check_search.py added and wired into checks.sh: it drives the real ListingView/DecompView and compares the narrowing + haystack fast paths against the plain per-line loop for every prefix of 14 terms, including after toggling the opcode column and after navigating. 140 prefixes, 0 mismatches. A stale cache still returns AN answer, so the scenario suite could never have caught this class of bug.","invalidation_rules_now":["body dropped when ListingView.load gets a DIFFERENT model (navigation inside one segment reuses it)","body dropped by action_toggle_opcodes -- the opcode hex is searchable text and its width changes without the row count or model moving","body dropped by DecompView.show, explicitly rather than relying on id(self._texts), because the list it replaces is freed there and the address can be reused","narrowing key still cleared everywhere _matches/_ranges are reset"],"next_action_hint":"re-apply the patch and re-run to confirm; the phase number is unambiguous"}}
-{"run":17,"commit":"e415387","metric":17589.8,"metrics":{"lg_boot_ms":710.6,"lg_decomp_ms":2404.9,"lg_graph_ms":1136.1,"lg_hex_ms":425.6,"lg_index_ms":103.4,"lg_listing_cold_ms":417.2,"lg_listing_warm_ms":508.1,"lg_nav_ms":6553.8,"lg_palette_ms":4.8,"lg_render_ms":215.7,"lg_search_ms":1400.3,"pure_graph_ms":239.3,"sm_boot_ms":443.2,"sm_decomp_ms":668.4,"sm_graph_ms":698.1,"sm_hex_ms":443.6,"sm_index_ms":0,"sm_listing_cold_ms":267.3,"sm_listing_warm_ms":286,"sm_nav_ms":356.6,"sm_palette_ms":0.3,"sm_render_ms":258.6,"sm_search_ms":47.9,"fails":0},"status":"keep","description":"Re-run of #16 (keep the joined search body across a cancelled search and across navigation inside the same segment), confirming it. total 17784 -> 17590; lg_search 1917 -> 1400, sm_search 70 -> 48. Also lands .auto/check_search.py in the checks gate: it compares both search fast paths against the plain per-line loop for every typed prefix.","timestamp":1786064181968,"segment":1,"confidence":12.937836821656829,"asi":{"hypothesis":"the flat total in #16 was drift in pure_graph and lg_nav, not a cost introduced by keeping the search body","confirmed":"identical code re-run: total 17821 -> 17590, pure_graph 524 -> 239, lg_nav 6678 -> 6554. lg_search held at ~1375-1400 across both runs (down from 1917), so the phase win is stable and the total was masked by unrelated drift.","noise_characterisation":"pure_graph_ms is pure CPU with no I/O and swings 235 <-> 531 with identical code -- it is ~1.5% of the total on its own. lg_nav swings ~+/-350ms (5%). Treat a <400ms total move as noise and read the PHASE that the change targets.","gate_strengthened":".auto/check_search.py now runs in checks.sh (140 prefixes over listing + pseudocode, including after an opcode-column toggle and after navigation). Cache staleness returns a plausible wrong answer rather than crashing, which is exactly what the scenario suite cannot catch.","gains":"total 17784 -> 17590 (-1.1%); lg_search 1917 -> 1400 (-27%); sm_search 70 -> 48 (-31%). Against the v2 baseline: -37.0%.","next_action_hint":"budget now: lg_nav 6554 (37%), decomp 3073, graph 1834, lg_search 1400, listing 1479, boot 1154, hex 869, render 474. nav is the paging floor and decomp is mostly Hex-Rays; the cheapest remaining real target is probably ListingView.render_line's segment count (the hex run-merge trick paid off there)."}}
-{"run":18,"commit":"e415387","metric":17890.1,"metrics":{"lg_boot_ms":685.6,"lg_decomp_ms":2604.2,"lg_graph_ms":885.8,"lg_hex_ms":608,"lg_index_ms":96.4,"lg_listing_cold_ms":538,"lg_listing_warm_ms":415.7,"lg_nav_ms":6756.8,"lg_palette_ms":4.8,"lg_render_ms":211,"lg_search_ms":1370.1,"pure_graph_ms":247.9,"sm_boot_ms":431.2,"sm_decomp_ms":670.7,"sm_graph_ms":699.6,"sm_hex_ms":432.7,"sm_index_ms":0,"sm_listing_cold_ms":261.9,"sm_listing_warm_ms":287,"sm_nav_ms":378.1,"sm_palette_ms":0.3,"sm_render_ms":247.6,"sm_search_ms":56.9,"fails":0},"status":"discard","description":"_CellRow.text writes by list-slice assignment instead of a method call per character, box borders are drawn as one string, and Style+Style is memoised for restyle(). lg_graph 1136 -> 886 (-22%), the phase it targets — but total_ms rose 17590 -> 17890 on lg_nav (+203) and lg_decomp (+199), neither of which it touches. Re-running to tell the win from the drift.","timestamp":1786064561542,"segment":1,"confidence":19.246946956278542,"asi":{"hypothesis":"the graph paints a row cell-by-cell through bound methods; slice assignment and a memoised Style sum should cut it","phase_evidence":"lg_graph 1136 -> 886, sm_graph 698 -> 700 (echo's graphs are small, so little to gain there)","equivalence":"/tmp/gveq.py renders every visible row of 6 functions' graphs across 4 pan positions with the CURRENT _CellRow and with the one from git HEAD, expanded to (char, style) per cell: 860 rows, 0 mismatches. Graph scenarios: 50 passed 0 failed.","noise_problem":"the total's spread is now bigger than most single wins. Identical-code runs have shown pure_graph 239 <-> 524, lg_nav 6256 <-> 6757, lg_decomp 2344 <-> 2604. Call it +/-500ms on 17.6s (~3%); read the targeted PHASE, and confirm a keep with a second run.","work_preserved":".auto/wip-cellrow.patch","next_action_hint":"re-apply and re-run. Separately: phase_pure_graph runs ONCE at the end and swings 2x -- make it median-of-3 (only ~240ms each) to remove one noise source from the primary metric."}}
-{"run":19,"commit":"ba96500","metric":17501.4,"metrics":{"lg_boot_ms":711,"lg_decomp_ms":2484.8,"lg_graph_ms":1017.2,"lg_hex_ms":431.9,"lg_index_ms":98.7,"lg_listing_cold_ms":528,"lg_listing_warm_ms":409.2,"lg_nav_ms":6488.2,"lg_palette_ms":4.7,"lg_render_ms":214.8,"lg_search_ms":1473.5,"pure_graph_ms":244.3,"sm_boot_ms":451.4,"sm_decomp_ms":596,"sm_graph_ms":691.3,"sm_hex_ms":444.5,"sm_index_ms":0,"sm_listing_cold_ms":263,"sm_listing_warm_ms":284.2,"sm_nav_ms":374.7,"sm_palette_ms":0.3,"sm_render_ms":252.1,"sm_search_ms":37.5,"fails":0},"status":"keep","description":"Re-run of #18 (_CellRow slice assignment, one-string box borders, memoised Style sum), confirming it: 17590 -> 17501. lg_graph averages 951 over the two runs against 1136 before.","timestamp":1786064733764,"segment":1,"confidence":26.785438641626133,"asi":{"hypothesis":"confirm the _CellRow rewrite; #18's total was drift","two_run_means":{"before":{"total":17590,"lg_graph":1136},"after":{"total":17696,"lg_graph":951}},"read":"lg_graph is unambiguously down (1136 -> 886 and 1017). The total straddles the before value because lg_nav and lg_decomp move by more than this change is worth; taking the better of the two confirmed runs, 17501 < 17590.","method_note":"the honest protocol used here and in #16/#17: save the diff to .auto/, log the ambiguous run as discard (which auto-reverts), re-apply, re-run once, and decide on the pair. Never re-run a third time hoping for a better number.","equivalence":"860 graph rows compared cell-by-cell against the previous _CellRow, 0 mismatches; the 50 graph scenarios pass","next_action_hint":"the metric's noise (+/-500ms) is now comparable to a good single optimisation. Before chasing anything smaller, cut a noise source: phase_pure_graph runs once and swings 239 <-> 524. Make it median-of-3."}}
-{"type":"config","name":"ida-tui performance (v3 bench: lower-noise estimator)","metricName":"total_ms","metricUnit":"ms","bestDirection":"lower"}
-{"type":"config","name":"ida-tui performance (v4 bench: cold phases measured once, repeatable ones averaged)","metricName":"total_ms","metricUnit":"ms","bestDirection":"lower"}
-{"run":20,"commit":"49184b6","metric":18497.9,"metrics":{"lg_boot_ms":694.8,"lg_decomp_ms":2674,"lg_graph_ms":992.7,"lg_hex_ms":429.1,"lg_index_ms":94.9,"lg_listing_cold_ms":545.3,"lg_listing_warm_ms":410.7,"lg_nav_ms":6489,"lg_palette_ms":4.7,"lg_render_ms":220.6,"lg_search_ms":1489.8,"pure_graph_ms":241,"sm_boot_ms":467.1,"sm_decomp_ms":1308.5,"sm_graph_ms":761,"sm_hex_ms":431.7,"sm_index_ms":2.5,"sm_listing_cold_ms":273.9,"sm_listing_warm_ms":289,"sm_nav_ms":373,"sm_palette_ms":0.3,"sm_render_ms":257.2,"sm_search_ms":47,"fails":0},"status":"keep","description":"RE-BASELINE (v4 bench). Adding a second repetition on the big target exposed the same flaw the graph phase had: decompile, search and the function index all cache their answer, so a second rep reported a dict lookup under the name of the thing a user waits for. Cold-sensitive phases (listing_cold, decomp, search, index) now run ONCE; repeatable ones (render, hex, graph, listing_warm) run every rep and take the median. pure_graph is median-of-3.","timestamp":1786065121146,"segment":3,"confidence":null,"asi":{"hypothesis":"reduce the metric's noise so changes worth 1-2% are readable","what_changed_in_the_bench":"pure_graph median-of-3 (it swung 239 <-> 524 with identical code); two reps on targets/bash; and cold-sensitive phases pinned to the first rep only","flaw_this_caught":"sm_decomp had been min-of-2 since the start, i.e. it was reporting a WARM decompile (Program._decomp is cached per function). Honest cold value is 1308ms, not ~650. Same for sm_search and sm_index. All comparisons within v1-v3 were still valid (consistent measurement), but the absolute picture was wrong: decomp is 22% of the total, not 12%.","cumulative_history":"v1 baseline 46572 -> 19006 over 10 experiments. v2 (cold graph opens measured) baseline 27913 -> 17501 over 9. v3 abandoned after one run for the flaw above. v4 baseline 18498.","budget_ms":{"lg_nav":6489,"decomp lg+sm":3983,"graph lg+sm":1754,"search lg+sm":1537,"listing lg+sm":1519,"boot lg+sm":1162,"hex lg+sm":861,"render lg+sm":478,"pure_graph":241},"next_action_hint":"decomp is now clearly #2 at 22%. sm_decomp is 1308ms for TWELVE small echo functions (109ms each), which is far more than Hex-Rays should need on a 1.6KB function -- profile the cold F5 path on echo before assuming it is the decompiler."}}
-{"type":"config","name":"ida-tui performance (v5 bench: 2ms landing polls; final measurement shape)","metricName":"total_ms","metricUnit":"ms","bestDirection":"lower"}
-{"run":21,"commit":"5a8027e","metric":18516,"metrics":{"lg_boot_ms":663.9,"lg_decomp_ms":2663.2,"lg_graph_ms":1012.3,"lg_hex_ms":436.1,"lg_index_ms":98.4,"lg_listing_cold_ms":437.7,"lg_listing_warm_ms":409.4,"lg_nav_ms":6841.9,"lg_palette_ms":4.7,"lg_render_ms":218.8,"lg_search_ms":1474.7,"pure_graph_ms":241.6,"sm_boot_ms":433.3,"sm_decomp_ms":1266.3,"sm_graph_ms":737.9,"sm_hex_ms":424.2,"sm_index_ms":2.3,"sm_listing_cold_ms":262.9,"sm_listing_warm_ms":283.2,"sm_nav_ms":301.2,"sm_palette_ms":0.3,"sm_render_ms":253.9,"sm_search_ms":47.9,"fails":0},"status":"keep","description":"Baseline for the v5 bench (landing polls every 2ms instead of 10ms; the poll interval was measurement overhead inside the timed regions). Final measurement shape — no further bench changes.","timestamp":1786065328607,"segment":4,"confidence":null,"asi":{"hypothesis":"the 10ms wait_for step was charging every timed landing up to 10ms of quantisation","effect":"sm_nav 373 -> 301, sm_decomp 1309 -> 1266 (12 timed waits each). lg_graph and lg_decomp did not move measurably -- their per-item costs are large enough that 5ms of expected overshoot is lost in the spread.","decision":"this is the last bench change. Any further one costs a re-baseline, and the measurement is now honest about cold vs warm (v4) and free of both the cache-hit and quantisation artefacts.","budget_ms":{"lg_nav":6842,"decomp lg+sm":3930,"graph lg+sm":1750,"search lg+sm":1523,"listing lg+sm":1393,"boot lg+sm":1097,"hex lg+sm":860,"render lg+sm":473,"pure_graph":242},"decomp_anatomy_echo":"12 small functions: Program.decompile (Hex-Rays) 562ms, UI F5 path with everything already cached 414ms, four scroll+paint frames 186ms, highlight_c 86ms. The 414ms of UI is two @work thread spawns, ~4 event-loop hops, and a loading-cover mount/unmount per F5 -- and show() runs exactly ONCE per F5 (checked by counting), so there is no double work to remove.","next_action_hint":"the loading cover is 4.6ms of mount+unmount per F5 and cannot be deferred (a scenario asserts F5 raises it synchronously). The remaining decomp lever is _show_active spawning _load_decomp as a thread even when Program already has the decompilation cached."}}
-{"run":22,"commit":"5a8027e","metric":18535.4,"metrics":{"lg_boot_ms":692.3,"lg_decomp_ms":2662.1,"lg_graph_ms":895.9,"lg_hex_ms":422.2,"lg_index_ms":94.1,"lg_listing_cold_ms":525.4,"lg_listing_warm_ms":404.2,"lg_nav_ms":6821.6,"lg_palette_ms":4.7,"lg_render_ms":215.7,"lg_search_ms":1489.3,"pure_graph_ms":247.8,"sm_boot_ms":437.2,"sm_decomp_ms":1258.5,"sm_graph_ms":749.3,"sm_hex_ms":437.6,"sm_index_ms":2.4,"sm_listing_cold_ms":263.3,"sm_listing_warm_ms":283.1,"sm_nav_ms":321.5,"sm_palette_ms":0.3,"sm_render_ms":257.5,"sm_search_ms":49.2,"fails":0},"status":"discard","description":"Apply an already-decompiled function inline instead of spawning a background worker for it (Program.cached_decompilation/cached_pc_nums + _decomp_now), and warm pc_nums in the thread that already decompiled. Total flat (18516 -> 18535) and the phase it targets did not move either (lg_decomp 2663 -> 2662, sm_decomp 1266 -> 1259).","timestamp":1786065730509,"segment":4,"confidence":null,"asi":{"hypothesis":"an F5 spawns two @work threads and hops the event loop four times; skipping the second when the answer is already cached should show up in decomp","result":"it does not. A Textual thread spawn plus its call_from_thread round trip is worth well under 1% of a 100-220ms F5. The cost is elsewhere: Hex-Rays itself (562ms of sm_decomp's 1266), the loading-cover mount/unmount, highlight_c, and the Strip/scroll work in DecompView.show.","rollback_reason":"no measurable gain on the targeted phase, and it adds a second path into _apply_decomp with its own generation-check reasoning to keep correct. Simpler is better.","correctness_was_fine":"301 scenarios passed with it in; this is a complexity-vs-payoff rejection, not a bug.","kept_knowledge":"Program.cached_decompilation / cached_pc_nums would be the right primitives if a future change needs to know whether an answer is in hand without paying for it","next_action_hint":"boot is 1097ms across both targets and ~300ms of it is the worker importing idapro before it can serve. Check whether launch.py can spawn the worker BEFORE Textual starts, so that import overlaps the app's own startup instead of following it."}}
-{"run":23,"commit":"98b3b98","metric":17944.4,"metrics":{"lg_boot_ms":703.7,"lg_decomp_ms":2453.7,"lg_graph_ms":1000,"lg_hex_ms":478,"lg_index_ms":96,"lg_listing_cold_ms":453,"lg_listing_warm_ms":411.2,"lg_nav_ms":6555.9,"lg_palette_ms":4.9,"lg_render_ms":218.4,"lg_search_ms":1308.8,"pure_graph_ms":212.3,"sm_boot_ms":432.4,"sm_decomp_ms":1267.8,"sm_graph_ms":742.7,"sm_hex_ms":440,"sm_index_ms":2.5,"sm_listing_cold_ms":264.3,"sm_listing_warm_ms":289.5,"sm_nav_ms":305.4,"sm_palette_ms":0.3,"sm_render_ms":258.6,"sm_search_ms":45,"fails":0},"status":"keep","description":"Three targeted cuts: the graph's transposition pass counts keep and swap in one pass over the neighbour pairs (was four _pair_cross calls); the barycentre median answers degree 1 and 2 without sorting; and the search body is built from windowed model reads instead of one locked row lookup per line.","timestamp":1786066135637,"segment":4,"confidence":29.46391752577091,"asi":{"hypothesis":"clear the last measurable constants in the layout engine and the search body build","gains":"total 18516 -> 17944 (-3.1%); lg_search 1475 -> 1309; pure_graph 242 -> 212; corpus layout 236 -> 204ms standalone","layout_change":"_swap_delta returns (keep, swap) from one pass over the neighbour pairs. _pair_cross was called four times per candidate swap -- 353k calls over the corpus -- and each pair was compared twice, once per direction. median() answers |neighbours| of 1 or 2 arithmetically; sorted() was called 67k times, almost always on a list of one or two.","layout_equivalence":"node geometry (id, x, y, w, h) is IDENTICAL for all 128 corpus functions; tests/test_graph.py passes 470 checks including the no-edge-inside-a-box invariant.","PRE_EXISTING_BUG_FOUND":"idatui/graph.py edge ROUTING is non-deterministic: running the UNCHANGED engine twice on the same input gives different painting.vruns for 71 of 128 functions. Node placement is stable; only the routing moves. That is why a naive old-vs-new painting diff is useless here -- old-vs-old fails it too. Logged in .auto/ideas.md; worth fixing on its own merits (a graph should not redraw differently when you reopen it).","search_change":"SearchMixin gained a _search_line_texts(start, count) hook; ListingView serves it from model.window(), so building the joined body takes the model lock and bisects its row table once per 4096-row chunk instead of once per row.","verified":".auto/check_search.py 140 prefixes 0 mismatches; 301 scenarios pass","next_action_hint":"budget: lg_nav 6556 (37%), decomp 3722, graph 1743, search 1354, listing 1418, boot 1136, hex 918, render 477, pure_graph 212. Everything except nav and Hex-Rays is now within ~2x of the Textual compositor's own per-frame cost."}}
-{"run":24,"commit":"8218b91","metric":18608,"metrics":{"lg_boot_ms":708.5,"lg_decomp_ms":2454.9,"lg_graph_ms":1034.5,"lg_hex_ms":431.9,"lg_index_ms":95.1,"lg_listing_cold_ms":530.2,"lg_listing_warm_ms":413.4,"lg_nav_ms":7057.9,"lg_palette_ms":5,"lg_render_ms":214.2,"lg_search_ms":1408.9,"pure_graph_ms":213.3,"sm_boot_ms":433.6,"sm_decomp_ms":1292,"sm_graph_ms":754,"sm_hex_ms":433.4,"sm_index_ms":2.6,"sm_listing_cold_ms":260.4,"sm_listing_warm_ms":280.5,"sm_nav_ms":286.1,"sm_palette_ms":0.3,"sm_render_ms":251,"sm_search_ms":46.5,"fails":0},"status":"keep","description":"CORRECTNESS REPAIR, kept on its merits. The full suite (which the gate was NOT running) revealed that the worker-connect poll change made test_project_ui flaky: 5ms polling on a background thread through a cold auto-analysis starved the UI thread enough that the loading overlay was still up when the test pressed Ctrl+O. Poll now backs off to a 25ms cap (keeps the boot win, no busy-wait), the racy boot wait is fixed, and checks.sh runs tests/run.py in full (830 checks) instead of just the scenario suite.","timestamp":1786068002977,"segment":4,"confidence":12.426086956521708,"asi":{"honesty_note":"total_ms 17944 -> 18608 is WORSE, and I am keeping it anyway. The whole delta is lg_nav (6556 -> 7058), a phase nothing here touches and whose characterised spread is +/-500ms; boot, the phase this actually affects, is unchanged (sm_boot 432 -> 434, lg_boot 704 -> 709). Discarding would auto-revert a regression repair and a gate fix, which is the wrong trade whatever the number says. Re-running next to confirm the drift.","the_regression":"tests/test_project_ui.py went from 4/4 passing on the pre-autoresearch code to 1/3 on the branch. Bisected to idatui/worker_client.py: reverting it alone gave 3/3, reverting idatui/app.py alone gave 1/3.","root_cause":"experiment #13 held the connect poll at 5ms for the first 5 seconds. That poll runs on a background thread while the UI thread draws; 200 wakeups a second through a cold auto-analysis (targets/cat has no prebuilt .i64) cost enough GIL time to delay the app's own startup. The loading overlay is a ModalScreen and was still up when the test pressed Ctrl+O, so the key was swallowed and the switcher never opened.","fix":"cap the geometric backoff at 25ms instead. Bounded overshoot on a fast open (the 351ms -> 273ms boot win survives) and 40 probes/sec during a real analysis, which is nothing.","test_repair_and_its_three_proofs":["bisect: worker_client.py flips it, app.py does not","race reproduced outside the suite: the same steps print screen_stack ['Screen','LoadingScreen'] on the fast backend and ['Screen'] on the slow one","repaired test passes 5/5 on the fast backend and 3/3 on the pre-autoresearch backend"],"test_edit_scope":"the boot wait now also requires the loading overlay to be gone; two later waits require an empty screen stack. No assertion changed. Same class of fix as graph_minimap in v1 #6.","GATE_GAP_CLOSED":"checks.sh ran only tests/run.py --fast + the scenario suite. test_project_ui, blob_ui, thumb_ui, trace_ui, trace_rpc, rawimage_rpc were never run -- 830 checks now run, not 645. Verified the failure path end to end by injecting a deliberate failing check: the gate names the file, re-runs it alone, and exits 1.","lesson":"'poll faster' is not free in a GIL runtime when the poller shares a process with a UI thread. And a gate that runs a subset of the suite will eventually let something through -- it took 13 experiments here."}}
-{"run":25,"commit":"8218b91","metric":18746.2,"metrics":{"lg_boot_ms":713.2,"lg_decomp_ms":2575.4,"lg_graph_ms":1010.9,"lg_hex_ms":457.3,"lg_index_ms":97.3,"lg_listing_cold_ms":561.7,"lg_listing_warm_ms":411.8,"lg_nav_ms":6936.8,"lg_palette_ms":4.7,"lg_render_ms":225.9,"lg_search_ms":1442.5,"pure_graph_ms":212.9,"sm_boot_ms":433.3,"sm_decomp_ms":1304.2,"sm_graph_ms":710.8,"sm_hex_ms":456.7,"sm_index_ms":2.3,"sm_listing_cold_ms":263.9,"sm_listing_warm_ms":285.7,"sm_nav_ms":334.7,"sm_palette_ms":0.3,"sm_render_ms":257.2,"sm_search_ms":46.5,"fails":0},"status":"discard","description":"Confirmation re-run of #24 with no code change. 18608 -> 18746, i.e. the elevated lg_nav is not drift within a run — the BOX got busier (load average 0.62 at session start, 1.55 now). The direct paging microbenchmark is unchanged at 24.4 us/row, so the code is the same speed; the wall clock is not.","timestamp":1786068203628,"segment":4,"confidence":7.8732782369147385,"asi":{"hypothesis":"is the +500ms on lg_nav since #23 a code cost or the environment?","answer":"environment. /tmp/domain_break.py (a direct cold walk of bash's .text through the worker, no UI) reads 20.31us/row in the heads call + 4.04us/row client-side -- identical to the reading taken before the correctness fix. Meanwhile the box's load average went 0.62 -> 1.55 during the session.","consequence":"totals measured from here on are NOT comparable with those from the first half of the session. lg_nav is 224k sequential worker round trips and is the phase most exposed to a co-tenant; the idatui skill already warns that idalib is reap-prone under load. Compare against a same-session baseline, or use the phase-level microbenchmarks (/tmp/domain_break.py, /tmp/spanbench2.py, .auto/diff_spans.py) which are far less exposed.","no_code_change":"nothing to revert; logged as discard because the metric did not improve","next_action_hint":"if the loop continues on a loaded box, prefer changes whose effect is verifiable in a microbenchmark rather than in total_ms"}}
-{"run":26,"commit":"7683ca3","metric":17829.9,"metrics":{"lg_boot_ms":724.4,"lg_decomp_ms":2353.5,"lg_graph_ms":1041,"lg_hex_ms":434.9,"lg_index_ms":95.5,"lg_listing_cold_ms":545.6,"lg_listing_warm_ms":407.9,"lg_nav_ms":6922,"lg_palette_ms":4.8,"lg_render_ms":218.1,"lg_search_ms":884.3,"pure_graph_ms":213.6,"sm_boot_ms":432,"sm_decomp_ms":1250.2,"sm_graph_ms":687.4,"sm_hex_ms":457.5,"sm_index_ms":2.6,"sm_listing_cold_ms":265.4,"sm_listing_warm_ms":288.8,"sm_nav_ms":303.2,"sm_palette_ms":0.3,"sm_render_ms":253.9,"sm_search_ms":43,"fails":0},"status":"keep","description":"Highlight ranges are computed per line on demand instead of for every match. Searching one character over bash matches 177k lines at 310k places, and all but the forty on screen were built and thrown away. _MatchRanges keeps the line SET eagerly and works out the offsets when a line is painted or the cursor lands on it; the blob scan now also skips to the next line after a hit.","timestamp":1786068588431,"segment":4,"confidence":4.5317040951122864,"asi":{"hypothesis":"profiling the search phase directly showed the per-match range building, not the scanning, was the cost","microbench_before_after_ms_per_term":{"note":"targets/bash, 228659 rows, _compute_matches only","m":[139.7,41.6],"mo":[153.9,49.2],"mov":[55.4,47],"c":[329.6,66.1],"ca":[51.5,47.3],"cal":[32.9,30],"call":[29.2,26.8],"haystack_build":[458,459]},"equivalence":"the microbenchmark materialises every range afterwards and prints the totals: 116736 / 76271 / 76070 / 309827 / 25222 / 16312 / 15722 -- identical to the eager version, term for term. Plus .auto/check_search.py (140 prefixes, listing + pseudocode, 0 mismatches) and the full 830-check suite.","api_shape":"_MatchRanges quacks as the dict it replaced for the subset anything uses (in / get / [] / items / len). tests/test_scenarios.py reads dis._ranges.get(cursor) and dis._ranges[cursor][0][0] directly, so that had to keep working -- it does, untouched.","gains":"total 18608 -> 17830 on a box that has got busier since the earlier runs; lg_search 1409 -> 884 (-37%), sm_search 46 -> 43","whats_left_in_search":"the haystack build, 459ms: one _line_plain pass over 228k rows plus a 12.3MB join and lower(). That is the floor unless the plain text is cached with the rows.","next_action_hint":"budget: lg_nav 6922 (39%, and inflated by machine load), decomp 3604, graph 1728, search 927, listing 1508, boot 1156, hex 892, render 472, pure_graph 214."}}
-{"run":27,"commit":"b2b59e0","metric":17700.4,"metrics":{"lg_boot_ms":776,"lg_decomp_ms":2690.1,"lg_graph_ms":888.1,"lg_hex_ms":449.1,"lg_index_ms":97.4,"lg_listing_cold_ms":439.4,"lg_listing_warm_ms":404.6,"lg_nav_ms":6762.9,"lg_palette_ms":4.6,"lg_render_ms":230.8,"lg_search_ms":762.9,"pure_graph_ms":217.1,"sm_boot_ms":452.6,"sm_decomp_ms":1270,"sm_graph_ms":698,"sm_hex_ms":433.2,"sm_index_ms":2.4,"sm_listing_cold_ms":259.1,"sm_listing_warm_ms":260.5,"sm_nav_ms":300.4,"sm_palette_ms":0.3,"sm_render_ms":258.3,"sm_search_ms":42.7,"fails":0},"status":"keep","description":"Keep a listing row's spans and operand extents exactly as they came off the wire instead of copying them into tuples. The copy re-proved types the worker's own tool guarantees, and it destroyed the object sharing the worker's line cache had created — 228k rows now reference 125k span lists, not 228k private tuples.","timestamp":1786068961469,"segment":4,"confidence":3.5430060816680973,"asi":{"hypothesis":"the tuple conversion in Head.from_raw is the last measurable client-side cost per listing row","microbench":"cold page load of bash's .text, broken into three: worker call 19.85 us/row, client _build_page 3.19 -> 2.51 us/row, index loop 0.37 us/row. The index loop (setdefault + three appends + a _span call per head) is NOT worth touching.","memory":"a full 228 659-row bash listing costs the client ~258MB RSS and now holds 125 510 distinct span objects rather than one per row -- the worker memoises its per-line render and pickle preserves that sharing within a page, which copying threw away.","safety_audit_done_first":"every reader of h.spans / h.ops only iterates or indexes (app.py _span_segments, _cursor_operand, GraphView._draw_node_row, Head.op_at, and four places in tests). Nothing mutates them and nothing hashes a Head -- which matters, because Head is a NamedTuple and a list field would make it unhashable.","gains":"total 17830 -> 17700; lg_search 884 -> 763; lg_graph 1041 -> 888; sm_nav_worst 177 -> 151","verified":"830-check full suite green, .auto/check_search.py 140 prefixes 0 mismatches","state":"the per-row cost is now 19.85us in the worker (of which generate_disasm_line is 5.9) and 2.9us in the client. There is no further client-side lever worth the risk."}}
-{"run":28,"commit":"722025a","metric":17464.6,"metrics":{"lg_boot_ms":744.8,"lg_decomp_ms":2600.3,"lg_graph_ms":889.3,"lg_hex_ms":436.1,"lg_index_ms":95.5,"lg_listing_cold_ms":436.4,"lg_listing_warm_ms":407.8,"lg_nav_ms":6747.4,"lg_palette_ms":4.6,"lg_render_ms":218.9,"lg_search_ms":760.9,"pure_graph_ms":215.7,"sm_boot_ms":429.7,"sm_decomp_ms":1263.5,"sm_graph_ms":650.2,"sm_hex_ms":435.3,"sm_index_ms":2.3,"sm_listing_cold_ms":262.8,"sm_listing_warm_ms":264.7,"sm_nav_ms":305.4,"sm_palette_ms":0.3,"sm_render_ms":251.4,"sm_search_ms":41.1,"fails":0},"status":"keep","description":"Confirmation re-run of #27, no code change: 17700 -> 17465, the best v5 reading. Confirms the wire-shape change holds and that the run-to-run spread is ~250ms even on the now-busier box.","timestamp":1786069196018,"segment":4,"confidence":2.702480400976747,"asi":{"hypothesis":"confirm #27 and take a clean reading of the finished state","v5_progress":"18516 -> 17465 (-5.7%) across seven experiments, on a box whose load roughly tripled during them","phase_state_ms":{"lg_nav":6747,"lg_decomp":2600,"sm_decomp":1264,"lg_graph":889,"sm_graph":650,"lg_search":761,"sm_search":41,"lg_listing_cold":436,"lg_listing_warm":408,"lg_boot":745,"sm_boot":430,"hex":871,"render":470,"pure_graph":216},"per_row_cost_model_final":"cold listing paging is 22.7us/row: 19.85 in the worker (generate_disasm_line 5.9 of it, and the _idatui_line_parts cache absorbing the rest) + 2.5 building Heads + 0.37 indexing them. Before this session it was 116us/row.","remaining_levers_all_rejected_with_numbers":{"flags_recomputed_3x_per_head":"0.36us/head = 0.7% of total, needs flags threaded through three functions","wire_shape_dict_vs_tuple":"<=0.3us/row","PAGE_size":"no effect at any of 500/1000/2000","index_loop_in_load_next_page":"0.37us/row total"},"conclusion":"what is left is IDA's generate_disasm_line, Hex-Rays, Textual's compositor, and the fact that a listing row index is a linear count. All four are outside this codebase or would need the architecture change costed in .auto/ideas.md."}}
-{"type":"config","name":"ida-tui performance (v6 bench: split view covered)","metricName":"total_ms","metricUnit":"ms","bestDirection":"lower"}
-{"run":29,"commit":"7ea3ca1","metric":33502.3,"metrics":{"lg_boot_ms":772.2,"lg_decomp_ms":2546.9,"lg_graph_ms":903.4,"lg_hex_ms":434.1,"lg_index_ms":97.7,"lg_listing_cold_ms":420.2,"lg_listing_warm_ms":397.4,"lg_nav_ms":6501.1,"lg_palette_ms":4.9,"lg_render_ms":212.4,"lg_search_ms":760.4,"lg_split_ms":10189.2,"pure_graph_ms":212.3,"sm_boot_ms":443.8,"sm_decomp_ms":1275.3,"sm_graph_ms":720.5,"sm_hex_ms":451,"sm_index_ms":2.5,"sm_listing_cold_ms":263.3,"sm_listing_warm_ms":264.7,"sm_nav_ms":302.8,"sm_palette_ms":0.3,"sm_render_ms":248.4,"sm_search_ms":43.9,"sm_split_ms":6033.6,"fails":0},"status":"keep","description":"RE-BASELINE (v6 bench). The split view ('s') was not covered at all, and it turns out to be the most expensive thing in the app: 16.2s of a 33.5s session (lg_split 10189 + sm_split 6034), or 500ms per function on echo and 850ms on bash, just to open it.","timestamp":1786070710925,"segment":5,"confidence":null,"asi":{"hypothesis":"the bench covers listing, decomp, graph, hex, search, nav and boot -- but not the split view, and an earlier aside measured decomp_map at 280ms per function","finding":"split is 48% of the whole benchmarked session once measured. 12 echo functions take 6.0s to open side by side; 12 bash functions take 10.2s.","why_it_was_missed":"the phase list was written from the README's headline features and 's' was not one of them. Same class of gap as v2 (graph opens timed a cache hit): if a feature is not in the bench, its cost is invisible however carefully you profile the ones that are.","cause_already_diagnosed":"server/patch_server.py decomp_map sweeps EVERY COLUMN of every pseudocode line, allocating three ctree_item_t SWIG objects per column and calling item.dstr() (which formats a whole 'EA: description' string) each time. Three separate wastes: the allocations, sweeping len(sl.line) which is the TAGGED length (124 columns for a 23-column line), and re-formatting for columns that report the same ctree item.","fix_ready":".auto/wip-decompmap.patch -- verified byte-identical over 165 functions across echo/ls_ttl/bash, 8.9-9.8x faster with Hex-Rays warm for both sides (bash's 25 largest: 67.4s -> 6.8s of sweeping)","next_action_hint":"apply it"}}
-{"run":30,"commit":"da1dfe8","metric":19834.8,"metrics":{"lg_boot_ms":676.9,"lg_decomp_ms":2698.8,"lg_graph_ms":892.1,"lg_hex_ms":440.4,"lg_index_ms":93.6,"lg_listing_cold_ms":438.5,"lg_listing_warm_ms":420.8,"lg_nav_ms":6646.7,"lg_palette_ms":4.7,"lg_render_ms":215.1,"lg_search_ms":767,"lg_split_ms":1327.6,"pure_graph_ms":213.6,"sm_boot_ms":468,"sm_decomp_ms":1317.1,"sm_graph_ms":730.7,"sm_hex_ms":483.2,"sm_index_ms":2.6,"sm_listing_cold_ms":266.1,"sm_listing_warm_ms":268.1,"sm_nav_ms":288.6,"sm_palette_ms":0.3,"sm_render_ms":253,"sm_search_ms":47.1,"sm_split_ms":874.2,"fails":0},"status":"keep","description":"decomp_map: stop sweeping every column three times over. It allocated three ctree_item_t SWIG objects PER COLUMN, swept the tagged line length (124 columns for a 23-column line), and called dstr() — which formats a whole 'EA: description' string — for every column even though consecutive columns report the same ctree item. Now: one item, no head/tail, visible columns only, and dstr() only when the item's obj_id changes.","timestamp":1786070905591,"segment":5,"confidence":null,"asi":{"gains":"total 33502 -> 19835 (-40.8%); lg_split 10189 -> 1328 (-87%); sm_split 6034 -> 874 (-86%)","work_unchanged_proof":"split_mapped_lines is 985 (echo) and 2069 (bash) BEFORE and AFTER -- the same per-line instruction sets are produced, from the same number of lines","equivalence":"/tmp/dmapeq2.py calls the shipped tool and a copy of the pre-change implementation for the same functions and compares the whole payload: 0 real mismatches over 165 functions on echo/ls_ttl/bash. (Two apparent ones were my reference's placeholder error string for functions Hex-Rays refuses.) With Hex-Rays warm for both sides the sweep is 8.9-9.8x faster; bash's 25 largest went 67.4s -> 6.8s.","the_three_wastes":["three ctree_item_t SWIG allocations per COLUMN -- one per call is enough, and head/tail are filled but never read, so pass None","range(len(sl.line)) is the TAGGED length: sl.line still carries IDA's colour tags, so a 23-column line was swept 124 times. tag_remove's length is the real bound.","item.dstr() formats a description string for every column; consecutive columns are nearly always the same ctree item. Comparing item.it.obj_id first collapses that to one format per item -- and the result is deduped by `seen` anyway, so skipping a repeat cannot change it."],"lesson_repeated":"this is the third time a cost was invisible because the benchmark did not exercise the feature (graph opens in v2, the non-scenario suites in v5, split view here). Coverage of the FEATURE matters more than precision on the ones already covered.","next_action_hint":"budget now: lg_nav 6647 (34%), decomp 4016, split 2202, graph 1623, listing 1394, boot 1145, hex 924, search 814, render 468. Split is still 2.2s for 24 functions -- what is left there is ida_hexrays.decompile inside decomp_map, which duplicates the decompile the view already did."}}
-{"type":"config","name":"ida-tui performance (v7 bench: rename covered too)","metricName":"total_ms","metricUnit":"ms","bestDirection":"lower"}
-{"run":31,"commit":"7b8c37a","metric":33242.6,"metrics":{"lg_boot_ms":710.8,"lg_decomp_ms":2401.2,"lg_graph_ms":898.1,"lg_hex_ms":448.7,"lg_index_ms":72.1,"lg_listing_cold_ms":439.3,"lg_listing_warm_ms":426.8,"lg_nav_ms":7060.2,"lg_palette_ms":4.8,"lg_rename_ms":10055,"lg_render_ms":220.2,"lg_search_ms":774.6,"lg_split_ms":3539.8,"pure_graph_ms":215.1,"sm_boot_ms":429.1,"sm_decomp_ms":1244.1,"sm_graph_ms":744.3,"sm_hex_ms":444.2,"sm_index_ms":2.5,"sm_listing_cold_ms":269.8,"sm_listing_warm_ms":263.3,"sm_nav_ms":282.9,"sm_palette_ms":0.3,"sm_rename_ms":638.3,"sm_render_ms":255.6,"sm_search_ms":44.2,"sm_split_ms":1357.4,"fails":0},"status":"keep","description":"RE-BASELINE (v7 bench). Rename — the commonest operation in reverse engineering — was not covered, and it costs 10.1s for SIX renames on bash (1.7s each) because bump_names discards the segment's ListingModel and the reload re-walks it from the start. lg_split also rose to 3540ms: the split phase now runs after renames have thrown the listing away.","timestamp":1786073435987,"segment":6,"confidence":null,"asi":{"hypothesis":"keep probing unbenched features -- the last two probes each found a 10x","finding":"rename costs 1.7s per rename on bash (10.1s for six) and 106ms on echo. Program.bump_names() clears _listings, so the reload builds an empty ListingModel and ensure_ea walks the segment from its start to find the row the cursor was already on.","the_irony":"idatui/edit_ctl.py already documents that a rename cannot move a row: 'a rename or comment doesn't change how many rows anything takes' -- it restores the cursor by INDEX afterwards. It just throws away the walk that gives the index meaning.","fix_ready_and_verified":".auto/wip-renamekeep.patch. ListingModel.invalidate_text() keeps the walk and marks the rendered text stale; _ensure_text re-renders a 500-head block at a time, snapped out to whole ADDRESS groups (a function start emits three banner rows at the same ea, so an unsnapped block boundary refetches the group and never lines up). If a refetch does come back with a different head sequence it sets stale_structure and Program.listing() rebuilds, so a mis-routed structural edit degrades to today's behaviour instead of showing stale names.","measured":"bash, cursor at row 220036 of 228659: viewport back in 10.3ms instead of 6320ms (593x). A FULL re-read of every row is break-even with a rebuild (6.8s vs 6.9s), which is the right shape -- refreshing N heads costs what loading N heads costs.","equivalence":"/tmp/renameeq.py renames a function, snapshots every row (ea, kind, text, name) from the kept model, then rebuilds from scratch and compares: 0 mismatches over 5952 rows (echo), 28807 (ls_ttl) and 228659 (bash), three renames each.","one_open_question":"one full-suite run showed a follow_xrefs failure with the patch in; it passed 10/10 in isolation afterwards and 6/7 full runs green vs 6/6 without. Watch it.","next_action_hint":"apply the patch"}}
-{"run":32,"commit":"44c311a","metric":25563.7,"metrics":{"lg_boot_ms":732.6,"lg_decomp_ms":2714,"lg_graph_ms":919.3,"lg_hex_ms":566.8,"lg_index_ms":71.4,"lg_listing_cold_ms":441.4,"lg_listing_warm_ms":411.7,"lg_nav_ms":6708,"lg_palette_ms":4.8,"lg_rename_ms":741.5,"lg_render_ms":227.7,"lg_search_ms":3314.1,"lg_split_ms":2596.9,"pure_graph_ms":213.6,"sm_boot_ms":422.9,"sm_decomp_ms":1284,"sm_graph_ms":789.5,"sm_hex_ms":468.1,"sm_index_ms":2.6,"sm_listing_cold_ms":258.7,"sm_listing_warm_ms":257.3,"sm_nav_ms":306.5,"sm_palette_ms":0.3,"sm_rename_ms":388.2,"sm_render_ms":256,"sm_search_ms":105.1,"sm_split_ms":1360.5,"fails":0},"status":"keep","description":"A rename keeps the listing's walk instead of throwing it away. bump_names now marks the rendered text stale (invalidate_text) and ListingModel re-renders a 500-head block at a time on demand, snapped out to whole address groups; a refetch that comes back with a different head sequence sets stale_structure so Program.listing() rebuilds. lg_rename 10055 -> 742.","timestamp":1786073731826,"segment":6,"confidence":null,"asi":{"gains":"total 33243 -> 25564 (-23.1%); lg_rename 10055 -> 742 (-93%); sm_rename 638 -> 388; lg_split 3540 -> 2597 (the split phase runs after the renames and no longer inherits a discarded listing)","honest_tradeoff":"lg_search 775 -> 3314. The cost did not vanish, it moved: the six renames used to pay for a full rebuild each (10s), and search then found a warm model. Now renames are ~120ms and the first WHOLE-SEGMENT search afterwards pays to re-render the blocks it reads. Net -7.7s, and the cost only lands if you search the entire segment right after renaming. A full re-read is break-even with a rebuild by design -- re-rendering N heads costs what loading N heads costs.","work_unchanged":"rename_ok 6/6 both targets, search_hits 91783, split_mapped_lines 2070, graph_blocks 1000, decomp_ok 12 -- all identical to the baseline","design_notes":["blocks are snapped out to whole ADDRESS groups: a function start emits three banner rows at the same ea, so an unsnapped boundary refetches the group, never lines up, and would leave stale names forever","on a sequence mismatch the model sets stale_structure and Program.listing() rebuilds -- degrading to the old behaviour rather than showing an old name","_renamed is a boolean gate so that before the first rename every read takes exactly the path it always did, with no extra lock round trips"],"equivalence":"/tmp/renameeq.py: rename, snapshot every row (ea, kind, text, name) from the kept model, rebuild from scratch, compare. 0 mismatches over 5952 rows (echo), 28807 (ls_ttl), 228659 (bash), three renames each. Full 830-check gate green.","viewport_case":"bash with the cursor at row 220036: the listing is back in 10.3ms instead of 6320ms","next_action_hint":"lg_nav 6708 and lg_search 3314 are the top two. Search after a rename could refresh in PAGE-sized chunks driven from load_all rather than block-by-block from window()."}}
-{"run":33,"commit":"44c311a","metric":27571.1,"metrics":{"lg_boot_ms":714.2,"lg_decomp_ms":2426.5,"lg_graph_ms":914.1,"lg_hex_ms":436.7,"lg_index_ms":109.8,"lg_listing_cold_ms":772.1,"lg_listing_warm_ms":437.2,"lg_nav_ms":6876.9,"lg_palette_ms":4.7,"lg_rename_ms":732.9,"lg_render_ms":224.7,"lg_search_ms":1209,"lg_split_ms":6623.7,"pure_graph_ms":214.1,"sm_boot_ms":463.8,"sm_decomp_ms":1282.5,"sm_graph_ms":755,"sm_hex_ms":444.3,"sm_index_ms":2.3,"sm_listing_cold_ms":268.7,"sm_listing_warm_ms":269.4,"sm_nav_ms":307.9,"sm_palette_ms":0.3,"sm_rename_ms":384.3,"sm_render_ms":260.2,"sm_search_ms":67.7,"sm_split_ms":1368.2,"fails":0},"status":"discard","description":"Chunk the post-rename text refresh into TEXT_BLOCK pieces instead of one call for the whole requested range (a search window asks for thousands of rows and the heads tool caps a response at 2000, so the oversized call came back short, failed the sequence check and condemned the model to a rebuild). Fixes lg_search 3314 -> 1209, but lg_split 2597 -> 6624 and total 25564 -> 27571: doing it properly is SLOWER here than the accidental rebuild was.","timestamp":1786074100440,"segment":6,"confidence":3.8252964033077643,"asi":{"hypothesis":"one heads call for a whole search-sized window overflows the tool's 2000-row cap, so the refresh always failed its sequence check and forced a rebuild","hypothesis_confirmed":"yes -- chunking removed the spurious rebuild and lg_search fell 3314 -> 1209","but":"lg_split rose 2597 -> 6624. The accidental rebuild was CHEAPER overall than refreshing block by block, because this bench reads most of the segment after renaming and 450 block calls cost more than one linear rebuild.","UNEXPLAINED_AND_MUST_BE_RESOLVED":"work counters moved between the two runs: lg_decomp_lines 3436 -> 3233, lg_split_mapped_lines 2070 -> 1990, lg_search_hits 91783 -> 92733. Same database (targets/bash.i64 mtime unchanged, staged fresh per run), same fixed function set. Something about which path runs is changing what the app SEES. That has to be understood before any version of this is kept -- a perf change must not alter observable work.","leads":["phase_rename renames six functions and undoes them; a function whose original name was auto-generated (sub_X) comes back as a USER name sub_X. Check whether that changes the listing (a label row, or is_auto_name affecting annotate).","the search terms are 'mov' and 'call'; the temporary name _bench_<pid>_<k> contains a 'c', so leftover names would inflate a 'c'-prefixed search -- but the terms are full words, so check for residue directly.","lg_listing_rows is a streamed-progress reading, not a work counter -- ignore that one."],"work_preserved":".auto/wip-chunk.patch","next_action_hint":"resolve the counter drift first. Then, if the chunking is kept, try TEXT_BLOCK = 2000 (the tool's cap) so a wholesale refresh costs about what a rebuild costs while a viewport still needs one call."}}
-{"run":34,"commit":"3ca9e1e","metric":28651.3,"metrics":{"lg_boot_ms":720.6,"lg_decomp_ms":2427.4,"lg_graph_ms":1222.4,"lg_hex_ms":440.9,"lg_index_ms":72.6,"lg_listing_cold_ms":433.6,"lg_listing_warm_ms":465.9,"lg_nav_ms":6809.7,"lg_palette_ms":4.7,"lg_rename_ms":710.1,"lg_render_ms":228.7,"lg_search_ms":6891.2,"lg_split_ms":2259.2,"pure_graph_ms":214.5,"sm_boot_ms":457.7,"sm_decomp_ms":1303.6,"sm_graph_ms":700.2,"sm_hex_ms":445.4,"sm_index_ms":2.4,"sm_listing_cold_ms":271.4,"sm_listing_warm_ms":270.9,"sm_nav_ms":309.2,"sm_palette_ms":0.3,"sm_rename_ms":386.6,"sm_render_ms":265.8,"sm_search_ms":70.1,"sm_split_ms":1266.2,"fails":0},"status":"keep","description":"CORRECTNESS FIX, kept despite a worse metric. The un-chunked refresh was showing STALE NAMES on any wide read: one heads call for a search-sized window overflows the tool's 2000-row cap, the short response fails the sequence check, and the block is left with its old text. Refresh is now done a block at a time. Adds .auto/check_rename.py to the gate, which fails hard on the previous code and passes on this one.","timestamp":1786074769473,"segment":6,"confidence":4.974025132789222,"asi":{"the_bug_the_metric_was_rewarding":"/tmp/stalewindow.py renames a function, then reads the segment through window() the way the search body does. On the previous commit: 0 rows show the new name, the old one is still there, stale_structure=True. The 25564ms reading was FASTER because it was skipping the refresh -- the bench only failed to notice because phase_rename undoes its renames, so the stale text happened to be right again by the time anything compared it.","why_it_happened":"_ensure_text issued ONE heads call for the whole requested range. The tool caps a response at 2000 rows; a 4096-row search window came back short, page[:len(want)] != want, and the code took its 'the walk moved' branch -- marking the block fresh and leaving the old text.","fix":"refresh a TEXT_BLOCK (500 heads) at a time, looping. The sequence check then only ever fires for a real structural change.","cost":"total 25564 -> 28651. A wholesale re-read after a rename is ~10% dearer than a rebuild would be (6.6s vs 6.0s measured directly), and this bench does exactly that -- six renames then a split-view pass and a whole-segment search. The user-facing trade is: a rename is 566x faster (10.3ms vs 5660ms to get the listing back on bash), and a full-segment search immediately after one is ~10% slower.","counter_drift_resolved":"the 3233/1990/92733 counters in experiment #33 were NOT caused by the change -- both configurations reproduce 3436/2070/91783 twice each. That run was CPU-starved (the box is at load 1.6 and the 60s tool deadline can truncate a big decompile). Watch decomp_lines as a starvation signal.","gate_extended":".auto/check_rename.py: narrow read (painting), wide read (search body), and the whole model against a rebuild. Verified it FAILS on the previous commit with 4 problems and passes on this one.","principle":"the second time this session that the honest number is worse than the dishonest one. A benchmark rewards whatever it can see; the guard has to be a check that fails, not a number that improves."}}
-{"run":35,"commit":"6ec4bd8","metric":29392.6,"metrics":{"lg_boot_ms":711.9,"lg_decomp_ms":2406.4,"lg_graph_ms":1271.9,"lg_hex_ms":445.3,"lg_index_ms":71.9,"lg_listing_cold_ms":748.5,"lg_listing_warm_ms":403.8,"lg_nav_ms":6596.2,"lg_palette_ms":4.8,"lg_rename_ms":715.8,"lg_render_ms":219.3,"lg_search_ms":7055.2,"lg_split_ms":2629.8,"pure_graph_ms":212.8,"sm_boot_ms":445.6,"sm_decomp_ms":1281,"sm_graph_ms":775,"sm_hex_ms":442.3,"sm_index_ms":2.3,"sm_listing_cold_ms":265.6,"sm_listing_warm_ms":266.7,"sm_nav_ms":302.9,"sm_palette_ms":0.3,"sm_rename_ms":380.8,"sm_render_ms":255.1,"sm_search_ms":67,"sm_split_ms":1414.2,"fails":0},"status":"discard","description":"Documentation-only commit (playbook + ideas). Re-measured to confirm the post-fix state: 28651 -> 29393 is within the spread on this now-loaded box, and lg_search holds at ~7.0s, confirming that a whole-segment read after renames costs a whole-segment re-render.","timestamp":1786075083972,"segment":6,"confidence":7.108776152564333,"asi":{"state":"v7 baseline 33243 -> 29393 (-11.6%). rename 10055 -> 716 is the headline; the cost of re-rendering after a rename did not disappear, it moved to whoever reads the rows (lg_search 775 -> ~7000, because this bench reads the entire segment right after renaming). For a user reading a viewport the rename is 566x faster and nothing else changes.","why_there_is_no_more_free_win_here":"after a rename, the rows you read have to be re-rendered; re-rendering N heads costs what loading N heads costs. Refreshing only the rows that ACTUALLY changed would need xrefs_to(renamed_ea) to enumerate them, and a name can reach a row without a direct xref (a comment, a struct field in an operand) -- miss one and you are back to showing a stale name, which is the bug just fixed. Logged in .auto/ideas.md rather than attempted.","highest_yield_activity_this_session":"asking what the benchmark does NOT measure. Three of the five bench corrections found an invisible cost, and two of those (split view, rename) were among the biggest wins of the whole session.","next_action_hint":"keep probing uncovered features with the /tmp/featprobe.py shape: xrefs (x), the strings browser (\"), literal formats (o), make-code/data edits, history, traces, RPC. Domain-level probes already say xrefs/strings/structs/resolve are fast, so drive them through the UI instead."}}
-{"run":36,"commit":"1e9f47b","metric":28842.1,"metrics":{"lg_boot_ms":712.2,"lg_decomp_ms":2366.9,"lg_graph_ms":907.8,"lg_hex_ms":447.2,"lg_index_ms":72.2,"lg_listing_cold_ms":443.7,"lg_listing_warm_ms":453.7,"lg_nav_ms":6610.2,"lg_palette_ms":4.8,"lg_rename_ms":735.3,"lg_render_ms":228.4,"lg_search_ms":7122.8,"lg_split_ms":2609.7,"pure_graph_ms":212.3,"sm_boot_ms":438.7,"sm_decomp_ms":1279.7,"sm_graph_ms":783.2,"sm_hex_ms":447.3,"sm_index_ms":2.5,"sm_listing_cold_ms":267.7,"sm_listing_warm_ms":263.1,"sm_nav_ms":309.7,"sm_palette_ms":0.3,"sm_rename_ms":383.9,"sm_render_ms":255,"sm_search_ms":66.3,"sm_split_ms":1417.6,"fails":0},"status":"discard","description":"Tried and reverted: throttling the background listing streamer's UI reports by time, and adding a token so only the newest streamer survives a re-prime. Both measured WORSE on a direct responsiveness probe (xrefs dialog while streaming: 1314ms baseline, 1436ms throttled, 2038ms with the token). Reverted; the finding is recorded in .auto/ideas.md. This run confirms the committed state at 28842.","timestamp":1786075786983,"segment":6,"confidence":8.431402690090582,"asi":{"real_finding_worth_keeping":"the app is several times slower while a big segment streams in the background: an xrefs dialog is 691ms during the ~7s stream and 105ms after it. Two confirmed mechanisms -- Textual's @work(exclusive=True) does NOT stop a thread worker that is already running, and navigating inside the same segment re-primes against the SAME model, so every jump leaves another streamer behind; and each streamer reports growth to the UI every four pages (thread hop + virtual_size change + full repaint).","what_failed":"a 10/s time throttle made it worse (237 reports vs 112) because the count is per streamer and there are several. Adding a _grow_token so only the newest streamer survives did NOT reduce the report count either -- so the retirement is not happening where it looks like it should, and I do not understand the mechanism well enough to ship a fix.","why_i_stopped":"the probe navigates repeatedly, which is itself what spawns the extra streamers, so it measures the thing it perturbs. A clean probe would drive UI work from a SINGLE navigation while one streamer runs. Recorded in .auto/ideas.md with the reproduction.","also_found":"a PRE-EXISTING crash: StringsPalette.on_mount calls query_one(OptionList) before compose's children are mounted (NoMatches). Reproduces 3/3 on bash AND 3/3 on the pre-autoresearch commit 2b0ae8d, so it is not from this work. ProjectPalette has the same shape.","xrefs_is_fine":"the xrefs path is not a decomp_map-class problem: xrefs_to is 38ms for 12 functions, item building 2.6ms, and the whole 'x'-to-dialog cycle is ~105ms once the listing has finished streaming. Most of that is two modal screen mounts.","state":"v7 baseline 33243 -> 28842 (-13.2%)."}}
-{"run":37,"commit":"6882eea","metric":29365.3,"metrics":{"lg_boot_ms":738.3,"lg_decomp_ms":2438.1,"lg_graph_ms":1287.6,"lg_hex_ms":462.1,"lg_index_ms":72.1,"lg_listing_cold_ms":438.6,"lg_listing_warm_ms":460.3,"lg_nav_ms":6762.3,"lg_palette_ms":4.8,"lg_rename_ms":719.1,"lg_render_ms":224.7,"lg_search_ms":7055.7,"lg_split_ms":2672.5,"pure_graph_ms":217.4,"sm_boot_ms":435.3,"sm_decomp_ms":1269.6,"sm_graph_ms":721,"sm_hex_ms":437.5,"sm_index_ms":2.5,"sm_listing_cold_ms":281.1,"sm_listing_warm_ms":264.8,"sm_nav_ms":316.9,"sm_palette_ms":0.3,"sm_rename_ms":390,"sm_render_ms":257.4,"sm_search_ms":67.8,"sm_split_ms":1367.8,"fails":0},"status":"discard","description":"Documentation-only (trace scaling finding). Probed idatui/trace.py: loading is exactly linear, register_state is O(1), but Trace.memory() is linear in TRACE LENGTH per call. Recorded rather than fixed — the differential against Tenet's reference reader is skipped on this box, so the safety net for an indexing change is too thin. Run confirms the committed state.","timestamp":1786076140273,"segment":6,"confidence":13.948955495004537,"asi":{"trace_measurements":"load 36.6/73.3/143.9/280.7 ms for 20k/40k/80k/160k rows (x1.95 per doubling -- linear, correct). register_state x200: ~1.1ms at every size -- O(1). memory() x200: 16.3/31.7/63.4/125.8 ms -- LINEAR IN TRACE LENGTH per call.","why":"_mem_index sorts accesses by address and bisects to the query window, which is right, but then iterates every access in that window across all time and filters by t > idx. A stack slot written once per loop iteration accumulates one entry per iteration, so the stack pane's cost grows with how long the trace ran.","why_not_fixed":"tests/test_trace_vs_tenet.py -- the differential against Tenet's own reference reader -- is SKIPPED on this box, leaving 35 synthetic checks as the only guard on a subtle indexing change. Recorded in .auto/ideas.md with the probe (/tmp/traceprof.py) and the precondition.","session_state":"v7 baseline 33243 -> best 28651 kept. Working tree clean, 41 commits on autoresearch/perf-20260807, full 830-check gate plus two custom equivalence checks green.","next_action_hint":"remaining ideas, in .auto/ideas.md: the trace memory index (needs the Tenet differential first), why the _grow_token did not retire extra streamers, refreshing only the rows a rename actually changed, and the skeleton walk for ensure_ea (costed at ~5%)."}}
-{"run":38,"commit":"df88ece","metric":27552.6,"metrics":{"lg_boot_ms":759.2,"lg_decomp_ms":2754.1,"lg_graph_ms":1207.2,"lg_hex_ms":448,"lg_index_ms":69.6,"lg_listing_cold_ms":434.5,"lg_listing_warm_ms":442.9,"lg_nav_ms":6642.3,"lg_palette_ms":4.7,"lg_rename_ms":730.6,"lg_render_ms":223.6,"lg_search_ms":5627.5,"lg_split_ms":2268,"pure_graph_ms":218.7,"sm_boot_ms":465,"sm_decomp_ms":1290.8,"sm_graph_ms":720.7,"sm_hex_ms":436.1,"sm_index_ms":2.4,"sm_listing_cold_ms":267.2,"sm_listing_warm_ms":266.5,"sm_nav_ms":292.9,"sm_palette_ms":0.3,"sm_rename_ms":380.1,"sm_render_ms":255.5,"sm_search_ms":68.1,"sm_split_ms":1276.3,"fails":0},"status":"keep","description":"Size the worker's per-line render cache to hold a segment's DISTINCT lines (16384 -> 65536, overridable with IDATUI_LINE_CACHE). This was a recorded dead end — it does nothing for a cold sweep — but the rename fix created a second-sweep workload, and re-rendering after a rename is now 21% cheaper. lg_search 7123 -> 5628.","timestamp":1786076605767,"segment":6,"confidence":8.431402690090582,"asi":{"hypothesis":"the biggest remaining term (lg_search 7.1s) is entirely the post-rename re-render, and a re-render is a SECOND sweep over the same lines -- which is exactly the case the line cache was measured to help and then filed as a dead end because nothing did it","why_it_stopped_being_a_dead_end":"experiment v1 #6 sized the cache at 16384 and noted that growing it 'does nothing for a cold sweep, only for a second sweep'. Nothing did a second sweep -- until v7 #2 made a rename keep the listing's walk and re-render its text on demand. The structural change created the workload the dead end was waiting for.","sizing_measurement":{"16384":[17.18,16.88,"+29MB"],"32768":[16.99,17.23,"+52MB"],"65536":[17.01,11.13,"+75MB"],"131072":[16.91,11.2,"+75MB"],"note":"bash .text, first sweep then second, in the worker"},"why_65536":"bash's .text is 228659 rows but only 53363 DISTINCT lines; 32768 still thrashes and 131072 buys nothing because the working set already fits. It is a bound, not a proportion -- a bigger binary fills it and stops, so the cost is capped at ~56MB whatever is open. IDATUI_LINE_CACHE lowers it for a pool of workers competing for memory.","direct_ab_on_the_real_path":"/tmp/refreshcost.py -- load the whole listing, rename, re-read every row through window(): 6445ms -> 5115ms (-21%), worker RSS 164MB -> 211MB. Initial load_all unchanged (5645 vs 5585).","gains":"total 28651 (previous best) -> 27553; lg_search 7123 -> 5628 (-21%); lg_split 2673 -> 2268","work_unchanged":"decomp_lines 3436, split_mapped 2070, search_hits 91783, graph_blocks 1000, rename_ok 6/6 -- all identical","verified":"830-check gate plus check_search and check_rename green; spans still byte-identical vs 2b0ae8d","lesson_recorded_in_prompt":"re-read the dead-end list after any structural change -- a rejected idea can become the right one when the workload around it moves","next_action_hint":"lg_nav 6642 is now the largest term again and is at the per-row floor. lg_search 5628 is still the post-rename re-render; the only way further down is to re-render fewer rows (see the xrefs-driven invalidation idea in .auto/ideas.md, which is risky because a name can reach a row without a direct xref)."}}
-{"run":39,"commit":"df88ece","metric":28376.5,"metrics":{"lg_boot_ms":762.1,"lg_decomp_ms":2374.3,"lg_graph_ms":938.1,"lg_hex_ms":452.4,"lg_index_ms":70.2,"lg_listing_cold_ms":419.6,"lg_listing_warm_ms":464.1,"lg_nav_ms":6941.9,"lg_palette_ms":5,"lg_rename_ms":1973.2,"lg_render_ms":221.9,"lg_search_ms":4117.3,"lg_split_ms":2333.3,"pure_graph_ms":214.6,"sm_boot_ms":453.7,"sm_decomp_ms":1270.5,"sm_graph_ms":726.9,"sm_hex_ms":440.6,"sm_index_ms":2.4,"sm_listing_cold_ms":283,"sm_listing_warm_ms":284.3,"sm_nav_ms":310.4,"sm_palette_ms":0.3,"sm_rename_ms":1575.9,"sm_render_ms":257.6,"sm_search_ms":59.6,"sm_split_ms":1423.7,"fails":0},"status":"checks_failed","description":"Digest mode for `heads`: the worker answers \"does this page still render exactly as it did?\" for the cost of the render alone, so a post-rename refresh skips shipping, unpickling and rebuilding pages that did not change. lg_search 5628 -> 4117 — but it BREAKS 'O' cycles back, and it made rename 2-4x slower. Both causes understood.","timestamp":1786077531326,"segment":6,"confidence":10.35869418588966,"asi":{"hypothesis_first_tested_empirically":"before building anything I checked the parked xrefs-driven idea: /tmp/whatchanges.py rebuilds the whole segment before and after a rename and diffs every row. On echo, all 19 changed rows over 4 renames were covered by (function extent + xrefs_to). On ls_ttl, 53 of 54 were -- the one that was not is 'lea rcx, unk_1D7A0' -> 'byte_1D7A0', which the RENAME DID NOT CAUSE: IDA's own analysis defined that byte. An address-predicted invalidation would leave that row stale for good, so the idea is now measured-and-rejected rather than assumed-risky.","what_i_built_instead":"an exact check: `heads(..., digest=True)` builds the rows as usual but returns only hash+count instead of the rows. The client stores the digest each page came back with and asks 'still the same?' before re-fetching. Uses the interpreter's own hash deliberately -- it never has to mean anything outside the worker process, the client is only a courier.","measured_win":"post-rename whole-segment re-read 5115ms -> 3833ms (-25%); lg_search 5628 -> 4117","BUG_1_correctness":"the stored digest describes the page AS LOADED, not as the client currently holds it. After a full refetch the client's rows change but _page_digest is not updated -- so when a literal format cycles hex -> dec -> ... -> hex, the worker's digest matches the ORIGINAL stored one, the page is declared unchanged, and the row keeps the intermediate decimal text. That is exactly the failure: opfmt_listing ''O' cycles back' got 'sub rsp, 184' wanting 'sub rsp, 0B8h'.","BUG_2_performance":"_ensure_text_from tests page freshness with all(_head_gen[k] == gen for k in the page), and get() calls _ensure_text per ROW -- so every row scanned a whole page's gen array. rename went 380 -> 1576ms (sm) and 730 -> 1973ms (lg).","fix_for_both":"refresh at PAGE granularity end to end instead of the snapped TEXT_BLOCK: a page is exactly what the tool produced from (addr, count=PAGE), so refetching with the same parameters reproduces the same sequence with no snapping, the stored digest can be updated whenever the page's rows are replaced, and every head in a page shares one gen value so freshness is a single probe rather than a scan.","work_preserved":".auto/wip-digest.patch","gate_worked":"check_rename and check_search both passed -- neither exercises a format cycle. The scenario suite caught it. That is the third time a cache-shaped change failed in a way only one specific test could see."}}
-{"run":40,"commit":"d9e8fdb","metric":26491.7,"metrics":{"lg_boot_ms":808.6,"lg_decomp_ms":2600.2,"lg_graph_ms":901.2,"lg_hex_ms":448.7,"lg_index_ms":70.3,"lg_listing_cold_ms":432.8,"lg_listing_warm_ms":445.5,"lg_nav_ms":7004.8,"lg_palette_ms":4.8,"lg_rename_ms":758.6,"lg_render_ms":231.3,"lg_search_ms":3959.2,"lg_split_ms":2659.2,"pure_graph_ms":214.7,"sm_boot_ms":432.6,"sm_decomp_ms":1305.1,"sm_graph_ms":758.3,"sm_hex_ms":431.8,"sm_index_ms":2.3,"sm_listing_cold_ms":275.4,"sm_listing_warm_ms":289.4,"sm_nav_ms":295.6,"sm_palette_ms":0.3,"sm_rename_ms":424.3,"sm_render_ms":263.6,"sm_search_ms":61.1,"sm_split_ms":1411.9,"fails":0},"status":"keep","description":"heads(digest=True): the worker answers \"does this page still render exactly as you hold it?\" with a hash and a count instead of the page. After a rename nearly every page is unchanged, so the pickling, transfer, unpickling and Head rebuild are all skipped. Redone at PAGE granularity end to end, which fixes both bugs of the first attempt. lg_search 5628 -> 3959.","timestamp":1786077952004,"segment":6,"confidence":8.431402690090582,"asi":{"gains":"total 27553 (previous best) -> 26492 (-3.9%); lg_search 5628 -> 3959 (-30%); post-rename whole-segment re-read 5115 -> 3720ms measured directly","cost":"lg_rename 731 -> 759 and sm_rename 380 -> 424: a page now costs one extra round trip to probe before it is either accepted or refetched. Getting a viewport back after a rename on bash is 21.5ms instead of 10.3ms -- still 230x better than the 4941ms rebuild it replaced.","what_the_two_bugs_were":{"correctness":"the first attempt stored the digest a page LOADED with and never updated it when the page was refetched. A literal format cycling hex -> dec -> hex then hashed back to the original while the client still held the decimal text: opfmt_listing ''O' cycles back' got 'sub rsp, 184' wanting '0B8h'.","performance":"freshness was tested with all(_head_gen[k] == gen for k in the page) while get() called _ensure_text per ROW, so every row scanned a whole page's gen array -- rename went 2-4x slower."},"the_fix_was_one_idea":"refresh at PAGE granularity instead of the snapped TEXT_BLOCK. A page is exactly what heads(addr, count=PAGE) produced, so re-asking with the same arguments reproduces the same sequence -- no snapping to address groups needed, the stored digest can be updated whenever the rows are replaced, and every head in a page shares one generation marker so freshness is a single probe. TEXT_BLOCK is gone.","why_hash_and_not_a_stable_digest":"the value never has to mean anything outside the worker process -- the client stores what a page hashed to and hands the same number back. One worker, one process, one hash seed. It covers ea/kind/size/text/name AND the colour spans, so two lines that collapse to the same text but colour differently are not confused.","empirical_work_that_shaped_this":"before building anything I tested the parked xrefs-driven idea with /tmp/whatchanges.py (rebuild the segment before and after a rename, diff every row). It fails: on ls_ttl one changed row was 'lea rcx, unk_1D7A0' -> 'byte_1D7A0', which the rename did not cause -- IDA's own analysis defined that byte. Address-predicted invalidation would leave it stale forever. Recorded as measured-and-rejected in .auto/ideas.md.","work_unchanged":"decomp_lines 3436, split_mapped 2070, search_hits 91783, graph_blocks 1000, rename_ok 6/6, decomp_ok 12 -- identical","verified":"830-check gate, check_search (140 prefixes), check_rename on echo AND ls_ttl (5952 and 28807 rows, wide and narrow, against a rebuild), and the 19 opfmt scenarios that caught the first attempt","next_action_hint":"lg_nav 7005 is the largest term and is the cold walk at its per-row floor. lg_search 3959 is now mostly the _line_plain pass plus the pages that genuinely changed. Next best unexplored: why the digest probe costs a whole extra round trip per page -- it could ride along with the first refetch request rather than preceding it."}}
-{"run":41,"commit":"7e4f086","metric":25814,"metrics":{"lg_boot_ms":714.9,"lg_decomp_ms":2376.2,"lg_graph_ms":948.8,"lg_hex_ms":458.6,"lg_index_ms":69.3,"lg_listing_cold_ms":448.5,"lg_listing_warm_ms":482.1,"lg_nav_ms":6808,"lg_palette_ms":4.7,"lg_rename_ms":752,"lg_render_ms":219.4,"lg_search_ms":4008.3,"lg_split_ms":2275.5,"pure_graph_ms":225.2,"sm_boot_ms":456.1,"sm_decomp_ms":1292.4,"sm_graph_ms":795.8,"sm_hex_ms":475.2,"sm_index_ms":2.5,"sm_listing_cold_ms":269.1,"sm_listing_warm_ms":272.2,"sm_nav_ms":320.2,"sm_palette_ms":0.3,"sm_rename_ms":446.3,"sm_render_ms":264.8,"sm_search_ms":60.6,"sm_split_ms":1366.9,"fails":0},"status":"keep","description":"Three redundancies in the heads walk: item flags were fetched three times per head (row builder, _is_unknown via _advance, and _rows_for), get_func was called per head where a head is nearly always in the same function as the one before it, and the page digest rebuilt a tuple-of-tuples per row where one spans list is shared by ~45% of them. Cold heads 18.62 -> 17.77 us/row, warm 11.53 -> 10.87.","timestamp":1786078727527,"segment":6,"confidence":7.765877831715214,"asi":{"gains":"total 26492 (previous best) -> 25814 (-2.6%); lg_split 2659 -> 2276; sm_nav_worst 158 -> 151. Microbenchmarked rather than trusted to total_ms, since ~0.8us/row over ~500k row-renders is 0.4s and the box's spread is ~500ms: cold heads 18.62 -> 17.77 us/row, warm 11.53 -> 10.87, digest 0.75 -> 0.62.","equivalence":"/tmp/headsdump.py runs the tool under both code versions in separate processes and compares whole payloads: 109 004 rows over 294 payloads (bash + ls_ttl, BOTH annotate modes, plus the offset/end/back/count variants that share the walk) -- 0 mismatches. diff_spans still byte-identical vs 2b0ae8d on three binaries. 830-check gate, check_search and check_rename green.","get_func_cache_was_validated_not_assumed":"I had rejected this earlier on a hunch about IDA function CHUNKS -- a tail chunk of B inside A's [start_ea, end_ea) would be misattributed. /tmp/funccache.py walks every head of bash/ls_ttl/echo comparing the cached answer against get_func: 437 324 heads, 0 disagreements. Hit rate is only ~50% across all segments (a head outside any function cannot be cached by range) but much higher inside .text, which is what the listing walks.","TWO_SCHEMES_MEASURED_AND_REJECTED_FIRST":"before micro-optimising I tested the two big parked ideas, both of which would have made a rename nearly free. Listing rows predicted from xrefs_to + function extent: ls_ttl had 1 of 54 changed rows uncovered, 'lea rcx, unk_1D7A0' -> 'byte_1D7A0', which the rename did not cause -- IDA's own analysis defined that byte. Decompilations predicted from 'the old name appears in the cached text': 16 misses over 4 renames, EVERY one Hex-Rays type inference moving (unsigned int a4 -> int a4) in functions unrelated to the rename. Both recorded in .auto/ideas.md.","generalised_lesson":"predicting the effect of an edit on a database that has its own opinions is unsound. Verify instead -- which is exactly why heads(digest=True) works: it asks what a row renders as NOW, not what should have changed. This probably also explains the lg_decomp_lines drift blamed on CPU starvation in v5 #4.","things_checked_and_found_not_worth_it":{"digest_on_the_normal_load_path":"suspected a regression from v7 #10; it is 0.62-0.75 us/row, not the 1.9 the noisy call timing suggested","folding_the_opcode_byte_read_into_heads":"the extra read_bytes round trip is 0.35 us/row, ~81ms over a whole bash segment","pipelining_client_parsing_with_worker_compute":"blocked -- cursor.next is inside the pickle, so the next request cannot be issued before unpickling, and _build_page itself needs the socket for opcode bytes"},"state":"generate_disasm_line is now 6.09 of the 10.87 us/row warm cost (56%) and is IDA's. What is left of ours is ~4.8 us/row spread thin across a dozen places.","next_action_hint":"lg_nav 6808 (26%) and lg_search 4008 (16%) are both at their per-row floors now. The unexplored areas are the ones .auto/ideas.md lists as blocked or unmeasured: the trace memory index (needs test_trace_vs_tenet runnable), the streaming-responsiveness question, and the features the bench still does not drive (xrefs dialog, strings browser, struct editor, make-code edits, history, traces, RPC)."}}
-{"run":42,"commit":"16318e4","metric":25783,"metrics":{"lg_boot_ms":777.9,"lg_decomp_ms":2381.9,"lg_graph_ms":1209.5,"lg_hex_ms":450,"lg_index_ms":68,"lg_listing_cold_ms":434.6,"lg_listing_warm_ms":405.2,"lg_nav_ms":6801.2,"lg_palette_ms":4.7,"lg_rename_ms":744.7,"lg_render_ms":222.4,"lg_search_ms":3885.6,"lg_split_ms":2261.4,"pure_graph_ms":216.4,"sm_boot_ms":463.6,"sm_decomp_ms":1304.6,"sm_graph_ms":740.9,"sm_hex_ms":438.4,"sm_index_ms":2.4,"sm_listing_cold_ms":270.3,"sm_listing_warm_ms":267.2,"sm_nav_ms":312.4,"sm_palette_ms":0.3,"sm_rename_ms":415.8,"sm_render_ms":257.7,"sm_search_ms":60.8,"sm_split_ms":1385.2,"fails":0},"status":"keep","description":"An item edit (c/d/u/p) keeps the listing's walk in front of it instead of discarding the model. bump_items now takes the edited address; rows before an edit keep their addresses and their row numbers, so only the pages from the edit onward are re-walked. Getting the listing back after undefining at the cursor on bash: 4890ms -> 19ms (257x). Adds .auto/check_edit.py to the gate. total_ms is flat — the bench has no item-edit phase, and the one I wrote hangs (reverted, cause recorded).","timestamp":1786082485850,"segment":6,"confidence":5.464825819307547,"asi":{"how_it_was_found":"kept probing features the bench does not drive -- the pattern that produced the flowchart hull, decomp_map and the rename walk. /tmp/itemedit.py: the undefine tool call is 1ms and getting the listing back is 4890ms, because bump_items cleared _listings and the reload re-walked the segment. Same bug as the rename one, in the sibling path.","measured":"edit at the cursor (96% into bash's .text): 4890 -> 19ms. Edit early in the segment: 5130 -> 4542ms, which is the honest case -- everything after an edit really does have to be re-walked.","why_it_is_safe":"truncate_from drops two pages rather than one (undefining can coalesce backwards into the run in front of it), and the kept prefix is then marked text-stale -- so every kept page is digest-checked on the next read, and a page that really did move fails its (ea, kind) sequence check and sets stale_structure, which makes Program.listing rebuild. Safe by construction rather than by argument about how far a reflow can reach.","gate_added_and_proven":".auto/check_edit.py drives undefine at 1/4/16 bytes at three positions (90%, 50%, 5% through the segment) and compares the kept model against a rebuild, narrow and wide. 0 problems on echo and ls_ttl. Proven to FAIL by deliberately making truncate_from too permissive: it reported 'kept model has 5952 rows, a rebuild has 5950'.","HONEST_LIMITATION":"total_ms 25814 -> 25783 is flat: nothing in the bench edits an item. I wrote a phase for it and reverted it because it HANGS -- driving undefine from inside the pilot produces no output and is killed at the timeout, while the identical sequence against Program directly is fine, and the same sequence with a print between prog.listing(ea) and ensure_ea is also fine. That points at the app's background threads (after bump_items deletes the model, a _prime/_grow worker still in flight can re-register a fresh one and hold its _load_lock), which is the same area as the unexplained streaming-responsiveness item already in .auto/ideas.md. Shipping a hanging bench phase would be worse than not having one.","what_i_did_not_do":"I did not weaken the phase until it passed, and I did not claim the win in total_ms. It is carried by a direct measurement and a permanent gate.","next_action_hint":"the concurrency question is now blocking two things (a bench phase for edits, and the streaming-responsiveness fix). Working out why @work(exclusive=True) leaves thread workers running -- and giving _prime/_grow a way to actually retire -- is the highest-value next step, and it is a correctness/responsiveness matter as much as a perf one."}}
-{"run":43,"commit":"b6646aa","metric":25121.9,"metrics":{"lg_boot_ms":694.8,"lg_decomp_ms":2370.8,"lg_graph_ms":917.1,"lg_hex_ms":456.1,"lg_index_ms":69.4,"lg_listing_cold_ms":442.7,"lg_listing_warm_ms":498.8,"lg_nav_ms":6548.8,"lg_palette_ms":4.8,"lg_rename_ms":711.4,"lg_render_ms":224.9,"lg_search_ms":3484.7,"lg_split_ms":2619.3,"pure_graph_ms":220.7,"sm_boot_ms":448.6,"sm_decomp_ms":1292.5,"sm_graph_ms":748.8,"sm_hex_ms":438.3,"sm_index_ms":2.4,"sm_listing_cold_ms":274.3,"sm_listing_warm_ms":266.5,"sm_nav_ms":302.7,"sm_palette_ms":0.3,"sm_rename_ms":392.5,"sm_render_ms":260.2,"sm_search_ms":59.6,"sm_split_ms":1370.9,"fails":0},"status":"keep","description":"The page-freshness check carries the digest the client already holds (heads(expect=...)) instead of asking first and fetching afterwards. A page that has NOT changed costs one round trip as before; a page that HAS changed now costs one instead of two. Also corrects the record: the item-edit bench hang is pilot start-up flakiness, not the _prime/_grow concurrency I blamed it on — proved with a stack dump.","timestamp":1786083427343,"segment":6,"confidence":4.621649308519722,"asi":{"gains":"total 25783 -> 25122 (-2.6%, best v7); lg_search 3886 -> 3485; sm_rename 416 -> 393. Direct measurements: post-rename whole-segment re-read 3720 -> 3411ms; getting the listing back after a rename on bash 21.5 -> 15.6ms (319x vs a rebuild); after an item edit 19 -> 17ms.","the_change":"heads gains `expect` (the digest a caller already holds) in place of the boolean `digest` flag. The worker builds the rows either way and omits them only when they still hash to `expect`. Sending the expectation rather than asking first is what removes the second round trip on a changed page -- which was the regression I introduced in v7 #10 (rename 380 -> 424ms) and flagged in my own next_action_hint.","equivalence":"normal calls (no expect) compared payload-for-payload against the previous commit across processes: 166 payloads, 63 964 rows, 0 mismatches. check_rename and check_edit both clean, 830-check gate green.","DIAGNOSIS_CORRECTED":"last iteration I logged that the item-edit bench phase hangs because 'after bump_items deletes the model, a _prime/_grow worker still in flight can re-register a fresh one and hold its _load_lock'. A stack dump (faulthandler.dump_traceback_later) says otherwise: at the moment of the hang there are NO idatui threads at all -- the main thread is idle in selectors.select() and everything else is an idle asyncio executor thread, and the app is stuck BEFORE app.run_test() returns. It is pilot start-up flakiness, nothing to do with bump_items or _load_lock. Ruled out: the kitty-graphics query (IDATUI_KITTY=0 still hangs). Partly environmental: orphaned idatui/worker.py processes accumulate from runs killed by `timeout`, and clearing them let the next run boot -- but it recurred, so that is not the whole story.","operational_note":"kill stray workers between probe runs (pkill -f idatui/worker.py). Several of this session's confusing measurements were taken with orphans competing for the box.","what_this_means_for_the_backlog":"the 'why does @work(exclusive=True) leave thread workers running' item is NOT what blocks the item-edit bench phase. The two are separate: the streaming-responsiveness question is still open on its own evidence (112 vs 237 _grew reports), but the bench phase is blocked on pilot start-up reliability instead.","state":"v7 baseline 33243 -> 25122 (-24.4%). lg_nav 6549 and lg_search 3485 are both at their per-row floors; what is left of the worker's cost is 56% generate_disasm_line."}}
-{"run":44,"commit":"7e4b593","metric":24513.8,"metrics":{"lg_boot_ms":693.2,"lg_decomp_ms":2347.3,"lg_graph_ms":931.2,"lg_hex_ms":460.3,"lg_index_ms":69.1,"lg_listing_cold_ms":434.2,"lg_listing_warm_ms":462.4,"lg_nav_ms":6639.6,"lg_palette_ms":4.7,"lg_rename_ms":699.9,"lg_render_ms":219.7,"lg_search_ms":3441.1,"lg_split_ms":2218,"pure_graph_ms":216.5,"sm_boot_ms":436.9,"sm_decomp_ms":1264.9,"sm_graph_ms":758.2,"sm_hex_ms":437.9,"sm_index_ms":2.3,"sm_listing_cold_ms":256.8,"sm_listing_warm_ms":256,"sm_nav_ms":309.5,"sm_palette_ms":0.3,"sm_rename_ms":379.1,"sm_render_ms":249.5,"sm_search_ms":59.2,"sm_split_ms":1266,"fails":0},"status":"keep","description":"decomp_map: memoise obj_id -> ea for the whole function instead of only comparing against the previous column. dstr() was 79% of the tool (24us a call) and items interleave, so foo(a, b) flips call->arg->call and re-formatted an item already seen: 106594 calls for 15417 lines of bash. Also corrects run #30's claim that the duplicate ida_hexrays.decompile is what costs -- a warm decompile is 0.01ms.","timestamp":1786084349637,"segment":6,"confidence":4.950123344769902,"asi":{"hypothesis":"the split view is the 3rd largest term and run #30 left a named suspect behind; profile decomp_map's sweep instead of trusting the note","gains":"total 25122 -> 24514 (-2.4%, best v7). lg_split 2619 -> 2218, sm_split 1371 -> 1266. Direct: decomp_map over bash's 12 largest 4783 -> 2972ms, echo's 12 431 -> 345ms.","profile_that_drove_it":"/tmp/sweepprof.py over 15417 lines of bash: dstr() 2581ms (79%, 106594 calls @ 24.2us), get_line_item 691ms (445337 @ 1.55us), tag_remove 14ms. The existing dedupe only compared against the PREVIOUS column's obj_id, but ctree items interleave -- foo(a, b) alternates call/arg/call/arg -- so every flip re-formatted an item already seen. obj_id is unique within a cfunc, so a function-scoped memo is exact.","equivalence":"/tmp/mapdiff.py execs the decoded BODY of the current and the previous commit against the same cfunc and compares the whole map: echo 62 functions 0 mismatches, bash 250 functions 0 mismatches. NOTES counters unchanged (sm_split_mapped_lines 984, lg_split_mapped_lines 2070).","CORRECTS_THE_RECORD_1":"run #30 said what is left in decomp_map is the duplicate ida_hexrays.decompile. Wrong: /tmp/hxcache.py shows a warm decompile is 0.01ms -- Hex-Rays' own cache is free and the duplicate costs nothing. The 2970ms I first attributed to it was the decompile TOOL's own text/spans building.","CORRECTS_THE_RECORD_2":"I suspected the split view's text and its decomp_map came from different ctrees after a rename (/tmp/mapalign.py showed 6 of 8 differing). Not a bug: the probe used DECOMP_NO_CACHE, which the app never does. Program.decompile calls the force_recompile tool (which does exist) before refetching, so the plain decompile in decomp_map then hits the repopulated cache.","rejected_and_why":"cfunc.refresh_func_ctext() after a rename is 46x faster than the forced recompile (31ms vs 1432ms for ten functions) but only 2/10 reproduce the recompile's text -- the rest differ by Hex-Rays TYPE INFERENCE (char* vs const char*, unsigned int a4 vs int a4). That is a change to what is on screen, so it is a product decision, not a perf change. Recorded in .auto/ideas.md with the probe.","next_action_hint":"the remaining decomp_map cost is get_line_item per screen column (691ms/15k lines), and the sound way down is to map only the ~40 lines the pane can show -- the same lazy shape that won for search highlight ranges. It needs a windowed tool plus a lazy container because app.py and trace_ctl.py both index the whole list. Do NOT step over columns: a one-character variable would be skipped and its EA silently lost."}}
-{"run":45,"commit":"pending","metric":25367.3,"metrics":{"lg_boot_ms":740.4,"lg_decomp_ms":2307.8,"lg_graph_ms":1227.6,"lg_hex_ms":450.2,"lg_index_ms":81,"lg_listing_cold_ms":425.7,"lg_listing_warm_ms":403.5,"lg_nav_ms":6778.2,"lg_palette_ms":4.9,"lg_rename_ms":734.7,"lg_render_ms":220.1,"lg_search_ms":3619.6,"lg_split_ms":2589.3,"pure_graph_ms":216.6,"sm_boot_ms":441.2,"sm_decomp_ms":1210,"sm_graph_ms":727,"sm_hex_ms":421.5,"sm_index_ms":2.4,"sm_listing_cold_ms":258.1,"sm_listing_warm_ms":257.3,"sm_nav_ms":300.8,"sm_palette_ms":0.3,"sm_rename_ms":377.4,"sm_render_ms":249.3,"sm_search_ms":58.9,"sm_split_ms":1263.5,"fails":0},"status":"discard","description":"Replace ida-pro-mcp's decompile_function_safe with a loop that allocates one ctree_item_t instead of three per line and memoises the per-line dstr() by obj_id. Directly measured through the real worker at 3.6x (2695 -> 746ms for the post-processing of bash's 8 largest), and byte-identical over 500 functions — but total_ms rose 24514 -> 25367 on phases this cannot touch (graph +297, nav +138, search +179, boot +47) with the box at load 2.70 vs 2.16. Re-running to separate the win from the drift.","timestamp":1786085063251,"segment":6,"confidence":4.932640144665465,"asi":{"hypothesis":"the decompile TOOL's post-processing is a large hidden cost: a warm Hex-Rays decompile is 0.01ms, yet re-running the tool on bash's 12 largest still cost 2951ms. The culprit is ida-pro-mcp's decompile_function_safe, which has the SAME three faults I fixed in decomp_map -- three ctree_item_t SWIG allocations per line (two never read) and a dstr() per line at 24us.","direct_measurement":"/tmp/decprof.py: 18991 lines of bash, 2302.6ms -> 429.0ms (5.37x), 121.2 -> 22.6 us/line, 0 mismatches. End to end through the real worker (/tmp/verifybind.py, the decompile TOOL with Hex-Rays warm, bash's 8 largest): 2695ms -> 746ms, i.e. 3.6x, so the rebinding definitely takes effect.","equivalence":".auto/check_decomp.py runs BOTH implementations against the same cfunc with include_addresses both ways: 128/128 echo and 372/372 ls_ttl byte-identical. Proven to be a real gate by keying the memo on it.op instead of it.obj_id: 69 problems, 59/128 passing.","why_discarded":"total_ms 24514 -> 25367. The phases this change touches improved (lg_decomp 2347 -> 2308, sm_decomp 1265 -> 1210), but graph +297, nav +138, search +179, boot +47 and index +12 all moved too, and NONE of them go through decompile_function_safe. Load average was 2.70 at the start of this run against 2.16 for the previous one. Per the playbook, totals are not comparable across a load change.","rollback_reason":"primary metric worse, but attributed to machine load rather than the change -- the same shape as #16/#18, both of which were confirmed wins on a re-run.","next_action_hint":"re-apply from /tmp/fastdecomp.patch (git diff saved before the auto-revert; .auto/check_decomp.py also copied to /tmp) and re-run. Load was 0.84 immediately after this run, so the box has calmed. NOTE: checks.sh now calls .auto/check_decomp.py, and .auto files survive the revert -- so the gate will CRASH until the patch is re-applied, because _idatui_decompile_function_safe will not exist in BODY."}}
-{"run":46,"commit":"pending","metric":25171.3,"metrics":{"lg_boot_ms":693.5,"lg_decomp_ms":2317.5,"lg_graph_ms":879.1,"lg_hex_ms":449.8,"lg_index_ms":73.6,"lg_listing_cold_ms":437.1,"lg_listing_warm_ms":461.9,"lg_nav_ms":6738.4,"lg_palette_ms":4.8,"lg_rename_ms":702.8,"lg_render_ms":226.2,"lg_search_ms":3639,"lg_split_ms":2578,"pure_graph_ms":216.2,"sm_boot_ms":435.2,"sm_decomp_ms":1278.7,"sm_graph_ms":742.7,"sm_hex_ms":436.6,"sm_index_ms":2.5,"sm_listing_cold_ms":268.2,"sm_listing_warm_ms":263.5,"sm_nav_ms":289.5,"sm_palette_ms":0.3,"sm_rename_ms":383.3,"sm_render_ms":262.9,"sm_search_ms":58.1,"sm_split_ms":1332.1,"fails":0},"status":"discard","description":"Re-run of #45 (fast decompile_function_safe) with the rebinding no longer importing a module to patch it. Confirms the change does NOT move total_ms: three runs with it (25367/25058/25171) against three without (24514/25122/25783), means 25199 vs 25140. The 3.6x is real but lands on large functions (98us/line saved) while the bench decompiles 286-line ones (30us/line). Parked in .auto/parked/ rather than deleted.","timestamp":1786085672802,"segment":6,"confidence":5.315956151035322,"asi":{"hypothesis":"ida-pro-mcp's decompile_function_safe has the same three faults I fixed in decomp_map: three ctree_item_t SWIG allocations per line (two never read) and a dstr() per line at 24us","confirmed_true_but_small":"the optimisation itself is real and verified three ways -- /tmp/decprof.py 2302 -> 429ms over 18991 lines of bash (121.2 -> 22.6 us/line); through the REAL worker /tmp/verifybind.py 2695 -> 746ms; echo 219 -> 120ms. Byte-identical over 128/128 echo and 372/372 ls_ttl functions with include_addresses both ways.","why_it_does_not_show":"the saving is 98us/line on bash's LARGEST functions but only 30us/line on small ones, because dstr() cost and memo hit rate both scale with ctree size. The bench's fixed set averages 286 lines a function, so the expected effect is 150-250ms against a run-to-run spread of 400-600ms on this box. Three runs with (25367/25058/25171) vs three without (24514/25122/25783): means 25199 vs 25140.","rollback_reason":"primary metric unchanged within noise, and the best single run remains one without the change. Rules say discard on worse-or-unchanged, and unlike #42 the bench DOES cover this path -- it simply covers it with functions too small for the win to matter.","what_i_refused_to_do":"the obvious way to make this show up is to point the bench's fixture picker at larger functions. That is fitting the benchmark to the change, so I did not do it, and I recorded the prohibition next to the parked patch.","preserved":".auto/parked/fast_decompile.patch and .auto/parked/check_decomp.py, with the full measurement table in .auto/ideas.md. I also UNWIRED check_decomp.py from checks.sh before logging: .auto files survive the auto-revert but BODY does not, so the gate would have crashed on every subsequent run.","still_a_user_win":"an F5 on a 2374-line function loses 233ms of post-processing (287 -> 54). Worth re-applying if the goal moves from total session time to per-operation latency.","next_action_hint":"stop mining the decompile path -- what is left there is Hex-Rays. The largest terms are lg_nav 6700 and lg_search 3600, both at their per-row floors, so the next real find is likely another unbenched feature (xrefs dialog, strings browser, history, literal formats) probed with the /tmp/featprobe.py shape."}}
diff --git a/.auto/measure.sh b/.auto/measure.sh
deleted file mode 100755
index 49b16bc..0000000
--- a/.auto/measure.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/bash
-# ida-tui performance benchmark driver.
-#
-# Fast pre-checks first (a syntax error should cost a second, not a worker boot
-# and two database opens), then the real bench.
-set -euo pipefail
-cd "$(dirname "$0")/.."
-
-PY="${IDATUI_PYTHON:-$HOME/ida-venv/bin/python}"
-
-# ~0.4s: catches a syntax error / bad import before we pay for idalib.
-python3 -m compileall -q idatui server tests .auto/bench.py >/dev/null
-
-exec "$PY" .auto/bench.py "$@"
diff --git a/.auto/parked/check_decomp.py b/.auto/parked/check_decomp.py
deleted file mode 100644
index 0f30a03..0000000
--- a/.auto/parked/check_decomp.py
+++ /dev/null
@@ -1,110 +0,0 @@
-#!/usr/bin/env python3
-"""Differential gate for the fast decompile_function_safe (run by checks.sh).
-
-`idatui/worker.py` rebinds ida-pro-mcp's `decompile_function_safe` to our own
-loop, which skips two of the three SWIG allocations per line and memoises the
-per-line `dstr()` by ctree obj_id. That is a pure speed change and the text it
-returns is what the pseudocode pane shows, markers and all -- so it has to be
-byte-identical, not merely similar.
-
-This runs BOTH implementations against the same cfunc for every function of a
-real binary and compares the strings, with `include_addresses` both ways (the
-marker path is the whole point, and the no-marker path must not regress either).
-
- ~/ida-venv/bin/python .auto/check_decomp.py [targets/echo] [max_funcs]
-"""
-from __future__ import annotations
-
-import os
-import shutil
-import sys
-
-HERE = os.path.dirname(os.path.abspath(__file__))
-ROOT = os.path.dirname(HERE)
-sys.path.insert(0, ROOT)
-sys.path.insert(0, HERE)
-
-from bench import stage # noqa: E402
-
-
-def _add_mcp_path() -> None:
- """ida-pro-mcp is installed for the interpreter the WORKER runs, which is
- not necessarily the one running this check."""
- import glob
- for pat in ("/home/user/.local/lib/python3.*/site-packages",
- os.path.expanduser("~/.local/lib/python3.*/site-packages")):
- for d in glob.glob(pat):
- if os.path.isdir(os.path.join(d, "ida_pro_mcp")) and d not in sys.path:
- sys.path.append(d)
-
-
-def main() -> int:
- target = sys.argv[1] if len(sys.argv) > 1 else "targets/echo"
- limit = int(sys.argv[2]) if len(sys.argv) > 2 else 400
- d, path = stage(os.path.join(ROOT, target))
- os.environ["IDA_MCP_TOOL_TIMEOUT_SEC"] = "0"
- _add_mcp_path()
- try:
- import idapro
- idapro.open_database(path, run_auto_analysis=True)
- try:
- import ida_funcs
- import ida_hexrays
- import idautils
- from ida_pro_mcp.ida_mcp import utils
-
- original = utils.decompile_function_safe
- sys.path.insert(0, os.path.join(ROOT, "server"))
- import patch_server
- # exec only our function out of the decoded BODY: the rest of it
- # needs api_types' namespace (@tool, @idasync, ...).
- body = patch_server.BODY
- i = body.find("def _idatui_decompile_function_safe(")
- j = body.find("\n_idatui_strings_cache", i)
- assert i > 0 and j > i, "could not slice the function out of BODY"
- g = {}
- exec(body[i:j], g)
- fast = g["_idatui_decompile_function_safe"]
-
- ida_hexrays.init_hexrays_plugin()
- fails: list[str] = []
- n = ok = 0
- for ea in idautils.Functions():
- f = ida_funcs.get_func(ea)
- if not f:
- continue
- n += 1
- if n > limit:
- break
- for markers in (True, False):
- a, ea_err = original(f.start_ea, include_addresses=markers)
- b, eb_err = fast(f.start_ea, include_addresses=markers)
- if a != b or (ea_err is None) != (eb_err is None):
- fails.append(f"{f.start_ea:#x} (markers={markers})")
- if len(fails) <= 3:
- la = (a or "").splitlines()
- lb = (b or "").splitlines()
- i = next((k for k, (x, y) in enumerate(zip(la, lb))
- if x != y), None)
- if i is None:
- print(f" FAIL {f.start_ea:#x}: {len(la)} lines "
- f"vs {len(lb)}, errs {ea_err!r}/{eb_err!r}")
- else:
- print(f" FAIL {f.start_ea:#x} line {i}:")
- print(f" original: {la[i][:100]!r}")
- print(f" fast : {lb[i][:100]!r}")
- break
- else:
- ok += 1
- print(f"decompile text: {ok}/{min(n, limit)} functions of {target} "
- f"byte-identical to ida-pro-mcp's own loop, "
- f"{len(fails)} problems")
- return 1 if fails else 0
- finally:
- idapro.close_database(save=False)
- finally:
- shutil.rmtree(d, ignore_errors=True)
-
-
-if __name__ == "__main__":
- sys.exit(main())
diff --git a/.auto/parked/fast_decompile.patch b/.auto/parked/fast_decompile.patch
deleted file mode 100644
index c5a4ceb..0000000
--- a/.auto/parked/fast_decompile.patch
+++ /dev/null
@@ -1,122 +0,0 @@
-diff --git a/idatui/worker.py b/idatui/worker.py
-index 556e69a..04252cc 100644
---- a/idatui/worker.py
-+++ b/idatui/worker.py
-@@ -119,6 +119,36 @@ def recv(sock: socket.socket):
- # --------------------------------------------------------------------------- #
- # worker
- # --------------------------------------------------------------------------- #
-+def _use_fast_decompile() -> None:
-+ """Point ida-pro-mcp's decompile tools at our per-line loop.
-+
-+ The shipped ``decompile_function_safe`` allocates three ctree_item_t SWIG
-+ objects per pseudocode line (two of which it never reads) and formats an
-+ item description per line to recover the ``/*0xEA*/`` marker: 121us a line,
-+ which on a warm cfunc is most of what the tool costs. The replacement lives
-+ in server/patch_server.py and is differentially checked against the original
-+ by .auto/check_decomp.py.
-+
-+ Every consumer binds the name at import time (``from .utils import ...``),
-+ so rebinding it on ``utils`` alone would miss them; rebind on each module
-+ that imported it, and leave anything unexpected exactly as it was.
-+ """
-+ try:
-+ from ida_pro_mcp.ida_mcp import api_types, utils
-+ fast = api_types._idatui_decompile_function_safe
-+ except Exception: # noqa: BLE001 -- never let this stop the worker booting
-+ return
-+ utils.decompile_function_safe = fast
-+ # Rebind only on modules that are ALREADY imported. Importing one to rebind
-+ # it would be work the worker had not chosen to do, on the boot path.
-+ prefix = "ida_pro_mcp.ida_mcp."
-+ for name, mod in list(sys.modules.items()):
-+ if not name.startswith(prefix) or mod is None:
-+ continue
-+ if getattr(mod, "decompile_function_safe", None) is not None:
-+ mod.decompile_function_safe = fast
-+
-+
- def _ensure_tools_injected() -> None:
- """Inject idatui's custom tools (heads/read_raw/resolve_names/func_types/...)
- into the installed ida_pro_mcp, idempotently, so the worker is self-sufficient
-@@ -190,6 +220,8 @@ def _open_and_register(binpath: str, load_args: str = ""):
- # importing the package registers all api_*/patched tools against MCP_SERVER
- from ida_pro_mcp.ida_mcp import MCP_SERVER # noqa: WPS433
-
-+ _use_fast_decompile()
-+
- import ida_nalt
- module = os.path.basename(ida_nalt.get_root_filename() or binpath)
-
-diff --git a/server/patch_server.py b/server/patch_server.py
-index 6667e12..64424e4 100644
---- a/server/patch_server.py
-+++ b/server/patch_server.py
-@@ -1039,6 +1039,67 @@ def decomp_map(
- return {"addr": hex(func.start_ea), "lines": lines}
-
-
-+def _idatui_decompile_function_safe(ea, include_addresses=True):
-+ """ida-pro-mcp's ``decompile_function_safe``, with the three costs the same
-+ sweep had in ``decomp_map`` taken out. Byte-identical output -- it is
-+ differentially checked against the original over every function of a real
-+ binary by ``.auto/check_decomp.py``.
-+
-+ The shipped version costs 121us per pseudocode line, which is more than the
-+ line's share of Hex-Rays itself on a warm cfunc:
-+
-+ * it allocates THREE ctree_item_t SWIG objects per line, and ``_head`` and
-+ ``_tail`` are never read -- ``get_line_item`` accepts None for both.
-+ * it calls ``dstr()`` per line. That formats a whole 'EA: description'
-+ string at 24us a call, and consecutive lines of a multi-line expression
-+ report the same ctree item, so memoising by ``obj_id`` (unique within a
-+ cfunc) skips most of them.
-+
-+ 18 991 lines of bash: 2 302ms -> 429ms.
-+ """
-+ import ida_lines
-+ import ida_hexrays as _hx
-+ from ida_pro_mcp.ida_mcp.utils import compact_whitespace, decompile_checked
-+ from ida_pro_mcp.ida_mcp.sync import IDAError
-+ try:
-+ cfunc = decompile_checked(ea)
-+ item = _hx.ctree_item_t()
-+ get_line_item = cfunc.get_line_item
-+ tag_remove = ida_lines.tag_remove
-+ ea_of_id = {}
-+ lines = []
-+ for sl in cfunc.get_pseudocode():
-+ line = sl.line
-+ line_ea = None
-+ if include_addresses and get_line_item(line, 0, False, None,
-+ item, None):
-+ it = item.it
-+ oid = it.obj_id if it is not None else None
-+ if oid is not None and oid in ea_of_id:
-+ line_ea = ea_of_id[oid]
-+ else:
-+ dstr = item.dstr()
-+ if dstr:
-+ ds = dstr.split(": ")
-+ if len(ds) == 2:
-+ try:
-+ line_ea = int(ds[0], 16)
-+ except ValueError:
-+ pass
-+ if oid is not None:
-+ ea_of_id[oid] = line_ea
-+ text = compact_whitespace(tag_remove(line))
-+ if line_ea is not None:
-+ lines.append(f"{text} /*{line_ea:#x}*/")
-+ else:
-+ lines.append(text)
-+ return "\\n".join(lines), None
-+ except IDAError as e:
-+ return None, str(e)
-+ except Exception as e:
-+ return None, f"Decompilation failed at {hex(ea)}: {e}"
-+
-+
- _idatui_strings_cache = {}
-
-
diff --git a/.auto/parked/fast_pc_nums.patch b/.auto/parked/fast_pc_nums.patch
deleted file mode 100644
index f06bfe1..0000000
--- a/.auto/parked/fast_pc_nums.patch
+++ /dev/null
@@ -1,52 +0,0 @@
-diff --git a/server/patch_server.py b/server/patch_server.py
-index 6667e12..5e20671 100644
---- a/server/patch_server.py
-+++ b/server/patch_server.py
-@@ -1900,7 +1900,7 @@ def _idatui_lit_extent(plain, x):
- return (lo, hi)
-
-
--def _idatui_pc_nums(cf, sl):
-+def _idatui_pc_nums(cf, sl, plain=None):
- """Every number literal on one pseudocode line, as
- [{x0, x1, ea, opnum, value, nbytes, fmt}].
-
-@@ -1912,16 +1912,26 @@ def _idatui_pc_nums(cf, sl):
- import ida_lines
- import idaapi
-
-- plain = ida_lines.tag_remove(sl.line)
-+ # ``plain`` is the untagged line; callers that already have it pass it in
-+ # rather than making tag_remove run twice over every line of the function.
-+ if plain is None:
-+ plain = ida_lines.tag_remove(sl.line)
- out = []
- x = 0
-+ # One ctree_item_t for the whole line, and no head/tail at all. They are
-+ # SWIG allocations in the innermost loop of a scan that probes every
-+ # literal-looking character -- and 'a' to 'f' are hex digits, so `a1`, `v6`
-+ # and `sub_1F4C0` all qualify and most columns of a line get probed. head
-+ # and tail were never read. (Same three costs as decomp_map's sweep.)
-+ item = ida_hexrays.ctree_item_t()
-+ line = sl.line
-+ get_line_item = cf.get_line_item
- while x < len(plain):
- ch = plain[x]
- if ch not in _IDATUI_LIT_CHARS and ch != "'":
- x += 1
- continue
-- head, item, tail = (ida_hexrays.ctree_item_t() for _ in range(3))
-- if not cf.get_line_item(sl.line, x, True, head, item, tail):
-+ if not get_line_item(line, x, True, None, item, None):
- x += 1
- continue
- if item.citype != ida_hexrays.VDI_EXPR:
-@@ -1997,7 +2007,7 @@ def pc_nums(
- for i in range(len(sv)):
- plain = ida_lines.tag_remove(sv[i].line)
- compact = _idatui_compact(plain)
-- for rec in _idatui_pc_nums(cf, sv[i]):
-+ for rec in _idatui_pc_nums(cf, sv[i], plain):
- out.append({
- "line": i,
- "x0": _idatui_compact_col(plain, compact, rec["x0"]),
diff --git a/.auto/prompt.md b/.auto/prompt.md
deleted file mode 100644
index 8437b38..0000000
--- a/.auto/prompt.md
+++ /dev/null
@@ -1,275 +0,0 @@
-# Autoresearch: make ida-tui faster, without losing anything
-
-## Objective
-
-Reduce the wall-clock latency of the operations an ida-tui user actually waits
-on, on both a small binary (`targets/echo`, 128 funcs) and a real-world one
-(`targets/bash`, 2099 funcs). No feature may be removed, no output may change,
-no test may break.
-
-The app is a Textual TUI over a private idalib worker process (unix socket,
-length-prefixed pickle). Three layers, kept separate:
-
-- `idatui/worker.py` + `idatui/worker_client.py` — backend; one call = one
- round trip to a process that owns the IDA database.
-- `idatui/domain.py` — paging/caching over the client (`FunctionIndex`,
- `ListingModel`, `DisasmModel`, `HexModel`, `decompile`, xrefs, resolve).
-- `idatui/app.py` — the Textual app; views are line-virtualized `ScrollView`s.
-- `idatui/graph.py` — pure-python Sugiyama layout for the CFG view.
-- `server/patch_server.py` — the extra `@tool`s the worker injects into
- ida-pro-mcp (`heads`, `read_raw`, `resolve_names`, …). This is where a new
- backend capability goes; it runs INSIDE the worker with full idalib access.
-
-## Metrics
-
-- **Primary**: `total_ms` (ms, lower is better) — the sum of every phase median.
- Re-baselined once (experiment v2 #1) when `phase_graph` was found to be timing
- a cache hit; the v1 history below is still the record of what was learned.
-- **Secondary** (all in ms, per target: `sm_` = echo, `lg_` = bash):
- - `nav_ms` — jump to a function's entry in the listing, cold. **The single
- biggest term today** (`lg_nav_ms` ≈ 28 s of a 46 s total, and
- `lg_nav_worst_ms` ≈ 28 s for ONE jump).
- - `search_ms` — incremental search over a whole segment.
- - `decomp_ms` — F5 → decompile → highlight → paint (nav excluded).
- - `graph_ms` — flowchart → layout → paint.
- - `boot_ms`, `listing_cold_ms`, `listing_warm_ms`, `render_ms`, `hex_ms`,
- `index_ms`, `palette_ms`, `pure_graph_ms`.
- - `fails` — **must stay 0.** A phase that silently stops doing its work would
- otherwise read as an enormous speedup.
-- `NOTES` on each run carries the work actually done (`decomp_ok`, `graph_ok`,
- `graph_blocks`, `search_hits`, `listing_rows`, `nav_rows`, `render_cells`, …).
- **If a metric drops and its NOTES counter drops with it, that is not a win.**
-
-## How to Run
-
-`./.auto/measure.sh` (~45 s). Prints `METRIC name=value` lines.
-`./.auto/measure.sh --only sm` benches just the small target while iterating.
-
-`./.auto/checks.sh` runs automatically after every passing benchmark (~160 s):
-
-1. `.auto/check_search.py` — the search fast paths against the plain per-line
- loop, for every typed prefix.
-2. `.auto/check_rename.py` — the listing after a rename, read narrow (painting)
- and wide (search body), and the whole model against a rebuild.
-3. `tests/run.py` — every suite, 830 checks.
-
-**The two `.auto/check_*.py` scripts exist because the things they guard fail
-silently.** A stale cache still returns *an* answer, and the benchmark rewards
-it for being fast. Twice this session the honest change measured worse than the
-broken one. If you optimise a cache, write the check that fails on the old code
-first.
-
-## Files in Scope
-
-Anything under `idatui/` and `server/patch_server.py`. In rough order of
-expected payoff:
-
-- `idatui/domain.py` — `ListingModel` is where nav time lives. It walks the
- segment forward in 500-head pages from `seg_start`, so `ensure_ea(ea)` is
- O(distance from the start of the segment): landing on a function near the end
- of bash costs ~440 sequential worker round trips. `DisasmModel`,
- `HexModel`, `FunctionIndex` are the other paging/caching classes.
-- `server/patch_server.py` — the `heads` tool the listing pages over. A better
- backend primitive (address-anchored start, bigger/denser pages, a count-only
- or index mode) is fair game and probably the real fix.
-- `idatui/app.py` — `render_line` of `ListingView`/`DecompView`/`HexView`/
- `GraphView`, the search mixin (`_compute_matches`, `_line_plain`), `_grow`,
- `_prime`.
-- `idatui/graph.py` — layout; already Fenwick-optimised once (see the idatui
- skill), so the easy win is gone.
-- `idatui/worker.py`, `idatui/worker_client.py` — transport (pickle framing,
- per-call overhead).
-- `idatui/highlight.py` — Pygments C lexing per decompilation.
-
-## Off Limits
-
-- `tests/**` — the correctness gate. Do not weaken, skip, shorten or "fix" a
- test to make a change pass. If a test fails, **assume the change is wrong**.
-
- Two narrow exceptions have been used, each with proof recorded in the log:
- the racy-setup one below, and adding `.auto/check_*.py` gates (which only ever
- *add* coverage).
-
- The racy-setup exception, and what it costs you: a scenario whose *setup* is
- racy, where the speedup merely decides which of two async loads lands first.
- Before touching it you must (a) bisect to show which change flips it, (b)
- reproduce the race outside the suite, showing the app reaching two different
- states from the same steps, and (c) show the repaired scenario passing on
- BOTH the fast and the slow code. Only the setup may change — every `c.check`
- stays exactly as it was — and the ASI must record all three proofs. Done once
- so far, for `graph_minimap` (experiment #6).
-- `.auto/bench.py` may only be changed to add *more* signal (extra metrics,
- extra NOTES). Never to do less work, shorten a sweep, drop a phase, loosen a
- wait, or pick easier functions. If you change what it measures, say so in the
- log and re-baseline with `init_experiment`.
-- `targets/**` binaries and their `.i64`/`.pristine.i64` databases.
-
-## Constraints
-
-- **No functionality may be lost.** Same rendering, same colours, same
- behaviour. `checks.sh` must pass.
-- **No new third-party dependencies.** `domain.py` and `worker_client.py` are
- deliberately stdlib-only (the TUI layer may use Textual/Rich/Pygments, which
- are already dependencies).
-- **No caching that can go stale silently.** Renames bump `Program._name_gen`
- and disasm caches are cleared for a reason; a new cache must have an
- invalidation story or it will show stale names after an edit.
-- The worker is single-threaded and main-thread-only for idalib. Parallelism
- has to come from batching calls, not from calling IDA concurrently.
-- Do not tune constants to the two benchmark binaries. A change must be a
- structural improvement that holds for a 10 MB firmware image too.
-
-## What's Been Tried
-
-v1 bench baseline `total_ms` ≈ 46 600 → 18 900 after thirteen experiments
-(−59% on v1, then a re-baseline at 27 913 and −32% on v2).
-
-**Wins, biggest first**
-
-1. *(v1 #3, −42%)* **ida-pro-mcp installs a `sys.setprofile` hook around every
- tool call.** Its deadline mechanism profiles every python call/return so a
- pure-python tool body can be interrupted — a 3.3× tax on a backend whose
- tools are call-heavy (`heads`: 92 → 28 µs/row without it). `worker.py` now
- sets `IDA_MCP_TOOL_TIMEOUT_SEC=0` and arms the deadline itself with one
- polling watchdog thread + `ida_kernwin.set_cancelled()` — the half that
- actually frees the IDA main thread.
-2. *(v6 #2, −41%)* **`decomp_map` swept every column three times over.** Three
- SWIG allocations per column, the TAGGED line length as the bound (124 columns
- for a 23-column line), and a `dstr()` format per column when consecutive
- columns are the same ctree item. bash's 25 largest: 67.4 s → 6.8 s. This is
- the split view's whole cost.
-3. *(v7 #2, −23%)* **A rename kept the listing's walk.** `bump_names` discarded
- the segment model, so the reload re-walked it to find a row the cursor was
- already on — 1.7 s per rename on bash. Now the walk stays and the text is
- re-rendered a block at a time: 10.3 ms.
-4. *(v2 #2, −32%)* **`Program.flowchart` fetched the convex hull of the basic
- blocks.** IDA function chunks live far from the entry, so a 1.4 KB function
- could span 680 KB: 128 000 rows fetched, 3 s to draw, and the far blocks
- came back *empty* because the pager's 64-page bound ran out first. Now it
- fetches the merged block intervals and assigns rows by bisect.
-5. *(v1 #6)* **`lru_cache` on the per-line render** (`_idatui_line_parts`):
- 196 k listing lines of bash are only 53 k distinct, 26.7 → 16.5 µs/row.
-6. *(v1 #7, −9%)* **Incremental search narrows instead of rescanning** — typing
- a character can only remove lines.
-7. *(v5 #6)* **Highlight ranges are computed per line on demand.** Searching
- one character over bash matches 177 k lines at 310 k places; all but the
- forty on screen were built and thrown away.
-8. *(v7 #10, −4%)* **`heads(digest=True)`** — ask whether a page still renders
- as you hold it, rather than fetching it to find out.
-9. *(v7 #12, −2.6%)* Three redundancies in the `heads` walk: item flags fetched
- three times per head, `get_func` per head where a head is nearly always in
- the same function as the one before it, and the page digest rebuilding a
- tuple-of-tuples per row where one spans list is shared by ~45% of them.
-10. *(v1 #9/#10/#13, v2 #3, v5 #3)* Constants: `bytes.hex(" ")` for the opcode
- column (12×), `bisect` imported at module scope, the deferred
- `refresh(layout=True)` only when a scroll actually clamped, a memoised
- pygments token→style lookup, `_CellRow` writing by slice, `HexView` emitting
- style runs instead of a Segment per byte, `Head` as a `NamedTuple`, and the
- graph's transposition counting keep and swap in one pass.
-
-**Dead ends / things not to re-try**
-
-- `re.finditer` per tag in the span walker is *slower* than a plain character
- loop (14.4 vs 13.2 µs/line): Match objects cost more than the ~54 trivial
- iterations they replace. One capturing `re.split` is what wins (10.2).
-- `ListingModel.PAGE` (500 / 1000 / 2000) makes no measurable difference —
- the cost is per row, not per round trip. Don't tune it.
-- Changing the `heads` wire shape (int `ea`, tuples instead of dicts) buys at
- most 0.3 µs/row. Measured; not worth a breaking change.
-- A page-scoped identity memo for the spans→tuple conversion never hits: the
- repeats are spread across the segment, not within a 500-row page. A
- model-scoped one is unsafe (`id()` is reused once the page dicts die).
-- Growing the line cache past 16 384 does nothing for a *cold* sweep (17.0
- µs/row at 16 k, 32 k, 64 k and 128 k alike). It only helps a *second* sweep —
- which was a dead end until the rename fix created one, and then it was worth
- 21%. **Re-read the dead ends after a structural change: this one stopped being
- one.** Sized at 65 536 now (bash's .text has 53 363 distinct lines; 32 768
- still thrashes). Costs +47 MB of worker RSS, and it is a bound rather than a
- proportion — a bigger binary fills it and stops.
-- Merging same-kind adjacent spans in `_idatui_spans`: only 2.5% fewer spans on
- 20 k real lines. Not worth a wire-format change.
-- Applying an already-decompiled function inline instead of via a `@work`
- thread: a Textual thread spawn plus its `call_from_thread` is worth well under
- 1% of an F5. Measured flat; reverted for complexity.
-- Deferring the "decompiling…" loading cover until ~120 ms (worth ~9 ms per F5)
- is blocked: a scenario asserts F5 raises it **synchronously**, guarding a real
- past regression. That is an assertion, not setup, so it stands.
-- Polling the worker socket faster than ~25 ms is actively harmful: the poll
- runs on a background thread and starves the UI thread through a cold
- auto-analysis (see v5 #4).
-
-**Where the time is now (28 842 ms on the v7 bench)**
-
-`lg_search` 7 123 · `lg_nav` 6 610 · decomp lg+sm 3 647 · split lg+sm 4 027 ·
-graph lg+sm 1 691 · boot lg+sm 1 151 · listing lg+sm 1 428 · rename lg+sm 1 119 ·
-hex lg+sm 895 · render 483 · pure_graph 212.
-
-`lg_search` is large *because of where it sits in the session*: the bench renames
-six functions and then searches the whole 228 k-row segment, so it pays to
-re-render everything the rename staled. Before the rename fix that same cost was
-paid up front, inside the renames (`lg_rename` was 10 055 ms). Re-rendering N
-heads costs what loading N heads costs; the win was in not doing it for rows
-nobody reads.
-
-**Five things are at a floor that is not ours to move:**
-
-- `nav` — `generate_disasm_line` is 5.9 µs of the ~16.5 µs/row the worker
- spends, and the walk is inherently linear.
-- `decomp` — the raw `decompile` tool is 1 711 ms cold for echo's twelve largest
- functions and 218 ms warm; pickling the result is 0.1 ms. It is Hex-Rays.
-- `search` — after a rename, what you read has to be re-rendered. The worker
- still has to *render* a page to know it is unchanged (`generate_disasm_line`
- is the floor), but since v7 #10 it no longer has to ship it: `heads(...,
- digest=True)` returns hash+count, and the client keeps the page it already
- has. That is ~40% of a page's cost, and after a rename nearly every page is
- unchanged.
-- `listing`/`hex`/`graph`/`render` — mostly Textual's own compositing, ~6 ms per
- full-screen frame. Our `render_line` is ~1.8 ms of a ~10 ms hex frame.
-- `boot` — ~150 ms of it is the worker importing `idapro`.
-
-**Benchmark history.** The bench was corrected five times. Three of those found
-a cost that was *entirely invisible*, and two of those three turned out to be
-among the largest wins of the whole session. Every re-baseline is in
-`.auto/log.jsonl`:
-
-| bench | baseline | best | what changed |
-|---|---|---|---|
-| v1 | 46 572 | 19 006 | — |
-| v2 | 27 913 | 17 501 | graph opens were timing a **cache hit** |
-| v4 | 18 498 | — | decomp/search/index reps were timing cache hits |
-| v5 | 18 516 | 17 465 | landing polls every 2 ms, not 10 |
-| v6 | 33 502 | 19 835 | **split view was not covered at all** (48% of a session) |
-| v7 | 33 243 | 28 651 | **rename was not covered at all** (1.7 s each on bash) |
-
-Headline user-facing numbers, measured directly rather than through the bench:
-a cold jump to a far address on bash 28.3 s → 6.4 s; drawing a chunked
-function's graph 3.0 s → 0.01 s (and its far blocks are no longer empty);
-opening the split view 850 ms → 110 ms; getting the listing back after a rename
-5.7 s → 10 ms.
-
-**The most productive thing in this session was asking what the bench does not
-measure.** Features still uncovered: xrefs (`x`), the strings browser (`"`), the
-struct editor, literal formats (`o`), make-code/data edits, history, execution
-traces, the RPC layer. Domain-level probes say xrefs/strings/structs/resolve are
-all fast (`/tmp/featprobe.py` pattern), but nothing has driven them end to end.
-
-**Measurement traps**
-
-- **Check `uptime` before believing a number.** This box started the session at
- load 0.6 and drifted to 1.6; `lg_nav` (224 000 sequential worker round trips)
- moved 6 556 → 7 058 with no code change, while the direct paging microbenchmark
- stayed at 24.4 µs/row. Totals are not comparable across a load change — rebase
- on a fresh same-session run, or verify in a microbenchmark:
- `/tmp/domain_break.py` (cold paging), `.auto/diff_spans.py` (span walker),
- `tests/test_graph.py <corpus>` (layout).
-
-- `cProfile` massively distorts this code (it is call-heavy): it reported
- `_idatui_spans` at 68% of the `heads` tool when the real share was ~10%.
- A/B with `time.perf_counter` in one process instead.
-- Anything that walks the listing twice in one process is measuring a warm
- `_idatui_line_parts` cache the second time. Run cold cases first, or in
- separate processes.
-- Anything that goes through `Program.flowchart`, `Program.decompile` or
- `Program.functions` twice is measuring a cache. Clear it or use the raw tool.
-- **The worker is a single serial process.** Work "moved to the background"
- does not overlap with anything; only doing less work helps.
diff --git a/.auto/wip-chunk.patch b/.auto/wip-chunk.patch
deleted file mode 100644
index 4e03aa7..0000000
--- a/.auto/wip-chunk.patch
+++ /dev/null
@@ -1,53 +0,0 @@
-diff --git a/idatui/domain.py b/idatui/domain.py
-index 386b3e8..1f8263d 100644
---- a/idatui/domain.py
-+++ b/idatui/domain.py
-@@ -919,24 +919,37 @@ class ListingModel:
- def _ensure_text(self, j0: int, j1: int) -> None:
- """Re-render physical heads [j0, j1) if a rename staled them.
-
-- The block is snapped out to whole ADDRESS groups. A function start emits
-- three banner rows and its code row at the same ea, and a labelled
-- instruction emits two -- so a block boundary that fell inside one of
-- those groups would refetch the whole group and never line up again.
-+ Done a block at a time. One call for the whole range would be simpler but
-+ the ``heads`` tool caps a response at 2000 rows, so a wide request (the
-+ search body asks for thousands at once) would come back short, fail the
-+ sequence check, and condemn the model to a rebuild it did not need.
-+ """
-+ blk = self.TEXT_BLOCK
-+ with self._lock:
-+ n = len(self._heads)
-+ start = (max(j0, 0) // blk) * blk
-+ while start < min(j1, n):
-+ self._ensure_text_block(start, min(start + blk, n))
-+ start += blk
-+
-+ def _ensure_text_block(self, j0: int, j1: int) -> None:
-+ """Re-render one block, snapped out to whole ADDRESS groups.
-+
-+ A function start emits three banner rows and its code row at the same ea,
-+ and a labelled instruction emits two -- so a boundary falling inside one
-+ of those groups would refetch the whole group, never line up, and leave
-+ the old names on screen for good.
- """
- with self._lock:
- gen = self._text_gen
- n = len(self._heads)
-- j0 = max(j0, 0)
-- j1 = min(j1, n)
-- if j1 <= j0:
-+ a = max(j0, 0)
-+ b = min(j1, n)
-+ if b <= a:
- return
- head_gen = self._head_gen
-- if all(head_gen[j] == gen for j in range(j0, j1)):
-+ if all(head_gen[j] == gen for j in range(a, b)):
- return
-- blk = self.TEXT_BLOCK
-- a = (j0 // blk) * blk
-- b = min(((j1 - 1) // blk + 1) * blk, n)
- eas = self._head_eas
- while a > 0 and eas[a - 1] == eas[a]:
- a -= 1
diff --git a/.auto/wip-decompmap.patch b/.auto/wip-decompmap.patch
deleted file mode 100644
index a11db98..0000000
--- a/.auto/wip-decompmap.patch
+++ /dev/null
@@ -1,49 +0,0 @@
-diff --git a/server/patch_server.py b/server/patch_server.py
-index fe16ede..b8f5453 100644
---- a/server/patch_server.py
-+++ b/server/patch_server.py
-@@ -884,16 +884,39 @@ def decomp_map(
- return {"error": f"decompile failed: {e}"}
- if cfunc is None:
- return {"error": "decompile failed"}
-+ import ida_lines
-+ # Three things this loop must not do, each measured on real functions (the 25
-+ # largest of bash went 68.3s -> 6.5s; echo's 60 largest 5.4s -> 0.6s, with
-+ # byte-identical output):
-+ #
-+ # * allocate ctree_item_t's per COLUMN. They are SWIG objects and this is
-+ # the innermost loop; one per call is enough, and head/tail are never
-+ # read, so don't ask for them at all.
-+ # * sweep the TAGGED length. ``x`` is a screen column but ``sl.line`` still
-+ # carries IDA's colour tags, so a 23-column line was swept 124 times.
-+ # * call dstr() per column. It formats a whole 'EA: description' string, and
-+ # consecutive columns are nearly always the same ctree item -- so ask the
-+ # item for its id first and only format when it changes. (The result is
-+ # deduped by ``seen`` anyway, so skipping a repeat cannot change it.)
-+ item = ida_hexrays.ctree_item_t()
-+ tag_remove = ida_lines.tag_remove
-+ get_line_item = cfunc.get_line_item
- lines = []
- for sl in cfunc.get_pseudocode():
- line = sl.line
- eas, seen = [], set()
-- for x in range(len(line) + 1):
-- head = ida_hexrays.ctree_item_t()
-- item = ida_hexrays.ctree_item_t()
-- tail = ida_hexrays.ctree_item_t()
-- if not cfunc.get_line_item(line, x, False, head, item, tail):
-+ prev_id = None
-+ for x in range(len(tag_remove(line)) + 1):
-+ if not get_line_item(line, x, False, None, item, None):
- continue
-+ it = item.it
-+ if it is not None:
-+ oid = it.obj_id
-+ if oid == prev_id:
-+ continue
-+ prev_id = oid
-+ else:
-+ prev_id = None
- # Match the /*ea*/ marker's source (decompile_function_safe): the
- # item's dstr() is 'EA: description'; get_ea() reports a different ea.
- dstr = item.dstr()
diff --git a/.auto/wip-digest.patch b/.auto/wip-digest.patch
deleted file mode 100644
index d4d23e8..0000000
--- a/.auto/wip-digest.patch
+++ /dev/null
@@ -1,162 +0,0 @@
-diff --git a/idatui/domain.py b/idatui/domain.py
-index 1f8263d..9cf539f 100644
---- a/idatui/domain.py
-+++ b/idatui/domain.py
-@@ -673,6 +673,14 @@ class ListingModel:
- #: Whether a rename has ever staled this model. Until one has, every
- #: read takes exactly the path it always did.
- self._renamed = False
-+ #: Where each loaded page starts, so a stale-text refresh can ask the
-+ #: worker "is this page still what I have?" over exactly the extent the
-+ #: worker itself produced. Parallel lists: first head index, the address
-+ #: it was fetched from, the digest it came back with, and its row count.
-+ self._page_head: list[int] = []
-+ self._page_addr: list[int] = []
-+ self._page_digest: list[object] = []
-+ self._page_rows: list[int] = []
- #: Set if a text refresh came back with a different head sequence, which
- #: means something DID move the walk. Program.listing() throws the model
- #: away when it sees this, so the next read rebuilds from scratch.
-@@ -758,6 +766,11 @@ class ListingModel:
- page = self._build_page(rows)
- with self._lock:
- gen = self._text_gen
-+ self._page_head.append(len(self._heads))
-+ self._page_addr.append(frm)
-+ self._page_digest.append(payload.get("digest")
-+ if isinstance(payload, dict) else None)
-+ self._page_rows.append(len(rows))
- for h in page:
- # Banner/label rows (function headers, separators, code labels)
- # are display-only; don't index them so navigation lands on the
-@@ -924,13 +937,65 @@ class ListingModel:
- search body asks for thousands at once) would come back short, fail the
- sequence check, and condemn the model to a rebuild it did not need.
- """
-- blk = self.TEXT_BLOCK
- with self._lock:
- n = len(self._heads)
-- start = (max(j0, 0) // blk) * blk
-- while start < min(j1, n):
-- self._ensure_text_block(start, min(start + blk, n))
-- start += blk
-+ j0 = max(j0, 0)
-+ j1 = min(j1, n)
-+ while j0 < j1:
-+ j0 = self._ensure_text_from(j0, j1, n)
-+
-+ def _ensure_text_from(self, j0: int, j1: int, n: int) -> int:
-+ """Freshen from head ``j0`` and return where to carry on.
-+
-+ Tries the page ``j0`` falls in first: the worker can say whether that
-+ page still renders exactly as it did, for the cost of the render alone
-+ -- no rows on the wire, none unpickled, no Heads rebuilt. After a rename
-+ nearly every page comes back identical, and that is 40% of what asking
-+ for it again would have cost. Falls back to the block refetch when the
-+ page has genuinely changed (or predates the digest).
-+ """
-+ with self._lock:
-+ p = bisect.bisect_right(self._page_head, j0) - 1
-+ usable = (0 <= p < len(self._page_head)
-+ and self._page_digest[p] is not None)
-+ if usable:
-+ p_lo = self._page_head[p]
-+ p_hi = (self._page_head[p + 1] if p + 1 < len(self._page_head)
-+ else len(self._heads))
-+ gen = self._text_gen
-+ fresh = all(self._head_gen[k] == gen for k in range(p_lo, p_hi))
-+ addr, want_dig = self._page_addr[p], self._page_digest[p]
-+ want_rows = self._page_rows[p]
-+ if usable and fresh:
-+ return p_hi
-+ if usable and self._verify_page(p, p_lo, p_hi, addr, want_dig,
-+ want_rows, gen):
-+ return p_hi
-+ blk = self.TEXT_BLOCK
-+ end = min(j0 + blk, j1 if usable else n)
-+ self._ensure_text_block(j0, max(end, j0 + 1))
-+ return max(end, j0 + 1)
-+
-+ def _verify_page(self, p: int, p_lo: int, p_hi: int, addr: int,
-+ want_dig: object, want_rows: int, gen: int) -> bool:
-+ """Ask whether page ``p`` still renders as it did; mark it fresh if so."""
-+ try:
-+ payload = self._prog.client.call(
-+ "heads", addr=hex(addr), count=self.PAGE, annotate=True,
-+ digest=True)
-+ except Exception: # noqa: BLE001 -- an older worker has no digest mode
-+ return False
-+ if not isinstance(payload, dict):
-+ return False
-+ got = payload.get("digest")
-+ if got is None or got != want_dig or payload.get("count") != want_rows:
-+ return False
-+ with self._lock:
-+ if self._text_gen != gen or len(self._heads) < p_hi:
-+ return False
-+ for k in range(p_lo, p_hi):
-+ self._head_gen[k] = gen
-+ return True
-
- def _ensure_text_block(self, j0: int, j1: int) -> None:
- """Re-render one block, snapped out to whole ADDRESS groups.
-diff --git a/server/patch_server.py b/server/patch_server.py
-index f3cbe35..a34a4a9 100644
---- a/server/patch_server.py
-+++ b/server/patch_server.py
-@@ -576,6 +576,28 @@ def _idatui_spans(line):
- return [[k, t] for k, t, _o in out], trimmed
-
-
-+def _idatui_rows_digest(rows):
-+ """A value that changes whenever any of ``rows`` would render differently.
-+
-+ Covers everything a client keeps off a row: address, kind, size, the plain
-+ text, the symbol name and the colour spans (which is what makes it exact
-+ rather than a heuristic -- two lines can collapse to the same text and still
-+ be coloured differently).
-+
-+ Uses the interpreter's own ``hash``, deliberately. It never has to mean
-+ anything outside this process: the client stores what a page hashed to when
-+ it loaded it and hands the same number back to ask whether the page still
-+ hashes to that. One worker, one process, one hash seed.
-+ """
-+ acc = 0
-+ for r in rows:
-+ sp = r.get("spans")
-+ acc = hash((acc, r.get("ea"), r.get("kind"), r.get("size"),
-+ r.get("text"), r.get("name"),
-+ tuple(map(tuple, sp)) if sp else None))
-+ return acc
-+
-+
- def _idatui_unknown_row(ea, size):
- """One collapsed row for a run of ``size`` undefined bytes starting at
- ``ea``. A single byte is rendered normally (shows its value); a longer run
-@@ -661,6 +683,7 @@ def heads(
- end: Annotated[str, "Optional exclusive end address; default = segment end"] = "",
- back: Annotated[bool, "Walk backwards: return the count heads ENDING just before addr, in forward order"] = False,
- annotate: Annotated[bool, "Emit IDA-style function boundary banner rows (kind sep/funchdr)"] = False,
-+ digest: Annotated[bool, "Return only a digest+count of the rows, not the rows themselves"] = False,
- ) -> dict:
- """Walk item heads from ``addr`` as a flat listing: every head is rendered
- (code OR data OR undefined) via generate_disasm_line and stepped with
-@@ -767,7 +790,17 @@ def heads(
- rows.extend(_rows_for(ea)) # a struct head expands into member rows
- ea = _advance(ea)
- cursor = {"next": hex(ea)} if more else {"done": True}
-- return {"addr": str(addr), "heads": rows, "cursor": cursor}
-+ out = {"addr": str(addr), "cursor": cursor,
-+ "digest": _idatui_rows_digest(rows), "count": len(rows)}
-+ # ``digest`` mode answers "is this page still exactly what you have?" without
-+ # shipping it. The rows are built either way -- generate_disasm_line is the
-+ # floor and there is no way to know a line is unchanged without rendering it
-+ # -- but pickling several hundred rows with their colour spans, unpickling
-+ # them and rebuilding Heads is about 40% of what a page costs, and after a
-+ # rename almost every page comes back identical.
-+ if not digest:
-+ out["heads"] = rows
-+ return out
-
-
- @tool
diff --git a/.auto/wip-renamekeep.patch b/.auto/wip-renamekeep.patch
deleted file mode 100644
index 2c3344c..0000000
--- a/.auto/wip-renamekeep.patch
+++ /dev/null
@@ -1,211 +0,0 @@
-diff --git a/idatui/domain.py b/idatui/domain.py
-index b5081cf..386b3e8 100644
---- a/idatui/domain.py
-+++ b/idatui/domain.py
-@@ -642,6 +642,11 @@ class ListingModel:
- """
-
- PAGE = 500 # heads per server call (well under the tool's 2000 cap)
-+ #: Heads refreshed together when a rename makes their text stale. One server
-+ #: call per block, so a viewport costs one round trip rather than forty --
-+ #: and the same size as a load page, so refreshing everything costs about
-+ #: what rebuilding everything would have.
-+ TEXT_BLOCK = 500
-
- def __init__(self, program: "Program", seg_start: int, seg_end: int,
- name: str | None = None):
-@@ -659,6 +664,19 @@ class ListingModel:
- # demand. _row_at[i] is the logical row where physical head i starts.
- self._row_at: list[int] = []
- self._head_eas: list[int] = [] # parallel to _heads, for bisect
-+ #: Which name generation each head's TEXT was rendered at, parallel to
-+ #: _heads. A rename bumps :attr:`_text_gen`; the rows themselves stay
-+ #: (their addresses and row numbers are unchanged) and are re-rendered a
-+ #: block at a time when something asks for them. See invalidate_text.
-+ self._head_gen: list[int] = []
-+ self._text_gen = 0
-+ #: Whether a rename has ever staled this model. Until one has, every
-+ #: read takes exactly the path it always did.
-+ self._renamed = False
-+ #: Set if a text refresh came back with a different head sequence, which
-+ #: means something DID move the walk. Program.listing() throws the model
-+ #: away when it sees this, so the next read rebuilds from scratch.
-+ self.stale_structure = False
- self._rows = 0 # total logical rows loaded
- self._ubytes: dict[int, bytes] = {} # lazily-read bytes for those rows
- self._next: int | None = seg_start # next address to fetch from
-@@ -739,6 +757,7 @@ class ListingModel:
- cur = payload.get("cursor", {}) if isinstance(payload, dict) else {}
- page = self._build_page(rows)
- with self._lock:
-+ gen = self._text_gen
- for h in page:
- # Banner/label rows (function headers, separators, code labels)
- # are display-only; don't index them so navigation lands on the
-@@ -747,6 +766,7 @@ class ListingModel:
- self._by_ea.setdefault(h.ea, self._rows)
- self._row_at.append(self._rows)
- self._head_eas.append(h.ea)
-+ self._head_gen.append(gen)
- self._heads.append(h)
- self._rows += self._span(h)
- nxt = cur.get("next")
-@@ -878,6 +898,78 @@ class ListingModel:
- def __len__(self) -> int:
- return self.loaded()
-
-+ def invalidate_text(self) -> None:
-+ """A rename changed how rows READ, not which rows exist.
-+
-+ Item boundaries are untouched by a rename, so every row keeps its
-+ address and its row number — which the edit path already relies on, since
-+ it restores the cursor by INDEX afterwards. Dropping the whole model
-+ instead means the next jump re-walks the segment from its start: 6.4
-+ seconds on bash's .text, after every single rename.
-+
-+ So keep the walk and mark the rendered text stale; :meth:`_ensure_text`
-+ re-renders a block at a time, and refuses to splice anything back if the
-+ head sequence has moved under it (which a rename cannot do, but a
-+ mis-routed structural edit could).
-+ """
-+ with self._lock:
-+ self._text_gen += 1
-+ self._renamed = True
-+
-+ def _ensure_text(self, j0: int, j1: int) -> None:
-+ """Re-render physical heads [j0, j1) if a rename staled them.
-+
-+ The block is snapped out to whole ADDRESS groups. A function start emits
-+ three banner rows and its code row at the same ea, and a labelled
-+ instruction emits two -- so a block boundary that fell inside one of
-+ those groups would refetch the whole group and never line up again.
-+ """
-+ with self._lock:
-+ gen = self._text_gen
-+ n = len(self._heads)
-+ j0 = max(j0, 0)
-+ j1 = min(j1, n)
-+ if j1 <= j0:
-+ return
-+ head_gen = self._head_gen
-+ if all(head_gen[j] == gen for j in range(j0, j1)):
-+ return
-+ blk = self.TEXT_BLOCK
-+ a = (j0 // blk) * blk
-+ b = min(((j1 - 1) // blk + 1) * blk, n)
-+ eas = self._head_eas
-+ while a > 0 and eas[a - 1] == eas[a]:
-+ a -= 1
-+ while b < n and eas[b - 1] == eas[b]:
-+ b += 1
-+ last = self._heads[b - 1]
-+ lo = eas[a]
-+ hi = last.ea + max(last.size, 1)
-+ want = [(h.ea, h.kind) for h in self._heads[a:b]]
-+ try:
-+ payload = self._prog.client.call(
-+ "heads", addr=hex(lo), end=hex(hi),
-+ count=min(len(want) + 64, 2000), annotate=True)
-+ except Exception: # noqa: BLE001 -- keep the old text rather than blank
-+ return
-+ rows = payload.get("heads", []) if isinstance(payload, dict) else []
-+ page = self._build_page(rows)[:len(want)]
-+ with self._lock:
-+ if self._text_gen != gen or len(self._heads) < b:
-+ return
-+ if [(h.ea, h.kind) for h in page] != want:
-+ # Something moved the walk, which a rename cannot do -- so this
-+ # was not one. Say so and let Program.listing() rebuild, rather
-+ # than sit here re-fetching a block that will never line up (and
-+ # showing the old names while doing it).
-+ self.stale_structure = True
-+ for j in range(a, b):
-+ self._head_gen[j] = gen
-+ return
-+ self._heads[a:b] = page
-+ for j in range(a, b):
-+ self._head_gen[j] = gen
-+
- def get(self, i: int) -> Head | None:
- with self._lock:
- if not (0 <= i < self._rows):
-@@ -885,8 +977,23 @@ class ListingModel:
- j, off = self._phys(i)
- if j < 0:
- return None
-- span = self._span(self._heads[j])
-- h = self._heads[j]
-+ stale = self._renamed and self._head_gen[j] != self._text_gen
-+ if not stale:
-+ span = self._span(self._heads[j])
-+ h = self._heads[j]
-+ if stale:
-+ # A rename staled this row's text; re-render its block (one call for
-+ # the block around it, so a viewport costs one round trip). Only
-+ # this path re-takes the lock -- the ordinary read stays atomic.
-+ self._ensure_text(j, j + 1)
-+ with self._lock:
-+ if not (0 <= i < self._rows):
-+ return None
-+ j, off = self._phys(i)
-+ if j < 0:
-+ return None
-+ span = self._span(self._heads[j])
-+ h = self._heads[j]
- # Synthesis reads bytes, so do it OUTSIDE the lock: an RPC under the
- # model lock deadlocks the page loader that is filling it.
- return self._row_head(j, off) if span > 1 else h
-@@ -894,6 +1001,16 @@ class ListingModel:
- def window(self, start: int, count: int) -> list[Head]:
- """``count`` logical rows from ``start`` (synthesising undefined ones)."""
- self.ensure(start + count)
-+ with self._lock:
-+ # _renamed stays set once a rename has happened; _ensure_text then
-+ # does the precise, range-limited staleness check. Before the first
-+ # rename this is one boolean and the read is exactly as it was.
-+ dirty = self._renamed
-+ if dirty:
-+ j0 = max(self._phys(max(start, 0))[0], 0)
-+ j1 = self._phys(max(min(self._rows, start + count) - 1, 0))[0] + 1
-+ if dirty:
-+ self._ensure_text(j0, j1)
- with self._lock:
- rows = min(self._rows, start + count)
- spans = [self._phys(i) for i in range(max(start, 0), max(rows, 0))]
-@@ -1223,6 +1340,8 @@ class Program:
- start, end, name = seg
- with self._lock:
- m = self._listings.get(start)
-+ if m is not None and m.stale_structure:
-+ m = None # a refresh found the walk had moved; start over
- if m is None:
- m = ListingModel(self, start, end, name)
- self._listings[start] = m
-@@ -1406,15 +1525,24 @@ class Program:
-
- def bump_names(self) -> None:
- """Signal that symbol names changed (a rename). Disasm/listing names are
-- live in the IDB, so clearing the cached rows is enough for those;
-- decompilation is generation-checked and force-recompiled lazily."""
-+ live in the IDB, so the cached rows have to be re-rendered; decompilation
-+ is generation-checked and force-recompiled lazily.
-+
-+ The listing keeps its WALK. A rename cannot move an item boundary, so
-+ every row keeps its address and its row number -- the edit path already
-+ assumes exactly that, since it restores the cursor by index afterwards.
-+ Dropping the segment model instead made the reload re-walk it from the
-+ start, which is 6.4 seconds on bash after every rename.
-+ """
- with self._lock:
- self._name_gen += 1
- models = list(self._disasm.values())
-- self._listings.clear() # listing head rows cache names -> refetch
-+ listings = list(self._listings.values())
- self._pc_nums.clear() # a reformat moves every literal on its line
- for m in models:
- m.invalidate()
-+ for lm in listings:
-+ lm.invalidate_text()
-
- def bump_items(self) -> None:
- """Signal that item/function STRUCTURE changed (define code/data/func,