source: branches/eraser6/BlackBox/Eraser.Util/BlackBox.cs @ 1388

Revision 1388, 15.9 KB checked in by lowjoel, 5 years ago (diff)

The first version of the BlackBox? class.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2009 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.Text;
25using System.Windows.Forms;
26using System.IO;
27using System.Runtime.InteropServices;
28using Microsoft.Win32.SafeHandles;
29using System.Diagnostics;
30using System.Threading;
31using System.Drawing;
32using System.Drawing.Imaging;
33using System.Reflection;
34
35namespace Eraser.Util
36{
37    /// <summary>
38    /// Handles application exceptions, stores minidumps and uploads them to the
39    /// Eraser server.
40    /// </summary>
41    public class BlackBox
42    {
43        /// <summary>
44        /// Stores DLL references for this class.
45        /// </summary>
46        private static class NativeMethods
47        {
48            /// <summary>
49            /// Writes user-mode minidump information to the specified file.
50            /// </summary>
51            /// <param name="hProcess">A handle to the process for which the information
52            /// is to be generated.</param>
53            /// <param name="ProcessId">The identifier of the process for which the information
54            /// is to be generated.</param>
55            /// <param name="hFile">A handle to the file in which the information is to be
56            /// written.</param>
57            /// <param name="DumpType">The type of information to be generated. This parameter
58            /// can be one or more of the values from the MINIDUMP_TYPE enumeration.</param>
59            /// <param name="ExceptionParam">A pointer to a MiniDumpExceptionInfo structure
60            /// describing the client exception that caused the minidump to be generated.
61            /// If the value of this parameter is NULL, no exception information is included
62            /// in the minidump file.</param>
63            /// <param name="UserStreamParam">Not supported. Use IntPtr.Zero</param>
64            /// <param name="CallbackParam">Not supported. Use IntPtr.Zero</param>
65            /// <returns>If the function succeeds, the return value is true; otherwise, the
66            /// return value is false. To retrieve extended error information, call GetLastError.
67            /// Note that the last error will be an HRESULT value.</returns>
68            [DllImport("dbghelp.dll", SetLastError = true)]
69            [return: MarshalAs(UnmanagedType.Bool)]
70            public static extern bool MiniDumpWriteDump(IntPtr hProcess, uint ProcessId,
71                SafeFileHandle hFile, MiniDumpType DumpType,
72                ref MiniDumpExceptionInfo ExceptionParam, IntPtr UserStreamParam,
73                IntPtr CallbackParam);
74
75            /// <summary>
76            /// Identifies the type of information that will be written to the minidump file
77            /// by the MiniDumpWriteDump function.
78            /// </summary>
79            public enum MiniDumpType
80            {
81                /// <summary>
82                /// Include just the information necessary to capture stack traces for all
83                /// existing threads in a process.
84                /// </summary>
85                MiniDumpNormal = 0x00000000,
86
87                /// <summary>
88                /// Include the data sections from all loaded modules. This results in the
89                /// inclusion of global variables, which can make the minidump file significantly
90                /// larger. For per-module control, use the ModuleWriteDataSeg enumeration
91                /// value from MODULE_WRITE_FLAGS.
92                /// </summary>
93                MiniDumpWithDataSegs = 0x00000001,
94
95                /// <summary>
96                /// Include all accessible memory in the process. The raw memory data is
97                /// included at the end, so that the initial structures can be mapped directly
98                /// without the raw memory information. This option can result in a very large
99                /// file.
100                /// </summary>
101                MiniDumpWithFullMemory = 0x00000002,
102
103                /// <summary>
104                /// Include high-level information about the operating system handles that are
105                /// active when the minidump is made.
106                /// </summary>
107                MiniDumpWithHandleData = 0x00000004,
108
109                /// <summary>
110                /// Stack and backing store memory written to the minidump file should be
111                /// filtered to remove all but the pointer values necessary to reconstruct a
112                /// stack trace. Typically, this removes any private information.
113                /// </summary>
114                MiniDumpFilterMemory = 0x00000008,
115
116                /// <summary>
117                /// Stack and backing store memory should be scanned for pointer references
118                /// to modules in the module list. If a module is referenced by stack or backing
119                /// store memory, the ModuleWriteFlags member of the MINIDUMP_CALLBACK_OUTPUT
120                /// structure is set to ModuleReferencedByMemory.
121                /// </summary>
122                MiniDumpScanMemory = 0x00000010,
123
124                /// <summary>
125                /// Include information from the list of modules that were recently unloaded,
126                /// if this information is maintained by the operating system.
127                /// </summary>
128                MiniDumpWithUnloadedModules = 0x00000020,
129
130                /// <summary>
131                /// Include pages with data referenced by locals or other stack memory.
132                /// This option can increase the size of the minidump file significantly.
133                /// </summary>
134                MiniDumpWithIndirectlyReferencedMemory = 0x00000040,
135
136                /// <summary>
137                /// Filter module paths for information such as user names or important
138                /// directories. This option may prevent the system from locating the image
139                /// file and should be used only in special situations.
140                /// </summary>
141                MiniDumpFilterModulePaths = 0x00000080,
142
143                /// <summary>
144                /// Include complete per-process and per-thread information from the operating
145                /// system.
146                /// </summary>
147                MiniDumpWithProcessThreadData = 0x00000100,
148
149                /// <summary>
150                /// Scan the virtual address space for PAGE_READWRITE memory to be included.
151                /// </summary>
152                MiniDumpWithPrivateReadWriteMemory = 0x00000200,
153
154                /// <summary>
155                /// Reduce the data that is dumped by eliminating memory regions that are not
156                /// essential to meet criteria specified for the dump. This can avoid dumping
157                /// memory that may contain data that is private to the user. However, it is
158                /// not a guarantee that no private information will be present.
159                /// </summary>
160                MiniDumpWithoutOptionalData = 0x00000400,
161
162                /// <summary>
163                /// Include memory region information. For more information, see
164                /// MINIDUMP_MEMORY_INFO_LIST.
165                /// </summary>
166                MiniDumpWithFullMemoryInfo = 0x00000800,
167
168                /// <summary>
169                /// Include thread state information. For more information, see
170                /// MINIDUMP_THREAD_INFO_LIST.
171                /// </summary>
172                MiniDumpWithThreadInfo = 0x00001000,
173
174                /// <summary>
175                /// Include all code and code-related sections from loaded modules to capture
176                /// executable content. For per-module control, use the ModuleWriteCodeSegs
177                /// enumeration value from MODULE_WRITE_FLAGS.
178                /// </summary>
179                MiniDumpWithCodeSegs = 0x00002000,
180
181                /// <summary>
182                /// Turns off secondary auxiliary-supported memory gathering.
183                /// </summary>
184                MiniDumpWithoutAuxiliaryState = 0x00004000,
185
186                /// <summary>
187                /// Requests that auxiliary data providers include their state in the dump
188                /// image; the state data that is included is provider dependent. This option
189                /// can result in a large dump image.
190                /// </summary>
191                MiniDumpWithFullAuxiliaryState = 0x00008000,
192
193                /// <summary>
194                /// Scans the virtual address space for PAGE_WRITECOPY memory to be included.
195                /// </summary>
196                MiniDumpWithPrivateWriteCopyMemory = 0x00010000,
197
198                /// <summary>
199                /// If you specify MiniDumpWithFullMemory, the MiniDumpWriteDump function will
200                /// fail if the function cannot read the memory regions; however, if you include
201                /// MiniDumpIgnoreInaccessibleMemory, the MiniDumpWriteDump function will
202                /// ignore the memory read failures and continue to generate the dump. Note that
203                /// the inaccessible memory regions are not included in the dump.
204                /// </summary>
205                MiniDumpIgnoreInaccessibleMemory = 0x00020000,
206
207                /// <summary>
208                /// Adds security token related data. This will make the "!token" extension work
209                /// when processing a user-mode dump.
210                /// </summary>
211                MiniDumpWithTokenInformation = 0x00040000
212            }
213
214            /// <summary>
215            /// Contains the exception information written to the minidump file by the
216            /// MiniDumpWriteDump function.
217            /// </summary>
218            [StructLayout(LayoutKind.Sequential, Pack = 4)]
219            public struct MiniDumpExceptionInfo
220            {
221                /// <summary>
222                /// The identifier of the thread throwing the exception.
223                /// </summary>
224                public uint ThreadId;
225
226                /// <summary>
227                ///  A pointer to an EXCEPTION_POINTERS structure specifying a
228                ///  computer-independent description of the exception and the processor
229                ///  context at the time of the exception.
230                /// </summary>
231                public IntPtr ExceptionPointers;
232
233                /// <summary>
234                /// Determines where to get the memory regions pointed to by the
235                /// ExceptionPointers member. Set to TRUE if the memory resides in the
236                /// process being debugged (the target process of the debugger). Otherwise,
237                /// set to FALSE if the memory resides in the address space of the calling
238                /// program (the debugger process). If you are accessing local memory (in
239                /// the calling process) you should not set this member to TRUE.
240                /// </summary>
241                [MarshalAs(UnmanagedType.Bool)]
242                public bool ClientPointers;
243            }
244        }
245
246        /// <summary>
247        /// Initialises the BlackBox handler. Call this initialiser once throughout
248        /// the lifespan of the application.
249        /// </summary>
250        /// <returns>The global BlackBox instance.</returns>
251        public static BlackBox Get()
252        {
253            if (Instance == null)
254                Instance = new BlackBox();
255            return Instance;
256        }
257
258        /// <summary>
259        /// Enumerates the list of crash dumps waiting for upload.
260        /// </summary>
261        /// <returns>A string array containing the list of dumps waiting for upload.</returns>
262        public string[] GetDumps()
263        {
264            DirectoryInfo dirInfo = new DirectoryInfo(CrashReportsPath);
265            List<string> result = new List<string>();
266            foreach (DirectoryInfo subDir in dirInfo.GetDirectories())
267                result.Add(subDir.Name);
268
269            return result.ToArray();
270        }
271
272        /// <summary>
273        /// Constructor. Use the <see cref="Initialise"/> function to use this class.
274        /// </summary>
275        private BlackBox()
276        {
277            AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
278            Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
279            Application.Idle += OnApplicationIdle;
280        }
281
282        /// <summary>
283        /// Called when the application has reached the idle state. This is used to
284        /// process and upload crash reports to the Eraser server.
285        /// </summary>
286        private void OnApplicationIdle(object sender, EventArgs e)
287        {
288            Application.Idle -= OnApplicationIdle;
289        }
290
291        /// <summary>
292        /// Called when an unhandled exception is raised in the application.
293        /// </summary>
294        private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
295        {
296            //Generate a unique identifier for this report.
297            string crashName = DateTime.Now.ToString();
298            crashName = crashName.Replace('/', '-').Replace(":", "");
299            string currentCrashReport = Path.Combine(CrashReportsPath, crashName);
300            Directory.CreateDirectory(currentCrashReport);
301
302            //Write a memory dump to the folder
303            Exception exception = (e.ExceptionObject as Exception);
304            WriteMemoryDump(currentCrashReport, exception);
305
306            //Then write a user-readable summary
307            WriteDebugLog(currentCrashReport, exception);
308
309            //Take a screenshot
310            WriteScreenshot(currentCrashReport);
311        }
312
313        /// <summary>
314        /// Dumps the contents of memory to a dumpfile.
315        /// </summary>
316        /// <param name="dumpFolder">Path to the folder to store the dump file.</param>
317        /// <param name="e">The exception which is being handled.</param>
318        private void WriteMemoryDump(string dumpFolder, Exception e)
319        {
320            //Open a file stream
321            using (FileStream stream = new FileStream(Path.Combine(dumpFolder, "Memory.dmp"),
322                FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))
323            {
324                //Store the exception information
325                NativeMethods.MiniDumpExceptionInfo exception =
326                    new NativeMethods.MiniDumpExceptionInfo();
327                exception.ClientPointers = false;
328                exception.ExceptionPointers = Marshal.GetExceptionPointers();
329                exception.ThreadId = (uint)AppDomain.GetCurrentThreadId();
330
331                NativeMethods.MiniDumpWriteDump(Process.GetCurrentProcess().Handle,
332                    (uint)Process.GetCurrentProcess().Id, stream.SafeFileHandle,
333                    NativeMethods.MiniDumpType.MiniDumpWithFullMemory,
334                    ref exception, IntPtr.Zero, IntPtr.Zero);
335            }
336        }
337
338        /// <summary>
339        /// Writes a debug log to the given directory.
340        /// </summary>
341        /// <param name="screenshotPath">The path to store the screenshot into.</param>
342        /// <param name="exception">The exception to log about.</param>
343        private void WriteDebugLog(string dumpFolder, Exception exception)
344        {
345            using (FileStream file = new FileStream(Path.Combine(dumpFolder, "Debug.log"),
346                FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))
347            using (StreamWriter stream = new StreamWriter(file))
348            {
349                //Application information
350                string separator = new string('-', 76);
351                string lineFormat = "{0,15}: {1}";
352                stream.WriteLine("Application Information");
353                stream.WriteLine(separator);
354                stream.WriteLine(string.Format(lineFormat, "Version",
355                    Assembly.GetEntryAssembly().GetName().Version));
356                StringBuilder commandLine = new StringBuilder();
357                foreach (string param in Environment.GetCommandLineArgs())
358                {
359                    commandLine.Append(param);
360                    commandLine.Append(' ');
361                }
362                stream.WriteLine(string.Format(lineFormat, "Command Line",
363                    commandLine.ToString().Trim()));
364
365                //Exception Information
366                stream.WriteLine();
367                stream.WriteLine("Exception Information (Outermost to innermost)");
368                stream.WriteLine(separator);
369
370                Exception currentException = exception;
371                for (uint i = 1; currentException != null; ++i)
372                {
373                    stream.WriteLine(string.Format("Exception {0}:", i));
374                    stream.WriteLine(string.Format(lineFormat, "Message", currentException.Message));
375                    stream.WriteLine(string.Format(lineFormat, "Exception Type",
376                        currentException.GetType().Name));
377
378                    //Parse the stack trace
379                    string[] stackTrace = currentException.StackTrace.Split(new char[] { '\n' });
380                    for (uint j = 0; j < stackTrace.Length; ++j)
381                        stream.WriteLine(string.Format(lineFormat,
382                            string.Format("Stack Trace [{0}]", j), stackTrace[j].Trim()));
383
384                    uint k = 0;
385                    foreach (System.Collections.DictionaryEntry value in currentException.Data)
386                        stream.WriteLine(string.Format(lineFormat, string.Format("Data[{0}]", ++k),
387                            string.Format("{0} {1}", value.Key.ToString(), value.Value.ToString())));
388
389                    //End the exception and get the inner exception.
390                    stream.WriteLine();
391                    currentException = exception.InnerException;
392                }
393            }
394        }
395
396        /// <summary>
397        /// Writes a screenshot to the given directory
398        /// </summary>
399        /// <param name="dumpFolder">The path to save the screenshot to.</param>
400        private void WriteScreenshot(string dumpFolder)
401        {
402            //Get the size of the screen
403            Rectangle rect = new Rectangle(int.MaxValue, int.MaxValue, int.MinValue, int.MinValue);
404            foreach (Screen screen in Screen.AllScreens)
405                rect = Rectangle.Union(rect, screen.Bounds);
406
407            //Copy a screen DC to the screenshot bitmap
408            Bitmap screenShot = new Bitmap(rect.Width, rect.Height);
409            Graphics bitmap = Graphics.FromImage(screenShot);
410            bitmap.CopyFromScreen(0, 0, 0, 0, rect.Size, CopyPixelOperation.SourceCopy);
411
412            //Save the bitmap to disk
413            screenShot.Save(Path.Combine(dumpFolder, "Screenshot.png"), ImageFormat.Png);
414        }
415
416        /// <summary>
417        /// The global BlackBox instance.
418        /// </summary>
419        private static BlackBox Instance;
420
421        /// <summary>
422        /// The path to all Eraser crash reports.
423        /// </summary>
424        private readonly string CrashReportsPath = Path.Combine(Environment.GetFolderPath(
425            Environment.SpecialFolder.LocalApplicationData), @"Eraser 6\Crash Reports");
426    }
427}
Note: See TracBrowser for help on using the repository browser.