From dfbe0b3122410955f1e1eed5edfc791e65132be8 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 3 Sep 2017 19:26:52 +0200 Subject: [PATCH] Enhanced crash handler on Linux * Override Ruby's crash handler to stop blaming Ruby for KLayout segfaults ... * Using addr2line on Linux to obtain DWARF debug info if available * A more elaborate crash handler dialog --- src/lay/lay/layApplication.cc | 8 +- src/lay/lay/layCrashMessage.cc | 33 ++++++- src/lay/lay/layCrashMessage.h | 43 +++++++- src/lay/lay/laySignalHandler.cc | 170 ++++++++++++++++++++++++++------ 4 files changed, 213 insertions(+), 41 deletions(-) diff --git a/src/lay/lay/layApplication.cc b/src/lay/lay/layApplication.cc index bcfd66b87..9a9681786 100644 --- a/src/lay/lay/layApplication.cc +++ b/src/lay/lay/layApplication.cc @@ -671,11 +671,9 @@ Application::Application (int &argc, char **argv, bool non_ui_mode) } - if (! m_no_gui) { - // Install the signal handlers after the interpreters, so we can be sure we - // installed our handler. - install_signal_handlers (); - } + // Install the signal handlers after the interpreters, so we can be sure we + // installed our handler. + install_signal_handlers (); lay::SaltController *sc = lay::SaltController::instance (); lay::TechnologyController *tc = lay::TechnologyController::instance (); diff --git a/src/lay/lay/layCrashMessage.cc b/src/lay/lay/layCrashMessage.cc index bfc56e21e..97ef3150b 100644 --- a/src/lay/lay/layCrashMessage.cc +++ b/src/lay/lay/layCrashMessage.cc @@ -28,16 +28,39 @@ namespace lay { -CrashMessage::CrashMessage (QWidget *parent, bool can_resume, const QString &stack_trace) +CrashMessage::CrashMessage (QWidget *parent, bool can_resume, const QString &t) : QDialog (parent, Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint) { setupUi (this); + m_cancel_pressed = false; - text->setPlainText (stack_trace); + text->setPlainText (t); + set_can_resume (can_resume); - if (! can_resume) { - buttonBox->removeButton (buttonBox->button (QDialogButtonBox::Ok)); - } + connect (buttonBox->button (QDialogButtonBox::Cancel), SIGNAL (pressed ()), this, SLOT (cancel_pressed ())); +} + +CrashMessage::~CrashMessage () +{ + // .. nothing yet .. +} + +void +CrashMessage::set_can_resume (bool f) +{ + buttonBox->button (QDialogButtonBox::Ok)->setVisible (f); +} + +void +CrashMessage::set_text (const QString &t) +{ + text->setPlainText (t); +} + +void +CrashMessage::cancel_pressed () +{ + m_cancel_pressed = true; } } diff --git a/src/lay/lay/layCrashMessage.h b/src/lay/lay/layCrashMessage.h index 6ca1aaf3a..9a0f86313 100644 --- a/src/lay/lay/layCrashMessage.h +++ b/src/lay/lay/layCrashMessage.h @@ -38,14 +38,53 @@ namespace lay class LAY_PUBLIC CrashMessage : public QDialog, private Ui::CrashMessage { +Q_OBJECT + public: /** * @brief Instantiate a dialog * * @param can_resume If true, an "Ok" button is provided - * @param stack_trace The stack trace message shown in the window + * @param text The message shown in the window */ - CrashMessage (QWidget *parent, bool can_resume, const QString &stack_trace); + CrashMessage (QWidget *parent, bool can_resume, const QString &text); + + /** + * @brief Destructor + */ + virtual ~CrashMessage (); + + /** + * @brief Configures the dialog for "can resume" or "can't resume" + */ + void set_can_resume (bool f); + + /** + * @brief Sets the text + */ + void set_text (const QString &text); + + /** + * @brief Gets a value indicating whether the Cancel button was pressed + */ + bool is_cancel_pressed () + { + return m_cancel_pressed; + } + + /** + * @brief Resets the flag indicating whether Cancel was pressed + */ + void reset_cancel_pressed () + { + m_cancel_pressed = false; + } + +private slots: + void cancel_pressed (); + +private: + bool m_cancel_pressed; }; } diff --git a/src/lay/lay/laySignalHandler.cc b/src/lay/lay/laySignalHandler.cc index 68d6aceb1..bbeae0136 100644 --- a/src/lay/lay/laySignalHandler.cc +++ b/src/lay/lay/laySignalHandler.cc @@ -23,7 +23,10 @@ #include "laySignalHandler.h" #include "layCrashMessage.h" #include "layVersion.h" +#include "layApplication.h" #include "tlException.h" +#include "tlString.h" +#include "tlLog.h" #ifdef _WIN32 # include @@ -43,7 +46,9 @@ #endif #include +#include #include +#include namespace lay { @@ -268,49 +273,156 @@ void signal_handler (int signo, siginfo_t *si, void *) size_t nptrs = backtrace (array, sizeof (array) / sizeof (array[0])); - QString text; - text += QObject::tr ("Signal number: %1\n").arg (signo); - text += QObject::tr ("Address: 0x%1\n").arg ((size_t) si->si_addr, 0, 16); - text += QObject::tr ("Program Version: ") + - QString::fromUtf8 (lay::Version::name ()) + - QString::fromUtf8 (" ") + - QString::fromUtf8 (lay::Version::version ()) + - QString::fromUtf8 (" (") + - QString::fromUtf8 (lay::Version::subversion ()) + - QString::fromUtf8 (")"); - text += QString::fromUtf8 ("\n"); - text += QObject::tr ("Backtrace:\n"); + std::string text; + text += tl::sprintf ("Signal number: %d\n", signo); + text += tl::sprintf ("Address: 0x%lx\n", (unsigned long) si->si_addr); + text += std::string ("Program Version: ") + + lay::Version::name () + " " + + lay::Version::version () + " (" + lay::Version::subversion () + ")\n"; + std::auto_ptr msg; + + bool has_gui = lay::Application::instance () && lay::Application::instance ()->has_gui (); + + if (has_gui) { + msg.reset (new CrashMessage (0, false, tl::to_qstring (text) + QObject::tr ("\nCollecting backtrace .."))); + msg->show (); + lay::Application::instance ()->setOverrideCursor (Qt::WaitCursor); + } + + text += std::string ("\nBacktrace:\n"); + +#if 0 + + // the approach with backtrace_symbols - this does not resolve shared object symbols char **symbols = backtrace_symbols (array, nptrs); if (symbols == NULL) { - text += QObject::tr ("-- Unable to obtain stack trace --"); + text += "-- Unable to obtain stack trace --\n"; } else { for (size_t i = 2; i < nptrs; i++) { - text += QString::fromUtf8 (symbols [i]) + QString::fromUtf8 ("\n"); + text += std::string (symbols [i]) + "\n"; } } free(symbols); - // YES! I! KNOW! - // In a signal handler you shall not do fancy stuff (in particular not - // open dialogs) nor shall you throw exceptions! But that scheme appears to - // be working since in most cases the signal is raised from our code (hence - // from our stack frames) and everything is better than just core dumping. - // Isn't it? +#else - CrashMessage msg (0, can_resume, text); - if (! msg.exec ()) { + // the more elaborate approach using the addr2line external tool to obtain debug information + // (if available) - _exit (signo); + const char *addr2line_call = "addr2line -C -s -f -e '%s' 0x%lx"; + + bool has_addr2line = true; + for (size_t i = 0; i < nptrs; ++i) { + + if (has_gui) { + lay::Application::instance ()->processEvents (); + if (msg->is_cancel_pressed ()) { + text += "...\n"; + break; + } + } + + Dl_info info; + dladdr (array [i], &info); + + if (info.dli_fname) { + + char sym [1024], source [1024]; + sym[0] = 0; + source[0] = 0; + + if (has_addr2line) { + + // two tries: one with the relativew address (for shared object) and one with + // absolute address. + // TODO: is there a better way to decide how to use addr2line (with executables)? + for (int abs_addr = 0; abs_addr < 2; ++abs_addr) { + + std::string cmd = tl::sprintf (addr2line_call, info.dli_fname, size_t (array[i]) - (abs_addr ? 0 : size_t (info.dli_fbase))); + FILE *addr2line_out = popen (cmd.c_str (), "r"); + if (! addr2line_out) { + has_addr2line = false; + } + + if (has_addr2line && ! fgets (sym, sizeof (sym) - 1, addr2line_out)) { + has_addr2line = false; + } + if (has_addr2line && ! fgets (source, sizeof (source) - 1, addr2line_out)) { + has_addr2line = false; + } + + int l; + l = strlen (sym); + if (l > 0 && sym[l - 1] == '\n') { + sym[l - 1] = 0; + } + l = strlen (source); + if (l > 0 && source[l - 1] == '\n') { + source[l - 1] = 0; + } + + if (addr2line_out) { + fclose (addr2line_out); + } + + // addr2line returns '??' on missing symbol - in that case use absolute address mode + if (sym[0] != '?') { + break; + } + + } + + } + + if (has_addr2line) { + text += tl::sprintf ("%s +0x%lx %s [%s]\n", info.dli_fname, size_t (array[i]) - size_t (info.dli_fbase), (const char *) sym, (const char *) source); + } else if (info.dli_sname) { + text += tl::sprintf ("%s +0x%lx %s\n", info.dli_fname, size_t (array[i]) - size_t (info.dli_fbase), info.dli_sname); + } else { + text += tl::sprintf ("%s +0x%lx\n", info.dli_fname, size_t (array[i]) - size_t (info.dli_fbase)); + } + + } else { + text += tl::sprintf ("0x%lx\n", (unsigned long)array[i]); + } + } + +#endif + + if (has_gui) { + + lay::Application::instance ()->setOverrideCursor (QCursor ()); + + // YES! I! KNOW! + // In a signal handler you shall not do fancy stuff (in particular not + // open dialogs) nor shall you throw exceptions! But that scheme appears to + // be working since in most cases the signal is raised from our code (hence + // from our stack frames) and everything is better than just core dumping. + // Isn't it? + + msg->set_text (tl::to_qstring (text)); + msg->set_can_resume (can_resume); + + if (! msg->exec ()) { + + _exit (signo); + + } else { + + sigset_t x; + sigemptyset (&x); + sigaddset(&x, signo); + sigprocmask(SIG_UNBLOCK, &x, NULL); + + throw tl::CancelException (); + + } } else { - sigset_t x; - sigemptyset (&x); - sigaddset(&x, signo); - sigprocmask(SIG_UNBLOCK, &x, NULL); - - throw tl::CancelException (); + tl::error << text << tl::noendl; + _exit (signo); } }