2010-10-23 16:00:24 UTC
Время от времени, неважно сколько усилий потрачено на отладку и тестирование, программы все равно падают. Причем, падают не в тестовой среде или на компьютере разработчика, а обязательно у конечного пользователя. При этом неплохо бы узнать, из какого места программы произошел сбой. Вот тут то на помошь и приходит Windows Debugging API, а точнее библиотека Dbghlp, с помощью которой возможно создание минидампов.
Что такое минидамп? Как правило, те кто читают это, уже знают что это такое, но все равно я повторюсь :). Минидамп это бинарный файл с информацией о состоянии программы на определенный момент времени, в нашем случае на момент падения. В нашем случае в этом минидампе будет состояние регистров процессора, стек вызовов в программе приведший к её падению, а также код ошибки и причина падения.
В DbgHlp есть такая замечательная функция MiniDumpWriteDump, позволяющая записывать дамп программы в файл. Далее, представлено готовое решение (код), создающий с помощью этой функции дамп программы. Код на чистом Си, накакого C++ (но разумеется в прогаммах на C++ этот код можно легко использовать). Итак сначала заголовочный файл DebugHelplers.h
):
#ifndef PRG_DEBUGHELPERS_H_ #define PRG_DEBUGHELPERS_H_ #ifdef __cplusplus extern "C" { #endif #include <windows.h> #include <Dbghelp.h> typedef BOOL (WINAPI * MINIDUMPWRITEDUMP) (HANDLE, DWORD, HANDLE, MINIDUMP_TYPE, PMINIDUMP_EXCEPTION_INFORMATION, PMINIDUMP_USER_STREAM_INFORMATION, PMINIDUMP_CALLBACK_INFORMATION); /*! * \brief Application top level exception handler that creates (if it's possible) core dump * @param pExceptionInfo pointer to exception information */ LONG WINAPI TopLevelFilter(struct _EXCEPTION_POINTERS* pExceptionInfo); #ifdef __cplusplus } #endif #endif // PRG_DEBUGHELPERS_H_
А теперь реализация DebugHelplers.c
:
#include "targetver.h" #include <stdio.h> #include "DebugHelplers.h" #define DBG_HELP_DLL "DBGHELP.DLL" #define DUMP_FILE_NAME PROGRAM_NAME ".exe.dmp" #define DUMP_FUNCTION "MiniDumpWriteDump" #define UNHANDLED_EXCEPTION_OCCURED " An unhandled exception occured. " void PrintWin32Error(const char* message); LONG WINAPI TopLevelFilter(struct _EXCEPTION_POINTERS* pExceptionInfo) { LONG result = EXCEPTION_CONTINUE_SEARCH; // finalize process in standart way by default HMODULE hDll = NULL; MINIDUMP_EXCEPTION_INFORMATION exInfo = { 0 }; BOOL isOK = FALSE; MINIDUMPWRITEDUMP pfnDump = NULL; HANDLE hFile = NULL; hDll = LoadLibraryA(DBG_HELP_DLL); if (hDll == NULL) { PrintWin32Error(" Cannot load dll " DBG_HELP_DLL); return result; } // get func address pfnDump = (MINIDUMPWRITEDUMP)GetProcAddress(hDll, DUMP_FUNCTION); if (!pfnDump) { PrintWin32Error(" Cannot get address of " DUMP_FUNCTION " function"); return result; } hFile = CreateFileA(DUMP_FILE_NAME, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { PrintWin32Error(UNHANDLED_EXCEPTION_OCCURED "Error on creating dump file: " DUMP_FILE_NAME); return result; } exInfo.ThreadId = GetCurrentThreadId(); exInfo.ExceptionPointers = pExceptionInfo; exInfo.ClientPointers = 0; // Write pDumpFile isOK = pfnDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &exInfo, NULL, NULL); if (isOK) { printf_s(UNHANDLED_EXCEPTION_OCCURED "Dump saved to: %s", DUMP_FILE_NAME); result = EXCEPTION_EXECUTE_HANDLER; } else { PrintWin32Error(UNHANDLED_EXCEPTION_OCCURED "Error saving dump file: " DUMP_FILE_NAME); } CloseHandle(hFile); return result; } void PrintWin32Error(const char* message) { DWORD errorCode = 0; void* buffer = NULL; __try { errorCode = GetLastError(); FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (char*)&buffer, 0, NULL); printf_s("%s. Windows error %#x: %s", message, errorCode, (char*)buffer); } __finally { if (buffer != NULL) { LocalFree(buffer); } } }
Обратите внимание, в файле реализации, в строке 1, подключается заголовочный файл targetver.h
— этот файл должен содержать определение макроса PROGRAM_NAME
, необходимого для формирования имени файла для дампа конкретной программы. Что я имею ввиду? Все не просто, а очень просто — если у вас в решении (solution) есть много проектов (exe) в которых нужно формировать дампы, удобно (во избежание дублирования кода), вынести все контекстно зависимое (в нашем случае имя конкретной программы) вынести в файл отражающий специфику конкретного проекта, и затем подключать его в каждом проекте свой. Пример такого файла targetver.h
для нашего случая:
#ifndef PRG_TARGETVER_H_ #define PRG_TARGETVER_H_ #define PROGRAM_NAME "prg" #endif // PRG_TARGETVER_H_
Данный код, при падении программы (необработанном исключении), будет генерировать дамп в файл prg.exe.dmp, который будет записан в тот же каталог что и исполняемый файл. Как вы уже заметили, prg, определяется в targetver.h
и удобно это определение делать эквивалентным имени программы. Как это использовать? Очень просто, — вот пример:
#include "DebugHelplers.h" int main(int argc, const char* const argv[]) { #ifndef _DEBUG // only Release configuration dump generating SetUnhandledExceptionFilter(TopLevelFilter); #endif // implementation ... return 0; }
В самом начале функции main, необходимо установить обработчик (TopLevelFilter
) на все необработанные в программе исключения. Делается это с помощью функции SetUnhandledExceptionFilter
(определена в Winbase.h
). Удобно это делать только в окончательной (не отладочной) версии программы (отсюда условная компиляция установки обработчика), т.к. в отладочной версии гораздо лучше чтобы при необработанном исключении вызывался отладчик, а не генерировался минидамп.