| /*------------------------------------------------------------------------- |
| * drawElements Quality Program Helper Library |
| * ------------------------------------------- |
| * |
| * Copyright 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| *//*! |
| * \file |
| * \brief System handler handler override |
| *//*--------------------------------------------------------------------*/ |
| |
| #include "qpCrashHandler.h" |
| #include "qpDebugOut.h" |
| |
| #include "deThread.h" |
| #include "deMemory.h" |
| #include "deString.h" |
| #include "deMutex.h" |
| |
| #include <stdio.h> |
| #include <stdarg.h> |
| #include <stdlib.h> |
| |
| #if (DE_OS == DE_OS_UNIX) |
| # include <unistd.h> |
| # include <execinfo.h> |
| # include <errno.h> |
| # include <inttypes.h> |
| #endif |
| |
| #if 0 |
| # define DBGPRINT(X) qpPrintf X |
| #else |
| # define DBGPRINT(X) |
| #endif |
| |
| /* Crash info write helper. */ |
| static void writeInfoFormat (qpWriteCrashInfoFunc writeFunc, void* userPtr, const char* format, ...) |
| { |
| char buf[256]; |
| va_list ap; |
| |
| va_start(ap, format); |
| vsnprintf(buf, sizeof(buf), format, ap); |
| va_end(ap); |
| |
| writeFunc(userPtr, buf); |
| } |
| |
| /* Shared crash info. */ |
| typedef enum qpCrashType_e |
| { |
| QP_CRASHTYPE_SEGMENTATION_FAULT = 0, |
| QP_CRASHTYPE_ASSERT, |
| QP_CRASHTYPE_UNHANDLED_EXCEPTION, |
| QP_CRASHTYPE_OTHER, |
| |
| QP_CRASHTYPE_LAST |
| } qpCrashType; |
| |
| typedef struct qpCrashInfo_s |
| { |
| qpCrashType type; |
| const char* message; |
| const char* file; |
| int line; |
| } qpCrashInfo; |
| |
| static void qpCrashInfo_init (qpCrashInfo* info) |
| { |
| info->type = QP_CRASHTYPE_LAST; |
| info->message = DE_NULL; |
| info->file = DE_NULL; |
| info->line = 0; |
| } |
| |
| static void qpCrashInfo_set (qpCrashInfo* info, qpCrashType type, const char* message, const char* file, int line) |
| { |
| info->type = type; |
| info->message = message; |
| info->file = file; |
| info->line = line; |
| } |
| |
| static void qpCrashInfo_write (qpCrashInfo* info, qpWriteCrashInfoFunc writeInfo, void* userPtr) |
| { |
| switch (info->type) |
| { |
| case QP_CRASHTYPE_SEGMENTATION_FAULT: |
| writeInfoFormat(writeInfo, userPtr, "Segmentation fault: '%s'\n", info->message); |
| break; |
| |
| case QP_CRASHTYPE_UNHANDLED_EXCEPTION: |
| writeInfoFormat(writeInfo, userPtr, "Unhandled exception: '%s'\n", info->message); |
| break; |
| |
| case QP_CRASHTYPE_ASSERT: |
| writeInfoFormat(writeInfo, userPtr, "Assertion '%s' failed at %s:%d\n", |
| info->message, |
| info->file, |
| info->line); |
| break; |
| |
| case QP_CRASHTYPE_OTHER: |
| default: |
| writeInfoFormat(writeInfo, userPtr, "Crash: '%s'\n", info->message); |
| break; |
| } |
| } |
| |
| static void defaultWriteInfo (void* userPtr, const char* infoString) |
| { |
| DE_UNREF(userPtr); |
| qpPrintf("%s", infoString); |
| } |
| |
| static void defaultCrashHandler (qpCrashHandler* crashHandler, void* userPtr) |
| { |
| DE_UNREF(userPtr); |
| qpCrashHandler_writeCrashInfo(crashHandler, defaultWriteInfo, DE_NULL); |
| qpDief("Test process crashed"); |
| } |
| |
| #if (DE_OS == DE_OS_WIN32) && (DE_COMPILER == DE_COMPILER_MSC) |
| |
| #define WIN32_LEAN_AND_MEAN |
| #include <windows.h> |
| |
| /* DbgHelp.h generates C4091 */ |
| #pragma warning (push) |
| #pragma warning (disable: 4091) |
| #include <DbgHelp.h> |
| #pragma warning (pop) |
| |
| struct qpCrashHandler_s |
| { |
| qpCrashHandlerFunc crashHandlerFunc; |
| void* handlerUserPointer; |
| |
| deMutex crashHandlerLock; |
| qpCrashInfo crashInfo; |
| deUintptr crashAddress; |
| |
| LPTOP_LEVEL_EXCEPTION_FILTER oldExceptionFilter; |
| }; |
| |
| qpCrashHandler* g_crashHandler = DE_NULL; |
| |
| static LONG WINAPI unhandledExceptionFilter (struct _EXCEPTION_POINTERS* info) |
| { |
| qpCrashType crashType = QP_CRASHTYPE_LAST; |
| const char* reason = DE_NULL; |
| |
| /* Skip breakpoints. */ |
| if (info->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT) |
| { |
| DBGPRINT(("qpCrashHandler::unhandledExceptionFilter(): breakpoint\n")); |
| return EXCEPTION_CONTINUE_SEARCH; |
| } |
| |
| /* If no handler present (how could that be?), don't handle. */ |
| if (g_crashHandler == DE_NULL) |
| { |
| DBGPRINT(("qpCrashHandler::unhandledExceptionFilter(): no crash handler registered\n")); |
| return EXCEPTION_CONTINUE_SEARCH; |
| } |
| |
| /* If we have a debugger, let it handle the exception. */ |
| if (IsDebuggerPresent()) |
| { |
| DBGPRINT(("qpCrashHandler::unhandledExceptionFilter(): debugger present\n")); |
| return EXCEPTION_CONTINUE_SEARCH; |
| } |
| |
| /* Acquire crash handler lock. Otherwise we might get strange behavior when multiple threads enter crash handler simultaneously. */ |
| deMutex_lock(g_crashHandler->crashHandlerLock); |
| |
| /* Map crash type. */ |
| switch (info->ExceptionRecord->ExceptionCode) |
| { |
| case EXCEPTION_ACCESS_VIOLATION: |
| crashType = QP_CRASHTYPE_SEGMENTATION_FAULT; |
| reason = "Access violation"; |
| break; |
| |
| case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: |
| crashType = QP_CRASHTYPE_SEGMENTATION_FAULT; |
| reason = "Array bounds exceeded"; |
| break; |
| |
| case EXCEPTION_ILLEGAL_INSTRUCTION: |
| crashType = QP_CRASHTYPE_OTHER; |
| reason = "Illegal instruction"; |
| break; |
| |
| case EXCEPTION_STACK_OVERFLOW: |
| crashType = QP_CRASHTYPE_OTHER; |
| reason = "Stack overflow"; |
| break; |
| |
| default: |
| /* \todo [pyry] Others. */ |
| crashType = QP_CRASHTYPE_OTHER; |
| reason = ""; |
| break; |
| } |
| |
| /* Store reason. */ |
| qpCrashInfo_set(&g_crashHandler->crashInfo, crashType, reason, __FILE__, __LINE__); |
| |
| /* Store win32-specific crash info. */ |
| g_crashHandler->crashAddress = (deUintptr)info->ExceptionRecord->ExceptionAddress; |
| |
| /* Handle the crash. */ |
| DBGPRINT(("qpCrashHandler::unhandledExceptionFilter(): handled quietly\n")); |
| if (g_crashHandler->crashHandlerFunc != DE_NULL) |
| g_crashHandler->crashHandlerFunc(g_crashHandler, g_crashHandler->handlerUserPointer); |
| |
| /* Release lock. */ |
| deMutex_unlock(g_crashHandler->crashHandlerLock); |
| |
| return EXCEPTION_EXECUTE_HANDLER; |
| } |
| |
| static void assertFailureCallback (const char* expr, const char* file, int line) |
| { |
| /* Don't execute crash handler function if debugger is present. */ |
| if (IsDebuggerPresent()) |
| { |
| DBGPRINT(("qpCrashHandler::assertFailureCallback(): debugger present\n")); |
| return; |
| } |
| |
| /* Acquire crash handler lock. */ |
| deMutex_lock(g_crashHandler->crashHandlerLock); |
| |
| /* Store info. */ |
| qpCrashInfo_set(&g_crashHandler->crashInfo, QP_CRASHTYPE_ASSERT, expr, file, line); |
| g_crashHandler->crashAddress = 0; |
| |
| /* Handle the crash. */ |
| if (g_crashHandler->crashHandlerFunc != DE_NULL) |
| g_crashHandler->crashHandlerFunc(g_crashHandler, g_crashHandler->handlerUserPointer); |
| |
| /* Release lock. */ |
| deMutex_unlock(g_crashHandler->crashHandlerLock); |
| } |
| |
| qpCrashHandler* qpCrashHandler_create (qpCrashHandlerFunc handlerFunc, void* userPointer) |
| { |
| /* Allocate & initialize. */ |
| qpCrashHandler* handler = (qpCrashHandler*)deCalloc(sizeof(qpCrashHandler)); |
| DBGPRINT(("qpCrashHandler::create() -- Win32\n")); |
| if (!handler) |
| return handler; |
| |
| DE_ASSERT(g_crashHandler == DE_NULL); |
| |
| handler->crashHandlerFunc = handlerFunc ? handlerFunc : defaultCrashHandler; |
| handler->handlerUserPointer = userPointer; |
| |
| /* Create lock for crash handler. \note Has to be recursive or otherwise crash in assert failure causes deadlock. */ |
| { |
| deMutexAttributes attr; |
| attr.flags = DE_MUTEX_RECURSIVE; |
| handler->crashHandlerLock = deMutex_create(&attr); |
| |
| if (!handler->crashHandlerLock) |
| { |
| deFree(handler); |
| return DE_NULL; |
| } |
| } |
| |
| qpCrashInfo_init(&handler->crashInfo); |
| handler->crashAddress = 0; |
| |
| /* Unhandled exception filter. */ |
| handler->oldExceptionFilter = SetUnhandledExceptionFilter(unhandledExceptionFilter); |
| |
| /* Prevent nasty error dialog. */ |
| SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOGPFAULTERRORBOX); |
| |
| /* DE_ASSERT callback. */ |
| deSetAssertFailureCallback(assertFailureCallback); |
| |
| g_crashHandler = handler; |
| return handler; |
| } |
| |
| void qpCrashHandler_destroy (qpCrashHandler* handler) |
| { |
| DBGPRINT(("qpCrashHandler::destroy()\n")); |
| |
| DE_ASSERT(g_crashHandler == handler); |
| |
| deSetAssertFailureCallback(DE_NULL); |
| SetUnhandledExceptionFilter(handler->oldExceptionFilter); |
| |
| g_crashHandler = DE_NULL; |
| deFree(handler); |
| } |
| |
| enum |
| { |
| MAX_NAME_LENGTH = 64 |
| }; |
| |
| void qpCrashHandler_writeCrashInfo (qpCrashHandler* handler, qpWriteCrashInfoFunc writeInfo, void* userPtr) |
| { |
| void* addresses[32]; |
| HANDLE process; |
| |
| /* Symbol info struct. */ |
| deUint8 symInfoStorage[sizeof(SYMBOL_INFO)+MAX_NAME_LENGTH]; |
| SYMBOL_INFO* symInfo = (SYMBOL_INFO*)symInfoStorage; |
| |
| DE_STATIC_ASSERT(sizeof(TCHAR) == sizeof(deUint8)); |
| |
| /* Write basic info. */ |
| qpCrashInfo_write(&handler->crashInfo, writeInfo, userPtr); |
| |
| /* Acquire process handle and initialize symbols. */ |
| process = GetCurrentProcess(); |
| |
| /* Write backtrace. */ |
| if (SymInitialize(process, NULL, TRUE)) |
| { |
| int batchStart = 0; |
| int globalFrameNdx = 0; |
| |
| /* Initialize symInfo. */ |
| deMemset(symInfo, 0, sizeof(symInfoStorage)); |
| symInfo->SizeOfStruct = sizeof(SYMBOL_INFO); |
| symInfo->MaxNameLen = MAX_NAME_LENGTH; |
| |
| /* Print address and symbol where crash happened. */ |
| if (handler->crashAddress != 0) |
| { |
| BOOL symInfoOk = SymFromAddr(process, (DWORD64)handler->crashAddress, 0, symInfo); |
| |
| writeInfoFormat(writeInfo, userPtr, " at %p %s%s\n", handler->crashAddress, |
| symInfoOk ? symInfo->Name : "(unknown)", |
| symInfoOk ? "()" : ""); |
| } |
| |
| writeInfo(userPtr, "Backtrace:\n"); |
| |
| for (;;) |
| { |
| int curFrame; |
| int numInBatch; |
| |
| /* Get one batch. */ |
| numInBatch = CaptureStackBackTrace(batchStart, DE_LENGTH_OF_ARRAY(addresses), addresses, NULL); |
| |
| for (curFrame = 0; curFrame < numInBatch; curFrame++) |
| { |
| BOOL symInfoOk = SymFromAddr(process, (DWORD64)addresses[curFrame], 0, symInfo); |
| |
| writeInfoFormat(writeInfo, userPtr, " %2d: %p %s%s\n", globalFrameNdx++, addresses[curFrame], |
| symInfoOk ? symInfo->Name : "(unknown)", |
| symInfoOk ? "()" : ""); |
| } |
| |
| batchStart += numInBatch; |
| |
| /* Check if we hit end of stack trace. */ |
| if (numInBatch == 0 || numInBatch < DE_LENGTH_OF_ARRAY(addresses)) |
| break; |
| } |
| } |
| } |
| |
| #else /* posix / generic implementation */ |
| |
| #if defined(QP_USE_SIGNAL_HANDLER) |
| # include <signal.h> |
| #endif |
| |
| #if defined(QP_USE_SIGNAL_HANDLER) |
| |
| typedef struct SignalInfo_s |
| { |
| int signalNum; |
| qpCrashType type; |
| const char* name; |
| } SignalInfo; |
| |
| static const SignalInfo s_signals[] = |
| { |
| { SIGABRT, QP_CRASHTYPE_UNHANDLED_EXCEPTION, "SIGABRT" }, |
| { SIGILL, QP_CRASHTYPE_OTHER, "SIGILL" }, |
| { SIGSEGV, QP_CRASHTYPE_SEGMENTATION_FAULT, "SIGSEGV" }, |
| { SIGFPE, QP_CRASHTYPE_OTHER, "SIGFPE" }, |
| { SIGBUS, QP_CRASHTYPE_SEGMENTATION_FAULT, "SIGBUS" }, |
| { SIGPIPE, QP_CRASHTYPE_OTHER, "SIGPIPE" } |
| }; |
| |
| #endif /* QP_USE_SIGNAL_HANDLER */ |
| |
| struct qpCrashHandler_s |
| { |
| qpCrashHandlerFunc crashHandlerFunc; |
| void* handlerUserPointer; |
| |
| qpCrashInfo crashInfo; |
| int crashSignal; |
| |
| #if defined(QP_USE_SIGNAL_HANDLER) |
| struct sigaction oldHandlers[DE_LENGTH_OF_ARRAY(s_signals)]; |
| #endif |
| }; |
| |
| qpCrashHandler* g_crashHandler = DE_NULL; |
| |
| static void assertFailureCallback (const char* expr, const char* file, int line) |
| { |
| /* Store info. */ |
| qpCrashInfo_set(&g_crashHandler->crashInfo, QP_CRASHTYPE_ASSERT, expr, file, line); |
| |
| /* Handle the crash. */ |
| if (g_crashHandler->crashHandlerFunc != DE_NULL) |
| g_crashHandler->crashHandlerFunc(g_crashHandler, g_crashHandler->handlerUserPointer); |
| } |
| |
| #if defined(QP_USE_SIGNAL_HANDLER) |
| |
| static const SignalInfo* getSignalInfo (int sigNum) |
| { |
| int ndx; |
| for (ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_signals); ndx++) |
| { |
| if (s_signals[ndx].signalNum == sigNum) |
| return &s_signals[ndx]; |
| } |
| return DE_NULL; |
| } |
| |
| static void signalHandler (int sigNum) |
| { |
| const SignalInfo* info = getSignalInfo(sigNum); |
| qpCrashType type = info ? info->type : QP_CRASHTYPE_OTHER; |
| const char* name = info ? info->name : "Unknown signal"; |
| |
| qpCrashInfo_set(&g_crashHandler->crashInfo, type, name, DE_NULL, 0); |
| |
| if (g_crashHandler->crashHandlerFunc != DE_NULL) |
| g_crashHandler->crashHandlerFunc(g_crashHandler, g_crashHandler->handlerUserPointer); |
| } |
| |
| #endif /* QP_USE_SIGNAL_HANDLER */ |
| |
| qpCrashHandler* qpCrashHandler_create (qpCrashHandlerFunc handlerFunc, void* userPointer) |
| { |
| /* Allocate & initialize. */ |
| qpCrashHandler* handler = (qpCrashHandler*)deCalloc(sizeof(qpCrashHandler)); |
| DBGPRINT(("qpCrashHandler::create()\n")); |
| if (!handler) |
| return handler; |
| |
| DE_ASSERT(g_crashHandler == DE_NULL); |
| |
| handler->crashHandlerFunc = handlerFunc ? handlerFunc : defaultCrashHandler; |
| handler->handlerUserPointer = userPointer; |
| |
| qpCrashInfo_init(&handler->crashInfo); |
| |
| g_crashHandler = handler; |
| |
| /* DE_ASSERT callback. */ |
| deSetAssertFailureCallback(assertFailureCallback); |
| |
| #if defined(QP_USE_SIGNAL_HANDLER) |
| /* Register signal handlers. */ |
| { |
| struct sigaction action; |
| int sigNdx; |
| |
| sigemptyset(&action.sa_mask); |
| action.sa_handler = signalHandler; |
| action.sa_flags = 0; |
| |
| for (sigNdx = 0; sigNdx < DE_LENGTH_OF_ARRAY(s_signals); sigNdx++) |
| sigaction(s_signals[sigNdx].signalNum, &action, &handler->oldHandlers[sigNdx]); |
| } |
| #endif |
| |
| return handler; |
| } |
| |
| void qpCrashHandler_destroy (qpCrashHandler* handler) |
| { |
| DBGPRINT(("qpCrashHandler::destroy()\n")); |
| |
| DE_ASSERT(g_crashHandler == handler); |
| |
| deSetAssertFailureCallback(DE_NULL); |
| |
| #if defined(QP_USE_SIGNAL_HANDLER) |
| /* Restore old handlers. */ |
| { |
| int sigNdx; |
| for (sigNdx = 0; sigNdx < DE_LENGTH_OF_ARRAY(s_signals); sigNdx++) |
| sigaction(s_signals[sigNdx].signalNum, &handler->oldHandlers[sigNdx], DE_NULL); |
| } |
| #endif |
| |
| g_crashHandler = DE_NULL; |
| |
| deFree(handler); |
| } |
| |
| #if (DE_PTR_SIZE == 8) |
| # define PTR_FMT "0x%016" |
| #elif (DE_PTR_SIZE == 4) |
| # define PTR_FMT "0x%08" |
| #else |
| # error Unknwon DE_PTR_SIZE |
| #endif |
| |
| void qpCrashHandler_writeCrashInfo (qpCrashHandler* crashHandler, qpWriteCrashInfoFunc writeInfo, void* userPtr) |
| { |
| qpCrashInfo_write(&crashHandler->crashInfo, writeInfo, userPtr); |
| |
| #if (DE_OS == DE_OS_UNIX) |
| { |
| char tmpFileName[] = "backtrace-XXXXXX"; |
| int tmpFile = mkstemp(tmpFileName); |
| |
| if (tmpFile == -1) |
| { |
| writeInfoFormat(writeInfo, userPtr, "Failed to create tmpfile '%s' for the backtrace %s.", tmpFileName, strerror(errno)); |
| return; |
| } |
| else |
| { |
| void* symbols[32]; |
| int symbolCount; |
| int symbolNdx; |
| |
| /* Remove file from filesystem. */ |
| remove(tmpFileName); |
| |
| symbolCount = backtrace(symbols, DE_LENGTH_OF_ARRAY(symbols)); |
| backtrace_symbols_fd(symbols, symbolCount, tmpFile); |
| |
| if (lseek(tmpFile, 0, SEEK_SET) < 0) |
| { |
| writeInfoFormat(writeInfo, userPtr, "Failed to seek to the beginning of the trace file %s.", strerror(errno)); |
| close(tmpFile); |
| return; |
| } |
| |
| for (symbolNdx = 0; symbolNdx < symbolCount; symbolNdx++) |
| { |
| char nameBuffer[256]; |
| size_t symbolNameLength = 0; |
| char c; |
| |
| { |
| const int ret = snprintf(nameBuffer, DE_LENGTH_OF_ARRAY(nameBuffer), PTR_FMT PRIXPTR " : ", (uintptr_t)symbols[symbolNdx]); |
| |
| if (ret < 0) |
| { |
| writeInfoFormat(writeInfo, userPtr, "Failed to print symbol pointer."); |
| symbolNameLength = 0; |
| } |
| else if (ret >= DE_LENGTH_OF_ARRAY(nameBuffer)) |
| { |
| symbolNameLength = DE_LENGTH_OF_ARRAY(nameBuffer) - 1; |
| nameBuffer[DE_LENGTH_OF_ARRAY(nameBuffer) - 1] = '\0'; |
| } |
| else |
| symbolNameLength = ret; |
| } |
| |
| for (;;) |
| { |
| if (read(tmpFile, &c, 1) == 1) |
| { |
| if (c == '\n') |
| { |
| /* Flush nameBuffer and move to next symbol. */ |
| nameBuffer[symbolNameLength] = '\0'; |
| writeInfo(userPtr, nameBuffer); |
| break; |
| } |
| else |
| { |
| /* Add character to buffer if there is still space left. */ |
| if (symbolNameLength+1 < DE_LENGTH_OF_ARRAY(nameBuffer)) |
| { |
| nameBuffer[symbolNameLength] = c; |
| symbolNameLength++; |
| } |
| } |
| } |
| else |
| { |
| /* Flush nameBuffer. */ |
| nameBuffer[symbolNameLength] = '\0'; |
| writeInfo(userPtr, nameBuffer); |
| |
| /* Temp file ended unexpectedly? */ |
| writeInfoFormat(writeInfo, userPtr, "Unexpected EOF reading backtrace file '%s'", tmpFileName); |
| close(tmpFile); |
| tmpFile = -1; |
| |
| break; |
| } |
| } |
| |
| if (tmpFile == -1) |
| break; |
| } |
| |
| if (tmpFile != -1) |
| close(tmpFile); |
| } |
| } |
| #endif |
| } |
| |
| #endif /* generic */ |