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