Repository navigation
Releases: glslang/dbgscope
Release list
v0.3.0 — ARM64, a PE reader and breakpoint control
[dependencies]
dbgscope = "0.3.0"This release lets a breakpoint callback read its hit and decide whether the target stops, decodes ARM64 instructions in full and walks ARM64 kernel pools and user heaps, adds a PE reader and bounded reads and symbol reloads, and corrects how the pool and heap walkers read current Windows builds.
Highlights
- Breakpoint callbacks receive a typed
BreakpointHit— the breakpoint's id and address, and an engine stopped at the hit — and return aBreakpointAction(Default,GoorBreak), so a callback can let a hit go past instead of only watching it. A callback that panics answersDefaultrather than unwinding intodbgeng.dll. - ARM64 operands, registers, effects, conditions, privilege, control flow and function extents are decoded, and
decode_instructiondecodes bytes with no session at all. The kernel pool walker and the heap queries accept ARM64 targets; a WoW64 process is refused rather than half-walked. - The new
pemodule reads a loaded image's sections, imports, import address table and export names through a reader that tolerates holes, which is enough to name the calls of a driver with no symbols. read_memory_boundedreads a page at a time against a deadline,reload_symbols_boundedputs a watchdog around a reload, and a failed command keeps the engine's own explanation inDbgEngError::CommandFailedWithOutput.- New session queries:
debuggee_type,dump_files,session_processes,kernel_connection_options,virtual_region,integer_register,current_thread_tebandcurrent_thread_data_offset.end_sessionreports whether a live kernel was left running. - Pool and heap walks read current builds correctly: big-pool allocations are named from
nt!PoolBigPageTable, user heaps are rooted fromntdll's heap list, LFH bitmaps and the VS free tree are read asntandntdllwrite them, page-aligned VS user blocks are placed where the allocator puts them, and reserved address space no longer counts against a heap walk's coverage. A pool walk that reached the end is cached, taking one live tier from 2,665 s to 1,032 s. - Break-ins still owed after a KD attach, or after a breakpoint every processor hit, are drained before
qd, so a detach no longer leaves the guest frozen.
Migration from 0.2
BreakpointCallbackisBox<dyn Fn(&BreakpointHit<'_>) -> BreakpointAction>. Register it withset_breakpoint_callbackand remove it withclear_breakpoint_callback;create_debug_event_context_callbacks,set_breakpoint_event_callbacksand the publicDebugEventContextCallbacksare removed.end_sessionreturnsTargetLeftrather than().KernelHaltedmeans the session ended and the guest is probably still stopped at a break.- A command that fails returns
DbgEngError::CommandFailedWithOutputwhere it returnedCommandFailed. Match both, or read the engine's line throughDbgEngError::output(). - Public enums gain variants and public structs gain fields, among them five
DbgEngErrorvariants,Operand::Undecoded,PoolState::Uncommitted,Instruction::privilegeandreads. Update exhaustive matches and struct literals. HeapRoot::indexis a position inntdll's heap list rather than the PEB'sProcessHeaps, and every pool and heap layout fingerprint moves. Re-resolve anything cached by fingerprint.
See the full changelog for detailed behavior and limitations.
Changes: v0.2.0...v0.3.0
v0.2.0 — typed analysis and object namespace
[dependencies]
dbgscope = "0.2.0"This release adds typed instruction analysis, kernel object namespace queries, breakpoint management and crash-event reads, and fixes live-target arrival and interrupt handling.
Highlights
- Decode x86/x64 operands, control flow, privilege requirements, effects, conditions and register writes from instruction bytes.
decode_rangedecodes a span from one memory read;effective_processor_type,function_extentandsymbol_forexpose the context needed to interpret it. ARM64 instruction semantics remain unsupported. - Resolve kernel object paths, list directories and read symbolic-link targets through the new
objectmodule. Partial listings distinguish unreadable entries from malformed ones, and callers can stop a walk withNamespace::halting. - Create, replace, enable and remove breakpoints through typed specifications, including data watchpoints and read-back of the watched region.
- Read exception events and stored dump contexts as values, and walk the recorded crash stack with
stack_frames_fromwithout changing the selected thread or scope. - Deliver live-target arrivals to the opener waiting for them, distinguish concurrent launches, scope interrupts to their operation, and invalidate saved contexts when the session is replaced. Launched console programs receive a console without opening a desktop window.
- Stop pool tag searches at a requested match threshold and report the partial result without caching it as complete.
Migration from 0.1
DebugEngineis no longerSendorSync. Create and use it on the same thread; useInterruptHandlefor interruption from another thread.- The public
Breakpointwrapper is removed. Useset_breakpoint,remove_breakpointandenable_breakpointwith breakpoint IDs. InstructionandBreakpointInfogain fields, and public enums gain variants. Update struct literals and exhaustive matches.
See the full changelog for detailed behavior and limitations.
Changes: v0.1.0...v0.2.0
v0.1.0 — first release
[dependencies]
dbgscope = "0.1.0"First release. dbgscope gives typed access to a WinDbg/DbgEng debug session, and kernel-pool
and user-heap walkers built on one. The organising rule, and the thing to know before reading
the API, is that every answer carries what the answering cost — see
Unknown, not absent.
Added — debug sessions
DebugEngineoverIDebugClient6/IDebugControl4/IDebugDataSpaces4/
IDebugSymbols3, owning its own session (new,Default) or borrowing an existing WinDbg
client (from_windbg_client,from_client_interface, and theirtry_forms).owns_session
governs teardown, so a borrowed client is never ended.- Openers for every target DbgEng handles:
attach_kernel,attach_local_kernel,
launch_process,attach_process,open_dump,open_trace. The two post-mortem openers
commit the session and leave the pump to the caller: the engine has no current process or
thread — andGetNumberRegistersanswers0x8000FFFF— untilwait_for_eventhas run. connect(remote_options)for an existing debugging server, so an extension can load out of
process.- Two-step opening on the four live openers, each a thin
x_begin()?.wait().x_begin
performs only the side effect that creates or claims the target and returns aPendingTarget;
waitcompletes the initial break. The split lets a caller distinguish "nothing happened,
retry is clean" from "the target exists and only the wait failed" — opposite recoveries, since
re-running the second spawns a second process, attaches twice, or re-dials a live KD link.
Dropping a guard is safe and non-blocking: deferred input buffers are parked on the engine
rather than the guard, becauseCreateProcessWidereads the command line at the next
WaitForEvent. - Per-process teardown. DbgEng holds several user-mode targets at once and
EndSession
takes one flag for the whole session, so provenance is recorded per pid at open time. A
process this engine attached to is detached individually before the session ends — otherwise
a passive end kills somebody else's service. A live kernel is resumed and actively detached,
or it stays frozen at its last break with one CPU halted. Failures are per process and
reported without stopping the teardown. launch_processusesDEBUG_ONLY_THIS_PROCESS | CREATE_NEW_CONSOLE, so a console target
cannot inherit a host's stdout — which may be a JSON-RPC channel.
Added — execution control
execute_command_bounded,execute_and_wait,settleandrun_to_address: bounded waits
with a condition-variable watchdog behind them. The watchdog stops the moment it is disarmed
rather than at the end of a poll interval, so a bound costs nothing until it is reached.CommandRun { output, cut_short, target_gone }— the output and whether the command
finished. AStringalone cannot answer "did this run?", and anErrwould discard the
output, which on an interrupted search is all there was.Interruption::Deadline { after_ms }distinguished fromInterruption::OnRequest, because
the advice differs and only the first needs saying. The origin is decided by the watchdog's
own flag, not by the shared interrupt bit that the watchdog also sets.RunToOutcomenames four endings —Hit,StoppedElsewhere { stopped_at },Timeout,
TargetGone— rather than a boolean.InterruptHandle: aSend + Synchandle that Ctrl+Breaks an engine from another thread, over
SetInterrupt, the one DbgEng call documented as safe there. It holds an owned interface
reference, so it may outlive theDebugEngineit came from.- A target that ends is an ending, not a failure. A debuggee running to completion is
reported asCommandRun::target_gone/RunToOutcome::TargetGone, each keeping the output the
run captured — the module loads, the breakpoint banner, an embedded script's prints, none of
which a successor will print again. It is terminal, and callers are told so. - Nothing runs on an engine with no debuggee. Driving DbgEng without a target faults inside
it with aSTATUS_ACCESS_VIOLATIONthatcatch_unwindcannot trap, soexecute_command,
execute_command_bounded,execute_and_waitandrun_to_addressall refuse first.
Added — typed session state
register_valuesreturningRegisterValue, decoded once fromDEBUG_VALUE's own tag:
Int,Float,Bytesfor x87 and vector registers that nof64can hold, and
Unavailablefor state a minidump does not carry — which is not0.register_descriptionsfor the whole register description rather than one flag of it.modules,unloaded_modules,module,module_at,module_identity,module_pdb,
module_symbol_file.SymbolKindkeepsDeferredseparate fromNoneand preserves an
unrecognised provider asOther(u32);has_type_info()answers the narrow question the pool
walker actually asks.stack_frames,bug_checkas the engine's five values,breakpointsasBreakpointInfo,
disassembleasInstructionrecords with a line the split does not recognise kept whole
rather than guessed at.- Scope save and restore:
scope,set_scope,scope_guardand theScopeGuardRAII type,
for running a command that moves the debugger's scope — measured:!analyze -vdiscards a
frame or.ecxrcontext the caller had selected. AScopecarries the target identity it was
read from and is refused rather than applied to a later target. - Memory and symbols:
read_memory,valid_virtual_region,symbol_offset,type_id,
type_size,field_offset,field_type_and_offset,field_names,set_symbol_path,
append_symbol_path,reload_symbols. - Breakpoints: the
Breakpoint<'a>RAII type,BreakpointCallback, and
DebugEventContextCallbacksfor event-driven handling.
Added — kernel pool
pool::query:find_tag,chunk_atwith immediate neighbours,tag_census,
snapshot_report, and cache invalidation hooks for a host that resumes or replaces a target.PoolAnswer<T>pairs every answer with thePoolSnapshotReportof the walk it came from, so
a count and a coverage figure can never be drawn from two different walks — which is what
happens otherwise, since an incomplete walk is deliberately not cached.WalkCoverage { Complete, BudgetExpired, Partial }, computed in one place from the walk's own
two bits. Not abool, because a walk that ran out of time reaches more of the pool if given
more, and one that met unreadable regions reports the same gaps however long it runs.PoolWalkwithcached(),refreshed(),within(Duration)andunbounded(), plus
impl From<bool>so existingrefresh: boolcall sites are unchanged and pick up
DEFAULT_WALK_BUDGET(120s). A walk that runs out of its budget is not an error: it returns
the chunks it reached with the coverage saying so.PoolDiagnosticsgroups complaints by shape — the message with every number standing in for
itself — keepingDIAGNOSTIC_EXAMPLES(8) verbatim per shape and the totals as numbers.
emitted()describes the walk;examples().len()describes the struct, and on a busy target
the two differ by two orders of magnitude.WalkStalls,refused_chunksandunplaced_bytessize what conservative decoding cost, so a
refusal to guess is auditable rather than invisible.- Both tag forms: one to four ASCII bytes, or the raw form
0xplus eight hex digits in memory
order, so it reads in the same direction as the printed tag (Tgsmis0x5467736d). The two
cannot collide — the raw form is exactly ten characters and a printed tag at most four.
display_is_ambiguousanddisplay_round_tripsseparate the two distinct ways a rendering
fails, andtag_labelis the one rule every output site prints through. PoolKind's eight variants are not collapsed to paged/nonpaged, because crossing one of those
boundaries creates false holes.PoolState::Unreadableis distinct from allocated and from
the two free states, andchunk_atreturnsOk(None)for "not covered by the snapshot",
which is a different answer from "it is a free hole".find_tagindexes allocated chunks only: a freed chunk's tag is not reliably preserved by the
allocator, so returning freed chunks by tag would be inventing information.
Added — user-mode Segment Heap
heap:list,allocations,chunk_at,census,diagnostics,diagnostics_for_heap,
withHeapWalkmirroringPoolWalkandHeapAnswer<T>mirroringPoolAnswer<T>.HeapScopenames the roots that were skipped and why —nt_heaps_skipped,
unknown_heaps_skipped,unreadable_heaps_skipped— rather than reporting only the ones that
worked.- Shared page-segment, LFH, VS, backend and large-allocation decoding with the pool walker,
because the two allocators are the same machinery either side of the ring boundary. allocator::LayoutProvenancecarries the image, PDB and a fingerprint of every resolved type
size and field offset actually used — deliberately with no build-number policy in it.requested_sizeisOption<u64>, set only where allocator metadata validates it, rather than
guessed from capacity.
Added — WinDbg extension
!dbgscope.poolmap, built from thecdylibcrate type, over the same walker and the same
caches aspool::query, so the interactive and programmatic entry points cannot drift apart.-tag(either form),-paged/-nonpaged,-refresh, and an address argument for detail
on the allocation or hole containing it.- DML colours and clickable address links where WinDbg accepts DML; meaningful ASCII glyphs and
a legend where it is stripped or the output is captured...