/* This file is part of Telegram Desktop, the official desktop version of Telegram messaging app, see https://telegram.org Telegram Desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" #include "logs.h" #include #include #include "pspecific.h" #ifndef TDESKTOP_DISABLE_CRASH_REPORTS // see https://blog.inventic.eu/2012/08/qt-and-google-breakpad/ #ifdef Q_OS_WIN #pragma warning(push) #pragma warning(disable:4091) #include "client/windows/handler/exception_handler.h" #pragma warning(pop) #elif defined Q_OS_MAC // Q_OS_WIN #include #ifdef MAC_USE_BREAKPAD #include "client/mac/handler/exception_handler.h" #else // MAC_USE_BREAKPAD #include "client/crashpad_client.h" #endif // else for MAC_USE_BREAKPAD #elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32 // Q_OS_MAC #include "client/linux/handler/exception_handler.h" #endif // Q_OS_LINUX64 || Q_OS_LINUX32 #endif // !TDESKTOP_DISABLE_CRASH_REPORTS enum LogDataType { LogDataMain, LogDataDebug, LogDataTcp, LogDataMtp, LogDataCount }; QMutex *_logsMutex(LogDataType type, bool clear = false) { static QMutex *LogsMutexes = 0; if (clear) { delete[] LogsMutexes; LogsMutexes = 0; } else if (!LogsMutexes) { LogsMutexes = new QMutex[LogDataCount]; } return &LogsMutexes[type]; } QString _logsFilePath(LogDataType type, const QString &postfix = QString()) { QString path(cWorkingDir()); switch (type) { case LogDataMain: path += qstr("log") + postfix + qstr(".txt"); break; case LogDataDebug: path += qstr("DebugLogs/log") + postfix + qstr(".txt"); break; case LogDataTcp: path += qstr("DebugLogs/tcp") + postfix + qstr(".txt"); break; case LogDataMtp: path += qstr("DebugLogs/mtp") + postfix + qstr(".txt"); break; } return path; } int32 LogsStartIndexChosen = -1; QString _logsEntryStart() { static int32 index = 0; QDateTime tm(QDateTime::currentDateTime()); QThread *thread = QThread::currentThread(); MTP::internal::Thread *mtpThread = qobject_cast(thread); uint threadId = mtpThread ? mtpThread->getThreadId() : 0; return QString("[%1 %2-%3]").arg(tm.toString("hh:mm:ss.zzz")).arg(QString("%1").arg(threadId, 2, 10, QChar('0'))).arg(++index, 7, 10, QChar('0')); } class LogsDataFields { public: LogsDataFields() { for (int32 i = 0; i < LogDataCount; ++i) { files[i].reset(new QFile()); } } bool openMain() { return reopen(LogDataMain, 0, qsl("start")); } void closeMain() { QMutexLocker lock(_logsMutex(LogDataMain)); if (files[LogDataMain]) { streams[LogDataMain].setDevice(0); files[LogDataMain]->close(); } } bool instanceChecked() { return reopen(LogDataMain, 0, QString()); } QString full() { if (!streams[LogDataMain].device()) { return QString(); } QFile out(files[LogDataMain]->fileName()); if (out.open(QIODevice::ReadOnly)) { return QString::fromUtf8(out.readAll()); } return QString(); } void write(LogDataType type, const QString &msg) { QMutexLocker lock(_logsMutex(type)); if (type != LogDataMain) reopenDebug(); if (!streams[type].device()) return; streams[type] << msg; streams[type].flush(); } private: QSharedPointer files[LogDataCount]; QTextStream streams[LogDataCount]; int32 part = -1; bool reopen(LogDataType type, int32 dayIndex, const QString &postfix) { if (streams[type].device()) { if (type == LogDataMain) { if (!postfix.isEmpty()) { return true; } } else { streams[type].setDevice(0); files[type]->close(); } } QFlags mode = QIODevice::WriteOnly | QIODevice::Text; if (type == LogDataMain) { // we can call LOG() in LogDataMain reopen - mutex not locked if (postfix.isEmpty()) { // instance checked, need to move to log.txt t_assert(!files[type]->fileName().isEmpty()); // one of log_startXX.txt should've been opened already QSharedPointer to(new QFile(_logsFilePath(type, postfix))); if (to->exists() && !to->remove()) { LOG(("Could not delete '%1' file to start new logging!").arg(to->fileName())); return false; } if (!QFile(files[type]->fileName()).copy(to->fileName())) { // don't close files[type] yet LOG(("Could not copy '%1' to '%2' to start new logging!").arg(files[type]->fileName()).arg(to->fileName())); return false; } if (to->open(mode | QIODevice::Append)) { qSwap(files[type], to); streams[type].setDevice(files[type].data()); streams[type].setCodec("UTF-8"); LOG(("Moved logging from '%1' to '%2'!").arg(to->fileName()).arg(files[type]->fileName())); to->remove(); LogsStartIndexChosen = -1; QDir working(cWorkingDir()); // delete all other log_startXX.txt that we can QStringList oldlogs = working.entryList(QStringList("log_start*.txt"), QDir::Files); for (QStringList::const_iterator i = oldlogs.cbegin(), e = oldlogs.cend(); i != e; ++i) { QString oldlog = cWorkingDir() + *i, oldlogend = i->mid(qstr("log_start").size()); if (oldlogend.size() == 1 + qstr(".txt").size() && oldlogend.at(0).isDigit() && oldlogend.midRef(1) == qstr(".txt")) { bool removed = QFile(*i).remove(); LOG(("Old start log '%1' found, deleted: %2").arg(*i).arg(Logs::b(removed))); } } return true; } LOG(("Could not open '%1' file to start new logging!").arg(to->fileName())); return false; } else { bool found = false; int32 oldest = -1; // find not existing log_startX.txt or pick the oldest one (by lastModified) QDateTime oldestLastModified; for (int32 i = 0; i < 10; ++i) { QString trying = _logsFilePath(type, qsl("_start%1").arg(i)); files[type]->setFileName(trying); if (!files[type]->exists()) { LogsStartIndexChosen = i; found = true; break; } QDateTime lastModified = QFileInfo(trying).lastModified(); if (oldest < 0 || lastModified < oldestLastModified) { oldestLastModified = lastModified; oldest = i; } } if (!found) { files[type]->setFileName(_logsFilePath(type, qsl("_start%1").arg(oldest))); LogsStartIndexChosen = oldest; } } } else { files[type]->setFileName(_logsFilePath(type, postfix)); if (files[type]->exists()) { if (files[type]->open(QIODevice::ReadOnly | QIODevice::Text)) { if (QString::fromUtf8(files[type]->readLine()).toInt() == dayIndex) { mode |= QIODevice::Append; } files[type]->close(); } } else { QDir().mkdir(cWorkingDir() + qstr("DebugLogs")); } } if (files[type]->open(mode)) { streams[type].setDevice(files[type].data()); streams[type].setCodec("UTF-8"); if (type != LogDataMain) { streams[type] << ((mode & QIODevice::Append) ? qsl("----------------------------------------------------------------\nNEW LOGGING INSTANCE STARTED!!!\n----------------------------------------------------------------\n") : qsl("%1\n").arg(dayIndex)); streams[type].flush(); } return true; } else if (type != LogDataMain) { LOG(("Could not open debug log '%1'!").arg(files[type]->fileName())); } return false; } void reopenDebug() { time_t t = time(NULL); struct tm tm; mylocaltime(&tm, &t); static const int switchEach = 15; // minutes int32 newPart = (tm.tm_min + tm.tm_hour * 60) / switchEach; if (newPart == part) return; part = newPart; int32 dayIndex = (tm.tm_year + 1900) * 10000 + (tm.tm_mon + 1) * 100 + tm.tm_mday; QString postfix = QString("_%4_%5").arg((part * switchEach) / 60, 2, 10, QChar('0')).arg((part * switchEach) % 60, 2, 10, QChar('0')); reopen(LogDataDebug, dayIndex, postfix); reopen(LogDataTcp, dayIndex, postfix); reopen(LogDataMtp, dayIndex, postfix); } }; LogsDataFields *LogsData = 0; typedef QList > LogsInMemoryList; LogsInMemoryList *LogsInMemory = 0; LogsInMemoryList *DeletedLogsInMemory = SharedMemoryLocation(); QString LogsBeforeSingleInstanceChecked; // LogsInMemory already dumped in LogsData, but LogsData is about to be deleted void _logsWrite(LogDataType type, const QString &msg) { if (LogsData && (type == LogDataMain || LogsStartIndexChosen < 0)) { if (type == LogDataMain || cDebug()) { LogsData->write(type, msg); } } else if (LogsInMemory != DeletedLogsInMemory) { if (!LogsInMemory) { LogsInMemory = new LogsInMemoryList; } LogsInMemory->push_back(qMakePair(type, msg)); } else if (!LogsBeforeSingleInstanceChecked.isEmpty() && type == LogDataMain) { LogsBeforeSingleInstanceChecked += msg; } } void _moveOldDataFiles(const QString &from); namespace SignalHandlers { void StartCrashHandler(); void FinishCrashHandler(); } namespace Logs { void start() { t_assert(LogsData == 0); if (!Sandbox::CheckBetaVersionDir()) { return; } bool workingDirChosen = cBetaVersion(); QString initialWorkingDir = QDir(cWorkingDir()).absolutePath() + '/', moveOldDataFrom; if (cBetaVersion()) { cSetDebug(true); #if defined Q_OS_MAC || defined Q_OS_LINUX } else { #ifdef _DEBUG cForceWorkingDir(cExeDir()); #else // _DEBUG if (cWorkingDir().isEmpty()) { cForceWorkingDir(psAppDataPath()); } #endif // else for _DEBUG workingDirChosen = true; #if defined Q_OS_LINUX && !defined _DEBUG // fix first version moveOldDataFrom = initialWorkingDir; #endif // Q_OS_LINUX && !_DEBUG #elif defined Q_OS_WINRT // Q_OS_MAC || Q_OS_LINUX } else { cForceWorkingDir(psAppDataPath()); workingDirChosen = true; #endif // Q_OS_WINRT } LogsData = new LogsDataFields(); if (!workingDirChosen) { cForceWorkingDir(cWorkingDir()); if (!LogsData->openMain()) { cForceWorkingDir(cExeDir()); if (!LogsData->openMain()) { cForceWorkingDir(psAppDataPath()); } } } cForceWorkingDir(QDir(cWorkingDir()).absolutePath() + '/'); // WinRT build requires the working dir to stay the same for plugin loading. #ifndef Q_OS_WINRT QDir().setCurrent(cWorkingDir()); #endif // !Q_OS_WINRT QDir().mkpath(cWorkingDir() + qstr("tdata")); Sandbox::WorkingDirReady(); SignalHandlers::StartCrashHandler(); if (!LogsData->openMain()) { delete LogsData; LogsData = 0; } LOG(("Launched version: %1, alpha: %2, beta: %3, debug mode: %4, test dc: %5").arg(AppVersion).arg(Logs::b(cAlphaVersion())).arg(cBetaVersion()).arg(Logs::b(cDebug())).arg(Logs::b(cTestMode()))); LOG(("Executable dir: %1, name: %2").arg(cExeDir()).arg(cExeName())); LOG(("Initial working dir: %1").arg(initialWorkingDir)); LOG(("Working dir: %1").arg(cWorkingDir())); LOG(("Arguments: %1").arg(cArguments())); if (!LogsData) { LOG(("FATAL: Could not open '%1' for writing log!").arg(_logsFilePath(LogDataMain, qsl("_startXX")))); return; } #ifdef Q_OS_WIN if (cWorkingDir() == psAppDataPath()) { // fix old "Telegram Win (Unofficial)" version moveOldDataFrom = psAppDataPathOld(); } #endif if (!moveOldDataFrom.isEmpty()) { _moveOldDataFiles(moveOldDataFrom); } if (LogsInMemory) { t_assert(LogsInMemory != DeletedLogsInMemory); LogsInMemoryList list = *LogsInMemory; for (LogsInMemoryList::const_iterator i = list.cbegin(), e = list.cend(); i != e; ++i) { if (i->first == LogDataMain) { _logsWrite(i->first, i->second); LOG(("First: %1, %2").arg(i->first).arg(i->second)); } } } LOG(("Logs started")); } void finish() { delete LogsData; LogsData = 0; if (LogsInMemory && LogsInMemory != DeletedLogsInMemory) { delete LogsInMemory; } LogsInMemory = DeletedLogsInMemory; _logsMutex(LogDataMain, true); SignalHandlers::FinishCrashHandler(); } bool started() { return LogsData != 0; } bool instanceChecked() { if (!LogsData) return false; if (!LogsData->instanceChecked()) { LogsBeforeSingleInstanceChecked = Logs::full(); delete LogsData; LogsData = 0; LOG(("FATAL: Could not move logging to '%1'!").arg(_logsFilePath(LogDataMain))); return false; } if (LogsInMemory) { t_assert(LogsInMemory != DeletedLogsInMemory); LogsInMemoryList list = *LogsInMemory; for (LogsInMemoryList::const_iterator i = list.cbegin(), e = list.cend(); i != e; ++i) { if (i->first != LogDataMain) { _logsWrite(i->first, i->second); } } } if (LogsInMemory) { t_assert(LogsInMemory != DeletedLogsInMemory); delete LogsInMemory; } LogsInMemory = DeletedLogsInMemory; DEBUG_LOG(("Debug logs started.")); LogsBeforeSingleInstanceChecked.clear(); return true; } void multipleInstances() { if (LogsInMemory) { t_assert(LogsInMemory != DeletedLogsInMemory); delete LogsInMemory; } LogsInMemory = DeletedLogsInMemory; if (cDebug()) { LOG(("WARNING: debug logs are not written in multiple instances mode!")); } LogsBeforeSingleInstanceChecked.clear(); } void closeMain() { LOG(("Explicitly closing main log and finishing crash handlers.")); if (LogsData) { LogsData->closeMain(); } } void writeMain(const QString &v) { time_t t = time(NULL); struct tm tm; mylocaltime(&tm, &t); QString msg(QString("[%1.%2.%3 %4:%5:%6] %7\n").arg(tm.tm_year + 1900).arg(tm.tm_mon + 1, 2, 10, QChar('0')).arg(tm.tm_mday, 2, 10, QChar('0')).arg(tm.tm_hour, 2, 10, QChar('0')).arg(tm.tm_min, 2, 10, QChar('0')).arg(tm.tm_sec, 2, 10, QChar('0')).arg(v)); _logsWrite(LogDataMain, msg); QString debugmsg(QString("%1 %2\n").arg(_logsEntryStart()).arg(v)); _logsWrite(LogDataDebug, debugmsg); } void writeDebug(const char *file, int32 line, const QString &v) { const char *last = strstr(file, "/"), *found = 0; while (last) { found = last; last = strstr(last + 1, "/"); } last = strstr(file, "\\"); while (last) { found = last; last = strstr(last + 1, "\\"); } if (found) { file = found + 1; } QString msg(QString("%1 %2 (%3 : %4)\n").arg(_logsEntryStart()).arg(v).arg(file).arg(line)); _logsWrite(LogDataDebug, msg); #ifdef Q_OS_WIN //OutputDebugString(reinterpret_cast(msg.utf16())); #elif defined Q_OS_MAC //objc_outputDebugString(msg); #elif defined Q_OS_LINUX && defined _DEBUG //std::cout << msg.toUtf8().constData(); #endif } void writeTcp(const QString &v) { QString msg(QString("%1 %2\n").arg(_logsEntryStart()).arg(v)); _logsWrite(LogDataTcp, msg); } void writeMtp(int32 dc, const QString &v) { QString msg(QString("%1 (dc:%2) %3\n").arg(_logsEntryStart()).arg(dc).arg(v)); _logsWrite(LogDataMtp, msg); } QString full() { if (LogsData) { return LogsData->full(); } if (!LogsInMemory || LogsInMemory == DeletedLogsInMemory) { return LogsBeforeSingleInstanceChecked; } int32 size = LogsBeforeSingleInstanceChecked.size(); for (LogsInMemoryList::const_iterator i = LogsInMemory->cbegin(), e = LogsInMemory->cend(); i != e; ++i) { if (i->first == LogDataMain) { size += i->second.size(); } } QString result; result.reserve(size); if (!LogsBeforeSingleInstanceChecked.isEmpty()) { result.append(LogsBeforeSingleInstanceChecked); } for (LogsInMemoryList::const_iterator i = LogsInMemory->cbegin(), e = LogsInMemory->cend(); i != e; ++i) { if (i->first == LogDataMain) { result += i->second; } } return result; } QString vector(const QVector &ids) { if (!ids.size()) return "[]"; QString idsStr = QString("[%1").arg(ids.cbegin()->v); for (QVector::const_iterator i = ids.cbegin() + 1, e = ids.cend(); i != e; ++i) { idsStr += QString(", %2").arg(i->v); } return idsStr + "]"; } QString vector(const QVector &ids) { if (!ids.size()) return "[]"; QString idsStr = QString("[%1").arg(*ids.cbegin()); for (QVector::const_iterator i = ids.cbegin() + 1, e = ids.cend(); i != e; ++i) { idsStr += QString(", %2").arg(*i); } return idsStr + "]"; } } void _moveOldDataFiles(const QString &wasDir) { QFile data(wasDir + "data"), dataConfig(wasDir + "data_config"), tdataConfig(wasDir + "tdata/config"); if (data.exists() && dataConfig.exists() && !QFileInfo(cWorkingDir() + "data").exists() && !QFileInfo(cWorkingDir() + "data_config").exists()) { // move to home dir LOG(("Copying data to home dir '%1' from '%2'").arg(cWorkingDir()).arg(wasDir)); if (data.copy(cWorkingDir() + "data")) { LOG(("Copied 'data' to home dir")); if (dataConfig.copy(cWorkingDir() + "data_config")) { LOG(("Copied 'data_config' to home dir")); bool tdataGood = true; if (tdataConfig.exists()) { tdataGood = false; QDir().mkpath(cWorkingDir() + "tdata"); if (tdataConfig.copy(cWorkingDir() + "tdata/config")) { LOG(("Copied 'tdata/config' to home dir")); tdataGood = true; } else { LOG(("Copied 'data' and 'data_config', but could not copy 'tdata/config'!")); } } if (tdataGood) { if (data.remove()) { LOG(("Removed 'data'")); } else { LOG(("Could not remove 'data'")); } if (dataConfig.remove()) { LOG(("Removed 'data_config'")); } else { LOG(("Could not remove 'data_config'")); } if (!tdataConfig.exists() || tdataConfig.remove()) { LOG(("Removed 'tdata/config'")); } else { LOG(("Could not remove 'tdata/config'")); } QDir().rmdir(wasDir + "tdata"); } } else { LOG(("Copied 'data', but could not copy 'data_config'!!")); } } else { LOG(("Could not copy 'data'!")); } } } #if defined Q_OS_MAC || defined Q_OS_LINUX32 || defined Q_OS_LINUX64 #include #include #include #ifdef Q_OS_MAC #include #endif #endif namespace SignalHandlers { namespace internal { using Annotations = std::map; using AnnotationRefs = std::map; Annotations ProcessAnnotations; AnnotationRefs ProcessAnnotationRefs; #ifndef TDESKTOP_DISABLE_CRASH_REPORTS QString ReportPath; FILE *ReportFile = nullptr; int ReportFileNo = 0; char LaunchedDateTimeStr[32] = { 0 }; char LaunchedBinaryName[256] = { 0 }; void writeChar(char ch) { fwrite(&ch, 1, 1, ReportFile); } template struct writeNumberSignAndRemoveIt { static void call(Type &number) { if (number < 0) { writeChar('-'); number = -number; } } }; template struct writeNumberSignAndRemoveIt { static void call(Type &number) { } }; template const dump &writeNumber(const dump &stream, Type number) { if (!ReportFile) return stream; writeNumberSignAndRemoveIt<(Type(-1) > Type(0)), Type>::call(number); Type upper = 1, prev = number / 10; while (prev >= upper) { upper *= 10; } while (upper > 0) { int digit = (number / upper); internal::writeChar('0' + digit); number -= digit * upper; upper /= 10; } return stream; } } // namespace internal dump::~dump() { if (internal::ReportFile) { fflush(internal::ReportFile); } } const dump &operator<<(const dump &stream, const char *str) { if (!internal::ReportFile) return stream; fwrite(str, 1, strlen(str), internal::ReportFile); return stream; } const dump &operator<<(const dump &stream, const wchar_t *str) { if (!internal::ReportFile) return stream; for (int i = 0, l = wcslen(str); i < l; ++i) { if (str[i] >= 0 && str[i] < 128) { internal::writeChar(char(str[i])); } else { internal::writeChar('?'); } } return stream; } const dump &operator<<(const dump &stream, int num) { return internal::writeNumber(stream, num); } const dump &operator<<(const dump &stream, unsigned int num) { return internal::writeNumber(stream, num); } const dump &operator<<(const dump &stream, unsigned long num) { return internal::writeNumber(stream, num); } const dump &operator<<(const dump &stream, unsigned long long num) { return internal::writeNumber(stream, num); } const dump &operator<<(const dump &stream, double num) { if (num < 0) { internal::writeChar('-'); num = -num; } internal::writeNumber(stream, uint64(floor(num))); internal::writeChar('.'); num -= floor(num); for (int i = 0; i < 4; ++i) { num *= 10; int digit = int(floor(num)); internal::writeChar('0' + digit); num -= digit; } return stream; } namespace internal { struct SomeAllocatedMemoryChunk { char data[1024 * 1024]; }; std_::unique_ptr SomeAllocatedMemory; void OperatorNewHandler() { std::set_new_handler(nullptr); SomeAllocatedMemory.reset(); t_assert(!"Could not allocate!"); } void InstallOperatorNewHandler() { SomeAllocatedMemory = std_::make_unique(); std::set_new_handler(OperatorNewHandler); } Qt::HANDLE ReportingThreadId = nullptr; bool ReportingHeaderWritten = false; QMutex ReportingMutex; const char *BreakpadDumpPath = nullptr; const wchar_t *BreakpadDumpPathW = nullptr; #if defined Q_OS_MAC || defined Q_OS_LINUX32 || defined Q_OS_LINUX64 struct sigaction SIG_def[32]; void Handler(int signum, siginfo_t *info, void *ucontext) { if (signum > 0) { sigaction(signum, &SIG_def[signum], 0); } #else // Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64 void Handler(int signum) { #endif // else for Q_OS_MAC || Q_OS_LINUX || Q_OS_LINUX64 const char* name = 0; switch (signum) { case SIGABRT: name = "SIGABRT"; break; case SIGSEGV: name = "SIGSEGV"; break; case SIGILL: name = "SIGILL"; break; case SIGFPE: name = "SIGFPE"; break; #ifndef Q_OS_WIN case SIGBUS: name = "SIGBUS"; break; case SIGSYS: name = "SIGSYS"; break; #endif // !Q_OS_WIN } Qt::HANDLE thread = QThread::currentThreadId(); if (thread == ReportingThreadId) return; QMutexLocker lock(&ReportingMutex); ReportingThreadId = thread; if (!ReportingHeaderWritten) { ReportingHeaderWritten = true; auto dec2hex = [](int value) -> char { if (value >= 0 && value < 10) { return '0' + value; } else if (value >= 10 && value < 16) { return 'a' + (value - 10); } return '#'; }; for (const auto &i : ProcessAnnotationRefs) { QByteArray utf8 = i.second->toUtf8(); std::string wrapped; wrapped.reserve(4 * utf8.size()); for (auto ch : utf8) { auto uch = static_cast(ch); wrapped.append("\\x", 2).append(1, dec2hex(uch >> 4)).append(1, dec2hex(uch & 0x0F)); } ProcessAnnotations[i.first] = wrapped; } const Annotations c_ProcessAnnotations(ProcessAnnotations); for (const auto &i : c_ProcessAnnotations) { dump() << i.first.c_str() << ": " << i.second.c_str() << "\n"; } psWriteDump(); dump() << "\n"; } if (name) { dump() << "Caught signal " << signum << " (" << name << ") in thread " << uint64(thread) << "\n"; } else if (signum == -1) { dump() << "Google Breakpad caught a crash, minidump written in thread " << uint64(thread) << "\n"; if (BreakpadDumpPath) { dump() << "Minidump: " << BreakpadDumpPath << "\n"; } else if (BreakpadDumpPathW) { dump() << "Minidump: " << BreakpadDumpPathW << "\n"; } } else { dump() << "Caught signal " << signum << " in thread " << uint64(thread) << "\n"; } // see https://github.com/benbjohnson/bandicoot #if defined Q_OS_MAC || defined Q_OS_LINUX32 || defined Q_OS_LINUX64 ucontext_t *uc = (ucontext_t*)ucontext; void *caller = 0; if (uc) { #if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6) /* OSX < 10.6 */ #if defined(__x86_64__) caller = (void*)uc->uc_mcontext->__ss.__rip; #elif defined(__i386__) caller = (void*)uc->uc_mcontext->__ss.__eip; #else caller = (void*)uc->uc_mcontext->__ss.__srr0; #endif #elif defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6) /* OSX >= 10.6 */ #if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__) caller = (void*)uc->uc_mcontext->__ss.__rip; #else caller = (void*)uc->uc_mcontext->__ss.__eip; #endif #elif defined(__linux__) /* Linux */ #if defined(__i386__) caller = (void*)uc->uc_mcontext.gregs[14]; /* Linux 32 */ #elif defined(__X86_64__) || defined(__x86_64__) caller = (void*)uc->uc_mcontext.gregs[16]; /* Linux 64 */ #elif defined(__ia64__) /* Linux IA64 */ caller = (void*)uc->uc_mcontext.sc_ip; #endif #endif } void *addresses[132] = { 0 }; size_t size = backtrace(addresses, 128); /* overwrite sigaction with caller's address */ if (caller) { for (int i = size; i > 1; --i) { addresses[i + 3] = addresses[i]; } addresses[2] = (void*)0x1; addresses[3] = caller; addresses[4] = (void*)0x1; } #ifdef Q_OS_MAC dump() << "\nBase image addresses:\n"; for (size_t i = 0; i < size; ++i) { Dl_info info; dump() << i << " "; if (dladdr(addresses[i], &info)) { dump() << uint64(info.dli_fbase) << " (" << info.dli_fname << ")\n"; } else { dump() << "_unknown_module_\n"; } } #endif // Q_OS_MAC dump() << "\nBacktrace:\n"; backtrace_symbols_fd(addresses, size, ReportFileNo); #else // Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64 dump() << "\nBacktrace:\n"; psWriteStackTrace(); #endif // else for Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64 dump() << "\n"; ReportingThreadId = nullptr; } bool SetSignalHandlers = true; bool CrashLogged = false; #if !defined Q_OS_MAC || defined MAC_USE_BREAKPAD google_breakpad::ExceptionHandler* BreakpadExceptionHandler = 0; #ifdef Q_OS_WIN bool DumpCallback(const wchar_t* _dump_dir, const wchar_t* _minidump_id, void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, bool success) #elif defined Q_OS_MAC // Q_OS_WIN bool DumpCallback(const char* _dump_dir, const char* _minidump_id, void *context, bool success) #elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32 // Q_OS_MAC bool DumpCallback(const google_breakpad::MinidumpDescriptor &md, void *context, bool success) #endif // Q_OS_LINUX64 || Q_OS_LINUX32 { if (CrashLogged) return success; CrashLogged = true; #ifdef Q_OS_WIN BreakpadDumpPathW = _minidump_id; Handler(-1); #else // Q_OS_WIN #ifdef Q_OS_MAC BreakpadDumpPath = _minidump_id; #else // Q_OS_MAC BreakpadDumpPath = md.path(); #endif // else for Q_OS_MAC Handler(-1, 0, 0); #endif // else for Q_OS_WIN return success; } #endif // !Q_OS_MAC || MAC_USE_BREAKPAD #endif // !TDESKTOP_DISABLE_CRASH_REPORTS } // namespace internal void StartCrashHandler() { #ifndef TDESKTOP_DISABLE_CRASH_REPORTS using internal::ProcessAnnotations; ProcessAnnotations["Binary"] = cExeName().toUtf8().constData(); ProcessAnnotations["ApiId"] = QString::number(ApiId).toUtf8().constData(); ProcessAnnotations["Version"] = (cBetaVersion() ? qsl("%1 beta").arg(cBetaVersion()) : (cAlphaVersion() ? qsl("%1 alpha") : qsl("%1")).arg(AppVersion)).toUtf8().constData(); ProcessAnnotations["Launched"] = QDateTime::currentDateTime().toString("dd.MM.yyyy hh:mm:ss").toUtf8().constData(); ProcessAnnotations["Platform"] = cPlatformString().toUtf8().constData(); ProcessAnnotations["UserTag"] = QString::number(Sandbox::UserTag(), 16).toUtf8().constData(); QString dumpspath = cWorkingDir() + qsl("tdata/dumps"); QDir().mkpath(dumpspath); #ifdef Q_OS_WIN internal::BreakpadExceptionHandler = new google_breakpad::ExceptionHandler( dumpspath.toStdWString(), google_breakpad::ExceptionHandler::FilterCallback(nullptr), internal::DumpCallback, (void*)nullptr, // callback_context google_breakpad::ExceptionHandler::HANDLER_ALL, MINIDUMP_TYPE(MiniDumpNormal), // MINIDUMP_TYPE(MiniDumpWithFullMemory | MiniDumpWithHandleData | MiniDumpWithThreadInfo | MiniDumpWithProcessThreadData | MiniDumpWithFullMemoryInfo | MiniDumpWithUnloadedModules | MiniDumpWithFullAuxiliaryState | MiniDumpIgnoreInaccessibleMemory | MiniDumpWithTokenInformation), (const wchar_t*)nullptr, // pipe_name (const google_breakpad::CustomClientInfo*)nullptr ); #elif defined Q_OS_MAC // Q_OS_WIN #ifdef MAC_USE_BREAKPAD #ifndef _DEBUG internal::BreakpadExceptionHandler = new google_breakpad::ExceptionHandler( QFile::encodeName(dumpspath).toStdString(), /*FilterCallback*/ 0, internal::DumpCallback, /*context*/ 0, true, 0 ); #endif // !_DEBUG internal::SetSignalHandlers = false; #else // MAC_USE_BREAKPAD crashpad::CrashpadClient crashpad_client; std::string handler = (cExeDir() + cExeName() + qsl("/Contents/Helpers/crashpad_handler")).toUtf8().constData(); std::string database = QFile::encodeName(dumpspath).constData(); if (crashpad_client.StartHandler(base::FilePath(handler), base::FilePath(database), std::string(), ProcessAnnotations, std::vector(), false)) { crashpad_client.UseHandler(); } #endif // else for MAC_USE_BREAKPAD #elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32 internal::BreakpadExceptionHandler = new google_breakpad::ExceptionHandler( google_breakpad::MinidumpDescriptor(QFile::encodeName(dumpspath).toStdString()), /*FilterCallback*/ 0, internal::DumpCallback, /*context*/ 0, true, -1 ); #endif // Q_OS_LINUX64 || Q_OS_LINUX32 #endif // !TDESKTOP_DISABLE_CRASH_REPORTS } void FinishCrashHandler() { #ifndef TDESKTOP_DISABLE_CRASH_REPORTS #if !defined Q_OS_MAC || defined MAC_USE_BREAKPAD if (internal::BreakpadExceptionHandler) { delete base::take(internal::BreakpadExceptionHandler); } #endif // !Q_OS_MAC || MAC_USE_BREAKPAD #endif // !TDESKTOP_DISABLE_CRASH_REPORTS } Status start() { #ifndef TDESKTOP_DISABLE_CRASH_REPORTS using internal::ReportPath; ReportPath = cWorkingDir() + qsl("tdata/working"); #ifdef Q_OS_WIN FILE *f = nullptr; if (_wfopen_s(&f, ReportPath.toStdWString().c_str(), L"rb") != 0) { f = nullptr; } else { #else // !Q_OS_WIN if (FILE *f = fopen(QFile::encodeName(ReportPath).constData(), "rb")) { #endif // else for !Q_OS_WIN QByteArray lastdump; char buffer[256 * 1024] = { 0 }; int32 read = fread(buffer, 1, 256 * 1024, f); if (read > 0) { lastdump.append(buffer, read); } fclose(f); Sandbox::SetLastCrashDump(lastdump); LOG(("Opened '%1' for reading, the previous Telegram Desktop launch was not finished properly :( Crash log size: %2").arg(ReportPath).arg(lastdump.size())); return LastCrashed; } #endif // !TDESKTOP_DISABLE_CRASH_REPORTS return restart(); } Status restart() { #ifndef TDESKTOP_DISABLE_CRASH_REPORTS if (internal::ReportFile) { return Started; } #ifdef Q_OS_WIN if (_wfopen_s(&internal::ReportFile, internal::ReportPath.toStdWString().c_str(), L"wb") != 0) { internal::ReportFile = nullptr; } #else // Q_OS_WIN internal::ReportFile = fopen(QFile::encodeName(internal::ReportPath).constData(), "wb"); #endif // else for Q_OS_WIN if (internal::ReportFile) { #ifdef Q_OS_WIN internal::ReportFileNo = _fileno(internal::ReportFile); #else // Q_OS_WIN internal::ReportFileNo = fileno(internal::ReportFile); #endif // else for Q_OS_WIN if (internal::SetSignalHandlers) { #ifndef Q_OS_WIN struct sigaction sigact; sigact.sa_sigaction = SignalHandlers::internal::Handler; sigemptyset(&sigact.sa_mask); sigact.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO; sigaction(SIGABRT, &sigact, &internal::SIG_def[SIGABRT]); sigaction(SIGSEGV, &sigact, &internal::SIG_def[SIGSEGV]); sigaction(SIGILL, &sigact, &internal::SIG_def[SIGILL]); sigaction(SIGFPE, &sigact, &internal::SIG_def[SIGFPE]); sigaction(SIGBUS, &sigact, &internal::SIG_def[SIGBUS]); sigaction(SIGSYS, &sigact, &internal::SIG_def[SIGSYS]); #else // !Q_OS_WIN signal(SIGABRT, SignalHandlers::internal::Handler); signal(SIGSEGV, SignalHandlers::internal::Handler); signal(SIGILL, SignalHandlers::internal::Handler); signal(SIGFPE, SignalHandlers::internal::Handler); #endif // else for !Q_OS_WIN } SignalHandlers::internal::InstallOperatorNewHandler(); return Started; } LOG(("FATAL: Could not open '%1' for writing!").arg(internal::ReportPath)); return CantOpen; #else // !TDESKTOP_DISABLE_CRASH_REPORTS return Started; #endif // else for !TDESKTOP_DISABLE_CRASH_REPORTS } void finish() { #ifndef TDESKTOP_DISABLE_CRASH_REPORTS FinishCrashHandler(); if (internal::ReportFile) { fclose(internal::ReportFile); internal::ReportFile = nullptr; #ifdef Q_OS_WIN _wunlink(internal::ReportPath.toStdWString().c_str()); #else // Q_OS_WIN unlink(internal::ReportPath.toUtf8().constData()); #endif // else for Q_OS_WIN } #endif // !TDESKTOP_DISABLE_CRASH_REPORTS } void setCrashAnnotation(const std::string &key, const QString &value) { if (!value.trimmed().isEmpty()) { internal::ProcessAnnotations[key] = value.toUtf8().constData(); } else { internal::ProcessAnnotations.erase(key); } } void setCrashAnnotationRef(const std::string &key, const QString *valuePtr) { if (valuePtr) { internal::ProcessAnnotationRefs[key] = valuePtr; } else { internal::ProcessAnnotationRefs.erase(key); } } }