src/crashCatcher.cpp

changeset 931
85080f7a1c20
parent 927
409b82a4765e
child 941
f895379d7fab
equal deleted inserted replaced
929:66e8453e1768 931:85080f7a1c20
1 /*
2 * LDForge: LDraw parts authoring CAD
3 * Copyright (C) 2013 - 2015 Teemu Piippo
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <QProcess>
20 #include <QTemporaryFile>
21 #include <unistd.h>
22 #include <signal.h>
23 #include "crashCatcher.h"
24 #include "dialogs.h"
25
26 #ifdef __unix__
27 # ifdef Q_OS_LINUX
28 # include <sys/prctl.h>
29 # endif
30
31 // Is the crash catcher active now?
32 static bool IsCrashCatcherActive = false;
33
34 // If an assertion failed, what was it?
35 static QString AssertionFailureText;
36
37 // List of signals to catch and crash on
38 static QList<int> SignalsToCatch ({
39 SIGSEGV, // segmentation fault
40 SIGABRT, // abort() calls
41 SIGFPE, // floating point exceptions (e.g. division by zero)
42 SIGILL, // illegal instructions
43 });
44
45 // -------------------------------------------------------------------------------------------------
46 //
47 // Removes the signal handler from SIGABRT and then aborts.
48 //
49 static void FinalAbort()
50 {
51 struct sigaction sighandler;
52 sighandler.sa_handler = SIG_DFL;
53 sighandler.sa_flags = 0;
54 sigaction (SIGABRT, &sighandler, 0);
55 abort();
56 }
57
58 // -------------------------------------------------------------------------------------------------
59 //
60 static void HandleCrash (int sig)
61 {
62 printf ("!! Caught signal %d, launching gdb\n", sig);
63
64 if (IsCrashCatcherActive)
65 {
66 printf ("Caught signal while crash catcher is active! Execution cannot continue.\n");
67 FinalAbort();
68 }
69
70 pid_t const pid (getpid());
71 QProcess proc;
72 QTemporaryFile commandsFile;
73
74 IsCrashCatcherActive = true;
75
76 if (commandsFile.open())
77 {
78 commandsFile.write (format ("attach %1\n", pid).toLocal8Bit());
79 commandsFile.write (QString ("backtrace full\n").toLocal8Bit());
80 commandsFile.write (QString ("detach\n").toLocal8Bit());
81 commandsFile.write (QString ("quit").toLocal8Bit());
82 commandsFile.close();
83 }
84
85 proc.start ("gdb", {"-x", commandsFile.fileName()});
86
87 // Linux doesn't allow ptrace to be used on anything but direct child processes
88 // so we need to use prctl to register an exception to this to allow GDB attach to us.
89 // We need to do this now and no earlier because only now we actually know GDB's PID.
90 #ifdef Q_OS_LINUX
91 prctl (PR_SET_PTRACER, proc.pid(), 0, 0, 0);
92 #endif
93
94 proc.waitForFinished (1000);
95 QString output (proc.readAllStandardOutput());
96 QString err (proc.readAllStandardError());
97 QFile f (UNIXNAME "-crash.log");
98
99 if (f.open (QIODevice::WriteOnly))
100 {
101 fprint (f, format ("=== Program crashed with signal %1 ===\n\n%2"
102 "GDB stdout:\n%3\nGDB stderr:\n%4\n", sig,
103 (not AssertionFailureText.isEmpty()) ? AssertionFailureText + "\n\n" : "",
104 output, err));
105 f.close();
106 }
107
108 if (not AssertionFailureText.isEmpty())
109 printf ("Assertion failed: \"%s\".\n", qPrintable (AssertionFailureText));
110
111 printf ("Backtrace written to " UNIXNAME "-crash.log. Aborting.\n");
112 FinalAbort();
113 }
114
115 // -------------------------------------------------------------------------------------------------
116 //
117 // Initializes the crash catcher.
118 //
119 void InitCrashCatcher()
120 {
121 struct sigaction sighandler;
122 sighandler.sa_handler = &HandleCrash;
123 sighandler.sa_flags = 0;
124 sigemptyset (&sighandler.sa_mask);
125
126 for (int sig : SignalsToCatch)
127 {
128 if (sigaction (sig, &sighandler, null) == -1)
129 {
130 fprint (stderr, "Couldn't set signal handler %1: %2", sig, strerror (errno));
131 SignalsToCatch.removeOne (sig);
132 }
133 }
134
135 print ("Crash catcher hooked to signals: %1\n", SignalsToCatch);
136 }
137
138 #endif // #ifdef __unix__
139
140 // -------------------------------------------------------------------------------------------------
141 //
142 // This function catches an assertion failure. It must be readily available in both Windows and
143 // Linux. We display the bomb box straight in Windows while in Linux we let abort() trigger
144 // the signal handler, which will cause the usual bomb box with GDB diagnostics. Said prompt will
145 // embed the assertion failure information.
146 //
147 void HandleAssertFailure (const char* file, int line, const char* funcname, const char* expr)
148 {
149 #ifdef __unix__
150 AssertionFailureText = format ("%1:%2: %3: %4", file, line, funcname, expr);
151 #else
152 DisplayBombBox (format (
153 "<p><b>File</b>: <tt>%1</tt><br />"
154 "<b>Line</b>: <tt>%2</tt><br />"
155 "<b>Function:</b> <tt>%3</tt></p>"
156 "<p>Assertion <b><tt>`%4'</tt></b> failed.</p>",
157 file, line, funcname, expr));
158 #endif
159
160 abort();
161 }

mercurial