Для создания минидампов в управляемой среде используются возможности библиотеки DbgHelp.dll. Рассмотрим применение ее функционала на примере готового проекта на WinForms.
Прежде всего, заметим, что минидампы должны создаваться при возникновении необработанного исключения. Чтобы, для примера, сгенерировать такое исключение, в процессе выполнения программы будем осуществлять деление на ноль при нажатии пользователем определенной кнопки:
Теперь рассмотрим те механизмы, с помощью которых в проектах обрабатываются исключения, не обработанные кодом разработчика. Нас интересуют два обработчика:
ThreadException обычно обрабатывает необработанные исключения, связанные с потоками UI (например, такие, которые могут возникнуть в событиях WinForms). Все остальные необработанные исключения вызывают остановку программы. Однако, до окончательного заверщения ее работы можно совершить некоторые необходимые нам действия: сохранить данные пользователя, или, как в нашем случае, записать на жесткий диск минидамп. Для этого служит обработчик UnhandledException.
Для того чтобы упростить решение задачи, сделаем так, чтобы все исключения обрабатывались UnhandledException. Для этого перед методом Application.Run(new Form1) в Main() вызовем следующий метод:
С этого момента ThreadException нас не интересует.
Все действия по обработке исключений с помощью DbgHelp инкапсулируем в один класс DumpMaker, у которого будет важный для нас метод с следующей сигнатурой:
Именно этот метод мы зарегистрируем для обработки UnhandledException:
Теперь программа перед тем, как аварийно завершить работу, вызовет наш метод.
Содержимое класса DumpMaker
MINIDUMP_TYPE содержит все типы минидампов, которые мы можем создавать. Каждый тип ассоциирован с определенной константой. Полный список констант можно узнать на сайте.
MINIDUMP_EXCEPTION_INFORMATION — структура, которая будет хранить информацию об исключении, благодаря которому программа завершила свою работу.
Возврещает ID текущего процесса.
Библиотечный метод, непосредственно выполняющий создание дампа и его запись. Он вызывается из метода
Дамп записывается в файл с уникальным названием (FileName), который будет храниться в той же директории, что и exe файл. Перед тем как вызвать MiniDumpWriteDump, инициализируем структуру типа MINIDUMP_EXCEPTION_INFORMATION.
Рассмотрим список параметров MiniDumpWriteDump.
hProcess – дескриптор процесса, для которого генерируется информация
ProcessID – ID процесса, для которого генерируется информация
hFile — дескриптор файла
DumpType — тип дампа (будем использовать MiniDumpNormal)
ExceptionParam — информация об исключении
UserStreamParam — информация, определяемая пользователем. Мы не будем включать ее в дамп и передадим в метод IntPtr.Zero
CallbackParam – информация об обратном вызове. Так же не будем ее использовать.
Метод CreateMiniDump будем вызывать непосредственно из CurrentDomain_UnhandledException, предупредив перед этим пользователя о произошедшем:
Проверим работу программы, запустив собранный exe-файл и нажав на созданную нами кнопку:
Мы получили дамп с расширением .dmp. Для его использования нужно просто открыть файл, и в результате запустится VisualStudio.
В правой части окна появится подобное меню:
Для того, чтобы иметь возможность работать с реальным кодом программы, а не с IL-кодом и трассировкой стека, необходимо указать symbol paths – директории, в которых предположительно хранятся pdb-файлы нашего приложения. Pdb файл содержит отладочные данные и сведения о состоянии проекта. По умолчанию искомые файлы содержатся в одной директорией с исполняемыми файлами.
Далее, нажав на Debug with mixed, запустим процесс отладки. Мы сможем увидеть состояние программы на момент обрушения, а так же предупреждение о возникшем исключении.
1. Stackoverflow
2. Using Crash Minidump
3. Howto: C# Generate dump file on crash
4. Writing Minidumps in C#
А. Федосин
Пишем код, создающий минидампы
Прежде всего, заметим, что минидампы должны создаваться при возникновении необработанного исключения. Чтобы, для примера, сгенерировать такое исключение, в процессе выполнения программы будем осуществлять деление на ноль при нажатии пользователем определенной кнопки:
private void button1_Click_1(object sender, EventArgs e)
{
int a = 3;
int b = 0;
int c = a / b;
}
Теперь рассмотрим те механизмы, с помощью которых в проектах обрабатываются исключения, не обработанные кодом разработчика. Нас интересуют два обработчика:
AppDomain.CurrentDomain.UnhandledException
Application.ThreadException
ThreadException обычно обрабатывает необработанные исключения, связанные с потоками UI (например, такие, которые могут возникнуть в событиях WinForms). Все остальные необработанные исключения вызывают остановку программы. Однако, до окончательного заверщения ее работы можно совершить некоторые необходимые нам действия: сохранить данные пользователя, или, как в нашем случае, записать на жесткий диск минидамп. Для этого служит обработчик UnhandledException.
Для того чтобы упростить решение задачи, сделаем так, чтобы все исключения обрабатывались UnhandledException. Для этого перед методом Application.Run(new Form1) в Main() вызовем следующий метод:
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
С этого момента ThreadException нас не интересует.
Все действия по обработке исключений с помощью DbgHelp инкапсулируем в один класс DumpMaker, у которого будет важный для нас метод с следующей сигнатурой:
public static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
Именно этот метод мы зарегистрируем для обработки UnhandledException:
AppDomain.CurrentDomain.UnhandledException += DumpMaker.CurrentDomain_UnhandledException;
Теперь программа перед тем, как аварийно завершить работу, вызовет наш метод.
Содержимое класса DumpMaker
private static class MINIDUMP_TYPE
{
public const int MiniDumpNormal = 0x00000000;
...
public const int MiniDumpWithCodeSegs = 0x00002000;
}
MINIDUMP_TYPE содержит все типы минидампов, которые мы можем создавать. Каждый тип ассоциирован с определенной константой. Полный список констант можно узнать на сайте.
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct MINIDUMP_EXCEPTION_INFORMATION
{
public uint ThreadId;
public IntPtr ExceptionPointers;
public int ClientPointers;
}
MINIDUMP_EXCEPTION_INFORMATION — структура, которая будет хранить информацию об исключении, благодаря которому программа завершила свою работу.
[DllImport("kernel32.dll")]
static extern uint GetCurrentThreadId();
Возврещает ID текущего процесса.
[DllImport("Dbghelp.dll")]
static extern bool MiniDumpWriteDump(IntPtr hProcess, uint ProcessId, IntPtr hFile, int DumpType, ref MINIDUMP_EXCEPTION_INFORMATION ExceptionParam, IntPtr UserStreamParam, IntPtr CallbackParam);
Библиотечный метод, непосредственно выполняющий создание дампа и его запись. Он вызывается из метода
private static void CreateMiniDump()
{
using (System.Diagnostics.Process process = System.Diagnostics.Process.GetCurrentProcess())
{
string FileName = string.Format(@"CRASH_DUMP_{0}_{1}.dmp", DateTime.Today.ToShortDateString(), DateTime.Now.Ticks);
MINIDUMP_EXCEPTION_INFORMATION Mdinfo = new MINIDUMP_EXCEPTION_INFORMATION();
Mdinfo.ThreadId = GetCurrentThreadId();
Mdinfo.ExceptionPointers = Marshal.GetExceptionPointers();
Mdinfo.ClientPointers = 1;
using (FileStream fs = new FileStream(FileName, FileMode.Create))
{
{
MiniDumpWriteDump(process.Handle,(uint)process.Id, fs.SafeFileHandle.DangerousGetHandle(), MINIDUMP_TYPE.MiniDumpNormal,
ref Mdinfo,
IntPtr.Zero,
IntPtr.Zero);
}
}
}
}
Дамп записывается в файл с уникальным названием (FileName), который будет храниться в той же директории, что и exe файл. Перед тем как вызвать MiniDumpWriteDump, инициализируем структуру типа MINIDUMP_EXCEPTION_INFORMATION.
Рассмотрим список параметров MiniDumpWriteDump.
hProcess – дескриптор процесса, для которого генерируется информация
ProcessID – ID процесса, для которого генерируется информация
hFile — дескриптор файла
DumpType — тип дампа (будем использовать MiniDumpNormal)
ExceptionParam — информация об исключении
UserStreamParam — информация, определяемая пользователем. Мы не будем включать ее в дамп и передадим в метод IntPtr.Zero
CallbackParam – информация об обратном вызове. Так же не будем ее использовать.
Метод CreateMiniDump будем вызывать непосредственно из CurrentDomain_UnhandledException, предупредив перед этим пользователя о произошедшем:
public static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
System.Windows.Forms.MessageBox.Show("Unhandled exception!");
CreateMiniDump();
}
Запускаем программу
Проверим работу программы, запустив собранный exe-файл и нажав на созданную нами кнопку:
Запускаем дамп
Мы получили дамп с расширением .dmp. Для его использования нужно просто открыть файл, и в результате запустится VisualStudio.
В правой части окна появится подобное меню:
Для того, чтобы иметь возможность работать с реальным кодом программы, а не с IL-кодом и трассировкой стека, необходимо указать symbol paths – директории, в которых предположительно хранятся pdb-файлы нашего приложения. Pdb файл содержит отладочные данные и сведения о состоянии проекта. По умолчанию искомые файлы содержатся в одной директорией с исполняемыми файлами.
Далее, нажав на Debug with mixed, запустим процесс отладки. Мы сможем увидеть состояние программы на момент обрушения, а так же предупреждение о возникшем исключении.
Использованные источники
1. Stackoverflow
2. Using Crash Minidump
3. Howto: C# Generate dump file on crash
4. Writing Minidumps in C#
Автор статьи
А. Федосин