File indexing completed on 2024-04-21 05:26:42
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 QString AbstractDrKonqiBackend::metadataPath() 0054 { 0055 static QString path = [] { 0056 const QString envPath = qEnvironmentVariable("DRKONQI_METADATA_FILE"); 0057 qunsetenv("DRKONQI_METADATA_FILE"); 0058 Q_ASSERT(!envPath.isEmpty()); 0059 return envPath; 0060 }(); 0061 return path; 0062 } 0063 0064 KCrashBackend::~KCrashBackend() 0065 { 0066 continueAttachedProcess(); 0067 } 0068 0069 bool KCrashBackend::init() 0070 { 0071 AbstractDrKonqiBackend::init(); 0072 0073 // check whether the attached process exists and whether we have permissions to inspect it 0074 if (crashedApplication()->pid() <= 0) { 0075 qCWarning(DRKONQI_LOG) << "Invalid pid specified"; 0076 return false; 0077 } 0078 0079 #if !defined(Q_OS_WIN32) 0080 if (::kill(crashedApplication()->pid(), 0) < 0) { 0081 switch (errno) { 0082 case EPERM: 0083 qCWarning(DRKONQI_LOG) << "DrKonqi doesn't have permissions to inspect the specified process"; 0084 break; 0085 case ESRCH: 0086 qCWarning(DRKONQI_LOG) << "The specified process does not exist."; 0087 break; 0088 default: 0089 break; 0090 } 0091 return false; 0092 } 0093 0094 //--keeprunning means: generate backtrace instantly and let the process continue execution 0095 if (DrKonqi::isKeepRunning()) { 0096 stopAttachedProcess(); 0097 debuggerManager()->backtraceGenerator()->start(); 0098 connect(debuggerManager(), &DebuggerManager::debuggerFinished, this, &KCrashBackend::continueAttachedProcess); 0099 } else { 0100 connect(debuggerManager(), &DebuggerManager::debuggerStarting, this, &KCrashBackend::onDebuggerStarting); 0101 connect(debuggerManager(), &DebuggerManager::debuggerFinished, this, &KCrashBackend::onDebuggerFinished); 0102 0103 // stop the process to avoid high cpu usage by other threads (bug 175362). 0104 // if the process was started by kdeinit, we need to wait a bit for KCrash 0105 // to reach the alarm(0); call in kdeui/util/kcrash.cpp line 406 or else 0106 // if we stop it before this call, pending alarm signals will kill the 0107 // process when we try to continue it. 0108 QTimer::singleShot(2s, this, &KCrashBackend::stopAttachedProcess); 0109 } 0110 #endif 0111 0112 // Handle drkonqi crashes 0113 s_pid = crashedApplication()->pid(); // copy pid for use by the crash handler, so that it is safer 0114 KCrash::setEmergencySaveFunction(emergencySaveFunction); 0115 0116 return true; 0117 } 0118 0119 CrashedApplication *KCrashBackend::constructCrashedApplication() 0120 { 0121 const auto pid = DrKonqi::pid(); 0122 QFileInfo executable; 0123 QString fakeBaseName; 0124 bool hasDeletedFiles = false; 0125 0126 // try to determine the executable that crashed 0127 const QString procPath(QStringLiteral("/proc/%1").arg(pid)); 0128 const QString exeProcPath(procPath + QStringLiteral("/exe")); 0129 if (QFileInfo::exists(exeProcPath)) { 0130 // on linux, the fastest and most reliable way is to get the path from /proc 0131 qCDebug(DRKONQI_LOG) << "Using /proc to determine executable path"; 0132 const QString exePath = QFile::symLinkTarget(exeProcPath); 0133 0134 executable.setFile(exePath); 0135 if (DrKonqi::isKdeinit() || executable.fileName().startsWith(QLatin1String("python"))) { 0136 fakeBaseName = DrKonqi::appName(); 0137 } 0138 0139 const QString mapsPath = procPath + QStringLiteral("/maps"); 0140 QFile mapsFile(mapsPath); 0141 if (mapsFile.open(QFile::ReadOnly)) { 0142 hasDeletedFiles = LinuxProc::hasMapsDeletedFiles(exePath, mapsFile.readAll(), LinuxProc::Check::DeletedMarker); 0143 } else { 0144 qCWarning(DRKONQI_LOG) << "failed to open maps file" << mapsPath; 0145 } 0146 0147 qCDebug(DRKONQI_LOG) << "exe" << exePath << "has deleted files:" << hasDeletedFiles; 0148 } else { 0149 if (DrKonqi::isKdeinit()) { 0150 executable = QFileInfo(QStandardPaths::findExecutable(QStringLiteral("kdeinit5"))); 0151 fakeBaseName = DrKonqi::appName(); 0152 } else { 0153 QFileInfo execPath(DrKonqi::appName()); 0154 if (execPath.isAbsolute()) { 0155 executable = execPath; 0156 } else if (!DrKonqi::appPath().isEmpty()) { 0157 QDir execDir(DrKonqi::appPath()); 0158 executable = QFileInfo(execDir.absoluteFilePath(execPath.fileName())); 0159 } else { 0160 executable = QFileInfo(QStandardPaths::findExecutable(execPath.fileName())); 0161 } 0162 } 0163 } 0164 0165 qCDebug(DRKONQI_LOG) << "Executable is:" << executable.absoluteFilePath(); 0166 qCDebug(DRKONQI_LOG) << "Executable exists:" << executable.exists(); 0167 0168 return new CrashedApplication(pid, 0169 DrKonqi::thread(), 0170 DrKonqi::signal(), 0171 executable, 0172 DrKonqi::appVersion(), 0173 BugReportAddress(DrKonqi::bugAddress()), 0174 DrKonqi::programName(), 0175 DrKonqi::productName(), 0176 QDateTime::currentDateTime(), 0177 DrKonqi::isRestarted(), 0178 hasDeletedFiles, 0179 fakeBaseName, 0180 this); 0181 } 0182 0183 DebuggerManager *KCrashBackend::constructDebuggerManager() 0184 { 0185 KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("DrKonqi")); 0186 #if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED > 1070 0187 QString defaultDebuggerName = config.readEntry("Debugger", QStringLiteral("lldb")); 0188 #elif !defined(Q_OS_WIN) 0189 QString defaultDebuggerName = config.readEntry("Debugger", QStringLiteral("gdb")); 0190 #else 0191 QString defaultDebuggerName = config.readEntry("Debugger", QStringLiteral("cdb")); 0192 #endif 0193 0194 const QList<Debugger> internalDebuggers = Debugger::availableInternalDebuggers(QStringLiteral("KCrash")); 0195 const QList<Debugger> externalDebuggers = Debugger::availableExternalDebuggers(QStringLiteral("KCrash")); 0196 0197 const Debugger preferredDebugger(Debugger::findDebugger(internalDebuggers, defaultDebuggerName)); 0198 qCDebug(DRKONQI_LOG) << "Using debugger:" << preferredDebugger.codeName(); 0199 return new DebuggerManager(preferredDebugger, externalDebuggers, this); 0200 } 0201 0202 void KCrashBackend::stopAttachedProcess() 0203 { 0204 if (m_state == ProcessRunning) { 0205 qCDebug(DRKONQI_LOG) << "Sending SIGSTOP to process"; 0206 ::kill(crashedApplication()->pid(), SIGSTOP); 0207 m_state = ProcessStopped; 0208 } 0209 } 0210 0211 void KCrashBackend::continueAttachedProcess() 0212 { 0213 if (m_state == ProcessStopped) { 0214 qCDebug(DRKONQI_LOG) << "Sending SIGCONT to process"; 0215 ::kill(crashedApplication()->pid(), SIGCONT); 0216 m_state = ProcessRunning; 0217 } 0218 } 0219 0220 void KCrashBackend::onDebuggerStarting() 0221 { 0222 continueAttachedProcess(); 0223 m_state = DebuggerRunning; 0224 } 0225 0226 void KCrashBackend::onDebuggerFinished() 0227 { 0228 m_state = ProcessRunning; 0229 stopAttachedProcess(); 0230 } 0231 0232 // static 0233 qint64 KCrashBackend::s_pid = 0; 0234 0235 // static 0236 void KCrashBackend::emergencySaveFunction(int signal) 0237 { 0238 // In case drkonqi itself crashes, we need to get rid of the process being debugged, 0239 // so we kill it, no matter what its state was. 0240 Q_UNUSED(signal); 0241 ::kill(s_pid, SIGKILL); 0242 } 0243 0244 #include "moc_drkonqibackends.cpp"