crash_report_writer.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. // This file is part of Desktop App Toolkit,
  2. // a set of libraries for developing nice desktop applications.
  3. //
  4. // For license and copyright information please follow this link:
  5. // https://github.com/desktop-app/legal/blob/master/LEGAL
  6. //
  7. #include "base/crash_report_writer.h"
  8. #include "base/platform/base_platform_info.h"
  9. #include "base/integration.h"
  10. #include "base/crash_report_header.h"
  11. #include <QtCore/QDir>
  12. #include <QtCore/QDateTime>
  13. #include <QtCore/QMutex>
  14. #include <QtCore/QMutexLocker>
  15. #include <signal.h>
  16. #include <new>
  17. #include <mutex>
  18. #if !defined Q_OS_MAC || defined MAC_USE_BREAKPAD
  19. #define USE_BREAKPAD
  20. #endif // !Q_OS_MAC || MAC_USE_BREAKPAD
  21. // see https://blog.inventic.eu/2012/08/qt-and-google-breakpad/
  22. #ifdef Q_OS_WIN
  23. #include <io.h>
  24. #include <fcntl.h>
  25. #pragma warning(push)
  26. #pragma warning(disable:4091)
  27. #include <client/windows/handler/exception_handler.h>
  28. #pragma warning(pop)
  29. #elif defined Q_OS_MAC // Q_OS_WIN
  30. #include <execinfo.h>
  31. #include <signal.h>
  32. #include <sys/syscall.h>
  33. #include <sys/stat.h>
  34. #include <fcntl.h>
  35. #include <dlfcn.h>
  36. #include <unistd.h>
  37. #ifdef USE_BREAKPAD
  38. #include <client/mac/handler/exception_handler.h>
  39. #else // USE_BREAKPAD
  40. #include <client/crashpad_client.h>
  41. #endif // USE_BREAKPAD
  42. #else // Q_OS_MAC
  43. #include <execinfo.h>
  44. #include <signal.h>
  45. #include <sys/syscall.h>
  46. #include <unistd.h>
  47. #include <client/linux/handler/exception_handler.h>
  48. #endif // else for Q_OS_WIN || Q_OS_MAC
  49. namespace base::Platform {
  50. using namespace ::Platform;
  51. } // namespace base::Platform
  52. namespace base {
  53. namespace {
  54. using namespace details;
  55. CrashReportWriter *Instance = nullptr;
  56. QMutex AnnotationsMutex;
  57. std::map<std::string, std::string> Annotations;
  58. int ReportFileNo = -1;
  59. std::atomic<Qt::HANDLE> ReportingThreadId = nullptr;
  60. bool SkipWriteReportHeader = false;
  61. bool ReportingHeaderWritten = false;
  62. QMutex ReportingMutex;
  63. #ifdef Q_OS_WIN
  64. const wchar_t *BreakpadDumpId = nullptr;
  65. std::wstring FinalReportPath;
  66. #else // Q_OS_WIN
  67. const char *BreakpadDumpId = nullptr;
  68. std::string FinalReportPath;
  69. #endif // Q_OS_WIN
  70. using ReservedMemoryChunk = std::array<gsl::byte, 1024 * 1024>;
  71. std::unique_ptr<ReservedMemoryChunk> ReservedMemory;
  72. const char *PlatformString() {
  73. if (Platform::IsWindowsStoreBuild()) {
  74. return Platform::IsWindows64Bit()
  75. ? "WinStore64Bit"
  76. : "WinStore32Bit";
  77. } else if (Platform::IsWindows32Bit()) {
  78. return "Windows32Bit";
  79. } else if (Platform::IsWindows64Bit()) {
  80. return "Windows64Bit";
  81. } else if (Platform::IsMacStoreBuild()) {
  82. return "MacAppStore";
  83. } else if (Platform::IsMac()) {
  84. return "MacOS";
  85. } else if (Platform::IsLinux()) {
  86. return "Linux";
  87. }
  88. Unexpected("Platform in CrashReports::PlatformString.");
  89. }
  90. void AddAnnotation(std::string key, std::string value) {
  91. QMutexLocker lock(&AnnotationsMutex);
  92. Annotations.emplace(std::move(key), std::move(value));
  93. }
  94. void InstallOperatorNewHandler() {
  95. ReservedMemory = std::make_unique<ReservedMemoryChunk>();
  96. std::set_new_handler([] {
  97. std::set_new_handler(nullptr);
  98. ReservedMemory.reset();
  99. Unexpected("Could not allocate!");
  100. });
  101. }
  102. void InstallQtMessageHandler() {
  103. static QtMessageHandler original = nullptr;
  104. original = qInstallMessageHandler([](
  105. QtMsgType type,
  106. const QMessageLogContext &context,
  107. const QString &message) {
  108. if (original) {
  109. original(type, context, message);
  110. }
  111. if (type == QtFatalMsg && Instance) {
  112. AddAnnotation("QtFatal", message.toStdString());
  113. Unexpected("Qt FATAL message was generated!");
  114. }
  115. });
  116. }
  117. #ifdef Q_OS_WIN
  118. void SignalHandler(int signum) {
  119. #else // Q_OS_WIN
  120. struct sigaction SIG_def[32];
  121. void SignalHandler(int signum, siginfo_t *info, void *ucontext) {
  122. if (signum > 0) {
  123. sigaction(signum, &SIG_def[signum], 0);
  124. }
  125. #endif // else for Q_OS_WIN
  126. const char* name = 0;
  127. switch (signum) {
  128. case SIGABRT: name = "SIGABRT"; break;
  129. case SIGSEGV: name = "SIGSEGV"; break;
  130. case SIGILL: name = "SIGILL"; break;
  131. case SIGFPE: name = "SIGFPE"; break;
  132. #ifndef Q_OS_WIN
  133. case SIGBUS: name = "SIGBUS"; break;
  134. case SIGSYS: name = "SIGSYS"; break;
  135. #endif // !Q_OS_WIN
  136. }
  137. const auto thread = QThread::currentThreadId();
  138. if (thread == ReportingThreadId) {
  139. return;
  140. }
  141. QMutexLocker lock(&ReportingMutex);
  142. ReportingThreadId = thread;
  143. if (SkipWriteReportHeader || ReportFileNo < 0) {
  144. return;
  145. }
  146. if (!ReportingHeaderWritten) {
  147. ReportingHeaderWritten = true;
  148. QMutexLocker lock(&AnnotationsMutex);
  149. for (const auto &i : Annotations) {
  150. ReportHeaderWriter() << i.first.c_str() << ": " << i.second.c_str() << "\n";
  151. }
  152. ReportHeaderWriter() << "\n";
  153. }
  154. if (name) {
  155. ReportHeaderWriter() << "Caught signal " << signum << " (" << name << ") in thread " << uint64(thread) << "\n";
  156. } else if (signum == -1) {
  157. ReportHeaderWriter() << "Google Breakpad caught a crash, minidump written in thread " << uint64(thread) << "\n";
  158. if (BreakpadDumpId) {
  159. ReportHeaderWriter() << "Minidump: " << BreakpadDumpId << "\n";
  160. }
  161. } else {
  162. ReportHeaderWriter() << "Caught signal " << signum << " in thread " << uint64(thread) << "\n";
  163. }
  164. #ifdef Q_OS_WIN
  165. _write(ReportFileNo, ReportHeaderBytes(), ReportHeaderLength());
  166. _close(ReportFileNo);
  167. #else // Q_OS_WIN
  168. [[maybe_unused]] auto result_ = write(ReportFileNo, ReportHeaderBytes(), ReportHeaderLength());
  169. close(ReportFileNo);
  170. #endif // Q_OS_WIN
  171. ReportFileNo = -1;
  172. #ifdef Q_OS_WIN
  173. if (BreakpadDumpId) {
  174. FinalReportPath.append(BreakpadDumpId);
  175. FinalReportPath.append(L".txt");
  176. auto handle = int();
  177. const auto errcode = _wsopen_s(
  178. &handle,
  179. FinalReportPath.c_str(),
  180. _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY,
  181. _SH_DENYWR,
  182. _S_IWRITE);
  183. if (!errcode) {
  184. _write(handle, ReportHeaderBytes(), ReportHeaderLength());
  185. _close(handle);
  186. }
  187. }
  188. #else // Q_OS_WIN
  189. if (BreakpadDumpId) {
  190. FinalReportPath.append(BreakpadDumpId);
  191. const auto good = int(FinalReportPath.size()) - 4;
  192. if (good > 0 && !strcmp(FinalReportPath.c_str() + good, ".dmp")) {
  193. FinalReportPath.erase(FinalReportPath.begin() + good, FinalReportPath.end());
  194. }
  195. FinalReportPath.append(".txt");
  196. const auto handle = open(
  197. FinalReportPath.c_str(),
  198. O_WRONLY | O_CREAT | O_TRUNC,
  199. S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
  200. if (handle >= 0) {
  201. [[maybe_unused]] auto result_ = write(handle, ReportHeaderBytes(), ReportHeaderLength());
  202. close(handle);
  203. }
  204. }
  205. #endif // Q_OS_WIN
  206. ReportingThreadId = nullptr;
  207. }
  208. bool SetSignalHandlers = Platform::IsLinux() || Platform::IsMac();
  209. bool CrashLogged = false;
  210. #ifdef USE_BREAKPAD
  211. google_breakpad::ExceptionHandler* BreakpadExceptionHandler = 0;
  212. #ifdef Q_OS_WIN
  213. bool DumpCallback(const wchar_t* _dump_dir, const wchar_t* _minidump_id, void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, bool success)
  214. #elif defined Q_OS_MAC // Q_OS_WIN
  215. bool DumpCallback(const char* _dump_dir, const char* _minidump_id, void *context, bool success)
  216. #else // Q_OS_MAC
  217. bool DumpCallback(const google_breakpad::MinidumpDescriptor &md, void *context, bool success)
  218. #endif // else for Q_OS_WIN || Q_OS_MAC
  219. {
  220. if (CrashLogged) return success;
  221. CrashLogged = true;
  222. #ifdef Q_OS_WIN
  223. BreakpadDumpId = _minidump_id;
  224. SignalHandler(-1);
  225. #else // Q_OS_WIN
  226. #ifdef Q_OS_MAC
  227. BreakpadDumpId = _minidump_id;
  228. #else // Q_OS_MAC
  229. BreakpadDumpId = md.path();
  230. auto afterLastSlash = BreakpadDumpId;
  231. for (auto ch = afterLastSlash; *ch != 0; ++ch) {
  232. if (*ch == '/') {
  233. afterLastSlash = (ch + 1);
  234. }
  235. }
  236. if (*afterLastSlash) {
  237. BreakpadDumpId = afterLastSlash;
  238. }
  239. #endif // else for Q_OS_MAC
  240. SignalHandler(-1, 0, 0);
  241. #endif // else for Q_OS_WIN
  242. return success;
  243. }
  244. #endif // USE_BREAKPAD
  245. } // namespace
  246. CrashReportWriter::CrashReportWriter(const QString &path) : _path(path) {
  247. Expects(Instance == nullptr);
  248. Expects(_path.endsWith('/'));
  249. Instance = this;
  250. _previousReport = readPreviousReport();
  251. }
  252. CrashReportWriter::~CrashReportWriter() {
  253. Expects(Instance == this);
  254. finishCatching();
  255. closeReport();
  256. Instance = nullptr;
  257. }
  258. void CrashReportWriter::start() {
  259. AddAnnotation(
  260. "Launched",
  261. QDateTime::currentDateTime().toString(
  262. "dd.MM.yyyy hh:mm:ss"
  263. ).toStdString());
  264. AddAnnotation("Platform", PlatformString());
  265. QDir().mkpath(_path);
  266. openReport();
  267. startCatching();
  268. }
  269. bool CrashReportWriter::openReport() {
  270. if (ReportFileNo >= 0) {
  271. return true;
  272. }
  273. // Try to lock the report file to kill
  274. // all remaining processes that opened it.
  275. _reportFile.setFileName(reportPath());
  276. if (!_reportLock.lock(_reportFile, QIODevice::WriteOnly)) {
  277. return false;
  278. }
  279. ReportFileNo = _reportFile.handle();
  280. if (ReportFileNo < 0) {
  281. return false;
  282. }
  283. #ifdef Q_OS_WIN
  284. FinalReportPath = _path.toStdWString();
  285. #else // Q_OS_WIN
  286. FinalReportPath = QFile::encodeName(_path).toStdString();
  287. #endif // Q_OS_WIN
  288. FinalReportPath.reserve(FinalReportPath.size() + 1024);
  289. if (SetSignalHandlers) {
  290. #ifndef Q_OS_WIN
  291. struct sigaction sigact;
  292. sigact.sa_sigaction = SignalHandler;
  293. sigemptyset(&sigact.sa_mask);
  294. sigact.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
  295. sigaction(SIGABRT, &sigact, &SIG_def[SIGABRT]);
  296. sigaction(SIGSEGV, &sigact, &SIG_def[SIGSEGV]);
  297. sigaction(SIGILL, &sigact, &SIG_def[SIGILL]);
  298. sigaction(SIGFPE, &sigact, &SIG_def[SIGFPE]);
  299. sigaction(SIGBUS, &sigact, &SIG_def[SIGBUS]);
  300. sigaction(SIGSYS, &sigact, &SIG_def[SIGSYS]);
  301. #else // !Q_OS_WIN
  302. signal(SIGABRT, SignalHandler);
  303. signal(SIGSEGV, SignalHandler);
  304. signal(SIGILL, SignalHandler);
  305. signal(SIGFPE, SignalHandler);
  306. #endif // else for !Q_OS_WIN
  307. }
  308. InstallOperatorNewHandler();
  309. InstallQtMessageHandler();
  310. return true;
  311. }
  312. void CrashReportWriter::closeReport() {
  313. QMutexLocker lock(&ReportingMutex);
  314. if (SkipWriteReportHeader) {
  315. return;
  316. }
  317. SkipWriteReportHeader = true;
  318. lock.unlock();
  319. _reportLock.unlock();
  320. _reportFile.close();
  321. _reportFile.remove();
  322. ReportFileNo = -1;
  323. }
  324. void CrashReportWriter::startCatching() {
  325. #ifdef Q_OS_WIN
  326. BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
  327. _path.toStdWString(),
  328. google_breakpad::ExceptionHandler::FilterCallback(nullptr),
  329. DumpCallback,
  330. (void*)nullptr, // callback_context
  331. google_breakpad::ExceptionHandler::HANDLER_ALL,
  332. MINIDUMP_TYPE(MiniDumpNormal),
  333. // MINIDUMP_TYPE(MiniDumpWithFullMemory | MiniDumpWithHandleData | MiniDumpWithThreadInfo | MiniDumpWithProcessThreadData | MiniDumpWithFullMemoryInfo | MiniDumpWithUnloadedModules | MiniDumpWithFullAuxiliaryState | MiniDumpIgnoreInaccessibleMemory | MiniDumpWithTokenInformation),
  334. (const wchar_t*)nullptr, // pipe_name
  335. (const google_breakpad::CustomClientInfo*)nullptr
  336. );
  337. #elif defined Q_OS_MAC // Q_OS_WIN
  338. #ifdef USE_BREAKPAD
  339. #ifndef _DEBUG
  340. BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
  341. QFile::encodeName(_path).toStdString(),
  342. /*FilterCallback*/ 0,
  343. DumpCallback,
  344. /*context*/ 0,
  345. true,
  346. 0
  347. );
  348. #endif // !_DEBUG
  349. #else // USE_BREAKPAD
  350. crashpad::CrashpadClient crashpad_client;
  351. const auto handler = (Integration::Instance().executablePath() + "/Contents/Helpers/crashpad_handler").toStdString();
  352. const auto database = QFile::encodeName(_path).constData();
  353. if (crashpad_client.StartHandler(base::FilePath(handler),
  354. base::FilePath(database),
  355. std::string(),
  356. Annotations,
  357. std::vector<std::string>(),
  358. false)) {
  359. crashpad_client.UseHandler();
  360. }
  361. #endif // USE_BREAKPAD
  362. #else
  363. BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
  364. google_breakpad::MinidumpDescriptor(QFile::encodeName(_path).toStdString()),
  365. /*FilterCallback*/ 0,
  366. DumpCallback,
  367. /*context*/ 0,
  368. true,
  369. -1
  370. );
  371. #endif // else for Q_OS_WIN || Q_OS_MAC
  372. }
  373. void CrashReportWriter::finishCatching() {
  374. #ifdef USE_BREAKPAD
  375. delete base::take(BreakpadExceptionHandler);
  376. #endif // USE_BREAKPAD
  377. }
  378. void CrashReportWriter::addAnnotation(std::string key, std::string value) {
  379. AddAnnotation(std::move(key), std::move(value));
  380. }
  381. QString CrashReportWriter::reportPath() const {
  382. return _path + "report";
  383. }
  384. std::optional<QByteArray> CrashReportWriter::readPreviousReport() {
  385. auto file = QFile(reportPath());
  386. if (file.open(QIODevice::ReadOnly)) {
  387. return file.readAll();
  388. }
  389. return std::nullopt;
  390. }
  391. } // namespace CrashReports