src/crashcatcher.cpp

changeset 513
29eb671b34f6
child 514
d78fea0f664c
equal deleted inserted replaced
512:adab82ab13a5 513:29eb671b34f6
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 handleSignal (int sig)
31 { printf ("caught signal %d\n", sig);
32
33 if (g_crashCatcherActive)
34 { printf ("caught signal while crash catcher is active!\n");
35 return;
36 }
37
38 if (g_signalsToCatch.indexOf (sig) != -1)
39 { const pid_t pid = getpid();
40 QProcess proc;
41 QTemporaryFile commandsFile;
42
43 g_crashCatcherActive = true;
44 printf ("%s: crashed with signal %d, launching gdb\n", __func__, sig);
45
46 if (commandsFile.open())
47 { commandsFile.write (fmt ("attach %1\n", pid).toLocal8Bit());
48 commandsFile.write (str ("backtrace full\n").toLocal8Bit());
49 commandsFile.write (str ("detach\n").toLocal8Bit());
50 commandsFile.write (str ("quit").toLocal8Bit());
51 commandsFile.flush();
52 commandsFile.close();
53 }
54
55 QStringList args ({"-x", commandsFile.fileName()});
56
57 proc.start ("gdb", args);
58
59 // Linux doesn't allow ptrace to be used on anything but direct child processes
60 // so we need to use prctl to register an exception to this to allow GDB attach to us.
61 // We need to do this now and no earlier because only now we actually know GDB's PID.
62 prctl (PR_SET_PTRACER, proc.pid(), 0, 0, 0);
63
64 proc.waitForFinished (5000);
65 str output = QString (proc.readAllStandardOutput());
66 str err = QString (proc.readAllStandardError());
67
68 bombBox (fmt ("<h3>Program crashed with signal %1</h3>\n\n"
69 "%2"
70 "<p><b>GDB <tt>stdout</tt>:</b></p><pre>%3</pre>\n"
71 "<p><b>GDB <tt>stderr</tt>:</b></p><pre>%4</pre>",
72 sig, (!g_assertionFailure.isEmpty()) ? g_assertionFailure : "", output, err));
73 exit (137);
74 }
75 }
76
77 // =============================================================================
78 // -----------------------------------------------------------------------------
79 void initCrashCatcher()
80 { struct sigaction sighandler;
81 sighandler.sa_handler = &handleSignal;
82 sighandler.sa_flags = 0;
83 sigemptyset (&sighandler.sa_mask);
84
85 for (int sig : g_signalsToCatch)
86 sigaction (sig, &sighandler, null);
87
88 log ("%1: crash catcher hooked to signals: %2\n", __func__, g_signalsToCatch);
89 }
90 #endif // #ifdef __unix__
91
92 // =============================================================================
93 // This function must be readily available in both Windows and Linux. We display
94 // the bomb box straight in Windows while in Linux we let abort() trigger the
95 // signal handler, which will cause the usual bomb box with GDB diagnostics.
96 // Said prompt will embed the assertion failure information.
97 // -----------------------------------------------------------------------------
98 void assertionFailure (const char* file, int line, const char* funcname, const char* expr)
99 { str errmsg = fmt (
100 "<p><b>File</b>: <tt>%1</tt><br />"
101 "<b>Line</b>: <tt>%2</tt><br />"
102 "<b>Function:</b> <tt>%3</tt></p>"
103 "<p>Assertion <b><tt>`%4'</tt></b> failed.</p>",
104 file, line, funcname, expr);
105
106 g_assertionFailure = errmsg;
107
108 #ifndef __unix__
109 bombBox (errmsg);
110 #endif
111
112 abort();
113 }

mercurial