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). Удобно это делать только в окончательной (не отладочной) версии программы (отсюда условная компиляция установки обработчика), т.к. в отладочной версии гораздо лучше чтобы при необработанном исключении вызывался отладчик, а не генерировался минидамп.