*** ВНИМАНИЕ: Блог переехал на другой адрес - demin.ws ***

среда, 25 февраля 2009 г.

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”.


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

11 комментариев:

  1. Попробуй возвращать
    return EXCEPTION_EXECUTE_HANDLER;
    вместо return EXCEPTION_CONTINUE_SEARCH;
    по крайнем мере у меня так, окошка нету не на Vista не на XP.

    ОтветитьУдалить
  2. Товарищ Автор, а чегось это вы рекламку на своем блоге не разместите? Блог у Вас очень интерессный и люди ходят наверняка, сможете как раз на копилку для ПАБа зарабатывать =) Я так вообще с радостью буду нажимать, ибо сам с этого немного имею

    ОтветитьУдалить
  3. С дампами есть одна проблема - они могут быть большими и забивать все место на диске, если падения частые. Да и встает проблема - как пользователю корректно сообщить про них и намекнуть куда, как и что послать.

    Самый простой способ всегда создавать дампы при падениях - это написать в cmd: drwtsn32 -i .
    Это установит drwtsn32 дебагером по умолчанию и он будет создавать дамп при unhandled exception в любой программе.

    Мой любимый блог про креш дампы и их отладку: http://www.dumpanalysis.org/blog/ - просто море информации.

    ОтветитьУдалить
  4. Andrey: Спасибо за совет. Следующий пост будет основан на нем.

    losaped: Я как-то слышал, что тот же Google AdSense мало чего дает, если у сайта посещаемость не по сто тысяч в день. Я ошибаюсь?

    bishop: Если падения частые - это не очень хорошо, чтобы отдавать заказчику ;-). Съедят с потрохами потом.

    По поводу доктора ватсона, надо тогда как-то быть уверенным, что у пользователей он включен, и может, автоматизировать процесс его включения. Кстати, если дампы создавать самим, как в моем примере, их можно прямо на лету сжимать, подчищать предыдущие и т.д.

    Спасибо за интересную ссылку. Добавил в ридер.

    ОтветитьУдалить
  5. >>Если падения частые - это не очень хорошо,
    >>чтобы отдавать заказчику ;-). Съедят с
    >>потрохами потом.

    Да не. Например, наш продукт - антивирусный пакет. Там около сотня ехешников. Если кто-то из них падает, то молча перезапускается и юзер даже не видит проблемы. А т.к. они работают постоянно и у миллионов юзеров, то падают они нередко :) А значит со временем, за месяцы, может накопиться прилично дампов, если нет системы очистки или лимитов.

    >>Спасибо за интересную ссылку. Добавил в ридер.

    Там добавить в ридер мало. Он уже почти все важное и интересное написал и все новые посты слишком сложны и непонятны :) Стоит начать читать сначала. Серия постов про паттерны отладки по крешдампам - шедевр.

    ОтветитьУдалить
  6. bishop: Я естественно засел читать этот блог ;-). Очень уж сурово он разбирается в этих крешдампах.

    ОтветитьУдалить
  7. приблизительно 10 кликов это $1,5, а иной раз, если нормальная реклама попадается, то и с клика несколько баксов можно срубить. Да и в любом случае - не трудно же на страницу пару рекламмных блоков добавить. Я например не жданно не гаданно получил 30.12.08 $100, прям под новый год. Всего за 3 месяца. по моему очень приятная мелочь.

    ОтветитьУдалить
  8. "Я слышал, что может так случиться, что после нажатия "Send Error Report" Майкрософт с вами свяжется и поможет решить проблему. Со мной такого ни разу не случалось, увы."
    И не случится если у Вас нет VeriSign Class 3 Digital ID сертификата и Вы не прописаны в Winqual (https://winqual.microsoft.com/help/default.htm#winqual_requirements.htm)

    ОтветитьУдалить
  9. У меня точно такой штуки нет.

    ОтветитьУдалить
  10. MS не связывается, если ваша програма отправляет дампы им. Есть возможность подписаться на их программу Windows Error Reporting (если не путаю), и иметь доступ к БД крешей вашей проги. Для этого надо иметь подписанный сертификатом exe.

    ОтветитьУдалить
  11. Упс, не заметил предпоследний комент :)

    ОтветитьУдалить