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

KDE's Doxygen guidelines are available online.