forked from qt-creator/qt-creator
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:
@@ -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
|
||||||
|
@@ -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())
|
||||||
|
87
src/plugins/debugger/shared/coredumputils.cpp
Normal file
87
src/plugins/debugger/shared/coredumputils.cpp
Normal 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 {
|
21
src/plugins/debugger/shared/coredumputils.h
Normal file
21
src/plugins/debugger/shared/coredumputils.h
Normal 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
|
Reference in New Issue
Block a user