XDP for Windows: Testing & Debugging
This skill covers everything an agent needs to execute tests on the
required separate test machine, attach kd for crash analysis, and
recover the machine to a known-good state after a bugcheck.
[!IMPORTANT]
All XDP testing must occur on a separate Windows test machine — never
the dev machine. Always pass -ComputerName <machine> explicitly on
every test command. Do not rely on
tools\remote-set-default.ps1 alone — the session default lives in
the PowerShell global scope and is silently lost when a new terminal
spawns (after Start-Process, kd sessions, or whenever the tool host
opens a fresh shell). A test script invoked without -ComputerName
and without an active session default will run against the dev
machine and may damage local state. Treat -ComputerName as
required.
When to Use
- Running any of:
functional.ps1, spinxsk.ps1, prepare-machine.ps1
(non-build scenarios), check-drivers.ps1, rxfilter.ps1,
rxfilterperf.ps1, ringperf.ps1, xskmaprx.ps1, xskfwdkm.ps1,
pktfuzz.ps1.
- Triggering, observing, or analyzing a kernel bugcheck.
- Recovering a test machine that's stuck after a crash.
- Choosing default parameters for a test invocation.
Build-only scripts (always local, ignore -ComputerName)
build.ps1, merge-artifacts.ps1, publish-nuget.ps1,
prepare-machine.ps1 -ForBuild / -ForEbpfBuild,
create-test-archive.ps1, update-nuspec.ps1.
Remote-test workflow
Ask the user once per chat session which test machine to use:
"Which test machine should I run tests on?". Accept hostname,
FQDN, or IP. Remember the value for the rest of the session and
pass it as -ComputerName <machine> on every test invocation.
(Optional, human convenience only) set the session default. Does
not replace -ComputerName for agent-driven runs:
.\tools\remote-set-default.ps1 <machine>
Run any test script with -ComputerName — it auto-deploys
artifacts\bin\<plat>_<cfg>\ and streams output back:
.\tools\functional.ps1 -ComputerName <machine> -ListTestCases
.\tools\functional.ps1 -ComputerName <machine> -TestCaseFilter "Name=GenericBinding"
.\tools\spinxsk.ps1 -ComputerName <machine> -Minutes 5
Credentials. First connect auto-adds the host to WSMan
TrustedHosts (UAC prompt) and, if WinRM rejects with auth error,
prompts at the console (Read-Host, no GUI) for username and
password. Credentials are cached in
$global:XdpRemoteCredentialCache for the rest of the session. Do
not generate Get-Credential calls or store passwords in
scripts.
[!IMPORTANT]
When run by an agent, console password prompts are invisible to
the user (the terminal is not interactive and any typed
characters would be shown in plaintext). If a test command stalls
waiting for credentials, do not send them to the terminal.
Instead, collect username and password through the chat UI
(vscode_askQuestions) and pass them to subsequent commands via
the -Credential parameter or by pre-populating
$global:XdpRemoteCredentialCache before re-running the script.
One-time test-machine setup (the user must run, requires
elevation):
.\tools\remote.ps1 -EnableRemoting
If the user hasn't done this, the connection fails with "WinRM
client cannot process the request" — surface this and ask them to
run it.
Build first. Always run .\tools\build.ps1 (or rely on a
recent build) before remote test commands, since deploy pulls from
artifacts\bin\<plat>_<cfg>\.
Choosing test parameters
When the user doesn't specify parameters, derive sensible defaults
from the matching job in .github/workflows/ci.yml.
That file is the source of truth for which switches each test exercises
in CI.
- spinxsk:
tools/spinxsk.ps1 -Verbose -Stats -Driver <XDPMP|FNMP> -Minutes <N> -XdpmpPollProvider <NDIS|FNDIS> -SuccessThresholdPercent <pct> -EnableEbpf [-TxInspect]. Scale
-Minutes down for smoke runs as requested.
- functional: mirror
-Config, -Platform, and any
-TestCaseFilter / -XdpInstaller flags from the CI job.
- perf/fuzz: copy the CI invocation verbatim, adjusting only
duration/iteration counts when the user asks for a shorter run.
Respect explicit user overrides; leave everything else at CI defaults.
Kernel debugger (kd) attach for bugcheck monitoring
Test machines typically have a kd "head" already attached and exposing
a debug server over a named pipe (started on the head with
.server npipe:pipe=<name>). To watch for breaks during a test run,
attach a passive remote kd client from the dev machine in a
separate background terminal before kicking off the test:
# Run with mode=async; <pipe-name> defaults to the same value as
# -ComputerName / Set-XdpRemoteDefault.
kd.exe -remote npipe:server=localhost,pipe=<pipe-name>
kd usage rules
- Always
-remote npipe:server=localhost,pipe=<name> — never
-k com:pipe,port=... (that's the serial-pipe transport for VMs and
will not connect to a kd debug server).
- Default
pipe to the value of -ComputerName /
Set-XdpRemoteDefault. If that fails to connect, ask the user once
for the correct pipe name.
- Run kd
mode=async so test invocations stream output in parallel.
- Periodically check the kd terminal for break-in prompts (
kd> after
a break) or bugcheck banners; surface them to the user, otherwise
let kd run idle.
- If kd reports "Cannot connect to server" or exits immediately, no kd
head is currently attached. Inform the user and continue without kd.
- Keep kd attached across reboots. After
.reboot, leave the kd
terminal running — secondary breaks (early-boot Driver Verifier hits,
watchdog timeouts, follow-on bugchecks from corrupted state) often
happen during the next boot.
- Issuing
.reboot non-interactively from the dev machine. Use
kd.exe -cf <scriptfile> with a hidden window:$cmd = New-TemporaryFile
Set-Content -Path $cmd -Value ".reboot`r`nqd"
Start-Process kd.exe -ArgumentList @(
"-remote","npipe:server=localhost,pipe=<machine>",
"-cf",$cmd.FullName) -PassThru -WindowStyle Hidden |
ForEach-Object { $_.WaitForExit(60000) | Out-Null }
Remove-Item $cmd.FullName -Force
Do not try to drive an interactive kd> prompt via PowerShell's
terminal stdin — that connects you to the host shell, not kd.
Then poll Test-WSMan -ComputerName <machine> until it succeeds.
- When the chat session ends or the user moves on, kill the kd
terminal.
Bugcheck recovery loop
Bugchecks frequently leave the test machine in a partially-corrupted
state that prevents the next iteration from running. The recovery
script is tools\check-drivers.ps1 -Force. The dev loop cannot move
on until that command reports "No loaded XDP drivers found!".
Recovery doctrine
Bugchecks can corrupt on-disk and registry state. Files written
in the seconds before the crash may be truncated, partially written,
or filled with NUL bytes. Driver registry keys, INF/catalog files,
PnP metadata, and Windows Installer product databases are common
victims.
Reinstall over partial state when feasible. When an
installer/MSI/INF reports the product is missing from its database
but the on-disk binaries are still present, reinstall on top of
the existing layout (which reseeds the database) and then run the
normal uninstall. Prefer this over hand-rolled service/driver
teardown.
Recovery flags MUST be opt-in. Behaviors that downgrade errors,
reinstall over corruption, or paper over partial state belong
behind a -Force switch on the recovery script (e.g.
tools\check-drivers.ps1 -Force). Don't change default
install/uninstall behavior — that would silently mask real bugs.
Persist until clean. Each bugcheck tends to expose a slightly
different corruption (NUL-filled scripts last time, wedged eBPF
service this time, something else next time). When
check-drivers.ps1 fails:
- Read the actual error and identify which component / step is
stuck.
- Improve
tools\setup.ps1 (or tools\check-drivers.ps1) with a
targeted fix for that specific failure mode — add a stuck
service to the cleanup list, detect a specific MSI exit code,
add a NUL-byte check for a specific file path, etc.
- Re-run
check-drivers.ps1 -Force and repeat until it succeeds.
- Only then return to the build/test/deploy loop.
Do not stop at "the recovery is partially working" — partial
recovery means the next test run will hit the residual broken
state and waste an iteration. Avoid broad ignore-everything error
handling; recovery should be targeted and understandable.
Common test commands
# Functional tests
.\tools\prepare-machine.ps1 -ComputerName <machine> -ForFunctionalTest
.\tools\functional.ps1 -ComputerName <machine> -Config Debug -Platform x64
.\tools\functional.ps1 -ComputerName <machine> -TestCaseFilter "Name=GenericBinding"
.\tools\functional.ps1 -ComputerName <machine> -ListTestCases
.\tools\log.ps1 -Convert -Name xdpfunc
# Stress tests (spinxsk)
.\tools\prepare-machine.ps1 -ComputerName <machine> -ForSpinxskTest
.\tools\spinxsk.ps1 -ComputerName <machine> -XdpmpPollProvider FNDIS -QueueCount 2 -Minutes 10
.\tools\log.ps1 -Convert -Name spinxsk
# Recovery
.\tools\check-drivers.ps1 -ComputerName <machine> -Force
See docs/remote-testing.md for the
human-facing reference.
1---2name: xdp-testing3description: XDP-for-Windows test execution and debugging workflow. Use when running tests (functional, spinxsk, pktfuzz, ringperf, rxfilter, xskmaprx, xskfwdkm), debugging bugchecks, attaching kd, recovering test machines after crashes, or invoking any tools/*.ps1 script that runs on hardware. Covers PowerShell remoting via -ComputerName, kd remote pipe debugging, and the inject/build/bugcheck/.reboot/check-drivers recovery loop.4---56# XDP for Windows: Testing & Debugging78This skill covers everything an agent needs to execute tests on the9required separate test machine, attach kd for crash analysis, and10recover the machine to a known-good state after a bugcheck.1112> [!IMPORTANT]13> All XDP testing must occur on a separate Windows test machine — never14> the dev machine. Always pass `-ComputerName <machine>` explicitly on15> every test command. Do **not** rely on16> `tools\remote-set-default.ps1` alone — the session default lives in17> the PowerShell global scope and is silently lost when a new terminal18> spawns (after `Start-Process`, kd sessions, or whenever the tool host19> opens a fresh shell). A test script invoked without `-ComputerName`20> and without an active session default will run **against the dev21> machine** and may damage local state. Treat `-ComputerName` as22> required.2324## When to Use2526- Running any of: `functional.ps1`, `spinxsk.ps1`, `prepare-machine.ps1`27 (non-build scenarios), `check-drivers.ps1`, `rxfilter.ps1`,28 `rxfilterperf.ps1`, `ringperf.ps1`, `xskmaprx.ps1`, `xskfwdkm.ps1`,29 `pktfuzz.ps1`.30- Triggering, observing, or analyzing a kernel bugcheck.31- Recovering a test machine that's stuck after a crash.32- Choosing default parameters for a test invocation.3334## Build-only scripts (always local, ignore -ComputerName)3536`build.ps1`, `merge-artifacts.ps1`, `publish-nuget.ps1`,37`prepare-machine.ps1 -ForBuild` / `-ForEbpfBuild`,38`create-test-archive.ps1`, `update-nuspec.ps1`.3940## Remote-test workflow41421. **Ask the user once per chat session** which test machine to use:43 *"Which test machine should I run tests on?"*. Accept hostname,44 FQDN, or IP. Remember the value for the rest of the session and45 pass it as `-ComputerName <machine>` on every test invocation.46472. *(Optional, human convenience only)* set the session default. Does48 **not** replace `-ComputerName` for agent-driven runs:49 ```powershell50 .\tools\remote-set-default.ps1 <machine>51 ```52533. **Run any test script with `-ComputerName`** — it auto-deploys54 `artifacts\bin\<plat>_<cfg>\` and streams output back:55 ```powershell56 .\tools\functional.ps1 -ComputerName <machine> -ListTestCases57 .\tools\functional.ps1 -ComputerName <machine> -TestCaseFilter "Name=GenericBinding"58 .\tools\spinxsk.ps1 -ComputerName <machine> -Minutes 559 ```60614. **Credentials.** First connect auto-adds the host to WSMan62 TrustedHosts (UAC prompt) and, if WinRM rejects with auth error,63 prompts at the **console** (`Read-Host`, no GUI) for username and64 password. Credentials are cached in65 `$global:XdpRemoteCredentialCache` for the rest of the session. Do66 **not** generate `Get-Credential` calls or store passwords in67 scripts.6869 > [!IMPORTANT]70 > When run by an agent, console password prompts are invisible to71 > the user (the terminal is not interactive and any typed72 > characters would be shown in plaintext). If a test command stalls73 > waiting for credentials, do **not** send them to the terminal.74 > Instead, collect username and password through the chat UI75 > (`vscode_askQuestions`) and pass them to subsequent commands via76 > the `-Credential` parameter or by pre-populating77 > `$global:XdpRemoteCredentialCache` before re-running the script.78795. **One-time test-machine setup** (the user must run, requires80 elevation):81 ```powershell82 .\tools\remote.ps1 -EnableRemoting83 ```84 If the user hasn't done this, the connection fails with "WinRM85 client cannot process the request" — surface this and ask them to86 run it.87886. **Build first.** Always run `.\tools\build.ps1` (or rely on a89 recent build) before remote test commands, since deploy pulls from90 `artifacts\bin\<plat>_<cfg>\`.9192## Choosing test parameters9394When the user doesn't specify parameters, **derive sensible defaults95from the matching job in [.github/workflows/ci.yml](../../workflows/ci.yml)**.96That file is the source of truth for which switches each test exercises97in CI.9899* spinxsk: `tools/spinxsk.ps1 -Verbose -Stats -Driver <XDPMP|FNMP>100 -Minutes <N> -XdpmpPollProvider <NDIS|FNDIS>101 -SuccessThresholdPercent <pct> -EnableEbpf [-TxInspect]`. Scale102 `-Minutes` down for smoke runs as requested.103* functional: mirror `-Config`, `-Platform`, and any104 `-TestCaseFilter` / `-XdpInstaller` flags from the CI job.105* perf/fuzz: copy the CI invocation verbatim, adjusting only106 duration/iteration counts when the user asks for a shorter run.107108Respect explicit user overrides; leave everything else at CI defaults.109110## Kernel debugger (kd) attach for bugcheck monitoring111112Test machines typically have a kd "head" already attached and exposing113a debug server over a named pipe (started on the head with114`.server npipe:pipe=<name>`). To watch for breaks during a test run,115attach a passive **remote** kd client from the dev machine in a116**separate background terminal** before kicking off the test:117118```powershell119# Run with mode=async; <pipe-name> defaults to the same value as120# -ComputerName / Set-XdpRemoteDefault.121kd.exe -remote npipe:server=localhost,pipe=<pipe-name>122```123124### kd usage rules125126* **Always** `-remote npipe:server=localhost,pipe=<name>` — never127 `-k com:pipe,port=...` (that's the serial-pipe transport for VMs and128 will not connect to a kd debug server).129* Default `pipe` to the value of `-ComputerName` /130 `Set-XdpRemoteDefault`. If that fails to connect, ask the user once131 for the correct pipe name.132* Run kd `mode=async` so test invocations stream output in parallel.133* Periodically check the kd terminal for break-in prompts (`kd>` after134 a break) or bugcheck banners; surface them to the user, otherwise135 let kd run idle.136* If kd reports "Cannot connect to server" or exits immediately, no kd137 head is currently attached. Inform the user and continue without kd.138* **Keep kd attached across reboots.** After `.reboot`, leave the kd139 terminal running — secondary breaks (early-boot Driver Verifier hits,140 watchdog timeouts, follow-on bugchecks from corrupted state) often141 happen during the next boot.142* **Issuing `.reboot` non-interactively from the dev machine.** Use143 `kd.exe -cf <scriptfile>` with a hidden window:144 ```powershell145 $cmd = New-TemporaryFile146 Set-Content -Path $cmd -Value ".reboot`r`nqd"147 Start-Process kd.exe -ArgumentList @(148 "-remote","npipe:server=localhost,pipe=<machine>",149 "-cf",$cmd.FullName) -PassThru -WindowStyle Hidden |150 ForEach-Object { $_.WaitForExit(60000) | Out-Null }151 Remove-Item $cmd.FullName -Force152 ```153 Do not try to drive an interactive `kd>` prompt via PowerShell's154 terminal stdin — that connects you to the *host* shell, not kd.155 Then poll `Test-WSMan -ComputerName <machine>` until it succeeds.156* When the chat session ends or the user moves on, kill the kd157 terminal.158159## Bugcheck recovery loop160161Bugchecks frequently leave the test machine in a partially-corrupted162state that prevents the next iteration from running. The recovery163script is `tools\check-drivers.ps1 -Force`. **The dev loop cannot move164on until that command reports "No loaded XDP drivers found!".**165166### Recovery doctrine167168* **Bugchecks can corrupt on-disk and registry state.** Files written169 in the seconds before the crash may be truncated, partially written,170 or filled with NUL bytes. Driver registry keys, INF/catalog files,171 PnP metadata, and Windows Installer product databases are common172 victims.173* **Reinstall over partial state when feasible.** When an174 installer/MSI/INF reports the product is missing from its database175 but the on-disk binaries are still present, *reinstall* on top of176 the existing layout (which reseeds the database) and then run the177 normal uninstall. Prefer this over hand-rolled service/driver178 teardown.179* **Recovery flags MUST be opt-in.** Behaviors that downgrade errors,180 reinstall over corruption, or paper over partial state belong181 behind a `-Force` switch on the recovery script (e.g.182 `tools\check-drivers.ps1 -Force`). Don't change default183 install/uninstall behavior — that would silently mask real bugs.184* **Persist until clean.** Each bugcheck tends to expose a *slightly185 different* corruption (NUL-filled scripts last time, wedged eBPF186 service this time, something else next time). When187 `check-drivers.ps1` fails:188 1. Read the actual error and identify which component / step is189 stuck.190 2. Improve `tools\setup.ps1` (or `tools\check-drivers.ps1`) with a191 **targeted** fix for that specific failure mode — add a stuck192 service to the cleanup list, detect a specific MSI exit code,193 add a NUL-byte check for a specific file path, etc.194 3. Re-run `check-drivers.ps1 -Force` and repeat until it succeeds.195 4. Only then return to the build/test/deploy loop.196197 Do not stop at "the recovery is partially working" — partial198 recovery means the next test run will hit the residual broken199 state and waste an iteration. Avoid broad ignore-everything error200 handling; recovery should be targeted and understandable.201202## Common test commands203204```powershell205# Functional tests206.\tools\prepare-machine.ps1 -ComputerName <machine> -ForFunctionalTest207.\tools\functional.ps1 -ComputerName <machine> -Config Debug -Platform x64208.\tools\functional.ps1 -ComputerName <machine> -TestCaseFilter "Name=GenericBinding"209.\tools\functional.ps1 -ComputerName <machine> -ListTestCases210.\tools\log.ps1 -Convert -Name xdpfunc211212# Stress tests (spinxsk)213.\tools\prepare-machine.ps1 -ComputerName <machine> -ForSpinxskTest214.\tools\spinxsk.ps1 -ComputerName <machine> -XdpmpPollProvider FNDIS -QueueCount 2 -Minutes 10215.\tools\log.ps1 -Convert -Name spinxsk216217# Recovery218.\tools\check-drivers.ps1 -ComputerName <machine> -Force219```220221See [docs/remote-testing.md](../../../docs/remote-testing.md) for the222human-facing reference.