KCrash

kcrash.cpp
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(QFile::ReadOnly)) {
140 qCWarning(LOG_KCRASH) << "Failed to read /proc/sys/kernel/random/boot_id" << file.errorString();
141 return {};
142 }
143 return QString::fromUtf8(file.readAll().simplified().replace('-', 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(qgetenv("LIBEXEC_PATH")).split(QLatin1Char(':'), Qt::SkipEmptyParts) // env var is used first
155 + QStringList{
156 QCoreApplication::applicationDirPath(), // then look where our application binary is located
157 QLibraryInfo::path(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"), libexecPaths()).isEmpty();
180 return s_coreConfig.isCoredumpd() && drkonqiCoredumpHelper && !qEnvironmentVariableIsSet("KCRASH_NO_METADATA");
181#else
182 return false;
183#endif
184}
185
187{
188 if (s_launchDrKonqi == 0) { // disabled by the program itself
189 return;
190 }
191
192 bool enableDrKonqi = !qEnvironmentVariableIsSet("KDE_DEBUG");
193 if (qEnvironmentVariableIsSet("KCRASH_AUTO_RESTARTED") || qEnvironmentVariableIntValue("RUNNING_UNDER_RR") == 1
194 || qEnvironmentVariableIntValue("KCRASH_DUMP_ONLY") == 1) {
195 enableDrKonqi = false;
196 }
197
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("KCRASH_JIT_DRKONQI") == 1)) {
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(qstrdup(qVersion()));
210
213 s_appFilePath.reset(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(QStandardPaths::GenericCacheLocation) + QStringLiteral("/kcrash-metadata");
224 if (QDir().mkpath(metadataDir)) {
225 const auto bootId = ::bootId();
226 const auto exe = QString::fromUtf8(s_appName.get());
228 s_metadataPath = QFile::encodeName(metadataDir + //
229 QStringLiteral("/%1.%2.%3.ini").arg(exe, bootId, 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
240 }
241 } // empty s_metadataPath disables writing
242}
243
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) {
254 }
255}
256
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(10s);
274 }
275
276protected:
277 void timerEvent(QTimerEvent *event) override
278 {
279 if (!s_crashHandler) { // not set meanwhile
281 }
282 killTimer(event->timerId());
283 this->deleteLater();
284 }
285};
286
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("KCRASH_AUTO_RESTARTED")) {
294 new KCrashDelaySetHandler;
295 } else {
297 }
298 }
299 }
300}
301
302void KCrash::setApplicationFilePath(const QString &filePath)
303{
304 const auto pos = filePath.lastIndexOf(QLatin1Char('/'));
305 const QString appName = filePath.mid(pos + 1);
306 const QString appPath = filePath.left(pos); // could be empty, in theory
307
308 s_appName.reset(qstrdup(QFile::encodeName(appName).constData()));
309 s_appPath.reset(qstrdup(QFile::encodeName(appPath).constData()));
310
311 // Prepare the auto-restart command
313 if (args.isEmpty()) { // edge case: tst_QX11Info::startupId does QApplication app(argc, nullptr)...
314 args.append(filePath);
315 } else {
316 args[0] = filePath; // replace argv[0] with full path above
317 }
318
319 s_autoRestartCommandLine.resize(args.count());
320 for (int i = 0; i < args.count(); ++i) {
321 s_autoRestartCommandLine.argv[i] = qstrdup(QFile::encodeName(args.at(i)).constData());
322 }
323}
324
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"), 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(qstrdup(qPrintable(exec)));
339 }
340 }
341
342 // we need at least the default crash handler to launch drkonqi
343 if (s_launchDrKonqi && !s_crashHandler) {
345 }
346}
347
349{
350 return s_launchDrKonqi == 1;
351}
352
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(&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(&action.sa_mask);
396 sigaction(signal, &action, nullptr);
397 sigaddset(&mask, signal);
398 }
399
400 sigprocmask(SIG_UNBLOCK, &mask, nullptr);
401#endif
402
403 s_crashHandler = handler;
404}
405
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, &rlp);
417 for (rlim_t i = 3; i < rlp.rlim_cur; i++) {
418 close(i);
419 }
420}
421#endif
422
423void crashOnSigTerm(int sig)
424{
425 Q_UNUSED(sig)
426 raise(s_originalSignal);
427}
428
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(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) {
448 startProcess(s_autoRestartCommandLine.argc, const_cast<const char **>(s_autoRestartCommandLine.argv), 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, &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, "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.constData(), value.constData(), 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.constData(), value.constData(), MetadataWriter::BoolValue::No);
485 }
486 }
487 if (s_kcrashErrorMessage) {
488 // And also our legacy error message
489 ini.add("--_kcrash_ErrorMessage", s_kcrashErrorMessage.get(), 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.constData(), value.constData(), 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("--exe", s_appFilePath.get(), 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("--exceptionname", optionalExceptionMetadata->klass);
513 }
514 if (optionalExceptionMetadata->what) {
515 data.add("--exceptionwhat", optionalExceptionMetadata->what);
516 }
517 }
518
519 if (s_qtVersion) {
520 data.add("--qtversion", s_qtVersion.get());
521 }
522
523 data.add("--kdeframeworksversion", KCRASH_VERSION_STRING);
524
525 const QByteArray platformName = QGuiApplication::platformName().toUtf8();
526 if (!platformName.isEmpty()) {
527 if (strcmp(platformName.constData(), "wayland-org.kde.kwin.qpa") == 0) { // redirect kwin's internal QPA to wayland proper
528 data.add("--platform", "wayland");
529 } else {
530 data.add("--platform", 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("DISPLAY");
542 }
543 data.add("--display", display);
544 }
545#endif
546
547 data.add("--appname", 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("--apppath", s_appPath.get());
552 }
553
554 // signal number -- will never be NULL
555 char sigtxt[10];
556 sprintf(sigtxt, "%d", sig);
557 data.add("--signal", sigtxt);
558
559 char pidtxt[20];
560 sprintf(pidtxt, "%lld", QCoreApplication::applicationPid());
561 data.add("--pid", pidtxt);
562
563 const KAboutData *about = KAboutData::applicationDataPointer();
564 if (about) {
565 if (about->internalVersion()) {
566 data.add("--appversion", about->internalVersion());
567 }
568
569 if (about->internalProgramName()) {
570 data.add("--programname", about->internalProgramName());
571 }
572
573 if (about->internalBugAddress()) {
574 data.add("--bugaddress", about->internalBugAddress());
575 }
576
577 if (about->internalProductName()) {
578 data.add("--productname", about->internalProductName());
579 }
580 }
581
582 if (s_flags & SaferDialog) {
583 data.addBool("--safer");
584 }
585
586 if ((s_flags & AutoRestart) && s_autoRestartCommandLine) {
587 data.addBool("--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, "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); // 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 "KCrash: Application Name = %s path = %s pid = %lld\n",
633 s_appName ? s_appName.get() : "<unknown>",
634 s_appPath ? s_appPath.get() : "<unknown>",
636 fprintf(stderr, "KCrash: Arguments: ");
637 for (int i = 0; i < s_autoRestartCommandLine.argc; ++i) {
638 fprintf(stderr, "%s ", s_autoRestartCommandLine.argv[i]);
639 }
640 fprintf(stderr, "\n");
641#endif
642
643 startProcess(argc, argv, true);
644 }
645
646 if (crashRecursionCounter < 4) {
647 fprintf(stderr, "Unable to start Dr. Konqi\n");
648 }
649
650 if (s_coreConfig.isProcess()) {
651 fprintf(stderr, "Re-raising signal for core dump handling.\n");
653 raise(sig);
654 // not getting here
655 }
656
657 _exit(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, "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(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, nullptr, 0) != pid) { }
734 }
735 if (!s_coreConfig.isProcess()) {
736 // Only exit if we don't forward to core dumps
737 _exit(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, "environ_data in KCrash not big enough!\n");
751 return 0;
752 }
753 auto end = std::copy_if(environ, environ_end, environ_data.begin(), [](const char *s) {
754 static const char envvar[] = "KCRASH_AUTO_RESTARTED=";
755 return strncmp(envvar, s, 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, "KCrash failed to fork(), errno = %d\n", errno);
763 return 0;
764 case 0:
765 setgroups(0, nullptr); // Remove any extraneous groups
766 if (setgid(getgid()) < 0 || setuid(getuid()) < 0) {
767 _exit(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(argv[0], const_cast<char **>(argv), const_cast<char **>(environ_data.data()));
773 fprintf(stderr, "KCrash failed to exec(), errno = %d\n", errno);
774 _exit(253);
775 default:
776 return pid;
777 }
778}
779#endif // Q_OS_UNIX
780
782{
783 s_kcrashErrorMessage.reset(qstrdup(message.toUtf8().constData()));
784}
785
787{
788 DetailsHash data;
789 for (const auto &[key, value] : details.asKeyValueRange()) {
790 data.insert(("--"_L1 + key).toUtf8(), 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>(std::move(data));
795}
796
798{
799 DetailsHash data;
800 for (const auto &[key, value] : details.asKeyValueRange()) {
801 data.insert(("--"_L1 + key).toUtf8(), 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>(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(("--"_L1 + key).toUtf8(), 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>(std::move(data));
817}
const char * internalBugAddress() const
const char * internalProductName() const
const char * internalVersion() const
const char * internalProgramName() const
This namespace contains functions to handle crashes.
KCRASH_EXPORT void setCrashHandler(HandlerType handler=defaultCrashHandler)
Install a function to be called when a crash occurs.
Definition kcrash.cpp:353
KCRASH_EXPORT void setErrorMessage(const QString &message)
Allows providing information to be included in the bug report.
Definition kcrash.cpp:781
KCRASH_EXPORT HandlerType crashHandler()
Returns the installed crash handler.
Definition kcrash.cpp:406
KCRASH_EXPORT void setErrorTags(const QHash< QString, QString > &details)
Sets the error tags to be included in the crash report.
Definition kcrash.cpp:786
KCRASH_EXPORT void defaultCrashHandler(int signal)
The default crash handler.
Definition kcrash.cpp:429
QFlags< CrashFlag > CrashFlags
Stores a combination of CrashFlag values.
Definition kcrash.h:117
KCRASH_EXPORT void setDrKonqiEnabled(bool enabled)
Enables or disables launching DrKonqi from the crash handler.
Definition kcrash.cpp:325
KCRASH_EXPORT void setFlags(KCrash::CrashFlags flags)
Set options to determine how the default crash handler should behave.
Definition kcrash.cpp:287
KCRASH_EXPORT void setErrorExtraData(const QHash< QString, QString > &details)
Sets the error details to be included in the crash report.
Definition kcrash.cpp:797
void(* HandlerType)(int)
Typedef for a pointer to a crash handler function.
Definition kcrash.h:62
KCRASH_EXPORT HandlerType emergencySaveFunction()
Returns the currently set emergency save function.
Definition kcrash.cpp:257
KCRASH_EXPORT void setGPUData(const QVariantHash &data)
Sets better GPU data.
Definition kcrash.cpp:808
KCRASH_EXPORT bool isDrKonqiEnabled()
Returns true if DrKonqi is set to be launched from the crash handler or false otherwise.
Definition kcrash.cpp:348
@ AutoRestart
autorestart this application. Only sensible for KUniqueApplications.
Definition kcrash.h:112
@ SaferDialog
start DrKonqi without arbitrary disk access
Definition kcrash.h:109
@ KeepFDs
don't close all file descriptors immediately
Definition kcrash.h:108
KCRASH_EXPORT void initialize()
Initialize KCrash.
Definition kcrash.cpp:186
KCRASH_EXPORT void setEmergencySaveFunction(HandlerType saveFunction=nullptr)
Installs a function which should try to save the application's data.
Definition kcrash.cpp:244
KIOCORE_EXPORT QStringList list(const QString &fileClass)
KGuiItem close()
const QList< QKeySequence > & end()
const char * constData() const const
bool isEmpty() const const
QString applicationDirPath()
QString applicationFilePath()
qint64 applicationPid()
QStringList arguments()
QCoreApplication * instance()
QString decodeName(const QByteArray &localFileName)
QByteArray encodeName(const QString &fileName)
auto asKeyValueRange() &
QString path(LibraryPath p)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
qsizetype count() const const
bool isEmpty() const const
void deleteLater()
virtual bool event(QEvent *e)
void killTimer(int id)
int startTimer(int interval, Qt::TimerType timerType)
QString findExecutable(const QString &executableName, const QStringList &paths)
QString writableLocation(StandardLocation type)
QString & append(QChar ch)
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
qsizetype lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const const
QString left(qsizetype n) const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QByteArray toUtf8() const const
const ushort * utf16() const const
typedef HANDLE
SkipEmptyParts
void sleep(std::chrono::nanoseconds nsecs)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Feb 28 2025 12:03:12 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.