Delve — Interfaces#
Interface catalog#
Process#
- Package:
pkg/proc - File:
pkg/proc/interface.go:26 - Methods:
BinInfo() *BinaryInfo EntryPoint() (uint64, error) FindThread(threadID int) (Thread, bool) ThreadList() []Thread Breakpoints() *BreakpointMap Memory() MemoryReadWriter - Purpose: Read-only public contract for a debugged process. Exposes inspection operations only — no state mutation. This is what upper layers (debugger, rpc server) hold references to.
- Implementations: All four backends implement this indirectly via
ProcessInternal—pkg/proc/native,pkg/proc/gdbserial,pkg/proc/core,pkg/proc/internal/ebpf. TheTargetstruct wraps anyProcessInternaland satisfiesProcess. - Design quality: Minimal and well-segregated. Six methods covering exactly what a caller needs to inspect a process without mutating it. Follows ISP — consumers that only inspect the target depend on this, not the full
ProcessInternal.
ProcessInternal#
- Package:
pkg/proc - File:
pkg/proc/interface.go:43 - Methods:
Process // embedded Valid() (bool, error) RequestManualStop(cctx *ContinueOnceContext) error WriteBreakpoint(*Breakpoint) error EraseBreakpoint(*Breakpoint) error SupportsBPF() bool SetUProbe(string, int64, []ebpf.UProbeArgMap) error GetBufferedTracepoints() []ebpf.RawUProbeParams DumpProcessNotes(notes []elfwriter.Note, threadDone func()) (bool, []elfwriter.Note, error) MemoryMap() ([]MemoryMapEntry, error) StartCallInjection() (func(), error) FollowExec(bool) error - Purpose: Full backend implementation contract. Extends
Processwith all state-mutating operations (breakpoint write/erase, eBPF uprobe management, core dump, call injection, exec follow). Only used insidepkg/proc; upper layers never hold aProcessInternaldirectly. - Implementations:
native.nativeProcess(ptrace/Mach/Windows),gdbserial.Process(GDB remote/LLDB/rr),core.Process(ELF/Mach-O dumps),ebpfpartial (uprobe subset only). - Design quality: Excellent two-level split. Separating
Process(public) fromProcessInternal(backend-only) is the central design decision of the entire architecture. It prevents external callers from accidentally accessing backend internals while keeping all implementations cohesive withinpkg/proc.
ProcessGroup#
- Package:
pkg/proc - File:
pkg/proc/interface.go:12 - Methods:
ContinueOnce(*ContinueOnceContext) (Thread, StopReason, error) StepInstruction(int) error Detach(int, bool) error Close() error - Purpose: Abstracts coordinated resumption of a group of processes (used for
--follow-execmode, where child processes are also debugged). TheTargetGroupstruct implements this. - Implementations:
proc.TargetGroup - Design quality: Clean 4-method interface for group lifecycle. The
ContinueOnceContextparameter carries a channel to communicate halt signals asynchronously, which is the only concurrency-safe method per the documented contract.
RecordingManipulation#
- Package:
pkg/proc - File:
pkg/proc/interface.go:74 - Methods:
Recorded() (recorded bool, tracedir string) ChangeDirection(Direction) error GetDirection() Direction When() (string, error) Checkpoint(where string) (id int, err error) Checkpoints() ([]Checkpoint, error) ClearCheckpoint(id int) error - Purpose: Optional interface for record-and-replay backends (
rrvia gdbserial). Exposes reverse execution controls and checkpoint management toservice/debugger. Checked at runtime via type assertion. - Implementations:
gdbserial.Processwhen operating inrrmode. - Design quality: Good optional-capability pattern. Non-recording backends simply don’t implement it; the debugger uses a type assertion to detect capability. The companion
RecordingManipulationInternaladdsRestart()for backend use only, mirroring theProcess/ProcessInternalsplit.
RecordingManipulationInternal#
- Package:
pkg/proc - File:
pkg/proc/interface.go:94 - Methods:
RecordingManipulation // embedded Restart(cctx *ContinueOnceContext, pos string) (Thread, error) - Purpose: Backend-facing extension of
RecordingManipulationwithRestart, which is internal (restarts from a position or checkpoint). The split mirrorsProcess/ProcessInternal. - Implementations:
gdbserial.Processinrrmode. - Design quality: Consistent with the two-level split pattern.
Restartis excluded from the publicRecordingManipulationbecause callers should use the higher-leveldebugger.Restart()which handles thread re-selection.
Server#
- Package:
service - File:
service/server.go:5 - Methods:
Run() error Stop() error - Purpose: Minimal lifecycle interface for protocol servers. Both JSON-RPC 2.0 and DAP servers implement this. The CLI wires the selected server and calls
Run(), which blocks until the session ends. - Implementations:
rpccommon.ServerImpl(JSON-RPC),dap.Server(DAP). - Design quality: Exemplary ISP compliance. Two methods — exactly the lifecycle control a caller needs. All protocol-specific concerns are hidden. The simplicity is intentional: the CLI never needs to know what protocol the server speaks.
Client#
- Package:
service - File:
service/client.go:11 - Methods: ~50 methods covering:
- Lifecycle:
ProcessPid,BuildID,Detach,Restart,Disconnect - Execution control:
Continue,Next,Step,StepOut,Halt,Call,StepInstruction(andReverse*variants) - Thread/goroutine:
SwitchThread,SwitchGoroutine,ListThreads,ListGoroutines - Breakpoints:
CreateBreakpoint,CreateWatchpoint,ListBreakpoints,ClearBreakpoint,AmendBreakpoint,ToggleBreakpoint - Variables:
EvalVariable,SetVariable,ListLocalVariables,ListFunctionArgs,ListPackageVariables,ListScopeRegisters - Symbols:
ListSources,ListFunctions,ListTypes,FindLocation - Recording:
Checkpoint,Rewind,TraceDirectory,ClearCheckpoint - Core dump:
CoreDumpStart,CoreDumpWait,CoreDumpCancel - Misc:
DisassembleRange,ExamineMemory,FollowExec,CallAPI
- Lifecycle:
- Purpose: The full debugging protocol contract consumed by
pkg/terminal(the REPL) and any programmatic client. Abstracting this allows the terminal to be fully decoupled from the transport — it works identically over an in-process pipe or a TCP connection. - Implementations:
rpc2.RPCClient(the only production implementation). - Design quality: This is a deliberately broad interface — the antithesis of ISP, but justified. The terminal and Starlark scripting need the full API surface. The design accepts the breadth in exchange for a single protocol-agnostic client type. A role-based split (ExecutionClient, BreakpointClient, InspectionClient) would be more ISP-correct but would complicate the terminal’s dependency management.
MemoryReader#
- Package:
pkg/proc - File:
pkg/proc/mem.go:17 - Methods:
ReadMemory(buf []byte, addr uint64) (n int, err error) - Purpose: Single-method read interface for process memory. Analogous to
io.ReaderAtbut withuint64address to cover the full 64-bit address space. Used throughout DWARF evaluation and variable reading. - Implementations:
memCache,compositeMemory, allProcessInternalimplementations. - Design quality: Minimal and correctly modeled. The
uint64offset choice overint64(as inio.ReaderAt) is a necessary deviation from stdlib given 64-bit address spaces where negative offsets don’t make sense.
MemoryReadWriter#
- Package:
pkg/proc - File:
pkg/proc/mem.go:25 - Methods:
MemoryReader // embedded WriteMemory(addr uint64, data []byte) (written int, err error) - Purpose: Read+write memory interface returned by
Process.Memory(). Used for breakpoint insertion (writing INT3 bytes) and call injection (writing arguments). ThecompositeMemoryimplementation handles register-spilled variables. - Implementations:
memCache(read-through cache),compositeMemory(register + memory pieces), backend process types. - Design quality: Correct embedding of
MemoryReader. ThememCachewrapper demonstrates the Decorator pattern — it adds caching to anyMemoryReadWritertransparently.
Thread#
- Package:
pkg/proc - File:
pkg/proc/threads.go:10 - Methods:
Breakpoint() *BreakpointState ThreadID() int Registers() (Registers, error) RestoreRegisters(Registers) error BinInfo() *BinaryInfo ProcessMemory() MemoryReadWriter SetCurrentBreakpoint(adjustPC bool) error SoftExc() bool Common() *CommonThread SetReg(uint64, *op.DwarfRegister) error - Purpose: OS thread abstraction. Carries the CPU state (registers), current breakpoint status, and access to process memory. The common stepping machinery in
pkg/procoperates onThreadvalues returned byProcessInternal.ThreadList(). - Implementations:
native.nativeThread(Linux/macOS/Windows),gdbserial.Thread,core.Thread. - Design quality: Well-balanced. Covers exactly what the stepping/variable-eval code needs from a thread.
CommonThread(returned byCommon()) carries shared state (return values from call injection) that all implementations reuse via embedding, avoiding code duplication.
Registers#
- Package:
pkg/proc - File:
pkg/proc/registers.go:16 - Methods:
PC() uint64 SP() uint64 BP() uint64 LR() uint64 TLS() uint64 GAddr() (uint64, bool) Slice(floatingPoint bool) ([]Register, error) Copy() (Registers, error) - Purpose: Architecture-neutral CPU register snapshot. The named accessors (PC, SP, BP, LR, TLS) cover what the stepping logic needs across all architectures.
GAddr()is Go-specific: returns the address of the current goroutine descriptor.Sliceenumerates all registers for display or Starlark inspection. - Implementations:
native.Regs(per arch:regs_amd64.go,regs_arm64.go, etc.),gdbserial.Regs. - Design quality: Good. The Go-specific
GAddr()is an intentional extension beyond generic register conventions — Delve must track goroutine state, which requires knowing whereglives. TheCopy()method pattern (documented as necessary because live register state may change) is a useful idiom for snapshot semantics.
LocationSpec#
- Package:
pkg/locspec - File:
pkg/locspec/locations.go:22 - Methods:
Find(t *proc.Target, processArgs []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, string, error) - Purpose: Abstracts over the many forms of location strings:
file:line,func:line,/regex/,+offset,*address. Each syntax is parsed into a concreteLocationSpecimplementor; the singleFindmethod resolves it to concrete addresses using the target’s debug info. - Implementations:
NormalLocationSpec,RegexLocationSpec,AddrLocationSpec,OffsetLocationSpec,LineLocationSpec(all in the same package). - Design quality: Classic Strategy pattern. The parser in
locspecreturns one of five implementations;debugger.FindLocationjust callsspec.Find(...)without caring which syntax was used. Single-method, purpose-focused.
Interface patterns#
Size distribution: Heavily bimodal.
service.ServerandMemoryReaderhave 1–2 methods;service.Clienthas ~50. The coreprocinterfaces cluster around 6–10 methods. Single-method interfaces (MemoryReader,LocationSpec) are used where a capability needs to be swappable.Embedding: Used deliberately to express capability extension:
ProcessInternalembedsProcess(mutable extends read-only)RecordingManipulationInternalembedsRecordingManipulation(backend extends public)MemoryReadWriterembedsMemoryReader(write extends read) This mirrors the Go stdlibio.ReadWriterembedsio.Reader+io.Writerconvention.
Implicit satisfaction: All interfaces are defined at abstraction boundaries (layer interfaces or capability interfaces), not alongside their implementations.
Process/ProcessInternalare defined inpkg/procbut implemented in sub-packages (native,gdbserial,core).service.Serveris defined inservicebut implemented inservice/rpccommonandservice/dap. No explicit registration or factory pattern required.stdlib interfaces used:
MemoryReaderis a deliberateio.ReaderAtanalogue withuint64offsets. No directio.Reader/io.Writersatisfaction, as process memory needs non-standard addressing.pkg/logflags.Logger(not analyzed here) mirrorslogrus.FieldLogger. Nofmt.Stringerorsort.Interfaceusage in the core abstractions.
Key abstractions#
proc.Process/proc.ProcessInternal— The central design. The two-level read-only/mutable split is the single decision that makes all four backends interchangeable. Every upper layer stays backend-agnostic because it only seesProcess; the stepping machinery insidepkg/proctalks toProcessInternal.service.Client— The full debugging protocol contract. Its breadth (~50 methods) is justified: it decouples the terminal and Starlark scripting from transport, making the interactive CLI and remote IDE clients identical code paths. The in-process pipe trick only works because both sides speakservice.Client.service.Server— Tiny but critical. Two methods (Run,Stop) are all the CLI layer needs to be protocol-agnostic. This is what letsdlv debuganddlv dapshare identical startup code while serving radically different protocols.proc.Thread— The OS thread contract. All stepping logic, register reads, and breakpoint detection operate via this interface, making the entire debugging engine portable across the three OS backends without any#ifdefequivalent.proc.MemoryReadWriter— The memory access contract. ThecompositeMemoryimplementation (reading variables that live partly in registers, partly in stack) and thememCachedecorator both satisfy this interface, making variable evaluation completely transparent to whether data lives in memory or registers.
Interface-driven extensibility#
Backend extensibility is Delve’s primary extension point. Adding a new debugging backend (e.g., a JTAG interface for embedded Go) requires implementing ProcessInternal (and optionally RecordingManipulationInternal). All upper-layer code — the debugger, both protocol servers, the terminal — immediately works with no changes. The four existing backends demonstrate this: they range from in-process ptrace to network GDB packets to static core file reading, all behind the same interface.
Protocol extensibility via service.Server: adding a third protocol (e.g., the Chrome DevTools Protocol) requires only implementing the 2-method Server interface and wiring a new --listen-cdp flag in the CLI. The debugger engine would be untouched.
Optional capabilities are handled via type assertions rather than interface bloat. RecordingManipulation, RecordingManipulationInternal, and the eBPF-specific methods (SupportsBPF, SetUProbe, GetBufferedTracepoints) are checked at runtime. This keeps ProcessInternal from becoming the union of all possible backend features while still exposing them cleanly when available.
Location syntax extensibility via LocationSpec: adding a new location syntax (e.g., a DWARF expression syntax) requires implementing one Find method. The parser, the debugger, and all clients are unaffected.