From cba6b2f155ccc8befe62b74eb2b7497e654b0489 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Fri, 20 Sep 2024 14:27:44 +0200 Subject: [PATCH] Debugger: Add option to attach to last core (Linux) Fixes: QTCREATORBUG-29256 Change-Id: I52e77eb6c5fe3b4c959d906a62495c4bb7344d88 Reviewed-by: hjk --- src/plugins/debugger/CMakeLists.txt | 1 + src/plugins/debugger/debuggerplugin.cpp | 37 ++++++++ src/plugins/debugger/shared/coredumputils.cpp | 87 +++++++++++++++++++ src/plugins/debugger/shared/coredumputils.h | 21 +++++ 4 files changed, 146 insertions(+) create mode 100644 src/plugins/debugger/shared/coredumputils.cpp create mode 100644 src/plugins/debugger/shared/coredumputils.h diff --git a/src/plugins/debugger/CMakeLists.txt b/src/plugins/debugger/CMakeLists.txt index fc405a7fe3a..82267c18f7f 100644 --- a/src/plugins/debugger/CMakeLists.txt +++ b/src/plugins/debugger/CMakeLists.txt @@ -80,6 +80,7 @@ add_qtc_plugin(Debugger qml/qmlv8debuggerclientconstants.h registerhandler.cpp registerhandler.h shared/cdbsymbolpathlisteditor.cpp shared/cdbsymbolpathlisteditor.h + shared/coredumputils.cpp shared/coredumputils.h shared/hostutils.cpp shared/hostutils.h shared/peutils.cpp shared/peutils.h shared/symbolpathsdialog.cpp shared/symbolpathsdialog.h diff --git a/src/plugins/debugger/debuggerplugin.cpp b/src/plugins/debugger/debuggerplugin.cpp index 9fa2890b757..315bf3dc7ff 100644 --- a/src/plugins/debugger/debuggerplugin.cpp +++ b/src/plugins/debugger/debuggerplugin.cpp @@ -21,6 +21,7 @@ #include "unstartedappwatcherdialog.h" #include "loadcoredialog.h" #include "sourceutils.h" +#include "shared/coredumputils.h" #include "shared/hostutils.h" #include "console/console.h" @@ -109,6 +110,7 @@ #include #include #include +#include #include #include #include @@ -634,6 +636,7 @@ public: void attachToQmlPort(); void runScheduled(); void attachCore(); + void attachToLastCore(); void reloadDebuggingHelpers(); void remoteCommand(const QStringList &options); @@ -671,6 +674,7 @@ public: QAction m_attachToRemoteServerAction{Tr::tr("Attach to Running Debug Server...")}; QAction m_startRemoteCdbAction{Tr::tr("Attach to Remote CDB Session...")}; QAction m_attachToCoreAction{Tr::tr("Load Core File...")}; + QAction m_attachToLastCoreAction{Tr::tr("Load Last Core File")}; // In the Debug menu. QAction m_startAndBreakOnMain{Tr::tr("Start and Break on Main")}; @@ -872,6 +876,9 @@ DebuggerPluginPrivate::DebuggerPluginPrivate(const QStringList &arguments) connect(&m_attachToCoreAction, &QAction::triggered, this, &DebuggerPluginPrivate::attachCore); + connect(&m_attachToLastCoreAction, &QAction::triggered, + this, &DebuggerPluginPrivate::attachToLastCore); + connect(&m_attachToRemoteServerAction, &QAction::triggered, this, &StartApplicationDialog::attachToRemoteServer); @@ -947,6 +954,11 @@ DebuggerPluginPrivate::DebuggerPluginPrivate(const QStringList &arguments) cmd->setAttribute(Command::CA_Hide); mstart->addAction(cmd, MENU_GROUP_GENERAL); + cmd = ActionManager::registerAction(&m_attachToLastCoreAction, + "Debugger.AttachLastCore"); + cmd->setAttribute(Command::CA_Hide); + mstart->addAction(cmd, MENU_GROUP_GENERAL); + cmd = ActionManager::registerAction(&m_attachToRemoteServerAction, "Debugger.AttachToRemoteServer"); cmd->setAttribute(Command::CA_Hide); @@ -1571,6 +1583,8 @@ void DebuggerPluginPrivate::updatePresetState() m_startAndDebugApplicationAction.setEnabled(true); m_attachToQmlPortAction.setEnabled(true); m_attachToCoreAction.setEnabled(true); + m_attachToLastCoreAction.setEnabled(Utils::HostOsInfo::isLinuxHost()); + m_attachToRemoteServerAction.setEnabled(true); m_attachToRunningApplication.setEnabled(true); m_attachToUnstartedApplication.setEnabled(true); @@ -1635,6 +1649,29 @@ void DebuggerPluginPrivate::attachCore() debugger->startRunControl(); } +void DebuggerPluginPrivate::attachToLastCore() +{ + QGuiApplication::setOverrideCursor(Qt::WaitCursor); + LastCore lastCore = getLastCore(); + QGuiApplication::restoreOverrideCursor(); + if (!lastCore) { + AsynchronousMessageBox::warning(Tr::tr("Warning"), + Tr::tr("coredumpctl did not find any cores created by systemd-coredump")); + return; + } + + auto runControl = new RunControl(ProjectExplorer::Constants::DEBUG_RUN_MODE); + runControl->setKit(KitManager::defaultKit()); + runControl->setDisplayName(Tr::tr("Last Core file \"%1\"").arg(lastCore.coreFile.toString())); + auto debugger = new DebuggerRunTool(runControl); + + debugger->setInferiorExecutable(lastCore.binary); + debugger->setCoreFilePath(lastCore.coreFile); + debugger->setStartMode(AttachToCore); + debugger->setCloseMode(DetachAtClose); + debugger->startRunControl(); +} + void DebuggerPluginPrivate::reloadDebuggingHelpers() { if (DebuggerEngine *engine = EngineManager::currentEngine()) diff --git a/src/plugins/debugger/shared/coredumputils.cpp b/src/plugins/debugger/shared/coredumputils.cpp new file mode 100644 index 00000000000..ed7d8621563 --- /dev/null +++ b/src/plugins/debugger/shared/coredumputils.cpp @@ -0,0 +1,87 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "coredumputils.h" +#include + +#include +#include +#include +#include +#include + +namespace Debugger::Internal { + +const char COREDUMPCTL[] = "coredumpctl"; +const char UNZSTD[] = "unzstd"; + +static LastCore findLastCore() +{ + static const auto coredumpctlBinary = QStandardPaths::findExecutable(COREDUMPCTL); + if (coredumpctlBinary.isEmpty()) { + qWarning("%s was not found", COREDUMPCTL); + return {}; + } + Utils::Process coredumpctl; + coredumpctl.setCommand({Utils::FilePath::fromString(coredumpctlBinary), {"info"}}); + coredumpctl.runBlocking(); + if (coredumpctl.exitStatus() != QProcess::NormalExit + || coredumpctl.exitCode() != 0) { + qWarning("%s failed: %s", COREDUMPCTL, qPrintable(coredumpctl.errorString())); + return {}; + } + + LastCore result; + const QString output = coredumpctl.readAllStandardOutput(); + const auto lines = QStringView{output}.split('\n'); + for (const QStringView &line : lines) { + if (line.startsWith(u" Executable: ")) { + const QStringView binary = line.sliced(line.indexOf(':') + 1).trimmed(); + result.binary = Utils::FilePath::fromString(binary.toString()); + } else if (line.startsWith(u" Storage: ") && line.endsWith(u" (present)")) { + auto pos = line.indexOf(':') + 1; + const auto len = line.size() - 10 - pos; + const QStringView coreFile = line.sliced(pos, len).trimmed(); + result.coreFile = Utils::FilePath::fromString(coreFile.toString()); + } + if (result) + break; + } + return result; +} + +LastCore getLastCore() +{ + auto lastCore = findLastCore(); + if (!lastCore || !lastCore.coreFile.endsWith(".zst")) { + qWarning("No core was found"); + return {}; + } + + // Copy core to /tmp and uncompress (unless it already exists + // on 2nd invocation). + QString tmpCorePath = QDir::tempPath() + '/' + lastCore.coreFile.fileName(); + const auto tmpCore = Utils::FilePath::fromString(tmpCorePath); + if (!tmpCore.exists() && !QFile::copy(lastCore.coreFile.toString(), tmpCorePath)) + return {}; + + const QString uncompressedCorePath = tmpCorePath.sliced(0, tmpCorePath.size() - 4); + const auto uncompressedCore = Utils::FilePath::fromString(uncompressedCorePath); + + if (!uncompressedCore.exists()) { + Utils::Process uncompress; + uncompress.setCommand({Utils::FilePath::fromString(UNZSTD), + {"-f", tmpCorePath}}); + uncompress.runBlocking(std::chrono::seconds(20)); + if (uncompress.exitStatus() != QProcess::NormalExit + || uncompress.exitCode() != 0) { + qWarning("%s failed: %s", UNZSTD, qPrintable(uncompress.errorString())); + return {}; + } + } + + lastCore.coreFile = uncompressedCore; + return lastCore; +} + +} // namespace Debugger::Internal { diff --git a/src/plugins/debugger/shared/coredumputils.h b/src/plugins/debugger/shared/coredumputils.h new file mode 100644 index 00000000000..118ea1d7469 --- /dev/null +++ b/src/plugins/debugger/shared/coredumputils.h @@ -0,0 +1,21 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +/* Helper functions to get the last core from systemd/coredumpctl */ +namespace Debugger::Internal { + +struct LastCore +{ + operator bool() const { return !binary.isEmpty() && !coreFile.isEmpty(); } + + Utils::FilePath binary; + Utils::FilePath coreFile; +}; + +LastCore getLastCore(); + +} // namespace Debugger::Internal