├── .gitattributes ├── Download └── DumpReport.zip ├── screenshots ├── envvars.png ├── modules.png └── report.png ├── app.config ├── LICENSE ├── DumpReport.sln ├── Properties └── AssemblyInfo.cs ├── LogProgress.cs ├── DumpReport.csproj ├── Utils.cs ├── .gitignore ├── Resources.cs ├── Program.cs ├── Report.cs ├── Config.cs ├── LogManager.cs ├── README.md └── Parser.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /Download/DumpReport.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xquintana/DumpReport/HEAD/Download/DumpReport.zip -------------------------------------------------------------------------------- /screenshots/envvars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xquintana/DumpReport/HEAD/screenshots/envvars.png -------------------------------------------------------------------------------- /screenshots/modules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xquintana/DumpReport/HEAD/screenshots/modules.png -------------------------------------------------------------------------------- /screenshots/report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xquintana/DumpReport/HEAD/screenshots/report.png -------------------------------------------------------------------------------- /app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2022 Xavier Quintana 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /DumpReport.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.13.35931.197 d17.13 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DumpReport", "DumpReport.csproj", "{D85CAE49-5CC7-45DE-9564-72D7185F7FA6}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {D85CAE49-5CC7-45DE-9564-72D7185F7FA6}.Debug|x64.ActiveCfg = Debug|x64 17 | {D85CAE49-5CC7-45DE-9564-72D7185F7FA6}.Debug|x64.Build.0 = Debug|x64 18 | {D85CAE49-5CC7-45DE-9564-72D7185F7FA6}.Debug|x86.ActiveCfg = Debug|x86 19 | {D85CAE49-5CC7-45DE-9564-72D7185F7FA6}.Debug|x86.Build.0 = Debug|x86 20 | {D85CAE49-5CC7-45DE-9564-72D7185F7FA6}.Release|x64.ActiveCfg = Release|x64 21 | {D85CAE49-5CC7-45DE-9564-72D7185F7FA6}.Release|x64.Build.0 = Release|x64 22 | {D85CAE49-5CC7-45DE-9564-72D7185F7FA6}.Release|x86.ActiveCfg = Release|x86 23 | {D85CAE49-5CC7-45DE-9564-72D7185F7FA6}.Release|x86.Build.0 = Release|x86 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {98D53D9F-A51A-4F3E-8357-A448A29B4639} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("DumpReport")] 9 | [assembly: AssemblyDescription("Generates an HTML report from a user-mode dump.")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("DumpReport")] 13 | [assembly: AssemblyCopyright("Xavier Quintana (MIT License)")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("d85cae49-5cc7-45de-9564-72d7185f7fa6")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.3.0.0")] 36 | [assembly: AssemblyFileVersion("1.3.0.0")] 37 | -------------------------------------------------------------------------------- /LogProgress.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace DumpReport 6 | { 7 | /// 8 | /// Shows the progress of the debugger's log generation in the console. 9 | /// 10 | // The progress of the creation of the debugger's log file is measured by reading the number of bytes of an auxiliar 11 | // file. For example, if the size is 5 and the total number of steps is 10, we know that the progress is 50%. 12 | // This auxiliary file is created by using the command 'writemem', and its size is increased by one byte in every 13 | // progress step. 14 | class LogProgress 15 | { 16 | const string progressMark = "{PROGRESS_STEP}"; 17 | string progressCommand = @".writemem ""{file}"" {reg} L?{step}"; 18 | double maxSteps; 19 | string progressFile; // Auxiliary file used to measure the progress of the creation of the log 20 | 21 | // Inserts the command to measure the progress of the creation of the log 22 | public string PrepareScript(string script, string outFile, bool is32bitDump) 23 | { 24 | // Create a temporary progress file 25 | progressFile = outFile + ".prg"; 26 | File.Delete(progressFile); // Delete any older file 27 | progressFile = progressFile.Replace("\\", "\\\\"); 28 | 29 | // Format the command used to measure the progress 30 | progressCommand = progressCommand.Replace("{reg}", is32bitDump ? "@esp" : "@rsp"); 31 | progressCommand = progressCommand.Replace("{file}", progressFile); 32 | 33 | // Get the number of progress steps in the script 34 | maxSteps = Regex.Matches(script, progressMark).Count; 35 | if (maxSteps == 0) 36 | return script; 37 | 38 | // Separate the script into progress steps 39 | string[] steps = script.Split(new string[] { progressMark }, StringSplitOptions.None); 40 | 41 | // Insert the command used to measure the progress into the main script 42 | int numStep = 1; 43 | string scriptWithProgress = ""; 44 | foreach (string step in steps) 45 | { 46 | scriptWithProgress += step; 47 | if (numStep <= maxSteps) 48 | scriptWithProgress += progressCommand.Replace("{step}", numStep.ToString()); 49 | ++numStep; 50 | } 51 | return scriptWithProgress; 52 | } 53 | 54 | // Removes the progress marks from the debugger's script. 55 | // It must be called when the progress is not enabled. 56 | static public string RemoveProgressMark(string script) 57 | { 58 | return script.Replace(progressMark, String.Empty); 59 | } 60 | 61 | // Shows the progress of the creation of the log file by reading the size of the auxiliary file 62 | public void ShowLogProgress() 63 | { 64 | double progress = 0; 65 | if (File.Exists(progressFile)) 66 | { 67 | // The number of bytes in the file indicates the current progress step 68 | FileInfo fileInfo = new FileInfo(progressFile); 69 | progress = 100.0 * (double)fileInfo.Length / maxSteps; 70 | } 71 | Console.Write("\rCreating log... {0}%", (int)progress); 72 | } 73 | 74 | public void DeleteProgressFile() 75 | { 76 | if (progressFile.Length > 0) 77 | File.Delete(progressFile); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /DumpReport.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {D85CAE49-5CC7-45DE-9564-72D7185F7FA6} 8 | Exe 9 | DumpReport 10 | DumpReport 11 | v4.8 12 | 512 13 | false 14 | publish\ 15 | true 16 | Disk 17 | false 18 | Foreground 19 | 7 20 | Days 21 | false 22 | false 23 | true 24 | 0 25 | 1.0.0.%2a 26 | false 27 | true 28 | 29 | 30 | 31 | AnyCPU 32 | true 33 | full 34 | false 35 | bin\Debug\ 36 | DEBUG;TRACE 37 | prompt 38 | 4 39 | 40 | 41 | AnyCPU 42 | pdbonly 43 | true 44 | bin\Release\ 45 | TRACE 46 | prompt 47 | 4 48 | 49 | 50 | true 51 | bin\x86\Debug\ 52 | DEBUG;TRACE 53 | full 54 | x86 55 | prompt 56 | MinimumRecommendedRules.ruleset 57 | true 58 | 59 | 60 | bin\x86\Release\ 61 | TRACE 62 | true 63 | pdbonly 64 | x86 65 | prompt 66 | MinimumRecommendedRules.ruleset 67 | 68 | 69 | true 70 | bin\x64\Debug\ 71 | DEBUG;TRACE 72 | full 73 | x64 74 | 7.3 75 | prompt 76 | true 77 | 78 | 79 | bin\x64\Release\ 80 | TRACE 81 | true 82 | pdbonly 83 | x64 84 | 7.3 85 | prompt 86 | true 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | False 111 | .NET Framework 3.5 SP1 112 | false 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.IO; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace DumpReport 7 | { 8 | class Utils 9 | { 10 | // Converts a value from hexadecimal string format to unsigned 64-bit integer 11 | public static UInt64 StrHexToUInt64(string hexString) 12 | { 13 | UInt64 result = 0; 14 | try 15 | { 16 | result = UInt64.Parse(hexString.Replace("0x", String.Empty), System.Globalization.NumberStyles.HexNumber); 17 | } 18 | catch (Exception) 19 | { 20 | throw new Exception("Cannot convert address " + hexString); 21 | } 22 | return result; 23 | } 24 | 25 | // Converts a value from unsigned 64-bit integer to hexadecimal string format 26 | public static string UInt64toStringHex(UInt64 value, bool prefix = true) 27 | { 28 | string valueHex = (Program.is32bitDump) ? value.ToString("X8") : value.ToString("X16"); 29 | return prefix ? "0x" + valueHex : valueHex; 30 | } 31 | 32 | // Returns true if the input strings represent the same hexadecimal address 33 | public static bool SameStrAddress(string addr1, string addr2) 34 | { 35 | // Normalize strings 36 | string addr1Norm = addr1.Replace("0x", String.Empty).ToUpper().TrimStart('0'); 37 | string addr2Norm = addr2.Replace("0x", String.Empty).ToUpper().TrimStart('0'); 38 | return addr1Norm == addr2Norm; 39 | } 40 | 41 | // Returns true if the address represented by the input string is not zero 42 | public static bool NotZeroAddress(string addr) 43 | { 44 | return (addr.Replace("0x", String.Empty).TrimStart('0').Length > 0); 45 | } 46 | 47 | // Returns a string with the UTC time, based on the input local time and the current UTC offset. 48 | public static string GetUtcTimeFromLocalTime(DateTime localTime) 49 | { 50 | TimeSpan utcOffset = TimeZone.CurrentTimeZone.GetUtcOffset(localTime); 51 | string sign = ((utcOffset < TimeSpan.Zero) ? "-" : "+"); 52 | string offset = utcOffset.ToString("hh"); 53 | if (utcOffset.Minutes > 0) 54 | offset += ":" + utcOffset.ToString("mm"); 55 | return String.Format($"{localTime.ToString()} (UTC {sign} {offset})"); 56 | } 57 | 58 | // Converts a timestamp string containing a 32-bit hexadecimal value to a human-readable time. 59 | // The timestamp represents the number of seconds since January 1, 1970 UTC, so the output time is also UTC. 60 | // The output is formatted according to the current culture. 61 | public static string GetUtcTimeFromTimestamp(string timestamp) 62 | { 63 | try 64 | { 65 | if (timestamp == null) return String.Empty; 66 | int secondsAfterEpoch = Int32.Parse(timestamp, System.Globalization.NumberStyles.HexNumber); 67 | DateTime epoch = new DateTime(1970, 1, 1); // Unix Epoch 68 | return epoch.AddSeconds(secondsAfterEpoch).ToString(); 69 | } 70 | catch (Exception) 71 | { 72 | return String.Empty; 73 | } 74 | } 75 | 76 | // Returns a string with the dump's creation time formatted with the current culture. 77 | // If the input time contains a UTC offset, the output is normalized to UTC + 0 and '(UTC)' is appended. 78 | // The input is expected to be in the "en-US" culture. Otherwise, it is returned without conversion. 79 | public static string GetNormalizedDumpTime(string debuggerDumpTime) 80 | { 81 | string timeStr = String.Empty; 82 | bool withOffset = false; 83 | string pattern = @"\w+\s+(?\w+)\s+(?[0-9]+)\s+(?[0-9]+):(?[0-9]+):(?[0-9]+).+(?[0-9]{4})"; 84 | MatchCollection matches = Regex.Matches(debuggerDumpTime, pattern); 85 | if (matches.Count == 1) 86 | { 87 | int month = DateTime.ParseExact(matches[0].Groups["month"].Value, "MMM", new CultureInfo("en-US", false)).Month; 88 | int day = Int32.Parse(matches[0].Groups["day"].Value); 89 | int hour = Int32.Parse(matches[0].Groups["hour"].Value); 90 | int min = Int32.Parse(matches[0].Groups["min"].Value); 91 | int sec = Int32.Parse(matches[0].Groups["sec"].Value); 92 | int year = Int32.Parse(matches[0].Groups["year"].Value); 93 | DateTime time = new DateTime(year, month, day, hour, min, sec); 94 | // If it contains a UTC offset, normalize to UTC + 0 95 | pattern = @"\(UTC(?.*)\)"; 96 | matches = Regex.Matches(debuggerDumpTime, pattern); 97 | if (matches.Count == 1) 98 | { 99 | withOffset = true; 100 | string offset = matches[0].Groups["offset"].Value.Replace(" ", String.Empty); 101 | if (offset.Length > 0) 102 | { 103 | pattern = @"(?[+,-])(?[0-9]+):(?[0-9]+)"; 104 | matches = Regex.Matches(offset, pattern); 105 | if (matches.Count == 1) 106 | { 107 | int offsetHour = Int32.Parse(matches[0].Groups["hour"].Value); 108 | int offsetMin = Int32.Parse(matches[0].Groups["min"].Value); 109 | int offsetTotalMin = offsetMin + 60 * offsetHour; 110 | if (matches[0].Groups["sign"].Value == "+") 111 | offsetTotalMin *= -1; 112 | time = time.AddMinutes(offsetTotalMin); 113 | } 114 | } 115 | } 116 | timeStr = time.ToString(); 117 | if (withOffset) { timeStr += " (UTC)"; } 118 | } 119 | return (timeStr.Length > 0) ? timeStr : debuggerDumpTime; 120 | } 121 | 122 | // Returns a full path based on the input path. 123 | // If the input path is null or empty, returns the input. 124 | public static string GetAbsolutePath(string path) 125 | { 126 | if (path != null && path.Length > 0 && !Path.IsPathRooted(path)) 127 | { 128 | path = Path.Combine(Program.appDirectory, path); 129 | path = path.Replace("\\.\\", "\\"); 130 | } 131 | return path; 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | -------------------------------------------------------------------------------- /Resources.cs: -------------------------------------------------------------------------------- 1 | namespace DumpReport 2 | { 3 | class Resources 4 | { 5 | static public string configFile = "DumpReportCfg.xml"; 6 | 7 | #region css 8 | static public string css = @"body { 9 | font-family: verdana, arial, sans-serif; 10 | font-size: 12px; 11 | margin-left: 25px; 12 | } 13 | h1 { 14 | color: DarkBlue; 15 | font-family: verdana, arial, sans-serif; 16 | font-size: 20px; 17 | margin-left: -15px; 18 | margin-bottom: 0px; 19 | } 20 | h2 { 21 | color: DarkBlue; 22 | font-family: verdana, arial, sans-serif; 23 | font-size: 14px; 24 | margin-left: -15px; 25 | } 26 | button { 27 | padding: 2px 10px; 28 | font-family: verdana, arial, sans-serif; 29 | font-size: 12px; 30 | border-radius: 3px; 31 | border: 1px solid #7F7F7F; 32 | } 33 | button:hover { 34 | background-color: DarkGray; 35 | color: white; 36 | } 37 | button:focus { 38 | outline:0; 39 | } 40 | .toggle-button { 41 | padding: 0px 0px; 42 | margin-right: 3px; 43 | font-size: 10px; 44 | font-weight: bold; 45 | height: 15px; 46 | width: 15px; 47 | font-family: 'Courier New', monospace; 48 | text-align: center; 49 | vertical-align: middle; 50 | } 51 | .toggle-header { 52 | margin-bottom: 0; 53 | margin-left: 0; 54 | margin-top: 3px; 55 | padding: 0; 56 | font-size: 12px; 57 | vertical-align: top; 58 | } 59 | .toggle-header td { 60 | margin-bottom: 0; 61 | padding: 0; 62 | font-family: verdana, arial, sans-serif; 63 | vertical-align: top; 64 | } 65 | .report-table { 66 | margin-left: 15px; 67 | margin-top: 0px; 68 | margin-bottom: 0px; 69 | padding: 0.5em; 70 | } 71 | .report-table td { 72 | text-align: left; 73 | padding: 0.3em; 74 | font-size: 11px; 75 | } 76 | .report-table th { 77 | font-size: 12px; 78 | text-align: left; 79 | padding: 0.3em; 80 | background-color: #4f81BD; 81 | color: white; 82 | } 83 | .report-table tr { 84 | font-size: 12px; 85 | height: 1em; 86 | } 87 | .report-table tr:nth-child(even) 88 | { 89 | background-color: #eee; 90 | } 91 | .report-table tr:nth-child(odd) 92 | { 93 | background-color:#fff; 94 | } 95 | .sourcecode-frame { 96 | font-weight: bold; 97 | color: black; 98 | } 99 | .thread-id { 100 | font-family: 'Consolas', 'Courier New', monospace; 101 | font-weight: normal; 102 | } 103 | "; 104 | #endregion 105 | 106 | #region javascript 107 | static public string scripts = @" 108 | function expand(divName, buttonName) { 109 | if (document.getElementById(divName) === null) return; 110 | document.getElementById(divName).style.display = 'block'; 111 | document.getElementById(buttonName).firstChild.data = '-'; 112 | } 113 | function collapse(divName, buttonName) { 114 | if (document.getElementById(divName) === null) return; 115 | document.getElementById(divName).style.display = 'none'; 116 | document.getElementById(buttonName).firstChild.data = '+'; 117 | } 118 | function toggle(divName, buttonName) { 119 | var div = document.getElementById(divName); 120 | if (div === null) return; 121 | if (div.style.display === 'none') { 122 | expand(divName, buttonName); 123 | } 124 | else { 125 | collapse(divName, buttonName); 126 | } 127 | } 128 | function setVisibility(show) { 129 | var divName = ''; 130 | var buttonId = ''; 131 | for (var i = 0; i < numThreads; i++) 132 | { 133 | divName = 'divThread' + i.toString(); 134 | buttonId = 'btThread' + i.toString(); 135 | if (show == true) { 136 | expand(divName, buttonId); 137 | } 138 | else { 139 | collapse(divName, buttonId); 140 | } 141 | } 142 | } 143 | "; 144 | #endregion 145 | 146 | #region xml 147 | static public string xml = @" 148 | 149 | 150 | 151 | ", style)); 124 | } 125 | 126 | // Imports the CSS code from an external file 127 | bool ImportStyle(string path) 128 | { 129 | try 130 | { 131 | string style = File.ReadAllText(path); 132 | WriteStyle(style); 133 | return true; 134 | } 135 | catch (Exception) 136 | { 137 | return false; 138 | } 139 | } 140 | 141 | public void WriteDumpInfo(DumpInfoParser dumpInfo) 142 | { 143 | if (stream == null) return; 144 | 145 | WriteValue("Dump Creation Time", dumpInfo.CreationTime); 146 | 147 | if (dumpInfo.DumpBitness != null) 148 | { 149 | string dumpType = dumpInfo.DumpBitness; 150 | if (dumpInfo.Wow64Found) 151 | dumpType += " (64-bit dump)"; 152 | WriteValue("Dump Architecture", dumpType); 153 | } 154 | 155 | if (dumpInfo.SosLoaded && dumpInfo.ClrVersion != null && dumpInfo.ClrVersion.Length > 0) 156 | WriteValue("CLR Version", dumpInfo.ClrVersion); 157 | else 158 | WriteValue("CLR Version", "Not found"); 159 | } 160 | 161 | public void WriteTargetInfo(TargetInfoParser targetInfo) 162 | { 163 | if (stream == null) return; 164 | 165 | if (targetInfo.CommandLine != null) 166 | WriteValue("Command Line", targetInfo.CommandLine); 167 | if (targetInfo.ProcessId != null) 168 | WriteValue("Process Id", string.Format("{0} ({1})", targetInfo.ProcessId, Utils.StrHexToUInt64(targetInfo.ProcessId))); 169 | if (targetInfo.ComputerName != null) 170 | WriteValue("Computer Name", targetInfo.ComputerName); 171 | if (targetInfo.UserName != null) 172 | WriteValue("User Name", targetInfo.UserName); 173 | 174 | WriteValue("Operating System", targetInfo.OsInfo); 175 | 176 | if (targetInfo.Environment.Keys.Count > 0) 177 | { 178 | Table table = new Table("report-table"); 179 | table.EmphasizeFirstCol = true; 180 | table.AddHeader(new string[] { "Environment Variable", "Value" }); 181 | foreach (string envvar in targetInfo.Environment.Keys) 182 | table.AddRow(new string[] { envvar, targetInfo.Environment[envvar] }); 183 | InsertToggleContent("Environment Variables", table.Serialize()); 184 | } 185 | } 186 | 187 | public void WriteModuleInfo(List modules) 188 | { 189 | if (stream == null) return; 190 | Table table = new Table("report-table"); 191 | table.AddHeader(new string[] { "Start Address", "End Address", "Module Name", "Timestamp", "UTC Time", 192 | "Path", "File Version", "Product Version", "Description", "PDB status", "PDB Path" }); 193 | foreach (ModuleInfo module in modules) 194 | table.AddRow(new string[] { module.startAddr.TrimStart('0'), module.endAddr.TrimStart('0'), module.moduleName, module.timestamp, Utils.GetUtcTimeFromTimestamp(module.timestamp), 195 | module.imagePath, module.fileVersion, module.productVersion, module.fileDescription, module.pdbStatus, module.pdbPath }, "td"); 196 | InsertToggleContent("Loaded Modules", table.Serialize()); 197 | } 198 | 199 | public void WriteNotes(List notes) 200 | { 201 | if (notes.Count > 0) 202 | Write("
Notes:"); 203 | foreach (string note in notes) 204 | Write(note); 205 | } 206 | 207 | public void WriteExceptionInfo(ExceptionInfo exceptionInfo) 208 | { 209 | if (exceptionInfo.description != null && exceptionInfo.description.Length > 0) 210 | WriteValue("Exception", exceptionInfo.description); 211 | if (exceptionInfo.module != null && exceptionInfo.module.Length > 0) 212 | WriteValue("Module", exceptionInfo.module); 213 | if (exceptionInfo.address > 0) 214 | WriteValue("Exception Address", Utils.UInt64toStringHex(exceptionInfo.address)); 215 | if (exceptionInfo.frame != null && exceptionInfo.frame.Length > 0) 216 | WriteValue("Faulting Frame", EscapeSpecialChars(exceptionInfo.frame)); 217 | } 218 | 219 | public void WriteFaultingThreadInfo(ThreadInfo faultThread) 220 | { 221 | List threadList = new List(); 222 | threadList.Add(faultThread); 223 | WriteThreadInfo(threadList, true); 224 | } 225 | 226 | // Receives a list of threads with a common call stack 227 | public void WriteThreadInfo(List threads, bool isFaultThread = false) 228 | { 229 | int counter = 0; 230 | if (stream == null) return; 231 | if (threads.Count == 0) 232 | return; 233 | 234 | // Expand/collapse buttons 235 | string divName = string.Format("divThread{0}{1}", threads[0].threadNum, isFaultThread ? "_fault" : String.Empty); 236 | string buttonId = string.Format("btThread{0}{1}", threads[0].threadNum, isFaultThread ? "_fault" : String.Empty); 237 | string threadLabel = ""; 238 | // Thread Id and instruction pointer 239 | for (int i = 0; i < threads.Count; i++) 240 | { 241 | if (i > 0) threadLabel += "
"; 242 | threadLabel += string.Format("Thread #{0} (id=0x{1}/{2}) {3}:{4}", 243 | threads[i].threadNum, threads[i].threadId, UInt32.Parse(threads[i].threadId, System.Globalization.NumberStyles.HexNumber), 244 | Program.is32bitDump ? "EIP" : "RIP", threads[i].instructPtr); 245 | } 246 | // Thread stack 247 | List stack = threads[0].stack; 248 | Table table = new Table("report-table"); 249 | table.AddHeader(new string[] { "", "Module", "Function", "File", "Line" }); 250 | foreach (FrameInfo frame in stack) 251 | { 252 | table.AddRow(new string[] { counter.ToString(), frame.module, EscapeSpecialChars(frame.function), frame.file, frame.line }, 253 | "td", GetStackFrameStyle(frame.file)); 254 | counter++; 255 | } 256 | InsertToggleContent(threadLabel, table.Serialize(), isFaultThread, buttonId, divName); 257 | } 258 | 259 | public void WriteAllThreadsMenu() 260 | { 261 | if (stream == null) return; 262 | stream.WriteLine("" + 263 | "

"); 264 | } 265 | 266 | // Writes the javascript code at the end of the report 267 | public void WriteJavascript(int numThreads) 268 | { 269 | if (stream == null) return; 270 | stream.WriteLine(string.Format("", numThreads, Resources.scripts)); 271 | } 272 | 273 | // Writes 'content' in an HTML section that can be collapsed or expanded with a button 274 | void InsertToggleContent(string label, string content, bool show = false, string buttonId = null, string divName = null) 275 | { 276 | string id = label.Replace(" ", String.Empty); 277 | string displayStyle = null; 278 | if (buttonId == null) 279 | buttonId = "bt" + id; 280 | if (divName == null) 281 | divName = "div" + id; 282 | displayStyle = show ? "display:initial" : "display:none"; 283 | 284 | string toggleCode = string.Format("
{2}
\n" + 286 | "
", 287 | buttonId, divName, label, displayStyle, show ? "-" : "+"); 288 | stream.WriteLine(toggleCode); 289 | stream.WriteLine(content); 290 | stream.WriteLine("
"); 291 | } 292 | 293 | string GetStackFrameStyle(string file) 294 | { 295 | if (file != null && file.Length > 0 && config.SourceCodeRoot.Length > 0) 296 | if (file.ToUpper().Contains(config.SourceCodeRoot)) 297 | return FRAME_STYLE_SOURCECODE; 298 | return String.Empty; 299 | } 300 | 301 | // Escape special HTML characters 302 | string EscapeSpecialChars(string line) 303 | { 304 | return line.Replace("&", "&"). 305 | Replace("<", "<"). 306 | Replace(">", ">"). 307 | Replace("\"", """). 308 | Replace("'", "'"); 309 | } 310 | 311 | /// 312 | /// Helper class that creates an HTML table 313 | /// 314 | class Table 315 | { 316 | string html; 317 | public bool EmphasizeFirstCol { get; set; } 318 | 319 | public Table(string className = null) 320 | { 321 | string attribClass = String.Empty; 322 | EmphasizeFirstCol = false; 323 | if (className != null) 324 | attribClass = string.Format(" class='{0}'", className); 325 | html = string.Format("\r\n\r\n", attribClass); 326 | } 327 | public void AddHeader(string[] fields) 328 | { 329 | AddRow(fields, "th"); 330 | } 331 | public void AddRow(string[] fields, string cellTag = "td", string style = "") 332 | { 333 | string field; 334 | string styleAttrib = String.Empty; 335 | if (style.Length > 0) 336 | styleAttrib = string.Format(" class='{0}'", style); 337 | html += ""; 338 | for (int i = 0; i < fields.Length; i++) 339 | { 340 | field = fields[i]; 341 | if (i == 0 && EmphasizeFirstCol == true && cellTag == "td") 342 | field = string.Format("{0}", field); 343 | html += "<" + cellTag + ">" + field + ""; 344 | } 345 | html += "\r\n"; 346 | } 347 | public string Serialize(StreamWriter stream = null) 348 | { 349 | html += ""; 350 | if (stream != null) 351 | stream.WriteLine(html); 352 | return html; 353 | } 354 | } 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Xml; 3 | using System.IO; 4 | 5 | namespace DumpReport 6 | { 7 | /// 8 | /// Contains the input parameters, specified from command line and from the XML configuration file. 9 | /// 10 | class Config 11 | { 12 | public string DbgExe64 { get; set; } // Full path of the 64-bit version debugger 13 | public string DbgExe32 { get; set; } // Full path of the 32-bit version debugger 14 | public Int32 DbgTimeout { get; set; } // Maximum number of minutes to wait for the debugger to finish 15 | public string StyleFile { get; set; } // Full path of a custom CSS file to use 16 | public string ReportFile { get; set; } // Full path of the report to be created 17 | public bool ReportShow { get; set; } // If true, the report will be displayed automatically in the default browser 18 | public bool QuietMode { get; set; } // If true. the application will not show progress messages in the console 19 | public string SymbolCache { get; set; } // Folder to use as the debugger's symbol cache 20 | public string DumpFile { get; set; } // Full path of the DMP file 21 | public string PdbFolder { get; set; } // Folder where the PDBs are located 22 | public string LogFile { get; set; } // Full path of the debugger's output file 23 | public string LogFolder { get; set; } // Folder where the debugger's output file is stored 24 | public bool LogClean { get; set; } // If true, log files are deleted after execution 25 | public string SourceCodeRoot { get; set; } // Specifies a root folder for the source files 26 | 27 | public Config() 28 | { 29 | DbgExe64 = String.Empty; 30 | DbgExe32 = String.Empty; 31 | DbgTimeout = 60; 32 | StyleFile = String.Empty; 33 | ReportFile = "DumpReport.html"; 34 | ReportShow = false; 35 | QuietMode = false; 36 | SymbolCache = ""; 37 | DumpFile = String.Empty; 38 | PdbFolder = String.Empty; 39 | LogFolder = String.Empty; 40 | LogFile = String.Empty; 41 | SourceCodeRoot = String.Empty; 42 | } 43 | 44 | // If the user requested for help, displays the help in the console and returns true. 45 | // Otherwise returns false. 46 | public bool CheckHelp(string[] args) 47 | { 48 | if (args.Length == 0 || (args.Length == 1 && args[0] == "/?")) 49 | return PrintAppHelp(); 50 | 51 | for (int idx = 0; idx < args.Length; idx++) 52 | { 53 | if (args[idx] == "/CONFIG") 54 | { 55 | if ((idx + 1 < args.Length) && args[idx + 1] == "HELP") 56 | return PrintConfigHelp(); 57 | if ((idx + 1 < args.Length) && args[idx + 1] == "CREATE") 58 | return CreateConfigFile(); 59 | throw new ArgumentException("Use /CONFIG with HELP or CREATE"); 60 | } 61 | if (args[idx] == "/STYLE") 62 | { 63 | if ((idx + 1 < args.Length) && args[idx + 1] == "HELP") 64 | return PrintStyleHelp(); 65 | if ((idx + 1 < args.Length) && args[idx + 1] == "CREATE") 66 | return CreateCSS(); 67 | throw new ArgumentException("Use /STYLE with HELP or CREATE"); 68 | } 69 | } 70 | return false; 71 | } 72 | 73 | // Reads the parameters from the configuration file 74 | public void ReadConfigFile(string configPath) 75 | { 76 | string value; 77 | 78 | if (!File.Exists(configPath)) 79 | throw new Exception("Configuration file does not exist.\nPlease run 'DumpReport /CONFIG CREATE' to create it."); 80 | try 81 | { 82 | using (XmlReader reader = XmlReader.Create(configPath)) 83 | { 84 | while (reader.Read()) 85 | { 86 | if (reader.IsStartElement()) 87 | { 88 | switch (reader.Name) 89 | { 90 | case "Debugger": 91 | value = reader["exe64"]; 92 | if (value != null && value.Length > 0) 93 | DbgExe64 = value; 94 | value = reader["exe32"]; 95 | if (value != null && value.Length > 0) 96 | DbgExe32 = value; 97 | value = reader["timeout"]; 98 | if (value != null && value.Length > 0) 99 | DbgTimeout = Convert.ToInt32(value); 100 | break; 101 | case "Pdb": 102 | value = reader["folder"]; 103 | if (value != null && value.Length > 0) 104 | PdbFolder = value; 105 | break; 106 | case "Style": 107 | value = reader["file"]; 108 | if (value != null && value.Length > 0) 109 | StyleFile = value; 110 | break; 111 | case "Report": 112 | value = reader["file"]; 113 | if (value != null && value.Length > 0) 114 | ReportFile = value; 115 | value = reader["show"]; 116 | if (value != null && value.Length > 0) 117 | ReportShow = (value == "1"); 118 | break; 119 | case "Log": 120 | value = reader["folder"]; 121 | if (value != null && value.Length > 0) 122 | LogFolder = value; 123 | value = reader["clean"]; 124 | if (value != null && value.Length > 0) 125 | LogClean = Convert.ToInt32(value) == 1; 126 | break; 127 | case "SymbolCache": 128 | value = reader["folder"]; 129 | if (value != null && value.Length > 0) 130 | SymbolCache = value; 131 | break; 132 | case "SourceCodeRoot": 133 | value = reader["folder"]; 134 | if (value != null && value.Length > 0) 135 | SourceCodeRoot = value.ToUpper(); 136 | break; 137 | } 138 | } 139 | } 140 | } 141 | } 142 | catch (Exception) 143 | { 144 | throw new Exception("Configuration file contains errors.\nPlease run 'DumpReport /CONFIG HELP' for XML syntax."); 145 | } 146 | } 147 | 148 | // Reads the parameters from the command-line 149 | public void ReadCommandLine(string[] args) 150 | { 151 | if (args.Length == 1 && args[0][0] != '/') 152 | { 153 | DumpFile = args[0]; 154 | return; 155 | } 156 | try 157 | { 158 | for (int idx = 0; idx < args.Length; idx++) 159 | { 160 | if (args[idx] == "/DUMPFILE") DumpFile = GetParamValue(args, ref idx); 161 | else if (args[idx] == "/PDBFOLDER") PdbFolder = GetParamValue(args, ref idx); 162 | else if (args[idx] == "/REPORTFILE") ReportFile = GetParamValue(args, ref idx); 163 | else if (args[idx] == "/SHOWREPORT") ReportShow = (GetParamValue(args, ref idx) == "1"); 164 | else if (args[idx] == "/QUIET") QuietMode = (GetParamValue(args, ref idx) == "1"); 165 | else throw new ArgumentException("Invalid parameter " + args[idx]); 166 | } 167 | if (DumpFile.Length == 0) 168 | throw new ArgumentException("/DUMPFILE parameter not found"); 169 | } 170 | catch (ArgumentException ex) 171 | { 172 | throw new ArgumentException(ex.Message + "\r\nPlease type 'DumpReport' for help."); 173 | } 174 | } 175 | 176 | // Retrieves the value from the pair '/PARAMETER value' 177 | string GetParamValue(string[] args, ref int idx) 178 | { 179 | if (idx + 1 >= args.Length || args[idx + 1].Length == 0 || args[idx + 1][0] == '/') 180 | throw new ArgumentException("Value not found for parameter " + args[idx]); 181 | return args[++idx]; 182 | } 183 | 184 | public void CheckDebugger(string dbgFullPath, string bitness) 185 | { 186 | if (dbgFullPath.Length > 0) 187 | { 188 | if (!File.Exists(dbgFullPath)) 189 | throw new ArgumentException(String.Format("{0} debugger not found: {1}", bitness, dbgFullPath)); 190 | string debugger = Path.GetFileName(dbgFullPath).ToLower(); 191 | if (debugger != "windbg.exe" && debugger != "cdb.exe") 192 | throw new ArgumentException(String.Format("Wrong {0} debugger ('{1}'). Only 'WinDBG.exe' or 'CDB.exe' are supported.", bitness, debugger)); 193 | } 194 | } 195 | 196 | // Checks that the files and folders exist and sets all paths to absolute paths. 197 | public void CheckArguments() 198 | { 199 | // Check dump file path. 200 | DumpFile = Utils.GetAbsolutePath(DumpFile); 201 | if (Path.GetExtension(DumpFile).ToUpper() != ".DMP") 202 | throw new Exception("Only dump files (*.dmp) are supported."); 203 | if (!File.Exists(DumpFile)) 204 | throw new ArgumentException("Dump file not found: " + DumpFile); 205 | 206 | // Check pdb file path. 207 | if (PdbFolder.Length == 0) 208 | PdbFolder = Path.GetDirectoryName(DumpFile); 209 | else 210 | PdbFolder = Utils.GetAbsolutePath(PdbFolder); 211 | if (!Directory.Exists(PdbFolder)) 212 | throw new ArgumentException("PDB folder not found: " + PdbFolder); 213 | 214 | // Check debugger paths. 215 | DbgExe64 = Utils.GetAbsolutePath(DbgExe64); 216 | DbgExe32 = Utils.GetAbsolutePath(DbgExe32); 217 | if (DbgExe64.Length == 0 && DbgExe32.Length == 0) 218 | throw new ArgumentException("No debuggers specified in the configuration file.\r\nPlease type 'DumpReport /CONFIG HELP' for help."); 219 | if (!Environment.Is64BitOperatingSystem && DbgExe32.Length == 0) 220 | throw new Exception("The attribute 'exe32' must be set on 32-bit computers."); 221 | CheckDebugger(DbgExe64, "64-bit"); 222 | CheckDebugger(DbgExe32, "32-bit"); 223 | 224 | // Check style file. 225 | StyleFile = Utils.GetAbsolutePath(StyleFile); 226 | if (StyleFile.Length > 0 && !File.Exists(StyleFile)) 227 | throw new ArgumentException("Style file (CSS) not found: " + StyleFile); 228 | 229 | // Check log file. 230 | LogFolder = Utils.GetAbsolutePath(LogFolder); 231 | if (LogFolder.Length > 0) 232 | { 233 | if (!Directory.Exists(LogFolder)) 234 | throw new ArgumentException("Invalid log folder " + LogFolder); 235 | LogFile = Path.Combine(LogFolder, Path.GetFileName(DumpFile) + ".log"); 236 | } 237 | else 238 | LogFile = DumpFile + ".log"; 239 | 240 | // Check symbol cache. 241 | SymbolCache = Utils.GetAbsolutePath(SymbolCache); 242 | if (SymbolCache.Length > 0 && !Directory.Exists(SymbolCache)) 243 | throw new ArgumentException("Symbol cache folder not found: " + SymbolCache); 244 | 245 | // Make sure the report file contains a full path. 246 | ReportFile = Utils.GetAbsolutePath(ReportFile); 247 | } 248 | 249 | static void PrintColor(string line, ConsoleColor color) 250 | { 251 | Console.ForegroundColor = color; 252 | Console.WriteLine(line); 253 | Console.ResetColor(); 254 | } 255 | 256 | // Prints the application usage to the console 257 | static bool PrintAppHelp() 258 | { 259 | Console.WriteLine(string.Format(Resources.appHelp, Path.GetFileName(Program.configFile))); 260 | return true; 261 | } 262 | 263 | // Prints the configuration file syntax to the console 264 | static bool PrintConfigHelp() 265 | { 266 | Console.WriteLine(string.Format(Resources.xmlHelpIntro, Path.GetFileName(Program.configFile))); 267 | PrintColor("\r\nSample:\r\n", ConsoleColor.White); 268 | Console.WriteLine(Resources.xml); 269 | PrintColor("Nodes:", ConsoleColor.White); 270 | Console.WriteLine(Resources.xmlHelpNodes); 271 | return true; 272 | } 273 | 274 | // Prints the CSS file syntax to the console 275 | static bool PrintStyleHelp() 276 | { 277 | Console.WriteLine(Resources.cssHelp); 278 | return true; 279 | } 280 | 281 | // Creates an empty configuration file. 282 | static bool CreateConfigFile() 283 | { 284 | string file = Program.configFile; 285 | if (File.Exists(file)) 286 | { 287 | Console.Write("File already exists. Overwrite? [Y/N] > "); 288 | if (Console.ReadKey().Key != ConsoleKey.Y) 289 | return true; 290 | Console.WriteLine(); 291 | } 292 | 293 | using (StreamWriter stream = new StreamWriter(file)) 294 | stream.WriteLine(Resources.xml); 295 | Console.WriteLine("Configuration file created.\nPlease edit the path to the debuggers (WinDBG.exe or CDB.exe)."); 296 | return true; 297 | } 298 | 299 | // Creates a default CSS file (style.css). 300 | static bool CreateCSS() 301 | { 302 | string file = Utils.GetAbsolutePath("style.css"); 303 | if (File.Exists(file)) 304 | { 305 | Console.Write("File " + file + " already exists. Overwrite? [Y/N] > "); 306 | if (Console.ReadKey().Key != ConsoleKey.Y) 307 | return true; 308 | Console.WriteLine(); 309 | } 310 | using (StreamWriter stream = new StreamWriter(file)) 311 | stream.WriteLine(Resources.css); 312 | Console.WriteLine("File " + file + " has been created.\nEdit the