Файл 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
Теперь, когда у заказчика программа падает, можно попросить его прислать такой файл для анализа. Единственное, надо хранить .PDB файлы, соответствующие отданным заказчику исполняемым модулям.
Пока я не придумал, как сделать для модуля coredump.cpp
автоматизированный unit-тест. Проблема в том, что тут надо как-то подавить вывод окна с ошибкой (см. картинку выше). Если это сделать, то тест может быть вполне себе автоматизированным.
Более подробная информация вопросу создания образов памяти процесса есть в хорошей статье “Effective minidumps”.Другие посты по теме:
Попробуй возвращать
ОтветитьУдалитьreturn EXCEPTION_EXECUTE_HANDLER;
вместо return EXCEPTION_CONTINUE_SEARCH;
по крайнем мере у меня так, окошка нету не на Vista не на XP.
Товарищ Автор, а чегось это вы рекламку на своем блоге не разместите? Блог у Вас очень интерессный и люди ходят наверняка, сможете как раз на копилку для ПАБа зарабатывать =) Я так вообще с радостью буду нажимать, ибо сам с этого немного имею
ОтветитьУдалитьС дампами есть одна проблема - они могут быть большими и забивать все место на диске, если падения частые. Да и встает проблема - как пользователю корректно сообщить про них и намекнуть куда, как и что послать.
ОтветитьУдалитьСамый простой способ всегда создавать дампы при падениях - это написать в cmd: drwtsn32 -i .
Это установит drwtsn32 дебагером по умолчанию и он будет создавать дамп при unhandled exception в любой программе.
Мой любимый блог про креш дампы и их отладку: http://www.dumpanalysis.org/blog/ - просто море информации.
Andrey: Спасибо за совет. Следующий пост будет основан на нем.
ОтветитьУдалитьlosaped: Я как-то слышал, что тот же Google AdSense мало чего дает, если у сайта посещаемость не по сто тысяч в день. Я ошибаюсь?
bishop: Если падения частые - это не очень хорошо, чтобы отдавать заказчику ;-). Съедят с потрохами потом.
По поводу доктора ватсона, надо тогда как-то быть уверенным, что у пользователей он включен, и может, автоматизировать процесс его включения. Кстати, если дампы создавать самим, как в моем примере, их можно прямо на лету сжимать, подчищать предыдущие и т.д.
Спасибо за интересную ссылку. Добавил в ридер.
>>Если падения частые - это не очень хорошо,
ОтветитьУдалить>>чтобы отдавать заказчику ;-). Съедят с
>>потрохами потом.
Да не. Например, наш продукт - антивирусный пакет. Там около сотня ехешников. Если кто-то из них падает, то молча перезапускается и юзер даже не видит проблемы. А т.к. они работают постоянно и у миллионов юзеров, то падают они нередко :) А значит со временем, за месяцы, может накопиться прилично дампов, если нет системы очистки или лимитов.
>>Спасибо за интересную ссылку. Добавил в ридер.
Там добавить в ридер мало. Он уже почти все важное и интересное написал и все новые посты слишком сложны и непонятны :) Стоит начать читать сначала. Серия постов про паттерны отладки по крешдампам - шедевр.
bishop: Я естественно засел читать этот блог ;-). Очень уж сурово он разбирается в этих крешдампах.
ОтветитьУдалитьприблизительно 10 кликов это $1,5, а иной раз, если нормальная реклама попадается, то и с клика несколько баксов можно срубить. Да и в любом случае - не трудно же на страницу пару рекламмных блоков добавить. Я например не жданно не гаданно получил 30.12.08 $100, прям под новый год. Всего за 3 месяца. по моему очень приятная мелочь.
ОтветитьУдалить"Я слышал, что может так случиться, что после нажатия "Send Error Report" Майкрософт с вами свяжется и поможет решить проблему. Со мной такого ни разу не случалось, увы."
ОтветитьУдалитьИ не случится если у Вас нет VeriSign Class 3 Digital ID сертификата и Вы не прописаны в Winqual (https://winqual.microsoft.com/help/default.htm#winqual_requirements.htm)
У меня точно такой штуки нет.
ОтветитьУдалитьMS не связывается, если ваша програма отправляет дампы им. Есть возможность подписаться на их программу Windows Error Reporting (если не путаю), и иметь доступ к БД крешей вашей проги. Для этого надо иметь подписанный сертификатом exe.
ОтветитьУдалитьУпс, не заметил предпоследний комент :)
ОтветитьУдалить