764 lines
26 KiB
C++
764 lines
26 KiB
C++
/*
|
|
This file is part of the KDE Libraries
|
|
SPDX-FileCopyrightText: 2000 Timo Hummel <timo.hummel@sap.com>
|
|
SPDX-FileCopyrightText: 2000 Tom Braun <braunt@fh-konstanz.de>
|
|
SPDX-FileCopyrightText: 2010 George Kiagiadakis <kiagiadakis.george@gmail.com>
|
|
SPDX-FileCopyrightText: 2009 KDE e.V. <kde-ev-board@kde.org>
|
|
SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org>
|
|
SPDX-FileContributor: 2009 Adriaan de Groot <groot@kde.org>
|
|
|
|
SPDX-License-Identifier: LGPL-2.0-or-later
|
|
*/
|
|
|
|
#include "kcrash.h"
|
|
|
|
#include "kcrash_debug.h"
|
|
|
|
#include <config-kcrash.h>
|
|
|
|
#include <csignal>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
#include <qplatformdefs.h>
|
|
#ifndef Q_OS_WIN
|
|
#include <cerrno>
|
|
#include <grp.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/un.h>
|
|
#else
|
|
#include <qt_windows.h>
|
|
#endif
|
|
#ifdef Q_OS_LINUX
|
|
#include <sys/prctl.h>
|
|
#endif
|
|
|
|
#include <KAboutData>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <memory>
|
|
|
|
#include <QDebug>
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QGuiApplication>
|
|
#include <QLibraryInfo>
|
|
#include <QOffscreenSurface>
|
|
#include <QOpenGLContext>
|
|
#include <QOpenGLFunctions>
|
|
#include <QStandardPaths>
|
|
#include <QThread>
|
|
|
|
#if HAVE_X11
|
|
#include <X11/Xlib.h>
|
|
#endif
|
|
|
|
#include "coreconfig_p.h"
|
|
#include "exception_p.h"
|
|
#include "metadata_p.h"
|
|
|
|
// WARNING: do not use qGlobalStatics in here, they get destroyed too early on
|
|
// shutdown and may inhibit crash handling in late-exit scenarios (e.g. when
|
|
// a function local static gets destroyed by __cxa_finalize)
|
|
#undef Q_GLOBAL_STATIC
|
|
|
|
using namespace std::chrono_literals;
|
|
|
|
struct Args {
|
|
Args() = default;
|
|
~Args()
|
|
{
|
|
clear();
|
|
}
|
|
Q_DISABLE_COPY_MOVE(Args)
|
|
|
|
void clear()
|
|
{
|
|
if (!argc) {
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < argc; ++i) {
|
|
delete[] argv[i];
|
|
}
|
|
delete[] argv;
|
|
|
|
argv = nullptr;
|
|
argc = 0;
|
|
}
|
|
|
|
void resize(int size)
|
|
{
|
|
clear();
|
|
argc = size;
|
|
argv = new char *[argc + 1];
|
|
for (int i = 0; i < argc + 1; ++i) {
|
|
argv[i] = nullptr;
|
|
}
|
|
}
|
|
|
|
explicit operator bool() const
|
|
{
|
|
return argc > 0;
|
|
}
|
|
|
|
int argc = 0;
|
|
// null-terminated array of null-terminated strings
|
|
char **argv = nullptr;
|
|
};
|
|
|
|
static KCrash::HandlerType s_emergencySaveFunction = nullptr;
|
|
static KCrash::HandlerType s_crashHandler = nullptr;
|
|
static std::unique_ptr<char[]> s_appFilePath; // this is the actual QCoreApplication::applicationFilePath
|
|
static std::unique_ptr<char[]> s_appName; // the binary name (may be altered by the application)
|
|
static std::unique_ptr<char[]> s_appPath; // the binary dir path (may be altered by the application)
|
|
static Args s_autoRestartCommandLine;
|
|
static std::unique_ptr<char[]> s_drkonqiPath;
|
|
static KCrash::CrashFlags s_flags = KCrash::CrashFlags();
|
|
static int s_launchDrKonqi = -1; // -1=initial value 0=disabled 1=enabled
|
|
static int s_originalSignal = -1;
|
|
static QByteArray s_metadataPath;
|
|
|
|
static std::unique_ptr<char[]> s_kcrashErrorMessage;
|
|
|
|
namespace
|
|
{
|
|
const KCrash::CoreConfig s_coreConfig;
|
|
|
|
std::unique_ptr<char[]> s_glRenderer; // the 0
|
|
std::unique_ptr<char[]> s_qtVersion;
|
|
|
|
QString glRenderer()
|
|
{
|
|
return QString();
|
|
}
|
|
|
|
QString bootId()
|
|
{
|
|
#ifdef Q_OS_LINUX
|
|
QFile file(QStringLiteral("/proc/sys/kernel/random/boot_id"));
|
|
if (!file.open(QFile::ReadOnly)) {
|
|
qCWarning(LOG_KCRASH) << "Failed to read /proc/sys/kernel/random/boot_id" << file.errorString();
|
|
return {};
|
|
}
|
|
return QString::fromUtf8(file.readAll().simplified().replace('-', QByteArrayView()));
|
|
#else
|
|
return {};
|
|
#endif
|
|
}
|
|
|
|
} // namespace
|
|
|
|
static QStringList libexecPaths()
|
|
{
|
|
// Static since we only need to evaluate once.
|
|
static QStringList list = QFile::decodeName(qgetenv("LIBEXEC_PATH")).split(QLatin1Char(':'), Qt::SkipEmptyParts) // env var is used first
|
|
+ QStringList{
|
|
QCoreApplication::applicationDirPath(), // then look where our application binary is located
|
|
QLibraryInfo::path(QLibraryInfo::LibraryExecutablesPath), // look where libexec path is (can be set in qt.conf)
|
|
QFile::decodeName(KDE_INSTALL_FULL_LIBEXECDIR) // look at our installation location
|
|
};
|
|
return list;
|
|
}
|
|
|
|
namespace KCrash
|
|
{
|
|
void setApplicationFilePath(const QString &filePath);
|
|
void startProcess(int argc, const char *argv[], bool waitAndExit);
|
|
|
|
#if defined(Q_OS_WIN)
|
|
LONG WINAPI win32UnhandledExceptionFilter(_EXCEPTION_POINTERS *exceptionInfo);
|
|
#endif
|
|
}
|
|
|
|
static bool shouldWriteMetadataToDisk()
|
|
{
|
|
#ifdef Q_OS_LINUX
|
|
// NB: The daemon being currently running must not be a condition here. If something crashes during logout
|
|
// the daemon may already be gone but we'll still want to deal with the crash on next login!
|
|
// Similar reasoning applies to not checking the presence of the launcher socket.
|
|
const bool drkonqiCoredumpHelper = !QStandardPaths::findExecutable(QStringLiteral("drkonqi-coredump-processor"), libexecPaths()).isEmpty();
|
|
return s_coreConfig.isCoredumpd() && drkonqiCoredumpHelper && !qEnvironmentVariableIsSet("KCRASH_NO_METADATA");
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void KCrash::initialize()
|
|
{
|
|
if (s_launchDrKonqi == 0) { // disabled by the program itself
|
|
return;
|
|
}
|
|
|
|
bool enableDrKonqi = !qEnvironmentVariableIsSet("KDE_DEBUG");
|
|
if (qEnvironmentVariableIsSet("KCRASH_AUTO_RESTARTED") || qEnvironmentVariableIntValue("RUNNING_UNDER_RR") == 1
|
|
|| qEnvironmentVariableIntValue("KCRASH_DUMP_ONLY") == 1) {
|
|
enableDrKonqi = false;
|
|
}
|
|
|
|
const QStringList args = QCoreApplication::arguments();
|
|
// Default to core dumping whenever a process is set. When not or when explicitly opting into just in time debugging
|
|
// we enable drkonqi. This causes the signal handler to directly fork drkonqi opening us to race conditions.
|
|
// NOTE: depending on the specific signal other threads are running while the signal handler runs and may trip over
|
|
// the signal handler's closed FDs. That is primarily why we do not like JIT debugging.
|
|
if (enableDrKonqi && (!s_coreConfig.isProcess() || qEnvironmentVariableIntValue("KCRASH_JIT_DRKONQI") == 1)) {
|
|
KCrash::setDrKonqiEnabled(true);
|
|
} else {
|
|
// Don't qDebug here, it loads qtlogging.ini very early which prevents unittests from doing QStandardPaths::setTestModeEnabled(true) in initTestCase()
|
|
}
|
|
|
|
s_qtVersion.reset(qstrdup(qVersion()));
|
|
|
|
if (QCoreApplication::instance()) {
|
|
const QString path = QCoreApplication::applicationFilePath();
|
|
s_appFilePath.reset(qstrdup(qPrintable(path))); // This intentionally cannot be changed by the application!
|
|
KCrash::setApplicationFilePath(path);
|
|
if (qobject_cast<QGuiApplication *>(QCoreApplication::instance())) {
|
|
s_glRenderer.reset(qstrdup(glRenderer().toUtf8().constData()));
|
|
}
|
|
} else {
|
|
qWarning() << "This process needs a QCoreApplication instance in order to use KCrash";
|
|
}
|
|
|
|
if (shouldWriteMetadataToDisk()) {
|
|
// We do not actively clean up metadata via KCrash but some other service. This potentially means we litter
|
|
// a lot -> put the metadata in a subdir.
|
|
// This data is consumed by DrKonqi in combination with coredumpd metadata.
|
|
const QString metadataDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/kcrash-metadata");
|
|
if (QDir().mkpath(metadataDir)) {
|
|
const auto bootId = ::bootId();
|
|
const auto exe = QString::fromUtf8(s_appName.get());
|
|
const auto pid = QString::number(QCoreApplication::applicationPid());
|
|
s_metadataPath = QFile::encodeName(metadataDir + //
|
|
QStringLiteral("/%1.%2.%3.ini").arg(exe, bootId, pid));
|
|
}
|
|
if (!s_crashHandler) {
|
|
// Always enable the default handler. We cannot create the metadata ahead of time since we do not know
|
|
// when the application metadata is "complete".
|
|
// TODO: kf6 maybe change the way init works and have the users run it when their done with kaboutdata etc.?
|
|
// the problem with delayed writing is that our crash handler (or any crash handler really) introduces a delay
|
|
// in dumping and this in turn increases the risk of another stepping into a puddle (SEGV only runs on the
|
|
// faulting thread; all other threads continue running!). therefore it'd be greatly preferred if we
|
|
// were able to write the metadata during initial app setup instead of when a crash occurs
|
|
setCrashHandler(defaultCrashHandler);
|
|
}
|
|
} // empty s_metadataPath disables writing
|
|
}
|
|
|
|
void KCrash::setEmergencySaveFunction(HandlerType saveFunction)
|
|
{
|
|
s_emergencySaveFunction = saveFunction;
|
|
|
|
/*
|
|
* We need at least the default crash handler for
|
|
* emergencySaveFunction to be called
|
|
*/
|
|
if (s_emergencySaveFunction && !s_crashHandler) {
|
|
setCrashHandler(defaultCrashHandler);
|
|
}
|
|
}
|
|
|
|
KCrash::HandlerType KCrash::emergencySaveFunction()
|
|
{
|
|
return s_emergencySaveFunction;
|
|
}
|
|
|
|
// Set the default crash handler in 10 seconds
|
|
// This is used after an autorestart, the second instance of the application
|
|
// is started with KCRASH_AUTO_RESTARTED=1, and we
|
|
// set the defaultCrashHandler (to handle autorestart) after 10s.
|
|
// The delay is to see if we stay up for more than 10s time, to avoid infinite
|
|
// respawning if the app crashes on startup.
|
|
class KCrashDelaySetHandler : public QObject
|
|
{
|
|
public:
|
|
KCrashDelaySetHandler()
|
|
{
|
|
startTimer(10s);
|
|
}
|
|
|
|
protected:
|
|
void timerEvent(QTimerEvent *event) override
|
|
{
|
|
if (!s_crashHandler) { // not set meanwhile
|
|
KCrash::setCrashHandler(KCrash::defaultCrashHandler);
|
|
}
|
|
killTimer(event->timerId());
|
|
this->deleteLater();
|
|
}
|
|
};
|
|
|
|
void KCrash::setFlags(KCrash::CrashFlags flags)
|
|
{
|
|
s_flags = flags;
|
|
if (s_flags & AutoRestart) {
|
|
// We need at least the default crash handler for autorestart to work.
|
|
if (!s_crashHandler) {
|
|
if (qEnvironmentVariableIsSet("KCRASH_AUTO_RESTARTED")) {
|
|
new KCrashDelaySetHandler;
|
|
} else {
|
|
setCrashHandler(defaultCrashHandler);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void KCrash::setApplicationFilePath(const QString &filePath)
|
|
{
|
|
const auto pos = filePath.lastIndexOf(QLatin1Char('/'));
|
|
const QString appName = filePath.mid(pos + 1);
|
|
const QString appPath = filePath.left(pos); // could be empty, in theory
|
|
|
|
s_appName.reset(qstrdup(QFile::encodeName(appName).constData()));
|
|
s_appPath.reset(qstrdup(QFile::encodeName(appPath).constData()));
|
|
|
|
// Prepare the auto-restart command
|
|
QStringList args = QCoreApplication::arguments();
|
|
if (args.isEmpty()) { // edge case: tst_QX11Info::startupId does QApplication app(argc, nullptr)...
|
|
args.append(filePath);
|
|
} else {
|
|
args[0] = filePath; // replace argv[0] with full path above
|
|
}
|
|
|
|
s_autoRestartCommandLine.resize(args.count());
|
|
for (int i = 0; i < args.count(); ++i) {
|
|
s_autoRestartCommandLine.argv[i] = qstrdup(QFile::encodeName(args.at(i)).constData());
|
|
}
|
|
}
|
|
|
|
void KCrash::setDrKonqiEnabled(bool enabled)
|
|
{
|
|
const int launchDrKonqi = enabled ? 1 : 0;
|
|
if (s_launchDrKonqi == launchDrKonqi) {
|
|
return;
|
|
}
|
|
s_launchDrKonqi = launchDrKonqi;
|
|
if (s_launchDrKonqi && !s_drkonqiPath) {
|
|
const QString exec = QStandardPaths::findExecutable(QStringLiteral("drkonqi"), libexecPaths());
|
|
if (exec.isEmpty()) {
|
|
qCDebug(LOG_KCRASH) << "Could not find drkonqi in search paths:" << libexecPaths();
|
|
s_launchDrKonqi = 0;
|
|
} else {
|
|
s_drkonqiPath.reset(qstrdup(qPrintable(exec)));
|
|
}
|
|
}
|
|
|
|
// we need at least the default crash handler to launch drkonqi
|
|
if (s_launchDrKonqi && !s_crashHandler) {
|
|
setCrashHandler(defaultCrashHandler);
|
|
}
|
|
}
|
|
|
|
bool KCrash::isDrKonqiEnabled()
|
|
{
|
|
return s_launchDrKonqi == 1;
|
|
}
|
|
|
|
void KCrash::setCrashHandler(HandlerType handler)
|
|
{
|
|
#if defined(Q_OS_WIN)
|
|
static LPTOP_LEVEL_EXCEPTION_FILTER s_previousExceptionFilter = NULL;
|
|
|
|
if (handler && !s_previousExceptionFilter) {
|
|
s_previousExceptionFilter = SetUnhandledExceptionFilter(KCrash::win32UnhandledExceptionFilter);
|
|
} else if (!handler && s_previousExceptionFilter) {
|
|
SetUnhandledExceptionFilter(s_previousExceptionFilter);
|
|
s_previousExceptionFilter = NULL;
|
|
}
|
|
#else
|
|
if (!handler) {
|
|
handler = SIG_DFL;
|
|
}
|
|
|
|
sigset_t mask;
|
|
sigemptyset(&mask);
|
|
|
|
const auto signals = {
|
|
#ifdef SIGSEGV
|
|
SIGSEGV,
|
|
#endif
|
|
#ifdef SIGBUS
|
|
SIGBUS,
|
|
#endif
|
|
#ifdef SIGFPE
|
|
SIGFPE,
|
|
#endif
|
|
#ifdef SIGILL
|
|
SIGILL,
|
|
#endif
|
|
#ifdef SIGABRT
|
|
SIGABRT,
|
|
#endif
|
|
};
|
|
|
|
for (const auto &signal : signals) {
|
|
struct sigaction action {
|
|
};
|
|
action.sa_handler = handler;
|
|
action.sa_flags = SA_RESTART;
|
|
sigemptyset(&action.sa_mask);
|
|
sigaction(signal, &action, nullptr);
|
|
sigaddset(&mask, signal);
|
|
}
|
|
|
|
sigprocmask(SIG_UNBLOCK, &mask, nullptr);
|
|
#endif
|
|
|
|
s_crashHandler = handler;
|
|
}
|
|
|
|
KCrash::HandlerType KCrash::crashHandler()
|
|
{
|
|
return s_crashHandler;
|
|
}
|
|
|
|
#if !defined(Q_OS_WIN) && !defined(Q_OS_OSX)
|
|
static void closeAllFDs()
|
|
{
|
|
// Close all remaining file descriptors except for stdin/stdout/stderr
|
|
struct rlimit rlp = {};
|
|
getrlimit(RLIMIT_NOFILE, &rlp);
|
|
for (rlim_t i = 3; i < rlp.rlim_cur; i++) {
|
|
close(i);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void crashOnSigTerm(int sig)
|
|
{
|
|
Q_UNUSED(sig)
|
|
raise(s_originalSignal);
|
|
}
|
|
|
|
void KCrash::defaultCrashHandler(int sig)
|
|
{
|
|
// WABA: Do NOT use qDebug() in this function because it is much too risky!
|
|
// Handle possible recursions
|
|
static int crashRecursionCounter = 0;
|
|
crashRecursionCounter++; // Nothing before this, please !
|
|
s_originalSignal = sig;
|
|
|
|
#if !defined(Q_OS_WIN)
|
|
signal(SIGALRM, SIG_DFL);
|
|
alarm(3); // Kill me... (in case we deadlock in malloc)
|
|
#endif
|
|
|
|
if (crashRecursionCounter < 2) {
|
|
if (s_emergencySaveFunction) {
|
|
s_emergencySaveFunction(sig);
|
|
}
|
|
if ((s_flags & AutoRestart) && s_autoRestartCommandLine) {
|
|
QThread::sleep(1);
|
|
startProcess(s_autoRestartCommandLine.argc, const_cast<const char **>(s_autoRestartCommandLine.argv), false);
|
|
}
|
|
crashRecursionCounter++;
|
|
}
|
|
|
|
if (crashRecursionCounter < 3) {
|
|
// If someone is telling me to stop while I'm already crashing, then I should resume crashing
|
|
signal(SIGTERM, &crashOnSigTerm);
|
|
|
|
// NB: all metadata writing ought to happen before closing FDs to reduce synchronization problems with dbus.
|
|
|
|
// WARNING: do not forget to increase Metadata::argv's size when adding more potential arguments!
|
|
Metadata data(s_drkonqiPath.get());
|
|
#ifdef Q_OS_LINUX
|
|
// The ini is required to be scoped here, as opposed to the conditional scope, so its lifetime is the same as
|
|
// the regular data instance!
|
|
MetadataINIWriter ini(s_metadataPath);
|
|
// s_appFilePath can point to nullptr
|
|
// not exactly sure how, maybe some race condition due to KCrashDelaySetHandler ?
|
|
if (!s_appFilePath) {
|
|
fprintf(stderr, "KCrash: appFilePath points to nullptr!\n");
|
|
} else if (ini.isWritable()) {
|
|
// Add the canonical exe path so the coredump daemon has more data points to map metadata to journald entry.
|
|
ini.add("--exe", s_appFilePath.get(), MetadataWriter::BoolValue::No);
|
|
data.setAdditionalWriter(&ini);
|
|
}
|
|
#endif
|
|
|
|
if (auto optionalExceptionMetadata = KCrash::exceptionMetadata(); optionalExceptionMetadata.has_value()) {
|
|
if (optionalExceptionMetadata->klass) {
|
|
data.add("--exceptionname", optionalExceptionMetadata->klass);
|
|
}
|
|
if (optionalExceptionMetadata->what) {
|
|
data.add("--exceptionwhat", optionalExceptionMetadata->what);
|
|
}
|
|
}
|
|
|
|
if (s_glRenderer) {
|
|
data.add("--glrenderer", s_glRenderer.get());
|
|
}
|
|
|
|
if (s_qtVersion) {
|
|
data.add("--qtversion", s_qtVersion.get());
|
|
}
|
|
|
|
data.add("--kdeframeworksversion", KCRASH_VERSION_STRING);
|
|
|
|
const QByteArray platformName = QGuiApplication::platformName().toUtf8();
|
|
if (!platformName.isEmpty()) {
|
|
if (strcmp(platformName.constData(), "wayland-org.kde.kwin.qpa") == 0) { // redirect kwin's internal QPA to wayland proper
|
|
data.add("--platform", "wayland");
|
|
} else {
|
|
data.add("--platform", platformName.constData());
|
|
}
|
|
}
|
|
|
|
#if HAVE_X11
|
|
if (platformName == QByteArrayLiteral("xcb")) {
|
|
// start up on the correct display
|
|
char *display = nullptr;
|
|
if (auto disp = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->display()) {
|
|
display = XDisplayString(disp);
|
|
} else {
|
|
display = getenv("DISPLAY");
|
|
}
|
|
data.add("--display", display);
|
|
}
|
|
#endif
|
|
|
|
data.add("--appname", s_appName ? s_appName.get() : "<unknown>");
|
|
|
|
// only add apppath if it's not NULL
|
|
if (s_appPath && s_appPath[0]) {
|
|
data.add("--apppath", s_appPath.get());
|
|
}
|
|
|
|
// signal number -- will never be NULL
|
|
char sigtxt[10];
|
|
sprintf(sigtxt, "%d", sig);
|
|
data.add("--signal", sigtxt);
|
|
|
|
char pidtxt[20];
|
|
sprintf(pidtxt, "%lld", QCoreApplication::applicationPid());
|
|
data.add("--pid", pidtxt);
|
|
|
|
const KAboutData *about = KAboutData::applicationDataPointer();
|
|
if (about) {
|
|
if (about->internalVersion()) {
|
|
data.add("--appversion", about->internalVersion());
|
|
}
|
|
|
|
if (about->internalProgramName()) {
|
|
data.add("--programname", about->internalProgramName());
|
|
}
|
|
|
|
if (about->internalBugAddress()) {
|
|
data.add("--bugaddress", about->internalBugAddress());
|
|
}
|
|
|
|
if (about->internalProductName()) {
|
|
data.add("--productname", about->internalProductName());
|
|
}
|
|
}
|
|
|
|
if (s_flags & SaferDialog) {
|
|
data.addBool("--safer");
|
|
}
|
|
|
|
if ((s_flags & AutoRestart) && s_autoRestartCommandLine) {
|
|
data.addBool("--restarted");
|
|
}
|
|
|
|
#if defined(Q_OS_WIN)
|
|
char threadId[8] = {0};
|
|
sprintf(threadId, "%d", GetCurrentThreadId());
|
|
data.add("--thread", threadId);
|
|
#endif
|
|
|
|
data.close();
|
|
const int argc = data.argc;
|
|
const char **argv = data.argv.data();
|
|
|
|
fprintf(stderr, "KCrash: Application '%s' crashing... crashRecursionCounter = %d\n", s_appName ? s_appName.get() : "<unknown>", crashRecursionCounter);
|
|
|
|
if (s_launchDrKonqi != 1) {
|
|
setCrashHandler(nullptr);
|
|
#if !defined(Q_OS_WIN)
|
|
raise(sig); // dump core, or whatever is the default action for this signal.
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
#if !defined(Q_OS_WIN) && !defined(Q_OS_OSX)
|
|
if (!(s_flags & KeepFDs)) {
|
|
// This tries to prevent problems where applications fail to release resources that drkonqi might need.
|
|
// Specifically this was introduced to ensure that an application that had grabbed the X11 cursor would
|
|
// forcefully have it removed upon crash to ensure it is ungrabbed by the time drkonqi makes an appearance.
|
|
// This is also the point in time when, for example, dbus services are lost. Closing the socket indicates
|
|
// to dbus-daemon that the process has disappeared and it will forcefully reclaim the registered service names.
|
|
//
|
|
// Once we close our socket we lose potential dbus names and if we were running as a systemd service anchored to a name,
|
|
// the daemon may decide to jump at us with a TERM signal. We'll want to have finished the metadata by now and
|
|
// be near our tracing/raise().
|
|
closeAllFDs();
|
|
}
|
|
#if HAVE_X11
|
|
else if (auto display = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->display()) {
|
|
close(ConnectionNumber(display));
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#ifndef NDEBUG
|
|
fprintf(stderr,
|
|
"KCrash: Application Name = %s path = %s pid = %lld\n",
|
|
s_appName ? s_appName.get() : "<unknown>",
|
|
s_appPath ? s_appPath.get() : "<unknown>",
|
|
QCoreApplication::applicationPid());
|
|
fprintf(stderr, "KCrash: Arguments: ");
|
|
for (int i = 0; i < s_autoRestartCommandLine.argc; ++i) {
|
|
fprintf(stderr, "%s ", s_autoRestartCommandLine.argv[i]);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
#endif
|
|
|
|
startProcess(argc, argv, true);
|
|
}
|
|
|
|
if (crashRecursionCounter < 4) {
|
|
fprintf(stderr, "Unable to start Dr. Konqi\n");
|
|
}
|
|
|
|
if (s_coreConfig.isProcess()) {
|
|
fprintf(stderr, "Re-raising signal for core dump handling.\n");
|
|
KCrash::setCrashHandler(nullptr);
|
|
raise(sig);
|
|
// not getting here
|
|
}
|
|
|
|
_exit(255);
|
|
}
|
|
|
|
#if defined(Q_OS_WIN)
|
|
|
|
void KCrash::startProcess(int argc, const char *argv[], bool waitAndExit)
|
|
{
|
|
QString cmdLine;
|
|
for (int i = 0; i < argc; ++i) {
|
|
cmdLine.append(QLatin1Char('\"'));
|
|
cmdLine.append(QFile::decodeName(argv[i]));
|
|
cmdLine.append(QStringLiteral("\" "));
|
|
}
|
|
|
|
PROCESS_INFORMATION procInfo;
|
|
STARTUPINFOW startupInfo =
|
|
{sizeof(STARTUPINFO), 0, 0, 0, (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
|
|
bool success = CreateProcess(0, (wchar_t *)cmdLine.utf16(), NULL, NULL, false, CREATE_UNICODE_ENVIRONMENT, NULL, NULL, &startupInfo, &procInfo);
|
|
|
|
if (success && waitAndExit) {
|
|
// wait for child to exit
|
|
WaitForSingleObject(procInfo.hProcess, INFINITE);
|
|
_exit(253);
|
|
}
|
|
}
|
|
|
|
// glue function for calling the unix signal handler from the windows unhandled exception filter
|
|
LONG WINAPI KCrash::win32UnhandledExceptionFilter(_EXCEPTION_POINTERS *exceptionInfo)
|
|
{
|
|
// kdbgwin needs the context inside exceptionInfo because if getting the context after the
|
|
// exception happened, it will walk down the stack and will stop at KiUserEventDispatch in
|
|
// ntdll.dll, which is supposed to dispatch the exception from kernel mode back to user mode
|
|
// so... let's create some shared memory
|
|
HANDLE hMapFile = NULL;
|
|
hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(CONTEXT), TEXT("Local\\KCrashShared"));
|
|
|
|
LPCTSTR pBuf = NULL;
|
|
pBuf = (LPCTSTR)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(CONTEXT));
|
|
CopyMemory((PVOID)pBuf, exceptionInfo->ContextRecord, sizeof(CONTEXT));
|
|
|
|
if (s_crashHandler) {
|
|
s_crashHandler(exceptionInfo->ExceptionRecord->ExceptionCode);
|
|
}
|
|
|
|
CloseHandle(hMapFile);
|
|
return EXCEPTION_EXECUTE_HANDLER; // allow windows to do the default action (terminate)
|
|
}
|
|
#else
|
|
|
|
static pid_t startDirectly(const char *argv[]);
|
|
|
|
void KCrash::startProcess(int argc, const char *argv[], bool waitAndExit)
|
|
{
|
|
Q_UNUSED(argc);
|
|
fprintf(stderr, "KCrash: Attempting to start %s\n", argv[0]);
|
|
|
|
pid_t pid = startDirectly(argv);
|
|
|
|
if (pid > 0 && waitAndExit) {
|
|
// Seems we made it....
|
|
alarm(0); // Stop the pending alarm that was set at the top of the defaultCrashHandler
|
|
|
|
bool running = true;
|
|
// Wait forever until the started process exits. This code path is executed
|
|
// when launching drkonqi. Note that DrKonqi will SIGSTOP this process in the meantime
|
|
// and only send SIGCONT when it is about to attach a debugger.
|
|
#ifdef Q_OS_LINUX
|
|
// Declare the process that will be debugging the crashed KDE app (#245529).
|
|
#ifndef PR_SET_PTRACER
|
|
#define PR_SET_PTRACER 0x59616d61
|
|
#endif
|
|
prctl(PR_SET_PTRACER, pid, 0, 0, 0);
|
|
#endif
|
|
if (running) {
|
|
// If the process was started directly, use waitpid(), as it's a child...
|
|
while (waitpid(pid, nullptr, 0) != pid) { }
|
|
}
|
|
if (!s_coreConfig.isProcess()) {
|
|
// Only exit if we don't forward to core dumps
|
|
_exit(253);
|
|
}
|
|
}
|
|
}
|
|
|
|
extern "C" char **environ;
|
|
static pid_t startDirectly(const char *argv[])
|
|
{
|
|
char **environ_end;
|
|
for (environ_end = environ; *environ_end; ++environ_end) { }
|
|
|
|
std::array<const char *, 1024> environ_data; // hope it's big enough
|
|
if ((unsigned)(environ_end - environ) + 2 >= environ_data.size()) {
|
|
fprintf(stderr, "environ_data in KCrash not big enough!\n");
|
|
return 0;
|
|
}
|
|
auto end = std::copy_if(environ, environ_end, environ_data.begin(), [](const char *s) {
|
|
static const char envvar[] = "KCRASH_AUTO_RESTARTED=";
|
|
return strncmp(envvar, s, sizeof(envvar) - 1) != 0;
|
|
});
|
|
*end++ = "KCRASH_AUTO_RESTARTED=1";
|
|
*end++ = nullptr;
|
|
pid_t pid = fork();
|
|
switch (pid) {
|
|
case -1:
|
|
fprintf(stderr, "KCrash failed to fork(), errno = %d\n", errno);
|
|
return 0;
|
|
case 0:
|
|
setgroups(0, nullptr); // Remove any extraneous groups
|
|
if (setgid(getgid()) < 0 || setuid(getuid()) < 0) {
|
|
_exit(253); // This cannot happen. Theoretically.
|
|
}
|
|
#ifndef Q_OS_OSX
|
|
closeAllFDs(); // We are in the child now. Close FDs unconditionally.
|
|
#endif
|
|
execve(argv[0], const_cast<char **>(argv), const_cast<char **>(environ_data.data()));
|
|
fprintf(stderr, "KCrash failed to exec(), errno = %d\n", errno);
|
|
_exit(253);
|
|
default:
|
|
return pid;
|
|
}
|
|
}
|
|
#endif // Q_OS_UNIX
|
|
|
|
void KCrash::setErrorMessage(const QString &message)
|
|
{
|
|
s_kcrashErrorMessage.reset(qstrdup(message.toUtf8().constData()));
|
|
}
|