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:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -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