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

source code of kcrash/src/kcrash.cpp