Coredump для Windows

Тривиальная задачка: что будет, если запустить вот такую программу?

Файл main.cpp:

int main() {
  *(char *)0 = 0;
  return 0;
}

В UNIX’е самым вероятным исходом будет сообщение:

Segmentation fault

Это означает безусловное падение из-за нарушения защиты памяти. Если пользователь разрешил создание coredump файлов (например, для командного интерпретатора bash это делается командой):

ulimit -c unlimited

то после запуска будет создан файл coredump. Этот файл фактически содержит образ памяти вашей программы в момент возникновения ошибки, и его можно открыть в отладчике, например в gdb:

gdb -c coredump

После чего командой bt (back trace) можно точно установить место в программе, в котором произошла ошибка. Естественно, для удобства, программа должна быть скомпилирована с включенной отладочной информацией.

Когда ошибка происходит в процессе отладки у вас на глазах, то проще запустить программу сразу под отладчиком. Но когда, например, ошибка происходит у заказчика, и вас нет рядом, то тогда можно попросить прислать вам coredump файл для анализа. Во многих случаях этого хватает для локализации проблемы.

А что делать под Windows? Запуск приведенной программы под виндами обычно приведет вот к такому сообщению:

Неискушенный пользователь обычно жмет Don't send, и затем программа благополучно падает, и не останется никакой информации произошедшей ошибке. Конечно, программа может вести подробное журналирование, но часто ошибки подобного рода редко удается локализовать по журналам.

Я слышал, что может так случиться, что после нажатия Send Error Report Майкрософт с вами свяжется и поможет решить проблему. Со мной такого ни разу не случалось, увы.

В Windows тоже есть схожий механизм создания “на лету” образов памяти работающего процесса, но для его использования надо немного потрудиться.

Windows предоставляет механизм исключений, чем-то схожий с исключениями в С++ и системными сигналами в UNIX. На С++ эти исключения похожи try-catch синтаксисом, а на UNIX — тем, что можно перехватывать исключительные ситуации (например, ошибки) в программе типа нашей (для UNIX можно было бы перехватить сигнал SIGSEGV и получить управление при возникновении подобной ошибки).

Естественно, сигналы в UNIX используются не только для этого.

Итак, ниже я приведу исходный текст небольшого модуля, который будет создавать аналогичный по смыслу юниксовому coredump’у файл, по которому можно будет установить причину аварийного завершения программы. Все, что нужно — это прилинковать этот модуль в ваш проект. Ничего вызывать специально не надо. Модуль инициализируется автоматически.

Файл coredump.cpp:

#include <windows.h>
#include <dbghelp.h>   
#include <stdio.h>       // _snprintf

// Наш обработчик непойманного исключения.
static LONG WINAPI ExceptionFilter(EXCEPTION_POINTERS* ExceptionInfo);

// Статический экземпляр переменной, конструктор которой
// вызывается до начала функции main().
static struct CoredumpInitializer {
  CoredumpInitializer() {
    SetUnhandledExceptionFilter(&ExceptionFilter);
  }
} coredumpInitializer;

LONG WINAPI ExceptionFilter(EXCEPTION_POINTERS* ExceptionInfo) {
  char fname[_MAX_PATH];

  SYSTEMTIME st;
  GetLocalTime(&st);

  HANDLE proc = GetCurrentProcess();

  // Формируем имя для coredump'а.
  _snprintf(
    fname, _MAX_PATH, 
    "coredump-%ld-%ld-%04d%02d%02d%02d%02d%02d%03d.dmp", 
    GetProcessId(proc), GetCurrentThreadId(),
    st.wYear, st.wMonth, st.wDay, 
    st.wHour, st.wMinute, st.wSecond, st.wMilliseconds
  );

  // Открываем файл.
  HANDLE file = CreateFile(
    fname,
    GENERIC_READ|GENERIC_WRITE, 
    FILE_SHARE_READ,
    NULL,
    CREATE_ALWAYS,
    FILE_ATTRIBUTE_NORMAL, 
    NULL
  );

  MINIDUMP_EXCEPTION_INFORMATION info;
  info.ExceptionPointers = ExceptionInfo;
  info.ThreadId = GetCurrentThreadId();
  info.ClientPointers = NULL;

  // Собственно, сбрасываем образ памяти в файл.
  MiniDumpWriteDump(
    proc,
    GetProcessId(proc),
    file,
    MiniDumpWithFullMemory,
    ExceptionInfo ? &info : NULL,
    NULL, NULL
  );

  CloseHandle(file);

  return EXCEPTION_CONTINUE_SEARCH;
}

Данный модуль создает статический объект coredumpInitializer, конструктор которого вызывается до функции main(). Конструктор устанавливает специальный обработчик для системных исключений, в котором в файл и записывается образ памяти текущего процесса при возникновении системных ошибок. Имя файла содержит идентификатор процесса, идентификатор потока и текущее время. Этот файл можно открыть в Visual Studio просто запустив его, либо в самой студии в меню File->Open->Project/Solution выбрать этот файл, указав его тип как Dump files. Далее надо запустить программу через отладчик, нажав F5, и отладчик остановится на месте возникновения ошибки. Естественно, для работы на уровне исходного текста необходимо наличие файла с расширением .PDB, содержащего символьную информацию, в том каталоге, где расположен ваш coredump файл. Файл с раширением .PDB обычно создается при компиляции с включенной отладочной информацией, например, при использования ключа /Zi.

Надо отметить, что если ошибка в вашей программе произойдет до функции main(), например, при инициализации каких-то статических объектов, то данный модуль может и не сработать, так как вызов конструктора объекта coredumpInitializer может быть запланирован уже после проблемного места. Вообще, статические переменные и объекты — это источник многих проблем, в основном из-за неопределенного порядка их инициализации, и их использования стоит избегать по возможности.

Итак, компилируем:

cl /Zi main.cpp coredump.cpp dbghelp.lib

Для компиляции необходима библиотека DbgHelp.lib. Она обычно входит в состав Windows SDK в составе студии. Также получившийся исполняемый файл main.exe будет использовать динамическую библиотеку DbgHelp.dll. DbgHelp.dll входит в состав так называемых redistributable файлов, то есть которые вы имеете право распространять вместе с программой на случай, если у клиента нет этой dll’ки. Эта dll’ка с большой вероятностью находится у вас в каталоге C:\WINDOWS\system32.

Теперь при запуске программы main.exe и после закрытия пользователем окна с сообщением об ошибке (см. картинку выше), будет создан .DMP файл с именем, начинающийся со слова coredump, например coredump-4584-3240-20090226022327093.dmp. Этот файл уже можно открыть в Visual Studio.

Теперь, когда у заказчика программа падает, можно попросить его прислать такой файл для анализа. Единственное, надо хранить .PDB файлы, соответствующие отданным заказчику исполняемым модулям.

Пока я не придумал, как сделать для модуля coredump.cpp автоматизированный unit-тест. Проблема в том, что тут надо как-то подавить вывод окна с ошибкой (см. картинку выше). Если это сделать, то тест может быть вполне себе автоматизированным.

Более подробная информация вопросу создания образов памяти процесса есть в хорошей статье “Effective minidumps”.

Другие посты по теме:


Оригинальный пост | Disclaimer

Комментарии