Вывод информации об исключениях перед аварийным выходом

Зачем и для чего

Во время разработки не всегда всё бывает гладко. Бывают проблемы, которые не всплывают на тестах, а уже после релиза. Для таких случаев мы сделали небольшую систему отлова исключений и внутренних ошибок.

Перехват исключений

Для того, чтобы программа перед падением могла сообщить нам (вне отладчика) в чём проблема, можно воспользоваться функцией обработки сигналов.

Несколько примеров таких сигналов:

    signal(SIGABRT, abort_handler); // Аварийное завершение, стандартные исключения с nullptr и прочим      signal(SIGFPE, floating_point_handler); // Ошибки арифметики с плавающей запятой      signal(SIGILL, illegal_instruction_handler); // Ошибки при работе с intrinsics (SSE/AVX/Other) 

Обработчики сигналов

 inline void abort_handler(int signal) {     handler_base("Application is aborting"); }  inline void floating_point_handler(int signal) {     handler_base("Floating point error"); }  inline void illegal_instruction_handler(int signal) {     if (!CPU::Info.hasFeature(CPUFeature::SSE42))         handler_base("SSE4.2 and AVX instructions isn't legal on your CPU");     else if (!CPU::Info.hasFeature(CPUFeature::AVX))          handler_base("AVX instructions isn't legal on your CPU");     else         handler_base("Illegal instruction"); }

Так же есть обработчик недопустимых параметров (_set_invalid_parameter_handler), некорректного выделения памяти (_set_new_handler) и подобные им.

Проблемное место: Получение callstack’a

Помимо самого исключения, нужно узнать, где именно проблема. К примеру, CallStack. Для этого можно воспользоваться готовым решением: StackWalker

Проблемное место: Получения dump-файла

Если вам нужно получить более детальную информацию о проблеме, то следует сгенерировать Dump файл.

using MINIDUMPWRITEDUMP = BOOL(WINAPI*)(HANDLE hProcess, DWORD dwPid, HANDLE hFile, MINIDUMP_TYPE DumpType,     CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,     CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,     CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam     );  LONG WriteMinidump(struct _EXCEPTION_POINTERS* pExceptionInfo) {     long retval = EXCEPTION_CONTINUE_SEARCH;      // firstly see if dbghelp.dll is around and has the function we need     // look next to the EXE first, as the one in System32 might be old     // (e.g. Windows 2000)     HMODULE hDll = nullptr;     string_path szDbgHelpPath;      if (GetModuleFileName(nullptr, szDbgHelpPath, _MAX_PATH))     {         char* pSlash = strchr(szDbgHelpPath, '\\');         if (pSlash)         {             const char dbgHelpStr[] = "DBGHELP.DLL";             xr_strcpy(pSlash + 1, sizeof(dbgHelpStr), dbgHelpStr);             hDll = LoadLibraryA(szDbgHelpPath);         }     }      if (!hDll)     {         // Загружаем dbgHelp          hDll = LoadLibraryA("DBGHELP.DLL");     }      LPCSTR szResult = nullptr;      if (hDll)     {         MINIDUMPWRITEDUMP pDump = (MINIDUMPWRITEDUMP)::GetProcAddress(hDll, "MiniDumpWriteDump");         if (pDump)         {             string512 ErrorString = { NULL };             DWORD ErrorSysCode = NULL;             DWORD ErrorStringSize = NULL;              string_path szDumpPath = { 0 };             string_path szFilename = { 0 };             string_path szScratch = { 0 };             string64 t_stemp = { 0 };              timestamp(t_stemp);             xr_strcat(szDumpPath, DumpFilePath);              // Генерируем название файла : App Name, User Name, Time, Ext             xr_strcat(szFilename, Core.ApplicationName);             xr_strcat(szFilename, "_");             xr_strcat(szFilename, Core.UserName);             xr_strcat(szFilename, "_");             xr_strcat(szFilename, t_stemp);             xr_strcat(szFilename, ".mdmp");              xr_strcat(szDumpPath, szFilename);              // Создаём файл             HANDLE hFile = CreateFileA(szDumpPath, GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);             if (INVALID_HANDLE_VALUE == hFile)             {                 // Попробуем записать в директорию, из которой запущено приложение                 ZeroMemory(szDumpPath, sizeof(szDumpPath));                 xr_strcat(szDumpPath, szFilename);                 hFile = CreateFileA(szDumpPath, GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);             }             if (hFile != INVALID_HANDLE_VALUE)             {                 _MINIDUMP_EXCEPTION_INFORMATION ExInfo;                  ExInfo.ThreadId = GetCurrentThreadId();                 ExInfo.ExceptionPointers = pExceptionInfo;                 ExInfo.ClientPointers = NULL;                  // Записываем Dump                  MINIDUMP_TYPE dump_flags = MINIDUMP_TYPE(MiniDumpNormal | MiniDumpFilterMemory | MiniDumpScanMemory);                  // Сообщение ошибки                  char* logFileContent = nullptr;                 DWORD logFileContentSize = 0;  /////////////////////////////////////                 // Вставьте ваш код генерации сообщения об ошибке (logFileContent) /////////////////////////////////////          MINIDUMP_USER_STREAM_INFORMATION UserStreamsInfo = { 0 };                 MINIDUMP_USER_STREAM LogFileUserStream = { 0 };                  if (logFileContent)                 {                     UserStreamsInfo.UserStreamCount = 1;                     LogFileUserStream.Buffer = logFileContent; // Передаём ваше сообщение                     LogFileUserStream.BufferSize = logFileContentSize; // Размер сообщения                     LogFileUserStream.Type = MINIDUMP_STREAM_TYPE::CommentStreamA; // Тип                     UserStreamsInfo.UserStreamArray = &LogFileUserStream;                 }                  BOOL bOK = pDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, dump_flags, &ExInfo, &UserStreamsInfo, nullptr);                 if (bOK)                 {                     xr_sprintf(szScratch, "Saved dump file to '%s'", szDumpPath);                     szResult = szScratch;                     retval = EXCEPTION_EXECUTE_HANDLER;                 }                 else                 {                     ErrorSysCode = GetLastError();                     ErrorStringSize = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, ErrorSysCode, 0, ErrorString, sizeof(ErrorString) - 1, nullptr);                      if (!!ErrorString && ErrorSysCode && ErrorStringSize)                         xr_sprintf(szScratch, "Failed to save dump file to '%s' (error %d '%s')", szDumpPath, ErrorSysCode, ErrorString);                     else                         xr_sprintf(szScratch, "Failed to save dump file to '%s' (No system error)", szDumpPath);                     szResult = szScratch;                  }                 CloseHandle(hFile);             }             else             {                 ErrorSysCode = GetLastError();                 ErrorStringSize = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, ErrorSysCode, 0, ErrorString, sizeof(ErrorString) - 1, nullptr);                  if (!!ErrorString && ErrorSysCode && ErrorStringSize)                     xr_sprintf(szScratch, "Failed to create dump file '%s' (error %d '%s')", szDumpPath, ErrorSysCode, ErrorString);                 else                     xr_sprintf(szScratch, "Failed to create dump file '%s' (No system error)", szDumpPath);                 szResult = szScratch;             }         }         else             szResult = "DBGHELP.DLL too old";     }     else         szResult = "DBGHELP.DLL not found";      return retval; }

Сообщим пользователю о проблеме

Для вывода сообщений о проблемах, как исключений, так и внутренних (скриптов, конфигов, неправильного поведения программы), мы написали небольшую утилиту, которая выглядит следующим образом:
image
Написана она на C# + WPF, исходный код доступен

Функционал достаточно примитивен:

  • Продолжить выполнение программы (если это внутренняя ошибка)
  • Сообщить о проблеме (стек улетает в буффер обмена, пользователь переходит на сервер дискорда в нужный топик)
  • Прервать выполнение программы

ссылка на оригинал статьи https://habr.com/ru/post/486510/

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *