Зачем и для чего
Во время разработки не всегда всё бывает гладко. Бывают проблемы, которые не всплывают на тестах, а уже после релиза. Для таких случаев мы сделали небольшую систему отлова исключений и внутренних ошибок.
Перехват исключений
Для того, чтобы программа перед падением могла сообщить нам (вне отладчика) в чём проблема, можно воспользоваться функцией обработки сигналов.
Несколько примеров таких сигналов:
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; }
Сообщим пользователю о проблеме
Для вывода сообщений о проблемах, как исключений, так и внутренних (скриптов, конфигов, неправильного поведения программы), мы написали небольшую утилиту, которая выглядит следующим образом:
Написана она на C# + WPF, исходный код доступен
Функционал достаточно примитивен:
- Продолжить выполнение программы (если это внутренняя ошибка)
- Сообщить о проблеме (стек улетает в буффер обмена, пользователь переходит на сервер дискорда в нужный топик)
- Прервать выполнение программы
ссылка на оригинал статьи https://habr.com/ru/post/486510/
Добавить комментарий