Skip to content

Instantly share code, notes, and snippets.

@MarkKharitonov
Last active March 11, 2020 08:46
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MarkKharitonov/91698caf8c695902eacde2b6c7825bf1 to your computer and use it in GitHub Desktop.
Save MarkKharitonov/91698caf8c695902eacde2b6c7825bf1 to your computer and use it in GitHub Desktop.
A Powershell script to automate CDB.exe - the debugger engine of WinDBG
<#
.SYNOPSIS
Runs a WinDBG command or a script file.
.DESCRIPTION
Simetimes a WinDBG command produces a lot of output that we may not wish to see in the GUI. Sometimes we would like to run a complex post-processing on
a WinDBG command output. These are two examples of cases when scripting WinDBG and redirecting the output into a file is what we want to do and this
script is a way to do it.
WinDBG and CDB are two front-ends for the same debugger. The difference is the user interface - WinDBG is a GUI whereas CDB is console based.
As such, CDB lends itself naturally to scripting and so this script uses it.
The script has many parameters, but all of them can be saved in config files, so the actual command line becomes very small.
Any parameter can be given on the command line and/or through some config file. 4 different kinds of config files are recognized:
1. The user config file.
This is the file runcdb.config.ps1 found in the user profile directory.
For example, on my machine it is C:\Users\mkharitonov\runcdb.config.ps1
2. The default config file.
This is the file runcdb.config.ps1 found besides the runcdb.ps1 script.
I do not have it on my machine.
3. The current config file.
This is the file runcdb.config.ps1 found in the current working directory.
For example, I work with a dump located in "d:\tmp\cantestr52 - 06-09-2017" and this is where I run this script from.
So, I also have the current config file - "d:\tmp\cantestr52 - 06-09-2017\runcdb.config.ps1"
4. The temporary config file.
The path to the config file is given in the ConfigFile parameter of the script.
The 4 config files are evaluated successively one after another in the aforementioned order. So, for example, if the same parameter appears both
in the user and in the current config files, then the value from the latter overrides the value from the former.
Finally, any parameters given on the command line override the same parameters found in the config files.
There are roughly three kinds of parameters:
- Fixed per machine
For example, the CDB parameter. Its value is determined by the location of the CDB debugger, which is fixed for the particular machine.
It makes perfect sense to place it in the user or the default config file. I am the only user on my machine, so I have it in the user config file.
- Fixed per dump
For example, the ImagePath parameter. It does not change during the analysis of the same dump.
Perfect for the current config file. Place it in the folder of the dump and always run the script from that folder (as long as you are analysing
that same dump, of course).
- Not fixed
For example, the Command parameter. It is likely to be different each time the script is run.
This one should be given on the command line.
At the end, all the parameters, except the following end up in one of the config files:
- Command
- NoEcho
- ScriptFile
- Init
- NoExit
I rarely use the last two. The ScriptFile is also rare. Most of the time my command line looks like this:
runcdb.ps1 -Command "..." -NoEcho
where ... denotes some WinDBG command
The WinDBG commands executed by the script can be divided into two kinds:
1. The initialization:
a. .imgscan /l (if !$NoScanForImages)
b. load $Sosex
c. !lhi (when -Command or -ScriptFile is given)
2. The given command/script
Either the given command or the given script
The output of both kinds is redirected to a file. The name of the log file:
- If -Command is given, then the log file name is the Command text stripped of the following characters - :*><"
The spaces in the command text are replaced with '_'
- If -ScriptFile is given, then the name of the Script file.
- If -Init is given the log file is some temporary file name
The initialization phase outputs to a log file with the same name and extension "init". When the command/script is run, the log file name would be
modeled after the command text or the script file name. The log files are created in the TEMP directory.
.PARAMETER Dump
[Fixed per dump] The path to the dump file.
.PARAMETER Command
A WinDBG command to be run against the dump.
.PARAMETER ScriptFile
A script file with WinDBG commands to be run against the dump.
.PARAMETER NoEcho
A switch indicating to NOT to echo the output of the given command/script to the console.
This is a very useful flag for commands/scripts that generate a lot of output.
.PARAMETER Init
A switch requesting to build the SOSEX heap index file.
I usually build it from within the WinDBG GUI, so I typically do not use this parameter.
Should be used before Command and ScriptFile, which assume the heap index file already exists.
.PARAMETER ImagePath
[Fixed per dump] The path to the application binaries and their respective PDBs.
.PARAMETER Sosex
[Fixed per machine] The path to the sosex.dll
.PARAMETER CDB
[Fixed per machine] The path to the cdb.exe
.PARAMETER SymbolsCache,
[Fixed per machine] The path to the local symbol cache directory
.PARAMETER ConfigFile
The path to the temporary config file
.PARAMETER NoScanForImages
[Fixed per dump] A switch indicating that .imgscan /l should NOT be called
.PARAMETER NoSymbolServer
[Fixed per dump] A switch indicating that Microsoft Symbol Server should NOT be used
.PARAMETER WithDotPeek
[Fixed per dump] A switch indicating that JetBrains Dot Peek Symbol Server should be used
.PARAMETER NoExit
A switch indicating to CDB to continue running after the given command/script are done
.NOTES
The default is to scan for images using the .imgscan /l. One has to check first with the WinDBG GUI to see if this is necessary, because this is a
time consuming operation.
.EXAMPLE
cd "D:\tmp\cantestr52 - 06-09-2017"
#############
# The User Config File
#############
PS D:\tmp\cantestr52 - 06-09-2017> cat C:\Users\mkharitonov\runcdb.config.ps1
$Sosex = "E:\Utils\sosex\64\sosex.dll"
$CDB = "e:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe"
$SymbolsCache = "E:\Symbols"
.EXAMPLE
cd "D:\tmp\cantestr52 - 06-09-2017"
#############
# The Current Config File
#############
PS D:\tmp\cantestr52 - 06-09-2017> cat .\runcdb.config.ps1
$dump = "d:\tmp\cantestr52 - 06-09-2017\Quartz.Server.DMP"
$ImagePath = "d:\tmp\cantestr52 - 06-09-2017\BJE"
$NoScanForImages = $false
.EXAMPLE
cd "D:\tmp\cantestr52 - 06-09-2017"
#############
# Dumping the addresses of all the live objects having the MT address of 00007fff11d90f78 (corresponds to List<WorkflowNodeData>).
# The actual output lists 48 addresses, but only the first and the last ones are included in this help screen.
#############
PS D:\tmp\cantestr52 - 06-09-2017> runcdb.ps1 -Command "!dumpheap -mt 00007fff11d90f78 -live"
C:\Users\mkharitonov\runcdb.config.ps1
.\runcdb.config.ps1
Sosex = E:\Utils\sosex\64\sosex.dll
CDB = e:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe
SymbolsCache = E:\Symbols
ImagePath = d:\tmp\cantestr52 - 06-09-2017\BJE
Dump = d:\tmp\cantestr52 - 06-09-2017\Quartz.Server.DMP
NoScanForImages = False
Log = c:\Users\MKHARI~1\AppData\Local\Temp\!dumpheap_-mt_00007fff11d90f78_-live.log
Opened log file 'c:\Users\MKHARI~1\AppData\Local\Temp\!dumpheap_-mt_00007fff11d90f78_-live.log'
0:000> !dumpheap -mt 00007fff11d90f78 -live
Address MT Size
000000934b37e318 00007fff11d90f78 40
...
00000097e4f6b7e0 00007fff11d90f78 40
Statistics:
MT Count TotalSize Class Name
00007fff11d90f78 48 1920 System.Collections.Generic.List`1[[SharpTop.DataServices.Platform.WorkflowNodeData, Dayforce.Data]]
Total 48 objects
0:000> .logclose
Closing open log file c:\Users\MKHARI~1\AppData\Local\Temp\!dumpheap_-mt_00007fff11d90f78_-live.log
#############
The script produced two log files:
c:\Users\MKHARI~1\AppData\Local\Temp\!dumpheap_-mt_00007fff11d90f78_-live.log.init
c:\Users\MKHARI~1\AppData\Local\Temp\!dumpheap_-mt_00007fff11d90f78_-live.log
Here is the WinDBG code executed by the script:
| .logopen "c:\Users\MKHARI~1\AppData\Local\Temp\!dumpheap_-mt_00007fff11d90f78_-live.log.init"
| .imgscan /l
| .load E:\Utils\sosex\64\sosex.dll
| !lhi
| .logclose
| .logopen "c:\Users\MKHARI~1\AppData\Local\Temp\!dumpheap_-mt_00007fff11d90f78_-live.log"
| !dumpheap -mt 00007fff11d90f78 -live
| .logclose
| q
#>
param(
[Parameter(Position = 0)][ValidateScript({Test-Path $_ -PathType Leaf})]$Dump,
[Parameter(ParameterSetName='cmd', Position = 1)][ValidateNotNullOrEmpty()]$Command,
[Parameter(ParameterSetName='script', Mandatory=$true)][ValidateScript({Test-Path $_ -PathType Leaf})]$ScriptFile,
[Parameter(ParameterSetName='init', Mandatory=$true)][ValidateNotNullOrEmpty()][switch]$Init,
[Parameter()][ValidateScript({Test-Path $_ -PathType Container})]$ImagePath,
[Parameter()][ValidateScript({Test-Path $_ -PathType Leaf})]$Sosex,
[Parameter()][ValidateScript({Test-Path $_ -PathType Leaf})]$CDB,
[Parameter()][ValidateScript({Test-Path $_ -PathType Container})]$SymbolsCache,
[Parameter()][ValidateScript({Test-Path $_ -PathType Leaf})]$ConfigFile,
[Parameter()][switch]$NoScanForImages,
[Parameter()][switch]$NoSymbolServer,
[Parameter()][switch]$WithDotPeek,
[Parameter()][switch]$NoExit,
[Parameter()][switch]$NoEcho
)
if (Test-Path "$env:USERPROFILE\runcdb.config.ps1")
{
Write-Host -ForegroundColor Green "$env:USERPROFILE\runcdb.config.ps1"
. $env:USERPROFILE\runcdb.config.ps1
}
if (Test-Path "$PSScriptRoot\runcdb.config.ps1")
{
Write-Host -ForegroundColor Green "$PSScriptRoot\runcdb.config.ps1"
. $PSScriptRoot\runcdb.config.ps1
}
if (Test-Path ".\runcdb.config.ps1")
{
Write-Host -ForegroundColor Green ".\runcdb.config.ps1"
. .\runcdb.config.ps1
}
if ($ConfigFile)
{
Write-Host -ForegroundColor Green $ConfigFile
. $ConfigFile
}
$PSBoundParameters.GetEnumerator() |% { Set-Variable -Name $_.Key -Value $_.Value }
"Sosex = $Sosex"
"CDB = $CDB"
"SymbolsCache = $SymbolsCache"
"ImagePath = $ImagePath"
"Dump = $Dump"
"NoScanForImages = $NoScanForImages"
$Dump = (dir $Dump).FullName
$Sosex = (dir $Sosex).FullName
if ($command)
{
$Log = "$env:temp\$(($Command -replace '[:*><\"]','') -replace '[ \\]','_').log"
}
elseif ($ScriptFile)
{
$Log = "$env:temp\$((dir $ScriptFile).BaseName).log"
}
else
{
$Log = [io.path]::GetTempFileName()
}
if ($ScriptFile -and !$Command -and (cat $ScriptFile | sls -Quiet '{'))
{
$ScriptFile = (dir $ScriptFile).FullName
$Command = "`$`$`>`<`"$ScriptFile`""
}
$InternalScriptFile = [io.path]::GetTempFileName()
if (!$NoScanForImages)
{
$ImgScan = ".imgscan /l"
}
Set-Content $InternalScriptFile @"
.logopen `"${Log}.init`"
$ImgScan
.load $Sosex
"@
if ($Init)
{
$CDBOutputDevice = "Out-Default"
Add-Content $InternalScriptFile @"
.logclose
!bhi
"@
}
elseif ($Command)
{
$CDBOutputDevice = "Out-Null"
Add-Content $InternalScriptFile @"
!lhi
.logclose
.logopen `"${Log}`"
$Command
.logclose
"@
}
else
{
$CDBOutputDevice = "Out-Null"
Add-Content $InternalScriptFile @"
!lhi
.logclose
.logopen `"${Log}`"
"@
Get-Content $ScriptFile | Out-File -FilePath $InternalScriptFile -Encoding ASCII -Append
Add-Content $InternalScriptFile @"
.logclose
"@
}
if ($NoExit)
{
$CDBOutputDevice = "Out-Default"
}
else
{
Add-Content $InternalScriptFile @"
q
"@
}
$DayforceCacheUNC = "\\devstatic.dayforce.com\symbols"
$DotPeekUrl = "http://localhost:33417"
$MSFTSymbolsUrl = "http://msdl.microsoft.com/download/symbols"
if ($NoSymbolServer)
{
$symbols = $SymbolsCache
}
elseif ($WithDotPeek)
{
$symbols = "cache*${symbolsCache};srv*${DayforceCacheUNC}*${DotPeekUrl};srv*${DayforceCacheUNC}*${MSFTSymbolsUrl}"
}
else
{
$symbols = "cache*${symbolsCache};srv*${DayforceCacheUNC}*${MSFTSymbolsUrl}"
}
if ($ImagePath -and (Test-Path $ImagePath -PathType Container))
{
$symbols = "$ImagePath;$symbols"
}
"Log = $log"
&$CDB -cf $InternalScriptFile -z $Dump -y $symbols | &$CDBOutputDevice
if (!$Init -and !$NoExit -and !$NoEcho)
{
# The name of the log is output as part of the .logclose command
cat $Log
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment