2010-10-23 16:00:24

Время от времени, неважно сколько усилий потрачено на отладку и тестирование, программы все равно падают. Причем, падают не в тестовой среде или на компьютере разработчика, а обязательно у конечного пользователя. При этом неплохо бы узнать, из какого места программы произошел сбой. Вот тут то на помошь и приходит Windows Debugging API, а точнее библиотека Dbghlp, с помощью которой возможно создание минидампов.

Что такое минидамп? Как правило, те кто читают это, уже знают что это такое, но все равно я повторюсь:). Минидамп это бинарный файл с информацией о состоянии программы на определенный момент времени, в нашем случае на момент падения. В нашем случае в этом минидампе будет состояние регистров процессора, стек вызовов в программе приведший к её падению, а также код ошибки и причина падения.

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

2010-10-23 16:00:24  c snippet debugging noweb
comments powered by Disqus