WinDbg.psm1
################################################################################ ## ## WinDbg.psm1 ## Automate WinDbg with PowerShell scripting ## ## To use: ## In WinDbg, set up a server: .server tcp:Port=10456 ## ## In PowerShell ## ## Import-Module windbg ## Connect-DbgSession -remote "tcp:Port=10456,Server=<server>" ## Other remote connection strings / protocols work, too. ## ## For local kernel debugging: ## ## Import-Module Windbg -ArgumentList 'C:\<path>\kd.exe' ## Connect-DbgSession -ArgumentList "-kl" ## Invoke-DbgCommand "!process" ## ## Cleaning up: ## Disconnect-DbgSession ## ################################################################################ param($CdbPath = @("C:\Program Files\Debugging Tools for Windows (x86)\cdb.exe","C:\Debuggers\cdb.exe","C:\Program Files (x86)\Windows Kits\*\Debuggers\x64\cdb.exe") ) $SCRIPT:windbgProcess = $null $SCRIPT:currentConnection = $null $SCRIPT:debugee = 0 $env:PATH += ";$psScriptRoot" $MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = { Disconnect-DbgSession $env:PATH = $env:PATH.Replace(";$psScriptRoot", "") } ## Debug a process function New-DbgSession { param( [Parameter(Mandatory)] $Id ) $SCRIPT:debugee = $Id Connect-DbgSession -ArgumentList "-p $Id" } ## Connect to a windbg remote session function Connect-DbgSession { [CmdletBinding(DefaultParameterSetName = "Remote")] param( [Parameter(ParameterSetName = "Remote")] $Remote = "tcp:Port=10456,Server=$($ENV:Computername)", [Parameter(ParameterSetName = "ArgumentList", Mandatory = $true)] $ArgumentList ) if($SCRIPT:currentConnection -and ($SCRIPT:currentConnection -ne $remote)) { throw "Already connected to $remote. Use Disconnect-DbgSession, " + "then connect to another instance." } ## Launch cdb.exe, the command-line version of WinDbg. ## Take control of its input and output streams, which we'll use ## to capture commands and their output. if(-not $SCRIPT:currentConnection) { foreach($realPath in @($cdbPath)) { if($resolved = Get-Command $realPath -ErrorAction Ignore) { $cdbPath = $resolved.Source break } } $processStartInfo = New-Object System.Diagnostics.ProcessStartInfo $processStartInfo.FileName = $cdbPath $processStartInfo.WorkingDirectory = (Get-Location).Path if($PSBoundParameters.ContainsKey("Remote")) { $processStartInfo.Arguments = "-remote $remote" } else { $processStartInfo.Arguments = $argumentList } $processStartInfo.UseShellExecute = $false $processStartInfo.RedirectStandardInput = $true $processStartInfo.RedirectStandardOutput = $true $SCRIPT:windbgProcess = [System.Diagnostics.Process]::Start($processStartInfo) $SCRIPT:currentConnection = $remote if(-not $SCRIPT:windbgProcess) { return } ## Ignore the stuff that was in the session before we ## connected $null = Invoke-DbgCommand "!loadby sos clr" $null = Receive-DbgOutput } } ## Detatch / stop debugging function Disconnect-DbgSession { if($SCRIPT:windbgProcess -and (-not $SCRIPT:windbgProcess.HasExited)) { $SCRIPT:windbgProcess.StandardOutput.Close() $SCRIPT:windbgProcess.StandardInput.Close() $SCRIPT:windbgProcess.Kill() } $SCRIPT:currentConnection = $null $SCRIPT:windbgProcess = $null } ## Stop debugging and kill the process function Stop-DbgSession { if($SCRIPT:windbgProcess -and (-not $SCRIPT:windbgProcess.HasExited)) { $SCRIPT:windbgProcess.StandardOutput.Close() $SCRIPT:windbgProcess.StandardInput.Close() $SCRIPT:windbgProcess.Kill() } $SCRIPT:currentConnection = $null $SCRIPT:windbgProcess = $null if($SCRIPT:debugee) { Stop-Process -Id $SCRIPT:debugee -ErrorAction Ignore } } ## Invoke a command in the connected WinDbg session, and return ## its output function Invoke-DbgCommand { if(-not $SCRIPT:windbgProcess) { throw "Not connected. Use Connect-DbgSession to connect to an " + "instance of WinDbg." } ## Hack - '?' causes a request for keyboard input. Intercept that. if($args -eq "?") { Get-DbgHelp return } $SCRIPT:windbgProcess.StandardInput.WriteLine("$args") $SCRIPT:windbgProcess.StandardInput.Flush() ## The 'g' command restarts the program, so we don't want to wait for our ## output. if(($args -ne "g") -and ($args -ne "♠")) { $output = Receive-DbgOutput ## Handle SOS errors if($output -match 'c0000005 Exception in') { Invoke-DbgCommand ($args -join " ") } else { $output } } } ## Retrieve pending output from the connected WinDbg session function Receive-DbgOutput { ## Add a special tag so that we know the end of the command ## response $sent = "PSWINDBG_COMPLETE_{0:o}" -f [DateTime]::Now $SCRIPT:windbgProcess.StandardInput.WriteLine(".echo $sent") $SCRIPT:windbgProcess.StandardInput.Flush() $received = New-Object System.Text.StringBuilder ## Wait for the response to end while($received.ToString().IndexOf($sent) -lt 0) { $null = $received.AppendLine($SCRIPT:windbgProcess.StandardOutput.ReadLine()) } $output = $received.ToString() ## remove leading prompt if exists if($output -match "^\w+:\w+>") { ## remove "X:YYY> " $output = $output.Replace($matches[0], "") } ## remove trailing output $sentIndex = $output.IndexOf($sent) $lastNewline = $output.LastIndexOf("`r`n", $sentIndex) ## if there are no newlines before the end token, then there was no output if($lastNewline -gt 0) { $results = $output.SubString(0, $lastNewline) -split "`r`n" | Foreach-Object Trim $results } } function Suspend-DbgSession { <# .SYNOPSIS Breaks into the currently running application. #> $member = @' [DllImport("kernel32.dll")] public static extern bool SetConsoleCtrlHandler( IntPtr handlerRoutive, bool add); '@ $type = Add-Type -MemberDefinition $member -Name ConsoleControl -Passthru $null = $type::SetConsoleCtrlHandler([IntPtr]::Zero, $true) } function Resume-DbgSession { <# .SYNOPSIS Resumes an application being debugged #> Invoke-DbgCommand g } function Get-DbgHelp { @' B[C|D|E][<bps>] - clear/disable/enable breakpoint(s) BL - list breakpoints BA <access> <size> <addr> - set processor breakpoint BP <address> - set soft breakpoint D[type][<range>] - dump memory DT [-n|y] [[mod!]name] [[-n|y]fields] [address] [-l list] [-a[]|c|i|o|r[#]|v] - dump using type information DV [<name>] - dump local variables E[type] <address> [<values>] - enter memory values G[H|N] [=<address> [<address>...]] - go K <count> - stacktrace KP <count> - stacktrace with source arguments LM[k|l|u|v] - list modules LN <expr> - list nearest symbols P [=<addr>] [<value>] - step over Q - quit R [[<reg> [= <expr>]]] - view or set registers S[<opts>] <range> <values> - search memory SX [{e|d|i|n} [-c "Cmd1"] [-c2 "Cmd2"] [-h] {Exception|Event|*}] - event filter T [=<address>] [<expr>] - trace into U [<range>] - unassemble version - show debuggee and debugger version X [<*|module>!]<*|symbol> - view symbols ? <expr> - display expression ?? <expr> - display C++ expression $< <filename> - take input from a command file <expr> unary ops: + - not by wo dwo qwo poi hi low binary ops: + - * / mod(%) and(&) xor(^) or(|) comparisons: == (=) < > != operands: number in current radix, public symbol, <reg> <type> : b (byte), w (word), d[s] (doubleword [with symbols]), a (ascii), c (dword and Char), u (unicode), l (list) f (float), D (double), s|S (ascii/unicode string) q (quadword) <pattern> : [(nt | <dll-name>)!]<var-name> (<var-name> can include ? and *) <range> : <address> <address> : <address> L <count> User-mode options: ~ - list threads status ~#s - set default thread | - list processes status |#s - set default process x86 options: DG <selector> - dump selector <reg> : [e]ax, [e]bx, [e]cx, [e]dx, [e]si, [e]di, [e]bp, [e]sp, [e]ip, [e]fl, al, ah, bl, bh, cl, ch, dl, dh, cs, ds, es, fs, gs, ss dr0, dr1, dr2, dr3, dr6, dr7 fpcw, fpsw, fptw, st0-st7, mm0-mm7 xmm0-xmm7 <flag> : iopl, of, df, if, tf, sf, zf, af, pf, cf <addr> : #<16-bit protect-mode [seg:]address>, &<V86-mode [seg:]address> '@ } Set-Alias dbg Invoke-DbgCommand Export-ModuleMember -Alias * Export-ModuleMember -Function * |