File indexing completed on 2024-04-28 11:35:24
0001 /* 0002 This file is part of the KDE Libraries 0003 SPDX-FileCopyrightText: 2000 Timo Hummel <timo.hummel@sap.com> 0004 SPDX-FileCopyrightText: 2000 Tom Braun <braunt@fh-konstanz.de> 0005 SPDX-FileCopyrightText: 2010 George Kiagiadakis <kiagiadakis.george@gmail.com> 0006 SPDX-FileCopyrightText: 2009 KDE e.V. <kde-ev-board@kde.org> 0007 SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org> 0008 SPDX-FileContributor: 2009 Adriaan de Groot <groot@kde.org> 0009 0010 SPDX-License-Identifier: LGPL-2.0-or-later 0011 */ 0012 0013 #include "kcrash.h" 0014 0015 #include <config-kcrash.h> 0016 0017 #include <signal.h> 0018 #include <stdio.h> 0019 #include <stdlib.h> 0020 #include <string.h> 0021 0022 #include <qplatformdefs.h> 0023 #ifndef Q_OS_WIN 0024 #include <cerrno> 0025 #include <sys/resource.h> 0026 #include <sys/un.h> 0027 #else 0028 #include <qt_windows.h> 0029 #endif 0030 #ifdef Q_OS_LINUX 0031 #include <sys/poll.h> 0032 #include <sys/prctl.h> 0033 #endif 0034 0035 #include <KAboutData> 0036 0037 #include <algorithm> 0038 #include <array> 0039 #include <memory> 0040 0041 #include <QDebug> 0042 #include <QDir> 0043 #include <QFile> 0044 #include <QGuiApplication> 0045 #include <QLibraryInfo> 0046 #include <QStandardPaths> 0047 #include <QThread> 0048 0049 #include <QLoggingCategory> 0050 Q_DECLARE_LOGGING_CATEGORY(LOG_KCRASH) 0051 0052 // logging category for this framework, default: log stuff >= info 0053 Q_LOGGING_CATEGORY(LOG_KCRASH, "kf.crash", QtInfoMsg) 0054 0055 #if HAVE_X11 0056 #include <X11/Xlib.h> 0057 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0058 #include <QX11Info> 0059 #endif 0060 #endif 0061 0062 #include "coreconfig_p.h" 0063 #include "metadata_p.h" 0064 0065 // Copy from klauncher_cmds 0066 typedef struct { 0067 long cmd; 0068 long arg_length; 0069 } kcrash_launcher_header; 0070 0071 #define LAUNCHER_OK 4 0072 #define LAUNCHER_EXEC_NEW 12 0073 0074 namespace KCrash 0075 { 0076 KCRASH_EXPORT bool loadedByKdeinit = false; 0077 void setApplicationFilePath(const QString &filePath); 0078 // Create socket path to transfer ptrace scope and open connection 0079 } 0080 #ifdef Q_OS_LINUX 0081 static QByteArray s_socketpath; 0082 #endif 0083 0084 struct Args { 0085 ~Args() 0086 { 0087 clear(); 0088 } 0089 0090 void clear() 0091 { 0092 if (!argc) { 0093 return; 0094 } 0095 0096 for (int i = 0; i < argc; ++i) { 0097 delete[] argv[i]; 0098 } 0099 delete[] argv; 0100 0101 argv = nullptr; 0102 argc = 0; 0103 } 0104 0105 void resize(int size) 0106 { 0107 clear(); 0108 argc = size; 0109 argv = new char *[argc + 1]; 0110 for (int i = 0; i < argc + 1; ++i) { 0111 argv[i] = nullptr; 0112 } 0113 } 0114 0115 explicit operator bool() const 0116 { 0117 return argc > 0; 0118 } 0119 0120 int argc = 0; 0121 // null-terminated array of null-terminated strings 0122 char **argv = nullptr; 0123 }; 0124 0125 static KCrash::HandlerType s_emergencySaveFunction = nullptr; 0126 static KCrash::HandlerType s_crashHandler = nullptr; 0127 static std::unique_ptr<char[]> s_appFilePath; // this is the actual QCoreApplication::applicationFilePath 0128 static std::unique_ptr<char[]> s_appName; // the binary name (may be altered by the application) 0129 static std::unique_ptr<char[]> s_appPath; // the binary dir path (may be altered by the application) 0130 static Args s_autoRestartCommandLine; 0131 static std::unique_ptr<char[]> s_drkonqiPath; 0132 static KCrash::CrashFlags s_flags = KCrash::CrashFlags(); 0133 static int s_launchDrKonqi = -1; // -1=initial value 0=disabled 1=enabled 0134 static int s_originalSignal = -1; 0135 static QByteArray s_metadataPath; 0136 0137 static std::unique_ptr<char[]> s_kcrashErrorMessage; 0138 Q_GLOBAL_STATIC(KCrash::CoreConfig, s_coreConfig) 0139 0140 static void kcrashInitialize() 0141 { 0142 // Static because in some cases this is called multiple times 0143 // but if an application had any of the bad cases we always want 0144 // to skip the check 0145 static bool doAutoInitKCrash = true; 0146 0147 if (!doAutoInitKCrash) { 0148 return; 0149 } 0150 0151 QCoreApplication *app = QCoreApplication::instance(); 0152 if (!app) { 0153 doAutoInitKCrash = false; 0154 return; 0155 } 0156 0157 if (!QCoreApplication::startingUp()) { 0158 // If the app has already started, this means we're not being run as part of 0159 // qt_call_pre_routines, which most probably means that we're being run as part 0160 // of KCrash being loaded as part of some plugin of the app, so don't 0161 // do any magic 0162 doAutoInitKCrash = false; 0163 return; 0164 } 0165 0166 if (!QCoreApplication::eventDispatcher()) { 0167 // We are called with event dispatcher being null when KCrash is being loaded 0168 // through plasma-integration instead of being linked to the app (i.e. QtCreator vs Okular) 0169 // For apps that don't link directly to KCrash do not do the magic 0170 doAutoInitKCrash = false; 0171 return; 0172 } 0173 0174 KCrash::initialize(); 0175 } 0176 Q_COREAPP_STARTUP_FUNCTION(kcrashInitialize) 0177 0178 static QStringList libexecPaths() 0179 { 0180 // Static since we only need to evaluate once. 0181 static QStringList list = QFile::decodeName(qgetenv("LIBEXEC_PATH")).split(QLatin1Char(':'), Qt::SkipEmptyParts) // env var is used first 0182 + QStringList{ 0183 QCoreApplication::applicationDirPath(), // then look where our application binary is located 0184 QLibraryInfo::location(QLibraryInfo::LibraryExecutablesPath), // look where libexec path is (can be set in qt.conf) 0185 QFile::decodeName(KDE_INSTALL_FULL_LIBEXECDIR) // look at our installation location 0186 }; 0187 return list; 0188 } 0189 0190 namespace KCrash 0191 { 0192 void setApplicationFilePath(const QString &filePath); 0193 void startProcess(int argc, const char *argv[], bool waitAndExit); 0194 0195 #if defined(Q_OS_WIN) 0196 LONG WINAPI win32UnhandledExceptionFilter(_EXCEPTION_POINTERS *exceptionInfo); 0197 #endif 0198 } 0199 0200 static bool shouldWriteMetadataToDisk() 0201 { 0202 #ifdef Q_OS_LINUX 0203 // NB: The daemon being currently running must not be a condition here. If something crashes during logout 0204 // the daemon may already be gone but we'll still want to deal with the crash on next login! 0205 // Similar reasoning applies to not checking the presence of the launcher socket. 0206 const bool drkonqiCoredumpHelper = !QStandardPaths::findExecutable(QStringLiteral("drkonqi-coredump-processor"), libexecPaths()).isEmpty(); 0207 return s_coreConfig()->isCoredumpd() && drkonqiCoredumpHelper && !qEnvironmentVariableIsSet("KCRASH_NO_METADATA"); 0208 #else 0209 return false; 0210 #endif 0211 } 0212 0213 void KCrash::initialize() 0214 { 0215 if (s_launchDrKonqi == 0) { // disabled by the program itself 0216 return; 0217 } 0218 const QStringList args = QCoreApplication::arguments(); 0219 if (!qEnvironmentVariableIsSet("KDE_DEBUG") // 0220 && !qEnvironmentVariableIsSet("KCRASH_AUTO_RESTARTED") // 0221 && !qEnvironmentVariableIntValue("RUNNING_UNDER_RR") // 0222 && qEnvironmentVariableIntValue("KCRASH_DUMP_ONLY") == 0) { 0223 // enable drkonqi 0224 KCrash::setDrKonqiEnabled(true); 0225 } else { 0226 // This loads qtlogging.ini very early which prevents unittests from doing QStandardPaths::setTestModeEnabled(true) in initTestCase() 0227 // qCDebug(LOG_KCRASH) << "KCrash disabled through environment."; 0228 } 0229 0230 if (QCoreApplication::instance()) { 0231 const QString path = QCoreApplication::applicationFilePath(); 0232 s_appFilePath.reset(qstrdup(qPrintable(path))); // This intentionally cannot be changed by the application! 0233 KCrash::setApplicationFilePath(path); 0234 } else { 0235 qWarning() << "This process needs a QCoreApplication instance in order to use KCrash"; 0236 } 0237 0238 #ifdef Q_OS_LINUX 0239 // Create socket path to transfer ptrace scope and open connection 0240 s_socketpath = QFile::encodeName(QStringLiteral("%1/kcrash_%2").arg(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation)).arg(getpid())); 0241 #endif 0242 0243 if (shouldWriteMetadataToDisk()) { 0244 // We do not actively clean up metadata via KCrash but some other service. This potentially means we litter 0245 // a lot -> put the metadata in a subdir. 0246 const QString metadataDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/kcrash-metadata"); 0247 if (QDir().mkpath(metadataDir)) { 0248 s_metadataPath = QFile::encodeName(metadataDir + QStringLiteral("/%1.ini").arg(QCoreApplication::applicationPid())); 0249 } 0250 if (!s_crashHandler) { 0251 // Always enable the default handler. We cannot create the metadata ahead of time since we do not know 0252 // when the application metadata is "complete". 0253 // TODO: kf6 maybe change the way init works and have the users run it when their done with kaboutdata etc.? 0254 // the problem with delayed writing is that our crash handler (or any crash handler really) introduces a delay 0255 // in dumping and this in turn increases the risk of another stepping into a puddle (SEGV only runs on the 0256 // faulting thread; all other threads continue running!). therefore it'd be greatly preferred if we 0257 // were able to write the metadata during initial app setup instead of when a crash occurs 0258 setCrashHandler(defaultCrashHandler); 0259 } 0260 } // empty s_metadataPath disables writing 0261 0262 s_coreConfig(); // Initialize. 0263 } 0264 0265 void KCrash::setEmergencySaveFunction(HandlerType saveFunction) 0266 { 0267 s_emergencySaveFunction = saveFunction; 0268 0269 /* 0270 * We need at least the default crash handler for 0271 * emergencySaveFunction to be called 0272 */ 0273 if (s_emergencySaveFunction && !s_crashHandler) { 0274 setCrashHandler(defaultCrashHandler); 0275 } 0276 } 0277 0278 KCrash::HandlerType KCrash::emergencySaveFunction() 0279 { 0280 return s_emergencySaveFunction; 0281 } 0282 0283 // Set the default crash handler in 10 seconds 0284 // This is used after an autorestart, the second instance of the application 0285 // is started with KCRASH_AUTO_RESTARTED=1, and we 0286 // set the defaultCrashHandler (to handle autorestart) after 10s. 0287 // The delay is to see if we stay up for more than 10s time, to avoid infinite 0288 // respawning if the app crashes on startup. 0289 class KCrashDelaySetHandler : public QObject 0290 { 0291 public: 0292 KCrashDelaySetHandler() 0293 { 0294 startTimer(10000); // 10 s 0295 } 0296 0297 protected: 0298 void timerEvent(QTimerEvent *event) override 0299 { 0300 if (!s_crashHandler) { // not set meanwhile 0301 KCrash::setCrashHandler(KCrash::defaultCrashHandler); 0302 } 0303 killTimer(event->timerId()); 0304 this->deleteLater(); 0305 } 0306 }; 0307 0308 void KCrash::setFlags(KCrash::CrashFlags flags) 0309 { 0310 s_flags = flags; 0311 if (s_flags & AutoRestart) { 0312 // We need at least the default crash handler for autorestart to work. 0313 if (!s_crashHandler) { 0314 if (qEnvironmentVariableIsSet("KCRASH_AUTO_RESTARTED")) { 0315 new KCrashDelaySetHandler; 0316 } else { 0317 setCrashHandler(defaultCrashHandler); 0318 } 0319 } 0320 } 0321 } 0322 0323 void KCrash::setApplicationFilePath(const QString &filePath) 0324 { 0325 const int pos = filePath.lastIndexOf(QLatin1Char('/')); 0326 const QString appName = filePath.mid(pos + 1); 0327 const QString appPath = filePath.left(pos); // could be empty, in theory 0328 0329 s_appName.reset(qstrdup(QFile::encodeName(appName).constData())); 0330 s_appPath.reset(qstrdup(QFile::encodeName(appPath).constData())); 0331 0332 // Prepare the auto-restart command 0333 QStringList args = QCoreApplication::arguments(); 0334 if (args.isEmpty()) { // edge case: tst_QX11Info::startupId does QApplication app(argc, nullptr)... 0335 args.append(filePath); 0336 } else { 0337 args[0] = filePath; // replace argv[0] with full path above 0338 } 0339 0340 s_autoRestartCommandLine.resize(args.count()); 0341 for (int i = 0; i < args.count(); ++i) { 0342 s_autoRestartCommandLine.argv[i] = qstrdup(QFile::encodeName(args.at(i)).constData()); 0343 } 0344 } 0345 0346 void KCrash::setDrKonqiEnabled(bool enabled) 0347 { 0348 const int launchDrKonqi = enabled ? 1 : 0; 0349 if (s_launchDrKonqi == launchDrKonqi) { 0350 return; 0351 } 0352 s_launchDrKonqi = launchDrKonqi; 0353 if (s_launchDrKonqi && !s_drkonqiPath) { 0354 #ifdef Q_OS_WINDOWS 0355 static const QString drkonqi = QStringLiteral("drkonqi.exe"); 0356 #else 0357 static const QString drkonqi = QStringLiteral("drkonqi"); 0358 #endif 0359 const QString exec = QStandardPaths::findExecutable(drkonqi, libexecPaths()); 0360 if (exec.isEmpty()) { 0361 qCDebug(LOG_KCRASH) << "Could not find drkonqi in search paths:" << libexecPaths(); 0362 s_launchDrKonqi = 0; 0363 } else { 0364 s_drkonqiPath.reset(qstrdup(qPrintable(exec))); 0365 } 0366 } 0367 0368 // we need at least the default crash handler to launch drkonqi 0369 if (s_launchDrKonqi && !s_crashHandler) { 0370 setCrashHandler(defaultCrashHandler); 0371 } 0372 } 0373 0374 bool KCrash::isDrKonqiEnabled() 0375 { 0376 return s_launchDrKonqi == 1; 0377 } 0378 0379 void KCrash::setCrashHandler(HandlerType handler) 0380 { 0381 #if defined(Q_OS_WIN) 0382 static LPTOP_LEVEL_EXCEPTION_FILTER s_previousExceptionFilter = NULL; 0383 0384 if (handler && !s_previousExceptionFilter) { 0385 s_previousExceptionFilter = SetUnhandledExceptionFilter(KCrash::win32UnhandledExceptionFilter); 0386 } else if (!handler && s_previousExceptionFilter) { 0387 SetUnhandledExceptionFilter(s_previousExceptionFilter); 0388 s_previousExceptionFilter = NULL; 0389 } 0390 #else 0391 if (!handler) { 0392 handler = SIG_DFL; 0393 } 0394 0395 sigset_t mask; 0396 sigemptyset(&mask); 0397 0398 #ifdef SIGSEGV 0399 signal(SIGSEGV, handler); 0400 sigaddset(&mask, SIGSEGV); 0401 #endif 0402 #ifdef SIGBUS 0403 signal(SIGBUS, handler); 0404 sigaddset(&mask, SIGBUS); 0405 #endif 0406 #ifdef SIGFPE 0407 signal(SIGFPE, handler); 0408 sigaddset(&mask, SIGFPE); 0409 #endif 0410 #ifdef SIGILL 0411 signal(SIGILL, handler); 0412 sigaddset(&mask, SIGILL); 0413 #endif 0414 #ifdef SIGABRT 0415 signal(SIGABRT, handler); 0416 sigaddset(&mask, SIGABRT); 0417 #endif 0418 0419 sigprocmask(SIG_UNBLOCK, &mask, nullptr); 0420 #endif 0421 0422 s_crashHandler = handler; 0423 } 0424 0425 KCrash::HandlerType KCrash::crashHandler() 0426 { 0427 return s_crashHandler; 0428 } 0429 0430 #if !defined(Q_OS_WIN) && !defined(Q_OS_OSX) 0431 static void closeAllFDs() 0432 { 0433 // Close all remaining file descriptors except for stdin/stdout/stderr 0434 struct rlimit rlp = {}; 0435 getrlimit(RLIMIT_NOFILE, &rlp); 0436 for (rlim_t i = 3; i < rlp.rlim_cur; i++) { 0437 close(i); 0438 } 0439 } 0440 #endif 0441 0442 void crashOnSigTerm(int sig) 0443 { 0444 Q_UNUSED(sig) 0445 raise(s_originalSignal); 0446 } 0447 0448 void KCrash::defaultCrashHandler(int sig) 0449 { 0450 // WABA: Do NOT use qDebug() in this function because it is much too risky! 0451 // Handle possible recursions 0452 static int crashRecursionCounter = 0; 0453 crashRecursionCounter++; // Nothing before this, please ! 0454 s_originalSignal = sig; 0455 0456 #if !defined(Q_OS_WIN) 0457 signal(SIGALRM, SIG_DFL); 0458 alarm(3); // Kill me... (in case we deadlock in malloc) 0459 #endif 0460 0461 if (crashRecursionCounter < 2) { 0462 if (s_emergencySaveFunction) { 0463 s_emergencySaveFunction(sig); 0464 } 0465 if ((s_flags & AutoRestart) && s_autoRestartCommandLine) { 0466 QThread::sleep(1); 0467 startProcess(s_autoRestartCommandLine.argc, const_cast<const char **>(s_autoRestartCommandLine.argv), false); 0468 } 0469 crashRecursionCounter++; 0470 } 0471 0472 if (crashRecursionCounter < 3) { 0473 // If someone is telling me to stop while I'm already crashing, then I should resume crashing 0474 signal(SIGTERM, &crashOnSigTerm); 0475 0476 // NB: all metadata writing ought to happen before closing FDs to reduce synchronization problems with dbus. 0477 0478 // WARNING: do not forget to increase Metadata::argv's size when adding more potential arguments! 0479 Metadata data(s_drkonqiPath.get()); 0480 #ifdef Q_OS_LINUX 0481 // The ini is required to be scoped here, as opposed to the conditional scope, so its lifetime is the same as 0482 // the regular data instance! 0483 MetadataINIWriter ini(s_metadataPath); 0484 if (ini.isWritable()) { 0485 // Add the canonical exe path so the coredump daemon has more data points to map metadata to journald entry. 0486 ini.add("--exe", s_appFilePath.get(), MetadataWriter::BoolValue::No); 0487 data.setAdditionalWriter(&ini); 0488 } 0489 #endif 0490 0491 const QByteArray platformName = QGuiApplication::platformName().toUtf8(); 0492 if (!platformName.isEmpty()) { 0493 data.add("--platform", platformName.constData()); 0494 } 0495 0496 #if HAVE_X11 0497 if (platformName == QByteArrayLiteral("xcb")) { 0498 // start up on the correct display 0499 char *display = nullptr; 0500 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0501 if (QX11Info::display()) { 0502 display = XDisplayString(QX11Info::display()); 0503 #else 0504 if (auto disp = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->display()) { 0505 display = XDisplayString(disp); 0506 #endif 0507 } else { 0508 display = getenv("DISPLAY"); 0509 } 0510 data.add("--display", display); 0511 } 0512 #endif 0513 0514 data.add("--appname", s_appName ? s_appName.get() : "<unknown>"); 0515 0516 if (loadedByKdeinit) { 0517 data.addBool("--kdeinit"); 0518 } 0519 0520 // only add apppath if it's not NULL 0521 if (s_appPath && s_appPath[0]) { 0522 data.add("--apppath", s_appPath.get()); 0523 } 0524 0525 // signal number -- will never be NULL 0526 char sigtxt[10]; 0527 sprintf(sigtxt, "%d", sig); 0528 data.add("--signal", sigtxt); 0529 0530 char pidtxt[20]; 0531 sprintf(pidtxt, "%lld", QCoreApplication::applicationPid()); 0532 data.add("--pid", pidtxt); 0533 0534 const KAboutData *about = KAboutData::applicationDataPointer(); 0535 if (about) { 0536 if (about->internalVersion()) { 0537 data.add("--appversion", about->internalVersion()); 0538 } 0539 0540 if (about->internalProgramName()) { 0541 data.add("--programname", about->internalProgramName()); 0542 } 0543 0544 if (about->internalBugAddress()) { 0545 data.add("--bugaddress", about->internalBugAddress()); 0546 } 0547 0548 if (about->internalProductName()) { 0549 data.add("--productname", about->internalProductName()); 0550 } 0551 } 0552 0553 if (s_flags & SaferDialog) { 0554 data.addBool("--safer"); 0555 } 0556 0557 if ((s_flags & AutoRestart) && s_autoRestartCommandLine) { 0558 data.addBool("--restarted"); 0559 } 0560 0561 #if defined(Q_OS_WIN) 0562 char threadId[8] = {0}; 0563 sprintf(threadId, "%d", GetCurrentThreadId()); 0564 data.add("--thread", threadId); 0565 #endif 0566 0567 data.close(); 0568 const int argc = data.argc; 0569 const char **argv = data.argv.data(); 0570 0571 #ifndef NDEBUG 0572 fprintf(stderr, "KCrash: crashing... crashRecursionCounter = %d\n", crashRecursionCounter); 0573 fprintf(stderr, 0574 "KCrash: Application Name = %s path = %s pid = %lld\n", 0575 s_appName ? s_appName.get() : "<unknown>", 0576 s_appPath ? s_appPath.get() : "<unknown>", 0577 QCoreApplication::applicationPid()); 0578 fprintf(stderr, "KCrash: Arguments: "); 0579 for (int i = 0; i < s_autoRestartCommandLine.argc; ++i) { 0580 fprintf(stderr, "%s ", s_autoRestartCommandLine.argv[i]); 0581 } 0582 fprintf(stderr, "\n"); 0583 #else 0584 fprintf(stderr, "KCrash: Application '%s' crashing...\n", s_appName ? s_appName.get() : "<unknown>"); 0585 #endif 0586 0587 #if !defined(Q_OS_WIN) && !defined(Q_OS_OSX) 0588 if (!(s_flags & KeepFDs)) { 0589 // This tries to prevent problems where applications fail to release resources that drkonqi might need. 0590 // Specifically this was introduced to ensure that an application that had grabbed the X11 cursor would 0591 // forcefully have it removed upon crash to ensure it is ungrabbed by the time drkonqi makes an appearance. 0592 // This is also the point in time when, for example, dbus services are lost. Closing the socket indicates 0593 // to dbus-daemon that the process has disappeared and it will forcefully reclaim the registered service names. 0594 // 0595 // Once we close our socket we lose potential dbus names and if we were running as a systemd service anchored to a name, 0596 // the daemon may decide to jump at us with a TERM signal. We'll want to have finished the metadata by now and 0597 // be near our tracing/raise(). 0598 closeAllFDs(); 0599 } 0600 #if HAVE_X11 0601 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0602 else if (QX11Info::display()) { 0603 close(ConnectionNumber(QX11Info::display())); 0604 } 0605 #else 0606 else if (auto display = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->display()) { 0607 close(ConnectionNumber(display)); 0608 } 0609 #endif 0610 #endif 0611 #endif 0612 0613 if (s_launchDrKonqi != 1) { 0614 setCrashHandler(nullptr); 0615 #if !defined(Q_OS_WIN) 0616 raise(sig); // dump core, or whatever is the default action for this signal. 0617 #endif 0618 return; 0619 } 0620 0621 startProcess(argc, argv, true); 0622 } 0623 0624 if (crashRecursionCounter < 4) { 0625 fprintf(stderr, "Unable to start Dr. Konqi\n"); 0626 } 0627 0628 if (s_coreConfig->isProcess()) { 0629 fprintf(stderr, "Re-raising signal for core dump handling.\n"); 0630 KCrash::setCrashHandler(nullptr); 0631 raise(sig); 0632 // not getting here 0633 } 0634 0635 _exit(255); 0636 } 0637 0638 #if defined(Q_OS_WIN) 0639 0640 void KCrash::startProcess(int argc, const char *argv[], bool waitAndExit) 0641 { 0642 QString cmdLine; 0643 for (int i = 0; i < argc; ++i) { 0644 cmdLine.append(QLatin1Char('\"')); 0645 cmdLine.append(QFile::decodeName(argv[i])); 0646 cmdLine.append(QStringLiteral("\" ")); 0647 } 0648 0649 PROCESS_INFORMATION procInfo; 0650 STARTUPINFOW startupInfo = 0651 {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}; 0652 0653 bool success = CreateProcess(0, (wchar_t *)cmdLine.utf16(), NULL, NULL, false, CREATE_UNICODE_ENVIRONMENT, NULL, NULL, &startupInfo, &procInfo); 0654 0655 if (success && waitAndExit) { 0656 // wait for child to exit 0657 WaitForSingleObject(procInfo.hProcess, INFINITE); 0658 _exit(253); 0659 } 0660 } 0661 0662 // glue function for calling the unix signal handler from the windows unhandled exception filter 0663 LONG WINAPI KCrash::win32UnhandledExceptionFilter(_EXCEPTION_POINTERS *exceptionInfo) 0664 { 0665 // kdbgwin needs the context inside exceptionInfo because if getting the context after the 0666 // exception happened, it will walk down the stack and will stop at KiUserEventDispatch in 0667 // ntdll.dll, which is supposed to dispatch the exception from kernel mode back to user mode 0668 // so... let's create some shared memory 0669 HANDLE hMapFile = NULL; 0670 hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(CONTEXT), TEXT("Local\\KCrashShared")); 0671 0672 LPCTSTR pBuf = NULL; 0673 pBuf = (LPCTSTR)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(CONTEXT)); 0674 CopyMemory((PVOID)pBuf, exceptionInfo->ContextRecord, sizeof(CONTEXT)); 0675 0676 if (s_crashHandler) { 0677 s_crashHandler(exceptionInfo->ExceptionRecord->ExceptionCode); 0678 } 0679 0680 CloseHandle(hMapFile); 0681 return EXCEPTION_EXECUTE_HANDLER; // allow windows to do the default action (terminate) 0682 } 0683 #else 0684 0685 static pid_t startDirectly(const char *argv[]); 0686 0687 #ifdef Q_OS_LINUX 0688 static int write_socket(int sock, char *buffer, int len); 0689 static int read_socket(int sock, char *buffer, int len); 0690 0691 static int openDrKonqiSocket(const QByteArray &socketpath); 0692 static int pollDrKonqiSocket(pid_t pid, int sockfd); 0693 #endif 0694 0695 void KCrash::startProcess(int argc, const char *argv[], bool waitAndExit) 0696 { 0697 Q_UNUSED(argc); 0698 fprintf(stderr, "KCrash: Attempting to start %s\n", argv[0]); 0699 0700 pid_t pid = startDirectly(argv); 0701 0702 if (pid > 0 && waitAndExit) { 0703 // Seems we made it.... 0704 alarm(0); // Stop the pending alarm that was set at the top of the defaultCrashHandler 0705 0706 bool running = true; 0707 // Wait forever until the started process exits. This code path is executed 0708 // when launching drkonqi. Note that DrKonqi will SIGSTOP this process in the meantime 0709 // and only send SIGCONT when it is about to attach a debugger. 0710 #ifdef Q_OS_LINUX 0711 // Declare the process that will be debugging the crashed KDE app (#245529). 0712 // For now that will be DrKonqi, which may ask to transfer the ptrace scope to 0713 // a debugger it is not an ancestor of (because it was started via kdeinit or 0714 // KProcess::startDetached()) using a socket. 0715 #ifndef PR_SET_PTRACER 0716 #define PR_SET_PTRACER 0x59616d61 0717 #endif 0718 prctl(PR_SET_PTRACER, pid, 0, 0, 0); 0719 0720 int sockfd = openDrKonqiSocket(s_socketpath); 0721 0722 if (sockfd >= 0) { 0723 // Wait while DrKonqi is running and the socket connection exists 0724 // If the process was started directly, use waitpid(), as it's a child... 0725 while ((running = waitpid(pid, nullptr, WNOHANG) != pid) && pollDrKonqiSocket(pid, sockfd) >= 0) { } 0726 close(sockfd); 0727 unlink(s_socketpath.constData()); 0728 } 0729 #endif 0730 if (running) { 0731 // If the process was started directly, use waitpid(), as it's a child... 0732 while (waitpid(pid, nullptr, 0) != pid) { } 0733 } 0734 if (!s_coreConfig->isProcess()) { 0735 // Only exit if we don't forward to core dumps 0736 _exit(253); 0737 } 0738 } 0739 } 0740 0741 extern "C" char **environ; 0742 static pid_t startDirectly(const char *argv[]) 0743 { 0744 char **environ_end; 0745 for (environ_end = environ; *environ_end; ++environ_end) { } 0746 0747 std::array<const char *, 1024> environ_data; // hope it's big enough 0748 if ((unsigned)(environ_end - environ) + 2 >= environ_data.size()) { 0749 fprintf(stderr, "environ_data in KCrash not big enough!\n"); 0750 return 0; 0751 } 0752 auto end = std::copy_if(environ, environ_end, environ_data.begin(), [](const char *s) { 0753 static const char envvar[] = "KCRASH_AUTO_RESTARTED="; 0754 return strncmp(envvar, s, sizeof(envvar) - 1) != 0; 0755 }); 0756 *end++ = "KCRASH_AUTO_RESTARTED=1"; 0757 *end++ = nullptr; 0758 pid_t pid = fork(); 0759 switch (pid) { 0760 case -1: 0761 fprintf(stderr, "KCrash failed to fork(), errno = %d\n", errno); 0762 return 0; 0763 case 0: 0764 setgroups(0, nullptr); // Remove any extraneous groups 0765 if (setgid(getgid()) < 0 || setuid(getuid()) < 0) { 0766 _exit(253); // This cannot happen. Theoretically. 0767 } 0768 #ifndef Q_OS_OSX 0769 closeAllFDs(); // We are in the child now. Close FDs unconditionally. 0770 #endif 0771 execve(argv[0], const_cast<char **>(argv), const_cast<char **>(environ_data.data())); 0772 fprintf(stderr, "KCrash failed to exec(), errno = %d\n", errno); 0773 _exit(253); 0774 default: 0775 return pid; 0776 } 0777 } 0778 0779 #ifdef Q_OS_LINUX 0780 0781 /* 0782 * Write 'len' bytes from 'buffer' into 'sock'. 0783 * returns 0 on success, -1 on failure. 0784 */ 0785 static int write_socket(int sock, char *buffer, int len) 0786 { 0787 ssize_t result; 0788 int bytes_left = len; 0789 while (bytes_left > 0) { 0790 result = write(sock, buffer, bytes_left); 0791 if (result > 0) { 0792 buffer += result; 0793 bytes_left -= result; 0794 } else if (result == 0) { 0795 return -1; 0796 } else if ((result == -1) && (errno != EINTR) && (errno != EAGAIN)) { 0797 return -1; 0798 } 0799 } 0800 return 0; 0801 } 0802 0803 /* 0804 * Read 'len' bytes from 'sock' into 'buffer'. 0805 * returns 0 on success, -1 on failure. 0806 */ 0807 static int read_socket(int sock, char *buffer, int len) 0808 { 0809 ssize_t result; 0810 int bytes_left = len; 0811 while (bytes_left > 0) { 0812 result = read(sock, buffer, bytes_left); 0813 if (result > 0) { 0814 buffer += result; 0815 bytes_left -= result; 0816 } else if (result == 0) { 0817 return -1; 0818 } else if ((result == -1) && (errno != EINTR) && (errno != EAGAIN)) { 0819 return -1; 0820 } 0821 } 0822 return 0; 0823 } 0824 0825 static int openDrKonqiSocket(const QByteArray &socketpath) 0826 { 0827 int sockfd = socket(PF_UNIX, SOCK_STREAM, 0); 0828 if (sockfd < 0) { 0829 perror("Warning: socket() for communication with DrKonqi failed"); 0830 return -1; 0831 } 0832 0833 struct sockaddr_un drkonqi_server; 0834 drkonqi_server.sun_family = AF_UNIX; 0835 0836 if (socketpath.size() >= static_cast<int>(sizeof(drkonqi_server.sun_path))) { 0837 fprintf(stderr, "Warning: socket path is too long\n"); 0838 close(sockfd); 0839 return -1; 0840 } 0841 strcpy(drkonqi_server.sun_path, socketpath.constData()); 0842 0843 unlink(drkonqi_server.sun_path); // remove potential stale socket 0844 if (bind(sockfd, (struct sockaddr *)&drkonqi_server, sizeof(drkonqi_server)) < 0) { 0845 perror("Warning: bind() for communication with DrKonqi failed"); 0846 close(sockfd); 0847 unlink(drkonqi_server.sun_path); 0848 return -1; 0849 } 0850 0851 listen(sockfd, 1); 0852 0853 return sockfd; 0854 } 0855 0856 static int pollDrKonqiSocket(pid_t pid, int sockfd) 0857 { 0858 struct pollfd fd; 0859 fd.fd = sockfd; 0860 fd.events = POLLIN; 0861 int r; 0862 do { 0863 r = poll(&fd, 1, 1000); // wait for 1 second for a request by DrKonqi 0864 } while (r == -1 && errno == EINTR); 0865 // only continue if POLLIN event returned 0866 if (r == 0) { // timeout 0867 return 0; 0868 } else if (r == -1 || !(fd.revents & POLLIN)) { // some error 0869 return -1; 0870 } 0871 0872 static struct sockaddr_un drkonqi_client; 0873 static socklen_t cllength = sizeof(drkonqi_client); 0874 int clsockfd; 0875 do { 0876 clsockfd = accept(sockfd, (struct sockaddr *)&drkonqi_client, &cllength); 0877 } while (clsockfd == -1 && errno == EINTR); 0878 if (clsockfd < 0) { 0879 return -1; 0880 } 0881 0882 // check whether the message is coming from DrKonqi 0883 static struct ucred ucred; 0884 static socklen_t credlen = sizeof(struct ucred); 0885 if (getsockopt(clsockfd, SOL_SOCKET, SO_PEERCRED, &ucred, &credlen) < 0) { 0886 return -1; 0887 } 0888 0889 if (ucred.pid != pid) { 0890 fprintf(stderr, "Warning: peer pid does not match DrKonqi pid\n"); 0891 return -1; 0892 } 0893 0894 // read PID to change ptrace scope 0895 static const int msize = 21; // most digits in a 64bit int (+sign +'\0') 0896 char msg[msize]; 0897 if (read_socket(clsockfd, msg, msize) == 0) { 0898 int dpid = atoi(msg); 0899 prctl(PR_SET_PTRACER, dpid, 0, 0, 0); 0900 // confirm change to DrKonqi 0901 if (write_socket(clsockfd, msg, msize) == 0) { 0902 fprintf(stderr, "KCrash: ptrace access transferred to %s\n", msg); 0903 } 0904 } 0905 close(clsockfd); 0906 0907 return 1; 0908 } 0909 0910 #endif 0911 0912 #endif // Q_OS_UNIX 0913 0914 void KCrash::setErrorMessage(const QString &message) 0915 { 0916 s_kcrashErrorMessage.reset(qstrdup(message.toUtf8().constData())); 0917 }