forked from qt-creator/qt-creator
Apparently not used anymore. Change-Id: Ic9ee826edd383254dcb77ea435843904a53d10a8 Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2658 lines
98 KiB
C++
2658 lines
98 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
** Contact: https://www.qt.io/licensing/
|
|
**
|
|
** This file is part of Qt Creator.
|
|
**
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 3 as published by the Free Software
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
** included in the packaging of this file. Please review the following
|
|
** information to ensure the GNU General Public License requirements will
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "debuggerplugin.h"
|
|
|
|
#include "debuggeractions.h"
|
|
#include "debuggerinternalconstants.h"
|
|
#include "debuggercore.h"
|
|
#include "debuggerdialogs.h"
|
|
#include "debuggerengine.h"
|
|
#include "debuggericons.h"
|
|
#include "debuggeritem.h"
|
|
#include "debuggeritemmanager.h"
|
|
#include "debuggermainwindow.h"
|
|
#include "debuggerrunconfigurationaspect.h"
|
|
#include "debuggerruncontrol.h"
|
|
#include "debuggerkitinformation.h"
|
|
#include "memoryagent.h"
|
|
#include "breakhandler.h"
|
|
#include "disassemblerlines.h"
|
|
#include "enginemanager.h"
|
|
#include "logwindow.h"
|
|
#include "moduleshandler.h"
|
|
#include "stackhandler.h"
|
|
#include "stackwindow.h"
|
|
#include "watchhandler.h"
|
|
#include "watchwindow.h"
|
|
#include "watchutils.h"
|
|
#include "unstartedappwatcherdialog.h"
|
|
#include "localsandexpressionswindow.h"
|
|
#include "loadcoredialog.h"
|
|
#include "sourceutils.h"
|
|
#include "shared/hostutils.h"
|
|
#include "console/console.h"
|
|
|
|
#include "threadshandler.h"
|
|
#include "commonoptionspage.h"
|
|
|
|
#include "analyzer/analyzerconstants.h"
|
|
#include "analyzer/analyzermanager.h"
|
|
|
|
#include <app/app_version.h>
|
|
|
|
#include <coreplugin/actionmanager/actioncontainer.h>
|
|
#include <coreplugin/actionmanager/actionmanager.h>
|
|
#include <coreplugin/actionmanager/command.h>
|
|
#include <coreplugin/coreconstants.h>
|
|
#include <coreplugin/editormanager/documentmodel.h>
|
|
#include <coreplugin/editormanager/editormanager.h>
|
|
#include <coreplugin/find/itemviewfind.h>
|
|
#include <coreplugin/findplaceholder.h>
|
|
#include <coreplugin/icore.h>
|
|
#include <coreplugin/imode.h>
|
|
#include <coreplugin/messagebox.h>
|
|
#include <coreplugin/messagemanager.h>
|
|
#include <coreplugin/modemanager.h>
|
|
#include <coreplugin/modemanager.h>
|
|
#include <coreplugin/navigationwidget.h>
|
|
#include <coreplugin/outputpane.h>
|
|
#include <coreplugin/rightpane.h>
|
|
|
|
#include <extensionsystem/pluginmanager.h>
|
|
|
|
#include <cppeditor/cppeditorconstants.h>
|
|
#include <qmljseditor/qmljseditorconstants.h>
|
|
|
|
#include <projectexplorer/buildconfiguration.h>
|
|
#include <projectexplorer/buildmanager.h>
|
|
#include <projectexplorer/devicesupport/deviceprocessesdialog.h>
|
|
#include <projectexplorer/devicesupport/deviceprocesslist.h>
|
|
#include <projectexplorer/itaskhandler.h>
|
|
#include <projectexplorer/project.h>
|
|
#include <projectexplorer/projectexplorer.h>
|
|
#include <projectexplorer/projectexplorericons.h>
|
|
#include <projectexplorer/projectexplorersettings.h>
|
|
#include <projectexplorer/projecttree.h>
|
|
#include <projectexplorer/runconfiguration.h>
|
|
#include <projectexplorer/session.h>
|
|
#include <projectexplorer/target.h>
|
|
#include <projectexplorer/taskhub.h>
|
|
#include <projectexplorer/toolchain.h>
|
|
#include <ssh/sshconnection.h>
|
|
|
|
#include <texteditor/texteditor.h>
|
|
#include <texteditor/textdocument.h>
|
|
#include <texteditor/fontsettings.h>
|
|
#include <texteditor/texteditorsettings.h>
|
|
|
|
#include <utils/algorithm.h>
|
|
#include <utils/appmainwindow.h>
|
|
#include <utils/basetreeview.h>
|
|
#include <utils/checkablemessagebox.h>
|
|
#include <utils/fancymainwindow.h>
|
|
#include <utils/hostosinfo.h>
|
|
#include <utils/proxyaction.h>
|
|
#include <utils/qtcassert.h>
|
|
#include <utils/savedaction.h>
|
|
#include <utils/statuslabel.h>
|
|
#include <utils/styledbar.h>
|
|
#include <utils/temporarydirectory.h>
|
|
#include <utils/utilsicons.h>
|
|
#include <utils/winutils.h>
|
|
|
|
#include <QAction>
|
|
#include <QApplication>
|
|
#include <QCheckBox>
|
|
#include <QComboBox>
|
|
#include <QDebug>
|
|
#include <QDialog>
|
|
#include <QDialogButtonBox>
|
|
#include <QDockWidget>
|
|
#include <QFileDialog>
|
|
#include <QHBoxLayout>
|
|
#include <QHeaderView>
|
|
#include <QInputDialog>
|
|
#include <QMenu>
|
|
#include <QMessageBox>
|
|
#include <QPointer>
|
|
#include <QPushButton>
|
|
#include <QSettings>
|
|
#include <QStackedWidget>
|
|
#include <QTextBlock>
|
|
#include <QToolButton>
|
|
#include <QTreeWidget>
|
|
#include <QVBoxLayout>
|
|
#include <QVariant>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QtPlugin>
|
|
|
|
#ifdef WITH_TESTS
|
|
|
|
#include <cpptools/cpptoolstestcase.h>
|
|
#include <cpptools/projectinfo.h>
|
|
|
|
#include <utils/executeondestruction.h>
|
|
|
|
#include <QTest>
|
|
#include <QSignalSpy>
|
|
#include <QTestEventLoop>
|
|
|
|
//#define WITH_BENCHMARK
|
|
#ifdef WITH_BENCHMARK
|
|
#include <valgrind/callgrind.h>
|
|
#endif
|
|
|
|
#endif // WITH_TESTS
|
|
|
|
#include <climits>
|
|
|
|
#define DEBUG_STATE 1
|
|
#ifdef DEBUG_STATE
|
|
//# define STATE_DEBUG(s)
|
|
// do { QString msg; QTextStream ts(&msg); ts << s;
|
|
// showMessage(msg, LogDebug); } while (0)
|
|
# define STATE_DEBUG(s) do { qDebug() << s; } while (0)
|
|
#else
|
|
# define STATE_DEBUG(s)
|
|
#endif
|
|
|
|
/*!
|
|
\namespace Debugger
|
|
Debugger plugin namespace
|
|
*/
|
|
|
|
/*!
|
|
\namespace Debugger::Internal
|
|
Internal namespace of the Debugger plugin
|
|
\internal
|
|
*/
|
|
|
|
/*!
|
|
\class Debugger::DebuggerEngine
|
|
|
|
\brief The DebuggerEngine class is the base class of a debugger engine.
|
|
|
|
\note The Debugger process itself and any helper processes like
|
|
gdbserver are referred to as 'Engine', whereas the debugged process
|
|
is referred to as 'Inferior'.
|
|
|
|
Transitions marked by '---' are done in the individual engines.
|
|
Transitions marked by '+-+' are done in the base DebuggerEngine.
|
|
Transitions marked by '*' are done asynchronously.
|
|
|
|
The GdbEngine->setupEngine() function is described in more detail below.
|
|
|
|
The engines are responsible for local roll-back to the last
|
|
acknowledged state before calling notify*Failed. I.e. before calling
|
|
notifyEngineSetupFailed() any process started during setupEngine()
|
|
so far must be terminated.
|
|
\code
|
|
|
|
DebuggerNotReady
|
|
progressmanager/progressmanager.cpp +
|
|
EngineSetupRequested
|
|
+
|
|
(calls *Engine->setupEngine())
|
|
| |
|
|
| |
|
|
{notify- {notify-
|
|
Engine- Engine-
|
|
SetupOk} SetupFailed}
|
|
+ +
|
|
+ `+-+-+> EngineSetupFailed
|
|
+ +
|
|
+ [calls RunControl->startFailed]
|
|
+ +
|
|
+ DebuggerFinished
|
|
v
|
|
EngineSetupOk
|
|
+
|
|
[calls RunControl->StartSuccessful]
|
|
+
|
|
+
|
|
EngineRunRequested
|
|
+
|
|
(calls *Engine->runEngine())
|
|
/ | | \
|
|
/ | | \
|
|
| (core) | (attach) | |
|
|
| | | |
|
|
{notify- {notifyER&- {notifyER&- {notify-
|
|
Inferior- Inferior- Inferior- EngineRun-
|
|
Unrunnable} StopOk} RunOk} Failed}
|
|
+ + + +
|
|
InferiorUnrunnable + InferiorRunOk +
|
|
+ +
|
|
InferiorStopOk EngineRunFailed
|
|
+
|
|
`-+-+-+-+-+-+-+-+-+-+-+>-+
|
|
+
|
|
+
|
|
#Interrupt@InferiorRunOk# +
|
|
+ +
|
|
InferiorStopRequested +
|
|
#SpontaneousStop + +
|
|
@InferiorRunOk# (calls *Engine-> +
|
|
+ interruptInferior()) +
|
|
{notify- | | +
|
|
Spontaneous- {notify- {notify- +
|
|
Inferior- Inferior- Inferior- +
|
|
StopOk} StopOk} StopFailed} +
|
|
+ + + +
|
|
+ + + +
|
|
InferiorStopOk + +
|
|
+ + +
|
|
+ + +
|
|
+ + +
|
|
#Stop@InferiorUnrunnable# + +
|
|
#Creator Close Event# + +
|
|
+ + +
|
|
InferiorShutdownRequested +
|
|
+ +
|
|
(calls *Engine->shutdownInferior()) +
|
|
| +
|
|
{notifyInferiorShutdownFinished} +
|
|
+ +
|
|
+ +
|
|
#Inferior exited# + +
|
|
| + +
|
|
{notifyInferior- + +
|
|
Exited} + +
|
|
+ + +
|
|
+ + +
|
|
+ + +
|
|
InferiorShutdownFinished +
|
|
* +
|
|
EngineShutdownRequested +
|
|
+ +
|
|
(calls *Engine->shutdownEngine()) <+-+-+-+-+-+-+-+-+-+-+-+-+-+'
|
|
|
|
|
|
|
|
{notifyEngineShutdownFinished}
|
|
+
|
|
EngineShutdownFinished
|
|
*
|
|
DebuggerFinished
|
|
|
|
\endcode */
|
|
|
|
/* Here is a matching graph as a GraphViz graph. View it using
|
|
* \code
|
|
grep "^sg1:" debuggerplugin.cpp | cut -c5- | dot -osg1.ps -Tps && gv sg1.ps
|
|
|
|
sg1: digraph DebuggerStates {
|
|
sg1: DebuggerNotReady -> EngineSetupRequested
|
|
sg1: EngineSetupRequested -> EngineSetupOk [ label="notifyEngineSetupOk", style="dashed" ];
|
|
sg1: EngineSetupRequested -> EngineSetupFailed [ label= "notifyEngineSetupFailed", style="dashed"];
|
|
sg1: EngineSetupFailed -> DebuggerFinished [ label= "RunControl::StartFailed" ];
|
|
sg1: EngineSetupOk -> EngineRunRequested [ label= "RunControl::StartSuccessful" ];
|
|
sg1: EngineRunRequested -> InferiorUnrunnable [ label="notifyInferiorUnrunnable", style="dashed" ];
|
|
sg1: EngineRunRequested -> InferiorStopOk [ label="notifyEngineRunAndInferiorStopOk", style="dashed" ];
|
|
sg1: EngineRunRequested -> InferiorRunOk [ label="notifyEngineRunAndInferiorRunOk", style="dashed" ];
|
|
sg1: EngineRunRequested -> EngineRunFailed [ label="notifyEngineRunFailed", style="dashed" ];
|
|
sg1: EngineRunFailed -> EngineShutdownRequested
|
|
sg1: InferiorRunOk -> InferiorStopOk [ label="SpontaneousStop\nnotifyInferiorSpontaneousStop", style="dashed" ];
|
|
sg1: InferiorRunOk -> InferiorStopRequested [ label="User stop\nEngine::interruptInferior", style="dashed"];
|
|
sg1: InferiorStopRequested -> InferiorStopOk [ label="notifyInferiorStopOk", style="dashed" ];
|
|
sg1: InferiorStopRequested -> InferiorShutdownRequested [ label="notifyInferiorStopFailed", style="dashed" ];
|
|
sg1: InferiorStopOk -> InferiorRunRequested [ label="User\nEngine::continueInferior" ];
|
|
sg1: InferiorRunRequested -> InferiorRunOk [ label="notifyInferiorRunOk", style="dashed"];
|
|
sg1: InferiorRunRequested -> InferiorRunFailed [ label="notifyInferiorRunFailed", style="dashed"];
|
|
sg1: InferiorRunFailed -> InferiorStopOk
|
|
sg1: InferiorStopOk -> InferiorShutdownRequested [ label="Close event" ];
|
|
sg1: InferiorUnrunnable -> InferiorShutdownRequested [ label="Close event" ];
|
|
sg1: InferiorShutdownRequested -> InferiorShutdownFinished [ label= "Engine::shutdownInferior\nnotifyInferiorShutdownFinished", style="dashed" ];
|
|
sg1: InferiorExited -> InferiorExitOk [ label="notifyInferiorExited", style="dashed"];
|
|
sg1: InferiorExitOk -> InferiorShutdownOk
|
|
sg1: InferiorShutdownFinished -> EngineShutdownRequested
|
|
sg1: EngineShutdownRequested -> EngineShutdownFinished [ label="Engine::shutdownEngine\nnotifyEngineShutdownFinished", style="dashed" ];
|
|
sg1: EngineShutdownFinished -> DebuggerFinished [ style = "dotted" ];
|
|
sg1: }
|
|
* \endcode */
|
|
// Additional signalling: {notifyInferiorIll} {notifyEngineIll}
|
|
|
|
|
|
/*!
|
|
\class Debugger::Internal::GdbEngine
|
|
\brief The GdbEngine class implements Debugger::Engine driving a GDB
|
|
executable.
|
|
|
|
GdbEngine specific startup. All happens in EngineSetupRequested state:
|
|
|
|
\list
|
|
\li Transitions marked by '---' are done in the individual adapters.
|
|
|
|
\li Transitions marked by '+-+' are done in the GdbEngine.
|
|
\endlist
|
|
|
|
\code
|
|
GdbEngine::setupEngine()
|
|
+
|
|
(calls *Adapter->startAdapter())
|
|
| |
|
|
| `---> handleAdapterStartFailed()
|
|
| +
|
|
| {notifyEngineSetupFailed}
|
|
|
|
|
handleAdapterStarted()
|
|
+
|
|
{notifyEngineSetupOk}
|
|
|
|
|
|
|
|
GdbEngine::setupInferior()
|
|
+
|
|
(calls *Adapter->prepareInferior())
|
|
| |
|
|
| `---> handlePrepareInferiorFailed()
|
|
| +
|
|
| {notifyInferiorSetupFailed}
|
|
|
|
|
handleInferiorPrepared()
|
|
+
|
|
{notifyInferiorSetupOk}
|
|
|
|
\endcode */
|
|
|
|
using namespace Core;
|
|
using namespace Core::Constants;
|
|
using namespace Debugger::Constants;
|
|
using namespace Debugger::Internal;
|
|
using namespace ExtensionSystem;
|
|
using namespace ProjectExplorer;
|
|
using namespace TextEditor;
|
|
using namespace Utils;
|
|
|
|
namespace CC = Core::Constants;
|
|
namespace PE = ProjectExplorer::Constants;
|
|
|
|
namespace Debugger {
|
|
namespace Internal {
|
|
|
|
const char DEBUGGER_START[] = "Debugger.Start";
|
|
|
|
// Menu Groups
|
|
const char MENU_GROUP_GENERAL[] = "Debugger.Group.General";
|
|
const char MENU_GROUP_SPECIAL[] = "Debugger.Group.Special";
|
|
const char MENU_GROUP_START_QML[] = "Debugger.Group.Start.Qml";
|
|
|
|
void addCdbOptionPages(QList<IOptionsPage*> *opts);
|
|
void addGdbOptionPages(QList<IOptionsPage*> *opts);
|
|
|
|
static QIcon startIcon(bool toolBarStyle)
|
|
{
|
|
const static QIcon sidebarIcon =
|
|
Icon::sideBarIcon(ProjectExplorer::Icons::DEBUG_START, ProjectExplorer::Icons::DEBUG_START_FLAT);
|
|
const static QIcon icon =
|
|
Icon::combinedIcon({ProjectExplorer::Icons::DEBUG_START_SMALL.icon(), sidebarIcon});
|
|
const static QIcon iconToolBar =
|
|
Icon::combinedIcon({ProjectExplorer::Icons::DEBUG_START_SMALL_TOOLBAR.icon(), sidebarIcon});
|
|
return toolBarStyle ? iconToolBar : icon;
|
|
}
|
|
|
|
static QIcon continueIcon(bool toolBarStyle)
|
|
{
|
|
const static QIcon sidebarIcon =
|
|
Icon::sideBarIcon(Icons::CONTINUE, Icons::CONTINUE_FLAT);
|
|
const static QIcon icon =
|
|
Icon::combinedIcon({Icons::DEBUG_CONTINUE_SMALL.icon(), sidebarIcon});
|
|
const static QIcon iconToolBar =
|
|
Icon::combinedIcon({Icons::DEBUG_CONTINUE_SMALL_TOOLBAR.icon(), sidebarIcon});
|
|
return toolBarStyle ? iconToolBar : icon;
|
|
}
|
|
|
|
static QIcon interruptIcon(bool toolBarStyle)
|
|
{
|
|
const static QIcon sidebarIcon =
|
|
Icon::sideBarIcon(Icons::INTERRUPT, Icons::INTERRUPT_FLAT);
|
|
const static QIcon icon =
|
|
Icon::combinedIcon({Icons::DEBUG_INTERRUPT_SMALL.icon(), sidebarIcon});
|
|
const static QIcon iconToolBar =
|
|
Icon::combinedIcon({Icons::DEBUG_INTERRUPT_SMALL_TOOLBAR.icon(), sidebarIcon});
|
|
return toolBarStyle ? iconToolBar : icon;
|
|
}
|
|
|
|
QAction *addAction(QMenu *menu, const QString &display, bool on,
|
|
const std::function<void()> &onTriggered)
|
|
{
|
|
QAction *act = menu->addAction(display);
|
|
act->setEnabled(on);
|
|
QObject::connect(act, &QAction::triggered, onTriggered);
|
|
return act;
|
|
};
|
|
|
|
QAction *addAction(QMenu *menu, const QString &d1, const QString &d2, bool on,
|
|
const std::function<void()> &onTriggered)
|
|
{
|
|
return on ? addAction(menu, d1, true, onTriggered) : addAction(menu, d2, false);
|
|
};
|
|
|
|
QAction *addCheckableAction(QMenu *menu, const QString &display, bool on, bool checked,
|
|
const std::function<void()> &onTriggered)
|
|
{
|
|
QAction *act = addAction(menu, display, on, onTriggered);
|
|
act->setCheckable(true);
|
|
act->setChecked(checked);
|
|
return act;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
//
|
|
// DebugMode
|
|
//
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
class DebugMode : public IMode
|
|
{
|
|
public:
|
|
DebugMode()
|
|
{
|
|
setObjectName("DebugMode");
|
|
setContext(Context(C_DEBUGMODE, CC::C_NAVIGATION_PANE));
|
|
setDisplayName(DebuggerPlugin::tr("Debug"));
|
|
setIcon(Utils::Icon::modeIcon(Icons::MODE_DEBUGGER_CLASSIC,
|
|
Icons::MODE_DEBUGGER_FLAT, Icons::MODE_DEBUGGER_FLAT_ACTIVE));
|
|
setPriority(85);
|
|
setId(MODE_DEBUG);
|
|
|
|
DebuggerMainWindow *mainWindow = DebuggerMainWindow::instance();
|
|
|
|
auto editorHolderLayout = new QVBoxLayout;
|
|
editorHolderLayout->setMargin(0);
|
|
editorHolderLayout->setSpacing(0);
|
|
|
|
auto editorAndFindWidget = new QWidget;
|
|
editorAndFindWidget->setLayout(editorHolderLayout);
|
|
editorHolderLayout->addWidget(mainWindow->centralWidgetStack());
|
|
editorHolderLayout->addWidget(new FindToolBarPlaceHolder(editorAndFindWidget));
|
|
|
|
auto documentAndRightPane = new MiniSplitter;
|
|
documentAndRightPane->addWidget(editorAndFindWidget);
|
|
documentAndRightPane->addWidget(new RightPanePlaceHolder(MODE_DEBUG));
|
|
documentAndRightPane->setStretchFactor(0, 1);
|
|
documentAndRightPane->setStretchFactor(1, 0);
|
|
|
|
auto centralEditorWidget = new QWidget;
|
|
auto centralLayout = new QVBoxLayout(centralEditorWidget);
|
|
centralEditorWidget->setLayout(centralLayout);
|
|
centralLayout->setMargin(0);
|
|
centralLayout->setSpacing(0);
|
|
centralLayout->addWidget(documentAndRightPane);
|
|
centralLayout->setStretch(0, 1);
|
|
centralLayout->setStretch(1, 0);
|
|
|
|
// Right-side window with editor, output etc.
|
|
auto mainWindowSplitter = new MiniSplitter;
|
|
mainWindowSplitter->addWidget(mainWindow);
|
|
mainWindowSplitter->addWidget(new OutputPanePlaceHolder(MODE_DEBUG, mainWindowSplitter));
|
|
auto outputPane = new OutputPanePlaceHolder(MODE_DEBUG, mainWindowSplitter);
|
|
outputPane->setObjectName("DebuggerOutputPanePlaceHolder");
|
|
mainWindowSplitter->addWidget(outputPane);
|
|
mainWindowSplitter->setStretchFactor(0, 10);
|
|
mainWindowSplitter->setStretchFactor(1, 0);
|
|
mainWindowSplitter->setOrientation(Qt::Vertical);
|
|
|
|
// Navigation and right-side window.
|
|
auto splitter = new MiniSplitter;
|
|
splitter->setFocusProxy(mainWindow->centralWidgetStack());
|
|
splitter->addWidget(new NavigationWidgetPlaceHolder(MODE_DEBUG, Side::Left));
|
|
splitter->addWidget(mainWindowSplitter);
|
|
splitter->setStretchFactor(0, 0);
|
|
splitter->setStretchFactor(1, 1);
|
|
splitter->setObjectName("DebugModeWidget");
|
|
|
|
mainWindow->setCentralWidget(centralEditorWidget);
|
|
mainWindow->addSubPerspectiveSwitcher(EngineManager::engineChooser());
|
|
|
|
setWidget(splitter);
|
|
}
|
|
|
|
~DebugMode() { delete widget(); }
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Misc
|
|
//
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
QWidget *addSearch(BaseTreeView *treeView)
|
|
{
|
|
QAction *act = action(UseAlternatingRowColors);
|
|
treeView->setAlternatingRowColors(act->isChecked());
|
|
QObject::connect(act, &QAction::toggled,
|
|
treeView, &BaseTreeView::setAlternatingRowColors);
|
|
|
|
return ItemViewFind::createSearchableWrapper(treeView);
|
|
}
|
|
|
|
static Kit::Predicate cdbPredicate(char wordWidth = 0)
|
|
{
|
|
return [wordWidth](const Kit *k) -> bool {
|
|
if (DebuggerKitAspect::engineType(k) != CdbEngineType
|
|
|| DebuggerKitAspect::configurationErrors(k)) {
|
|
return false;
|
|
}
|
|
if (wordWidth)
|
|
return ToolChainKitAspect::targetAbi(k).wordWidth() == wordWidth;
|
|
return true;
|
|
};
|
|
}
|
|
|
|
// Find a CDB kit for debugging unknown processes.
|
|
// On a 64bit OS, prefer a 64bit debugger.
|
|
static Kit *findUniversalCdbKit()
|
|
{
|
|
if (Utils::is64BitWindowsSystem()) {
|
|
if (Kit *cdb64Kit = KitManager::kit(cdbPredicate(64)))
|
|
return cdb64Kit;
|
|
}
|
|
return KitManager::kit(cdbPredicate());
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Debuginfo Taskhandler
|
|
//
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
class DebugInfoTaskHandler : public ITaskHandler
|
|
{
|
|
public:
|
|
bool canHandle(const Task &task) const final
|
|
{
|
|
return m_debugInfoTasks.contains(task.taskId);
|
|
}
|
|
|
|
void handle(const Task &task) final
|
|
{
|
|
QString cmd = m_debugInfoTasks.value(task.taskId);
|
|
QProcess::startDetached(cmd);
|
|
}
|
|
|
|
void addTask(unsigned id, const QString &cmd)
|
|
{
|
|
m_debugInfoTasks[id] = cmd;
|
|
}
|
|
|
|
QAction *createAction(QObject *parent) const final
|
|
{
|
|
QAction *action = new QAction(DebuggerPlugin::tr("Install &Debug Information"), parent);
|
|
action->setToolTip(DebuggerPlugin::tr("Tries to install missing debug information."));
|
|
return action;
|
|
}
|
|
|
|
private:
|
|
QHash<unsigned, QString> m_debugInfoTasks;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
//
|
|
// DebuggerPluginPrivate
|
|
//
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
static DebuggerPluginPrivate *dd = nullptr;
|
|
|
|
/*!
|
|
\class Debugger::Internal::DebuggerCore
|
|
|
|
This is the "internal" interface of the debugger plugin that's
|
|
used by debugger views and debugger engines. The interface is
|
|
implemented in DebuggerPluginPrivate.
|
|
*/
|
|
|
|
/*!
|
|
\class Debugger::Internal::DebuggerPluginPrivate
|
|
|
|
Implementation of DebuggerCore.
|
|
*/
|
|
|
|
struct Callback
|
|
{
|
|
Callback()
|
|
: cb([]{})
|
|
{}
|
|
Callback(void (DebuggerEngine::*func)())
|
|
: cb([func] { if (DebuggerEngine *engine = EngineManager::currentEngine()) (engine->*func)(); })
|
|
{}
|
|
|
|
std::function<void()> cb;
|
|
};
|
|
|
|
class DebuggerPluginPrivate : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
explicit DebuggerPluginPrivate(DebuggerPlugin *plugin);
|
|
~DebuggerPluginPrivate() override;
|
|
|
|
bool initialize(const QStringList &arguments, QString *errorMessage);
|
|
void extensionsInitialized();
|
|
void aboutToShutdown();
|
|
void doShutdown();
|
|
|
|
RunControl *attachToRunningProcess(Kit *kit, DeviceProcessItem process, bool contAfterAttach);
|
|
|
|
void writeSettings()
|
|
{
|
|
m_debuggerSettings->writeSettings();
|
|
// writeWindowSettings();
|
|
}
|
|
|
|
void breakpointSetMarginActionTriggered(bool isMessageOnly, const ContextData &data)
|
|
{
|
|
QString message;
|
|
if (isMessageOnly) {
|
|
if (data.type == LocationByAddress) {
|
|
//: Message tracepoint: Address hit.
|
|
message = tr("0x%1 hit").arg(data.address, 0, 16);
|
|
} else {
|
|
//: Message tracepoint: %1 file, %2 line %3 function hit.
|
|
message = tr("%1:%2 %3() hit").arg(FilePath::fromString(data.fileName).fileName()).
|
|
arg(data.lineNumber).
|
|
arg(cppFunctionAt(data.fileName, data.lineNumber));
|
|
}
|
|
QInputDialog dialog; // Create wide input dialog.
|
|
dialog.setWindowFlags(dialog.windowFlags()
|
|
& ~(Qt::WindowContextHelpButtonHint|Qt::MSWindowsFixedSizeDialogHint));
|
|
dialog.resize(600, dialog.height());
|
|
dialog.setWindowTitle(tr("Add Message Tracepoint"));
|
|
dialog.setLabelText (tr("Message:"));
|
|
dialog.setTextValue(message);
|
|
if (dialog.exec() != QDialog::Accepted || dialog.textValue().isEmpty())
|
|
return;
|
|
message = dialog.textValue();
|
|
}
|
|
BreakpointManager::toggleBreakpoint(data, message);
|
|
}
|
|
|
|
void editorOpened(IEditor *editor);
|
|
void updateBreakMenuItem(IEditor *editor);
|
|
void requestMark(TextEditorWidget *widget, int lineNumber,
|
|
TextMarkRequestKind kind);
|
|
void requestContextMenu(TextEditorWidget *widget,
|
|
int lineNumber, QMenu *menu);
|
|
|
|
void toggleBreakpointHelper();
|
|
void updateDebugWithoutDeployMenu();
|
|
|
|
void startRemoteCdbSession();
|
|
void attachToRunningApplication();
|
|
void attachToUnstartedApplicationDialog();
|
|
void attachToQmlPort();
|
|
void runScheduled();
|
|
void attachCore();
|
|
|
|
void remoteCommand(const QStringList &options);
|
|
|
|
void dumpLog();
|
|
void setInitialState();
|
|
|
|
void onStartupProjectChanged(Project *project);
|
|
|
|
bool parseArgument(QStringList::const_iterator &it,
|
|
const QStringList::const_iterator &cend, QString *errorMessage);
|
|
bool parseArguments(const QStringList &args, QString *errorMessage);
|
|
void parseCommandLineArguments();
|
|
|
|
void updatePresetState();
|
|
|
|
public:
|
|
QPointer<DebugMode> m_mode;
|
|
|
|
ActionContainer *m_menu = nullptr;
|
|
|
|
QVector<DebuggerRunTool *> m_scheduledStarts;
|
|
|
|
ProxyAction m_visibleStartAction; // The fat debug button
|
|
ProxyAction m_hiddenStopAction;
|
|
QAction m_undisturbableAction;
|
|
OptionalAction m_startAction;
|
|
QAction m_debugWithoutDeployAction{tr("Start Debugging Without Deployment")};
|
|
QAction m_startAndDebugApplicationAction{tr("Start and Debug External Application...")};
|
|
QAction m_attachToRunningApplication{tr("Attach to Running Application...")};
|
|
QAction m_attachToUnstartedApplication{tr("Attach to Unstarted Application...")};
|
|
QAction m_attachToQmlPortAction{tr("Attach to QML Port...")};
|
|
QAction m_attachToRemoteServerAction{tr("Attach to Running Debug Server...")};
|
|
QAction m_startRemoteCdbAction{tr("Attach to Remote CDB Session...")};
|
|
QAction m_attachToCoreAction{tr("Load Core File...")};
|
|
|
|
// In the Debug menu.
|
|
QAction m_startAndBreakOnMain{tr("Start and Break on Main")};
|
|
QAction m_watchAction{tr("Add Expression Evaluator")};
|
|
Command *m_watchCommand = nullptr;
|
|
QAction m_breakAction{tr("Toggle Breakpoint")};
|
|
|
|
BreakpointManager m_breakpointManager;
|
|
QPointer<BaseTreeView> m_breakpointManagerView;
|
|
QPointer<QWidget> m_breakpointManagerWindow;
|
|
|
|
QPointer<BaseTreeView> m_engineManagerView;
|
|
QPointer<QWidget> m_engineManagerWindow;
|
|
QPointer<GlobalLogWindow> m_globalLogWindow;
|
|
|
|
QString m_lastPermanentStatusMessage;
|
|
|
|
DebuggerPlugin *m_plugin = nullptr;
|
|
|
|
EngineManager m_engineManager;
|
|
QTimer m_shutdownTimer;
|
|
bool m_shuttingDown = false;
|
|
DebuggerSettings *m_debuggerSettings = nullptr;
|
|
QStringList m_arguments;
|
|
const QSharedPointer<GlobalDebuggerOptions> m_globalDebuggerOptions;
|
|
|
|
DebuggerItemManager m_debuggerItemManager;
|
|
QList<IOptionsPage *> m_optionPages;
|
|
IContext m_debugModeContext;
|
|
|
|
DebugInfoTaskHandler m_debugInfoTaskHandler;
|
|
Perspective m_perspective{Constants::PRESET_PERSPECTIVE_ID, tr("Debugger")};
|
|
|
|
DebuggerKitAspect debuggerKitAspect;
|
|
};
|
|
|
|
DebuggerPluginPrivate::DebuggerPluginPrivate(DebuggerPlugin *plugin)
|
|
: m_globalDebuggerOptions(new GlobalDebuggerOptions)
|
|
{
|
|
qRegisterMetaType<ContextData>("ContextData");
|
|
qRegisterMetaType<DebuggerRunParameters>("DebuggerRunParameters");
|
|
|
|
QTC_CHECK(!dd);
|
|
dd = this;
|
|
|
|
m_plugin = plugin;
|
|
debuggerConsole(); // ensure Debugger Console is created before settings are taken into account
|
|
}
|
|
|
|
DebuggerPluginPrivate::~DebuggerPluginPrivate()
|
|
{
|
|
destroyDebuggerConsole();
|
|
|
|
qDeleteAll(m_optionPages);
|
|
m_optionPages.clear();
|
|
|
|
delete m_debuggerSettings;
|
|
m_debuggerSettings = nullptr;
|
|
}
|
|
|
|
static QString msgParameterMissing(const QString &a)
|
|
{
|
|
return DebuggerPlugin::tr("Option \"%1\" is missing the parameter.").arg(a);
|
|
}
|
|
|
|
static Kit *guessKitFromAbis(const Abis &abis)
|
|
{
|
|
Kit *kit = nullptr;
|
|
|
|
// Try to find a kit via ABI.
|
|
if (!abis.isEmpty()) {
|
|
// Try exact abis.
|
|
kit = KitManager::kit([abis](const Kit *k) {
|
|
const Abi tcAbi = ToolChainKitAspect::targetAbi(k);
|
|
return abis.contains(tcAbi) && !DebuggerKitAspect::configurationErrors(k);
|
|
});
|
|
if (!kit) {
|
|
// Or something compatible.
|
|
kit = KitManager::kit([abis](const Kit *k) {
|
|
const Abi tcAbi = ToolChainKitAspect::targetAbi(k);
|
|
return !DebuggerKitAspect::configurationErrors(k)
|
|
&& Utils::contains(abis, [tcAbi](const Abi &a) { return a.isCompatibleWith(tcAbi); });
|
|
});
|
|
}
|
|
}
|
|
|
|
if (!kit)
|
|
kit = KitManager::defaultKit();
|
|
|
|
return kit;
|
|
}
|
|
|
|
bool DebuggerPluginPrivate::parseArgument(QStringList::const_iterator &it,
|
|
const QStringList::const_iterator &cend, QString *errorMessage)
|
|
{
|
|
const QString &option = *it;
|
|
// '-debug <pid>'
|
|
// '-debug <exe>[,server=<server:port>][,core=<core>][,kit=<kit>][,terminal={0,1}]'
|
|
if (*it == "-debug") {
|
|
++it;
|
|
if (it == cend) {
|
|
*errorMessage = msgParameterMissing(*it);
|
|
return false;
|
|
}
|
|
const qulonglong pid = it->toULongLong();
|
|
const QStringList args = it->split(',');
|
|
|
|
Kit *kit = nullptr;
|
|
DebuggerStartMode startMode = StartExternal;
|
|
QString executable;
|
|
QString remoteChannel;
|
|
QString coreFile;
|
|
bool useTerminal = false;
|
|
|
|
if (!pid) {
|
|
for (const QString &arg : args) {
|
|
const QString key = arg.section('=', 0, 0);
|
|
const QString val = arg.section('=', 1, 1);
|
|
if (val.isEmpty()) {
|
|
if (key.isEmpty()) {
|
|
continue;
|
|
} else if (executable.isEmpty()) {
|
|
executable = key;
|
|
} else {
|
|
*errorMessage = DebuggerPlugin::tr("Only one executable allowed.");
|
|
return false;
|
|
}
|
|
} else if (key == "kit") {
|
|
kit = KitManager::kit(Id::fromString(val));
|
|
if (!kit)
|
|
kit = KitManager::kit(Utils::equal(&Kit::displayName, val));
|
|
} else if (key == "server") {
|
|
startMode = AttachToRemoteServer;
|
|
remoteChannel = val;
|
|
} else if (key == "core") {
|
|
startMode = AttachCore;
|
|
coreFile = val;
|
|
} else if (key == "terminal") {
|
|
useTerminal = true;
|
|
}
|
|
}
|
|
}
|
|
if (!kit)
|
|
kit = guessKitFromAbis(Abi::abisOfBinary(FilePath::fromString(executable)));
|
|
|
|
auto runControl = new RunControl(ProjectExplorer::Constants::DEBUG_RUN_MODE);
|
|
runControl->setKit(kit);
|
|
auto debugger = new DebuggerRunTool(runControl);
|
|
debugger->setInferiorExecutable(executable);
|
|
if (pid) {
|
|
debugger->setStartMode(AttachExternal);
|
|
debugger->setCloseMode(DetachAtClose);
|
|
debugger->setAttachPid(pid);
|
|
debugger->setRunControlName(tr("Process %1").arg(pid));
|
|
debugger->setStartMessage(tr("Attaching to local process %1.").arg(pid));
|
|
} else if (startMode == AttachToRemoteServer) {
|
|
debugger->setStartMode(AttachToRemoteServer);
|
|
debugger->setRemoteChannel(remoteChannel);
|
|
debugger->setRunControlName(tr("Remote: \"%1\"").arg(remoteChannel));
|
|
debugger->setStartMessage(tr("Attaching to remote server %1.").arg(remoteChannel));
|
|
} else if (startMode == AttachCore) {
|
|
debugger->setStartMode(AttachCore);
|
|
debugger->setCloseMode(DetachAtClose);
|
|
debugger->setCoreFileName(coreFile);
|
|
debugger->setRunControlName(tr("Core file \"%1\"").arg(coreFile));
|
|
debugger->setStartMessage(tr("Attaching to core file %1.").arg(coreFile));
|
|
} else {
|
|
debugger->setStartMode(StartExternal);
|
|
debugger->setRunControlName(tr("Executable file \"%1\"").arg(executable));
|
|
debugger->setStartMessage(tr("Debugging file %1.").arg(executable));
|
|
}
|
|
debugger->setUseTerminal(useTerminal);
|
|
|
|
m_scheduledStarts.append(debugger);
|
|
return true;
|
|
}
|
|
// -wincrashevent <event-handle>:<pid>. A handle used for
|
|
// a handshake when attaching to a crashed Windows process.
|
|
// This is created by $QTC/src/tools/qtcdebugger/main.cpp:
|
|
// args << "-wincrashevent"
|
|
// << QString::fromLatin1("%1:%2").arg(argWinCrashEvent).arg(argProcessId);
|
|
if (*it == "-wincrashevent") {
|
|
++it;
|
|
if (it == cend) {
|
|
*errorMessage = msgParameterMissing(*it);
|
|
return false;
|
|
}
|
|
qint64 pid = it->section(':', 1, 1).toULongLong();
|
|
auto runControl = new RunControl(ProjectExplorer::Constants::DEBUG_RUN_MODE);
|
|
runControl->setKit(findUniversalCdbKit());
|
|
auto debugger = new DebuggerRunTool(runControl);
|
|
debugger->setStartMode(AttachCrashedExternal);
|
|
debugger->setCrashParameter(it->section(':', 0, 0));
|
|
debugger->setAttachPid(pid);
|
|
debugger->setRunControlName(tr("Crashed process %1").arg(pid));
|
|
debugger->setStartMessage(tr("Attaching to crashed process %1").arg(pid));
|
|
if (pid < 1) {
|
|
*errorMessage = DebuggerPlugin::tr("The parameter \"%1\" of option \"%2\" "
|
|
"does not match the pattern <handle>:<pid>.").arg(*it, option);
|
|
return false;
|
|
}
|
|
m_scheduledStarts.append(debugger);
|
|
return true;
|
|
}
|
|
|
|
*errorMessage = DebuggerPlugin::tr("Invalid debugger option: %1").arg(option);
|
|
return false;
|
|
}
|
|
|
|
bool DebuggerPluginPrivate::parseArguments(const QStringList &args,
|
|
QString *errorMessage)
|
|
{
|
|
const QStringList::const_iterator cend = args.constEnd();
|
|
for (QStringList::const_iterator it = args.constBegin(); it != cend; ++it)
|
|
if (!parseArgument(it, cend, errorMessage))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
void DebuggerPluginPrivate::parseCommandLineArguments()
|
|
{
|
|
QString errorMessage;
|
|
if (!parseArguments(m_arguments, &errorMessage)) {
|
|
errorMessage = tr("Error evaluating command line arguments: %1")
|
|
.arg(errorMessage);
|
|
qWarning("%s\n", qPrintable(errorMessage));
|
|
MessageManager::write(errorMessage);
|
|
}
|
|
if (!m_scheduledStarts.isEmpty())
|
|
QTimer::singleShot(0, this, &DebuggerPluginPrivate::runScheduled);
|
|
}
|
|
|
|
bool DebuggerPluginPrivate::initialize(const QStringList &arguments,
|
|
QString *errorMessage)
|
|
{
|
|
Q_UNUSED(errorMessage);
|
|
|
|
const Context debuggerNotRunning(C_DEBUGGER_NOTRUNNING);
|
|
ICore::addAdditionalContext(debuggerNotRunning);
|
|
|
|
m_arguments = arguments;
|
|
if (!m_arguments.isEmpty())
|
|
connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::finishedInitialization,
|
|
this, &DebuggerPluginPrivate::parseCommandLineArguments);
|
|
|
|
// Menus
|
|
m_menu = ActionManager::createMenu(M_DEBUG_ANALYZER);
|
|
m_menu->menu()->setTitle(tr("&Analyze"));
|
|
m_menu->menu()->setEnabled(true);
|
|
|
|
m_menu->appendGroup(G_ANALYZER_CONTROL);
|
|
m_menu->appendGroup(G_ANALYZER_TOOLS);
|
|
m_menu->appendGroup(G_ANALYZER_REMOTE_TOOLS);
|
|
m_menu->appendGroup(G_ANALYZER_OPTIONS);
|
|
|
|
ActionContainer *touchBar = ActionManager::createTouchBar("Debugger.TouchBar",
|
|
Icons::MACOS_TOUCHBAR_DEBUG.icon());
|
|
ActionManager::actionContainer(Core::Constants::TOUCH_BAR)
|
|
->addMenu(touchBar, Core::Constants::G_TOUCHBAR_OTHER);
|
|
|
|
ActionContainer *menubar = ActionManager::actionContainer(MENU_BAR);
|
|
ActionContainer *mtools = ActionManager::actionContainer(M_TOOLS);
|
|
menubar->addMenu(mtools, m_menu);
|
|
|
|
m_menu->addSeparator(G_ANALYZER_TOOLS);
|
|
m_menu->addSeparator(G_ANALYZER_REMOTE_TOOLS);
|
|
m_menu->addSeparator(G_ANALYZER_OPTIONS);
|
|
|
|
QAction *act;
|
|
|
|
// Populate Windows->Views menu with standard actions.
|
|
act = new QAction(tr("Memory..."), this);
|
|
act->setVisible(false);
|
|
act->setEnabled(false);
|
|
Command *cmd = ActionManager::registerAction(act, Constants::OPEN_MEMORY_EDITOR);
|
|
|
|
TaskHub::addCategory(TASK_CATEGORY_DEBUGGER_DEBUGINFO,
|
|
tr("Debug Information"));
|
|
TaskHub::addCategory(TASK_CATEGORY_DEBUGGER_RUNTIME,
|
|
tr("Debugger Runtime"));
|
|
|
|
QSettings *settings = ICore::settings();
|
|
|
|
m_debuggerSettings = new DebuggerSettings;
|
|
m_debuggerSettings->readSettings();
|
|
|
|
const auto addLabel = [](QWidget *widget, const QString &text) {
|
|
auto vbox = qobject_cast<QVBoxLayout *>(widget->layout());
|
|
QTC_ASSERT(vbox, return);
|
|
auto label = new QLabel(widget);
|
|
label->setText(text);
|
|
label->setMargin(6);
|
|
vbox->insertWidget(0, label);
|
|
};
|
|
|
|
m_breakpointManagerView = new BaseTreeView;
|
|
m_breakpointManagerView->setActivationMode(Utils::DoubleClickActivation);
|
|
m_breakpointManagerView->setIconSize(QSize(10, 10));
|
|
m_breakpointManagerView->setWindowIcon(Icons::BREAKPOINTS.icon());
|
|
m_breakpointManagerView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
|
m_breakpointManagerView->setSettings(settings, "Debugger.BreakWindow");
|
|
m_breakpointManagerView->setRootIsDecorated(true);
|
|
m_breakpointManagerView->setModel(BreakpointManager::model());
|
|
m_breakpointManagerView->setSpanColumn(BreakpointFunctionColumn);
|
|
m_breakpointManagerWindow = addSearch(m_breakpointManagerView);
|
|
m_breakpointManagerWindow->setWindowTitle(tr("Breakpoint Preset"));
|
|
m_breakpointManagerWindow->setObjectName("Debugger.Docks.BreakpointManager");
|
|
addLabel(m_breakpointManagerWindow, m_breakpointManagerWindow->windowTitle());
|
|
|
|
|
|
// Snapshot
|
|
m_engineManagerView = new BaseTreeView;
|
|
m_engineManagerView->setWindowTitle(tr("Running Debuggers"));
|
|
m_engineManagerView->setSettings(settings, "Debugger.SnapshotView");
|
|
m_engineManagerView->setIconSize(QSize(10, 10));
|
|
m_engineManagerView->setModel(m_engineManager.model());
|
|
m_engineManagerWindow = addSearch(m_engineManagerView);
|
|
m_engineManagerWindow->setWindowTitle(tr("Debugger Perspectives"));
|
|
m_engineManagerWindow->setObjectName("Debugger.Docks.Snapshots");
|
|
addLabel(m_engineManagerWindow, m_engineManagerWindow->windowTitle());
|
|
|
|
// Logging
|
|
m_globalLogWindow = new GlobalLogWindow;
|
|
|
|
ActionContainer *debugMenu = ActionManager::actionContainer(PE::M_DEBUG);
|
|
|
|
RunConfiguration::registerAspect<DebuggerRunConfigurationAspect>();
|
|
|
|
// The main "Start Debugging" action. Acts as "Continue" at times.
|
|
connect(&m_startAction, &QAction::triggered, this, [] {
|
|
ProjectExplorerPlugin::runStartupProject(ProjectExplorer::Constants::DEBUG_RUN_MODE, false);
|
|
});
|
|
|
|
connect(&m_debugWithoutDeployAction, &QAction::triggered, this, [] {
|
|
ProjectExplorerPlugin::runStartupProject(ProjectExplorer::Constants::DEBUG_RUN_MODE, true);
|
|
});
|
|
|
|
connect(&m_startAndDebugApplicationAction, &QAction::triggered,
|
|
this, &StartApplicationDialog::startAndDebugApplication);
|
|
|
|
connect(&m_attachToCoreAction, &QAction::triggered,
|
|
this, &DebuggerPluginPrivate::attachCore);
|
|
|
|
connect(&m_attachToRemoteServerAction, &QAction::triggered,
|
|
this, &StartApplicationDialog::attachToRemoteServer);
|
|
|
|
connect(&m_attachToRunningApplication, &QAction::triggered,
|
|
this, &DebuggerPluginPrivate::attachToRunningApplication);
|
|
|
|
connect(&m_attachToUnstartedApplication, &QAction::triggered,
|
|
this, &DebuggerPluginPrivate::attachToUnstartedApplicationDialog);
|
|
|
|
connect(&m_attachToQmlPortAction, &QAction::triggered,
|
|
this, &DebuggerPluginPrivate::attachToQmlPort);
|
|
|
|
connect(&m_startRemoteCdbAction, &QAction::triggered,
|
|
this, &DebuggerPluginPrivate::startRemoteCdbSession);
|
|
|
|
// "Start Debugging" sub-menu
|
|
// groups:
|
|
// G_DEFAULT_ONE
|
|
// MENU_GROUP_START_LOCAL
|
|
// MENU_GROUP_START_REMOTE
|
|
// MENU_GROUP_START_QML
|
|
|
|
ActionContainer *mstart = ActionManager::actionContainer(PE::M_DEBUG_STARTDEBUGGING);
|
|
const QKeySequence startShortcut(useMacShortcuts ? tr("Ctrl+Y") : tr("F5"));
|
|
|
|
|
|
cmd = ActionManager::registerAction(&m_visibleStartAction, "Debugger.Debug");
|
|
|
|
cmd->setDescription(tr("Start Debugging or Continue"));
|
|
cmd->setAttribute(Command::CA_UpdateText);
|
|
cmd->setAttribute(Command::CA_UpdateIcon);
|
|
//mstart->addAction(cmd, CC::G_DEFAULT_ONE);
|
|
|
|
cmd = ActionManager::registerAction(&m_startAction, DEBUGGER_START);
|
|
cmd->setDescription(tr("Start Debugging"));
|
|
cmd->setAttribute(Command::CA_UpdateText);
|
|
cmd->setDefaultKeySequence(startShortcut);
|
|
mstart->addAction(cmd, CC::G_DEFAULT_ONE);
|
|
|
|
m_visibleStartAction.initialize(&m_startAction);
|
|
m_visibleStartAction.setAttribute(ProxyAction::UpdateText);
|
|
m_visibleStartAction.setAttribute(ProxyAction::UpdateIcon);
|
|
m_visibleStartAction.setAction(&m_startAction);
|
|
|
|
m_visibleStartAction.setObjectName("Debug"); // used for UI introduction
|
|
ModeManager::addAction(&m_visibleStartAction, /*priority*/ 90);
|
|
|
|
m_undisturbableAction.setIcon(interruptIcon(false));
|
|
m_undisturbableAction.setEnabled(false);
|
|
|
|
cmd = ActionManager::registerAction(&m_debugWithoutDeployAction,
|
|
"Debugger.DebugWithoutDeploy");
|
|
cmd->setAttribute(Command::CA_Hide);
|
|
mstart->addAction(cmd, CC::G_DEFAULT_ONE);
|
|
|
|
cmd = ActionManager::registerAction(&m_attachToRunningApplication,
|
|
"Debugger.AttachToRemoteProcess");
|
|
cmd->setDescription(tr("Attach to Running Application"));
|
|
mstart->addAction(cmd, MENU_GROUP_GENERAL);
|
|
|
|
cmd = ActionManager::registerAction(&m_attachToUnstartedApplication,
|
|
"Debugger.AttachToUnstartedProcess");
|
|
cmd->setDescription(tr("Attach to Unstarted Application"));
|
|
mstart->addAction(cmd, MENU_GROUP_GENERAL);
|
|
|
|
cmd = ActionManager::registerAction(&m_startAndDebugApplicationAction,
|
|
"Debugger.StartAndDebugApplication");
|
|
cmd->setAttribute(Command::CA_Hide);
|
|
mstart->addAction(cmd, MENU_GROUP_GENERAL);
|
|
|
|
cmd = ActionManager::registerAction(&m_attachToCoreAction,
|
|
"Debugger.AttachCore");
|
|
cmd->setAttribute(Command::CA_Hide);
|
|
mstart->addAction(cmd, MENU_GROUP_GENERAL);
|
|
|
|
cmd = ActionManager::registerAction(&m_attachToRemoteServerAction,
|
|
"Debugger.AttachToRemoteServer");
|
|
cmd->setAttribute(Command::CA_Hide);
|
|
mstart->addAction(cmd, MENU_GROUP_SPECIAL);
|
|
|
|
if (HostOsInfo::isWindowsHost()) {
|
|
cmd = ActionManager::registerAction(&m_startRemoteCdbAction,
|
|
"Debugger.AttachRemoteCdb");
|
|
cmd->setAttribute(Command::CA_Hide);
|
|
mstart->addAction(cmd, MENU_GROUP_SPECIAL);
|
|
}
|
|
|
|
mstart->addSeparator(Context(CC::C_GLOBAL), MENU_GROUP_START_QML);
|
|
|
|
cmd = ActionManager::registerAction(&m_attachToQmlPortAction, "Debugger.AttachToQmlPort");
|
|
cmd->setAttribute(Command::CA_Hide);
|
|
mstart->addAction(cmd, MENU_GROUP_START_QML);
|
|
|
|
act = new QAction(tr("Detach Debugger"), this);
|
|
act->setEnabled(false);
|
|
cmd = ActionManager::registerAction(act, Constants::DETACH);
|
|
debugMenu->addAction(cmd, CC::G_DEFAULT_ONE);
|
|
|
|
act = new QAction(interruptIcon(false), tr("Interrupt"), this);
|
|
act->setEnabled(false);
|
|
cmd = ActionManager::registerAction(act, Constants::INTERRUPT);
|
|
cmd->setDescription(tr("Interrupt Debugger"));
|
|
cmd->setAttribute(Command::CA_UpdateText);
|
|
cmd->setDefaultKeySequence(startShortcut);
|
|
cmd->setTouchBarIcon(Icons::MACOS_TOUCHBAR_DEBUG_INTERRUPT.icon());
|
|
touchBar->addAction(cmd);
|
|
debugMenu->addAction(cmd, CC::G_DEFAULT_ONE);
|
|
|
|
act = new QAction(continueIcon(false), tr("Continue"), this);
|
|
act->setEnabled(false);
|
|
cmd = ActionManager::registerAction(act, Constants::CONTINUE);
|
|
cmd->setAttribute(Command::CA_UpdateText);
|
|
cmd->setDefaultKeySequence(startShortcut);
|
|
cmd->setTouchBarIcon(Icons::MACOS_TOUCHBAR_DEBUG_CONTINUE.icon());
|
|
touchBar->addAction(cmd);
|
|
debugMenu->addAction(cmd, CC::G_DEFAULT_ONE);
|
|
|
|
const QIcon sidebarStopIcon = Icon::sideBarIcon(Icons::STOP, Icons::STOP_FLAT);
|
|
const QIcon stopIcon = Icon::combinedIcon({Icons::DEBUG_EXIT_SMALL.icon(), sidebarStopIcon});
|
|
act = new QAction(stopIcon, tr("Stop Debugger"), this);
|
|
act->setEnabled(false);
|
|
cmd = ActionManager::registerAction(act, Constants::STOP);
|
|
cmd->setTouchBarIcon(Icons::MACOS_TOUCHBAR_DEBUG_EXIT.icon());
|
|
touchBar->addAction(cmd);
|
|
debugMenu->addAction(cmd, CC::G_DEFAULT_ONE);
|
|
m_hiddenStopAction.initialize(cmd->action());
|
|
m_hiddenStopAction.setAttribute(ProxyAction::UpdateText);
|
|
m_hiddenStopAction.setAttribute(ProxyAction::UpdateIcon);
|
|
|
|
cmd = ActionManager::registerAction(&m_hiddenStopAction, "Debugger.HiddenStop");
|
|
cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Shift+Ctrl+Y") : tr("Shift+F5")));
|
|
|
|
act = new QAction(tr("Abort Debugging"), this);
|
|
act->setEnabled(false);
|
|
cmd = ActionManager::registerAction(act, Constants::ABORT);
|
|
cmd->setDescription(tr("Reset Debugger"));
|
|
debugMenu->addAction(cmd, CC::G_DEFAULT_ONE);
|
|
|
|
act = new QAction(Icons::RESTART_TOOLBAR.icon(), tr("Restart Debugging"), this);
|
|
act->setEnabled(false);
|
|
act->setToolTip(tr("Restart the debugging session."));
|
|
cmd = ActionManager::registerAction(act, Constants::RESET);
|
|
cmd->setDescription(tr("Restart Debugging"));
|
|
debugMenu->addAction(cmd, CC::G_DEFAULT_ONE);
|
|
|
|
debugMenu->addSeparator();
|
|
|
|
cmd = ActionManager::registerAction(&m_startAndBreakOnMain,
|
|
"Debugger.StartAndBreakOnMain",
|
|
debuggerNotRunning);
|
|
cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Ctrl+Shift+O") : tr("F10")));
|
|
cmd->setAttribute(Command::CA_Hide);
|
|
debugMenu->addAction(cmd);
|
|
connect(&m_startAndBreakOnMain, &QAction::triggered, this, [] {
|
|
DebuggerRunTool::setBreakOnMainNextTime();
|
|
ProjectExplorerPlugin::runStartupProject(ProjectExplorer::Constants::DEBUG_RUN_MODE, false);
|
|
});
|
|
|
|
act = new QAction(Icons::STEP_OVER.icon(), tr("Step Over"), this);
|
|
act->setEnabled(false);
|
|
cmd = ActionManager::registerAction(act, Constants::NEXT);
|
|
cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Ctrl+Shift+O") : tr("F10")));
|
|
cmd->setTouchBarIcon(Icons::MACOS_TOUCHBAR_DEBUG_STEP_OVER.icon());
|
|
touchBar->addAction(cmd);
|
|
debugMenu->addAction(cmd);
|
|
|
|
act = new QAction(Icons::STEP_INTO.icon(), tr("Step Into"), this);
|
|
act->setEnabled(false);
|
|
cmd = ActionManager::registerAction(act, Constants::STEP);
|
|
cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Ctrl+Shift+I") : tr("F11")));
|
|
cmd->setTouchBarIcon(Icons::MACOS_TOUCHBAR_DEBUG_STEP_INTO.icon());
|
|
touchBar->addAction(cmd);
|
|
debugMenu->addAction(cmd);
|
|
|
|
act = new QAction(Icons::STEP_OUT.icon(), tr("Step Out"), this);
|
|
act->setEnabled(false);
|
|
cmd = ActionManager::registerAction(act, Constants::STEPOUT);
|
|
cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Ctrl+Shift+T") : tr("Shift+F11")));
|
|
cmd->setTouchBarIcon(Icons::MACOS_TOUCHBAR_DEBUG_STEP_OUT.icon());
|
|
touchBar->addAction(cmd);
|
|
debugMenu->addAction(cmd);
|
|
|
|
act = new QAction(tr("Run to Line"), this);
|
|
act->setEnabled(false);
|
|
act->setVisible(false);
|
|
cmd = ActionManager::registerAction(act, Constants::RUNTOLINE);
|
|
cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Shift+F8") : tr("Ctrl+F10")));
|
|
debugMenu->addAction(cmd);
|
|
|
|
act = new QAction(tr("Run to Selected Function"), this);
|
|
act->setEnabled(false);
|
|
act->setEnabled(false);
|
|
cmd = ActionManager::registerAction(act, Constants::RUNTOSELECTEDFUNCTION);
|
|
cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+F6")));
|
|
// Don't add to menu by default as keeping its enabled state
|
|
// and text up-to-date is a lot of hassle.
|
|
// debugMenu->addAction(cmd);
|
|
|
|
act = new QAction(tr("Jump to Line"), this);
|
|
act->setEnabled(false);
|
|
act->setVisible(false);
|
|
cmd = ActionManager::registerAction(act, Constants::JUMPTOLINE);
|
|
debugMenu->addAction(cmd);
|
|
|
|
act = new QAction(tr("Immediately Return From Inner Function"), this);
|
|
act->setEnabled(false);
|
|
act->setVisible(false);
|
|
cmd = ActionManager::registerAction(act, Constants::RETURNFROMFUNCTION);
|
|
debugMenu->addAction(cmd);
|
|
|
|
debugMenu->addSeparator();
|
|
|
|
act = new QAction(this);
|
|
act->setText(QCoreApplication::translate("Debugger::Internal::DebuggerPluginPrivate",
|
|
"Move to Calling Frame"));
|
|
act->setEnabled(false);
|
|
act->setVisible(false);
|
|
ActionManager::registerAction(act, Constants::FRAME_UP);
|
|
|
|
act = new QAction(this);
|
|
act->setText(QCoreApplication::translate("Debugger::Internal::DebuggerPluginPrivate",
|
|
"Move to Called Frame"));
|
|
act->setEnabled(false);
|
|
act->setVisible(false);
|
|
ActionManager::registerAction(act, Constants::FRAME_DOWN);
|
|
|
|
act = new QAction(this);
|
|
act->setText(QCoreApplication::translate("Debugger::Internal::DebuggerEnginePrivate",
|
|
"Operate by Instruction"));
|
|
act->setEnabled(false);
|
|
act->setVisible(false);
|
|
act->setCheckable(true);
|
|
act->setChecked(false);
|
|
cmd = ActionManager::registerAction(act, Constants::OPERATE_BY_INSTRUCTION);
|
|
debugMenu->addAction(cmd);
|
|
|
|
cmd = ActionManager::registerAction(&m_breakAction, "Debugger.ToggleBreak");
|
|
cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("F8") : tr("F9")));
|
|
debugMenu->addAction(cmd);
|
|
connect(&m_breakAction, &QAction::triggered,
|
|
this, &DebuggerPluginPrivate::toggleBreakpointHelper);
|
|
|
|
debugMenu->addSeparator();
|
|
|
|
auto qmlShowAppOnTopDummyAction = new QAction(tr("Show Application on Top"), this);
|
|
qmlShowAppOnTopDummyAction->setCheckable(true);
|
|
qmlShowAppOnTopDummyAction->setIcon(Icons::APP_ON_TOP.icon());
|
|
qmlShowAppOnTopDummyAction->setEnabled(false);
|
|
cmd = ActionManager::registerAction(qmlShowAppOnTopDummyAction, Constants::QML_SHOW_APP_ON_TOP);
|
|
debugMenu->addAction(cmd);
|
|
|
|
auto qmlSelectDummyAction = new QAction(tr("Select"), this);
|
|
qmlSelectDummyAction->setCheckable(true);
|
|
qmlSelectDummyAction->setIcon(Icons::SELECT.icon());
|
|
qmlSelectDummyAction->setEnabled(false);
|
|
cmd = ActionManager::registerAction(qmlSelectDummyAction, Constants::QML_SELECTTOOL);
|
|
debugMenu->addAction(cmd);
|
|
|
|
debugMenu->addSeparator();
|
|
|
|
cmd = m_watchCommand = ActionManager::registerAction(&m_watchAction, Constants::WATCH);
|
|
debugMenu->addAction(cmd);
|
|
|
|
// FIXME: Re-vive watcher creation before engine runs.
|
|
// connect(&m_watchAction, &QAction::triggered, this, [&] {
|
|
// QTC_CHECK(false);
|
|
// });
|
|
|
|
addGdbOptionPages(&m_optionPages);
|
|
addCdbOptionPages(&m_optionPages);
|
|
m_optionPages.append(new LocalsAndExpressionsOptionsPage);
|
|
|
|
connect(ModeManager::instance(), &ModeManager::currentModeAboutToChange, this, [] {
|
|
if (ModeManager::currentModeId() == MODE_DEBUG)
|
|
DebuggerMainWindow::leaveDebugMode();
|
|
});
|
|
|
|
connect(ModeManager::instance(), &ModeManager::currentModeChanged, [](Id mode, Id oldMode) {
|
|
QTC_ASSERT(mode != oldMode, return);
|
|
if (mode == MODE_DEBUG) {
|
|
DebuggerMainWindow::enterDebugMode();
|
|
if (IEditor *editor = EditorManager::currentEditor())
|
|
editor->widget()->setFocus();
|
|
}
|
|
});
|
|
|
|
connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::settingsChanged,
|
|
this, &DebuggerPluginPrivate::updateDebugWithoutDeployMenu);
|
|
|
|
// Debug mode setup
|
|
m_mode = new DebugMode;
|
|
|
|
m_debugModeContext.setContext(Context(CC::C_EDITORMANAGER));
|
|
m_debugModeContext.setWidget(m_mode->widget());
|
|
ICore::addContextObject(&m_debugModeContext);
|
|
|
|
//
|
|
// Connections
|
|
//
|
|
|
|
// Core
|
|
connect(ICore::instance(), &ICore::saveSettingsRequested,
|
|
this, &DebuggerPluginPrivate::writeSettings);
|
|
|
|
// TextEditor
|
|
connect(TextEditorSettings::instance(), &TextEditorSettings::fontSettingsChanged,
|
|
[this](const FontSettings &settings) {
|
|
if (!boolSetting(FontSizeFollowsEditor))
|
|
return;
|
|
qreal size = settings.fontZoom() * settings.fontSize() / 100.;
|
|
QFont font = m_breakpointManagerWindow->font();
|
|
font.setPointSizeF(size);
|
|
m_breakpointManagerWindow->setFont(font);
|
|
m_globalLogWindow->setFont(font);
|
|
m_engineManagerWindow->setFont(font);
|
|
});
|
|
|
|
|
|
// ProjectExplorer
|
|
connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::updateRunActions,
|
|
this, &DebuggerPluginPrivate::updatePresetState);
|
|
|
|
// EditorManager
|
|
connect(EditorManager::instance(), &EditorManager::editorOpened,
|
|
this, &DebuggerPluginPrivate::editorOpened);
|
|
connect(EditorManager::instance(), &EditorManager::currentEditorChanged,
|
|
this, &DebuggerPluginPrivate::updateBreakMenuItem);
|
|
|
|
// Application interaction
|
|
connect(action(SettingsDialog), &QAction::triggered,
|
|
[] { ICore::showOptionsDialog(DEBUGGER_COMMON_SETTINGS_ID); });
|
|
|
|
m_perspective.useSubPerspectiveSwitcher(EngineManager::engineChooser());
|
|
m_perspective.addToolBarAction(&m_startAction);
|
|
|
|
m_perspective.addWindow(m_engineManagerWindow, Perspective::SplitVertical, nullptr);
|
|
m_perspective.addWindow(m_breakpointManagerWindow, Perspective::SplitHorizontal, m_engineManagerWindow);
|
|
m_perspective.addWindow(m_globalLogWindow, Perspective::AddToTab, nullptr, false, Qt::TopDockWidgetArea);
|
|
|
|
setInitialState();
|
|
|
|
connect(SessionManager::instance(), &SessionManager::startupProjectChanged,
|
|
this, &DebuggerPluginPrivate::onStartupProjectChanged);
|
|
connect(EngineManager::instance(), &EngineManager::engineStateChanged,
|
|
this, &DebuggerPluginPrivate::updatePresetState);
|
|
connect(EngineManager::instance(), &EngineManager::currentEngineChanged,
|
|
this, &DebuggerPluginPrivate::updatePresetState);
|
|
|
|
m_optionPages.append(new CommonOptionsPage(m_globalDebuggerOptions));
|
|
|
|
m_globalDebuggerOptions->fromSettings();
|
|
|
|
return true;
|
|
}
|
|
|
|
void setConfigValue(const QString &name, const QVariant &value)
|
|
{
|
|
ICore::settings()->setValue("DebugMode/" + name, value);
|
|
}
|
|
|
|
QVariant configValue(const QString &name)
|
|
{
|
|
return ICore::settings()->value("DebugMode/" + name);
|
|
}
|
|
|
|
void DebuggerPluginPrivate::updatePresetState()
|
|
{
|
|
if (m_shuttingDown)
|
|
return;
|
|
|
|
Project *startupProject = SessionManager::startupProject();
|
|
RunConfiguration *startupRunConfig = RunConfiguration::startupRunConfiguration();
|
|
DebuggerEngine *currentEngine = EngineManager::currentEngine();
|
|
|
|
QString whyNot;
|
|
const bool canRun =
|
|
ProjectExplorerPlugin::canRunStartupProject(ProjectExplorer::Constants::DEBUG_RUN_MODE, &whyNot);
|
|
|
|
QString startupRunConfigName;
|
|
if (startupRunConfig)
|
|
startupRunConfigName = startupRunConfig->displayName();
|
|
if (startupRunConfigName.isEmpty() && startupProject)
|
|
startupRunConfigName = startupProject->displayName();
|
|
|
|
// Restrict width, otherwise Creator gets too wide, see QTCREATORBUG-21885
|
|
const QString startToolTip =
|
|
canRun ? tr("Start debugging of startup project") : whyNot;
|
|
|
|
m_startAction.setToolTip(startToolTip);
|
|
m_startAction.setText(canRun ? startToolTip : tr("Start Debugging"));
|
|
|
|
if (!currentEngine) {
|
|
// No engine running -- or -- we have a running engine but it does not
|
|
// correspond to the current start up project.
|
|
m_startAction.setEnabled(canRun);
|
|
m_startAction.setIcon(startIcon(true));
|
|
m_startAction.setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
|
m_startAction.setVisible(true);
|
|
m_debugWithoutDeployAction.setEnabled(canRun);
|
|
m_visibleStartAction.setAction(&m_startAction);
|
|
m_hiddenStopAction.setAction(&m_undisturbableAction);
|
|
return;
|
|
}
|
|
|
|
QTC_ASSERT(currentEngine, return);
|
|
|
|
// We have a current engine, and it belongs to the startup runconfig.
|
|
// The 'state' bits only affect the fat debug button, not the preset start button.
|
|
m_startAction.setIcon(startIcon(false));
|
|
m_startAction.setEnabled(false);
|
|
m_startAction.setVisible(false);
|
|
|
|
m_debugWithoutDeployAction.setEnabled(canRun);
|
|
|
|
const DebuggerState state = currentEngine->state();
|
|
|
|
if (state == InferiorStopOk) {
|
|
// F5 continues, Shift-F5 kills. It is "continuable".
|
|
m_startAction.setEnabled(false);
|
|
m_debugWithoutDeployAction.setEnabled(false);
|
|
m_visibleStartAction.setAction(ActionManager::command(Constants::CONTINUE)->action());
|
|
m_hiddenStopAction.setAction(ActionManager::command(Constants::STOP)->action());
|
|
} else if (state == InferiorRunOk) {
|
|
// Shift-F5 interrupts. It is also "interruptible".
|
|
m_startAction.setEnabled(false);
|
|
m_debugWithoutDeployAction.setEnabled(false);
|
|
m_visibleStartAction.setAction(ActionManager::command(Constants::INTERRUPT)->action());
|
|
m_hiddenStopAction.setAction(ActionManager::command(Constants::INTERRUPT)->action());
|
|
} else if (state == DebuggerFinished) {
|
|
// We don't want to do anything anymore.
|
|
m_startAction.setEnabled(canRun);
|
|
m_debugWithoutDeployAction.setEnabled(canRun);
|
|
m_visibleStartAction.setAction(ActionManager::command(DEBUGGER_START)->action());
|
|
m_hiddenStopAction.setAction(&m_undisturbableAction);
|
|
} else if (state == InferiorUnrunnable) {
|
|
// We don't want to do anything anymore.
|
|
m_startAction.setEnabled(false);
|
|
m_debugWithoutDeployAction.setEnabled(false);
|
|
m_visibleStartAction.setAction(ActionManager::command(Constants::STOP)->action());
|
|
m_hiddenStopAction.setAction(ActionManager::command(Constants::STOP)->action());
|
|
} else {
|
|
// The startup phase should be over once we are here.
|
|
// But treat it as 'undisturbable if we are here by accident.
|
|
//QTC_CHECK(state != DebuggerNotReady);
|
|
// Everything else is "undisturbable".
|
|
m_startAction.setEnabled(false);
|
|
m_debugWithoutDeployAction.setEnabled(false);
|
|
m_visibleStartAction.setAction(&m_undisturbableAction);
|
|
m_hiddenStopAction.setAction(&m_undisturbableAction);
|
|
}
|
|
|
|
// FIXME: Decentralize the actions below
|
|
const bool actionsEnabled = currentEngine->debuggerActionsEnabled();
|
|
const bool canDeref = actionsEnabled && currentEngine->hasCapability(AutoDerefPointersCapability);
|
|
action(AutoDerefPointers)->setEnabled(canDeref);
|
|
action(AutoDerefPointers)->setEnabled(true);
|
|
action(ExpandStack)->setEnabled(actionsEnabled);
|
|
|
|
m_startAndDebugApplicationAction.setEnabled(true);
|
|
m_attachToQmlPortAction.setEnabled(true);
|
|
m_attachToCoreAction.setEnabled(true);
|
|
m_attachToRemoteServerAction.setEnabled(true);
|
|
m_attachToRunningApplication.setEnabled(true);
|
|
m_attachToUnstartedApplication.setEnabled(true);
|
|
|
|
m_watchAction.setEnabled(state != DebuggerFinished && state != DebuggerNotReady);
|
|
m_breakAction.setEnabled(true);
|
|
}
|
|
|
|
void DebuggerPluginPrivate::onStartupProjectChanged(Project *project)
|
|
{
|
|
RunConfiguration *activeRc = nullptr;
|
|
if (project) {
|
|
Target *target = project->activeTarget();
|
|
if (target)
|
|
activeRc = target->activeRunConfiguration();
|
|
if (!activeRc)
|
|
return;
|
|
}
|
|
for (DebuggerEngine *engine : EngineManager::engines()) {
|
|
// Run controls might be deleted during exit.
|
|
engine->updateState();
|
|
}
|
|
|
|
updatePresetState();
|
|
}
|
|
|
|
void DebuggerPluginPrivate::attachCore()
|
|
{
|
|
AttachCoreDialog dlg(ICore::dialogParent());
|
|
|
|
const QString lastExternalKit = configValue("LastExternalKit").toString();
|
|
if (!lastExternalKit.isEmpty())
|
|
dlg.setKitId(Id::fromString(lastExternalKit));
|
|
dlg.setSymbolFile(configValue("LastExternalExecutableFile").toString());
|
|
dlg.setLocalCoreFile(configValue("LastLocalCoreFile").toString());
|
|
dlg.setRemoteCoreFile(configValue("LastRemoteCoreFile").toString());
|
|
dlg.setOverrideStartScript(configValue("LastExternalStartScript").toString());
|
|
dlg.setForceLocalCoreFile(configValue("LastForceLocalCoreFile").toBool());
|
|
|
|
if (dlg.exec() != QDialog::Accepted)
|
|
return;
|
|
|
|
setConfigValue("LastExternalExecutableFile", dlg.symbolFile());
|
|
setConfigValue("LastLocalCoreFile", dlg.localCoreFile());
|
|
setConfigValue("LastRemoteCoreFile", dlg.remoteCoreFile());
|
|
setConfigValue("LastExternalKit", dlg.kit()->id().toSetting());
|
|
setConfigValue("LastExternalStartScript", dlg.overrideStartScript());
|
|
setConfigValue("LastForceLocalCoreFile", dlg.forcesLocalCoreFile());
|
|
|
|
auto runControl = new RunControl(ProjectExplorer::Constants::DEBUG_RUN_MODE);
|
|
runControl->setKit(dlg.kit());
|
|
runControl->setDisplayName(tr("Core file \"%1\"")
|
|
.arg(dlg.useLocalCoreFile() ? dlg.localCoreFile() : dlg.remoteCoreFile()));
|
|
auto debugger = new DebuggerRunTool(runControl);
|
|
debugger->setInferiorExecutable(dlg.symbolFile());
|
|
debugger->setCoreFileName(dlg.localCoreFile());
|
|
debugger->setStartMode(AttachCore);
|
|
debugger->setCloseMode(DetachAtClose);
|
|
debugger->setOverrideStartScript(dlg.overrideStartScript());
|
|
debugger->startRunControl();
|
|
}
|
|
|
|
void DebuggerPluginPrivate::startRemoteCdbSession()
|
|
{
|
|
const QString connectionKey = "CdbRemoteConnection";
|
|
Kit *kit = findUniversalCdbKit();
|
|
QTC_ASSERT(kit, return);
|
|
|
|
StartRemoteCdbDialog dlg(ICore::dialogParent());
|
|
QString previousConnection = configValue(connectionKey).toString();
|
|
if (previousConnection.isEmpty())
|
|
previousConnection = "localhost:1234";
|
|
dlg.setConnection(previousConnection);
|
|
if (dlg.exec() != QDialog::Accepted)
|
|
return;
|
|
setConfigValue(connectionKey, dlg.connection());
|
|
|
|
auto runControl = new RunControl(ProjectExplorer::Constants::DEBUG_RUN_MODE);
|
|
runControl->setKit(kit);
|
|
auto debugger = new DebuggerRunTool(runControl);
|
|
debugger->setStartMode(AttachToRemoteServer);
|
|
debugger->setCloseMode(KillAtClose);
|
|
debugger->setRemoteChannel(dlg.connection());
|
|
debugger->startRunControl();
|
|
}
|
|
|
|
class RemoteAttachRunner : public DebuggerRunTool
|
|
{
|
|
public:
|
|
RemoteAttachRunner(RunControl *runControl, int pid)
|
|
: DebuggerRunTool(runControl)
|
|
{
|
|
setId("AttachToRunningProcess");
|
|
setUsePortsGatherer(true, false);
|
|
|
|
auto gdbServer = new GdbServerRunner(runControl, portsGatherer());
|
|
gdbServer->setUseMulti(false);
|
|
gdbServer->setDevice(runControl->device());
|
|
gdbServer->setAttachPid(ProcessHandle(pid));
|
|
|
|
addStartDependency(gdbServer);
|
|
|
|
setStartMode(AttachToRemoteProcess);
|
|
setCloseMode(DetachAtClose);
|
|
|
|
// setInferiorExecutable(localExecutable);
|
|
setUseContinueInsteadOfRun(true);
|
|
setContinueAfterAttach(false);
|
|
}
|
|
};
|
|
|
|
void DebuggerPluginPrivate::attachToRunningApplication()
|
|
{
|
|
auto kitChooser = new KitChooser;
|
|
kitChooser->setShowIcons(true);
|
|
|
|
auto dlg = new DeviceProcessesDialog(kitChooser, ICore::dialogParent());
|
|
dlg->addAcceptButton(DeviceProcessesDialog::tr("&Attach to Process"));
|
|
dlg->showAllDevices();
|
|
if (dlg->exec() == QDialog::Rejected) {
|
|
delete dlg;
|
|
return;
|
|
}
|
|
|
|
dlg->setAttribute(Qt::WA_DeleteOnClose);
|
|
Kit *kit = kitChooser->currentKit();
|
|
QTC_ASSERT(kit, return);
|
|
IDevice::ConstPtr device = DeviceKitAspect::device(kit);
|
|
QTC_ASSERT(device, return);
|
|
|
|
DeviceProcessItem process = dlg->currentProcess();
|
|
|
|
if (device->type() == PE::DESKTOP_DEVICE_TYPE) {
|
|
attachToRunningProcess(kit, process, false);
|
|
} else {
|
|
auto runControl = new RunControl(ProjectExplorer::Constants::DEBUG_RUN_MODE);
|
|
runControl->setKit(kit);
|
|
//: %1: PID
|
|
runControl->setDisplayName(tr("Process %1").arg(process.pid));
|
|
auto debugger = new RemoteAttachRunner(runControl, process.pid);
|
|
debugger->startRunControl();
|
|
}
|
|
}
|
|
|
|
void DebuggerPluginPrivate::attachToUnstartedApplicationDialog()
|
|
{
|
|
auto dlg = new UnstartedAppWatcherDialog(ICore::dialogParent());
|
|
|
|
connect(dlg, &QDialog::finished, dlg, &QObject::deleteLater);
|
|
connect(dlg, &UnstartedAppWatcherDialog::processFound, this, [this, dlg] {
|
|
RunControl *rc = attachToRunningProcess(dlg->currentKit(),
|
|
dlg->currentProcess(),
|
|
dlg->continueOnAttach());
|
|
if (!rc)
|
|
return;
|
|
|
|
if (dlg->hideOnAttach())
|
|
connect(rc, &RunControl::stopped, dlg, &UnstartedAppWatcherDialog::startWatching);
|
|
});
|
|
|
|
dlg->show();
|
|
}
|
|
|
|
RunControl *DebuggerPluginPrivate::attachToRunningProcess(Kit *kit,
|
|
DeviceProcessItem process, bool contAfterAttach)
|
|
{
|
|
QTC_ASSERT(kit, return nullptr);
|
|
IDevice::ConstPtr device = DeviceKitAspect::device(kit);
|
|
QTC_ASSERT(device, return nullptr);
|
|
if (process.pid == 0) {
|
|
AsynchronousMessageBox::warning(tr("Warning"), tr("Cannot attach to process with PID 0"));
|
|
return nullptr;
|
|
}
|
|
|
|
const Abi tcAbi = ToolChainKitAspect::targetAbi(kit);
|
|
const bool isWindows = (tcAbi.os() == Abi::WindowsOS);
|
|
if (isWindows && isWinProcessBeingDebugged(process.pid)) {
|
|
AsynchronousMessageBox::warning(
|
|
tr("Process Already Under Debugger Control"),
|
|
tr("The process %1 is already under the control of a debugger.\n"
|
|
"%2 cannot attach to it.").arg(process.pid)
|
|
.arg(Core::Constants::IDE_DISPLAY_NAME));
|
|
return nullptr;
|
|
}
|
|
|
|
if (device->type() != PE::DESKTOP_DEVICE_TYPE) {
|
|
AsynchronousMessageBox::warning(tr("Not a Desktop Device Type"),
|
|
tr("It is only possible to attach to a locally running process."));
|
|
return nullptr;
|
|
}
|
|
|
|
auto runControl = new RunControl(ProjectExplorer::Constants::DEBUG_RUN_MODE);
|
|
runControl->setKit(kit);
|
|
//: %1: PID
|
|
runControl->setDisplayName(tr("Process %1").arg(process.pid));
|
|
auto debugger = new DebuggerRunTool(runControl);
|
|
debugger->setAttachPid(ProcessHandle(process.pid));
|
|
debugger->setInferiorExecutable(process.exe);
|
|
debugger->setInferiorDevice(device);
|
|
debugger->setStartMode(AttachExternal);
|
|
debugger->setCloseMode(DetachAtClose);
|
|
debugger->setContinueAfterAttach(contAfterAttach);
|
|
|
|
debugger->startRunControl();
|
|
|
|
return debugger->runControl();
|
|
}
|
|
|
|
void DebuggerPlugin::attachExternalApplication(RunControl *rc)
|
|
{
|
|
ProcessHandle pid = rc->applicationProcessHandle();
|
|
auto runControl = new RunControl(ProjectExplorer::Constants::DEBUG_RUN_MODE);
|
|
runControl->setTarget(rc->target());
|
|
auto debugger = new DebuggerRunTool(runControl);
|
|
debugger->setAttachPid(pid);
|
|
debugger->setRunControlName(tr("Process %1").arg(pid.pid()));
|
|
debugger->setStartMode(AttachExternal);
|
|
debugger->setCloseMode(DetachAtClose);
|
|
debugger->startRunControl();
|
|
}
|
|
|
|
void DebuggerPlugin::getEnginesState(QByteArray *json) const
|
|
{
|
|
QTC_ASSERT(json, return);
|
|
QVariantMap result {
|
|
{"version", 1}
|
|
};
|
|
QVariantMap states;
|
|
|
|
int i = 0;
|
|
DebuggerEngine *currentEngine = EngineManager::currentEngine();
|
|
for (DebuggerEngine *engine : EngineManager::engines()) {
|
|
states[QString::number(i)] = QVariantMap({
|
|
{"current", engine == currentEngine},
|
|
{"pid", engine->inferiorPid()},
|
|
{"state", engine->state()}
|
|
});
|
|
++i;
|
|
}
|
|
|
|
if (!states.isEmpty())
|
|
result["states"] = states;
|
|
|
|
*json = QJsonDocument(QJsonObject::fromVariantMap(result)).toJson();
|
|
}
|
|
|
|
void DebuggerPluginPrivate::attachToQmlPort()
|
|
{
|
|
AttachToQmlPortDialog dlg(ICore::mainWindow());
|
|
|
|
const QVariant qmlServerPort = configValue("LastQmlServerPort");
|
|
if (qmlServerPort.isValid())
|
|
dlg.setPort(qmlServerPort.toInt());
|
|
else
|
|
dlg.setPort(-1);
|
|
|
|
const Id kitId = Id::fromSetting(configValue("LastProfile"));
|
|
if (kitId.isValid())
|
|
dlg.setKitId(kitId);
|
|
|
|
if (dlg.exec() != QDialog::Accepted)
|
|
return;
|
|
|
|
Kit *kit = dlg.kit();
|
|
QTC_ASSERT(kit, return);
|
|
setConfigValue("LastQmlServerPort", dlg.port());
|
|
setConfigValue("LastProfile", kit->id().toSetting());
|
|
|
|
IDevice::ConstPtr device = DeviceKitAspect::device(kit);
|
|
QTC_ASSERT(device, return);
|
|
|
|
auto runControl = new RunControl(ProjectExplorer::Constants::DEBUG_RUN_MODE);
|
|
runControl->setKit(kit);
|
|
auto debugger = new DebuggerRunTool(runControl);
|
|
|
|
QUrl qmlServer = device->toolControlChannel(IDevice::QmlControlChannel);
|
|
qmlServer.setPort(dlg.port());
|
|
debugger->setQmlServer(qmlServer);
|
|
|
|
QSsh::SshConnectionParameters sshParameters = device->sshParameters();
|
|
debugger->setRemoteChannel(sshParameters.host(), sshParameters.port());
|
|
debugger->setStartMode(AttachToQmlServer);
|
|
|
|
debugger->startRunControl();
|
|
}
|
|
|
|
void DebuggerPluginPrivate::runScheduled()
|
|
{
|
|
for (DebuggerRunTool *debugger : m_scheduledStarts)
|
|
debugger->startRunControl();
|
|
}
|
|
|
|
void DebuggerPluginPrivate::editorOpened(IEditor *editor)
|
|
{
|
|
if (auto widget = qobject_cast<TextEditorWidget *>(editor->widget())) {
|
|
connect(widget, &TextEditorWidget::markRequested,
|
|
this, &DebuggerPluginPrivate::requestMark);
|
|
|
|
connect(widget, &TextEditorWidget::markContextMenuRequested,
|
|
this, &DebuggerPluginPrivate::requestContextMenu);
|
|
}
|
|
}
|
|
|
|
void DebuggerPluginPrivate::updateBreakMenuItem(IEditor *editor)
|
|
{
|
|
BaseTextEditor *textEditor = qobject_cast<BaseTextEditor *>(editor);
|
|
m_breakAction.setEnabled(textEditor != nullptr);
|
|
}
|
|
|
|
void DebuggerPluginPrivate::requestContextMenu(TextEditorWidget *widget,
|
|
int lineNumber, QMenu *menu)
|
|
{
|
|
TextDocument *document = widget->textDocument();
|
|
|
|
const ContextData args = getLocationContext(document, lineNumber);
|
|
const GlobalBreakpoint gbp = BreakpointManager::findBreakpointFromContext(args);
|
|
|
|
if (gbp) {
|
|
|
|
// Remove existing breakpoint.
|
|
auto act = menu->addAction(tr("Remove Breakpoint"));
|
|
connect(act, &QAction::triggered, [gbp] { gbp->deleteBreakpoint(); });
|
|
|
|
// Enable/disable existing breakpoint.
|
|
if (gbp->isEnabled()) {
|
|
act = menu->addAction(tr("Disable Breakpoint"));
|
|
connect(act, &QAction::triggered, [gbp] { gbp->setEnabled(false); });
|
|
} else {
|
|
act = menu->addAction(tr("Enable Breakpoint"));
|
|
connect(act, &QAction::triggered, [gbp] { gbp->setEnabled(true); });
|
|
}
|
|
|
|
// Edit existing breakpoint.
|
|
act = menu->addAction(tr("Edit Breakpoint..."));
|
|
connect(act, &QAction::triggered, [gbp] {
|
|
BreakpointManager::editBreakpoint(gbp, ICore::dialogParent());
|
|
});
|
|
|
|
} else {
|
|
// Handle non-existing breakpoint.
|
|
const QString text = args.address
|
|
? tr("Set Breakpoint at 0x%1").arg(args.address, 0, 16)
|
|
: tr("Set Breakpoint at Line %1").arg(lineNumber);
|
|
auto act = menu->addAction(text);
|
|
act->setEnabled(args.isValid());
|
|
connect(act, &QAction::triggered, [this, args] {
|
|
breakpointSetMarginActionTriggered(false, args);
|
|
});
|
|
|
|
// Message trace point
|
|
const QString tracePointText = args.address
|
|
? tr("Set Message Tracepoint at 0x%1...").arg(args.address, 0, 16)
|
|
: tr("Set Message Tracepoint at Line %1...").arg(lineNumber);
|
|
act = menu->addAction(tracePointText);
|
|
act->setEnabled(args.isValid());
|
|
connect(act, &QAction::triggered, [this, args] {
|
|
breakpointSetMarginActionTriggered(true, args);
|
|
});
|
|
}
|
|
|
|
// Run to, jump to line below in stopped state.
|
|
for (const QPointer<DebuggerEngine> engine : EngineManager::engines()) {
|
|
if (engine->state() == InferiorStopOk && args.isValid()) {
|
|
menu->addSeparator();
|
|
if (engine->hasCapability(RunToLineCapability)) {
|
|
auto act = menu->addAction(args.address
|
|
? DebuggerEngine::tr("Run to Address 0x%1").arg(args.address, 0, 16)
|
|
: DebuggerEngine::tr("Run to Line %1").arg(args.lineNumber));
|
|
connect(act, &QAction::triggered, this, [args, engine] {
|
|
QTC_ASSERT(engine, return);
|
|
engine->executeRunToLine(args);
|
|
});
|
|
}
|
|
if (engine->hasCapability(JumpToLineCapability)) {
|
|
auto act = menu->addAction(args.address
|
|
? DebuggerEngine::tr("Jump to Address 0x%1").arg(args.address, 0, 16)
|
|
: DebuggerEngine::tr("Jump to Line %1").arg(args.lineNumber));
|
|
connect(act, &QAction::triggered, this, [args, engine] {
|
|
QTC_ASSERT(engine, return);
|
|
engine->executeJumpToLine(args);
|
|
});
|
|
}
|
|
// Disassemble current function in stopped state.
|
|
if (engine->hasCapability(DisassemblerCapability)) {
|
|
StackFrame frame;
|
|
frame.function = cppFunctionAt(args.fileName, lineNumber, 1);
|
|
frame.line = 42; // trick gdb into mixed mode.
|
|
if (!frame.function.isEmpty()) {
|
|
const QString text = tr("Disassemble Function \"%1\"")
|
|
.arg(frame.function);
|
|
auto act = new QAction(text, menu);
|
|
connect(act, &QAction::triggered, this, [frame, engine] {
|
|
QTC_ASSERT(engine, return);
|
|
engine->openDisassemblerView(Location(frame));
|
|
});
|
|
menu->addAction(act);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DebuggerPluginPrivate::toggleBreakpointHelper()
|
|
{
|
|
BaseTextEditor *textEditor = BaseTextEditor::currentTextEditor();
|
|
QTC_ASSERT(textEditor, return);
|
|
const int lineNumber = textEditor->currentLine();
|
|
ContextData location = getLocationContext(textEditor->textDocument(), lineNumber);
|
|
if (location.isValid())
|
|
BreakpointManager::toggleBreakpoint(location);
|
|
}
|
|
|
|
void DebuggerPluginPrivate::requestMark(TextEditorWidget *widget, int lineNumber,
|
|
TextMarkRequestKind kind)
|
|
{
|
|
if (kind == BreakpointRequest) {
|
|
ContextData location = getLocationContext(widget->textDocument(), lineNumber);
|
|
if (location.isValid())
|
|
BreakpointManager::toggleBreakpoint(location);
|
|
}
|
|
}
|
|
|
|
void DebuggerPluginPrivate::setInitialState()
|
|
{
|
|
m_startAndDebugApplicationAction.setEnabled(true);
|
|
m_attachToQmlPortAction.setEnabled(true);
|
|
m_attachToCoreAction.setEnabled(true);
|
|
m_attachToRemoteServerAction.setEnabled(true);
|
|
m_attachToRunningApplication.setEnabled(true);
|
|
m_attachToUnstartedApplication.setEnabled(true);
|
|
|
|
m_watchAction.setEnabled(false);
|
|
m_breakAction.setEnabled(false);
|
|
//m_snapshotAction.setEnabled(false);
|
|
|
|
action(AutoDerefPointers)->setEnabled(true);
|
|
action(ExpandStack)->setEnabled(false);
|
|
}
|
|
|
|
void DebuggerPluginPrivate::updateDebugWithoutDeployMenu()
|
|
{
|
|
const bool state = ProjectExplorerPlugin::projectExplorerSettings().deployBeforeRun;
|
|
m_debugWithoutDeployAction.setVisible(state);
|
|
}
|
|
|
|
void DebuggerPluginPrivate::dumpLog()
|
|
{
|
|
DebuggerEngine *engine = EngineManager::currentEngine();
|
|
if (!engine)
|
|
return;
|
|
LogWindow *logWindow = engine->logWindow();
|
|
QTC_ASSERT(logWindow, return);
|
|
|
|
QString fileName = QFileDialog::getSaveFileName(ICore::mainWindow(),
|
|
tr("Save Debugger Log"), Utils::TemporaryDirectory::masterDirectoryPath());
|
|
if (fileName.isEmpty())
|
|
return;
|
|
FileSaver saver(fileName);
|
|
if (!saver.hasError()) {
|
|
QTextStream ts(saver.file());
|
|
ts << logWindow->inputContents();
|
|
ts << "\n\n=======================================\n\n";
|
|
ts << logWindow->combinedContents();
|
|
saver.setResult(&ts);
|
|
}
|
|
saver.finalize(ICore::mainWindow());
|
|
}
|
|
|
|
void DebuggerPluginPrivate::aboutToShutdown()
|
|
{
|
|
m_shuttingDown = true;
|
|
|
|
disconnect(SessionManager::instance(), &SessionManager::startupProjectChanged, this, nullptr);
|
|
|
|
m_shutdownTimer.setInterval(0);
|
|
m_shutdownTimer.setSingleShot(true);
|
|
connect(&m_shutdownTimer, &QTimer::timeout, this, &DebuggerPluginPrivate::doShutdown);
|
|
if (EngineManager::shutDown()) {
|
|
// If any engine is aborting we give them extra three seconds.
|
|
m_shutdownTimer.setInterval(3000);
|
|
}
|
|
m_shutdownTimer.start();
|
|
}
|
|
|
|
static void createNewDock(QWidget *widget)
|
|
{
|
|
auto dockWidget = new QDockWidget;
|
|
dockWidget->setWidget(widget);
|
|
dockWidget->setWindowTitle(widget->windowTitle());
|
|
dockWidget->setFeatures(QDockWidget::DockWidgetClosable);
|
|
dockWidget->show();
|
|
}
|
|
|
|
void DebuggerPluginPrivate::remoteCommand(const QStringList &options)
|
|
{
|
|
if (options.isEmpty())
|
|
return;
|
|
|
|
QString errorMessage;
|
|
|
|
if (!parseArguments(options, &errorMessage)) {
|
|
qWarning("%s", qPrintable(errorMessage));
|
|
return;
|
|
}
|
|
runScheduled();
|
|
}
|
|
|
|
QMessageBox *showMessageBox(int icon, const QString &title,
|
|
const QString &text, int buttons)
|
|
{
|
|
QMessageBox *mb = new QMessageBox(QMessageBox::Icon(icon),
|
|
title, text, QMessageBox::StandardButtons(buttons),
|
|
ICore::mainWindow());
|
|
mb->setAttribute(Qt::WA_DeleteOnClose);
|
|
mb->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
|
mb->show();
|
|
return mb;
|
|
}
|
|
|
|
void addDebugInfoTask(unsigned id, const QString &cmd)
|
|
{
|
|
dd->m_debugInfoTaskHandler.addTask(id, cmd);
|
|
}
|
|
|
|
void DebuggerPluginPrivate::extensionsInitialized()
|
|
{
|
|
// If the CppEditor or QmlJS editor plugin is there, we want to add something to
|
|
// the editor context menu.
|
|
for (Id menuId : { CppEditor::Constants::M_CONTEXT, QmlJSEditor::Constants::M_CONTEXT }) {
|
|
if (ActionContainer *editorContextMenu = ActionManager::actionContainer(menuId)) {
|
|
auto cmd = editorContextMenu->addSeparator(m_watchCommand->context());
|
|
cmd->setAttribute(Command::CA_Hide);
|
|
cmd = m_watchCommand;
|
|
cmd->action()->setEnabled(true);
|
|
editorContextMenu->addAction(cmd);
|
|
cmd->setAttribute(Command::CA_Hide);
|
|
cmd->setAttribute(Command::CA_NonConfigurable);
|
|
}
|
|
}
|
|
|
|
auto constraint = [](RunConfiguration *runConfig) {
|
|
Runnable runnable = runConfig->runnable();
|
|
if (runnable.device && runnable.device->type() == ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE)
|
|
return true;
|
|
|
|
if (DeviceTypeKitAspect::deviceTypeId(runConfig->target()->kit())
|
|
== ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE)
|
|
return true;
|
|
|
|
QString mainScript = runConfig->property("mainScript").toString();
|
|
const bool isDebuggableScript = mainScript.endsWith(".py"); // Only Python for now.
|
|
return isDebuggableScript;
|
|
};
|
|
|
|
RunControl::registerWorker<DebuggerRunTool>
|
|
(ProjectExplorer::Constants::DEBUG_RUN_MODE, constraint);
|
|
|
|
DebuggerMainWindow::ensureMainWindowExists();
|
|
}
|
|
|
|
SavedAction *action(int code)
|
|
{
|
|
return dd->m_debuggerSettings->item(code);
|
|
}
|
|
|
|
bool boolSetting(int code)
|
|
{
|
|
return dd->m_debuggerSettings->item(code)->value().toBool();
|
|
}
|
|
|
|
QString stringSetting(int code)
|
|
{
|
|
return dd->m_debuggerSettings->item(code)->value().toString();
|
|
}
|
|
|
|
QStringList stringListSetting(int code)
|
|
{
|
|
return dd->m_debuggerSettings->item(code)->value().toStringList();
|
|
}
|
|
|
|
void showModuleSymbols(const QString &moduleName, const Symbols &symbols)
|
|
{
|
|
auto w = new QTreeWidget;
|
|
w->setUniformRowHeights(true);
|
|
w->setColumnCount(5);
|
|
w->setRootIsDecorated(false);
|
|
w->setAlternatingRowColors(true);
|
|
w->setSortingEnabled(true);
|
|
w->setObjectName("Symbols." + moduleName);
|
|
QStringList header;
|
|
header.append(DebuggerPlugin::tr("Symbol"));
|
|
header.append(DebuggerPlugin::tr("Address"));
|
|
header.append(DebuggerPlugin::tr("Code"));
|
|
header.append(DebuggerPlugin::tr("Section"));
|
|
header.append(DebuggerPlugin::tr("Name"));
|
|
w->setHeaderLabels(header);
|
|
w->setWindowTitle(DebuggerPlugin::tr("Symbols in \"%1\"").arg(moduleName));
|
|
for (const Symbol &s : symbols) {
|
|
auto it = new QTreeWidgetItem;
|
|
it->setData(0, Qt::DisplayRole, s.name);
|
|
it->setData(1, Qt::DisplayRole, s.address);
|
|
it->setData(2, Qt::DisplayRole, s.state);
|
|
it->setData(3, Qt::DisplayRole, s.section);
|
|
it->setData(4, Qt::DisplayRole, s.demangled);
|
|
w->addTopLevelItem(it);
|
|
}
|
|
createNewDock(w);
|
|
}
|
|
|
|
void showModuleSections(const QString &moduleName, const Sections §ions)
|
|
{
|
|
auto w = new QTreeWidget;
|
|
w->setUniformRowHeights(true);
|
|
w->setColumnCount(5);
|
|
w->setRootIsDecorated(false);
|
|
w->setAlternatingRowColors(true);
|
|
w->setSortingEnabled(true);
|
|
w->setObjectName("Sections." + moduleName);
|
|
QStringList header;
|
|
header.append(DebuggerPlugin::tr("Name"));
|
|
header.append(DebuggerPlugin::tr("From"));
|
|
header.append(DebuggerPlugin::tr("To"));
|
|
header.append(DebuggerPlugin::tr("Address"));
|
|
header.append(DebuggerPlugin::tr("Flags"));
|
|
w->setHeaderLabels(header);
|
|
w->setWindowTitle(DebuggerPlugin::tr("Sections in \"%1\"").arg(moduleName));
|
|
for (const Section &s : sections) {
|
|
auto it = new QTreeWidgetItem;
|
|
it->setData(0, Qt::DisplayRole, s.name);
|
|
it->setData(1, Qt::DisplayRole, s.from);
|
|
it->setData(2, Qt::DisplayRole, s.to);
|
|
it->setData(3, Qt::DisplayRole, s.address);
|
|
it->setData(4, Qt::DisplayRole, s.flags);
|
|
w->addTopLevelItem(it);
|
|
}
|
|
createNewDock(w);
|
|
}
|
|
|
|
void DebuggerPluginPrivate::doShutdown()
|
|
{
|
|
DebuggerMainWindow::doShutdown();
|
|
|
|
m_shutdownTimer.stop();
|
|
|
|
delete m_mode;
|
|
m_mode = nullptr;
|
|
emit m_plugin->asynchronousShutdownFinished();
|
|
}
|
|
|
|
void openTextEditor(const QString &titlePattern0, const QString &contents)
|
|
{
|
|
if (dd->m_shuttingDown)
|
|
return;
|
|
QString titlePattern = titlePattern0;
|
|
IEditor *editor = EditorManager::openEditorWithContents(
|
|
CC::K_DEFAULT_TEXT_EDITOR_ID, &titlePattern, contents.toUtf8(), QString(),
|
|
EditorManager::IgnoreNavigationHistory);
|
|
if (auto textEditor = qobject_cast<BaseTextEditor *>(editor)) {
|
|
QString suggestion = titlePattern;
|
|
if (!suggestion.contains('.'))
|
|
suggestion.append(".txt");
|
|
textEditor->textDocument()->setFallbackSaveAsFileName(suggestion);
|
|
}
|
|
QTC_ASSERT(editor, return);
|
|
}
|
|
|
|
QSharedPointer<Internal::GlobalDebuggerOptions> globalDebuggerOptions()
|
|
{
|
|
return dd->m_globalDebuggerOptions;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
//
|
|
// DebuggerPlugin
|
|
//
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
/*!
|
|
\class Debugger::DebuggerPlugin
|
|
|
|
This is the "external" interface of the debugger plugin that's visible
|
|
from Qt Creator core. The internal interface to global debugger
|
|
functionality that is used by debugger views and debugger engines
|
|
is DebuggerCore, implemented in DebuggerPluginPrivate.
|
|
*/
|
|
|
|
static DebuggerPlugin *m_instance = nullptr;
|
|
|
|
DebuggerPlugin::DebuggerPlugin()
|
|
{
|
|
setObjectName("DebuggerPlugin");
|
|
m_instance = this;
|
|
}
|
|
|
|
DebuggerPlugin::~DebuggerPlugin()
|
|
{
|
|
delete dd;
|
|
dd = nullptr;
|
|
m_instance = nullptr;
|
|
}
|
|
|
|
DebuggerPlugin *DebuggerPlugin::instance()
|
|
{
|
|
return m_instance;
|
|
}
|
|
|
|
bool DebuggerPlugin::initialize(const QStringList &arguments, QString *errorMessage)
|
|
{
|
|
dd = new DebuggerPluginPrivate(this);
|
|
|
|
// Needed for call from AppOutputPane::attachToRunControl() and GammarayIntegration.
|
|
ExtensionSystem::PluginManager::addObject(this);
|
|
|
|
// Menu groups
|
|
ActionContainer *mstart = ActionManager::actionContainer(PE::M_DEBUG_STARTDEBUGGING);
|
|
mstart->appendGroup(MENU_GROUP_GENERAL);
|
|
mstart->appendGroup(MENU_GROUP_SPECIAL);
|
|
mstart->appendGroup(MENU_GROUP_START_QML);
|
|
|
|
// Separators
|
|
mstart->addSeparator(MENU_GROUP_GENERAL);
|
|
mstart->addSeparator(MENU_GROUP_SPECIAL);
|
|
|
|
// Task integration.
|
|
//: Category under which Analyzer tasks are listed in Issues view
|
|
ProjectExplorer::TaskHub::addCategory(Debugger::Constants::ANALYZERTASK_ID, tr("Debugger"));
|
|
|
|
return dd->initialize(arguments, errorMessage);
|
|
}
|
|
|
|
IPlugin::ShutdownFlag DebuggerPlugin::aboutToShutdown()
|
|
{
|
|
ExtensionSystem::PluginManager::removeObject(this);
|
|
dd->aboutToShutdown();
|
|
return AsynchronousShutdown;
|
|
}
|
|
|
|
QObject *DebuggerPlugin::remoteCommand(const QStringList &options,
|
|
const QString &workingDirectory,
|
|
const QStringList &list)
|
|
{
|
|
Q_UNUSED(workingDirectory);
|
|
Q_UNUSED(list);
|
|
dd->remoteCommand(options);
|
|
return nullptr;
|
|
}
|
|
|
|
void DebuggerPlugin::extensionsInitialized()
|
|
{
|
|
dd->extensionsInitialized();
|
|
}
|
|
|
|
} // namespace Internal
|
|
|
|
static bool buildTypeAccepted(QFlags<ToolMode> toolMode, BuildConfiguration::BuildType buildType)
|
|
{
|
|
if (buildType == BuildConfiguration::Unknown)
|
|
return true;
|
|
if (buildType == BuildConfiguration::Debug && (toolMode & DebugMode))
|
|
return true;
|
|
if (buildType == BuildConfiguration::Release && (toolMode & ReleaseMode))
|
|
return true;
|
|
if (buildType == BuildConfiguration::Profile && (toolMode & ProfileMode))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static BuildConfiguration::BuildType startupBuildType()
|
|
{
|
|
BuildConfiguration::BuildType buildType = BuildConfiguration::Unknown;
|
|
if (RunConfiguration *runConfig = RunConfiguration::startupRunConfiguration()) {
|
|
if (const BuildConfiguration *buildConfig = runConfig->target()->activeBuildConfiguration())
|
|
buildType = buildConfig->buildType();
|
|
}
|
|
return buildType;
|
|
}
|
|
|
|
void showCannotStartDialog(const QString &text)
|
|
{
|
|
auto errorDialog = new QMessageBox(ICore::mainWindow());
|
|
errorDialog->setAttribute(Qt::WA_DeleteOnClose);
|
|
errorDialog->setIcon(QMessageBox::Warning);
|
|
errorDialog->setWindowTitle(text);
|
|
errorDialog->setText(DebuggerPlugin::tr("Cannot start %1 without a project. Please open the project "
|
|
"and try again.").arg(text));
|
|
errorDialog->setStandardButtons(QMessageBox::Ok);
|
|
errorDialog->setDefaultButton(QMessageBox::Ok);
|
|
errorDialog->show();
|
|
}
|
|
|
|
bool wantRunTool(ToolMode toolMode, const QString &toolName)
|
|
{
|
|
// Check the project for whether the build config is in the correct mode
|
|
// if not, notify the user and urge him to use the correct mode.
|
|
BuildConfiguration::BuildType buildType = startupBuildType();
|
|
if (!buildTypeAccepted(toolMode, buildType)) {
|
|
QString currentMode;
|
|
switch (buildType) {
|
|
case BuildConfiguration::Debug:
|
|
currentMode = DebuggerPlugin::tr("Debug");
|
|
break;
|
|
case BuildConfiguration::Profile:
|
|
currentMode = DebuggerPlugin::tr("Profile");
|
|
break;
|
|
case BuildConfiguration::Release:
|
|
currentMode = DebuggerPlugin::tr("Release");
|
|
break;
|
|
default:
|
|
QTC_CHECK(false);
|
|
}
|
|
|
|
QString toolModeString;
|
|
switch (toolMode) {
|
|
case DebugMode:
|
|
toolModeString = DebuggerPlugin::tr("in Debug mode");
|
|
break;
|
|
case ProfileMode:
|
|
toolModeString = DebuggerPlugin::tr("in Profile mode");
|
|
break;
|
|
case ReleaseMode:
|
|
toolModeString = DebuggerPlugin::tr("in Release mode");
|
|
break;
|
|
case SymbolsMode:
|
|
toolModeString = DebuggerPlugin::tr("with debug symbols (Debug or Profile mode)");
|
|
break;
|
|
case OptimizedMode:
|
|
toolModeString = DebuggerPlugin::tr("on optimized code (Profile or Release mode)");
|
|
break;
|
|
default:
|
|
QTC_CHECK(false);
|
|
}
|
|
const QString title = DebuggerPlugin::tr("Run %1 in %2 Mode?").arg(toolName).arg(currentMode);
|
|
const QString message = DebuggerPlugin::tr("<html><head/><body><p>You are trying "
|
|
"to run the tool \"%1\" on an application in %2 mode. "
|
|
"The tool is designed to be used %3.</p><p>"
|
|
"Run-time characteristics differ significantly between "
|
|
"optimized and non-optimized binaries. Analytical "
|
|
"findings for one mode may or may not be relevant for "
|
|
"the other.</p><p>"
|
|
"Running tools that need debug symbols on binaries that "
|
|
"don't provide any may lead to missing function names "
|
|
"or otherwise insufficient output.</p><p>"
|
|
"Do you want to continue and run the tool in %2 mode?</p></body></html>")
|
|
.arg(toolName).arg(currentMode).arg(toolModeString);
|
|
if (Utils::CheckableMessageBox::doNotAskAgainQuestion(ICore::mainWindow(),
|
|
title, message, ICore::settings(), "AnalyzerCorrectModeWarning")
|
|
!= QDialogButtonBox::Yes)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
QAction *createStartAction()
|
|
{
|
|
auto action = new QAction(DebuggerMainWindow::tr("Start"), DebuggerPlugin::instance());
|
|
action->setIcon(ProjectExplorer::Icons::ANALYZER_START_SMALL_TOOLBAR.icon());
|
|
action->setEnabled(true);
|
|
return action;
|
|
}
|
|
|
|
QAction *createStopAction()
|
|
{
|
|
auto action = new QAction(DebuggerMainWindow::tr("Stop"), DebuggerPlugin::instance());
|
|
action->setIcon(Utils::Icons::STOP_SMALL_TOOLBAR.icon());
|
|
action->setEnabled(true);
|
|
return action;
|
|
}
|
|
|
|
void enableMainWindow(bool on)
|
|
{
|
|
DebuggerMainWindow::instance()->setEnabled(on);
|
|
}
|
|
|
|
void showStatusMessage(const QString &message, int timeoutMS)
|
|
{
|
|
DebuggerMainWindow::showStatusMessage(message, timeoutMS);
|
|
}
|
|
|
|
void showPermanentStatusMessage(const QString &message)
|
|
{
|
|
DebuggerMainWindow::showStatusMessage(message, -1);
|
|
}
|
|
|
|
namespace Internal {
|
|
|
|
static bool s_testRun = false;
|
|
bool isTestRun() { return s_testRun; }
|
|
|
|
#ifdef WITH_TESTS
|
|
|
|
class DebuggerUnitTests : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
DebuggerUnitTests() = default;
|
|
|
|
private slots:
|
|
void initTestCase();
|
|
void cleanupTestCase();
|
|
|
|
void testDebuggerMatching_data();
|
|
void testDebuggerMatching();
|
|
|
|
void testBenchmark();
|
|
void testStateMachine();
|
|
|
|
private:
|
|
CppTools::Tests::TemporaryCopiedDir *m_tmpDir = nullptr;
|
|
};
|
|
|
|
void DebuggerUnitTests::initTestCase()
|
|
{
|
|
// const QList<Kit *> allKits = KitManager::kits();
|
|
// if (allKits.count() != 1)
|
|
// QSKIP("This test requires exactly one kit to be present");
|
|
// const ToolChain * const toolchain = ToolChainKitAspect::toolChain(allKits.first());
|
|
// if (!toolchain)
|
|
// QSKIP("This test requires that there is a kit with a toolchain.");
|
|
// bool hasClangExecutable;
|
|
// clangExecutableFromSettings(toolchain->typeId(), &hasClangExecutable);
|
|
// if (!hasClangExecutable)
|
|
// QSKIP("No clang suitable for analyzing found");
|
|
|
|
s_testRun = true;
|
|
m_tmpDir = new CppTools::Tests::TemporaryCopiedDir(":/unit-tests");
|
|
QVERIFY(m_tmpDir->isValid());
|
|
}
|
|
|
|
void DebuggerUnitTests::cleanupTestCase()
|
|
{
|
|
delete m_tmpDir;
|
|
}
|
|
|
|
void DebuggerUnitTests::testStateMachine()
|
|
{
|
|
QString proFile = m_tmpDir->absolutePath("simple/simple.pro");
|
|
|
|
CppTools::Tests::ProjectOpenerAndCloser projectManager;
|
|
const CppTools::ProjectInfo projectInfo = projectManager.open(proFile, true);
|
|
QVERIFY(projectInfo.isValid());
|
|
|
|
QEventLoop loop;
|
|
connect(BuildManager::instance(), &BuildManager::buildQueueFinished,
|
|
&loop, &QEventLoop::quit);
|
|
ProjectExplorerPlugin::buildProject(SessionManager::startupProject());
|
|
loop.exec();
|
|
|
|
ExecuteOnDestruction guard([] { EditorManager::closeAllEditors(false); });
|
|
|
|
Target *t = SessionManager::startupProject()->activeTarget();
|
|
QVERIFY(t);
|
|
RunConfiguration *rc = t->activeRunConfiguration();
|
|
QVERIFY(rc);
|
|
|
|
auto runControl = new RunControl(ProjectExplorer::Constants::DEBUG_RUN_MODE);
|
|
runControl->setRunConfiguration(rc);
|
|
auto debugger = new DebuggerRunTool(runControl);
|
|
|
|
debugger->setInferior(rc->runnable());
|
|
debugger->setTestCase(TestNoBoundsOfCurrentFunction);
|
|
|
|
connect(debugger, &DebuggerRunTool::stopped,
|
|
&QTestEventLoop::instance(), &QTestEventLoop::exitLoop);
|
|
|
|
debugger->startRunControl();
|
|
|
|
QTestEventLoop::instance().enterLoop(5);
|
|
}
|
|
|
|
|
|
enum FakeEnum { FakeDebuggerCommonSettingsId };
|
|
|
|
void DebuggerUnitTests::testBenchmark()
|
|
{
|
|
#ifdef WITH_BENCHMARK
|
|
CALLGRIND_START_INSTRUMENTATION;
|
|
volatile Id id1 = Id(DEBUGGER_COMMON_SETTINGS_ID);
|
|
CALLGRIND_STOP_INSTRUMENTATION;
|
|
CALLGRIND_DUMP_STATS;
|
|
|
|
CALLGRIND_START_INSTRUMENTATION;
|
|
volatile FakeEnum id2 = FakeDebuggerCommonSettingsId;
|
|
CALLGRIND_STOP_INSTRUMENTATION;
|
|
CALLGRIND_DUMP_STATS;
|
|
#endif
|
|
}
|
|
|
|
void DebuggerUnitTests::testDebuggerMatching_data()
|
|
{
|
|
QTest::addColumn<QStringList>("debugger");
|
|
QTest::addColumn<QString>("target");
|
|
QTest::addColumn<int>("result");
|
|
|
|
QTest::newRow("Invalid data")
|
|
<< QStringList()
|
|
<< QString()
|
|
<< int(DebuggerItem::DoesNotMatch);
|
|
QTest::newRow("Invalid debugger")
|
|
<< QStringList()
|
|
<< QString::fromLatin1("x86-linux-generic-elf-32bit")
|
|
<< int(DebuggerItem::DoesNotMatch);
|
|
QTest::newRow("Invalid target")
|
|
<< QStringList("x86-linux-generic-elf-32bit")
|
|
<< QString()
|
|
<< int(DebuggerItem::DoesNotMatch);
|
|
|
|
QTest::newRow("Fuzzy match 1")
|
|
<< QStringList("unknown-unknown-unknown-unknown-0bit")
|
|
<< QString::fromLatin1("x86-linux-generic-elf-32bit")
|
|
<< int(DebuggerItem::MatchesWell); // Is this the expected behavior?
|
|
QTest::newRow("Fuzzy match 2")
|
|
<< QStringList("unknown-unknown-unknown-unknown-0bit")
|
|
<< QString::fromLatin1("arm-windows-msys-pe-64bit")
|
|
<< int(DebuggerItem::MatchesWell); // Is this the expected behavior?
|
|
|
|
QTest::newRow("Architecture mismatch")
|
|
<< QStringList("x86-linux-generic-elf-32bit")
|
|
<< QString::fromLatin1("arm-linux-generic-elf-32bit")
|
|
<< int(DebuggerItem::DoesNotMatch);
|
|
QTest::newRow("OS mismatch")
|
|
<< QStringList("x86-linux-generic-elf-32bit")
|
|
<< QString::fromLatin1("x86-macosx-generic-elf-32bit")
|
|
<< int(DebuggerItem::DoesNotMatch);
|
|
QTest::newRow("Format mismatch")
|
|
<< QStringList("x86-linux-generic-elf-32bit")
|
|
<< QString::fromLatin1("x86-linux-generic-pe-32bit")
|
|
<< int(DebuggerItem::DoesNotMatch);
|
|
|
|
QTest::newRow("Linux perfect match")
|
|
<< QStringList("x86-linux-generic-elf-32bit")
|
|
<< QString::fromLatin1("x86-linux-generic-elf-32bit")
|
|
<< int(DebuggerItem::MatchesWell);
|
|
QTest::newRow("Linux match")
|
|
<< QStringList("x86-linux-generic-elf-64bit")
|
|
<< QString::fromLatin1("x86-linux-generic-elf-32bit")
|
|
<< int(DebuggerItem::MatchesSomewhat);
|
|
|
|
QTest::newRow("Windows perfect match 1")
|
|
<< QStringList("x86-windows-msvc2013-pe-64bit")
|
|
<< QString::fromLatin1("x86-windows-msvc2013-pe-64bit")
|
|
<< int(DebuggerItem::MatchesWell);
|
|
QTest::newRow("Windows perfect match 2")
|
|
<< QStringList("x86-windows-msvc2013-pe-64bit")
|
|
<< QString::fromLatin1("x86-windows-msvc2012-pe-64bit")
|
|
<< int(DebuggerItem::MatchesWell);
|
|
QTest::newRow("Windows match 1")
|
|
<< QStringList("x86-windows-msvc2013-pe-64bit")
|
|
<< QString::fromLatin1("x86-windows-msvc2013-pe-32bit")
|
|
<< int(DebuggerItem::MatchesSomewhat);
|
|
QTest::newRow("Windows match 2")
|
|
<< QStringList("x86-windows-msvc2013-pe-64bit")
|
|
<< QString::fromLatin1("x86-windows-msvc2012-pe-32bit")
|
|
<< int(DebuggerItem::MatchesSomewhat);
|
|
QTest::newRow("Windows mismatch on word size")
|
|
<< QStringList("x86-windows-msvc2013-pe-32bit")
|
|
<< QString::fromLatin1("x86-windows-msvc2013-pe-64bit")
|
|
<< int(DebuggerItem::DoesNotMatch);
|
|
QTest::newRow("Windows mismatch on osflavor 1")
|
|
<< QStringList("x86-windows-msvc2013-pe-32bit")
|
|
<< QString::fromLatin1("x86-windows-msys-pe-64bit")
|
|
<< int(DebuggerItem::DoesNotMatch);
|
|
QTest::newRow("Windows mismatch on osflavor 2")
|
|
<< QStringList("x86-windows-msys-pe-32bit")
|
|
<< QString::fromLatin1("x86-windows-msvc2010-pe-64bit")
|
|
<< int(DebuggerItem::DoesNotMatch);
|
|
}
|
|
|
|
void DebuggerUnitTests::testDebuggerMatching()
|
|
{
|
|
QFETCH(QStringList, debugger);
|
|
QFETCH(QString, target);
|
|
QFETCH(int, result);
|
|
|
|
auto expectedLevel = static_cast<DebuggerItem::MatchLevel>(result);
|
|
|
|
Abis debuggerAbis;
|
|
for (const QString &abi : qAsConst(debugger))
|
|
debuggerAbis << Abi::fromString(abi);
|
|
|
|
DebuggerItem item;
|
|
item.setAbis(debuggerAbis);
|
|
|
|
DebuggerItem::MatchLevel level = item.matchTarget(Abi::fromString(target));
|
|
if (level == DebuggerItem::MatchesPerfectly)
|
|
level = DebuggerItem::MatchesWell;
|
|
|
|
QCOMPARE(expectedLevel, level);
|
|
}
|
|
|
|
QVector<QObject *> DebuggerPlugin::createTestObjects() const
|
|
{
|
|
return {new DebuggerUnitTests};
|
|
}
|
|
|
|
#else // ^-- if WITH_TESTS else --v
|
|
|
|
QVector<QObject *> DebuggerPlugin::createTestObjects() const
|
|
{
|
|
return {};
|
|
}
|
|
|
|
#endif // if WITH_TESTS
|
|
|
|
} // namespace Internal
|
|
} // namespace Debugger
|
|
|
|
#include "debuggerplugin.moc"
|