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
This commit is contained in:
Matthias Koefferlein 2017-09-03 19:26:52 +02:00
parent 80226e8c1c
commit dfbe0b3122
4 changed files with 213 additions and 41 deletions

View File

@ -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 ();

View File

@ -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;
}
}

View File

@ -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;
};
}

View File

@ -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 <windows.h>
@ -43,7 +46,9 @@
#endif
#include <signal.h>
#include <cstdio>
#include <QFileInfo>
#include <QMessageBox>
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<CrashMessage> 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);
}
}