Debugger: Add option to attach to last core (Linux)

Fixes: QTCREATORBUG-29256
Change-Id: I52e77eb6c5fe3b4c959d906a62495c4bb7344d88
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Friedemann Kleint
2024-09-20 14:27:44 +02:00
parent 5d81179101
commit cba6b2f155
4 changed files with 146 additions and 0 deletions

View File

@@ -80,6 +80,7 @@ add_qtc_plugin(Debugger
qml/qmlv8debuggerclientconstants.h qml/qmlv8debuggerclientconstants.h
registerhandler.cpp registerhandler.h registerhandler.cpp registerhandler.h
shared/cdbsymbolpathlisteditor.cpp shared/cdbsymbolpathlisteditor.h shared/cdbsymbolpathlisteditor.cpp shared/cdbsymbolpathlisteditor.h
shared/coredumputils.cpp shared/coredumputils.h
shared/hostutils.cpp shared/hostutils.h shared/hostutils.cpp shared/hostutils.h
shared/peutils.cpp shared/peutils.h shared/peutils.cpp shared/peutils.h
shared/symbolpathsdialog.cpp shared/symbolpathsdialog.h shared/symbolpathsdialog.cpp shared/symbolpathsdialog.h

View File

@@ -21,6 +21,7 @@
#include "unstartedappwatcherdialog.h" #include "unstartedappwatcherdialog.h"
#include "loadcoredialog.h" #include "loadcoredialog.h"
#include "sourceutils.h" #include "sourceutils.h"
#include "shared/coredumputils.h"
#include "shared/hostutils.h" #include "shared/hostutils.h"
#include "console/console.h" #include "console/console.h"
@@ -109,6 +110,7 @@
#include <QJsonObject> #include <QJsonObject>
#include <QMenu> #include <QMenu>
#include <QMessageBox> #include <QMessageBox>
#include <QOperatingSystemVersion>
#include <QPointer> #include <QPointer>
#include <QPushButton> #include <QPushButton>
#include <QScopeGuard> #include <QScopeGuard>
@@ -634,6 +636,7 @@ public:
void attachToQmlPort(); void attachToQmlPort();
void runScheduled(); void runScheduled();
void attachCore(); void attachCore();
void attachToLastCore();
void reloadDebuggingHelpers(); void reloadDebuggingHelpers();
void remoteCommand(const QStringList &options); void remoteCommand(const QStringList &options);
@@ -671,6 +674,7 @@ public:
QAction m_attachToRemoteServerAction{Tr::tr("Attach to Running Debug Server...")}; QAction m_attachToRemoteServerAction{Tr::tr("Attach to Running Debug Server...")};
QAction m_startRemoteCdbAction{Tr::tr("Attach to Remote CDB Session...")}; QAction m_startRemoteCdbAction{Tr::tr("Attach to Remote CDB Session...")};
QAction m_attachToCoreAction{Tr::tr("Load Core File...")}; QAction m_attachToCoreAction{Tr::tr("Load Core File...")};
QAction m_attachToLastCoreAction{Tr::tr("Load Last Core File")};
// In the Debug menu. // In the Debug menu.
QAction m_startAndBreakOnMain{Tr::tr("Start and Break on Main")}; QAction m_startAndBreakOnMain{Tr::tr("Start and Break on Main")};
@@ -872,6 +876,9 @@ DebuggerPluginPrivate::DebuggerPluginPrivate(const QStringList &arguments)
connect(&m_attachToCoreAction, &QAction::triggered, connect(&m_attachToCoreAction, &QAction::triggered,
this, &DebuggerPluginPrivate::attachCore); this, &DebuggerPluginPrivate::attachCore);
connect(&m_attachToLastCoreAction, &QAction::triggered,
this, &DebuggerPluginPrivate::attachToLastCore);
connect(&m_attachToRemoteServerAction, &QAction::triggered, connect(&m_attachToRemoteServerAction, &QAction::triggered,
this, &StartApplicationDialog::attachToRemoteServer); this, &StartApplicationDialog::attachToRemoteServer);
@@ -947,6 +954,11 @@ DebuggerPluginPrivate::DebuggerPluginPrivate(const QStringList &arguments)
cmd->setAttribute(Command::CA_Hide); cmd->setAttribute(Command::CA_Hide);
mstart->addAction(cmd, MENU_GROUP_GENERAL); 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, cmd = ActionManager::registerAction(&m_attachToRemoteServerAction,
"Debugger.AttachToRemoteServer"); "Debugger.AttachToRemoteServer");
cmd->setAttribute(Command::CA_Hide); cmd->setAttribute(Command::CA_Hide);
@@ -1571,6 +1583,8 @@ void DebuggerPluginPrivate::updatePresetState()
m_startAndDebugApplicationAction.setEnabled(true); m_startAndDebugApplicationAction.setEnabled(true);
m_attachToQmlPortAction.setEnabled(true); m_attachToQmlPortAction.setEnabled(true);
m_attachToCoreAction.setEnabled(true); m_attachToCoreAction.setEnabled(true);
m_attachToLastCoreAction.setEnabled(Utils::HostOsInfo::isLinuxHost());
m_attachToRemoteServerAction.setEnabled(true); m_attachToRemoteServerAction.setEnabled(true);
m_attachToRunningApplication.setEnabled(true); m_attachToRunningApplication.setEnabled(true);
m_attachToUnstartedApplication.setEnabled(true); m_attachToUnstartedApplication.setEnabled(true);
@@ -1635,6 +1649,29 @@ void DebuggerPluginPrivate::attachCore()
debugger->startRunControl(); 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() void DebuggerPluginPrivate::reloadDebuggingHelpers()
{ {
if (DebuggerEngine *engine = EngineManager::currentEngine()) if (DebuggerEngine *engine = EngineManager::currentEngine())

View File

@@ -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 <utils/qtcprocess.h>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QProcess>
#include <QStandardPaths>
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 {

View File

@@ -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 <utils/filepath.h>
/* 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