Open through the r2 command so what it opens is mapped: the
disassembler skips whatever falls outside a map, and reading is not
enough. Drop the bin load, which only sniffed formats at address zero.
Let a failed read say so instead of returning null, remember the pages
that have none, and resolve an anchor live or not at all -- a stale
address lands on whatever occupies it now.
Analysis pulled every export, import and symbol across the wire
and then kept the handful that name code -- 87k rows for a
kernel, each carrying a type and an address as text.
Ask for what it uses instead: the offset, name and source of each
function the module knows about, chosen where the rows already
are.
Analyze the function on demand when r2 has no boundary at the
address yet, so the first view stops at the function end instead
of overshooting. Source jump and call targets from pdj/pDj, which
still carry them, since pdJ no longer emits the arrow field.
Drop the modules/threads tabs; show both as collapsible sidebar
groups mirroring the hooks UX: a handful of top picks plus a
browse popover for the rest. Default threads to collapsed.
Revert the eager re-analysis of persisted modules on attach; keep
disassembly analysis lazy and add a per-module Analyze action and
status indicator instead.
Share one sidebar-row and one browser-popover component across
hooks, modules, and threads in both the SwiftUI and GTK frontends.
Snapshot the r2 seek after each r2 eval, storing it on
SessionUIState as an AddressAnchor so it re-bases across runs.
Resolve and restore it during the session warm-up, which now
also runs when only a seek (no analysis) was saved.
Move to the single cmd that returns an R2CommandResult with an
optional output, and handle the nil that r2 yields for commands
with no string result (e.g. an invalid command from completion).
Bump the SwiftyR2 pin to the revision with that fix.
Replay every module that has a persisted analysis row into the
fresh r2 core once modules are enumerated, restoring last
session's warm state without blocking attach. Replays are cheap
re-basing; modules lacking persisted analysis stay lazy.
cmdWithLogs adds a collector callback for hasErrors but never
muted radare2's own stderr printing, so read-only function
probes flooded the console. Set log.quiet on both disassembler
cores; the collector still fires, so error detection is
unchanged.
New LumaCore.Appearance (light, dark). DisassemblyRequest,
Disassembler and TraceDisassembler key on it directly; the
r2 theme mapping lives in one place. ThemeWatcher returns
Appearance; remaining C-interop callers compare to .dark at
the boundary. CFG views use themeAppearance to avoid clashing
with NSView's inherited appearance property.
Split ModuleAnalysis persistence into module_analysis,
module_analysis_function and module_analysis_block tables.
Harvest blocks via afbj and use them to answer enclosingFunction
offline. R2Hex accepts both 0x-prefixed strings and raw integers,
since cfg.json.num=hex doesn't cover afbj. When the cache is
present but blocks are missing, replay anyway unless we're online
and can re-analyze.
Replace the auto-derived title with userTitle (nil = auto)
and a parentInsightID link. Display name is userTitle, else
<parent.title>+0x<delta>, else sub_<offset> if at a function
start, else module+offset. Sidebar context menu Rename… and
an LLM rename_insight tool both go through Engine.renameInsight,
which broadcasts via the existing updateInsight op and pushes
the new name into r2 as a flag (plus afn at function starts).
getOrCreateInsight kicks off a background pass that links a
new insight to its enclosing function-start insight, and
adopts siblings inside its region when the new insight is
itself the function start. The same adoption runs when a
fresh ModuleAnalysis newly reveals a function start under an
existing insight.
Identify a process instance by a deterministic identity returned
from the agent (per-OS, with a Process.mainModule + pid fallback)
and stash it on ProcessSession.processInfo. Rekey memory_page
into memory_page_module (module UUID + offset) so cached bytes
survive ASLR, and memory_page_anon (process identity + address)
for JIT / heap regions that only survive within one process
instance. Purge stale anon pages on every fresh attach.
Add updateProcessInfo, updateModuleAnalysis and updateInsight
SessionOps plus a side-channel publish-memory-page op and a
+fetch-memory-page request. The driver write-throughs every
page it fetches; non-drivers pull on miss and persist locally.
Module-analysis rows flow the same way so non-drivers can key
their cache by module UUID. The lab-join snapshot now carries
processInfo and module_analyses too.
Refresh actions on the insight detail view: Reread Bytes drops
the pages overlapping the visible range; Reanalyze Module drops
the module's analysis and pages, re-running r2 on next access.
The macOS detail view now reads the insight live from
insightsBySession via observation; GTK forwards .insightUpdated
into the detail and reschedules a refresh when the resolved
address changes.
Decouple Disassembler from ProcessNode via MemoryReader and
ModuleIntrospector protocols, and add a per-session page store
that r2 reads flow through. The disassembler now survives
detach; insights render from cache, with live reads
re-populating it on reattach.
Persist per-session module analysis (executable ranges + known
functions, keyed by module name + LC_UUID) so on-demand
analysis runs once per binary and rebases on reattach. The
agent gains enumerateModuleRanges and getModuleIdentity; the
store gains a module_analysis table.
Disassembler queries $FB/$FE for the request address; when it
hits a function start, fetch via pDJ bounded to the function's
byte range. Otherwise fall back to pdJ N. Filter pdJ
decoration entries (xrefs, fcn headers, var summaries) that
share the addr of an instruction.
DisassemblyPage carries a scope (.function/.span) so the
SwiftUI and GTK insight views can disable loadMore when the
page is function-bounded. The disassemble AI tool surfaces the
scope too, so the model knows whether it has the whole
function or just an N-instruction span.
Go-to-Function-Start now opens (or selects) the insight rooted
at the function start, which then renders via the bounded
path.
runCommand and decompile now return R2CommandResult so callers
can see r2's log entries. r2_cmd surfaces them to the agent;
findFunctionStart treats any error log as no result.
?v $FB needs an analyzed function; if none, run aap once per
session and retry. Surface a user-facing error if even that
finds nothing instead of failing silently.
LLM agent for user-stated RE goals: propose-and-approve over the
existing RPC, evidence-grounded findings, collab via the op-outbox
pattern. Anthropic / OpenAI / local-OAI providers. r2ai used for
explain_function when the plugin is loaded; falls back to the
mission's own provider otherwise.
Workspace is now a thin host adapter. Engine owns sessions and
notebook entries (via store observation), GitHub auth + OAuth
device flow, hook-pack discovery, address annotations and address
actions, the disassembler cache, and all instrument lifecycle.
EventLog is @Observable so SwiftUI tracks it directly; the host
no longer mirrors event arrays or version counters.
R2 (live disassembly + trace inspection) lives entirely in
LumaCore as concrete Disassembler / TraceDisassembler classes
returning portable StyledText spans. ProcessNodeViewModel and
InstrumentRuntime are gone; views read engine.processNodes /
node.instruments / engine.descriptor(for:) directly.
Reorganize Luma/Models/ — instrument UI dispatch lives under
Luma/Instruments/, Monaco editor glue under Luma/Editor/, and the
remaining UI types sit at the top of Luma/.