1/*
2 This file is part of the KDE Libraries
3 SPDX-FileCopyrightText: 2000 Timo Hummel <timo.hummel@sap.com>
4 SPDX-FileCopyrightText: 2000 Tom Braun <braunt@fh-konstanz.de>
5 SPDX-FileCopyrightText: 2010 George Kiagiadakis <kiagiadakis.george@gmail.com>
6 SPDX-FileCopyrightText: 2009 KDE e.V. <kde-ev-board@kde.org>
7 SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org>
8 SPDX-FileContributor: 2009 Adriaan de Groot <groot@kde.org>
9
10 SPDX-License-Identifier: LGPL-2.0-or-later
11*/
12
13#include "kcrash.h"
14
15#include "kcrash_debug.h"
16
17#include <config-kcrash.h>
18
19#include <csignal>
20#include <cstdio>
21#include <cstdlib>
22#include <cstring>
23
24#include <qplatformdefs.h>
25#ifndef Q_OS_WIN
26#include <cerrno>
27#include <sys/resource.h>
28#include <sys/un.h>
29#else
30#include <qt_windows.h>
31#endif
32#ifdef Q_OS_LINUX
33#include <sys/prctl.h>
34#endif
35
36#include <KAboutData>
37
38#include <algorithm>
39#include <array>
40#include <chrono>
41#include <memory>
42
43#include <QDebug>
44#include <QDir>
45#include <QFile>
46#include <QGuiApplication>
47#include <QLibraryInfo>
48#include <QStandardPaths>
49#include <QThread>
50
51#if HAVE_X11
52#include <X11/Xlib.h>
53#endif
54
55#include "coreconfig_p.h"
56#include "exception_p.h"
57#include "metadata_p.h"
58
59// WARNING: do not use qGlobalStatics in here, they get destroyed too early on
60// shutdown and may inhibit crash handling in late-exit scenarios (e.g. when
61// a function local static gets destroyed by __cxa_finalize)
62#undef Q_GLOBAL_STATIC
63
64using namespace std::chrono_literals;
65using namespace Qt::StringLiterals;
66
67struct Args {
68 Args() = default;
69 ~Args()
70 {
71 clear();
72 }
73 Q_DISABLE_COPY_MOVE(Args)
74
75 void clear()
76 {
77 if (!argc) {
78 return;
79 }
80
81 for (int i = 0; i < argc; ++i) {
82 delete[] argv[i];
83 }
84 delete[] argv;
85
86 argv = nullptr;
87 argc = 0;
88 }
89
90 void resize(int size)
91 {
92 clear();
93 argc = size;
94 argv = new char *[argc + 1];
95 for (int i = 0; i < argc + 1; ++i) {
96 argv[i] = nullptr;
97 }
98 }
99
100 explicit operator bool() const
101 {
102 return argc > 0;
103 }
104
105 int argc = 0;
106 // null-terminated array of null-terminated strings
107 char **argv = nullptr;
108};
109
110static KCrash::HandlerType s_emergencySaveFunction = nullptr;
111static KCrash::HandlerType s_crashHandler = nullptr;
112static std::unique_ptr<char[]> s_appFilePath; // this is the actual QCoreApplication::applicationFilePath
113static std::unique_ptr<char[]> s_appName; // the binary name (may be altered by the application)
114static std::unique_ptr<char[]> s_appPath; // the binary dir path (may be altered by the application)
115static Args s_autoRestartCommandLine;
116static std::unique_ptr<char[]> s_drkonqiPath;
117static KCrash::CrashFlags s_flags = KCrash::CrashFlags();
118static int s_launchDrKonqi = -1; // -1=initial value 0=disabled 1=enabled
119static int s_originalSignal = -1;
120static QByteArray s_metadataPath;
121static const bool s_crashInHandler = qEnvironmentVariableIntegerValue(varName: "KCRASH_CRASH_IN_HANDLER") == 1;
122
123static std::unique_ptr<char[]> s_kcrashErrorMessage;
124
125namespace
126{
127const KCrash::CoreConfig s_coreConfig;
128
129std::unique_ptr<char[]> s_qtVersion;
130
131using DetailsHash = QHash<QByteArray, QByteArray>;
132std::unique_ptr<const DetailsHash> s_tags; // Sentry tags
133std::unique_ptr<const DetailsHash> s_extraData; // Sentry extra data
134std::unique_ptr<const DetailsHash> s_gpuContext; // Sentry gpu context
135
136QString bootId()
137{
138#ifdef Q_OS_LINUX
139 QFile file(QStringLiteral("/proc/sys/kernel/random/boot_id"));
140 if (!file.open(flags: QFile::ReadOnly)) {
141 qCWarning(LOG_KCRASH) << "Failed to read /proc/sys/kernel/random/boot_id" << file.errorString();
142 return {};
143 }
144 return QString::fromUtf8(ba: file.readAll().simplified().replace(before: '-', after: QByteArrayView()));
145#else
146 return {};
147#endif
148}
149
150} // namespace
151
152static QStringList libexecPaths()
153{
154 // Static since we only need to evaluate once.
155 static QStringList list = QFile::decodeName(localFileName: qgetenv(varName: "LIBEXEC_PATH")).split(sep: QLatin1Char(':'), behavior: Qt::SkipEmptyParts) // env var is used first
156 + QStringList{
157 QCoreApplication::applicationDirPath(), // then look where our application binary is located
158 QLibraryInfo::path(p: QLibraryInfo::LibraryExecutablesPath), // look where libexec path is (can be set in qt.conf)
159 QFile::decodeName(KDE_INSTALL_FULL_LIBEXECDIR) // look at our installation location
160 };
161 return list;
162}
163
164namespace KCrash
165{
166void setApplicationFilePath(const QString &filePath);
167void startProcess(int argc, const char *argv[], bool waitAndExit);
168
169#if defined(Q_OS_WIN)
170LONG WINAPI win32UnhandledExceptionFilter(_EXCEPTION_POINTERS *exceptionInfo);
171#endif
172}
173
174static bool shouldWriteMetadataToDisk()
175{
176#ifdef Q_OS_LINUX
177 if (QStandardPaths::isTestModeEnabled()) {
178 return true;
179 }
180 // NB: The daemon being currently running must not be a condition here. If something crashes during logout
181 // the daemon may already be gone but we'll still want to deal with the crash on next login!
182 // Similar reasoning applies to not checking the presence of the launcher socket.
183 const bool drkonqiCoredumpHelper = !QStandardPaths::findExecutable(QStringLiteral("drkonqi-coredump-processor"), paths: libexecPaths()).isEmpty();
184 return s_coreConfig.isCoredumpd() && drkonqiCoredumpHelper && !qEnvironmentVariableIsSet(varName: "KCRASH_NO_METADATA");
185#else
186 return false;
187#endif
188}
189
190void KCrash::initialize()
191{
192 if (s_launchDrKonqi == 0) { // disabled by the program itself
193 return;
194 }
195
196 bool enableDrKonqi = !qEnvironmentVariableIsSet(varName: "KDE_DEBUG");
197 if (qEnvironmentVariableIsSet(varName: "KCRASH_AUTO_RESTARTED") || qEnvironmentVariableIntValue(varName: "RUNNING_UNDER_RR") == 1
198 || qEnvironmentVariableIntValue(varName: "KCRASH_DUMP_ONLY") == 1) {
199 enableDrKonqi = false;
200 }
201
202 const QStringList args = QCoreApplication::arguments();
203 // Default to core dumping whenever a process is set. When not or when explicitly opting into just in time debugging
204 // we enable drkonqi. This causes the signal handler to directly fork drkonqi opening us to race conditions.
205 // NOTE: depending on the specific signal other threads are running while the signal handler runs and may trip over
206 // the signal handler's closed FDs. That is primarily why we do not like JIT debugging.
207 if (enableDrKonqi && (!s_coreConfig.isProcess() || qEnvironmentVariableIntValue(varName: "KCRASH_JIT_DRKONQI") == 1)) {
208 KCrash::setDrKonqiEnabled(true);
209 } else {
210 // Don't qDebug here, it loads qtlogging.ini very early which prevents unittests from doing QStandardPaths::setTestModeEnabled(true) in initTestCase()
211 }
212
213 s_qtVersion.reset(p: qstrdup(qVersion()));
214
215 if (QCoreApplication::instance()) {
216 const QString path = QCoreApplication::applicationFilePath();
217 s_appFilePath.reset(p: qstrdup(qPrintable(path))); // This intentionally cannot be changed by the application!
218 KCrash::setApplicationFilePath(path);
219 } else {
220 qWarning() << "This process needs a QCoreApplication instance in order to use KCrash";
221 }
222
223 if (shouldWriteMetadataToDisk()) {
224 // We do not actively clean up metadata via KCrash but some other service. This potentially means we litter
225 // a lot -> put the metadata in a subdir.
226 // This data is consumed by DrKonqi in combination with coredumpd metadata.
227 const QString metadataDir = QStandardPaths::writableLocation(type: QStandardPaths::GenericCacheLocation) + QStringLiteral("/kcrash-metadata");
228 if (QDir().mkpath(dirPath: metadataDir)) {
229 if (QStandardPaths::isTestModeEnabled()) {
230 s_metadataPath = QFile::encodeName(fileName: metadataDir + QStringLiteral("/test.ini"));
231 } else {
232 const auto bootId = ::bootId();
233 const auto exe = QString::fromUtf8(utf8: s_appName.get());
234 const auto pid = QString::number(QCoreApplication::applicationPid());
235 s_metadataPath = QFile::encodeName(fileName: metadataDir + //
236 QStringLiteral("/%1.%2.%3.ini").arg(args: exe, args: bootId, args: pid));
237 }
238 }
239 if (!s_crashHandler) {
240 // Always enable the default handler. We cannot create the metadata ahead of time since we do not know
241 // when the application metadata is "complete".
242 // TODO: kf6 maybe change the way init works and have the users run it when their done with kaboutdata etc.?
243 // the problem with delayed writing is that our crash handler (or any crash handler really) introduces a delay
244 // in dumping and this in turn increases the risk of another stepping into a puddle (SEGV only runs on the
245 // faulting thread; all other threads continue running!). therefore it'd be greatly preferred if we
246 // were able to write the metadata during initial app setup instead of when a crash occurs
247 setCrashHandler(defaultCrashHandler);
248 }
249 } // empty s_metadataPath disables writing
250}
251
252void KCrash::setEmergencySaveFunction(HandlerType saveFunction)
253{
254 s_emergencySaveFunction = saveFunction;
255
256 /*
257 * We need at least the default crash handler for
258 * emergencySaveFunction to be called
259 */
260 if (s_emergencySaveFunction && !s_crashHandler) {
261 setCrashHandler(defaultCrashHandler);
262 }
263}
264
265KCrash::HandlerType KCrash::emergencySaveFunction()
266{
267 return s_emergencySaveFunction;
268}
269
270// Set the default crash handler in 10 seconds
271// This is used after an autorestart, the second instance of the application
272// is started with KCRASH_AUTO_RESTARTED=1, and we
273// set the defaultCrashHandler (to handle autorestart) after 10s.
274// The delay is to see if we stay up for more than 10s time, to avoid infinite
275// respawning if the app crashes on startup.
276class KCrashDelaySetHandler : public QObject
277{
278public:
279 KCrashDelaySetHandler()
280 {
281 startTimer(time: 10s);
282 }
283
284protected:
285 void timerEvent(QTimerEvent *event) override
286 {
287 if (!s_crashHandler) { // not set meanwhile
288 KCrash::setCrashHandler(KCrash::defaultCrashHandler);
289 }
290 killTimer(id: event->timerId());
291 this->deleteLater();
292 }
293};
294
295void KCrash::setFlags(KCrash::CrashFlags flags)
296{
297 s_flags = flags;
298 if (s_flags & AutoRestart) {
299 // We need at least the default crash handler for autorestart to work.
300 if (!s_crashHandler) {
301 if (qEnvironmentVariableIsSet(varName: "KCRASH_AUTO_RESTARTED")) {
302 new KCrashDelaySetHandler;
303 } else {
304 setCrashHandler(defaultCrashHandler);
305 }
306 }
307 }
308}
309
310void KCrash::setApplicationFilePath(const QString &filePath)
311{
312 const auto pos = filePath.lastIndexOf(c: QLatin1Char('/'));
313 const QString appName = filePath.mid(position: pos + 1);
314 const QString appPath = filePath.left(n: pos); // could be empty, in theory
315
316 s_appName.reset(p: qstrdup(QFile::encodeName(fileName: appName).constData()));
317 s_appPath.reset(p: qstrdup(QFile::encodeName(fileName: appPath).constData()));
318
319 // Prepare the auto-restart command
320 QStringList args = QCoreApplication::arguments();
321 if (args.isEmpty()) { // edge case: tst_QX11Info::startupId does QApplication app(argc, nullptr)...
322 args.append(t: filePath);
323 } else {
324 args[0] = filePath; // replace argv[0] with full path above
325 }
326
327 s_autoRestartCommandLine.resize(size: args.count());
328 for (int i = 0; i < args.count(); ++i) {
329 s_autoRestartCommandLine.argv[i] = qstrdup(QFile::encodeName(fileName: args.at(i)).constData());
330 }
331}
332
333void KCrash::setDrKonqiEnabled(bool enabled)
334{
335 const int launchDrKonqi = enabled ? 1 : 0;
336 if (s_launchDrKonqi == launchDrKonqi) {
337 return;
338 }
339 s_launchDrKonqi = launchDrKonqi;
340 if (s_launchDrKonqi && !s_drkonqiPath) {
341 const QString exec = QStandardPaths::findExecutable(QStringLiteral("drkonqi"), paths: libexecPaths());
342 if (exec.isEmpty()) {
343 qCDebug(LOG_KCRASH) << "Could not find drkonqi in search paths:" << libexecPaths();
344 s_launchDrKonqi = 0;
345 } else {
346 s_drkonqiPath.reset(p: qstrdup(qPrintable(exec)));
347 }
348 }
349
350 // we need at least the default crash handler to launch drkonqi
351 if (s_launchDrKonqi && !s_crashHandler) {
352 setCrashHandler(defaultCrashHandler);
353 }
354}
355
356bool KCrash::isDrKonqiEnabled()
357{
358 return s_launchDrKonqi == 1;
359}
360
361void KCrash::setCrashHandler(HandlerType handler)
362{
363#if defined(Q_OS_WIN)
364 static LPTOP_LEVEL_EXCEPTION_FILTER s_previousExceptionFilter = NULL;
365
366 if (handler && !s_previousExceptionFilter) {
367 s_previousExceptionFilter = SetUnhandledExceptionFilter(KCrash::win32UnhandledExceptionFilter);
368 } else if (!handler && s_previousExceptionFilter) {
369 SetUnhandledExceptionFilter(s_previousExceptionFilter);
370 s_previousExceptionFilter = NULL;
371 }
372#else
373 if (!handler) {
374 handler = SIG_DFL;
375 }
376
377 sigset_t mask;
378 sigemptyset(set: &mask);
379
380 const auto signals = {
381#ifdef SIGSEGV
382 SIGSEGV,
383#endif
384#ifdef SIGBUS
385 SIGBUS,
386#endif
387#ifdef SIGFPE
388 SIGFPE,
389#endif
390#ifdef SIGILL
391 SIGILL,
392#endif
393#ifdef SIGABRT
394 SIGABRT,
395#endif
396 };
397
398 for (const auto &signal : signals) {
399 struct sigaction action {
400 };
401 action.sa_handler = handler;
402 action.sa_flags = SA_RESTART;
403 sigemptyset(set: &action.sa_mask);
404 sigaction(sig: signal, act: &action, oact: nullptr);
405 sigaddset(set: &mask, signo: signal);
406 }
407
408 sigprocmask(SIG_UNBLOCK, set: &mask, oset: nullptr);
409#endif
410
411 s_crashHandler = handler;
412}
413
414KCrash::HandlerType KCrash::crashHandler()
415{
416 return s_crashHandler;
417}
418
419#if !defined(Q_OS_WIN) && !defined(Q_OS_OSX)
420static void closeAllFDs()
421{
422 // Close all remaining file descriptors except for stdin/stdout/stderr
423 struct rlimit rlp = {};
424 getrlimit(RLIMIT_NOFILE, rlimits: &rlp);
425 for (rlim_t i = 3; i < rlp.rlim_cur; i++) {
426 close(fd: i);
427 }
428}
429#endif
430
431void crashOnSigTerm(int sig)
432{
433 Q_UNUSED(sig)
434 raise(sig: s_originalSignal);
435}
436
437void KCrash::defaultCrashHandler(int sig)
438{
439 // WABA: Do NOT use qDebug() in this function because it is much too risky!
440 // Handle possible recursions
441 static int crashRecursionCounter = 0;
442 crashRecursionCounter++; // Nothing before this, please !
443 s_originalSignal = sig;
444
445#if !defined(Q_OS_WIN)
446 signal(SIGALRM, SIG_DFL);
447 alarm(seconds: 3); // Kill me... (in case we deadlock in malloc)
448#endif
449
450 if (crashRecursionCounter < 2) {
451 if (s_emergencySaveFunction) {
452 s_emergencySaveFunction(sig);
453 }
454 if ((s_flags & AutoRestart) && s_autoRestartCommandLine) {
455 QThread::sleep(1);
456 startProcess(argc: s_autoRestartCommandLine.argc, argv: const_cast<const char **>(s_autoRestartCommandLine.argv), waitAndExit: false);
457 }
458 crashRecursionCounter++;
459 }
460
461 if (crashRecursionCounter < 3) {
462 // If someone is telling me to stop while I'm already crashing, then I should resume crashing
463 signal(SIGTERM, handler: &crashOnSigTerm);
464
465 // NB: all metadata writing ought to happen before closing FDs to reduce synchronization problems with dbus.
466
467 // WARNING: do not forget to increase Metadata::argv's size when adding more potential arguments!
468 Metadata data(s_drkonqiPath.get());
469#ifdef Q_OS_LINUX
470 // The ini is required to be scoped here, as opposed to the conditional scope, so its lifetime is the same as
471 // the regular data instance!
472 MetadataINIWriter ini(s_metadataPath);
473 // s_appFilePath can point to nullptr
474 // not exactly sure how, maybe some race condition due to KCrashDelaySetHandler ?
475 if (!s_appFilePath) {
476 fprintf(stderr, format: "KCrash: appFilePath points to nullptr!\n");
477 } else if (ini.isWritable()) {
478 if (s_tags) {
479 // [KCrashTags]
480 ini.startTagsGroup();
481 // Add our dynamic details. Note that since we only add them to the ini they do not count against our static argv limit in the Metadata class.
482 for (const auto &[key, value] : s_tags->asKeyValueRange()) {
483 ini.add(key: key.constData(), value: value.constData(), boolValue: MetadataWriter::BoolValue::No);
484 }
485 }
486
487 if (s_extraData) {
488 // [KCrashExtra]
489 ini.startExtraGroup();
490 // Add our dynamic details. Note that since we only add them to the ini they do not count against our static argv limit in the Metadata class.
491 for (const auto &[key, value] : s_extraData->asKeyValueRange()) {
492 ini.add(key: key.constData(), value: value.constData(), boolValue: MetadataWriter::BoolValue::No);
493 }
494 }
495 if (s_kcrashErrorMessage) {
496 // And also our legacy error message
497 ini.add(key: "--_kcrash_ErrorMessage", value: s_kcrashErrorMessage.get(), boolValue: MetadataWriter::BoolValue::No);
498 }
499
500 if (s_gpuContext) {
501 // [KCrashGPU]
502 ini.startGPUGroup();
503 // Add our dynamic details. Note that since we only add them to the ini they do not count against our static argv limit in the Metadata class.
504 for (const auto &[key, value] : s_gpuContext->asKeyValueRange()) {
505 ini.add(key: key.constData(), value: value.constData(), boolValue: MetadataWriter::BoolValue::No);
506 }
507 }
508
509 // [KCrash]
510 ini.startKCrashGroup();
511 // Add the canonical exe path so the coredump daemon has more data points to map metadata to journald entry.
512 ini.add(key: "--exe", value: s_appFilePath.get(), boolValue: MetadataWriter::BoolValue::No);
513
514 data.setAdditionalWriter(&ini);
515 }
516#endif
517
518 if (auto optionalExceptionMetadata = KCrash::exceptionMetadata(); optionalExceptionMetadata.has_value()) {
519 if (optionalExceptionMetadata->klass) {
520 data.add(key: "--exceptionname", value: optionalExceptionMetadata->klass);
521 }
522 if (optionalExceptionMetadata->what) {
523 data.add(key: "--exceptionwhat", value: optionalExceptionMetadata->what);
524 }
525 }
526
527 if (s_qtVersion) {
528 data.add(key: "--qtversion", value: s_qtVersion.get());
529 }
530
531 data.add(key: "--kdeframeworksversion", KCRASH_VERSION_STRING);
532
533 const QByteArray platformName = QGuiApplication::platformName().toUtf8();
534 if (!platformName.isEmpty()) {
535 if (strcmp(s1: platformName.constData(), s2: "wayland-org.kde.kwin.qpa") == 0) { // redirect kwin's internal QPA to wayland proper
536 data.add(key: "--platform", value: "wayland");
537 } else {
538 data.add(key: "--platform", value: platformName.constData());
539 }
540 }
541
542#if HAVE_X11
543 if (platformName == QByteArrayLiteral("xcb")) {
544 // start up on the correct display
545 char *display = nullptr;
546 if (qGuiApp) {
547 if (auto disp = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->display()) {
548 display = XDisplayString(disp);
549 }
550 }
551 if (!display) {
552 display = getenv(name: "DISPLAY");
553 }
554 data.add(key: "--display", value: display);
555 }
556#endif
557
558 data.add(key: "--appname", value: s_appName ? s_appName.get() : "<unknown>");
559
560 // only add apppath if it's not NULL
561 if (s_appPath && s_appPath[0]) {
562 data.add(key: "--apppath", value: s_appPath.get());
563 }
564
565 // signal number -- will never be NULL
566 char sigtxt[10];
567 sprintf(s: sigtxt, format: "%d", sig);
568 data.add(key: "--signal", value: sigtxt);
569
570 char pidtxt[20];
571 sprintf(s: pidtxt, format: "%lld", QCoreApplication::applicationPid());
572 data.add(key: "--pid", value: pidtxt);
573
574 const KAboutData *about = KAboutData::applicationDataPointer();
575 if (about) {
576 if (about->internalVersion()) {
577 data.add(key: "--appversion", value: about->internalVersion());
578 }
579
580 if (about->internalProgramName()) {
581 data.add(key: "--programname", value: about->internalProgramName());
582 }
583
584 if (about->internalBugAddress()) {
585 data.add(key: "--bugaddress", value: about->internalBugAddress());
586 }
587
588 if (about->internalProductName()) {
589 data.add(key: "--productname", value: about->internalProductName());
590 }
591 }
592
593 if (s_flags & SaferDialog) {
594 data.addBool(key: "--safer");
595 }
596
597 if ((s_flags & AutoRestart) && s_autoRestartCommandLine) {
598 data.addBool(key: "--restarted");
599 }
600
601#if defined(Q_OS_WIN)
602 char threadId[8] = {0};
603 sprintf(threadId, "%d", GetCurrentThreadId());
604 data.add("--thread", threadId);
605#endif
606
607 if (s_crashInHandler) {
608#if !defined(Q_OS_WIN)
609 kill(pid: getpid(), SIGSEGV);
610#endif
611 return; // we may continue running a couple ms after the segv. return so we don't close the data!
612 }
613
614 data.close();
615 const int argc = data.argc;
616 const char **argv = data.argv.data();
617
618 fprintf(stderr, format: "KCrash: Application '%s' crashing... crashRecursionCounter = %d\n", s_appName ? s_appName.get() : "<unknown>", crashRecursionCounter);
619
620 if (s_launchDrKonqi != 1) {
621 setCrashHandler(nullptr);
622#if !defined(Q_OS_WIN)
623 raise(sig: sig); // dump core, or whatever is the default action for this signal.
624#endif
625 return;
626 }
627
628#if !defined(Q_OS_WIN) && !defined(Q_OS_OSX)
629 if (!(s_flags & KeepFDs)) {
630 // This tries to prevent problems where applications fail to release resources that drkonqi might need.
631 // Specifically this was introduced to ensure that an application that had grabbed the X11 cursor would
632 // forcefully have it removed upon crash to ensure it is ungrabbed by the time drkonqi makes an appearance.
633 // This is also the point in time when, for example, dbus services are lost. Closing the socket indicates
634 // to dbus-daemon that the process has disappeared and it will forcefully reclaim the registered service names.
635 //
636 // Once we close our socket we lose potential dbus names and if we were running as a systemd service anchored to a name,
637 // the daemon may decide to jump at us with a TERM signal. We'll want to have finished the metadata by now and
638 // be near our tracing/raise().
639 closeAllFDs();
640 }
641#if HAVE_X11
642 else if (auto display = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->display()) {
643 close(ConnectionNumber(display));
644 }
645#endif
646#endif
647
648#ifndef NDEBUG
649 fprintf(stderr,
650 format: "KCrash: Application Name = %s path = %s pid = %lld\n",
651 s_appName ? s_appName.get() : "<unknown>",
652 s_appPath ? s_appPath.get() : "<unknown>",
653 QCoreApplication::applicationPid());
654 fprintf(stderr, format: "KCrash: Arguments: ");
655 for (int i = 0; i < s_autoRestartCommandLine.argc; ++i) {
656 fprintf(stderr, format: "%s ", s_autoRestartCommandLine.argv[i]);
657 }
658 fprintf(stderr, format: "\n");
659#endif
660
661 startProcess(argc, argv, waitAndExit: true);
662 }
663
664 if (crashRecursionCounter < 4) {
665 fprintf(stderr, format: "Unable to start Dr. Konqi\n");
666 }
667
668 if (s_coreConfig.isProcess()) {
669 fprintf(stderr, format: "Re-raising signal for core dump handling.\n");
670 KCrash::setCrashHandler(nullptr);
671 raise(sig: sig);
672 // not getting here
673 }
674
675 _exit(status: 255);
676}
677
678#if defined(Q_OS_WIN)
679
680void KCrash::startProcess(int argc, const char *argv[], bool waitAndExit)
681{
682 QString cmdLine;
683 for (int i = 0; i < argc; ++i) {
684 cmdLine.append(QLatin1Char('\"'));
685 cmdLine.append(QFile::decodeName(argv[i]));
686 cmdLine.append(QStringLiteral("\" "));
687 }
688
689 PROCESS_INFORMATION procInfo;
690 STARTUPINFOW startupInfo =
691 {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};
692
693 bool success = CreateProcess(0, (wchar_t *)cmdLine.utf16(), NULL, NULL, false, CREATE_UNICODE_ENVIRONMENT, NULL, NULL, &startupInfo, &procInfo);
694
695 if (success && waitAndExit) {
696 // wait for child to exit
697 WaitForSingleObject(procInfo.hProcess, INFINITE);
698 _exit(253);
699 }
700}
701
702// glue function for calling the unix signal handler from the windows unhandled exception filter
703LONG WINAPI KCrash::win32UnhandledExceptionFilter(_EXCEPTION_POINTERS *exceptionInfo)
704{
705 // kdbgwin needs the context inside exceptionInfo because if getting the context after the
706 // exception happened, it will walk down the stack and will stop at KiUserEventDispatch in
707 // ntdll.dll, which is supposed to dispatch the exception from kernel mode back to user mode
708 // so... let's create some shared memory
709 HANDLE hMapFile = NULL;
710 hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(CONTEXT), TEXT("Local\\KCrashShared"));
711
712 LPCTSTR pBuf = NULL;
713 pBuf = (LPCTSTR)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(CONTEXT));
714 CopyMemory((PVOID)pBuf, exceptionInfo->ContextRecord, sizeof(CONTEXT));
715
716 if (s_crashHandler) {
717 s_crashHandler(exceptionInfo->ExceptionRecord->ExceptionCode);
718 }
719
720 CloseHandle(hMapFile);
721 return EXCEPTION_EXECUTE_HANDLER; // allow windows to do the default action (terminate)
722}
723#else
724
725static pid_t startDirectly(const char *argv[]);
726
727void KCrash::startProcess(int argc, const char *argv[], bool waitAndExit)
728{
729 Q_UNUSED(argc);
730 fprintf(stderr, format: "KCrash: Attempting to start %s\n", argv[0]);
731
732 pid_t pid = startDirectly(argv);
733
734 if (pid > 0 && waitAndExit) {
735 // Seems we made it....
736 alarm(seconds: 0); // Stop the pending alarm that was set at the top of the defaultCrashHandler
737
738 bool running = true;
739 // Wait forever until the started process exits. This code path is executed
740 // when launching drkonqi. Note that DrKonqi will SIGSTOP this process in the meantime
741 // and only send SIGCONT when it is about to attach a debugger.
742#ifdef Q_OS_LINUX
743 // Declare the process that will be debugging the crashed KDE app (#245529).
744#ifndef PR_SET_PTRACER
745#define PR_SET_PTRACER 0x59616d61
746#endif
747 prctl(PR_SET_PTRACER, pid, 0, 0, 0);
748#endif
749 if (running) {
750 // If the process was started directly, use waitpid(), as it's a child...
751 while (waitpid(pid: pid, stat_loc: nullptr, options: 0) != pid) { }
752 }
753 if (!s_coreConfig.isProcess()) {
754 // Only exit if we don't forward to core dumps
755 _exit(status: 253);
756 }
757 }
758}
759
760extern "C" char **environ;
761static pid_t startDirectly(const char *argv[])
762{
763 char **environ_end;
764 for (environ_end = environ; *environ_end; ++environ_end) { }
765
766 std::array<const char *, 1024> environ_data; // hope it's big enough
767 if ((unsigned)(environ_end - environ) + 2 >= environ_data.size()) {
768 fprintf(stderr, format: "environ_data in KCrash not big enough!\n");
769 return 0;
770 }
771 auto end = std::copy_if(first: environ, last: environ_end, result: environ_data.begin(), pred: [](const char *s) {
772 static const char envvar[] = "KCRASH_AUTO_RESTARTED=";
773 return strncmp(s1: envvar, s2: s, n: sizeof(envvar) - 1) != 0;
774 });
775 *end++ = "KCRASH_AUTO_RESTARTED=1";
776 *end++ = nullptr;
777 pid_t pid = fork();
778 switch (pid) {
779 case -1:
780 fprintf(stderr, format: "KCrash failed to fork(), errno = %d\n", errno);
781 return 0;
782 case 0:
783 setgroups(n: 0, groups: nullptr); // Remove any extraneous groups
784 if (setgid(getgid()) < 0 || setuid(getuid()) < 0) {
785 _exit(status: 253); // This cannot happen. Theoretically.
786 }
787#ifndef Q_OS_OSX
788 closeAllFDs(); // We are in the child now. Close FDs unconditionally.
789#endif
790 execve(path: argv[0], argv: const_cast<char **>(argv), envp: const_cast<char **>(environ_data.data()));
791 fprintf(stderr, format: "KCrash failed to exec(), errno = %d\n", errno);
792 _exit(status: 253);
793 default:
794 return pid;
795 }
796}
797#endif // Q_OS_UNIX
798
799void KCrash::setErrorMessage(const QString &message)
800{
801 s_kcrashErrorMessage.reset(p: qstrdup(message.toUtf8().constData()));
802}
803
804void KCrash::setErrorTags(const QHash<QString, QString> &details)
805{
806 DetailsHash data;
807 for (const auto &[key, value] : details.asKeyValueRange()) {
808 data.insert(key: ("--"_L1 + key).toUtf8(), value: value.toUtf8());
809 }
810 // A bit awkard. We want the s_details to be const so we can't accidentally cause
811 // detachments, so we move our data into a unique ptr that is const.
812 s_tags = std::make_unique<const DetailsHash>(args: std::move(data));
813}
814
815void KCrash::setErrorExtraData(const QHash<QString, QString> &details)
816{
817 DetailsHash data;
818 for (const auto &[key, value] : details.asKeyValueRange()) {
819 data.insert(key: ("--"_L1 + key).toUtf8(), value: value.toUtf8());
820 }
821 // A bit awkard. We want the s_details to be const so we can't accidentally cause
822 // detachments, so we move our data into a unique ptr that is const.
823 s_extraData = std::make_unique<const DetailsHash>(args: std::move(data));
824}
825
826void KCrash::setGPUData(const QVariantHash &gpuData)
827{
828 DetailsHash data;
829 for (const auto &[key, value] : gpuData.asKeyValueRange()) {
830 data.insert(key: ("--"_L1 + key).toUtf8(), value: value.toByteArray());
831 }
832 // A bit awkard. We want the s_details to be const so we can't accidentally cause
833 // detachments, so we move our data into a unique ptr that is const.
834 s_gpuContext = std::make_unique<const DetailsHash>(args: std::move(data));
835}
836

source code of kcrash/src/kcrash.cpp