src/crashcatcher.cpp

changeset 557
04e140bdeb0b
parent 556
5f4395ec5db0
child 558
5f6e30e0450c
equal deleted inserted replaced
556:5f4395ec5db0 557:04e140bdeb0b
1 #ifdef __unix__
2
3 #include <QString>
4 #include <QProcess>
5 #include <QTemporaryFile>
6 #include <QMessageBox>
7 #include <unistd.h>
8 #include <signal.h>
9 #include <sys/prctl.h>
10 #include "crashcatcher.h"
11 #include "types.h"
12 #include "dialogs.h"
13
14 // Is the crash catcher active now?
15 static bool g_crashCatcherActive = false;
16
17 // If an assertion failed, what was it?
18 static str g_assertionFailure;
19
20 // List of signals to catch and crash on
21 static QList<int> g_signalsToCatch ({
22 SIGSEGV, // segmentation fault
23 SIGABRT, // abort() calls
24 SIGFPE, // floating point exceptions (e.g. division by zero)
25 SIGILL, // illegal instructions
26 });
27
28 // =============================================================================
29 // -----------------------------------------------------------------------------
30 static void handleCrash (int sig)
31 { printf ("%s: crashed with signal %d, launching gdb\n", __func__, sig);
32
33 if (g_crashCatcherActive)
34 { printf ("caught signal while crash catcher is active!\n");
35 exit (149);
36 }
37
38 const pid_t pid = getpid();
39 QProcess proc;
40 QTemporaryFile commandsFile;
41
42 g_crashCatcherActive = true;
43
44 if (commandsFile.open())
45 { commandsFile.write (fmt ("attach %1\n", pid).toLocal8Bit());
46 commandsFile.write (str ("backtrace full\n").toLocal8Bit());
47 commandsFile.write (str ("detach\n").toLocal8Bit());
48 commandsFile.write (str ("quit").toLocal8Bit());
49 commandsFile.flush();
50 commandsFile.close();
51 }
52
53 QStringList args ({"-x", commandsFile.fileName()});
54
55 proc.start ("gdb", args);
56
57 // Linux doesn't allow ptrace to be used on anything but direct child processes
58 // so we need to use prctl to register an exception to this to allow GDB attach to us.
59 // We need to do this now and no earlier because only now we actually know GDB's PID.
60 prctl (PR_SET_PTRACER, proc.pid(), 0, 0, 0);
61
62 proc.waitForFinished (1000);
63 str output = QString (proc.readAllStandardOutput());
64 str err = QString (proc.readAllStandardError());
65
66 bombBox (fmt ("<h3>Program crashed with signal %1</h3>\n\n"
67 "%2"
68 "<p><b>GDB <tt>stdout</tt>:</b></p><pre>%3</pre>\n"
69 "<p><b>GDB <tt>stderr</tt>:</b></p><pre>%4</pre>",
70 sig, (!g_assertionFailure.isEmpty()) ? g_assertionFailure : "", output, err));
71 }
72
73 // =============================================================================
74 // -----------------------------------------------------------------------------
75 void initCrashCatcher()
76 { struct sigaction sighandler;
77 sighandler.sa_handler = &handleCrash;
78 sighandler.sa_flags = 0;
79 sigemptyset (&sighandler.sa_mask);
80
81 for (int sig : g_signalsToCatch)
82 sigaction (sig, &sighandler, null);
83
84 log ("%1: crash catcher hooked to signals: %2\n", __func__, g_signalsToCatch);
85 }
86 #endif // #ifdef __unix__
87
88 // =============================================================================
89 // This function must be readily available in both Windows and Linux. We display
90 // the bomb box straight in Windows while in Linux we let abort() trigger the
91 // signal handler, which will cause the usual bomb box with GDB diagnostics.
92 // Said prompt will embed the assertion failure information.
93 // -----------------------------------------------------------------------------
94 void assertionFailure (const char* file, int line, const char* funcname, const char* expr)
95 { str errmsg = fmt (
96 "<p><b>File</b>: <tt>%1</tt><br />"
97 "<b>Line</b>: <tt>%2</tt><br />"
98 "<b>Function:</b> <tt>%3</tt></p>"
99 "<p>Assertion <b><tt>`%4'</tt></b> failed.</p>",
100 file, line, funcname, expr);
101
102 g_assertionFailure = errmsg;
103
104 #ifndef __unix__
105 bombBox (errmsg);
106 #endif
107
108 abort();
109 }

mercurial