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("\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 + "" + cellTag + ">";
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