File: kcrash.cpp

package info (click to toggle)
kf6-kcrash 6.20.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 316 kB
  • sloc: cpp: 1,155; sh: 16; makefile: 7
file content (817 lines) | stat: -rw-r--r-- 28,970 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
/*
    This file is part of the KDE Libraries
    SPDX-FileCopyrightText: 2000 Timo Hummel <timo.hummel@sap.com>
    SPDX-FileCopyrightText: 2000 Tom Braun <braunt@fh-konstanz.de>
    SPDX-FileCopyrightText: 2010 George Kiagiadakis <kiagiadakis.george@gmail.com>
    SPDX-FileCopyrightText: 2009 KDE e.V. <kde-ev-board@kde.org>
    SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org>
    SPDX-FileContributor: 2009 Adriaan de Groot <groot@kde.org>

    SPDX-License-Identifier: LGPL-2.0-or-later
*/

#include "kcrash.h"

#include "kcrash_debug.h"

#include <config-kcrash.h>

#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <cstring>

#include <qplatformdefs.h>
#ifndef Q_OS_WIN
#include <cerrno>
#include <sys/resource.h>
#include <sys/un.h>
#else
#include <qt_windows.h>
#endif
#ifdef Q_OS_LINUX
#include <sys/prctl.h>
#endif

#include <KAboutData>

#include <algorithm>
#include <array>
#include <chrono>
#include <memory>

#include <QDebug>
#include <QDir>
#include <QFile>
#include <QGuiApplication>
#include <QLibraryInfo>
#include <QStandardPaths>
#include <QThread>

#if HAVE_X11
#include <X11/Xlib.h>
#endif

#include "coreconfig_p.h"
#include "exception_p.h"
#include "metadata_p.h"

// WARNING: do not use qGlobalStatics in here, they get destroyed too early on
// shutdown and may inhibit crash handling in late-exit scenarios (e.g. when
// a function local static gets destroyed by __cxa_finalize)
#undef Q_GLOBAL_STATIC

using namespace std::chrono_literals;
using namespace Qt::StringLiterals;

struct Args {
    Args() = default;
    ~Args()
    {
        clear();
    }
    Q_DISABLE_COPY_MOVE(Args)

    void clear()
    {
        if (!argc) {
            return;
        }

        for (int i = 0; i < argc; ++i) {
            delete[] argv[i];
        }
        delete[] argv;

        argv = nullptr;
        argc = 0;
    }

    void resize(int size)
    {
        clear();
        argc = size;
        argv = new char *[argc + 1];
        for (int i = 0; i < argc + 1; ++i) {
            argv[i] = nullptr;
        }
    }

    explicit operator bool() const
    {
        return argc > 0;
    }

    int argc = 0;
    // null-terminated array of null-terminated strings
    char **argv = nullptr;
};

static KCrash::HandlerType s_emergencySaveFunction = nullptr;
static KCrash::HandlerType s_crashHandler = nullptr;
static std::unique_ptr<char[]> s_appFilePath; // this is the actual QCoreApplication::applicationFilePath
static std::unique_ptr<char[]> s_appName; // the binary name (may be altered by the application)
static std::unique_ptr<char[]> s_appPath; // the binary dir path (may be altered by the application)
static Args s_autoRestartCommandLine;
static std::unique_ptr<char[]> s_drkonqiPath;
static KCrash::CrashFlags s_flags = KCrash::CrashFlags();
static int s_launchDrKonqi = -1; // -1=initial value 0=disabled 1=enabled
static int s_originalSignal = -1;
static QByteArray s_metadataPath;

static std::unique_ptr<char[]> s_kcrashErrorMessage;

namespace
{
const KCrash::CoreConfig s_coreConfig;

std::unique_ptr<char[]> s_qtVersion;

using DetailsHash = QHash<QByteArray, QByteArray>;
std::unique_ptr<const DetailsHash> s_tags; // Sentry tags
std::unique_ptr<const DetailsHash> s_extraData; // Sentry extra data
std::unique_ptr<const DetailsHash> s_gpuContext; // Sentry gpu context

QString bootId()
{
#ifdef Q_OS_LINUX
    QFile file(QStringLiteral("/proc/sys/kernel/random/boot_id"));
    if (!file.open(QFile::ReadOnly)) {
        qCWarning(LOG_KCRASH) << "Failed to read /proc/sys/kernel/random/boot_id" << file.errorString();
        return {};
    }
    return QString::fromUtf8(file.readAll().simplified().replace('-', QByteArrayView()));
#else
    return {};
#endif
}

} // namespace

static QStringList libexecPaths()
{
    // Static since we only need to evaluate once.
    static QStringList list = QFile::decodeName(qgetenv("LIBEXEC_PATH")).split(QLatin1Char(':'), Qt::SkipEmptyParts) // env var is used first
        + QStringList{
            QCoreApplication::applicationDirPath(), // then look where our application binary is located
            QLibraryInfo::path(QLibraryInfo::LibraryExecutablesPath), // look where libexec path is (can be set in qt.conf)
            QFile::decodeName(KDE_INSTALL_FULL_LIBEXECDIR) // look at our installation location
        };
    return list;
}

namespace KCrash
{
void setApplicationFilePath(const QString &filePath);
void startProcess(int argc, const char *argv[], bool waitAndExit);

#if defined(Q_OS_WIN)
LONG WINAPI win32UnhandledExceptionFilter(_EXCEPTION_POINTERS *exceptionInfo);
#endif
}

static bool shouldWriteMetadataToDisk()
{
#ifdef Q_OS_LINUX
    // NB: The daemon being currently running must not be a condition here. If something crashes during logout
    // the daemon may already be gone but we'll still want to deal with the crash on next login!
    // Similar reasoning applies to not checking the presence of the launcher socket.
    const bool drkonqiCoredumpHelper = !QStandardPaths::findExecutable(QStringLiteral("drkonqi-coredump-processor"), libexecPaths()).isEmpty();
    return s_coreConfig.isCoredumpd() && drkonqiCoredumpHelper && !qEnvironmentVariableIsSet("KCRASH_NO_METADATA");
#else
    return false;
#endif
}

void KCrash::initialize()
{
    if (s_launchDrKonqi == 0) { // disabled by the program itself
        return;
    }

    bool enableDrKonqi = !qEnvironmentVariableIsSet("KDE_DEBUG");
    if (qEnvironmentVariableIsSet("KCRASH_AUTO_RESTARTED") || qEnvironmentVariableIntValue("RUNNING_UNDER_RR") == 1
        || qEnvironmentVariableIntValue("KCRASH_DUMP_ONLY") == 1) {
        enableDrKonqi = false;
    }

    const QStringList args = QCoreApplication::arguments();
    // Default to core dumping whenever a process is set. When not or when explicitly opting into just in time debugging
    // we enable drkonqi. This causes the signal handler to directly fork drkonqi opening us to race conditions.
    // NOTE: depending on the specific signal other threads are running while the signal handler runs and may trip over
    //   the signal handler's closed FDs. That is primarily why we do not like JIT debugging.
    if (enableDrKonqi && (!s_coreConfig.isProcess() || qEnvironmentVariableIntValue("KCRASH_JIT_DRKONQI") == 1)) {
        KCrash::setDrKonqiEnabled(true);
    } else {
        // Don't qDebug here, it loads qtlogging.ini very early which prevents unittests from doing QStandardPaths::setTestModeEnabled(true) in initTestCase()
    }

    s_qtVersion.reset(qstrdup(qVersion()));

    if (QCoreApplication::instance()) {
        const QString path = QCoreApplication::applicationFilePath();
        s_appFilePath.reset(qstrdup(qPrintable(path))); // This intentionally cannot be changed by the application!
        KCrash::setApplicationFilePath(path);
    } else {
        qWarning() << "This process needs a QCoreApplication instance in order to use KCrash";
    }

    if (shouldWriteMetadataToDisk()) {
        // We do not actively clean up metadata via KCrash but some other service. This potentially means we litter
        // a lot -> put the metadata in a subdir.
        // This data is consumed by DrKonqi in combination with coredumpd metadata.
        const QString metadataDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/kcrash-metadata");
        if (QDir().mkpath(metadataDir)) {
            const auto bootId = ::bootId();
            const auto exe = QString::fromUtf8(s_appName.get());
            const auto pid = QString::number(QCoreApplication::applicationPid());
            s_metadataPath = QFile::encodeName(metadataDir + //
                                               QStringLiteral("/%1.%2.%3.ini").arg(exe, bootId, pid));
        }
        if (!s_crashHandler) {
            // Always enable the default handler. We cannot create the metadata ahead of time since we do not know
            // when the application metadata is "complete".
            // TODO: kf6 maybe change the way init works and have the users run it when their done with kaboutdata etc.?
            //    the problem with delayed writing is that our crash handler (or any crash handler really) introduces a delay
            //    in dumping and this in turn increases the risk of another stepping into a puddle (SEGV only runs on the
            //    faulting thread; all other threads continue running!). therefore it'd be greatly preferred if we
            //    were able to write the metadata during initial app setup instead of when a crash occurs
            setCrashHandler(defaultCrashHandler);
        }
    } // empty s_metadataPath disables writing
}

void KCrash::setEmergencySaveFunction(HandlerType saveFunction)
{
    s_emergencySaveFunction = saveFunction;

    /*
     * We need at least the default crash handler for
     * emergencySaveFunction to be called
     */
    if (s_emergencySaveFunction && !s_crashHandler) {
        setCrashHandler(defaultCrashHandler);
    }
}

KCrash::HandlerType KCrash::emergencySaveFunction()
{
    return s_emergencySaveFunction;
}

// Set the default crash handler in 10 seconds
// This is used after an autorestart, the second instance of the application
// is started with KCRASH_AUTO_RESTARTED=1, and we
// set the defaultCrashHandler (to handle autorestart) after 10s.
// The delay is to see if we stay up for more than 10s time, to avoid infinite
// respawning if the app crashes on startup.
class KCrashDelaySetHandler : public QObject
{
public:
    KCrashDelaySetHandler()
    {
        startTimer(10s);
    }

protected:
    void timerEvent(QTimerEvent *event) override
    {
        if (!s_crashHandler) { // not set meanwhile
            KCrash::setCrashHandler(KCrash::defaultCrashHandler);
        }
        killTimer(event->timerId());
        this->deleteLater();
    }
};

void KCrash::setFlags(KCrash::CrashFlags flags)
{
    s_flags = flags;
    if (s_flags & AutoRestart) {
        // We need at least the default crash handler for autorestart to work.
        if (!s_crashHandler) {
            if (qEnvironmentVariableIsSet("KCRASH_AUTO_RESTARTED")) {
                new KCrashDelaySetHandler;
            } else {
                setCrashHandler(defaultCrashHandler);
            }
        }
    }
}

void KCrash::setApplicationFilePath(const QString &filePath)
{
    const auto pos = filePath.lastIndexOf(QLatin1Char('/'));
    const QString appName = filePath.mid(pos + 1);
    const QString appPath = filePath.left(pos); // could be empty, in theory

    s_appName.reset(qstrdup(QFile::encodeName(appName).constData()));
    s_appPath.reset(qstrdup(QFile::encodeName(appPath).constData()));

    // Prepare the auto-restart command
    QStringList args = QCoreApplication::arguments();
    if (args.isEmpty()) { // edge case: tst_QX11Info::startupId does QApplication app(argc, nullptr)...
        args.append(filePath);
    } else {
        args[0] = filePath; // replace argv[0] with full path above
    }

    s_autoRestartCommandLine.resize(args.count());
    for (int i = 0; i < args.count(); ++i) {
        s_autoRestartCommandLine.argv[i] = qstrdup(QFile::encodeName(args.at(i)).constData());
    }
}

void KCrash::setDrKonqiEnabled(bool enabled)
{
    const int launchDrKonqi = enabled ? 1 : 0;
    if (s_launchDrKonqi == launchDrKonqi) {
        return;
    }
    s_launchDrKonqi = launchDrKonqi;
    if (s_launchDrKonqi && !s_drkonqiPath) {
        const QString exec = QStandardPaths::findExecutable(QStringLiteral("drkonqi"), libexecPaths());
        if (exec.isEmpty()) {
            qCDebug(LOG_KCRASH) << "Could not find drkonqi in search paths:" << libexecPaths();
            s_launchDrKonqi = 0;
        } else {
            s_drkonqiPath.reset(qstrdup(qPrintable(exec)));
        }
    }

    // we need at least the default crash handler to launch drkonqi
    if (s_launchDrKonqi && !s_crashHandler) {
        setCrashHandler(defaultCrashHandler);
    }
}

bool KCrash::isDrKonqiEnabled()
{
    return s_launchDrKonqi == 1;
}

void KCrash::setCrashHandler(HandlerType handler)
{
#if defined(Q_OS_WIN)
    static LPTOP_LEVEL_EXCEPTION_FILTER s_previousExceptionFilter = NULL;

    if (handler && !s_previousExceptionFilter) {
        s_previousExceptionFilter = SetUnhandledExceptionFilter(KCrash::win32UnhandledExceptionFilter);
    } else if (!handler && s_previousExceptionFilter) {
        SetUnhandledExceptionFilter(s_previousExceptionFilter);
        s_previousExceptionFilter = NULL;
    }
#else
    if (!handler) {
        handler = SIG_DFL;
    }

    sigset_t mask;
    sigemptyset(&mask);

    const auto signals = {
#ifdef SIGSEGV
        SIGSEGV,
#endif
#ifdef SIGBUS
        SIGBUS,
#endif
#ifdef SIGFPE
        SIGFPE,
#endif
#ifdef SIGILL
        SIGILL,
#endif
#ifdef SIGABRT
        SIGABRT,
#endif
    };

    for (const auto &signal : signals) {
        struct sigaction action {
        };
        action.sa_handler = handler;
        action.sa_flags = SA_RESTART;
        sigemptyset(&action.sa_mask);
        sigaction(signal, &action, nullptr);
        sigaddset(&mask, signal);
    }

    sigprocmask(SIG_UNBLOCK, &mask, nullptr);
#endif

    s_crashHandler = handler;
}

KCrash::HandlerType KCrash::crashHandler()
{
    return s_crashHandler;
}

#if !defined(Q_OS_WIN) && !defined(Q_OS_OSX)
static void closeAllFDs()
{
    // Close all remaining file descriptors except for stdin/stdout/stderr
    struct rlimit rlp = {};
    getrlimit(RLIMIT_NOFILE, &rlp);
    for (rlim_t i = 3; i < rlp.rlim_cur; i++) {
        close(i);
    }
}
#endif

void crashOnSigTerm(int sig)
{
    Q_UNUSED(sig)
    raise(s_originalSignal);
}

void KCrash::defaultCrashHandler(int sig)
{
    // WABA: Do NOT use qDebug() in this function because it is much too risky!
    // Handle possible recursions
    static int crashRecursionCounter = 0;
    crashRecursionCounter++; // Nothing before this, please !
    s_originalSignal = sig;

#if !defined(Q_OS_WIN)
    signal(SIGALRM, SIG_DFL);
    alarm(3); // Kill me... (in case we deadlock in malloc)
#endif

    if (crashRecursionCounter < 2) {
        if (s_emergencySaveFunction) {
            s_emergencySaveFunction(sig);
        }
        if ((s_flags & AutoRestart) && s_autoRestartCommandLine) {
            QThread::sleep(1);
            startProcess(s_autoRestartCommandLine.argc, const_cast<const char **>(s_autoRestartCommandLine.argv), false);
        }
        crashRecursionCounter++;
    }

    if (crashRecursionCounter < 3) {
        // If someone is telling me to stop while I'm already crashing, then I should resume crashing
        signal(SIGTERM, &crashOnSigTerm);

        // NB: all metadata writing ought to happen before closing FDs to reduce synchronization problems with dbus.

        // WARNING: do not forget to increase Metadata::argv's size when adding more potential arguments!
        Metadata data(s_drkonqiPath.get());
#ifdef Q_OS_LINUX
        // The ini is required to be scoped here, as opposed to the conditional scope, so its lifetime is the same as
        // the regular data instance!
        MetadataINIWriter ini(s_metadataPath);
        // s_appFilePath can point to nullptr
        // not exactly sure how, maybe some race condition due to KCrashDelaySetHandler ?
        if (!s_appFilePath) {
            fprintf(stderr, "KCrash: appFilePath points to nullptr!\n");
        } else if (ini.isWritable()) {
            if (s_tags) {
                // [KCrashTags]
                ini.startTagsGroup();
                // 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.
                for (const auto &[key, value] : s_tags->asKeyValueRange()) {
                    ini.add(key.constData(), value.constData(), MetadataWriter::BoolValue::No);
                }
            }

            if (s_extraData) {
                // [KCrashExtra]
                ini.startExtraGroup();
                // 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.
                for (const auto &[key, value] : s_extraData->asKeyValueRange()) {
                    ini.add(key.constData(), value.constData(), MetadataWriter::BoolValue::No);
                }
            }
            if (s_kcrashErrorMessage) {
                // And also our legacy error message
                ini.add("--_kcrash_ErrorMessage", s_kcrashErrorMessage.get(), MetadataWriter::BoolValue::No);
            }

            if (s_gpuContext) {
                // [KCrashGPU]
                ini.startGPUGroup();
                // 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.
                for (const auto &[key, value] : s_gpuContext->asKeyValueRange()) {
                    ini.add(key.constData(), value.constData(), MetadataWriter::BoolValue::No);
                }
            }

            // [KCrash]
            ini.startKCrashGroup();
            // Add the canonical exe path so the coredump daemon has more data points to map metadata to journald entry.
            ini.add("--exe", s_appFilePath.get(), MetadataWriter::BoolValue::No);

            data.setAdditionalWriter(&ini);
        }
#endif

        if (auto optionalExceptionMetadata = KCrash::exceptionMetadata(); optionalExceptionMetadata.has_value()) {
            if (optionalExceptionMetadata->klass) {
                data.add("--exceptionname", optionalExceptionMetadata->klass);
            }
            if (optionalExceptionMetadata->what) {
                data.add("--exceptionwhat", optionalExceptionMetadata->what);
            }
        }

        if (s_qtVersion) {
            data.add("--qtversion", s_qtVersion.get());
        }

        data.add("--kdeframeworksversion", KCRASH_VERSION_STRING);

        const QByteArray platformName = QGuiApplication::platformName().toUtf8();
        if (!platformName.isEmpty()) {
            if (strcmp(platformName.constData(), "wayland-org.kde.kwin.qpa") == 0) { // redirect kwin's internal QPA to wayland proper
                data.add("--platform", "wayland");
            } else {
                data.add("--platform", platformName.constData());
            }
        }

#if HAVE_X11
        if (platformName == QByteArrayLiteral("xcb")) {
            // start up on the correct display
            char *display = nullptr;
            if (auto disp = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->display()) {
                display = XDisplayString(disp);
            } else {
                display = getenv("DISPLAY");
            }
            data.add("--display", display);
        }
#endif

        data.add("--appname", s_appName ? s_appName.get() : "<unknown>");

        // only add apppath if it's not NULL
        if (s_appPath && s_appPath[0]) {
            data.add("--apppath", s_appPath.get());
        }

        // signal number -- will never be NULL
        char sigtxt[10];
        sprintf(sigtxt, "%d", sig);
        data.add("--signal", sigtxt);

        char pidtxt[20];
        sprintf(pidtxt, "%lld", QCoreApplication::applicationPid());
        data.add("--pid", pidtxt);

        const KAboutData *about = KAboutData::applicationDataPointer();
        if (about) {
            if (about->internalVersion()) {
                data.add("--appversion", about->internalVersion());
            }

            if (about->internalProgramName()) {
                data.add("--programname", about->internalProgramName());
            }

            if (about->internalBugAddress()) {
                data.add("--bugaddress", about->internalBugAddress());
            }

            if (about->internalProductName()) {
                data.add("--productname", about->internalProductName());
            }
        }

        if (s_flags & SaferDialog) {
            data.addBool("--safer");
        }

        if ((s_flags & AutoRestart) && s_autoRestartCommandLine) {
            data.addBool("--restarted");
        }

#if defined(Q_OS_WIN)
        char threadId[8] = {0};
        sprintf(threadId, "%d", GetCurrentThreadId());
        data.add("--thread", threadId);
#endif

        data.close();
        const int argc = data.argc;
        const char **argv = data.argv.data();

        fprintf(stderr, "KCrash: Application '%s' crashing... crashRecursionCounter = %d\n", s_appName ? s_appName.get() : "<unknown>", crashRecursionCounter);

        if (s_launchDrKonqi != 1) {
            setCrashHandler(nullptr);
#if !defined(Q_OS_WIN)
            raise(sig); // dump core, or whatever is the default action for this signal.
#endif
            return;
        }

#if !defined(Q_OS_WIN) && !defined(Q_OS_OSX)
        if (!(s_flags & KeepFDs)) {
            // This tries to prevent problems where applications fail to release resources that drkonqi might need.
            // Specifically this was introduced to ensure that an application that had grabbed the X11 cursor would
            // forcefully have it removed upon crash to ensure it is ungrabbed by the time drkonqi makes an appearance.
            // This is also the point in time when, for example, dbus services are lost. Closing the socket indicates
            // to dbus-daemon that the process has disappeared and it will forcefully reclaim the registered service names.
            //
            // Once we close our socket we lose potential dbus names and if we were running as a systemd service anchored to a name,
            // the daemon may decide to jump at us with a TERM signal. We'll want to have finished the metadata by now and
            // be near our tracing/raise().
            closeAllFDs();
        }
#if HAVE_X11
        else if (auto display = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->display()) {
            close(ConnectionNumber(display));
        }
#endif
#endif

#ifndef NDEBUG
        fprintf(stderr,
                "KCrash: Application Name = %s path = %s pid = %lld\n",
                s_appName ? s_appName.get() : "<unknown>",
                s_appPath ? s_appPath.get() : "<unknown>",
                QCoreApplication::applicationPid());
        fprintf(stderr, "KCrash: Arguments: ");
        for (int i = 0; i < s_autoRestartCommandLine.argc; ++i) {
            fprintf(stderr, "%s ", s_autoRestartCommandLine.argv[i]);
        }
        fprintf(stderr, "\n");
#endif

        startProcess(argc, argv, true);
    }

    if (crashRecursionCounter < 4) {
        fprintf(stderr, "Unable to start Dr. Konqi\n");
    }

    if (s_coreConfig.isProcess()) {
        fprintf(stderr, "Re-raising signal for core dump handling.\n");
        KCrash::setCrashHandler(nullptr);
        raise(sig);
        // not getting here
    }

    _exit(255);
}

#if defined(Q_OS_WIN)

void KCrash::startProcess(int argc, const char *argv[], bool waitAndExit)
{
    QString cmdLine;
    for (int i = 0; i < argc; ++i) {
        cmdLine.append(QLatin1Char('\"'));
        cmdLine.append(QFile::decodeName(argv[i]));
        cmdLine.append(QStringLiteral("\" "));
    }

    PROCESS_INFORMATION procInfo;
    STARTUPINFOW startupInfo =
        {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};

    bool success = CreateProcess(0, (wchar_t *)cmdLine.utf16(), NULL, NULL, false, CREATE_UNICODE_ENVIRONMENT, NULL, NULL, &startupInfo, &procInfo);

    if (success && waitAndExit) {
        // wait for child to exit
        WaitForSingleObject(procInfo.hProcess, INFINITE);
        _exit(253);
    }
}

// glue function for calling the unix signal handler from the windows unhandled exception filter
LONG WINAPI KCrash::win32UnhandledExceptionFilter(_EXCEPTION_POINTERS *exceptionInfo)
{
    // kdbgwin needs the context inside exceptionInfo because if getting the context after the
    // exception happened, it will walk down the stack and will stop at KiUserEventDispatch in
    // ntdll.dll, which is supposed to dispatch the exception from kernel mode back to user mode
    // so... let's create some shared memory
    HANDLE hMapFile = NULL;
    hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(CONTEXT), TEXT("Local\\KCrashShared"));

    LPCTSTR pBuf = NULL;
    pBuf = (LPCTSTR)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(CONTEXT));
    CopyMemory((PVOID)pBuf, exceptionInfo->ContextRecord, sizeof(CONTEXT));

    if (s_crashHandler) {
        s_crashHandler(exceptionInfo->ExceptionRecord->ExceptionCode);
    }

    CloseHandle(hMapFile);
    return EXCEPTION_EXECUTE_HANDLER; // allow windows to do the default action (terminate)
}
#else

static pid_t startDirectly(const char *argv[]);

void KCrash::startProcess(int argc, const char *argv[], bool waitAndExit)
{
    Q_UNUSED(argc);
    fprintf(stderr, "KCrash: Attempting to start %s\n", argv[0]);

    pid_t pid = startDirectly(argv);

    if (pid > 0 && waitAndExit) {
        // Seems we made it....
        alarm(0); // Stop the pending alarm that was set at the top of the defaultCrashHandler

        bool running = true;
        // Wait forever until the started process exits. This code path is executed
        // when launching drkonqi. Note that DrKonqi will SIGSTOP this process in the meantime
        // and only send SIGCONT when it is about to attach a debugger.
#ifdef Q_OS_LINUX
        // Declare the process that will be debugging the crashed KDE app (#245529).
#ifndef PR_SET_PTRACER
#define PR_SET_PTRACER 0x59616d61
#endif
        prctl(PR_SET_PTRACER, pid, 0, 0, 0);
#endif
        if (running) {
            // If the process was started directly, use waitpid(), as it's a child...
            while (waitpid(pid, nullptr, 0) != pid) { }
        }
        if (!s_coreConfig.isProcess()) {
            // Only exit if we don't forward to core dumps
            _exit(253);
        }
    }
}

extern "C" char **environ;
static pid_t startDirectly(const char *argv[])
{
    char **environ_end;
    for (environ_end = environ; *environ_end; ++environ_end) { }

    std::array<const char *, 1024> environ_data; // hope it's big enough
    if ((unsigned)(environ_end - environ) + 2 >= environ_data.size()) {
        fprintf(stderr, "environ_data in KCrash not big enough!\n");
        return 0;
    }
    auto end = std::copy_if(environ, environ_end, environ_data.begin(), [](const char *s) {
        static const char envvar[] = "KCRASH_AUTO_RESTARTED=";
        return strncmp(envvar, s, sizeof(envvar) - 1) != 0;
    });
    *end++ = "KCRASH_AUTO_RESTARTED=1";
    *end++ = nullptr;
    pid_t pid = fork();
    switch (pid) {
    case -1:
        fprintf(stderr, "KCrash failed to fork(), errno = %d\n", errno);
        return 0;
    case 0:
        setgroups(0, nullptr); // Remove any extraneous groups
        if (setgid(getgid()) < 0 || setuid(getuid()) < 0) {
            _exit(253); // This cannot happen. Theoretically.
        }
#ifndef Q_OS_OSX
        closeAllFDs(); // We are in the child now. Close FDs unconditionally.
#endif
        execve(argv[0], const_cast<char **>(argv), const_cast<char **>(environ_data.data()));
        fprintf(stderr, "KCrash failed to exec(), errno = %d\n", errno);
        _exit(253);
    default:
        return pid;
    }
}
#endif // Q_OS_UNIX

void KCrash::setErrorMessage(const QString &message)
{
    s_kcrashErrorMessage.reset(qstrdup(message.toUtf8().constData()));
}

void KCrash::setErrorTags(const QHash<QString, QString> &details)
{
    DetailsHash data;
    for (const auto &[key, value] : details.asKeyValueRange()) {
        data.insert(("--"_L1 + key).toUtf8(), value.toUtf8());
    }
    // A bit awkard. We want the s_details to be const so we can't accidentally cause
    // detachments, so we move our data into a unique ptr that is const.
    s_tags = std::make_unique<const DetailsHash>(std::move(data));
}

void KCrash::setErrorExtraData(const QHash<QString, QString> &details)
{
    DetailsHash data;
    for (const auto &[key, value] : details.asKeyValueRange()) {
        data.insert(("--"_L1 + key).toUtf8(), value.toUtf8());
    }
    // A bit awkard. We want the s_details to be const so we can't accidentally cause
    // detachments, so we move our data into a unique ptr that is const.
    s_extraData = std::make_unique<const DetailsHash>(std::move(data));
}

void KCrash::setGPUData(const QVariantHash &gpuData)
{
    DetailsHash data;
    for (const auto &[key, value] : gpuData.asKeyValueRange()) {
        data.insert(("--"_L1 + key).toUtf8(), value.toByteArray());
    }
    // A bit awkard. We want the s_details to be const so we can't accidentally cause
    // detachments, so we move our data into a unique ptr that is const.
    s_gpuContext = std::make_unique<const DetailsHash>(std::move(data));
}