Warning, file /plasma/drkonqi/src/drkonqibackends.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 SPDX-FileCopyrightText: 2009 George Kiagiadakis <gkiagia@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 #include "drkonqibackends.h" 0008 0009 #include <cerrno> 0010 #include <chrono> 0011 #include <cstdlib> 0012 #include <signal.h> 0013 #include <sys/types.h> 0014 0015 #include <QDir> 0016 #include <QRegularExpression> 0017 #include <QTimer> 0018 0019 #include "drkonqi_debug.h" 0020 #include <KConfig> 0021 #include <KConfigGroup> 0022 #include <KCrash> 0023 #include <QStandardPaths> 0024 0025 #include "backtracegenerator.h" 0026 #include "coredump/metadata.h" 0027 #include "crashedapplication.h" 0028 #include "debugger.h" 0029 #include "debuggermanager.h" 0030 #include "drkonqi.h" 0031 #include "linuxprocmapsparser.h" 0032 0033 #ifdef Q_OS_MACOS 0034 #include <AvailabilityMacros.h> 0035 #endif 0036 0037 using namespace std::chrono_literals; 0038 0039 AbstractDrKonqiBackend::~AbstractDrKonqiBackend() = default; 0040 0041 bool AbstractDrKonqiBackend::init() 0042 { 0043 m_crashedApplication = constructCrashedApplication(); 0044 m_debuggerManager = constructDebuggerManager(); 0045 return true; 0046 } 0047 0048 void AbstractDrKonqiBackend::prepareForDebugger() 0049 { 0050 Q_EMIT preparedForDebugger(); 0051 } 0052 0053 void AbstractDrKonqiBackend::cleanup() 0054 { 0055 // Cleanup of a potential metadata. 0056 // NOTE: This MUST be run regardless of the backend. If drkonqi has not crashed or quit because of a session logout 0057 // we must clean the metadata. Otherwise the coredump helper may produce a second drkonqi instance. 0058 // Lack of a metadata file indicates that drkonqi had been run on the crash already. 0059 qCDebug(DRKONQI_LOG) << "Cleaning up" << metadataPath(); 0060 if (!metadataPath().isEmpty()) { 0061 QFile::remove(metadataPath()); 0062 } 0063 } 0064 0065 static QString resolveMetadataPath() 0066 { 0067 const QString envPath = qEnvironmentVariable("DRKONQI_METADATA_FILE"); 0068 if (!envPath.isEmpty()) { 0069 qunsetenv("DRKONQI_METADATA_FILE"); 0070 return envPath; 0071 } 0072 return Metadata::resolveMetadataPath(DrKonqi::pid()); // resolve manually 0073 } 0074 0075 QString AbstractDrKonqiBackend::metadataPath() 0076 { 0077 static QString path = resolveMetadataPath(); 0078 return path; 0079 } 0080 0081 KCrashBackend::~KCrashBackend() 0082 { 0083 continueAttachedProcess(); 0084 } 0085 0086 bool KCrashBackend::init() 0087 { 0088 AbstractDrKonqiBackend::init(); 0089 0090 // check whether the attached process exists and whether we have permissions to inspect it 0091 if (crashedApplication()->pid() <= 0) { 0092 qCWarning(DRKONQI_LOG) << "Invalid pid specified"; 0093 return false; 0094 } 0095 0096 #if !defined(Q_OS_WIN32) 0097 if (::kill(crashedApplication()->pid(), 0) < 0) { 0098 switch (errno) { 0099 case EPERM: 0100 qCWarning(DRKONQI_LOG) << "DrKonqi doesn't have permissions to inspect the specified process"; 0101 break; 0102 case ESRCH: 0103 qCWarning(DRKONQI_LOG) << "The specified process does not exist."; 0104 break; 0105 default: 0106 break; 0107 } 0108 return false; 0109 } 0110 0111 //--keeprunning means: generate backtrace instantly and let the process continue execution 0112 if (DrKonqi::isKeepRunning()) { 0113 stopAttachedProcess(); 0114 debuggerManager()->backtraceGenerator()->start(); 0115 connect(debuggerManager(), &DebuggerManager::debuggerFinished, this, &KCrashBackend::continueAttachedProcess); 0116 } else { 0117 connect(debuggerManager(), &DebuggerManager::debuggerStarting, this, &KCrashBackend::onDebuggerStarting); 0118 connect(debuggerManager(), &DebuggerManager::debuggerFinished, this, &KCrashBackend::onDebuggerFinished); 0119 0120 // stop the process to avoid high cpu usage by other threads (bug 175362). 0121 // if the process was started by kdeinit, we need to wait a bit for KCrash 0122 // to reach the alarm(0); call in kdeui/util/kcrash.cpp line 406 or else 0123 // if we stop it before this call, pending alarm signals will kill the 0124 // process when we try to continue it. 0125 QTimer::singleShot(2s, this, &KCrashBackend::stopAttachedProcess); 0126 } 0127 #endif 0128 0129 // Handle drkonqi crashes 0130 s_pid = crashedApplication()->pid(); // copy pid for use by the crash handler, so that it is safer 0131 KCrash::setEmergencySaveFunction(emergencySaveFunction); 0132 0133 return true; 0134 } 0135 0136 CrashedApplication *KCrashBackend::constructCrashedApplication() 0137 { 0138 const auto pid = DrKonqi::pid(); 0139 QFileInfo executable; 0140 QString fakeBaseName; 0141 bool hasDeletedFiles = false; 0142 0143 // try to determine the executable that crashed 0144 const QString procPath(QStringLiteral("/proc/%1").arg(pid)); 0145 const QString exeProcPath(procPath + QStringLiteral("/exe")); 0146 if (QFileInfo::exists(exeProcPath)) { 0147 // on linux, the fastest and most reliable way is to get the path from /proc 0148 qCDebug(DRKONQI_LOG) << "Using /proc to determine executable path"; 0149 const QString exePath = QFile::symLinkTarget(exeProcPath); 0150 0151 executable.setFile(exePath); 0152 if (DrKonqi::isKdeinit() || executable.fileName().startsWith(QLatin1String("python"))) { 0153 fakeBaseName = DrKonqi::appName(); 0154 } 0155 0156 const QString mapsPath = procPath + QStringLiteral("/maps"); 0157 QFile mapsFile(mapsPath); 0158 if (mapsFile.open(QFile::ReadOnly)) { 0159 hasDeletedFiles = LinuxProc::hasMapsDeletedFiles(exePath, mapsFile.readAll(), LinuxProc::Check::DeletedMarker); 0160 } else { 0161 qCWarning(DRKONQI_LOG) << "failed to open maps file" << mapsPath; 0162 } 0163 0164 qCDebug(DRKONQI_LOG) << "exe" << exePath << "has deleted files:" << hasDeletedFiles; 0165 } else { 0166 if (DrKonqi::isKdeinit()) { 0167 executable = QFileInfo(QStandardPaths::findExecutable(QStringLiteral("kdeinit5"))); 0168 fakeBaseName = DrKonqi::appName(); 0169 } else { 0170 QFileInfo execPath(DrKonqi::appName()); 0171 if (execPath.isAbsolute()) { 0172 executable = execPath; 0173 } else if (!DrKonqi::appPath().isEmpty()) { 0174 QDir execDir(DrKonqi::appPath()); 0175 executable = QFileInfo(execDir.absoluteFilePath(execPath.fileName())); 0176 } else { 0177 executable = QFileInfo(QStandardPaths::findExecutable(execPath.fileName())); 0178 } 0179 } 0180 } 0181 0182 qCDebug(DRKONQI_LOG) << "Executable is:" << executable.absoluteFilePath(); 0183 qCDebug(DRKONQI_LOG) << "Executable exists:" << executable.exists(); 0184 0185 return new CrashedApplication(pid, 0186 DrKonqi::thread(), 0187 DrKonqi::signal(), 0188 executable, 0189 DrKonqi::appVersion(), 0190 BugReportAddress(DrKonqi::bugAddress()), 0191 DrKonqi::programName(), 0192 DrKonqi::productName(), 0193 QDateTime::currentDateTime(), 0194 DrKonqi::isRestarted(), 0195 hasDeletedFiles, 0196 fakeBaseName, 0197 this); 0198 } 0199 0200 DebuggerManager *KCrashBackend::constructDebuggerManager() 0201 { 0202 KConfigGroup config(KSharedConfig::openConfig(), "DrKonqi"); 0203 #if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED > 1070 0204 QString defaultDebuggerName = config.readEntry("Debugger", QStringLiteral("lldb")); 0205 #elif !defined(Q_OS_WIN) 0206 QString defaultDebuggerName = config.readEntry("Debugger", QStringLiteral("gdb")); 0207 #else 0208 QString defaultDebuggerName = config.readEntry("Debugger", QStringLiteral("cdb")); 0209 #endif 0210 0211 const QList<Debugger> internalDebuggers = Debugger::availableInternalDebuggers(QStringLiteral("KCrash")); 0212 const QList<Debugger> externalDebuggers = Debugger::availableExternalDebuggers(QStringLiteral("KCrash")); 0213 0214 const Debugger preferredDebugger(Debugger::findDebugger(internalDebuggers, defaultDebuggerName)); 0215 qCDebug(DRKONQI_LOG) << "Using debugger:" << preferredDebugger.codeName(); 0216 return new DebuggerManager(preferredDebugger, externalDebuggers, this); 0217 } 0218 0219 void KCrashBackend::stopAttachedProcess() 0220 { 0221 if (m_state == ProcessRunning) { 0222 qCDebug(DRKONQI_LOG) << "Sending SIGSTOP to process"; 0223 ::kill(crashedApplication()->pid(), SIGSTOP); 0224 m_state = ProcessStopped; 0225 } 0226 } 0227 0228 void KCrashBackend::continueAttachedProcess() 0229 { 0230 if (m_state == ProcessStopped) { 0231 qCDebug(DRKONQI_LOG) << "Sending SIGCONT to process"; 0232 ::kill(crashedApplication()->pid(), SIGCONT); 0233 m_state = ProcessRunning; 0234 } 0235 } 0236 0237 void KCrashBackend::onDebuggerStarting() 0238 { 0239 continueAttachedProcess(); 0240 m_state = DebuggerRunning; 0241 } 0242 0243 void KCrashBackend::onDebuggerFinished() 0244 { 0245 m_state = ProcessRunning; 0246 stopAttachedProcess(); 0247 } 0248 0249 // static 0250 qint64 KCrashBackend::s_pid = 0; 0251 0252 // static 0253 void KCrashBackend::emergencySaveFunction(int signal) 0254 { 0255 // In case drkonqi itself crashes, we need to get rid of the process being debugged, 0256 // so we kill it, no matter what its state was. 0257 Q_UNUSED(signal); 0258 ::kill(s_pid, SIGKILL); 0259 }