From c3785555b9096a27da2035f03561f5fa380b260e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Teuli=C3=A8re?= Date: Sun, 17 Oct 2010 21:23:39 +0000 Subject: [PATCH] Print to stderr the complete stack trace in case of exception or segmentation fault. This feature will only be activated in debug mode, and if available on the platform. The symbols will even be demangled if possible (i.e. if compiled with g++). --- configure.in | 3 ++ dic/Makefile.am | 2 + dic/base_exception.cpp | 44 +++++++++++++++++ dic/base_exception.h | 46 ++++++++++++++++++ dic/dic_exception.cpp | 10 +--- dic/dic_exception.h | 14 ++---- dic/stacktrace.cpp | 91 +++++++++++++++++++++++++++++++++++ dic/stacktrace.h | 38 +++++++++++++++ game/game_exception.cpp | 10 +--- game/game_exception.h | 13 ++--- m4/ax_cxx_gcc_abi_demangle.m4 | 58 ++++++++++++++++++++++ qt/Makefile.am | 2 + qt/main.cpp | 59 ++++++++++++++++++++++- 13 files changed, 353 insertions(+), 37 deletions(-) create mode 100644 dic/base_exception.cpp create mode 100644 dic/base_exception.h create mode 100644 dic/stacktrace.cpp create mode 100644 dic/stacktrace.h create mode 100644 m4/ax_cxx_gcc_abi_demangle.m4 diff --git a/configure.in b/configure.in index 9928373..204aaf1 100644 --- a/configure.in +++ b/configure.in @@ -22,6 +22,9 @@ AC_PROG_MAKE_SET AC_PROG_RANLIB PKG_PROG_PKG_CONFIG +AC_CHECK_HEADERS_ONCE(execinfo.h) +AX_CXX_GCC_ABI_DEMANGLE + dnl -------------------------------------------------------------- dnl Checks for compilation flags dnl -------------------------------------------------------------- diff --git a/dic/Makefile.am b/dic/Makefile.am index 47295ca..6c6c492 100644 --- a/dic/Makefile.am +++ b/dic/Makefile.am @@ -23,6 +23,7 @@ localedir = $(datadir)/locale AM_CPPFLAGS = -DLOCALEDIR=\"$(localedir)\" -I$(top_srcdir) -I../intl -I$(top_srcdir)/intl $(INCICONV) libdic_a_SOURCES = \ + base_exception.cpp base_exception.h \ dic_exception.cpp dic_exception.h \ header.cpp header.h \ dic_internals.h \ @@ -30,6 +31,7 @@ libdic_a_SOURCES = \ dic.cpp dic.h \ dic_search.cpp \ encoding.cpp encoding.h \ + stacktrace.cpp stacktrace.h \ automaton.cpp automaton.h \ regexp.cpp regexp.h \ grammar.cpp grammar.h \ diff --git a/dic/base_exception.cpp b/dic/base_exception.cpp new file mode 100644 index 0000000..06cf5c0 --- /dev/null +++ b/dic/base_exception.cpp @@ -0,0 +1,44 @@ +/***************************************************************************** + * Eliot + * Copyright (C) 2010 Olivier Teulière + * Authors: Olivier Teulière + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + *****************************************************************************/ + +#include "base_exception.h" +#include "stacktrace.h" + +using namespace std; + + +BaseException::BaseException(const string &iMessage) + : m_message(iMessage) +{ + m_stack = StackTrace::GetStack(); +} + + +const char *BaseException::what() const throw() +{ + return m_message.c_str(); +} + + +string BaseException::getStackTrace() const +{ + return m_stack; +} + diff --git a/dic/base_exception.h b/dic/base_exception.h new file mode 100644 index 0000000..1d1371f --- /dev/null +++ b/dic/base_exception.h @@ -0,0 +1,46 @@ +/***************************************************************************** + * Eliot + * Copyright (C) 2010 Olivier Teulière + * Authors: Olivier Teulière + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + *****************************************************************************/ + +#ifndef BASE_EXCEPTION_H_ +#define BASE_EXCEPTION_H_ + +#include +#include + + +/** + * Base exception class for all the exception classes in Eliot. + * It provides a stack trace. + */ +class BaseException: public std::exception +{ + public: + BaseException(const std::string &iMessage); + ~BaseException() throw() {} + virtual const char *what() const throw(); + + std::string getStackTrace() const; + + private: + std::string m_message; + std::string m_stack; +}; + +#endif diff --git a/dic/dic_exception.cpp b/dic/dic_exception.cpp index dabc84c..9b9e3f2 100644 --- a/dic/dic_exception.cpp +++ b/dic/dic_exception.cpp @@ -1,6 +1,6 @@ /***************************************************************************** * Eliot - * Copyright (C) 2007 Olivier Teulière + * Copyright (C) 2007-2010 Olivier Teulière * Authors: Olivier Teulière * * This program is free software; you can redistribute it and/or modify @@ -24,17 +24,11 @@ using namespace std; DicException::DicException(const string &iMessage) - : m_message(iMessage) + : BaseException(iMessage) { } -const char *DicException::what() const throw() -{ - return m_message.c_str(); -} - - InvalidRegexpException::InvalidRegexpException(const string &iMessage) : DicException(iMessage) { diff --git a/dic/dic_exception.h b/dic/dic_exception.h index 6f69b4f..de94822 100644 --- a/dic/dic_exception.h +++ b/dic/dic_exception.h @@ -1,6 +1,6 @@ /***************************************************************************** * Eliot - * Copyright (C) 2007 Olivier Teulière + * Copyright (C) 2007-2010 Olivier Teulière * Authors: Olivier Teulière * * This program is free software; you can redistribute it and/or modify @@ -21,24 +21,19 @@ #ifndef DIC_EXCEPTION_H_ #define DIC_EXCEPTION_H_ -#include #include +#include "base_exception.h" /** * Exception class for the dictionary. - * It simply inherits from the standard exception and overrides + * It simply inherits from the base exception and overrides * its what() method. */ -class DicException: public std::exception +class DicException: public BaseException { public: DicException(const std::string &iMessage); - ~DicException() throw() {} - virtual const char *what() const throw(); - - private: - std::string m_message; }; @@ -46,7 +41,6 @@ class InvalidRegexpException : public DicException { public: InvalidRegexpException(const std::string &iMessage); - ~InvalidRegexpException() throw() {} }; #endif diff --git a/dic/stacktrace.cpp b/dic/stacktrace.cpp new file mode 100644 index 0000000..dc1c4da --- /dev/null +++ b/dic/stacktrace.cpp @@ -0,0 +1,91 @@ +/***************************************************************************** + * Eliot + * Copyright (C) 2010 Olivier Teulière + * Authors: Olivier Teulière + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + *****************************************************************************/ + +#include "config.h" +#include +#include +#include +#include + +#include "stacktrace.h" + +#ifdef HAVE_EXECINFO_H +# include +#endif +#ifdef HAVE_GCC_ABI_DEMANGLE +# include +#endif + +using namespace std; + + +string StackTrace::GetStack() +{ +#if defined HAVE_EXECINFO_H && defined(DEBUG) + static const int MAX_FRAMES = 42; + void *frames[MAX_FRAMES]; + // Get the frames + int nb = backtrace(frames, MAX_FRAMES); + // Get the corresponding symbols (ignoring the first frame) + char **symbols = backtrace_symbols(frames + 1, nb - 1); + // Demangle the symbols and build a nice stack trace + ostringstream oss; + for (int i = 0; i < nb - 1; ++i) + { + oss << " at " << Demangle(symbols[i]) << endl; + } + + return oss.str(); +#else + return ""; +#endif +} + + +// See http://tombarta.wordpress.com/2008/08/01/c-stack-traces-with-gcc/ +// and http://mykospark.net/2009/09/runtime-backtrace-in-c-with-name-demangling/ +// for more details +string StackTrace::Demangle(char *symbol) +{ +#if defined HAVE_GCC_ABI_DEMANGLE && defined(DEBUG) + char temp1[200]; + char temp2[200]; + if (sscanf(symbol, "%199[^(]%*[^_]%199[^)+]", temp1, temp2) == 2) + { + // Try to demangle a C++ name + int status; + char *demangled = abi::__cxa_demangle(temp2, NULL, NULL, &status); + if (demangled != NULL) + { + string result = temp1 + string(": ") + demangled; + free(demangled); + return result; + } + // Try to demangle a C name + else if (sscanf(symbol, "%199s", temp2) == 1) + { + return temp1 + string(": ") + temp2; + } + } + + // Everything else failed, return the symbol +#endif + return symbol; +} diff --git a/dic/stacktrace.h b/dic/stacktrace.h new file mode 100644 index 0000000..3fbb866 --- /dev/null +++ b/dic/stacktrace.h @@ -0,0 +1,38 @@ +/***************************************************************************** + * Eliot + * Copyright (C) 2010 Olivier Teulière + * Authors: Olivier Teulière + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + *****************************************************************************/ + +#ifndef STACK_TRACE_H_ +#define STACK_TRACE_H_ + +#include + +using std::string; + + +class StackTrace +{ +public: + static string GetStack(); + +private: + static string Demangle(char *symbol); +}; + +#endif diff --git a/game/game_exception.cpp b/game/game_exception.cpp index 23ac62b..beccfc5 100644 --- a/game/game_exception.cpp +++ b/game/game_exception.cpp @@ -1,6 +1,6 @@ /***************************************************************************** * Eliot - * Copyright (C) 2007 Olivier Teulière + * Copyright (C) 2007-2010 Olivier Teulière * Authors: Olivier Teulière * * This program is free software; you can redistribute it and/or modify @@ -24,17 +24,11 @@ using namespace std; GameException::GameException(const string &iMessage) - : m_message(iMessage) + : BaseException(iMessage) { } -const char *GameException::what() const throw() -{ - return m_message.c_str(); -} - - EndGameException::EndGameException(const string &iMessage) : GameException(iMessage) { diff --git a/game/game_exception.h b/game/game_exception.h index 1b40029..206adf1 100644 --- a/game/game_exception.h +++ b/game/game_exception.h @@ -1,6 +1,6 @@ /***************************************************************************** * Eliot - * Copyright (C) 2007 Olivier Teulière + * Copyright (C) 2007-2010 Olivier Teulière * Authors: Olivier Teulière * * This program is free software; you can redistribute it and/or modify @@ -21,24 +21,19 @@ #ifndef GAME_EXCEPTION_H_ #define GAME_EXCEPTION_H_ -#include #include +#include "base_exception.h" /** * Exception class for the Game library. - * It simply inherits from the standard exception and overrides + * It simply inherits from the base exception and overrides * its what() method. */ -class GameException: public std::exception +class GameException: public BaseException { public: GameException(const std::string &iMessage); - ~GameException() throw() {} - virtual const char *what() const throw(); - - private: - std::string m_message; }; diff --git a/m4/ax_cxx_gcc_abi_demangle.m4 b/m4/ax_cxx_gcc_abi_demangle.m4 new file mode 100644 index 0000000..b2a4b43 --- /dev/null +++ b/m4/ax_cxx_gcc_abi_demangle.m4 @@ -0,0 +1,58 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_cxx_gcc_abi_demangle.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CXX_GCC_ABI_DEMANGLE +# +# DESCRIPTION +# +# If the compiler supports GCC C++ ABI name demangling (has header +# cxxabi.h and abi::__cxa_demangle() function), define +# HAVE_GCC_ABI_DEMANGLE +# +# Adapted from AX_CXX_RTTI by Luc Maisonobe +# +# LICENSE +# +# Copyright (c) 2008 Neil Ferguson +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 8 + +AC_DEFUN([AX_CXX_GCC_ABI_DEMANGLE], +[AC_CACHE_CHECK(whether the compiler supports GCC C++ ABI name demangling, +ax_cv_cxx_gcc_abi_demangle, +[AC_LANG_SAVE + AC_LANG_CPLUSPLUS + AC_TRY_COMPILE([#include +#include +#include +#include + +template +class A {}; +],[A instance; +int status = 0; +char* c_name = 0; + +c_name = abi::__cxa_demangle(typeid(instance).name(), 0, 0, &status); + +std::string name(c_name); +free(c_name); + +return name == "A"; +], + ax_cv_cxx_gcc_abi_demangle=yes, ax_cv_cxx_gcc_abi_demangle=no) + AC_LANG_RESTORE +]) +if test "$ax_cv_cxx_gcc_abi_demangle" = yes; then + AC_DEFINE(HAVE_GCC_ABI_DEMANGLE,1, + [define if the compiler supports GCC C++ ABI name demangling]) +fi +]) diff --git a/qt/Makefile.am b/qt/Makefile.am index 70dd188..bb5bc42 100644 --- a/qt/Makefile.am +++ b/qt/Makefile.am @@ -104,6 +104,8 @@ BUILT_SOURCES = $(nodist_eliot_SOURCES) MOSTLYCLEANFILES = $(nodist_eliot_SOURCES) eliot_LDADD = ../game/libgame.a ../dic/libdic.a @QT_LIBS@ @LIBINTL@ @LIBCONFIG_LIBS@ @ARABICA_LIBS@ @EXPAT_LDFLAGS@ +# Needed for proper stack trace handling +eliot_LDFLAGS = -rdynamic # Generate a cpp file from the resources resources.cpp: eliot.qrc $(RESOURCES) diff --git a/qt/main.cpp b/qt/main.cpp index ac6cde9..163f94d 100644 --- a/qt/main.cpp +++ b/qt/main.cpp @@ -21,9 +21,13 @@ #include "config.h" #include +#include +#include #include #include #include +#include "base_exception.h" +#include "stacktrace.h" #include "main_window.h" #ifdef WIN32 # include @@ -32,11 +36,48 @@ # include #endif -using std::string; +#ifdef HAVE_EXECINFO_H +# include +# include +#endif +using namespace std; + + +static void bt_sighandler(int); + +// Custom QApplication to catch and log exceptions properly +// See http://forum.qtfr.org/viewtopic.php?id=7615 +class MyApplication : public QApplication +{ +public: + MyApplication(int argc, char **argv) + : QApplication(argc, argv) + {} + + virtual bool notify(QObject *receiver, QEvent *event) + { + try + { + return QApplication::notify(receiver, event); + } + catch (const BaseException &e) + { + cerr << "Exception caught: " << e.what() << endl; + cerr << e.getStackTrace() << endl; + return false; + } + } +}; int main(int argc, char **argv) { +#ifdef HAVE_EXECINFO_H + // Install a custom signal handler to print a backtrace when crashing + // See http://www.linuxjournal.com/article/6391 for inspiration + signal(SIGSEGV, &bt_sighandler); +#endif + // On Mac, running Eliot from the dock does not automatically set the LANG // variable, so we do it ourselves. // Note: The following block of code is copied from VLC, and slightly @@ -77,7 +118,7 @@ int main(int argc, char **argv) #endif #endif - QApplication app(argc, argv); + MyApplication app(argc, argv); app.setWindowIcon(QIcon(":/images/eliot.xpm")); #ifdef ENABLE_NLS @@ -121,3 +162,17 @@ int main(int argc, char **argv) qmain.show(); return app.exec(); } + +#ifdef HAVE_EXECINFO_H +static void bt_sighandler(int signum) +{ + cerr << "Segmentation fault!" << endl; + cerr << "Backtrace:" << endl; + cerr << StackTrace::GetStack() << endl; + + // Restore the default handler to generate a nice core dump + signal(signum, SIG_DFL); + raise(signum); +} +#endif +