From d79994a78065366fc55107f02b1d5fbe936ac0fa Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Thu, 3 Nov 2016 02:19:48 -0500 Subject: [PATCH] New cross platform exception handler for libopenshot! Logs basic stacktrace on segmentation fault. This will be a huge help in finding bugs and crashes. --- include/CrashHandler.h | 86 +++++++++++ include/Timeline.h | 1 + include/ZmqLogger.h | 3 + src/CMakeLists.txt | 6 + src/CrashHandler.cpp | 319 +++++++++++++++++++++++++++++++++++++++++ src/FFmpegReader.cpp | 2 +- src/Timeline.cpp | 3 + src/ZmqLogger.cpp | 10 +- 8 files changed, 428 insertions(+), 2 deletions(-) create mode 100644 include/CrashHandler.h create mode 100644 src/CrashHandler.cpp diff --git a/include/CrashHandler.h b/include/CrashHandler.h new file mode 100644 index 00000000..e3a4bbe5 --- /dev/null +++ b/include/CrashHandler.h @@ -0,0 +1,86 @@ +/** + * @file + * @brief Header file for CrashHandler class + * @author Jonathan Thomas + * + * @section LICENSE + * + * Copyright (c) 2008-2014 OpenShot Studios, LLC + * . This file is part of + * OpenShot Library (libopenshot), an open-source project dedicated to + * delivering high quality video editing and animation solutions to the + * world. For more information visit . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * OpenShot Library (libopenshot) is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#ifndef OPENSHOT_CRASH_HANDLER_H +#define OPENSHOT_CRASH_HANDLER_H + +#include +#include +#include +#ifdef __MINGW32__ + #include + #include + #include +#else + #include +#endif +#include +#include +#include "ZmqLogger.h" + +namespace openshot { + + /** + * @brief This class is designed to catch exceptions thrown by libc (SIGABRT, SIGSEGV, SIGILL, SIGFPE) + * + * This class is a singleton which only needs to be instantiated 1 time, and it will register as a signal + * handler with libc, and log errors using the ZmqLogger class. + */ + class CrashHandler { + private: + /// Default constructor + CrashHandler(){}; // Don't allow user to create an instance of this singleton + + /// Default copy method + CrashHandler(CrashHandler const&){}; // Don't allow the user to copy this instance + + /// Default assignment operator + CrashHandler & operator=(CrashHandler const&){}; // Don't allow the user to assign this instance + + /// Private variable to keep track of singleton instance + static CrashHandler *m_pInstance; + + public: + /// Create or get an instance of this crash handler singleton (invoke the class with this method). This also + /// registers the instance as a signal handler for libc + static CrashHandler *Instance(); + +#ifdef __MINGW32__ + // TODO: Windows exception handling methods + static void abortHandler(int signum); +#else + /// Method which handles crashes and logs error + static void abortHandler(int signum, siginfo_t* si, void* unused); +#endif + + /// Method which prints a stacktrace + static void printStackTrace(FILE *out, unsigned int max_frames); + }; + +} + +#endif diff --git a/include/Timeline.h b/include/Timeline.h index 2241a6c2..f8998321 100644 --- a/include/Timeline.h +++ b/include/Timeline.h @@ -37,6 +37,7 @@ #include "CacheMemory.h" #include "Color.h" #include "Clip.h" +#include "CrashHandler.h" #include "Point.h" #include "EffectBase.h" #include "Effects.h" diff --git a/include/ZmqLogger.h b/include/ZmqLogger.h index 93e167a0..c134f2cf 100644 --- a/include/ZmqLogger.h +++ b/include/ZmqLogger.h @@ -107,6 +107,9 @@ namespace openshot { /// Log message to all subscribers of this logger (if any) void Log(string message); + + /// Log message to a file (if path set) + void LogToFile(string message); }; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index be53da5d..b377fa4f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -196,6 +196,7 @@ SET ( OPENSHOT_SOURCE_FILES Clip.cpp ClipBase.cpp Coordinate.cpp + CrashHandler.cpp DummyReader.cpp ReaderBase.cpp RendererBase.cpp @@ -289,6 +290,11 @@ SET ( REQUIRED_LIBRARIES SET ( REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} ${BLACKMAGIC_LIBRARY_DIR} ) ENDIF (BLACKMAGIC_FOUND) + IF (WIN32) + # Required for exception handling on Windows + SET ( REQUIRED_LIBRARIES ${REQUIRED_LIBRARIES} "imagehlp" ) + ENDIF(WIN32) + # Link all referenced libraries target_link_libraries(openshot ${REQUIRED_LIBRARIES}) diff --git a/src/CrashHandler.cpp b/src/CrashHandler.cpp new file mode 100644 index 00000000..95e9c84a --- /dev/null +++ b/src/CrashHandler.cpp @@ -0,0 +1,319 @@ +/** + * @file + * @brief Source file for CrashHandler class + * @author Jonathan Thomas + * + * @section LICENSE + * + * Copyright (c) 2008-2014 OpenShot Studios, LLC + * . This file is part of + * OpenShot Library (libopenshot), an open-source project dedicated to + * delivering high quality video editing and animation solutions to the + * world. For more information visit . + * + * OpenShot Library (libopenshot) is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * OpenShot Library (libopenshot) is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with OpenShot Library. If not, see . + */ + +#include "../include/CrashHandler.h" + +using namespace std; +using namespace openshot; + + +// Global reference to logger +CrashHandler *CrashHandler::m_pInstance = NULL; + +// Create or Get an instance of the logger singleton +CrashHandler *CrashHandler::Instance() +{ + if (!m_pInstance) { + // Create the actual instance of crash handler only once + m_pInstance = new CrashHandler; + +#ifdef __MINGW32__ + // TODO: Windows exception handling methods + signal(SIGSEGV, CrashHandler::abortHandler); + +#else + struct sigaction sa; + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = CrashHandler::abortHandler; + sigemptyset( &sa.sa_mask ); + + // Register abortHandler function callback + sigaction( SIGABRT, &sa, NULL ); + sigaction( SIGSEGV, &sa, NULL ); + sigaction( SIGBUS, &sa, NULL ); + sigaction( SIGILL, &sa, NULL ); + sigaction( SIGFPE, &sa, NULL ); + sigaction( SIGPIPE, &sa, NULL ); +#endif + } + + return m_pInstance; +} + +#ifdef __MINGW32__ +// Windows exception handler +void CrashHandler::abortHandler(int signum) +{ + // Associate each signal with a signal name string. + const char* name = NULL; + switch( signum ) + { + case SIGABRT: name = "SIGABRT"; break; + case SIGSEGV: name = "SIGSEGV"; break; + case SIGILL: name = "SIGILL"; break; + case SIGFPE: name = "SIGFPE"; break; + } + + // Notify the user which signal was caught + if ( name ) + fprintf( stderr, "Caught signal %d (%s)\n", signum, name ); + else + fprintf( stderr, "Caught signal %d\n", signum ); + + // Dump a stack trace. + printStackTrace(stderr, 63); + + // Quit + exit( signum ); +} +#else +// Linux and Mac Exception Handler +void CrashHandler::abortHandler( int signum, siginfo_t* si, void* unused ) +{ + // Associate each signal with a signal name string. + const char* name = NULL; + switch( signum ) + { + case SIGABRT: name = "SIGABRT"; break; + case SIGSEGV: name = "SIGSEGV"; break; + case SIGBUS: name = "SIGBUS"; break; + case SIGILL: name = "SIGILL"; break; + case SIGFPE: name = "SIGFPE"; break; + case SIGPIPE: name = "SIGPIPE"; break; + } + + // Notify the user which signal was caught + if ( name ) + fprintf( stderr, "Caught signal %d (%s)\n", signum, name ); + else + fprintf( stderr, "Caught signal %d\n", signum ); + + // Dump a stack trace. + printStackTrace(stderr, 63); + + // Quit + exit( signum ); +} +#endif + +void CrashHandler::printStackTrace(FILE *out, unsigned int max_frames) +{ + fprintf(out, "---- Unhandled Exception: Stack Trace ----\n"); + ZmqLogger::Instance()->LogToFile("---- Unhandled Exception: Stack Trace ----\n"); + stringstream stack_output; + +#ifdef __MINGW32__ + // Windows stack unwinding + HANDLE process = GetCurrentProcess(); + HANDLE thread = GetCurrentThread(); + + CONTEXT context; + memset(&context, 0, sizeof(CONTEXT)); + context.ContextFlags = CONTEXT_FULL; + RtlCaptureContext(&context); + + SymInitialize(process, NULL, TRUE); + + DWORD image; + STACKFRAME64 stackframe; + ZeroMemory(&stackframe, sizeof(STACKFRAME64)); + +#ifdef _M_IX86 + image = IMAGE_FILE_MACHINE_I386; + stackframe.AddrPC.Offset = context.Eip; + stackframe.AddrPC.Mode = AddrModeFlat; + stackframe.AddrFrame.Offset = context.Ebp; + stackframe.AddrFrame.Mode = AddrModeFlat; + stackframe.AddrStack.Offset = context.Esp; + stackframe.AddrStack.Mode = AddrModeFlat; +#elif _M_X64 + image = IMAGE_FILE_MACHINE_AMD64; + stackframe.AddrPC.Offset = context.Rip; + stackframe.AddrPC.Mode = AddrModeFlat; + stackframe.AddrFrame.Offset = context.Rsp; + stackframe.AddrFrame.Mode = AddrModeFlat; + stackframe.AddrStack.Offset = context.Rsp; + stackframe.AddrStack.Mode = AddrModeFlat; +#elif _M_IA64 + image = IMAGE_FILE_MACHINE_IA64; + stackframe.AddrPC.Offset = context.StIIP; + stackframe.AddrPC.Mode = AddrModeFlat; + stackframe.AddrFrame.Offset = context.IntSp; + stackframe.AddrFrame.Mode = AddrModeFlat; + stackframe.AddrBStore.Offset = context.RsBSP; + stackframe.AddrBStore.Mode = AddrModeFlat; + stackframe.AddrStack.Offset = context.IntSp; + stackframe.AddrStack.Mode = AddrModeFlat; +#endif + + // Loop through the entire stack + for (size_t i = 0; i < max_frames; i++) { + + BOOL result = StackWalk64( + image, process, thread, + &stackframe, &context, NULL, + SymFunctionTableAccess64, SymGetModuleBase64, NULL); + + if (i <= 2) { continue; } // Skip the first 3 elements (those relate to these functions) + if (!result) { break; } + + char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; + PSYMBOL_INFO symbol = (PSYMBOL_INFO)buffer; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + symbol->MaxNameLen = MAX_SYM_NAME; + WINBOOL found_symbol = SymFromAddr(process, stackframe.AddrPC.Offset, NULL, symbol); + + if (found_symbol) { + printf("[%i] %s, address 0x%0X\n", i, symbol->Name, symbol->Address); + stack_output << left << setw(30) << symbol->Name << " " << setw(40) << std::hex << symbol->Address << std::dec << endl; + } else { + printf("[%i] ???\n", i); + stack_output << left << setw(30) << "???" << endl; + } + } + SymCleanup(process); + +#else + // Storage array for stack trace address data + void* addrlist[max_frames+1]; + + // Retrieve current stack addresses + unsigned int addrlen = backtrace( addrlist, sizeof( addrlist ) / sizeof( void* )); + + if ( addrlen == 0 ) + { + fprintf(out, " No stack trace found (addrlen == 0)\n"); + ZmqLogger::Instance()->LogToFile(" No stack trace found (addrlen == 0)\n"); + return; + } + + // Resolve addresses into strings containing "filename(function+address)", + // Actually it will be ## program address function + offset + // this array must be free()-ed + char** symbollist = backtrace_symbols( addrlist, addrlen ); + + size_t funcnamesize = 1024; + char funcname[1024]; + + // Iterate over the returned symbol lines. Skip the first 4, it is the + // address of this function. + for ( unsigned int i = 4; i < addrlen; i++ ) + { + char* begin_name = NULL; + char* begin_offset = NULL; + char* end_offset = NULL; + + // Find parentheses and +address offset surrounding the mangled name +#ifdef DARWIN + // OSX style stack trace + for ( char *p = symbollist[i]; *p; ++p ) + { + if (( *p == '_' ) && ( *(p-1) == ' ' )) + begin_name = p-1; + else if ( *p == '+' ) + begin_offset = p-1; + } + + if ( begin_name && begin_offset && ( begin_name < begin_offset )) + { + *begin_name++ = '\0'; + *begin_offset++ = '\0'; + + // Mangled name is now in [begin_name, begin_offset) and caller + // offset in [begin_offset, end_offset). now apply + // __cxa_demangle(): + int status; + char* ret = abi::__cxa_demangle( begin_name, &funcname[0], &funcnamesize, &status ); + if ( status == 0 ) + { + funcname = ret; // Use possibly realloc()-ed string + fprintf( out, " %-30s %-40s %s\n", symbollist[i], funcname, begin_offset ); + stack_output << left << " " << setw(30) << symbollist[i] << " " << setw(40) << funcname << " " << begin_offset << endl; + } else { + // Demangling failed. Output function name as a C function with + // no arguments. + fprintf( out, " %-30s %-38s() %s\n", symbollist[i], begin_name, begin_offset ); + stack_output << left << " " << setw(30) << symbollist[i] << " " << setw(38) << begin_name << " " << begin_offset << endl; + } + +#else // !DARWIN - but is posix + // not OSX style + // ./module(function+0x15c) [0x8048a6d] + for ( char *p = symbollist[i]; *p; ++p ) + { + if ( *p == '(' ) + begin_name = p; + else if ( *p == '+' ) + begin_offset = p; + else if ( *p == ')' && ( begin_offset || begin_name )) + end_offset = p; + } + + if ( begin_name && end_offset && ( begin_name < end_offset )) + { + *begin_name++ = '\0'; + *end_offset++ = '\0'; + if ( begin_offset ) + *begin_offset++ = '\0'; + + // Mangled name is now in [begin_name, begin_offset) and caller + // offset in [begin_offset, end_offset). now apply + // __cxa_demangle(): + int status = 0; + char* ret = abi::__cxa_demangle( begin_name, funcname, &funcnamesize, &status ); + char* fname = begin_name; + if ( status == 0 ) + fname = ret; + + if ( begin_offset ) + { + fprintf( out, " %-30s ( %-40s + %-6s) %s\n", symbollist[i], fname, begin_offset, end_offset ); + stack_output << left << " " << setw(30) << symbollist[i] << " " << setw(40) << fname << " " << begin_offset << " " << end_offset << endl; + + } else { + fprintf( out, " %-30s ( %-40s %-6s) %s\n", symbollist[i], fname, "", end_offset ); + stack_output << left << " " << setw(30) << symbollist[i] << " " << setw(40) << fname << " " << end_offset << endl; + + } +#endif // !DARWIN - but is posix + } else { + // Couldn't parse the line? print the whole line. + fprintf(out, " %-40s\n", symbollist[i]); + stack_output << left << " " << setw(40) << symbollist[i] << endl; + } + } + + // Free array + free(symbollist); +#endif + + // Write stacktrace to file (if log path set) + ZmqLogger::Instance()->LogToFile(stack_output.str()); + + fprintf(out, "---- End of Stack Trace ----\n"); + ZmqLogger::Instance()->LogToFile("---- End of Stack Trace ----\n"); +} \ No newline at end of file diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index 00f9d316..0793ee96 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -277,7 +277,7 @@ void FFmpegReader::UpdateAudioInfo() info.acodec = aCodecCtx->codec->name; info.channels = aCodecCtx->channels; if (aCodecCtx->channel_layout == 0) - aCodecCtx->channel_layout = av_get_default_channel_layout( aCodecCtx->channels );; + aCodecCtx->channel_layout = av_get_default_channel_layout( aCodecCtx->channels ); info.channel_layout = (ChannelLayout) aCodecCtx->channel_layout; info.sample_rate = aCodecCtx->sample_rate; info.audio_bit_rate = aCodecCtx->bit_rate; diff --git a/src/Timeline.cpp b/src/Timeline.cpp index ff8b1ed1..7cfb7763 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -33,6 +33,9 @@ using namespace openshot; Timeline::Timeline(int width, int height, Fraction fps, int sample_rate, int channels, ChannelLayout channel_layout) : is_open(false), auto_map_clips(true) { + // Create CrashHandler and Attach (incase of errors) + CrashHandler::Instance(); + // Init viewport size (curve based, because it can be animated) viewport_scale = Keyframe(100.0); viewport_x = Keyframe(0.0); diff --git a/src/ZmqLogger.cpp b/src/ZmqLogger.cpp index db0abb42..27da2977 100644 --- a/src/ZmqLogger.cpp +++ b/src/ZmqLogger.cpp @@ -113,7 +113,15 @@ void ZmqLogger::Log(string message) // Write to log file (if opened, and force it to write to disk in case of a crash) if (log_file.is_open()) - log_file << message << std::flush;; + log_file << message << std::flush; +} + +// Log message to a file (if path set) +void ZmqLogger::LogToFile(string message) +{ + // Write to log file (if opened, and force it to write to disk in case of a crash) + if (log_file.is_open()) + log_file << message << std::flush; } void ZmqLogger::Path(string new_path)