tdesktop/Telegram/SourceFiles/logs.cpp
John Preston 08167a6a91 Removed #include "stdafx.h" from all files.
Currently the build without implicitly included precompiled header
is not supported anyway (because Qt MOC source files do not include
stdafx.h, they include plain headers).

So when we decide to support building without implicitly included
precompiled headers we'll have to fix all the headers anyway.
2017-03-04 12:27:52 +03:00

1146 lines
33 KiB
C++

/*
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-2017 John Preston, https://desktop.telegram.org
*/
#include "logs.h"
#include <signal.h>
#include <new>
#include "pspecific.h"
#include "mtproto/connection.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 <unistd.h>
#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());
auto thread = qobject_cast<MTP::internal::Thread*>(QThread::currentThread());
auto threadId = thread ? thread->getThreadIndex() : 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<QFile> 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<QIODevice::OpenModeFlag> 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<QFile> 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<QPair<LogDataType, QString> > LogsInMemoryList;
LogsInMemoryList *LogsInMemory = 0;
LogsInMemoryList *DeletedLogsInMemory = SharedMemoryLocation<LogsInMemoryList, 0>();
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;
#elif defined OS_WIN_STORE
#ifdef _DEBUG
cForceWorkingDir(cExeDir());
#else // _DEBUG
cForceWorkingDir(psAppDataPath());
#endif // else for _DEBUG
#endif // OS_WIN_STORE
}
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<const wchar_t *>(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<MTPlong> &ids) {
if (!ids.size()) return "[]";
QString idsStr = QString("[%1").arg(ids.cbegin()->v);
for (QVector<MTPlong>::const_iterator i = ids.cbegin() + 1, e = ids.cend(); i != e; ++i) {
idsStr += QString(", %2").arg(i->v);
}
return idsStr + "]";
}
QString vector(const QVector<uint64> &ids) {
if (!ids.size()) return "[]";
QString idsStr = QString("[%1").arg(*ids.cbegin());
for (QVector<uint64>::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 <execinfo.h>
#include <signal.h>
#include <sys/syscall.h>
#ifdef Q_OS_MAC
#include <dlfcn.h>
#endif
#endif
namespace SignalHandlers {
namespace internal {
using Annotations = std::map<std::string, std::string>;
using AnnotationRefs = std::map<std::string, const QString*>;
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 <bool Unsigned, typename Type>
struct writeNumberSignAndRemoveIt {
static void call(Type &number) {
if (number < 0) {
writeChar('-');
number = -number;
}
}
};
template <typename Type>
struct writeNumberSignAndRemoveIt<true, Type> {
static void call(Type &number) {
}
};
template <typename Type>
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<SomeAllocatedMemoryChunk> SomeAllocatedMemory;
void InstallOperatorNewHandler() {
SomeAllocatedMemory = std::make_unique<SomeAllocatedMemoryChunk>();
std::set_new_handler([] {
std::set_new_handler(nullptr);
SomeAllocatedMemory.reset();
t_assert(!"Could not allocate!");
});
}
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<uchar>(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<std::string>(),
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);
}
}
}