Breakpoints and events

!hvbp

Set a breakpoint that stops on a hypercall to the Windows hypervisor, optionally only from one partition or VP.

!hvbp [/1] [/c <processor>] [/w "<expr>"] <code|name> [partition-id [vp-index]] [if <expr>] [do <commands>]

Sets a hardware execute breakpoint on the hypercall’s handler, which the hypervisor’s hypercall table gives (see !hvcalls), so it needs the gdb backend and a free debug register. The call is a call code, in the current radix, or its name: the TLFS name (HvCallPostMessage) or the name x hv!* shows (HvCall0004). Many codes share a handler, HvCallUnimplemented’s for every code that is not implemented, so at each hit ntoseye reads the caller’s call code from its RCX and resumes the target without a stop when it is another. The caller is the VP whose exit the processor handles: the guest partition’s VP it serves, or the root partition’s VP. With a partition ID, and a VP index in it, the breakpoint stops only for that caller; the IDs use the current radix, and ntoseye refuses a partition or VP that the hypervisor does not have. A hit whose caller ntoseye cannot tell, or whose caller matches but whose registers it cannot read, stops, so the filter does not hide a hit. /1, /c, /w, if, and do work as for ba; a condition sees the caller’s registers at its VMCALL (as !hvcall decodes them), not the hypervisor’s at the handler, and stops the hit with an error when they are not known. The command takes no pass count: set it with bpp. bl shows the filter. Needs the VM’s hv-evmcs enlightenment. Example: !hvbp HvCallSendSyntheticClusterIpi 3; g

!hvexit

Set a breakpoint that stops on a VM exit to the Windows hypervisor by its reason, optionally only from one partition or VP.

!hvexit [/1] [/c <processor>] [/w "<expr>"] <reason> [partition-id [vp-index]] [if <expr>] [do <commands>]

Sets a hardware execute breakpoint on the hypervisor’s VM-exit entry point (hv!VmExitEntry, the host RIP of every eVMCS), so it needs the gdb backend, the VM’s hv-evmcs enlightenment and a free debug register. The reason is a basic exit reason (Intel SDM Appendix C), as a decimal or 0x number, or a name as !hvvps shows it: cpuid, rdmsr, wrmsr, io_instruction, ept_violation, vmcall, hlt. Every exit enters there, so at each hit ntoseye reads the reason from the eVMCS the processor has loaded and resumes the target without a stop when it is another; the guest runs far slower while the breakpoint is set. With a guest partition’s ID it rarely stops: that partition’s VPs run only when the root partition’s NT runs them, which it barely does then; !hvbp stops on a guest’s hypercalls without slowing the rest. The caller is the VP whose exit it is, as for !hvbp: with a partition ID, and a VP index in it, the breakpoint stops only for that caller. The condition is evaluated on the caller’s registers at its exit and reads the caller’s memory. Example: !hvexit cpuid 6 if @rax==0x40000000

.bpcmds

Print the commands that set the current breakpoints again.

.bpcmds

The output has one line for each breakpoint, in ID order. An address breakpoint shows as bp <address>, a symbolic or source breakpoint as bu <symbol> (as bm creates it), a hardware breakpoint as ba <access><size> <address>, and a hypercall breakpoint as !hvbp <code> [partition-id [vp-index]], without its pass count, which !hvbp does not take. Each line also has the /1, /p, /t, and /c options of the breakpoint, its condition (as /w), its pass count, and its command string. To set the same breakpoints again, run the lines, either by pasting them or by saving them to a file for $$<. WinDbg puts a breakpoint ID in each line, but these lines have none because the ntoseye bp command does not take one, so the new breakpoints get new IDs. A disabled breakpoint comes back enabled.

ba

Set a hardware (debug-register) breakpoint.

ba [/1] [/p <pid>] [/t <tid|ethread>] [/c <processor>] [/w "<expr>"] <access><size> <address> [passes] [if <expr>] [do <commands>]

The access is e=execute, r=read/write, or w=write, and the size is 1, 2, 4, or 8 bytes. An execute breakpoint has size 1. The options may also follow the access and size, as WinDbg writes them: ba e1 /p 4 kernelbase!SleepEx. With /p, the address is evaluated in that process, as after .process /p, and the current process context stays as it was. The command is not available on a dump or on the memory backend. Example: ba w4 nt!MyGlobal. In VTL1 (the .vtl 1 view or a vCPU stopped in VTL1), ntoseye accepts only ba e1, and only on GDB backends. This breakpoint is global, so you cannot use /p or /t, which name NT processes and threads. Example: .vtl 1; ba e1 securekernel!SkeSelectProcessAddressSpace; g

bc

Clear one or more breakpoints by ID.

bc <id|id-id|*>...
bd

Disable one or more breakpoints by ID.

bd <id|id-id|*>...
be

Enable one or more breakpoints by ID.

be <id|id-id|*>...
bl

List all breakpoints.

bl

The status column shows e for enabled, d for disabled, or o for owed. An owed breakpoint is a kernel code breakpoint that waits for its page to become resident.

bm

Set deferred symbolic breakpoints on the symbols that match a pattern.

bm [/1] [/p <pid>] [/t <tid|ethread>] [/c <processor>] [/w "<expr>"] <symbol-pattern> [passes] [if <expr>] [do <commands>]

Only code symbols match, so bm skips the data symbols that the pattern also matches. With /p, the pattern matches the symbols of that process’s modules, whose symbols ntoseye loads for it.

bp

Set a breakpoint.

bp [/1] [/a] [/p <pid>] [/t <tid|ethread>] [/c <processor>] [/w "<expr>"] <address|file:line> [passes] [if <expr>] [do <commands>]

The breakpoint replaces the first byte of the instruction at the address, so an instruction must start there. ntoseye confirms one by the symbol at the address, or by decoding from the start of the function around it, and refuses an address inside an instruction. When nothing near the address says where instructions start, or that code cannot be read, bp sets the breakpoint and warns that it could not check. /a skips the check. A file:line sets a source breakpoint, as bu does. With /p, the address is evaluated in that process, as after .process /p, so bp /p 4140 kernelbase!SleepEx+0x10 needs no .process first; the current process context stays as it was.

bpc

Change or clear a breakpoint condition.

bpc <id> <condition|clear>
bpp

Reset the pass count of a breakpoint.

bpp <id> <passes>
br

Change the ID of a breakpoint.

br <id> <new-id>
bs

Set or clear the command action of a breakpoint.

bs <id> <commands|clear>

Also: bpa

bsc

Set the condition and the commands of a breakpoint together.

bsc <id> <condition> ["commands"]

This is the WinDbg update-conditional-breakpoint command. The breakpoint stops only when is nonzero, and then runs the quoted commands. Use semicolons between the commands (bsc 0 @rcx==4 "k; g"). If you do not give commands, bsc removes the current commands of the breakpoint.

bu

Set a deferred symbolic breakpoint.

bu [/1] [/a] [/p <pid>] [/t <tid|ethread>] [/c <processor>] [/w "<expr>"] <symbol|file:line> [passes] [if <expr>] [do <commands>]

A symbol with an offset, such as mydriver!DriverEntry+0x20, must name the start of an instruction, which ntoseye checks as bp does. Until the symbol resolves nothing says where its instructions start, so bu sets the breakpoint and warns that it could not check. /a skips the check. A file:line sets a source breakpoint on each address the line has.

gc

Resume from the breakpoint that runs the current command string.

gc

Use this command only in the command string of a breakpoint, where it stops the command string and resumes the target. You can put it at the end (bp nt!NtClose "r rcx; gc") or in a branch (bp nt!NtClose "j (@rcx == 0) '' ; 'gc'"), and the commands after it do not run. A plain g in the command string does the same, as in WinDbg scripts, but ntoseye does not accept g <address> or other run control there.

sx

List the configured exception policies and module load and unload filters.

sx

Also: sxl

sxd

Pass first-chance exceptions and break on second chance.

sxd [-c <commands>] [-f <break|gh|gn>] <exception-code|alias|ld[:<module>]|ud[:<module>]>

-c runs commands at the event, and -f sets an explicit final action: break, handled, or not handled. Without -f, the event ends after the commands as it would without -c. sxd ld[:<module>] and sxd ud[:<module>] let the load or unload continue with no output. See sxe.

sxe

Break when an exception occurs or a kernel module loads or unloads.

sxe [-c <commands>] [-f <break|gh|gn>] <exception-code|alias|ld[:<module>]|ud[:<module>]>

-c runs commands at the event, and -f sets an explicit final action: break, handled, or not handled. Without -f, the event ends after the commands as it would without -c. sxe ld stops when a kernel image loads, and sxe ld:<module> stops when that module loads. The module name is case-insensitive, the extension is optional, and you can use */? globs. The stop occurs after ntoseye lists the module and arms its deferred breakpoints, and before its entry point runs. sxe ud and sxe ud:<module> stop when a kernel image unloads, after its driver’s unload routine has run and while the image is still in the module list. -c works with sxe ld and sxe ud, but -f does not apply to them. sxn ld prints a ModLoad: line and continues, sxn ud prints an Unload module line and continues, and sxd and sxi let the load or unload continue with no output. A named filter has precedence over bare ld or ud. The load filters apply to the user-mode images processes map too: while one can name a DLL, ntoseye traps nt!PsCallImageNotifyRoutines, which runs for every image a process maps. On ARM64 the kernel reaches it only for a new process’s executable and ntdll, so set !gflag +ksl there for later DLL loads (Windows then reports each image’s first mapping). Over KD with the ksl global flag set, the unload filters apply to user-mode images.

sxi

Pass exceptions without a break or a notification.

sxi [-c <commands>] [-f <break|gh|gn>] <exception-code|alias|ld[:<module>]|ud[:<module>]>

-c runs commands at the event, and -f sets an explicit final action: break, handled, or not handled. Without -f, the event ends after the commands as it would without -c. sxi ld[:<module>] and sxi ud[:<module>] let the load or unload continue with no output. See sxe.

sxn

Show a notification for exceptions and pass them without a break.

sxn [-c <commands>] [-f <break|gh|gn>] <exception-code|alias|ld[:<module>]|ud[:<module>]>

-c runs commands at the event, and -f sets an explicit final action: break, handled, or not handled. Without -f, the event ends after the commands as it would without -c. sxn ld[:<module>] prints a ModLoad: line at the load and continues, and sxn ud[:<module>] prints an Unload module line at the unload and continues. See sxe.

sxr

Reset exception policies to the default break behavior, and clear module load and unload filters.

sxr