Advance Wayland and KDE package bring-up
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
add_library(KF6Crash)
|
||||
add_library(KF6::Crash ALIAS KF6Crash)
|
||||
|
||||
set_target_properties(KF6Crash PROPERTIES
|
||||
VERSION ${KCRASH_VERSION}
|
||||
SOVERSION ${KCRASH_SOVERSION}
|
||||
EXPORT_NAME Crash
|
||||
)
|
||||
|
||||
target_sources(KF6Crash PRIVATE
|
||||
coreconfig.cpp
|
||||
coreconfig_p.h
|
||||
kcrash.cpp
|
||||
kcrash.h
|
||||
metadata.cpp
|
||||
metadata_p.h
|
||||
exception.cpp
|
||||
)
|
||||
|
||||
kde_source_files_enable_exceptions(exception.cpp)
|
||||
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/config-kcrash.h.cmake
|
||||
${CMAKE_CURRENT_BINARY_DIR}/config-kcrash.h
|
||||
)
|
||||
|
||||
ecm_qt_declare_logging_category(
|
||||
KF6Crash
|
||||
HEADER kcrash_debug.h
|
||||
IDENTIFIER LOG_KCRASH
|
||||
CATEGORY_NAME kf.crash
|
||||
OLD_CATEGORY_NAMES org.kde.kcrash
|
||||
DESCRIPTION "KCrash"
|
||||
EXPORT KCRASH
|
||||
)
|
||||
|
||||
ecm_generate_export_header(KF6Crash
|
||||
BASE_NAME KCrash
|
||||
GROUP_BASE_NAME KF
|
||||
VERSION ${KF_VERSION}
|
||||
USE_VERSION_HEADER
|
||||
DEPRECATED_BASE_VERSION 0
|
||||
)
|
||||
|
||||
target_include_directories(KF6Crash INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KCrash>")
|
||||
|
||||
target_link_libraries(KF6Crash PUBLIC Qt6::Core)
|
||||
target_link_libraries(KF6Crash PRIVATE Qt6::Gui KF6::CoreAddons)
|
||||
|
||||
if (HAIKU)
|
||||
target_link_libraries(KF6Crash PRIVATE network)
|
||||
endif ()
|
||||
|
||||
# FIXME: It is needed to work around undefined reference error on FreeBSD
|
||||
# caused by --no-undefined because the `environ' variable does not exist
|
||||
# in libc.so.7 -- it is in crt1.o, and so not available to shared
|
||||
# libraries. We just drop the normal no-undefined flag for library building
|
||||
# here, on the assumption that Linux CI will catch any *actual* undefineds.
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
|
||||
string(REPLACE "-Wl,--no-undefined" "" CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}")
|
||||
endif()
|
||||
|
||||
if(WITH_X11)
|
||||
target_link_libraries(KF6Crash PRIVATE X11::X11)
|
||||
endif()
|
||||
|
||||
ecm_generate_headers(KCrash_HEADERS
|
||||
HEADER_NAMES
|
||||
KCrash
|
||||
|
||||
REQUIRED_HEADERS KCrash_HEADERS
|
||||
)
|
||||
|
||||
install(TARGETS KF6Crash EXPORT KF6CrashTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/kcrash_export.h
|
||||
${KCrash_HEADERS}
|
||||
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KCrash COMPONENT Devel
|
||||
)
|
||||
|
||||
ecm_qt_install_logging_categories(
|
||||
EXPORT KCRASH
|
||||
FILE kcrash.categories
|
||||
DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
|
||||
)
|
||||
|
||||
if(BUILD_QCH)
|
||||
ecm_add_qch(
|
||||
KF6Crash_QCH
|
||||
NAME KCrash
|
||||
BASE_NAME KF6Crash
|
||||
VERSION ${KF_VERSION}
|
||||
ORG_DOMAIN org.kde
|
||||
SOURCES # using only public headers, to cover only public API
|
||||
${KCrash_HEADERS}
|
||||
MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
|
||||
LINK_QCHS
|
||||
Qt6Core_QCH
|
||||
INCLUDE_DIRS
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
BLANK_MACROS
|
||||
KCRASH_EXPORT
|
||||
KCRASH_DEPRECATED
|
||||
KCRASH_DEPRECATED_EXPORT
|
||||
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
|
||||
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
|
||||
COMPONENT Devel
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#cmakedefine01 HAVE_X11
|
||||
#cmakedefine01 KCRASH_CORE_PATTERN_RAISE
|
||||
|
||||
#define kde_socklen_t socklen_t
|
||||
#define KDE_INSTALL_FULL_LIBEXECDIR "${KDE_INSTALL_FULL_LIBEXECDIR}"
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016-2021 Harald Sitter <sitter@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "coreconfig_p.h"
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#include <config-kcrash.h>
|
||||
namespace KCrash
|
||||
{
|
||||
CoreConfig::CoreConfig(const QString &path)
|
||||
{
|
||||
#if !KCRASH_CORE_PATTERN_RAISE
|
||||
return; // Leave everything false unless enabled.
|
||||
#endif
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
return;
|
||||
}
|
||||
char first = 0;
|
||||
if (!file.getChar(&first)) {
|
||||
return;
|
||||
}
|
||||
m_supported = true;
|
||||
m_process = first == '|';
|
||||
|
||||
if (file.readLine().contains(QByteArrayLiteral("systemd-coredump"))) {
|
||||
m_coredumpd = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool CoreConfig::isProcess() const
|
||||
{
|
||||
return m_supported && m_process;
|
||||
}
|
||||
|
||||
bool CoreConfig::isCoredumpd() const
|
||||
{
|
||||
return m_coredumpd;
|
||||
}
|
||||
|
||||
} // namespace KCrash
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016-2021 Harald Sitter <sitter@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KCRASH_CORECONFIG_H
|
||||
#define KCRASH_CORECONFIG_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace KCrash
|
||||
{
|
||||
class CoreConfig
|
||||
{
|
||||
public:
|
||||
CoreConfig(const QString &path = QStringLiteral("/proc/sys/kernel/core_pattern"));
|
||||
|
||||
bool isProcess() const;
|
||||
// should this need expansion please refactor to enum. could also store cmdline and compare in kcrash.cpp
|
||||
bool isCoredumpd() const;
|
||||
|
||||
private:
|
||||
bool m_supported = false;
|
||||
bool m_process = false;
|
||||
bool m_coredumpd = false;
|
||||
};
|
||||
|
||||
} // namespace KCrash
|
||||
|
||||
#endif // KCRASH_CORECONFIG_H
|
||||
@@ -0,0 +1,57 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "exception_p.h"
|
||||
|
||||
#include <exception>
|
||||
|
||||
#include <QUnhandledException>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::optional<KCrash::ExceptionMetadata> qUnhandledExceptionMetadata(const QUnhandledException &exception)
|
||||
{
|
||||
auto exceptionPtr = exception.exception();
|
||||
if (!exceptionPtr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
std::rethrow_exception(exceptionPtr);
|
||||
} catch (const std::exception &e) {
|
||||
return KCrash::ExceptionMetadata{
|
||||
.ptr = exceptionPtr,
|
||||
.klass = typeid(e).name(),
|
||||
.what = e.what(),
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace KCrash
|
||||
{
|
||||
std::optional<ExceptionMetadata> exceptionMetadata()
|
||||
{
|
||||
auto exceptionPtr = std::current_exception();
|
||||
if (!exceptionPtr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
std::rethrow_exception(exceptionPtr);
|
||||
} catch (const QUnhandledException &e) {
|
||||
return qUnhandledExceptionMetadata(e);
|
||||
} catch (const std::bad_alloc &e) {
|
||||
return {};
|
||||
} catch (const std::exception &e) {
|
||||
return KCrash::ExceptionMetadata{
|
||||
.ptr = exceptionPtr,
|
||||
.klass = typeid(e).name(),
|
||||
.what = e.what(),
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
} // namespace KCrash
|
||||
@@ -0,0 +1,19 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kcrash_export.h"
|
||||
|
||||
#include <exception>
|
||||
#include <optional>
|
||||
|
||||
namespace KCrash
|
||||
{
|
||||
struct KCRASH_NO_EXPORT ExceptionMetadata {
|
||||
std::exception_ptr ptr;
|
||||
const char *klass;
|
||||
const char *what;
|
||||
};
|
||||
|
||||
KCRASH_NO_EXPORT std::optional<ExceptionMetadata> exceptionMetadata();
|
||||
} // namespace KCrash
|
||||
@@ -0,0 +1,761 @@
|
||||
/*
|
||||
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 <sys/resource.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()));
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
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-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KCRASH_H
|
||||
#define KCRASH_H
|
||||
|
||||
#include <kcrash_export.h>
|
||||
|
||||
#include <qglobal.h>
|
||||
|
||||
class QString;
|
||||
|
||||
/**
|
||||
* This namespace contains functions to handle crashes.
|
||||
* It allows you to set a crash handler function that will be called
|
||||
* when your application crashes and also provides a default crash
|
||||
* handler that implements the following functionality:
|
||||
* @li Launches the KDE crash display application (DrKonqi) to let
|
||||
* the user report the bug and/or debug it.
|
||||
* @li Calls an emergency save function that you can set with
|
||||
* setEmergencySaveFunction() to attempt to save the application's data.
|
||||
* @li Autorestarts your application.
|
||||
*
|
||||
* @note All the above features are optional and you need to enable them
|
||||
* explicitly. By default, the defaultCrashHandler() will not do anything.
|
||||
* However, if you are using KApplication, it will by default enable launching
|
||||
* DrKonqi on crashes, unless the --nocrashhandler argument was passed on
|
||||
* the command line or the environment variable KDE_DEBUG is set to any value.
|
||||
*/
|
||||
namespace KCrash
|
||||
{
|
||||
/**
|
||||
* Initialize KCrash.
|
||||
*
|
||||
* This does nothing if $KDE_DEBUG is set.
|
||||
*
|
||||
* Call this in your main() after setting up KAboutData to ensure that the crash handler is launched.
|
||||
* @since 5.15
|
||||
*/
|
||||
KCRASH_EXPORT void initialize();
|
||||
|
||||
/**
|
||||
* The default crash handler.
|
||||
* Do not call this function directly. Instead, use
|
||||
* setCrashHandler() to set it as your application's crash handler.
|
||||
* @param signal the signal number
|
||||
* @note If you implement your own crash handler, you will have to
|
||||
* call this function from your implementation if you want to use the
|
||||
* features of this namespace.
|
||||
*/
|
||||
KCRASH_EXPORT void defaultCrashHandler(int signal);
|
||||
|
||||
/**
|
||||
* Typedef for a pointer to a crash handler function.
|
||||
* The function's argument is the number of the signal.
|
||||
*/
|
||||
typedef void (*HandlerType)(int);
|
||||
|
||||
/**
|
||||
* Install a function to be called when a crash occurs.
|
||||
* A crash occurs when one of the following signals is
|
||||
* caught: SIGSEGV, SIGBUS, SIGFPE, SIGILL, SIGABRT.
|
||||
* @param handler this can be one of:
|
||||
* @li null, in which case signal catching is disabled
|
||||
* (by setting the signal handler for the crash signals to SIG_DFL)
|
||||
* @li a user defined function in the form:
|
||||
* static (if in a class) void myCrashHandler(int);
|
||||
* @li if handler is omitted, the default crash handler is installed
|
||||
* @note If you use setDrKonqiEnabled(true), setEmergencySaveFunction(myfunc)
|
||||
* or setFlags(AutoRestart), you do not need to call this function
|
||||
* explicitly. The default crash handler is automatically installed by
|
||||
* those functions if needed. However, if you set a custom crash handler,
|
||||
* those functions will not change it.
|
||||
*/
|
||||
KCRASH_EXPORT void setCrashHandler(HandlerType handler = defaultCrashHandler);
|
||||
|
||||
/**
|
||||
* Returns the installed crash handler.
|
||||
* @return the crash handler
|
||||
*/
|
||||
KCRASH_EXPORT HandlerType crashHandler();
|
||||
|
||||
/**
|
||||
* Installs a function which should try to save the application's data.
|
||||
* @note It is the crash handler's responsibility to call this function.
|
||||
* Therefore, if no crash handler is set, the default crash handler
|
||||
* is installed to ensure the save function will be called.
|
||||
* @param saveFunction the handler to install
|
||||
*/
|
||||
KCRASH_EXPORT void setEmergencySaveFunction(HandlerType saveFunction = nullptr);
|
||||
|
||||
/**
|
||||
* Returns the currently set emergency save function.
|
||||
* @return the emergency save function
|
||||
*/
|
||||
KCRASH_EXPORT HandlerType emergencySaveFunction();
|
||||
|
||||
/**
|
||||
* Options to determine how the default crash handler should behave.
|
||||
* @see CrashFlags
|
||||
*/
|
||||
enum CrashFlag {
|
||||
KeepFDs = 1, ///< don't close all file descriptors immediately
|
||||
SaferDialog = 2, ///< start DrKonqi without arbitrary disk access
|
||||
AlwaysDirectly =
|
||||
4, ///< never try to to start DrKonqi via kdeinit. Use fork() and exec() instead. @deprecated This is now the default, and does not need to be set.
|
||||
AutoRestart = 8, ///< autorestart this application. Only sensible for KUniqueApplications. @since 4.1.
|
||||
};
|
||||
/**
|
||||
* Stores a combination of #CrashFlag values.
|
||||
*/
|
||||
Q_DECLARE_FLAGS(CrashFlags, CrashFlag)
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(CrashFlags)
|
||||
|
||||
/**
|
||||
* Set options to determine how the default crash handler should behave.
|
||||
* @param flags ORed together CrashFlags
|
||||
*/
|
||||
KCRASH_EXPORT void setFlags(KCrash::CrashFlags flags);
|
||||
|
||||
/**
|
||||
* Enables or disables launching DrKonqi from the crash handler.
|
||||
* By default, launching DrKonqi is enabled when QCoreApplication is created.
|
||||
* To disable it:
|
||||
* @code
|
||||
void disableDrKonqi()
|
||||
{
|
||||
KCrash::setDrKonqiEnabled(false);
|
||||
}
|
||||
Q_CONSTRUCTOR_FUNCTION(disableDrKonqi)
|
||||
* \endcode
|
||||
* @note It is the crash handler's responsibility to launch DrKonqi.
|
||||
* Therefore, if no crash handler is set, this method also installs
|
||||
* the default crash handler to ensure that DrKonqi will be launched.
|
||||
* @since 4.5
|
||||
*/
|
||||
KCRASH_EXPORT void setDrKonqiEnabled(bool enabled);
|
||||
|
||||
/**
|
||||
* Returns true if DrKonqi is set to be launched from the crash handler or false otherwise.
|
||||
* @since 4.5
|
||||
*/
|
||||
KCRASH_EXPORT bool isDrKonqiEnabled();
|
||||
|
||||
/**
|
||||
* Allows providing information to be included in the bug report.
|
||||
*
|
||||
* @since 5.69
|
||||
*/
|
||||
KCRASH_EXPORT void setErrorMessage(const QString &message);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org>
|
||||
*/
|
||||
|
||||
#include "metadata_p.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <cerrno>
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <span>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace KCrash
|
||||
{
|
||||
#ifdef Q_OS_LINUX
|
||||
MetadataINIWriter::MetadataINIWriter(const QByteArray &path)
|
||||
{
|
||||
if (path.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
fd = ::open(path.constData(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, S_IRUSR | S_IWUSR);
|
||||
if (fd == -1) {
|
||||
fprintf(stderr, "Failed to open metadata file: %s\n", strerror(errno));
|
||||
} else if (fd >= 0) {
|
||||
writable = true;
|
||||
const char *header = "[KCrash]\n";
|
||||
write(fd, header, strlen(header));
|
||||
} else {
|
||||
fprintf(stderr, "MetadataINIWriter: Unexpected fd %d\n", fd);
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
void MetadataINIWriter::close()
|
||||
{
|
||||
if (fd >= 0 && ::close(fd) == -1) {
|
||||
fprintf(stderr, "Failed to close metadata file: %s\n", strerror(errno));
|
||||
}
|
||||
writable = false;
|
||||
}
|
||||
|
||||
void MetadataINIWriter::add(const char *key, const char *value, BoolValue boolValue)
|
||||
{
|
||||
Q_ASSERT(key);
|
||||
Q_ASSERT(value);
|
||||
Q_ASSERT(key[0] == '-' && key[1] == '-'); // well-formed '--' prefix. This is important. MetadataWriter presume this
|
||||
Q_UNUSED(boolValue); // value is a bool string but we don't care, we always write the value anyway
|
||||
|
||||
if (fd < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto valueSpan = std::span{value, strlen(value)};
|
||||
|
||||
write(fd, key + 2, strlen(key + 2));
|
||||
write(fd, "=", 1);
|
||||
if (strstr(value, "\n")) { // if it contains \n then write literally \n (2 characters)
|
||||
// Could appear in the exception what() string. KConfig knows what to do with this.
|
||||
for (const auto &character : valueSpan) {
|
||||
if (character == '\n') {
|
||||
write(fd, "\\n", 2);
|
||||
} else {
|
||||
write(fd, &character, 1);
|
||||
}
|
||||
}
|
||||
} else { // fast write entire string in one go since it contains no newlines
|
||||
write(fd, valueSpan.data(), valueSpan.size());
|
||||
}
|
||||
write(fd, "\n", 1);
|
||||
}
|
||||
|
||||
bool MetadataINIWriter::isWritable() const
|
||||
{
|
||||
return writable;
|
||||
}
|
||||
#endif
|
||||
|
||||
Metadata::Metadata(const char *cmd)
|
||||
{
|
||||
// NB: cmd may be null! Just because we create metadata doesn't mean we'll execute drkonqi (we may only need the
|
||||
// backing writers)
|
||||
Q_ASSERT(argc == 0);
|
||||
argv.at(argc++) = cmd;
|
||||
}
|
||||
|
||||
void Metadata::setAdditionalWriter(MetadataWriter *writer)
|
||||
{
|
||||
// Once set the writer oughtn't be reset as we have no use case for this and should we get one in the future
|
||||
// it'll need at least review of the existing code to handle writer switching correctly.
|
||||
Q_ASSERT(m_writer == nullptr);
|
||||
Q_ASSERT(writer != nullptr);
|
||||
m_writer = writer;
|
||||
}
|
||||
|
||||
void Metadata::add(const char *key, const char *value)
|
||||
{
|
||||
add(key, value, BoolValue::No);
|
||||
}
|
||||
|
||||
void Metadata::addBool(const char *key)
|
||||
{
|
||||
add(key, "true", BoolValue::Yes);
|
||||
}
|
||||
|
||||
void Metadata::close()
|
||||
{
|
||||
// NULL terminated list
|
||||
argv.at(argc) = nullptr;
|
||||
|
||||
if (m_writer) {
|
||||
m_writer->close();
|
||||
}
|
||||
}
|
||||
|
||||
void Metadata::add(const char *key, const char *value, BoolValue boolValue)
|
||||
{
|
||||
Q_ASSERT(key);
|
||||
Q_ASSERT(value);
|
||||
Q_ASSERT(key[0] == '-' && key[1] == '-'); // well-formed '--' prefix. This is important. MetadataWriter presume this
|
||||
Q_ASSERT(argc < argv.max_size()); // argv has a static max size. guard against exhaustion
|
||||
|
||||
argv.at(argc++) = key;
|
||||
if (!boolValue) {
|
||||
argv.at(argc++) = value;
|
||||
}
|
||||
|
||||
if (m_writer) {
|
||||
m_writer->add(key, value, boolValue);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace KCrash
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org>
|
||||
*/
|
||||
|
||||
#ifndef KCRASH_METADATA_H
|
||||
#define KCRASH_METADATA_H
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#include <array>
|
||||
|
||||
class QByteArray;
|
||||
|
||||
namespace KCrash
|
||||
{
|
||||
// A metadata writer interface.
|
||||
class MetadataWriter
|
||||
{
|
||||
public:
|
||||
enum BoolValue { No = false, Yes = true };
|
||||
|
||||
virtual void add(const char *key, const char *value, BoolValue boolValue) = 0;
|
||||
virtual void close() = 0;
|
||||
|
||||
protected:
|
||||
MetadataWriter() = default;
|
||||
virtual ~MetadataWriter() = default;
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY_MOVE(MetadataWriter)
|
||||
};
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
// This writes the metadata file. Only really useful on Linux for now as this needs
|
||||
// cleanup by a helper daemon later. Also, this is only ever useful when coredump is in use.
|
||||
class MetadataINIWriter : public MetadataWriter
|
||||
{
|
||||
public:
|
||||
explicit MetadataINIWriter(const QByteArray &path);
|
||||
~MetadataINIWriter() override = default;
|
||||
|
||||
void add(const char *key, const char *value, BoolValue boolValue) override;
|
||||
void close() override;
|
||||
|
||||
// open or not, all functions are generally save to call without checking this
|
||||
[[nodiscard]] bool isWritable() const;
|
||||
|
||||
private:
|
||||
bool writable = false;
|
||||
int fd = -1;
|
||||
|
||||
Q_DISABLE_COPY_MOVE(MetadataINIWriter)
|
||||
};
|
||||
#endif
|
||||
|
||||
// Compile the crash metadata. These are the primary ARGV metadata, but additional
|
||||
// metadata writers may be added behind it to (e.g.) write the data to a file as well.
|
||||
// man 7 signal-safety
|
||||
class Metadata : public MetadataWriter
|
||||
{
|
||||
public:
|
||||
explicit Metadata(const char *cmd);
|
||||
~Metadata() override = default;
|
||||
|
||||
// Add an additional writer that should receive write calls as well. Do not call this after having called add.
|
||||
void setAdditionalWriter(MetadataWriter *writer);
|
||||
|
||||
void add(const char *key, const char *value);
|
||||
void addBool(const char *key);
|
||||
|
||||
// Also closes the backing writer.
|
||||
void close() override;
|
||||
|
||||
// WARNING: DO NOT FORGET TO BUMP AND SHRINK THE CAPACITY
|
||||
// - boolean values increase the argv by 1 slot
|
||||
// - non-boolean values increase the argv by 2 slots
|
||||
// - this should always be the maximum of potentially used slots
|
||||
// - if you re-count slots, don't forget that the 'cmd' and terminal NULL take a slot each
|
||||
std::array<const char *, 38> argv{};
|
||||
std::size_t argc = 0;
|
||||
|
||||
private:
|
||||
void add(const char *key, const char *value, BoolValue boolValue) override;
|
||||
|
||||
// Obviously if we should ever need more writers, refactor to std::initializer_list or something similar.
|
||||
MetadataWriter *m_writer = nullptr;
|
||||
|
||||
Q_DISABLE_COPY_MOVE(Metadata)
|
||||
};
|
||||
|
||||
} // namespace KCrash
|
||||
|
||||
#endif // KCRASH_METADATA_H
|
||||
Reference in New Issue
Block a user