Тривиальная задачка: что будет, если запустить вот такую программу?
Файл 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”.
Другие посты по теме: