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

source code of kcrash/src/kcrash.cpp