explorer

package
v0.3.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 19, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package explorer contains format-neutral navigation and service logic shared by the TUI.

Index

Constants

View Source
const CacheCap = 100

CacheCap bounds how many hits one query's cache retains.

Variables

This section is empty.

Functions

func DefaultExecAddr

func DefaultExecAddr(f *binfile.File, strategy string) uint64

DefaultExecAddr resolves a guaranteed-executable address for the disasm view to land on, honouring the requested strategy and falling back down a sensible chain when the choice can't be resolved. Returns 0 only when the binary has no executable code at all.

Strategies: "entry" (the entry point), "main"/"start" (those symbols), "text" (the .text/__text section), "lowest" (lowest executable address).

func IsDyldSharedCacheLib

func IsDyldSharedCacheLib(lib string) bool

IsDyldSharedCacheLib reports whether lib is normally served from Apple's dyld shared cache instead of as a standalone user-openable file.

func ResolveLibPath

func ResolveLibPath(lib, binaryPath string, info *binfile.Info, exists FileExists) (string, bool)

ResolveLibPath resolves a dynamic-library reference to an on-disk path. Mach-O @loader_path, @executable_path, and @rpath tokens are expanded using the binary path and loader-provided runpath/rpath entries.

Types

type CursorMode

type CursorMode uint8

CursorMode says where the search cursor sits relative to the hits, which decides what "the next one" means when the cursor has run off an end.

const (
	CursorAtMatch     CursorMode = iota // on a hit (or anywhere in the image)
	CursorAfterEnd                      // ran off the end going forward
	CursorBeforeStart                   // ran off the start going backward
)

type DisasmService

type DisasmService struct {
	// contains filtered or unexported fields
}

DisasmService owns bounded disassembly window decoding and its cache. It is independent of the TUI event loop; callers decide how decoded windows affect UI state.

func NewDisasmService

func NewDisasmService(file *binfile.File, dis disasm.Disassembler, maxBytes, searchWorkers int) *DisasmService

NewDisasmService creates a bounded disassembly decoder/cache.

func (*DisasmService) CanDecode

func (s *DisasmService) CanDecode() bool

CanDecode reports whether a disassembler exists for this file's architecture. Every decode method is a safe no-op without one; callers that would otherwise mutate state on an empty decode check this first.

func (*DisasmService) DecodeAt

func (s *DisasmService) DecodeAt(addr uint64, before int) (binfile.Window, []disasm.Inst)

DecodeAt returns a window containing addr and the instructions overlapping it.

func (*DisasmService) DecodeRange

func (s *DisasmService) DecodeRange(start, size, lead int) []disasm.Inst

DecodeRange decodes the instructions in [start, start+size) of the executable image, using only `lead` bytes of context before start to re-synchronise the decoder (vs DecodeWindow's large interactive overlap). It is uncached, so a contiguous full-image scan neither re-decodes each chunk's predecessor (~2× less work) nor evicts the interactive decode cache. `lead` should keep the architecture's instruction alignment (a multiple of 4 covers arm64/riscv).

func (*DisasmService) DecodeRangeFunc

func (s *DisasmService) DecodeRangeFunc(start, size, lead int, fn func(disasm.Inst) bool)

DecodeRangeFunc is DecodeRange streamed: it calls fn for each instruction in the range instead of returning a slice, so a scan that keeps only matching instructions never allocates the full decoded slice. fn returns false to stop.

func (*DisasmService) DecodeSpanAt

func (s *DisasmService) DecodeSpanAt(addr uint64, before int) Span

DecodeSpanAt decodes a bounded window around addr, using `before` bytes of lead-in context, and returns it as a Span.

func (*DisasmService) DecodeSpanWindow

func (s *DisasmService) DecodeSpanWindow(win binfile.Window) Span

DecodeSpanWindow decodes an explicit image window as a Span.

func (*DisasmService) DecodeWindow

func (s *DisasmService) DecodeWindow(win binfile.Window) []disasm.Inst

DecodeWindow decodes instructions overlapping win, using cache and overlap.

func (*DisasmService) LeadBytes

func (s *DisasmService) LeadBytes() int

LeadBytes returns the preferred bytes of context before a target address.

func (*DisasmService) MaxBytes

func (s *DisasmService) MaxBytes() int

MaxBytes returns the current decode budget per window.

func (*DisasmService) OverlapBytes

func (s *DisasmService) OverlapBytes() int

OverlapBytes returns the context decoded before each visible window.

func (*DisasmService) PrefetchAround

func (s *DisasmService) PrefetchAround(addr uint64)

PrefetchAround warms the decode cache around addr for smoother navigation.

func (*DisasmService) ScanMatching

func (s *DisasmService) ScanMatching(match func(text string) bool, limit int, done <-chan struct{}) []Match

ScanMatching decodes the executable image in parallel chunks and returns the `limit` lowest-addressed instructions whose text satisfies match. It stops early when done is closed.

Retained memory is bounded by limit, not by the number of matches. Each worker used to accumulate *every* match in its chunk, and the cap was applied only after all of them had been joined and sorted: searching "mov" over exex's own 15 MB binary matches ~98,000 instructions, so ~10 MB was allocated (and sorted) to show 500 rows — and it grew linearly with the target's size.

Bounding it also fixes what the cap discarded. The old merge stopped after the first chunks filled the quota, so matches in later chunks were dropped before the sort even looked at them; a low-addressed hit late in the image lost to a high-addressed one early in it. The shared heap below compares every match, so the result is exactly the lowest `limit` by address.

func (*DisasmService) SearchBatchChunks

func (s *DisasmService) SearchBatchChunks() int

SearchBatchChunks returns how many chunks should be queued per search batch.

func (*DisasmService) SearchChunkBytes

func (s *DisasmService) SearchChunkBytes() int

SearchChunkBytes returns the background-search chunk size.

func (*DisasmService) SearchWorkersFor

func (s *DisasmService) SearchWorkersFor(chunks int) int

SearchWorkersFor returns the worker count capped by available chunks.

func (*DisasmService) SetOptions

func (s *DisasmService) SetOptions(maxBytes, searchWorkers int)

SetOptions updates the decode budget and search worker preference.

func (*DisasmService) SpanFor

func (s *DisasmService) SpanFor(win binfile.Window, insts []disasm.Inst) Span

SpanFor pairs an already-decoded window with its bounds, for callers that decoded it themselves (the instruction-text search decodes in parallel chunks).

PosLo is the first instruction's position, not the window's start. The two differ when DecodeAt began at a symbol (a section or function jump): the window reserves lead bytes that hold no decoded instructions, so anchoring "scroll up" on win.Start would jump far before the actual preceding code.

type FileExists

type FileExists func(string) bool

FileExists abstracts filesystem probes for tests and alternate resolvers.

type Match

type Match struct {
	Addr uint64 // address of the matching instruction
	Text string // its (trimmed) assembly text
	Sym  string // display name of the symbol it lives in, or ""
}

Match is one instruction a whole-image scan kept.

type SearchCache

type SearchCache struct {
	// contains filtered or unexported fields
}

SearchCache is the per-query cache. The zero value is an empty cache for the empty query; call Reset before use.

func (*SearchCache) Add

func (c *SearchCache) Add(hits []SearchHit, forward bool)

Add merges freshly found hits, keeping the slice sorted and deduplicated by address. Past CacheCap it trims to the window nearest the direction of travel: forward keeps the highest addresses, backward the lowest.

Add reorders hits in place.

func (*SearchCache) Boundary

func (c *SearchCache) Boundary(forward bool) (uint64, bool)

Boundary returns the outermost cached hit in a direction: the last one going forward, the first going backward. It is where an incremental scan resumes.

func (*SearchCache) Complete

func (c *SearchCache) Complete(imgLen int) bool

Complete reports whether the cache holds every hit in an image of imgLen bytes: the whole image was scanned and nothing was dropped.

func (*SearchCache) Coverage

func (c *SearchCache) Coverage() (lo, hi int)

Coverage returns the scanned image range; lo is -1 when nothing was scanned.

func (*SearchCache) EnsureQuery

func (c *SearchCache) EnsureQuery(query string) bool

EnsureQuery resets the cache when the query has changed, and reports whether it did.

func (*SearchCache) Exhausted

func (c *SearchCache) Exhausted(forward bool) bool

Exhausted reports whether scanning in this direction has reached the image's end without finding anything more.

func (*SearchCache) Hits

func (c *SearchCache) Hits() []SearchHit

Hits returns the cached hits, in ascending address order.

func (*SearchCache) Next

func (c *SearchCache) Next(cur uint64, mode CursorMode, forward, inclusive bool) (SearchHit, bool)

Next returns the cached hit the search would move to from cur, or ok=false when the cache cannot answer and the image must be scanned.

inclusive keeps a hit exactly at cur (the initial Enter); n / N pass false to step past it. When the cursor has run off an end, the search wraps to the hit at the opposite end.

func (*SearchCache) NoteCoverage

func (c *SearchCache) NoteCoverage(lo, hi int)

NoteCoverage widens the scanned image range.

func (*SearchCache) Overflow

func (c *SearchCache) Overflow() bool

Overflow reports whether more hits were found than the cache can hold, so it no longer holds all of them.

func (*SearchCache) Query

func (c *SearchCache) Query() string

Query returns the query this cache holds hits for.

func (*SearchCache) Reset

func (c *SearchCache) Reset(query string)

Reset empties the cache for a new query.

func (*SearchCache) SetExhausted

func (c *SearchCache) SetExhausted(forward bool)

SetExhausted records that a direction has been scanned to its end.

type SearchHit

type SearchHit struct {
	Addr uint64
	Text string
}

SearchHit is a cached match: only the address and the instruction text.

Deliberately *not* the decoded window that found it. Retaining those would pin a decoded chunk per hit; a repeat jump re-decodes around the address instead, which the service's window cache usually satisfies anyway.

type Span

type Span struct {
	Insts []disasm.Inst
	// PosLo is the image position of the *first decoded instruction*, which is
	// what bounds "is there code above / where does it start".
	PosLo int
	// PosHi is the image position just past the decoded window.
	PosHi int
}

Span is a decoded, bounded slice of the executable image: the instructions themselves plus the image offsets that bound them.

func (Span) Contains

func (s Span) Contains(pos int) bool

Contains reports whether an image position falls inside the decoded span.

func (Span) Empty

func (s Span) Empty() bool

Empty reports whether the span decoded to nothing.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL