Debugger

final class ntoseye.Debugger

A live debugging session. You can use it as a context manager: when the with block ends, the debugger closes (close()), which removes all breakpoints, resumes the target, and ends the session.

You can use it from any Python thread, and the session thread runs the calls one at a time. A call that waits (run(), wait()) releases the GIL. Ctrl+C (KeyboardInterrupt) during a call that runs or waits on the target breaks in, as in the REPL, and raises the exception; the target is left halted where it was. Between calls the session continues to service the guest, resuming breakpoint hits in the wrong process and hits with a false condition so the guest does not stay frozen. A debugger that ntoseye gives to a REPL custom command is valid only on the REPL thread, and only for that command.

__enter__() → Debugger
__exit__(_exc_type: Any, _exc_value: Any, _traceback: Any) → bool
property breakpoints: Breakpoints

Code breakpoints and data watchpoints: .add(...), .watch(...), iteration, [id].

property capabilities: list[BackendCapability]

The capability matrix of the backend, which shows the operations that the transport supports.

close() → None

Remove all breakpoints, let the target run, and end the session. This releases the connection and the single-instance lock of the target, so you can attach to the target again. After this call, this debugger and its handles raise an exception, and a second call does nothing. If closing fails, the target stays halted, the session stays open, and the error is raised. A borrowed debugger (from a REPL command) does not own the session and leaves it unchanged.

property coherent: bool

False after a reboot until the module list of the kernel exists. During that period kernel symbols and breakpoints work, but process and module enumeration do not work yet.

command(line: str, timeout: float | None = None) → str

Run a REPL command line and return its text output without styling. If a command resumes the target, this waits up to timeout seconds for the next stop, which then becomes dbg.stop. Command loops and .sleep also end when timeout elapses.

property cpus: Cpus

The processors (vCPUs) of the target: cpus[0].registers.rip.

crash(timeout: float | None = None) → Stop

Force a MANUALLY_INITIATED_CRASH (bugcheck 0xE2).

Windows first writes its crash dump, which often takes a minute, and the target ignores break-ins during this time. Then Windows reboots or, if automatic restart is disabled, breaks in. Ctrl+C stops the wait.

debug_log(*, since: int = 0) → DebugOutputPage

Captured guest debug output (DbgPrint) since sequence since. To poll only for new lines, pass the previous next_seq.

property drivers: Drivers

Driver objects from the Driver directory of the object manager: drivers["Disk"], .at(addr).

eval(expr: str) → int

Evaluate a debugger (MASM) expression to an integer (?) in this object’s scope: the debugger’s is the kernel’s, with the registers of the stopped vCPU (on a user-mode minidump, of the selected thread); a process’s resolves the symbols of its modules, which it loads first; a VTL1 address space’s (the secure kernel’s, a trustlet’s) has no registers, as the live ones hold VTL0 state.

property exceptions: Exceptions

Exception stop policies (sx*): .set(code, mode), iteration, .reset().

property generation: int

The number of times ntoseye has rebuilt its view of the guest, for example after a reboot. Handles from an older generation raise StaleHandleError, so keep this value with raw addresses to know when they become stale.

hypercalls() → list[Hypercall]

The Windows hypervisor’s hypercall table, one entry per call code, as !hvcalls -a lists it. Needs the VM’s hv-evmcs enlightenment or a vCPU stopped in the hypervisor. This feature is experimental.

hypervisor_partitions() → list[HypervisorPartition]

The partitions of the Windows hypervisor, root first, with their virtual processors, as !hvpartitions and !hvvps list them. Each call walks them again. This needs the VM’s hv-evmcs enlightenment or a vCPU stopped in the hypervisor, and an Intel host. Raises NtoseyeError if ntoseye does not recognize the layout of this hypervisor build. This feature is experimental.

property inspect: Inspect

System-wide reports and helpers that decode an object at an address (!vm, !pool, …).

interrupt() → Stop

Break into the running target and return the stop that results. On a halted target, return the stop it is halted at (break refuses it).

property memory: Memory

Kernel virtual memory, read through the page tables of the kernel. User addresses are not mapped here, so read them through process.memory. On a user-mode minidump, the captured process’s memory.

property modules: Modules

Loaded kernel modules: modules["nt"], iteration, .at(address). On a user-mode minidump, the modules the dump lists.

notices() → list[Notice]

Remove and return what the debugger reported since the last call, in order. Each notice has a level: "warning" for something that did not work as it should, such as a breakpoint that failed to re-arm or host memory that no longer matched after a reload, or "info" for status, such as a background symbol fetch finishing, a breakpoint slot that was reclaimed, or the ModLoad: line of an sxn ld filter.

property partition: int | None

The ID of the hypervisor partition inspected in place of the target (see select_partition), or None while the target is.

property physical: Memory

Guest-physical memory, without address translation. A user-mode minidump has none, and its reads raise NtoseyeError.

property processes: Processes

Running processes, keyed by PID: processes[4], .find(name).

reboot(timeout: float | None = None) → Stop

Reboot the debug target and reload its kernel context.

ntoseye sends the reboot without confirmation, and handles the next KD state-change through the normal target-reload path.

reload() → None

Rebuild the guest state now (find the kernel again). Stops already do this when the backend reports a reload, and this function forces a rebuild. A user-mode minidump has no kernel to find, and raises NtoseyeError.

run(timeout: float | None = None, *, address: int | str | Process | Thread | Module | Driver | Device | Heap | None = None, disposition: Literal['handled', 'not_handled'] | None = None) → Stop | None

Resume VM execution.

With an address, g runs to a temporary breakpoint at that address. ntoseye checks that an instruction starts there as bp does: it refuses an address inside an instruction and warns when it cannot check. In secure-kernel code, this breakpoint uses a debug register, and ntoseye never patches the code. The .vtl 1 memory view accepts only g without an address, which leaves the view before it resumes the VM. To stop in VTL1, use ba e1.

property secure_kernel: SecureKernel

The VBS secure kernel (VTL1), with read-only memory, symbols, types, modules, and trustlets. ntoseye finds it in host memory on first use and raises NtoseyeError if VBS is not running or the backend cannot read host memory. This feature is experimental.

select_partition(partition_id: int) → None

Inspect the Windows guest that hypervisor partition partition_id runs (a Windows Sandbox, a Hyper-V VM) in place of the target, as .partition does: memory, processes, modules, symbols, types and threads then read that guest, through its EPT, and the vCPUs are its VPs, with their VTL0 registers. The target stays halted. memory.write writes the guest’s memory through its EPT, and a register write goes to the target’s vCPU that runs the selected VP at the stop; a VP no vCPU runs has its registers in the hypervisor’s memory, and raises NtoseyeError. breakpoints.add(..., hardware=True) and breakpoints.watch set the partition’s breakpoints, which stop only on its VPs (processor= names one) and need the gdb backend; software breakpoints raise. run leaves the view and runs the target, and a hit of the partition’s breakpoint returns in its view, on the VP that hit it. step, step_over and step_out step the thread of the selected VP, and run(address=...) runs until any of its VPs reaches an address, each a run of the target to a breakpoint of the partition’s; step(until=...) and trace_calls raise. The root partition’s ID (1) returns to the target. Handles minted on either side of a switch go stale (generation advances). The target must be halted. This feature is experimental.

step(timeout: float | None = None, *, until: Literal['call', 'ret', 'branch'] | int | str | Process | Thread | Module | Driver | Device | Heap | None = None) → Stop

Step one instruction (step into).

step_out(timeout: float | None = None) → Stop

Run until the current function returns.

gu stops at the return address only when the thread that you step returns from this call. Other threads that get to the address, and deeper calls of the same function, continue to run.

step_over(timeout: float | None = None, *, until: Literal['call', 'ret', 'branch'] | int | str | Process | Thread | Module | Driver | Device | Heap | None = None) → Stop

Step over the current instruction.

If the instruction is a call, p runs to the instruction after the call and stops there only when the thread that you step returns from that call. Other threads that get to the address, and deeper calls of the same code, continue to run.

property stop: Stop | None

The current stop when the target is halted. None when the target runs.

property symbols: Symbols

Kernel-scope symbols: symbols["nt!KeBugCheckEx"], nearest(addr), search(query), and the symbol and source paths. On a user-mode minidump, the symbols of the dump’s modules.

property threads: Threads

All Windows threads, keyed by TID: threads[tid], .at(ethread).

trace_calls(timeout: float | None = None, *, limit: int = 10000) → CallTrace

Watch and trace calls until the current function returns.

property types: Types

Kernel-scope PDB types: types["_EPROCESS"].at(addr). On a user-mode minidump, the types of the dump’s modules’ PDBs (types["ntdll!_PEB"]).

property user_dump: UserDump | None

The process, threads, and exception a user-mode minidump captured, or None on a kernel target. A user-mode minidump has no kernel, so processes, threads, cpus, drivers, physical, and the kernel’s inspect reports raise NtoseyeError on it.

wait(timeout: float | None = None) → Stop | None

Wait for the next stop without resuming. If the target is already halted, return the current stop immediately, and return None if the target is still running after timeout.

write_dump(path: str) → DumpWrite

Write a full PAGEDU64 kernel dump of the live target (.dump).