crash_reports.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "core/crash_reports.h"
  8. #include "platform/platform_specific.h"
  9. #include "base/platform/base_platform_info.h"
  10. #include "core/launcher.h"
  11. #include <signal.h>
  12. #include <new>
  13. #include <mutex>
  14. #ifndef TDESKTOP_DISABLE_CRASH_REPORTS
  15. #ifdef Q_OS_WIN
  16. #include <new.h>
  17. #pragma warning(push)
  18. #pragma warning(disable:4091)
  19. #include <client/windows/handler/exception_handler.h>
  20. #pragma warning(pop)
  21. #else // Q_OS_WIN
  22. #include <execinfo.h>
  23. #include <sys/syscall.h>
  24. #ifdef Q_OS_MAC
  25. #include <dlfcn.h>
  26. #include <unistd.h>
  27. #ifdef MAC_USE_BREAKPAD
  28. #include <client/mac/handler/exception_handler.h>
  29. #else // MAC_USE_BREAKPAD
  30. #include <client/crashpad_client.h>
  31. #endif // else for MAC_USE_BREAKPAD
  32. #else // Q_OS_MAC
  33. #include <client/linux/handler/exception_handler.h>
  34. #endif // Q_OS_MAC
  35. #endif // Q_OS_WIN
  36. #endif // !TDESKTOP_DISABLE_CRASH_REPORTS
  37. namespace CrashReports {
  38. namespace {
  39. using Annotations = std::map<std::string, std::string>;
  40. using AnnotationRefs = std::map<std::string, const QString*>;
  41. Annotations ProcessAnnotations;
  42. AnnotationRefs ProcessAnnotationRefs;
  43. #ifndef TDESKTOP_DISABLE_CRASH_REPORTS
  44. QString ReportPath;
  45. FILE *ReportFile = nullptr;
  46. int ReportFileNo = 0;
  47. void SafeWriteChar(char ch) {
  48. fwrite(&ch, 1, 1, ReportFile);
  49. }
  50. template <bool Unsigned, typename Type>
  51. struct writeNumberSignAndRemoveIt {
  52. static void call(Type &number) {
  53. if (number < 0) {
  54. SafeWriteChar('-');
  55. number = -number;
  56. }
  57. }
  58. };
  59. template <typename Type>
  60. struct writeNumberSignAndRemoveIt<true, Type> {
  61. static void call(Type &number) {
  62. }
  63. };
  64. template <typename Type>
  65. const dump &SafeWriteNumber(const dump &stream, Type number) {
  66. if (!ReportFile) return stream;
  67. writeNumberSignAndRemoveIt<(Type(-1) > Type(0)), Type>::call(number);
  68. Type upper = 1, prev = number / 10;
  69. while (prev >= upper) {
  70. upper *= 10;
  71. }
  72. while (upper > 0) {
  73. int digit = (number / upper);
  74. SafeWriteChar('0' + digit);
  75. number -= digit * upper;
  76. upper /= 10;
  77. }
  78. return stream;
  79. }
  80. using ReservedMemoryChunk = std::array<gsl::byte, 1024 * 1024>;
  81. std::unique_ptr<ReservedMemoryChunk> ReservedMemory;
  82. void InstallOperatorNewHandler() {
  83. ReservedMemory = std::make_unique<ReservedMemoryChunk>();
  84. #ifdef Q_OS_WIN
  85. _set_new_handler([](size_t requested) -> int {
  86. _set_new_handler(nullptr);
  87. ReservedMemory.reset();
  88. CrashReports::SetAnnotation("Requested", QString::number(requested));
  89. Unexpected("Could not allocate!");
  90. });
  91. #else // Q_OS_WIN
  92. std::set_new_handler([] {
  93. std::set_new_handler(nullptr);
  94. ReservedMemory.reset();
  95. Unexpected("Could not allocate!");
  96. });
  97. #endif // Q_OS_WIN
  98. }
  99. void InstallQtMessageHandler() {
  100. static QtMessageHandler original = nullptr;
  101. original = qInstallMessageHandler([](
  102. QtMsgType type,
  103. const QMessageLogContext &context,
  104. const QString &message) {
  105. if (original) {
  106. original(type, context, message);
  107. }
  108. if (type == QtFatalMsg) {
  109. CrashReports::SetAnnotation("QtFatal", message);
  110. Unexpected("Qt FATAL message was generated!");
  111. }
  112. });
  113. }
  114. std::atomic<Qt::HANDLE> ReportingThreadId/* = nullptr*/;
  115. bool ReportingHeaderWritten/* = false*/;
  116. const char *BreakpadDumpPath/* = nullptr*/;
  117. const wchar_t *BreakpadDumpPathW/* = nullptr*/;
  118. void WriteReportHeader() {
  119. if (ReportingHeaderWritten) {
  120. return;
  121. }
  122. ReportingHeaderWritten = true;
  123. const auto dec2hex = [](int value) -> char {
  124. if (value >= 0 && value < 10) {
  125. return '0' + value;
  126. } else if (value >= 10 && value < 16) {
  127. return 'a' + (value - 10);
  128. }
  129. return '#';
  130. };
  131. for (const auto &i : ProcessAnnotationRefs) {
  132. QByteArray utf8 = i.second->toUtf8();
  133. std::string wrapped;
  134. wrapped.reserve(4 * utf8.size());
  135. for (auto ch : utf8) {
  136. auto uch = static_cast<uchar>(ch);
  137. wrapped.append("\\x", 2).append(1, dec2hex(uch >> 4)).append(1, dec2hex(uch & 0x0F));
  138. }
  139. ProcessAnnotations[i.first] = wrapped;
  140. }
  141. for (const auto &i : ProcessAnnotations) {
  142. dump() << i.first.c_str() << ": " << i.second.c_str() << "\n";
  143. }
  144. Platform::WriteCrashDumpDetails();
  145. dump() << "\n";
  146. }
  147. void WriteReportInfo(int signum, const char *name) {
  148. WriteReportHeader();
  149. const auto thread = ReportingThreadId.load();
  150. if (name) {
  151. dump() << "Caught signal " << signum << " (" << name << ") in thread " << uint64(thread) << "\n";
  152. } else if (signum == -1) {
  153. dump() << "Google Breakpad caught a crash, minidump written in thread " << uint64(thread) << "\n";
  154. if (BreakpadDumpPath) {
  155. dump() << "Minidump: " << BreakpadDumpPath << "\n";
  156. } else if (BreakpadDumpPathW) {
  157. dump() << "Minidump: " << BreakpadDumpPathW << "\n";
  158. }
  159. } else {
  160. dump() << "Caught signal " << signum << " in thread " << uint64(thread) << "\n";
  161. }
  162. }
  163. const int HandledSignals[] = {
  164. SIGSEGV,
  165. SIGABRT,
  166. SIGFPE,
  167. SIGILL,
  168. #ifndef Q_OS_WIN
  169. SIGBUS,
  170. SIGTRAP,
  171. #endif // !Q_OS_WIN
  172. };
  173. #ifdef Q_OS_WIN
  174. void SignalHandler(int signum) {
  175. #else // Q_OS_WIN
  176. struct sigaction OldSigActions[32]/* = { 0 }*/;
  177. void RestoreSignalHandlers() {
  178. for (const auto signum : HandledSignals) {
  179. sigaction(signum, &OldSigActions[signum], nullptr);
  180. }
  181. }
  182. void InvokeOldSignalHandler(int signum, siginfo_t *info, void *ucontext) {
  183. if (signum < 0 || signum > 31) {
  184. return;
  185. } else if (OldSigActions[signum].sa_flags & SA_SIGINFO) {
  186. if (OldSigActions[signum].sa_sigaction) {
  187. OldSigActions[signum].sa_sigaction(signum, info, ucontext);
  188. }
  189. } else {
  190. if (OldSigActions[signum].sa_handler) {
  191. OldSigActions[signum].sa_handler(signum);
  192. }
  193. }
  194. }
  195. void SignalHandler(int signum, siginfo_t *info, void *ucontext) {
  196. RestoreSignalHandlers();
  197. #endif // else for Q_OS_WIN
  198. const char* name = 0;
  199. switch (signum) {
  200. case SIGABRT: name = "SIGABRT"; break;
  201. case SIGSEGV: name = "SIGSEGV"; break;
  202. case SIGILL: name = "SIGILL"; break;
  203. case SIGFPE: name = "SIGFPE"; break;
  204. #ifndef Q_OS_WIN
  205. case SIGBUS: name = "SIGBUS"; break;
  206. case SIGSYS: name = "SIGSYS"; break;
  207. #endif // !Q_OS_WIN
  208. }
  209. auto expected = Qt::HANDLE(nullptr);
  210. const auto thread = QThread::currentThreadId();
  211. if (ReportingThreadId.compare_exchange_strong(expected, thread)) {
  212. WriteReportInfo(signum, name);
  213. ReportingThreadId = nullptr;
  214. }
  215. #ifndef Q_OS_WIN
  216. InvokeOldSignalHandler(signum, info, ucontext);
  217. #endif // !Q_OS_WIN
  218. }
  219. bool SetSignalHandlers = true;
  220. bool CrashLogged = false;
  221. #if !defined Q_OS_MAC || defined MAC_USE_BREAKPAD
  222. google_breakpad::ExceptionHandler* BreakpadExceptionHandler = 0;
  223. #ifdef Q_OS_WIN
  224. bool DumpCallback(const wchar_t* _dump_dir, const wchar_t* _minidump_id, void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, bool success)
  225. #elif defined Q_OS_MAC // Q_OS_WIN
  226. bool DumpCallback(const char* _dump_dir, const char* _minidump_id, void *context, bool success)
  227. #else // Q_OS_MAC
  228. bool DumpCallback(const google_breakpad::MinidumpDescriptor &md, void *context, bool success)
  229. #endif // else for Q_OS_WIN || Q_OS_MAC
  230. {
  231. if (CrashLogged) return success;
  232. CrashLogged = true;
  233. #ifdef Q_OS_WIN
  234. BreakpadDumpPathW = _minidump_id;
  235. SignalHandler(-1);
  236. #else // Q_OS_WIN
  237. #ifdef Q_OS_MAC
  238. BreakpadDumpPath = _minidump_id;
  239. #else // Q_OS_MAC
  240. BreakpadDumpPath = md.path();
  241. #endif // else for Q_OS_MAC
  242. SignalHandler(-1, 0, 0);
  243. #endif // else for Q_OS_WIN
  244. return success;
  245. }
  246. #endif // !Q_OS_MAC || MAC_USE_BREAKPAD
  247. #endif // !TDESKTOP_DISABLE_CRASH_REPORTS
  248. } // namespace
  249. QString PlatformString() {
  250. if (Platform::IsWindowsStoreBuild()) {
  251. return Platform::IsWindowsARM64()
  252. ? u"WinStoreARM64"_q
  253. : Platform::IsWindows64Bit()
  254. ? u"WinStore64Bit"_q
  255. : u"WinStore32Bit"_q;
  256. } else if (Platform::IsWindows32Bit()) {
  257. return u"Windows32Bit"_q;
  258. } else if (Platform::IsWindows64Bit()) {
  259. return u"Windows64Bit"_q;
  260. } else if (Platform::IsWindowsARM64()) {
  261. return u"WindowsARM64"_q;
  262. } else if (Platform::IsMacStoreBuild()) {
  263. return u"MacAppStore"_q;
  264. } else if (Platform::IsMac()) {
  265. return u"MacOS"_q;
  266. } else if (Platform::IsLinux()) {
  267. return u"Linux"_q;
  268. }
  269. Unexpected("Platform in CrashReports::PlatformString.");
  270. }
  271. void StartCatching() {
  272. #ifndef TDESKTOP_DISABLE_CRASH_REPORTS
  273. ProcessAnnotations["Binary"] = cExeName().toUtf8().constData();
  274. ProcessAnnotations["ApiId"] = QString::number(ApiId).toUtf8().constData();
  275. ProcessAnnotations["Version"] = (cAlphaVersion()
  276. ? u"%1 alpha"_q.arg(cAlphaVersion())
  277. : (AppBetaVersion
  278. ? u"%1 beta"_q
  279. : u"%1"_q).arg(AppVersion)).toUtf8().constData();
  280. ProcessAnnotations["Launched"] = QDateTime::currentDateTime().toString("dd.MM.yyyy hh:mm:ss").toUtf8().constData();
  281. ProcessAnnotations["Platform"] = PlatformString().toUtf8().constData();
  282. ProcessAnnotations["UserTag"] = QString::number(Core::Launcher::Instance().installationTag(), 16).toUtf8().constData();
  283. QString dumpspath = cWorkingDir() + u"tdata/dumps"_q;
  284. QDir().mkpath(dumpspath);
  285. #ifdef Q_OS_WIN
  286. BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
  287. dumpspath.toStdWString(),
  288. google_breakpad::ExceptionHandler::FilterCallback(nullptr),
  289. DumpCallback,
  290. (void*)nullptr, // callback_context
  291. google_breakpad::ExceptionHandler::HANDLER_ALL,
  292. MINIDUMP_TYPE(MiniDumpNormal),
  293. // MINIDUMP_TYPE(MiniDumpWithFullMemory | MiniDumpWithHandleData | MiniDumpWithThreadInfo | MiniDumpWithProcessThreadData | MiniDumpWithFullMemoryInfo | MiniDumpWithUnloadedModules | MiniDumpWithFullAuxiliaryState | MiniDumpIgnoreInaccessibleMemory | MiniDumpWithTokenInformation),
  294. (const wchar_t*)nullptr, // pipe_name
  295. (const google_breakpad::CustomClientInfo*)nullptr
  296. );
  297. #elif defined Q_OS_MAC // Q_OS_WIN
  298. #ifdef MAC_USE_BREAKPAD
  299. #ifndef _DEBUG
  300. BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
  301. QFile::encodeName(dumpspath).toStdString(),
  302. /*FilterCallback*/ 0,
  303. DumpCallback,
  304. /*context*/ 0,
  305. true,
  306. 0
  307. );
  308. #endif // !_DEBUG
  309. SetSignalHandlers = false;
  310. #else // MAC_USE_BREAKPAD
  311. crashpad::CrashpadClient crashpad_client;
  312. std::string handler = (cExeDir() + cExeName() + u"/Contents/Helpers/crashpad_handler"_q).toUtf8().constData();
  313. std::string database = QFile::encodeName(dumpspath).constData();
  314. if (crashpad_client.StartHandler(
  315. base::FilePath(handler),
  316. base::FilePath(database),
  317. {}, // metrics_dir
  318. std::string(), // url
  319. ProcessAnnotations,
  320. std::vector<std::string>(), // arguments
  321. false, // restartable
  322. false)) { // asynchronous_start
  323. }
  324. #endif // else for MAC_USE_BREAKPAD
  325. #else
  326. BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
  327. google_breakpad::MinidumpDescriptor(QFile::encodeName(dumpspath).toStdString()),
  328. /*FilterCallback*/ 0,
  329. DumpCallback,
  330. /*context*/ 0,
  331. true,
  332. -1
  333. );
  334. #endif // else for Q_OS_WIN || Q_OS_MAC
  335. #endif // !TDESKTOP_DISABLE_CRASH_REPORTS
  336. }
  337. void FinishCatching() {
  338. #ifndef TDESKTOP_DISABLE_CRASH_REPORTS
  339. #if !defined Q_OS_MAC || defined MAC_USE_BREAKPAD
  340. delete base::take(BreakpadExceptionHandler);
  341. #endif // !Q_OS_MAC || MAC_USE_BREAKPAD
  342. #endif // !TDESKTOP_DISABLE_CRASH_REPORTS
  343. }
  344. StartResult Start() {
  345. #ifndef TDESKTOP_DISABLE_CRASH_REPORTS
  346. ReportPath = cWorkingDir() + u"tdata/working"_q;
  347. #ifdef Q_OS_WIN
  348. FILE *f = nullptr;
  349. if (_wfopen_s(&f, ReportPath.toStdWString().c_str(), L"rb") != 0) {
  350. f = nullptr;
  351. } else {
  352. #else // !Q_OS_WIN
  353. if (FILE *f = fopen(QFile::encodeName(ReportPath).constData(), "rb")) {
  354. #endif // else for !Q_OS_WIN
  355. QByteArray lastdump;
  356. char buffer[256 * 1024] = { 0 };
  357. int32 read = fread(buffer, 1, 256 * 1024, f);
  358. if (read > 0) {
  359. lastdump.append(buffer, read);
  360. }
  361. fclose(f);
  362. LOG(("Opened '%1' for reading, the previous "
  363. "Telegram Desktop launch was not finished properly :( "
  364. "Crash log size: %2").arg(ReportPath).arg(lastdump.size()));
  365. return lastdump;
  366. }
  367. #endif // !TDESKTOP_DISABLE_CRASH_REPORTS
  368. return Restart();
  369. }
  370. Status Restart() {
  371. #ifndef TDESKTOP_DISABLE_CRASH_REPORTS
  372. if (ReportFile) {
  373. return Started;
  374. }
  375. #ifdef Q_OS_WIN
  376. if (_wfopen_s(&ReportFile, ReportPath.toStdWString().c_str(), L"wb") != 0) {
  377. ReportFile = nullptr;
  378. }
  379. #else // Q_OS_WIN
  380. ReportFile = fopen(QFile::encodeName(ReportPath).constData(), "wb");
  381. #endif // else for Q_OS_WIN
  382. if (ReportFile) {
  383. #ifdef Q_OS_WIN
  384. ReportFileNo = _fileno(ReportFile);
  385. #else // Q_OS_WIN
  386. ReportFileNo = fileno(ReportFile);
  387. #endif // else for Q_OS_WIN
  388. if (SetSignalHandlers) {
  389. #ifndef Q_OS_WIN
  390. struct sigaction sigact;
  391. sigact.sa_sigaction = SignalHandler;
  392. sigemptyset(&sigact.sa_mask);
  393. sigact.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
  394. for (const auto signum : HandledSignals) {
  395. sigaction(signum, &sigact, &OldSigActions[signum]);
  396. }
  397. #else // !Q_OS_WIN
  398. for (const auto signum : HandledSignals) {
  399. signal(signum, SignalHandler);
  400. }
  401. #endif // else for !Q_OS_WIN
  402. }
  403. InstallOperatorNewHandler();
  404. InstallQtMessageHandler();
  405. return Started;
  406. }
  407. LOG(("FATAL: Could not open '%1' for writing!").arg(ReportPath));
  408. return CantOpen;
  409. #else // !TDESKTOP_DISABLE_CRASH_REPORTS
  410. return Started;
  411. #endif // else for !TDESKTOP_DISABLE_CRASH_REPORTS
  412. }
  413. void Finish() {
  414. #ifndef TDESKTOP_DISABLE_CRASH_REPORTS
  415. FinishCatching();
  416. if (ReportFile) {
  417. fclose(ReportFile);
  418. ReportFile = nullptr;
  419. #ifdef Q_OS_WIN
  420. _wunlink(ReportPath.toStdWString().c_str());
  421. #else // Q_OS_WIN
  422. unlink(ReportPath.toUtf8().constData());
  423. #endif // else for Q_OS_WIN
  424. }
  425. #endif // !TDESKTOP_DISABLE_CRASH_REPORTS
  426. }
  427. void SetAnnotation(const std::string &key, const QString &value) {
  428. static QMutex mutex;
  429. QMutexLocker lock(&mutex);
  430. if (!value.trimmed().isEmpty()) {
  431. ProcessAnnotations[key] = value.toUtf8().constData();
  432. } else {
  433. ProcessAnnotations.erase(key);
  434. }
  435. }
  436. void SetAnnotationHex(const std::string &key, const QString &value) {
  437. if (value.isEmpty()) {
  438. return SetAnnotation(key, value);
  439. }
  440. const auto utf = value.toUtf8();
  441. auto buffer = std::string();
  442. buffer.reserve(4 * utf.size());
  443. const auto hexDigit = [](std::uint8_t value) {
  444. if (value >= 10) {
  445. return 'A' + (value - 10);
  446. }
  447. return '0' + value;
  448. };
  449. const auto appendHex = [&](std::uint8_t value) {
  450. buffer.push_back('\\');
  451. buffer.push_back('x');
  452. buffer.push_back(hexDigit(value / 16));
  453. buffer.push_back(hexDigit(value % 16));
  454. };
  455. for (const auto ch : utf) {
  456. appendHex(ch);
  457. }
  458. ProcessAnnotations[key] = std::move(buffer);
  459. }
  460. void SetAnnotationRef(const std::string &key, const QString *valuePtr) {
  461. static QMutex mutex;
  462. QMutexLocker lock(&mutex);
  463. if (valuePtr) {
  464. ProcessAnnotationRefs[key] = valuePtr;
  465. } else {
  466. ProcessAnnotationRefs.erase(key);
  467. }
  468. }
  469. #ifndef TDESKTOP_DISABLE_CRASH_REPORTS
  470. dump::~dump() {
  471. if (ReportFile) {
  472. fflush(ReportFile);
  473. }
  474. }
  475. const dump &operator<<(const dump &stream, const char *str) {
  476. if (!ReportFile) return stream;
  477. fwrite(str, 1, strlen(str), ReportFile);
  478. return stream;
  479. }
  480. const dump &operator<<(const dump &stream, const wchar_t *str) {
  481. if (!ReportFile) return stream;
  482. for (int i = 0, l = wcslen(str); i < l; ++i) {
  483. if (
  484. #if !defined(__WCHAR_UNSIGNED__)
  485. str[i] >= 0 &&
  486. #endif
  487. str[i] < 128) {
  488. SafeWriteChar(char(str[i]));
  489. } else {
  490. SafeWriteChar('?');
  491. }
  492. }
  493. return stream;
  494. }
  495. const dump &operator<<(const dump &stream, int num) {
  496. return SafeWriteNumber(stream, num);
  497. }
  498. const dump &operator<<(const dump &stream, unsigned int num) {
  499. return SafeWriteNumber(stream, num);
  500. }
  501. const dump &operator<<(const dump &stream, unsigned long num) {
  502. return SafeWriteNumber(stream, num);
  503. }
  504. const dump &operator<<(const dump &stream, unsigned long long num) {
  505. return SafeWriteNumber(stream, num);
  506. }
  507. const dump &operator<<(const dump &stream, double num) {
  508. if (num < 0) {
  509. SafeWriteChar('-');
  510. num = -num;
  511. }
  512. SafeWriteNumber(stream, uint64(floor(num)));
  513. SafeWriteChar('.');
  514. num -= floor(num);
  515. for (int i = 0; i < 4; ++i) {
  516. num *= 10;
  517. int digit = int(floor(num));
  518. SafeWriteChar('0' + digit);
  519. num -= digit;
  520. }
  521. return stream;
  522. }
  523. #endif // TDESKTOP_DISABLE_CRASH_REPORTS
  524. } // namespace CrashReports