diff --git a/src/backtracewidget.cpp b/src/backtracewidget.cpp --- a/src/backtracewidget.cpp +++ b/src/backtracewidget.cpp @@ -76,6 +76,12 @@ "install the missing debug symbols packages."))); ui.m_installDebugButton->setVisible(false); connect(ui.m_installDebugButton, &QPushButton::clicked, this, &BacktraceWidget::installDebugPackages); + if (DrKonqi::crashedApplication()->hasDeletedFiles()) { + ui.m_installDebugButton->setEnabled(false); + ui.m_installDebugButton->setToolTip(i18nc("@info:tooltip", + "Symbol installation is unavailable because the application " + "was updated or uninstalled after it had been started.")); + } KGuiItem::assign(ui.m_copyButton, KGuiItem2(QString(), QIcon::fromTheme(QStringLiteral("edit-copy")), i18nc("@info:tooltip", "Use this button to copy the " diff --git a/src/crashedapplication.h b/src/crashedapplication.h --- a/src/crashedapplication.h +++ b/src/crashedapplication.h @@ -66,6 +66,9 @@ const QDateTime& datetime() const; + /** @returns whether mmap'd files have been deleted, e.g. updated since start of app */ + bool hasDeletedFiles() const; + public Q_SLOTS: void restart(); @@ -86,6 +89,7 @@ bool m_restarted; int m_thread; QDateTime m_datetime; + bool m_hasDeletedFiles; }; QString getSuggestedKCrashFilename(const CrashedApplication* app); diff --git a/src/crashedapplication.cpp b/src/crashedapplication.cpp --- a/src/crashedapplication.cpp +++ b/src/crashedapplication.cpp @@ -146,6 +146,11 @@ return m_datetime; } +bool CrashedApplication::hasDeletedFiles() const +{ + return m_hasDeletedFiles; +} + void CrashedApplication::restart() { if (m_restarted) { diff --git a/src/drkonqibackends.cpp b/src/drkonqibackends.cpp --- a/src/drkonqibackends.cpp +++ b/src/drkonqibackends.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -125,16 +126,52 @@ a->m_thread = DrKonqi::thread(); //try to determine the executable that crashed - if ( QFileInfo(QStringLiteral("/proc/%1/exe").arg(a->m_pid)).exists() ) { + const QString procPath(QStringLiteral("/proc/%1").arg(a->m_pid)); + const QString exeProcPath(procPath + QStringLiteral("/exe")); + if (QFileInfo(exeProcPath).exists()) { //on linux, the fastest and most reliable way is to get the path from /proc qCDebug(DRKONQI_LOG) << "Using /proc to determine executable path"; - a->m_executable.setFile(QFile::symLinkTarget(QStringLiteral("/proc/%1/exe").arg(a->m_pid))); + const QString exePath = QFile::symLinkTarget(exeProcPath); + a->m_executable.setFile(exePath); if (DrKonqi::isKdeinit() || a->m_executable.fileName().startsWith(QLatin1String("python")) ) { - a->m_fakeBaseName = DrKonqi::appName(); } + + QDir mapFilesDir(procPath + QStringLiteral("/map_files")); + mapFilesDir.setFilter(mapFilesDir.filter() | QDir::System); // proc is system! + + // "/bin/foo (deleted)" is how the kernel tells us that a file has been deleted since + // it was mmap'd. + QRegularExpression expression(QStringLiteral("(?.+) \\(deleted\\)$")); + // For the map_files we filter only .so files to ensure that + // we don't trip over cache files or the like, as a result we + // manually need to check if the main exe was deleted and add + // it. + // NB: includes .so* and .py* since we also implicitly support snakes to + // a degree + QRegularExpression soExpression(QStringLiteral("(?.+\\.(so|py)([^/]*)) \\(deleted\\)$")); + + bool hasDeletedFiles = false; + + const auto exeMatch = expression.match(exePath); + if (exeMatch.isValid() && exeMatch.hasMatch()) { + hasDeletedFiles = true; + } + + const auto list = mapFilesDir.entryInfoList(); + for (auto it = list.constBegin(); !hasDeletedFiles && it != list.constEnd(); ++it) { + const auto match = soExpression.match(it->symLinkTarget()); + if (!match.isValid() || !match.hasMatch()) { + continue; + } + hasDeletedFiles = true; + } + + a->m_hasDeletedFiles = hasDeletedFiles; + + qCDebug(DRKONQI_LOG) << "exe" << exePath << "has deleted files:" << hasDeletedFiles; } else { if ( DrKonqi::isKdeinit() ) { a->m_executable = QFileInfo(QStandardPaths::findExecutable(QStringLiteral("kdeinit5"))); diff --git a/src/drkonqidialog.cpp b/src/drkonqidialog.cpp --- a/src/drkonqidialog.cpp +++ b/src/drkonqidialog.cpp @@ -128,6 +128,14 @@ "(including the backtrace from the " "Developer Information " "tab.)", crashedApp->bugReportAddress()); + } else if (crashedApp->hasDeletedFiles()) { + reportMessage = xi18nc("@info", "The reporting assistant is disabled because " + "the crashed application appears to have been updated or " + "uninstalled since it had been started. This prevents accurate " + "crash reporting and can also be the cause of this crash." + "After updating it is always a good idea to log out and back " + "in to make sure the update is fully applied and will not cause " + "any side effects."); } else { reportMessage = xi18nc("@info", "You can help us improve KDE Software by reporting " "this error.Learn " @@ -209,7 +217,7 @@ reportButton->setEnabled(!crashedApp->bugReportAddress().isEmpty() && crashedApp->fakeExecutableBaseName() != QLatin1String("drkonqi") && - !DrKonqi::isSafer()); + !DrKonqi::isSafer() && !crashedApp->hasDeletedFiles()); connect(reportButton, &QPushButton::clicked, this, &DrKonqiDialog::startBugReportAssistant); //Restart application button