1 #include "CrashHandler.h"
2 #include "BugReporting.h"
3
4 #include <QMessageBox>
5 #include <QPushButton>
6 #include <QFileDialog>
7 #include <QStandardPaths>
8 #include <QTime>
9
10 #ifdef CUTTER_ENABLE_CRASH_REPORTS
11
12 #include <QApplication>
13 #include <QString>
14 #include <QFile>
15 #include <QDir>
16 #include <QMap>
17 #include <QProcess>
18
19 #if defined (Q_OS_LINUX)
20 #include "client/linux/handler/exception_handler.h"
21 #elif defined (Q_OS_WIN32)
22 #include "client/windows/handler/exception_handler.h"
23 #elif defined (Q_OS_MACOS)
24 #include "client/mac/handler/exception_handler.h"
25 #endif // Q_OS
26
27 static google_breakpad::ExceptionHandler *exceptionHandler = nullptr;
28
finishCrashHandler()29 static void finishCrashHandler()
30 {
31 delete exceptionHandler;
32 }
33
34 #ifdef Q_OS_WIN32
35 // Called if crash dump was successfully created
36 // Saves path to file
callback(const wchar_t * _dump_dir,const wchar_t * _minidump_id,void * context,EXCEPTION_POINTERS * exinfo,MDRawAssertionInfo * assertion,bool success)37 bool callback(const wchar_t *_dump_dir,
38 const wchar_t *_minidump_id,
39 void *context, EXCEPTION_POINTERS *exinfo,
40 MDRawAssertionInfo *assertion,
41 bool success)
42 {
43 const QDir dir = QString::fromWCharArray(_dump_dir);
44 const QString id = QString::fromWCharArray(_minidump_id);
45 QProcess::startDetached(QCoreApplication::applicationFilePath(),
46 { "--start-crash-handler", dir.filePath(id + ".dmp") });
47 _exit(1);
48 return true;
49 }
50 #elif defined (Q_OS_LINUX)
51 // Called if crash dump was successfully created
52 // Saves path to file
callback(const google_breakpad::MinidumpDescriptor & md,void * context,bool b)53 bool callback(const google_breakpad::MinidumpDescriptor &md, void *context, bool b)
54 {
55 QProcess::startDetached(QCoreApplication::applicationFilePath(),
56 { "--start-crash-handler", md.path() });
57 _exit(1);
58 return true;
59 }
60 #elif defined (Q_OS_MACOS)
61 // Called if crash dump was successfully created
62 // Saves path to file
callback(const char * dump_dir,const char * minidump_id,void * context,bool succeeded)63 bool callback(const char *dump_dir, const char *minidump_id, void *context, bool succeeded)
64 {
65 const QDir dir = QString::fromUtf8(dump_dir);
66 const QString id = QString::fromUtf8(minidump_id);
67 QProcess::startDetached(QCoreApplication::applicationFilePath(),
68 { "--start-crash-handler", dir.filePath(id + ".dmp") });
69 _exit(1);
70 return true;
71 }
72 #endif // Q_OS
73
initCrashHandler()74 void initCrashHandler()
75 {
76 if (exceptionHandler) {
77 return;
78 }
79 // Here will be placed crash dump at the first place
80 // and then moved if needed
81
82 #if defined (Q_OS_LINUX)
83 static std::string tmpLocation = QStandardPaths::writableLocation(QStandardPaths::TempLocation).toStdString();
84 exceptionHandler = new google_breakpad::ExceptionHandler(google_breakpad::MinidumpDescriptor(tmpLocation),
85 nullptr,
86 callback,
87 nullptr,
88 true,
89 -1);
90 #elif defined (Q_OS_MACOS)
91 static std::string tmpLocation = QStandardPaths::writableLocation(QStandardPaths::TempLocation).toStdString();
92 exceptionHandler = new google_breakpad::ExceptionHandler(tmpLocation,
93 nullptr,
94 callback,
95 nullptr,
96 true,
97 nullptr);
98 #else
99 static std::wstring tmpLocation = QStandardPaths::writableLocation(QStandardPaths::TempLocation).toStdWString();
100 exceptionHandler = new google_breakpad::ExceptionHandler(tmpLocation,
101 nullptr,
102 callback,
103 nullptr,
104 google_breakpad::ExceptionHandler::HANDLER_ALL);
105 #endif
106 atexit(finishCrashHandler);
107 }
108
109 #else // CUTTER_ENABLE_CRASH_REPORTS
110
initCrashHandler()111 void initCrashHandler()
112 {
113
114 }
115
116 #endif // CUTTER_ENABLE_CRASH_REPORTS
117
118
showCrashDialog(const QString & dumpFile)119 void showCrashDialog(const QString &dumpFile)
120 {
121 QMessageBox mb;
122 mb.setWindowTitle(QObject::tr("Crash"));
123 mb.setText(QObject::tr("Cutter received a signal it can't handle and will close.<br/>"
124 "Would you like to create a crash dump for a bug report?"));
125 mb.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
126 mb.button(QMessageBox::Yes)->setText(QObject::tr("Create a Crash Dump"));
127 mb.button(QMessageBox::No)->setText(QObject::tr("Quit"));
128 mb.setDefaultButton(QMessageBox::Yes);
129
130 bool ok = false;
131 int ret = mb.exec();
132 if (ret == QMessageBox::Yes) {
133 QString dumpSaveFileName;
134 int placementFailCounter = 0;
135 do {
136 placementFailCounter++;
137 if (placementFailCounter == 4) {
138 break;
139 }
140 dumpSaveFileName = QFileDialog::getSaveFileName(nullptr,
141 QObject::tr("Choose a directory to save the crash dump in"),
142 QStandardPaths::writableLocation(QStandardPaths::HomeLocation) +
143 QDir::separator() +
144 "Cutter_crash_dump_"
145 + QDate::currentDate().toString("dd.MM.yy") + "_"
146 + QTime::currentTime().toString("HH.mm.ss") + ".dmp",
147 QObject::tr("Minidump (*.dmp)"));
148
149 if (dumpSaveFileName.isEmpty()) {
150 return;
151 }
152 if (QFile::rename(dumpFile, dumpSaveFileName)) {
153 ok = true;
154 break;
155 }
156 QMessageBox::critical(nullptr,
157 QObject::tr("Save Crash Dump"),
158 QObject::tr("Failed to write to %1.<br/>"
159 "Please make sure you have access to that directory "
160 "and try again.").arg(QFileInfo(dumpSaveFileName).dir().path()));
161 } while (true);
162
163 if (ok) {
164 QMessageBox info;
165 info.setWindowTitle(QObject::tr("Success"));
166 info.setText(QObject::tr("<a href=\"%1\">Crash dump</a> was successfully created.")
167 .arg(QFileInfo(dumpSaveFileName).dir().path()));
168 info.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
169
170 info.button(QMessageBox::Yes)->setText(QObject::tr("Open an Issue"));
171 info.button(QMessageBox::No)->setText(QObject::tr("Quit"));
172 info.setDefaultButton(QMessageBox::Yes);
173
174 int ret = info.exec();
175 if (ret == QMessageBox::Yes) {
176 openIssue();
177 }
178 } else {
179 QMessageBox::critical(nullptr,
180 QObject::tr("Error"),
181 QObject::tr("Error occurred during crash dump creation."));
182 }
183 } else {
184 QFile f(dumpFile);
185 f.remove();
186 }
187 }
188