diff --git a/src/plugins/clangtools/clangtidyclazytool.cpp b/src/plugins/clangtools/clangtidyclazytool.cpp index 27d7fe171b6..a5f28b9e49b 100644 --- a/src/plugins/clangtools/clangtidyclazytool.cpp +++ b/src/plugins/clangtools/clangtidyclazytool.cpp @@ -282,12 +282,12 @@ ClangTidyClazyTool::ClangTidyClazyTool() action->setEnabled(m_startAction->isEnabled()); }); - perspective->addToolbarAction(m_startAction); - perspective->addToolbarAction(m_stopAction); - perspective->addToolbarAction(m_goBack); - perspective->addToolbarAction(m_goNext); - perspective->addToolbarWidget(m_filterLineEdit); - perspective->addToolbarWidget(m_applyFixitsButton); + perspective->addToolBarAction(m_startAction); + perspective->addToolBarAction(m_stopAction); + perspective->addToolBarAction(m_goBack); + perspective->addToolBarAction(m_goNext); + perspective->addToolBarWidget(m_filterLineEdit); + perspective->addToolBarWidget(m_applyFixitsButton); Debugger::registerPerspective(perspective); diff --git a/src/plugins/debugger/analyzer/analyzermanager.h b/src/plugins/debugger/analyzer/analyzermanager.h index 20c7e741c40..ebdaced8b09 100644 --- a/src/plugins/debugger/analyzer/analyzermanager.h +++ b/src/plugins/debugger/analyzer/analyzermanager.h @@ -32,9 +32,8 @@ #include -#include - #include +#include #include @@ -63,14 +62,11 @@ DEBUGGER_EXPORT void showCannotStartDialog(const QString &toolName); // Register a tool for a given start mode. DEBUGGER_EXPORT void registerPerspective(Utils::Perspective *perspective); -DEBUGGER_EXPORT void destroyDynamicPerspective(const QByteArray &perspectiveId); -DEBUGGER_EXPORT void setPerspectiveEnabled(const QByteArray &perspectiveId, bool enable); DEBUGGER_EXPORT void enableMainWindow(bool on); DEBUGGER_EXPORT QWidget *mainWindow(); DEBUGGER_EXPORT void selectPerspective(const QByteArray &perspectiveId); -DEBUGGER_EXPORT QByteArray currentPerspective(); // Convenience functions. DEBUGGER_EXPORT void showStatusMessage(const QString &message, int timeoutMS = 10000); diff --git a/src/plugins/debugger/breakhandler.cpp b/src/plugins/debugger/breakhandler.cpp index d926159ff63..4972f7f616d 100644 --- a/src/plugins/debugger/breakhandler.cpp +++ b/src/plugins/debugger/breakhandler.cpp @@ -30,7 +30,9 @@ #include "debuggerengine.h" #include "debuggericons.h" #include "debuggerinternalconstants.h" +#include "disassembleragent.h" #include "simplifytype.h" +#include "snapshothandler.h" #include #include @@ -38,7 +40,6 @@ #include #include -#include #include #include @@ -73,83 +74,7 @@ using namespace Utils; namespace Debugger { namespace Internal { -class LocationItem : public TypedTreeItem -{ -public: - QVariant data(int column, int role) const final - { - if (role == Qt::DecorationRole && column == 0) { - return params.enabled ? Icons::BREAKPOINT.icon() - : Icons::BREAKPOINT_DISABLED.icon(); - } - - if (role == Qt::DisplayRole) { - switch (column) { - case BreakpointNumberColumn: - return params.id.toString(); - case BreakpointFunctionColumn: - return params.functionName; - case BreakpointAddressColumn: - if (params.address) - return QString::fromLatin1("0x%1").arg(params.address, 0, 16); - } - } - return QVariant(); - } - - BreakpointResponse params; -}; - -class BreakpointMarker; - -class BreakpointItem : public QObject, public TypedTreeItem -{ - Q_DECLARE_TR_FUNCTIONS(Debugger::Internal::BreakHandler) - -public: - ~BreakpointItem() override; - - QVariant data(int column, int role) const override; - - QIcon icon() const; - - void removeBreakpoint(); - void updateLineNumberFromMarker(int lineNumber); - void updateFileNameFromMarker(const QString &fileName); - void changeLineNumberFromMarker(int lineNumber); - bool isLocatedAt(const QString &fileName, int lineNumber, bool useMarkerPosition) const; - - void setMarkerFileAndLine(const QString &fileName, int lineNumber); - - void insertSubBreakpoint(const BreakpointResponse ¶ms); - QString markerFileName() const; - int markerLineNumber() const; - - bool needsChange() const; - -private: - friend class BreakHandler; - friend class Breakpoint; - BreakpointItem(BreakHandler *handler); - - void destroyMarker(); - void updateMarker(); - void updateMarkerIcon(); - void scheduleSynchronization(); - - QString toToolTip() const; - void setState(BreakpointState state); - void deleteThis(); - bool isEngineRunning() const; - - BreakHandler * const m_handler; - const BreakpointModelId m_id; - BreakpointParameters m_params; - BreakpointState m_state; // Current state of breakpoint. - DebuggerEngine *m_engine; // Engine currently handling the breakpoint. - BreakpointResponse m_response; - BreakpointMarker *m_marker; -}; +static BreakpointManager *theBreakpointManager = nullptr; // // BreakpointMarker @@ -159,40 +84,121 @@ private: class BreakpointMarker : public TextEditor::TextMark { public: - BreakpointMarker(BreakpointItem *b, const FileName &fileName, int lineNumber) - : TextMark(fileName, lineNumber, Constants::TEXT_MARK_CATEGORY_BREAKPOINT), m_bp(b) + BreakpointMarker(const Breakpoint &bp, const FileName &fileName, int lineNumber) + : TextMark(fileName, lineNumber, Constants::TEXT_MARK_CATEGORY_BREAKPOINT), m_bp(bp) { setColor(Theme::Debugger_Breakpoint_TextMarkColor); setDefaultToolTip(QApplication::translate("BreakHandler", "Breakpoint")); setPriority(TextEditor::TextMark::NormalPriority); - setIcon(b->icon()); + setIcon(bp->icon()); + setToolTip(bp->toolTip()); } - void removedFromEditor() override - { - if (m_bp) - m_bp->removeBreakpoint(); - } - - void updateLineNumber(int lineNumber) override + void updateLineNumber(int lineNumber) final { TextMark::updateLineNumber(lineNumber); - m_bp->updateLineNumberFromMarker(lineNumber); + QTC_ASSERT(m_bp, return); + m_bp->setLineNumber(lineNumber); } - void updateFileName(const FileName &fileName) override + void updateFileName(const FileName &fileName) final { TextMark::updateFileName(fileName); - m_bp->updateFileNameFromMarker(fileName.toString()); + QTC_ASSERT(m_bp, return); + m_bp->setFileName(fileName.toString()); } - bool isDraggable() const override { return true; } - void dragToLine(int line) override { m_bp->changeLineNumberFromMarker(line); } - bool isClickable() const override { return true; } - void clicked() override { m_bp->removeBreakpoint(); } + bool isDraggable() const final { return true; } + + void dragToLine(int line) final + { + QTC_ASSERT(m_bp, return); + GlobalBreakpoint gbp = m_bp->globalBreakpoint(); + if (!gbp) + return; + BreakpointParameters params = gbp->m_params; + params.lineNumber = line; + gbp->deleteBreakpoint(); + BreakpointManager::createBreakpoint(params); + } + + bool isClickable() const final { return true; } + + void clicked() final + { + QTC_ASSERT(m_bp, return); + m_bp->deleteGlobalOrThisBreakpoint(); + } public: - BreakpointItem *m_bp; + Breakpoint m_bp; +}; + +// The red blob on the left side in the cpp editor. +class GlobalBreakpointMarker : public TextEditor::TextMark +{ +public: + GlobalBreakpointMarker(GlobalBreakpoint gbp, const FileName &fileName, int lineNumber) + : TextMark(fileName, lineNumber, Constants::TEXT_MARK_CATEGORY_BREAKPOINT), m_gbp(gbp) + { + setColor(Theme::Debugger_Breakpoint_TextMarkColor); + setDefaultToolTip(QApplication::translate("BreakHandler", "Breakpoint")); + setPriority(TextEditor::TextMark::NormalPriority); + setIcon(m_gbp->icon()); + } + + void removedFromEditor() final + { + QTC_ASSERT(m_gbp, return); + m_gbp->removeBreakpointFromModel(); + } + + void updateLineNumber(int lineNumber) final + { + TextMark::updateLineNumber(lineNumber); + QTC_ASSERT(m_gbp, return); + + // Ignore updates to the "real" line number while the debugger is + // running, as this can be triggered by moving the breakpoint to + // the next line that generated code. + + m_gbp->m_params.lineNumber = lineNumber; + m_gbp->updateMarker(); + m_gbp->update(); + } + + void updateFileName(const FileName &fileName) final + { + TextMark::updateFileName(fileName); + QTC_ASSERT(m_gbp, return); + m_gbp->m_params.fileName = fileName.toString(); + m_gbp->update(); + } + + bool isDraggable() const final { return true; } + + void dragToLine(int line) final + { + QTC_ASSERT(m_gbp, return); + QTC_ASSERT(BreakpointManager::globalBreakpoints().contains(m_gbp), return); + BreakpointParameters params = m_gbp->m_params; + params.lineNumber = line; + GlobalBreakpoint gbp = m_gbp; + m_gbp = GlobalBreakpoint(); + gbp->deleteBreakpoint(); + m_gbp = BreakpointManager::createBreakpoint(params); + } + + bool isClickable() const final { return true; } + + void clicked() final + { + QTC_ASSERT(m_gbp, return); + m_gbp->removeBreakpointFromModel(); + } + +public: + GlobalBreakpoint m_gbp; }; static QString stateToString(BreakpointState state) @@ -200,13 +206,13 @@ static QString stateToString(BreakpointState state) switch (state) { case BreakpointNew: return BreakHandler::tr("New"); - case BreakpointInsertRequested: + case BreakpointInsertionRequested: return BreakHandler::tr("Insertion requested"); - case BreakpointInsertProceeding: + case BreakpointInsertionProceeding: return BreakHandler::tr("Insertion proceeding"); - case BreakpointChangeRequested: + case BreakpointUpdateRequested: return BreakHandler::tr("Change requested"); - case BreakpointChangeProceeding: + case BreakpointUpdateProceeding: return BreakHandler::tr("Change proceeding"); case BreakpointInserted: return BreakHandler::tr("Breakpoint inserted"); @@ -304,7 +310,7 @@ class BreakpointDialog : public QDialog Q_DECLARE_TR_FUNCTIONS(Debugger::Internal::BreakHandler) public: - explicit BreakpointDialog(Breakpoint b, QWidget *parent = nullptr); + explicit BreakpointDialog(const DebuggerEngine *engine, QWidget *parent = nullptr); bool showDialog(BreakpointParameters *data, BreakpointParts *parts); void setParameters(const BreakpointParameters &data); @@ -361,7 +367,7 @@ private: QDialogButtonBox *m_buttonBox; }; -BreakpointDialog::BreakpointDialog(Breakpoint b, QWidget *parent) +BreakpointDialog::BreakpointDialog(const DebuggerEngine *engine, QWidget *parent) : QDialog(parent), m_enabledParts(~0), m_previousType(UnknownBreakpointType), m_firstTypeChange(true) { @@ -491,15 +497,13 @@ BreakpointDialog::BreakpointDialog(Breakpoint b, QWidget *parent) m_buttonBox = new QDialogButtonBox(this); m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); - if (b) { - if (DebuggerEngine *engine = b.engine()) { - if (!engine->hasCapability(BreakConditionCapability)) - m_enabledParts &= ~ConditionPart; - if (!engine->hasCapability(BreakModuleCapability)) - m_enabledParts &= ~ModulePart; - if (!engine->hasCapability(TracePointCapability)) - m_enabledParts &= ~TracePointPart; - } + if (engine) { + if (!engine->hasCapability(BreakConditionCapability)) + m_enabledParts &= ~ConditionPart; + if (!engine->hasCapability(BreakModuleCapability)) + m_enabledParts &= ~ModulePart; + if (!engine->hasCapability(TracePointCapability)) + m_enabledParts &= ~TracePointPart; } auto basicLayout = new QFormLayout(groupBoxBasic); @@ -850,7 +854,7 @@ class MultiBreakPointsDialog : public QDialog Q_DECLARE_TR_FUNCTIONS(Debugger::Internal::BreakHandler) public: - MultiBreakPointsDialog(QWidget *parent = nullptr); + MultiBreakPointsDialog(bool canUseConditions, QWidget *parent); QString condition() const { return m_lineEditCondition->text(); } int ignoreCount() const { return m_spinBoxIgnoreCount->value(); } @@ -869,7 +873,7 @@ private: QDialogButtonBox *m_buttonBox; }; -MultiBreakPointsDialog::MultiBreakPointsDialog(QWidget *parent) : +MultiBreakPointsDialog::MultiBreakPointsDialog(bool canUseConditions, QWidget *parent) : QDialog(parent) { setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); @@ -885,7 +889,7 @@ MultiBreakPointsDialog::MultiBreakPointsDialog(QWidget *parent) : m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); auto formLayout = new QFormLayout; - if (currentEngine()->hasCapability(BreakConditionCapability)) + if (canUseConditions) formLayout->addRow(tr("&Condition:"), m_lineEditCondition); formLayout->addRow(tr("&Ignore count:"), m_spinBoxIgnoreCount); formLayout->addRow(tr("&Thread specification:"), m_lineEditThreadSpec); @@ -898,16 +902,14 @@ MultiBreakPointsDialog::MultiBreakPointsDialog(QWidget *parent) : connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); } -BreakHandler::BreakHandler() - : m_syncTimerId(-1) +BreakHandler::BreakHandler(DebuggerEngine *engine) + : m_engine(engine) { - qRegisterMetaType(); - #if USE_BREAK_MODEL_TEST new ModelTest(this, 0); #endif - setHeader(QStringList({tr("Number"), tr("Function"), tr("File"), tr("Line"), tr("Address"), - tr("Condition"), tr("Ignore"), tr("Threads")})); + setHeader({tr("Number"), tr("Function"), tr("File"), tr("Line"), tr("Address"), + tr("Condition"), tr("Ignore"), tr("Threads")}); } static inline bool fileNameMatch(const QString &f1, const QString &f2) @@ -917,7 +919,13 @@ static inline bool fileNameMatch(const QString &f1, const QString &f2) return f1 == f2; } -static bool isSimilarTo(const BreakpointParameters ¶ms, const BreakpointResponse &needle) +bool BreakpointParameters::isLocatedAt(const QString &file, int line, const QString &markerFile) const +{ + return lineNumber == line + && (fileNameMatch(fileName, file) || fileNameMatch(fileName, markerFile)); +} + +static bool isSimilarTo(const BreakpointParameters ¶ms, const BreakpointParameters &needle) { // Clear miss. if (needle.type != UnknownBreakpointType && params.type != UnknownBreakpointType @@ -949,48 +957,18 @@ static bool isSimilarTo(const BreakpointParameters ¶ms, const BreakpointResp return false; } -Breakpoint BreakHandler::findSimilarBreakpoint(const BreakpointResponse &needle) const +Breakpoint BreakHandler::findBreakpointByResponseId(const QString &id) const { - // Search a breakpoint we might refer to. - return Breakpoint(findItemAtLevel<1>([needle](BreakpointItem *b) { - if (b->m_response.id.isValid() && b->m_response.id.majorPart() == needle.id.majorPart()) - return true; - return isSimilarTo(b->m_params, needle); - })); + return findItemAtLevel<1>([id](const Breakpoint bp) { + return bp && bp->responseId() == id; + }); } -Breakpoint BreakHandler::findBreakpointByResponseId(const BreakpointResponseId &id) const +SubBreakpoint BreakHandler::findSubBreakpointByResponseId(const QString &id) const { - return Breakpoint(findItemAtLevel<1>([id](BreakpointItem *b) { - return b->m_response.id.majorPart() == id.majorPart(); - })); -} - -Breakpoint BreakHandler::findBreakpointByFunction(const QString &functionName) const -{ - return Breakpoint(findItemAtLevel<1>([functionName](BreakpointItem *b) { - return b->m_params.functionName == functionName; - })); -} - -Breakpoint BreakHandler::findBreakpointByAddress(quint64 address) const -{ - return Breakpoint(findItemAtLevel<1>([address](BreakpointItem *b) { - return b->m_params.address == address; - })); -} - -Breakpoint BreakHandler::findBreakpointByFileAndLine(const QString &fileName, - int lineNumber, bool useMarkerPosition) -{ - return Breakpoint(findItemAtLevel<1>([=](BreakpointItem *b) { - return b->isLocatedAt(fileName, lineNumber, useMarkerPosition); - })); -} - -Breakpoint BreakHandler::breakpointById(BreakpointModelId id) const -{ - return Breakpoint(findItemAtLevel<1>([id](BreakpointItem *b) { return b->m_id == id; })); + return findItemAtLevel<2>([id](const SubBreakpoint sub) { + return sub && sub->responseId == id; + }); } QVariant BreakHandler::data(const QModelIndex &idx, int role) const @@ -998,32 +976,1699 @@ QVariant BreakHandler::data(const QModelIndex &idx, int role) const if (role == BaseTreeView::ItemDelegateRole) return QVariant::fromValue(new LeftElideDelegate); - return BreakModel::data(idx, role); -} - -void BreakHandler::deletionHelper(BreakpointModelId id) -{ - Breakpoint b = breakpointById(id); - QTC_ASSERT(b, return); - destroyItem(b.b); + return BreakHandlerModel::data(idx, role); } Breakpoint BreakHandler::findWatchpoint(const BreakpointParameters ¶ms) const { - return Breakpoint(findItemAtLevel<1>([params](BreakpointItem *b) { - return b->m_params.isWatchpoint() - && b->m_params.address == params.address - && b->m_params.size == params.size - && b->m_params.expression == params.expression - && b->m_params.bitpos == params.bitpos; - })); + return findItemAtLevel<1>([params](const Breakpoint &bp) { + return bp->m_parameters.isWatchpoint() + && bp->m_parameters.address == params.address + && bp->m_parameters.size == params.size + && bp->m_parameters.expression == params.expression + && bp->m_parameters.bitpos == params.bitpos; + }); } -void BreakHandler::saveBreakpoints() +Breakpoint BreakHandler::findBreakpointByIndex(const QModelIndex &index) const +{ + return itemForIndexAtLevel<1>(index); +} + +SubBreakpoint BreakHandler::findSubBreakpointByIndex(const QModelIndex &index) const +{ + return itemForIndexAtLevel<2>(index); +} + +Breakpoint BreakHandler::findBreakpointByModelId(int modelId) const +{ + return findItemAtLevel<1>([modelId](const Breakpoint &bp) { + QTC_ASSERT(bp, return false); + return bp->modelId() == modelId; + }); +} + +Breakpoints BreakHandler::findBreakpointsByIndex(const QList &list) const +{ + QSet items; + for (const QModelIndex &index : list) { + if (Breakpoint bp = findBreakpointByIndex(index)) + items.insert(bp); + } + return items.toList(); +} + +SubBreakpoints BreakHandler::findSubBreakpointsByIndex(const QList &list) const +{ + QSet items; + for (const QModelIndex &index : list) { + if (SubBreakpoint sbp = findSubBreakpointByIndex(index)) + items.insert(sbp); + } + return items.toList(); + +} + +QString BreakHandler::displayFromThreadSpec(int spec) +{ + return spec == -1 ? BreakHandler::tr("(all)") : QString::number(spec); +} + +int BreakHandler::threadSpecFromDisplay(const QString &str) +{ + bool ok = false; + int result = str.toInt(&ok); + return ok ? result : -1; +} + +const QString empty(QLatin1Char('-')); + +QVariant BreakpointItem::data(int column, int role) const +{ + if (role == Qt::ForegroundRole) { + static const QVariant gray(QColor(140, 140, 140)); + switch (m_state) { + case BreakpointInsertionRequested: + case BreakpointInsertionProceeding: + case BreakpointUpdateRequested: + case BreakpointUpdateProceeding: + case BreakpointRemoveRequested: + case BreakpointRemoveProceeding: + return gray; + case BreakpointInserted: + case BreakpointNew: + case BreakpointDead: + break; + }; + } + + switch (column) { + case BreakpointNumberColumn: + if (role == Qt::DisplayRole) + return m_displayName.isEmpty() ? m_responseId : m_displayName; + if (role == Qt::DecorationRole) + return icon(); + break; + case BreakpointFunctionColumn: + if (role == Qt::DisplayRole) { + if (!m_parameters.functionName.isEmpty()) + return simplifyType(m_parameters.functionName); + if (m_parameters.type == BreakpointAtMain + || m_parameters.type == BreakpointAtThrow + || m_parameters.type == BreakpointAtCatch + || m_parameters.type == BreakpointAtFork + || m_parameters.type == BreakpointAtExec + //|| m_response.type == BreakpointAtVFork + || m_parameters.type == BreakpointAtSysCall) + return typeToString(m_parameters.type); + if (m_parameters.type == WatchpointAtAddress) { + quint64 address = m_parameters.address ? m_parameters.address : m_parameters.address; + return BreakHandler::tr("Data at 0x%1").arg(address, 0, 16); + } + if (m_parameters.type == WatchpointAtExpression) { + QString expression = !m_parameters.expression.isEmpty() + ? m_parameters.expression : m_parameters.expression; + return BreakHandler::tr("Data at %1").arg(expression); + } + return empty; + } + break; + case BreakpointFileColumn: + if (role == Qt::DisplayRole) { + QString str; + if (!m_parameters.fileName.isEmpty()) + str = m_parameters.fileName; + if (str.isEmpty()) { + QString s = FileName::fromString(str).fileName(); + if (!s.isEmpty()) + str = s; + } + // FIXME: better? + //if (params.multiple && str.isEmpty() && !response.fileName.isEmpty()) + // str = response.fileName; + if (!str.isEmpty()) + return QDir::toNativeSeparators(str); + return empty; + } + break; + case BreakpointLineColumn: + if (role == Qt::DisplayRole) { + if (m_parameters.lineNumber > 0) + return m_parameters.lineNumber; + return empty; + } + if (role == Qt::UserRole + 1) + return m_parameters.lineNumber; + break; + case BreakpointAddressColumn: + if (role == Qt::DisplayRole) { + if (m_parameters.address) + return QString("0x%1").arg(m_parameters.address, 0, 16); + return QVariant(); + } + break; + case BreakpointConditionColumn: + if (role == Qt::DisplayRole) + return m_parameters.condition; + if (role == Qt::ToolTipRole) + return BreakHandler::tr("Breakpoint will only be hit if this condition is met."); + if (role == Qt::UserRole + 1) + return m_parameters.condition; + break; + case BreakpointIgnoreColumn: + if (role == Qt::DisplayRole) { + const int ignoreCount = m_parameters.ignoreCount; + return ignoreCount ? QVariant(ignoreCount) : QVariant(QString()); + } + if (role == Qt::ToolTipRole) + return BreakHandler::tr("Breakpoint will only be hit after being ignored so many times."); + if (role == Qt::UserRole + 1) + return m_parameters.ignoreCount; + break; + case BreakpointThreadsColumn: + if (role == Qt::DisplayRole) + return BreakHandler::displayFromThreadSpec(m_parameters.threadSpec); + if (role == Qt::ToolTipRole) + return BreakHandler::tr("Breakpoint will only be hit in the specified thread(s)."); + if (role == Qt::UserRole + 1) + return BreakHandler::displayFromThreadSpec(m_parameters.threadSpec); + break; + } + + if (role == Qt::ToolTipRole && boolSetting(UseToolTipsInBreakpointsView)) + return toolTip(); + + return QVariant(); +} + +void BreakpointItem::addToCommand(DebuggerCommand *cmd) const +{ + cmd->arg("modelid", modelId()); + cmd->arg("id", m_responseId); + cmd->arg("type", m_requestedParameters.type); + cmd->arg("ignorecount", m_requestedParameters.ignoreCount); + cmd->arg("condition", toHex(m_requestedParameters.condition)); + cmd->arg("command", toHex(m_requestedParameters.command)); + cmd->arg("function", m_requestedParameters.functionName); + cmd->arg("oneshot", m_requestedParameters.oneShot); + cmd->arg("enabled", m_requestedParameters.enabled); + cmd->arg("file", m_requestedParameters.fileName); + cmd->arg("line", m_requestedParameters.lineNumber); + cmd->arg("address", m_requestedParameters.address); + cmd->arg("expression", m_requestedParameters.expression); +} + +void BreakpointItem::updateFromGdbOutput(const GdbMi &bkpt) +{ + m_parameters.updateFromGdbOutput(bkpt); + adjustMarker(); +} + +int BreakpointItem::modelId() const +{ + return m_globalBreakpoint ? m_globalBreakpoint->modelId() : 0; +} + +void BreakpointItem::setPending(bool pending) +{ + m_parameters.pending = pending; + adjustMarker(); +} + +void BreakHandler::removeAlienBreakpoint(const QString &rid) +{ + Breakpoint bp = findBreakpointByResponseId(rid); + destroyItem(bp); +} + +void BreakHandler::requestBreakpointInsertion(const Breakpoint &bp) +{ + bp->gotoState(BreakpointInsertionRequested, BreakpointNew); + QTimer::singleShot(0, m_engine, [this, bp] { m_engine->insertBreakpoint(bp); }); +} + +void BreakHandler::requestBreakpointUpdate(const Breakpoint &bp) +{ + bp->gotoState(BreakpointUpdateRequested, BreakpointInserted); + QTimer::singleShot(0, m_engine, [this, bp] { m_engine->updateBreakpoint(bp); }); +} + +void BreakHandler::requestBreakpointRemoval(const Breakpoint &bp) +{ + bp->gotoState(BreakpointRemoveRequested, BreakpointInserted); + QTimer::singleShot(0, m_engine, [this, bp] { m_engine->removeBreakpoint(bp); }); +} + +void BreakHandler::requestBreakpointEnabling(const Breakpoint &bp, bool enabled) +{ + if (bp->m_parameters.enabled != enabled) { + bp->m_requestedParameters.enabled = enabled; + bp->updateMarkerIcon(); + bp->update(); + requestBreakpointUpdate(bp); + } +} + +void BreakHandler::requestSubBreakpointEnabling(const SubBreakpoint &sbp, bool enabled) +{ + if (sbp->params.enabled != enabled) { + sbp->params.enabled = enabled; + sbp->breakpoint()->update(); + QTimer::singleShot(0, m_engine, [this, sbp, enabled] { + m_engine->enableSubBreakpoint(sbp, enabled); + }); + } +} + +void BreakpointItem::setMarkerFileAndLine(const QString &fileName, int lineNumber) +{ + if (m_parameters.fileName == fileName && m_parameters.lineNumber == lineNumber) + return; + m_parameters.fileName = fileName; + m_parameters.lineNumber = lineNumber; + destroyMarker(); + updateMarker(); + update(); +} + +static bool isAllowedTransition(BreakpointState from, BreakpointState to) +{ + switch (from) { + case BreakpointNew: + return to == BreakpointInsertionRequested + || to == BreakpointDead; + case BreakpointInsertionRequested: + return to == BreakpointInsertionProceeding; + case BreakpointInsertionProceeding: + return to == BreakpointInserted + || to == BreakpointDead + || to == BreakpointUpdateRequested + || to == BreakpointRemoveRequested; + case BreakpointUpdateRequested: + return to == BreakpointUpdateProceeding; + case BreakpointUpdateProceeding: + return to == BreakpointInserted + || to == BreakpointDead; + case BreakpointInserted: + return to == BreakpointUpdateRequested + || to == BreakpointRemoveRequested; + case BreakpointRemoveRequested: + return to == BreakpointRemoveProceeding; + case BreakpointRemoveProceeding: + return to == BreakpointDead; + case BreakpointDead: + return false; + } + qDebug() << "UNKNOWN BREAKPOINT STATE:" << from; + return false; +} + +void BreakpointItem::gotoState(BreakpointState target, BreakpointState assumedCurrent) +{ + QTC_ASSERT(m_state == assumedCurrent, qDebug() << m_state); + setState(target); +} + +void BreakHandler::updateDisassemblerMarker(const Breakpoint &bp) +{ + return m_engine->disassemblerAgent()->updateBreakpointMarker(bp); +} + +void BreakHandler::removeDisassemblerMarker(const Breakpoint &bp) +{ + m_engine->disassemblerAgent()->removeBreakpointMarker(bp); + bp->destroyMarker(); + if (GlobalBreakpoint gbp = bp->globalBreakpoint()) + gbp->updateMarker(); +} + +void BreakpointItem::setState(BreakpointState state) +{ + //qDebug() << "BREAKPOINT STATE TRANSITION, ID: " << m_id + // << " FROM: " << state << " TO: " << state; + if (!isAllowedTransition(m_state, state)) { + qDebug() << "UNEXPECTED BREAKPOINT STATE TRANSITION" << m_state << state; + QTC_CHECK(false); + } + + if (m_state == state) { + qDebug() << "STATE UNCHANGED: " << responseId() << m_state; + return; + } + + m_state = state; + + // FIXME: updateMarker() should recognize the need for icon changes. + if (state == BreakpointInserted) { + destroyMarker(); + updateMarker(); + } + update(); +} + +const GlobalBreakpoint BreakpointItem::globalBreakpoint() const +{ + return m_globalBreakpoint; +} + +void BreakpointItem::setParameters(const BreakpointParameters &value) +{ + m_parameters = value; + adjustMarker(); +} + +void BreakpointItem::setEnabled(bool on) +{ + m_parameters.enabled = on; + adjustMarker(); +} + +void BreakHandler::setBreakpointEnabled(const Breakpoint &bp, bool on) +{ + bp->setEnabled(on); + requestBreakpointUpdate(bp); +} + +void DebuggerEngine::notifyBreakpointInsertProceeding(const Breakpoint &bp) +{ + QTC_ASSERT(bp, return); + bp->gotoState(BreakpointInsertionProceeding, BreakpointInsertionRequested); +} + +void DebuggerEngine::notifyBreakpointInsertOk(const Breakpoint &bp) +{ + QTC_ASSERT(bp, return); + bp->adjustMarker(); + bp->gotoState(BreakpointInserted, BreakpointInsertionProceeding); + breakHandler()->updateDisassemblerMarker(bp); + bp->updateMarker(); +} + +void DebuggerEngine::notifyBreakpointInsertFailed(const Breakpoint &bp) +{ + QTC_ASSERT(bp, return); + bp->gotoState(BreakpointDead, BreakpointInsertionProceeding); +} + +void DebuggerEngine::notifyBreakpointRemoveProceeding(const Breakpoint &bp) +{ + QTC_ASSERT(bp, return); + bp->gotoState(BreakpointRemoveProceeding, BreakpointRemoveRequested); +} + +void DebuggerEngine::notifyBreakpointRemoveOk(const Breakpoint &bp) +{ + QTC_ASSERT(bp, return); + QTC_ASSERT(bp->state() == BreakpointRemoveProceeding, qDebug() << bp->state()); + breakHandler()->removeDisassemblerMarker(bp); + breakHandler()->destroyItem(bp); +} + +void DebuggerEngine::notifyBreakpointRemoveFailed(const Breakpoint &bp) +{ + QTC_ASSERT(bp, return); + QTC_ASSERT(bp->m_state == BreakpointRemoveProceeding, qDebug() << bp->m_state); + breakHandler()->removeDisassemblerMarker(bp); + breakHandler()->destroyItem(bp); +} + +void DebuggerEngine::notifyBreakpointChangeProceeding(const Breakpoint &bp) +{ + bp->gotoState(BreakpointUpdateProceeding, BreakpointUpdateRequested); +} + +void DebuggerEngine::notifyBreakpointChangeOk(const Breakpoint &bp) +{ + bp->gotoState(BreakpointInserted, BreakpointUpdateProceeding); +} + +void DebuggerEngine::notifyBreakpointChangeFailed(const Breakpoint &bp) +{ + bp->gotoState(BreakpointDead, BreakpointUpdateProceeding); +} + +void DebuggerEngine::notifyBreakpointNeedsReinsertion(const Breakpoint &bp) +{ + QTC_ASSERT(bp, return); + QTC_ASSERT(bp->m_state == BreakpointUpdateProceeding, qDebug() << bp->m_state); + bp->m_state = BreakpointInsertionRequested; +} + +void BreakHandler::handleAlienBreakpoint(const QString &responseId, const BreakpointParameters ¶ms) +{ + // Search a breakpoint we might refer to. + Breakpoint bp = findItemAtLevel<1>([params, responseId](const Breakpoint &bp) { + if (bp && !bp->responseId().isEmpty() && bp->responseId() == responseId) + return true; + return bp && isSimilarTo(bp->m_parameters, params); + }); + + if (bp) { + // FIXME: x.y looks rather gdb specific. + if (bp->responseId().contains('.')) { + SubBreakpoint loc = bp->findOrCreateSubBreakpoint(bp->responseId()); + QTC_ASSERT(loc, return); + loc->setParameters(params); + } else { + bp->setParameters(params); + } + } else { + bp = new BreakpointItem(nullptr); + bp->setState(BreakpointInserted); + bp->setParameters(params); + rootItem()->appendChild(bp); + // This has no global breakpoint, so there's nothing to update here. + } +} + +SubBreakpoint BreakpointItem::findOrCreateSubBreakpoint(const QString &responseId) +{ + const QString minorPart = responseId.section('.', 1); + + SubBreakpoint loc = findFirstLevelChild([&](const SubBreakpoint &l) { + return l->responseId == responseId; + }); + if (loc) { + // This modifies an existing sub-breakpoint. + loc->update(); + } else { + // This is a new sub-breakpoint. + loc = new SubBreakpointItem; + loc->responseId = responseId; + appendChild(loc); + expand(); + } + return loc; +} + +bool BreakHandler::tryClaimBreakpoint(const GlobalBreakpoint &gbp) +{ + const Breakpoints bps = breakpoints(); + if (Utils::anyOf(bps, [gbp](const Breakpoint &bp) { return bp->globalBreakpoint() == gbp; })) + return false; + + if (!m_engine->acceptsBreakpoint(gbp->parameters())) { + m_engine->showMessage(QString("BREAKPOINT %1 IS NOT ACCEPTED BY ENGINE %2") + .arg(gbp->displayName()).arg(objectName())); + return false; + } + + m_engine->showMessage(QString("TAKING OWNERSHIP OF BREAKPOINT %1").arg(gbp->displayName())); + + Breakpoint bp(new BreakpointItem(gbp)); + rootItem()->appendChild(bp); + + gbp->updateMarker(); + requestBreakpointInsertion(bp); + + return true; +} + +void BreakHandler::gotoLocation(const Breakpoint &bp) const +{ + QTC_ASSERT(bp, return); + QTC_ASSERT(m_engine, return); + if (bp->m_parameters.type == BreakpointByAddress) { + m_engine->gotoLocation(bp->m_parameters.address); + } else { + // Don't use gotoLocation unconditionally as this ends up in + // disassembly if OperateByInstruction is on. But fallback + // to disassembly if we can't open the file. + const QString file = QDir::cleanPath(bp->markerFileName()); + if (IEditor *editor = EditorManager::openEditor(file)) + editor->gotoLine(bp->markerLineNumber(), 0); + else + m_engine->openDisassemblerView(Location(bp->m_parameters.address)); + } +} + +const Breakpoints BreakHandler::breakpoints() const +{ + QList items; + forItemsAtLevel<1>([&items](Breakpoint bp) { if (bp) items.append(bp); }); + return items; +} + +void BreakpointItem::adjustMarker() +{ + destroyMarker(); + updateMarker(); +} + +void BreakpointItem::deleteBreakpoint() +{ + QTC_ASSERT(!globalBreakpoint(), return); // Use deleteBreakpoint(GlobalBreakpoint gbp) instead. + + for (QPointer engine : EngineManager::engines()) + engine->breakHandler()->requestBreakpointRemoval(this); +} + +void BreakpointItem::deleteGlobalOrThisBreakpoint() +{ + if (GlobalBreakpoint gbp = globalBreakpoint()) { + gbp->deleteBreakpoint(); + } else { + deleteBreakpoint(); + } +} + +bool BreakHandler::setData(const QModelIndex &idx, const QVariant &value, int role) +{ + if (role == BaseTreeView::ItemActivatedRole) { + if (Breakpoint bp = findBreakpointByIndex(idx)) + gotoLocation(bp); + return true; + } + + if (role == BaseTreeView::ItemViewEventRole) { + ItemViewEvent ev = value.value(); + + if (ev.as()) + return contextMenuEvent(ev); + + if (auto kev = ev.as(QEvent::KeyPress)) { + if (kev->key() == Qt::Key_Delete) { + QModelIndexList si = ev.currentOrSelectedRows(); + const Breakpoints bps = findBreakpointsByIndex(si); + for (Breakpoint bp : bps) { + if (GlobalBreakpoint gbp = bp->globalBreakpoint()) + gbp->deleteBreakpoint(); + else + bp->deleteBreakpoint(); + } +// int row = qMin(rowCount() - ids.size() - 1, idx.row()); +// setCurrentIndex(index(row, 0)); FIXME + return true; + } + if (kev->key() == Qt::Key_Space) { + const QModelIndexList selectedIds = ev.selectedRows(); + if (!selectedIds.isEmpty()) { + const Breakpoints bps = findBreakpointsByIndex(selectedIds); + const SubBreakpoints sbps = findSubBreakpointsByIndex(selectedIds); + const bool isEnabled = (bps.isEmpty() && sbps.isEmpty()) + || (!bps.isEmpty() && bps.at(0)->isEnabled()) + || (!sbps.isEmpty() && sbps.at(0)->params.enabled); + for (Breakpoint bp : bps) + requestBreakpointEnabling(bp, !isEnabled); + for (SubBreakpoint sbp : sbps) + requestSubBreakpointEnabling(sbp, !isEnabled); + return true; + } + } + } + + if (ev.as(QEvent::MouseButtonDblClick)) { + if (Breakpoint bp = findBreakpointByIndex(idx)) { + if (idx.column() >= BreakpointAddressColumn) + editBreakpoints({bp}, ev.view()); + else + gotoLocation(bp); + } else if (SubBreakpoint loc = itemForIndexAtLevel<2>(idx)) { + gotoLocation(loc->breakpoint()); + } else { + BreakpointManager::executeAddBreakpointDialog(); + } + return true; + } + } + + return false; +} + +bool BreakHandler::contextMenuEvent(const ItemViewEvent &ev) +{ + const QModelIndexList selectedIndices = ev.selectedRows(); + + const Breakpoints selectedBreakpoints = findBreakpointsByIndex(selectedIndices); + const bool breakpointsEnabled = selectedBreakpoints.isEmpty() || selectedBreakpoints.at(0)->isEnabled(); + + QList selectedLocations; + bool handlesIndividualLocations = false; + for (const QModelIndex &index : selectedIndices) { + if (SubBreakpointItem *location = itemForIndexAtLevel<2>(index)) { + if (selectedLocations.contains(location)) + continue; + selectedLocations.append(location); + if (m_engine->hasCapability(BreakIndividualLocationsCapability)) + handlesIndividualLocations = true; + } + } + const bool locationsEnabled = selectedLocations.isEmpty() || selectedLocations.at(0)->params.enabled; + + auto menu = new QMenu; + + addAction(menu, tr("Add Breakpoint..."), true, &BreakpointManager::executeAddBreakpointDialog); + + addAction(menu, tr("Delete Selected Breakpoints"), + !selectedBreakpoints.isEmpty(), + [selectedBreakpoints] { + for (Breakpoint bp : selectedBreakpoints) { + if (GlobalBreakpoint gbp = bp->globalBreakpoint()) { + gbp->deleteBreakpoint(); + } else { + bp->deleteBreakpoint(); + } + } + }); + + addAction(menu, tr("Edit Selected Breakpoints..."), + !selectedBreakpoints.isEmpty(), + [this, selectedBreakpoints, ev] { editBreakpoints(selectedBreakpoints, ev.view()); }); + + + // FIXME BP: m_engine->threadsHandler()->currentThreadId(); + // int threadId = 0; + // addAction(menu, + // threadId == -1 ? tr("Associate Breakpoint with All Threads") + // : tr("Associate Breakpoint with Thread %1").arg(threadId), + // !selectedItems.isEmpty(), + // [this, selectedItems, threadId] { + // for (Breakpoint bp : selectedItems) + // bp.setThreadSpec(threadId); + // }); + + addAction(menu, + selectedBreakpoints.size() > 1 + ? breakpointsEnabled ? tr("Disable Selected Breakpoints") : tr("Enable Selected Breakpoints") + : breakpointsEnabled ? tr("Disable Breakpoint") : tr("Enable Breakpoint"), + !selectedBreakpoints.isEmpty(), + [this, selectedBreakpoints, breakpointsEnabled] { + for (Breakpoint bp : selectedBreakpoints) + requestBreakpointEnabling(bp, !breakpointsEnabled); + } + ); + + addAction(menu, + selectedLocations.size() > 1 + ? locationsEnabled ? tr("Disable Selected Locations") : tr("Enable Selected Locations") + : locationsEnabled ? tr("Disable Location") : tr("Enable Location"), + !selectedLocations.isEmpty() && handlesIndividualLocations, + [this, selectedLocations, locationsEnabled] { + for (const SubBreakpoint &sbp : selectedLocations) + requestSubBreakpointEnabling(sbp, !locationsEnabled); + } + ); + + menu->addSeparator(); + + addAction(menu, tr("Delete All Breakpoints"), + rowCount() > 0, + &BreakpointManager::executeDeleteAllBreakpointsDialog); + + // Delete by file: Find indices of breakpoints of the same file. + QList breakpointsInFile; + QString file; + if (Breakpoint bp = itemForIndexAtLevel<1>(ev.index())) { + const QModelIndex index = ev.index().sibling(ev.index().row(), BreakpointFileColumn); + if (!file.isEmpty()) { + for (int i = 0; i != rowCount(); ++i) + if (index.data().toString() == file) + breakpointsInFile.append(findBreakpointByIndex(index)); + } + } + addAction(menu, tr("Delete Breakpoints of \"%1\"").arg(file), + tr("Delete Breakpoints of File"), + breakpointsInFile.size() > 1, + [breakpointsInFile] { + for (Breakpoint bp : breakpointsInFile) + bp->deleteGlobalOrThisBreakpoint(); + }); + + menu->addSeparator(); + + menu->addAction(action(UseToolTipsInBreakpointsView)); + menu->addAction(action(UseAddressInBreakpointsView)); + menu->addSeparator(); + menu->addAction(action(SettingsDialog)); + + menu->popup(ev.globalPos()); + + return true; +} + +void BreakHandler::removeBreakpoint(const Breakpoint &bp) +{ + QTC_ASSERT(bp, return); + switch (bp->m_state) { + case BreakpointRemoveRequested: + break; + case BreakpointInserted: + case BreakpointInsertionProceeding: + bp->setState(BreakpointRemoveRequested); + requestBreakpointRemoval(bp); + break; + case BreakpointNew: + bp->setState(BreakpointDead); + bp->destroyMarker(); + destroyItem(bp); + break; + default: + qWarning("Warning: Cannot remove breakpoint %s in state '%s'.", + qPrintable(bp->responseId()), qPrintable(stateToString(bp->m_state))); + bp->m_state = BreakpointRemoveRequested; + break; + } +} + +void BreakHandler::editBreakpoint(const Breakpoint &bp, QWidget *parent) +{ + QTC_ASSERT(bp, return); + BreakpointParameters params = bp->requestedParameters(); + BreakpointParts parts = NoParts; + + BreakpointDialog dialog(m_engine, parent); + if (!dialog.showDialog(¶ms, &parts)) + return; + + if (params != bp->requestedParameters()) { + bp->setParameters(params); + updateDisassemblerMarker(bp); + bp->updateMarker(); + bp->update(); + if (bp->needsChange() && bp->m_state != BreakpointNew) + requestBreakpointUpdate(bp); + } +} + +void BreakHandler::editBreakpoints(const Breakpoints &bps, QWidget *parent) +{ + QTC_ASSERT(!bps.isEmpty(), return); + + Breakpoint bp = bps.at(0); + + if (bps.size() == 1) { + editBreakpoint(bp, parent); + return; + } + + // This allows to change properties of multiple breakpoints at a time. + if (!bp) + return; + + const bool canUseConditions = m_engine->hasCapability(BreakConditionCapability); + + MultiBreakPointsDialog dialog(canUseConditions, parent); + dialog.setCondition(bp->condition()); + dialog.setIgnoreCount(bp->ignoreCount()); + dialog.setThreadSpec(bp->threadSpec()); + + if (dialog.exec() == QDialog::Rejected) + return; + + const QString newCondition = dialog.condition(); + const int newIgnoreCount = dialog.ignoreCount(); + const int newThreadSpec = dialog.threadSpec(); + + for (Breakpoint bp : bps) { + if (bp) { + bp->m_parameters.condition = newCondition; + bp->m_parameters.ignoreCount = newIgnoreCount; + bp->m_parameters.threadSpec = newThreadSpec; + if (bp->m_state != BreakpointNew) + requestBreakpointUpdate(bp); + } + } +} + +////////////////////////////////////////////////////////////////// +// +// Storage +// +////////////////////////////////////////////////////////////////// + +BreakpointItem::BreakpointItem(const GlobalBreakpoint &gbp) + : m_globalBreakpoint(gbp) +{ + if (gbp) + m_requestedParameters = gbp->parameters(); +} + +BreakpointItem::~BreakpointItem() +{ + delete m_marker; +} + +void BreakpointItem::destroyMarker() +{ + if (m_marker) { + BreakpointMarker *m = m_marker; + m_marker = nullptr; + delete m; + } +} + +QString BreakpointItem::markerFileName() const +{ + // Some heuristics to find a "good" file name. + if (!m_parameters.fileName.isEmpty()) { + QFileInfo fi(m_parameters.fileName); + if (fi.exists()) + return fi.absoluteFilePath(); + } + const QString origFileName = requestedParameters().fileName; + if (m_parameters.fileName.endsWith(origFileName)) + return m_parameters.fileName; + if (origFileName.endsWith(m_parameters.fileName)) + return origFileName; + return m_parameters.fileName.size() > origFileName ? m_parameters.fileName : origFileName; +} + +int BreakpointItem::markerLineNumber() const +{ + return m_parameters.lineNumber; +} + +const BreakpointParameters &BreakpointItem::requestedParameters() const +{ + return m_requestedParameters; +} + +static void formatAddress(QTextStream &str, quint64 address) +{ + if (address) { + str << "0x"; + str.setIntegerBase(16); + str << address; + str.setIntegerBase(10); + } +} + +bool BreakpointItem::needsChange() const +{ + const BreakpointParameters &oparams = requestedParameters(); + if (!oparams.conditionsMatch(m_parameters.condition)) + return true; + if (oparams.ignoreCount != m_parameters.ignoreCount) + return true; + if (oparams.enabled != m_parameters.enabled) + return true; + if (oparams.threadSpec != m_parameters.threadSpec) + return true; + if (oparams.command != m_parameters.command) + return true; + if (oparams.type == BreakpointByFileAndLine && oparams.lineNumber != m_parameters.lineNumber) + return true; + // FIXME: Too strict, functions may have parameter lists, or not. + // if (m_params.type == BreakpointByFunction && m_params.functionName != m_response.functionName) + // return true; + // if (m_params.type == BreakpointByAddress && m_params.address != m_response.address) + // return true; + return false; +} + +void BreakpointItem::updateMarkerIcon() +{ + if (m_marker) { + m_marker->setIcon(icon()); + m_marker->updateMarker(); + } +} + +void BreakpointItem::updateMarker() +{ + FileName file = FileName::fromString(markerFileName()); + int line = markerLineNumber(); + if (m_marker && (file != m_marker->fileName() || line != m_marker->lineNumber())) + destroyMarker(); + + if (!m_marker && !file.isEmpty() && line > 0) + m_marker = new BreakpointMarker(this, file, line); + + if (m_marker) + m_marker->setToolTip(toolTip()); +} + +QIcon BreakpointItem::icon() const +{ + // FIXME: This seems to be called on each cursor blink as soon as the + // cursor is near a line with a breakpoint marker (+/- 2 lines or so). + if (m_parameters.isTracepoint()) + return Icons::TRACEPOINT.icon(); + if (m_parameters.type == WatchpointAtAddress) + return Icons::WATCHPOINT.icon(); + if (m_parameters.type == WatchpointAtExpression) + return Icons::WATCHPOINT.icon(); + if (!m_parameters.enabled) + return Icons::BREAKPOINT_DISABLED.icon(); + if (m_state == BreakpointInserted && !m_parameters.pending) + return Icons::BREAKPOINT.icon(); + return Icons::BREAKPOINT_PENDING.icon(); +} + +QString BreakpointItem::toolTip() const +{ + QString rc; + QTextStream str(&rc); + str << "" << tr("Breakpoint") << "" + << "" + << "" + << ""; + str << "" + << "" + << "" + << "" + << "
" << tr("Internal ID:") + << "" << m_responseId << "
" << tr("State:") + << "" << (requestedParameters().enabled ? tr("Enabled") : tr("Disabled")); + if (m_parameters.pending) + str << ", " << tr("pending"); + str << ", " << stateToString(m_state) << "
" << tr("Breakpoint Type:") + << "" << typeToString(m_requestedParameters.type) << "
" << tr("Marker File:") + << "" << QDir::toNativeSeparators(markerFileName()) << "
" << tr("Marker Line:") + << "" << markerLineNumber() << "
" << tr("Hit Count:") + << "" << m_parameters.hitCount << "

" + << ""; + if (!m_displayName.isEmpty()) { + str << ""; + } + if (m_parameters.type == BreakpointByFunction) { + str << ""; + } + if (m_parameters.type == BreakpointByFileAndLine) { + str << "" + << ""; + } + if (m_requestedParameters.type == BreakpointByFunction || m_parameters.type == BreakpointByFileAndLine) { + str << ""; + } + str << ""; + if (!m_requestedParameters.command.isEmpty() || !m_parameters.command.isEmpty()) { + str << ""; + } + if (!m_requestedParameters.message.isEmpty() || !m_parameters.message.isEmpty()) { + str << ""; + } + if (!m_requestedParameters.condition.isEmpty() || !m_parameters.condition.isEmpty()) { + str << ""; + } + if (m_requestedParameters.ignoreCount || m_parameters.ignoreCount) { + str << ""; + } + if (m_requestedParameters.threadSpec >= 0 || m_parameters.threadSpec >= 0) { + str << ""; + } + str << "
" << tr("Property") + << "" << tr("Requested") + << "" << tr("Obtained") << "
" << tr("Display Name:") + << "" << m_displayName << "
" << tr("Function Name:") + << "" << m_requestedParameters.functionName + << "" << m_parameters.functionName + << "
" << tr("File Name:") + << "" << QDir::toNativeSeparators(m_requestedParameters.fileName) + << "" << QDir::toNativeSeparators(m_parameters.fileName) + << "
" << tr("Line Number:") + << "" << m_requestedParameters.lineNumber + << "" << m_parameters.lineNumber << "
" << tr("Module:") + << "" << m_requestedParameters.module + << "" << m_parameters.module + << "
" << tr("Breakpoint Address:") + << ""; + formatAddress(str, m_requestedParameters.address); + str << ""; + formatAddress(str, m_parameters.address); + str << "
" << tr("Command:") + << "" << m_requestedParameters.command + << "" << m_parameters.command + << "
" << tr("Message:") + << "" << m_requestedParameters.message + << "" << m_parameters.message + << "
" << tr("Condition:") + << "" << m_requestedParameters.condition + << "" << m_parameters.condition + << "
" << tr("Ignore Count:") << ""; + if (m_requestedParameters.ignoreCount) + str << m_parameters.ignoreCount; + str << ""; + if (m_parameters.ignoreCount) + str << m_parameters.ignoreCount; + str << "
" << tr("Thread Specification:") + << ""; + if (m_requestedParameters.threadSpec >= 0) + str << m_requestedParameters.threadSpec; + str << ""; + if (m_parameters.threadSpec >= 0) + str << m_parameters.threadSpec; + str << "
"; + return rc; +} + +void BreakHandler::setWatchpointAtAddress(quint64 address, unsigned size) +{ + BreakpointParameters params(WatchpointAtAddress); + params.address = address; + params.size = size; + if (findWatchpoint(params)) { + qDebug() << "WATCHPOINT EXISTS"; + // removeBreakpoint(index); + return; + } + BreakpointManager::createBreakpointForEngine(params, m_engine); +} + +void BreakHandler::setWatchpointAtExpression(const QString &exp) +{ + BreakpointParameters params(WatchpointAtExpression); + params.expression = exp; + if (findWatchpoint(params)) { + qDebug() << "WATCHPOINT EXISTS"; + // removeBreakpoint(index); + return; + } + BreakpointManager::createBreakpointForEngine(params, m_engine); +} + +void BreakHandler::releaseAllBreakpoints() +{ + for (Breakpoint bp : breakpoints()) { + bp->removeChildren(); + bp->destroyMarker(); + if (GlobalBreakpoint gbp = bp->globalBreakpoint()) + gbp->updateMarker(); + } + clear(); + // The now-unclaimed breakpoints are globally visible again. +} + +QString BreakpointItem::msgWatchpointByExpressionTriggered(const QString &expr) const +{ + return tr("Internal data breakpoint %1 at %2 triggered.") + .arg(responseId()).arg(expr); +} + +QString BreakpointItem::msgWatchpointByExpressionTriggered(const QString &expr, + const QString &threadId) const +{ + return tr("Internal data breakpoint %1 at %2 in thread %3 triggered.") + .arg(responseId()).arg(expr).arg(threadId); +} + +QString BreakpointItem::msgWatchpointByAddressTriggered(quint64 address) const +{ + return tr("Internal data breakpoint %1 at 0x%2 triggered.") + .arg(responseId()).arg(address, 0, 16); +} + +QString BreakpointItem::msgWatchpointByAddressTriggered(quint64 address, + const QString &threadId) const +{ + return tr("Internal data breakpoint %1 at 0x%2 in thread %3 triggered.") + .arg(responseId()).arg(address, 0, 16).arg(threadId); +} + +QString BreakpointItem::msgBreakpointTriggered(const QString &threadId) const +{ + return tr("Stopped at breakpoint %1 in thread %2.") + .arg(responseId()).arg(threadId); +} + + +QVariant SubBreakpointItem::data(int column, int role) const +{ + if (role == Qt::DecorationRole && column == 0) { + return params.enabled ? Icons::BREAKPOINT.icon() + : Icons::BREAKPOINT_DISABLED.icon(); + } + + if (role == Qt::DisplayRole) { + switch (column) { + case BreakpointNumberColumn: + return displayName.isEmpty() ? responseId : displayName; + case BreakpointFunctionColumn: + return params.functionName; + case BreakpointAddressColumn: + if (params.address) + return QString::fromLatin1("0x%1").arg(params.address, 0, 16); + } + } + return QVariant(); +} + + +// +// GlobalBreakpointItem +// + +// Ok to be not thread-safe. The order does not matter and only the gui +// produces authoritative ids. +static int currentId = 0; + +GlobalBreakpointItem::GlobalBreakpointItem() + : m_modelId(++currentId) +{ +} + +GlobalBreakpointItem::~GlobalBreakpointItem() +{} + +QVariant GlobalBreakpointItem::data(int column, int role) const +{ + + switch (column) { + case BreakpointNumberColumn: + if (role == Qt::DisplayRole) { + if (auto engine = usingEngine()) + return engine->runParameters().displayName; + + + return QString("-"); + } + if (role == Qt::DecorationRole) + return icon(); + break; + case BreakpointFunctionColumn: + if (role == Qt::DisplayRole) { + if (!m_params.functionName.isEmpty()) + return m_params.functionName; + if (m_params.type == BreakpointAtMain + || m_params.type == BreakpointAtThrow + || m_params.type == BreakpointAtCatch + || m_params.type == BreakpointAtFork + || m_params.type == BreakpointAtExec + //|| m_params.type == BreakpointAtVFork + || m_params.type == BreakpointAtSysCall) + return typeToString(m_params.type); + if (m_params.type == WatchpointAtAddress) + return BreakHandler::tr("Data at 0x%1").arg(m_params.address, 0, 16); + if (m_params.type == WatchpointAtExpression) + return BreakHandler::tr("Data at %1").arg(m_params.expression); + return empty; + } + break; + case BreakpointFileColumn: + if (role == Qt::DisplayRole) { + QString str = m_params.fileName; + if (!str.isEmpty()) + return QDir::toNativeSeparators(str); + return empty; + } + break; + case BreakpointLineColumn: + if (role == Qt::DisplayRole) { + if (m_params.lineNumber > 0) + return m_params.lineNumber; + return empty; + } + if (role == Qt::UserRole + 1) + return m_params.lineNumber; + break; + case BreakpointAddressColumn: + if (role == Qt::DisplayRole) { + const quint64 address = m_params.address; + if (address) + return QString("0x%1").arg(address, 0, 16); + return QVariant(); + } + break; + case BreakpointConditionColumn: + if (role == Qt::DisplayRole) + return m_params.condition; + if (role == Qt::ToolTipRole) + return BreakHandler::tr("Breakpoint will only be hit if this condition is met."); + if (role == Qt::UserRole + 1) + return m_params.condition; + break; + case BreakpointIgnoreColumn: + if (role == Qt::DisplayRole) { + const int ignoreCount = m_params.ignoreCount; + return ignoreCount ? QVariant(ignoreCount) : QVariant(QString()); + } + if (role == Qt::ToolTipRole) + return BreakHandler::tr("Breakpoint will only be hit after being ignored so many times."); + if (role == Qt::UserRole + 1) + return m_params.ignoreCount; + break; + case BreakpointThreadsColumn: + if (role == Qt::DisplayRole) + return BreakHandler::displayFromThreadSpec(m_params.threadSpec); + if (role == Qt::ToolTipRole) + return BreakHandler::tr("Breakpoint will only be hit in the specified thread(s)."); + if (role == Qt::UserRole + 1) + return BreakHandler::displayFromThreadSpec(m_params.threadSpec); + break; + } + + if (role == Qt::ToolTipRole && boolSetting(UseToolTipsInBreakpointsView)) + return toolTip(); + + return QVariant(); +} + +QIcon GlobalBreakpointItem::icon() const +{ + // FIXME: This seems to be called on each cursor blink as soon as the + // cursor is near a line with a breakpoint marker (+/- 2 lines or so). + if (m_params.isTracepoint()) + return Icons::TRACEPOINT.icon(); + if (m_params.type == WatchpointAtAddress) + return Icons::WATCHPOINT.icon(); + if (m_params.type == WatchpointAtExpression) + return Icons::WATCHPOINT.icon(); + if (!m_params.enabled) + return Icons::BREAKPOINT_DISABLED.icon(); + + return Icons::BREAKPOINT_PENDING.icon(); +} + +QPointer GlobalBreakpointItem::usingEngine() const +{ + for (QPointer engine : EngineManager::engines()) { + for (Breakpoint bp : engine->breakHandler()->breakpoints()) { + if (bp->globalBreakpoint() == this) + return engine; + } + } + return nullptr; +} + +int GlobalBreakpointItem::modelId() const +{ + return m_modelId; +} + +QString GlobalBreakpointItem::displayName() const +{ + return QString::number(m_modelId); +} + +void GlobalBreakpointItem::deleteBreakpoint() +{ + for (QPointer engine : EngineManager::engines()) { + BreakHandler *handler = engine->breakHandler(); + for (Breakpoint bp : handler->breakpoints()) { + if (bp->globalBreakpoint() == this) + handler->removeBreakpoint(bp); + } + } + removeBreakpointFromModel(); +} + +void GlobalBreakpointItem::removeBreakpointFromModel() +{ + delete m_marker; + m_marker = nullptr; + theBreakpointManager->destroyItem(this); +} + +QString GlobalBreakpointItem::markerFileName() const +{ + // Some heuristics to find a "good" file name. + if (!m_params.fileName.isEmpty()) { + QFileInfo fi(m_params.fileName); + if (fi.exists()) + return fi.absoluteFilePath(); + } + return m_params.fileName; +} + +int GlobalBreakpointItem::markerLineNumber() const +{ + return m_params.lineNumber; +} + +void GlobalBreakpointItem::updateMarkerIcon() +{ + if (m_marker) { + m_marker->setIcon(icon()); + m_marker->updateMarker(); + } +} + +void GlobalBreakpointItem::updateMarker() +{ + if (usingEngine() != nullptr) { + // Don't show markers that are claimed by engines. + // FIXME: Apart, perhaps, when the engine's reported location does not match? + destroyMarker(); + return; + } + + const FileName file = FileName::fromString(m_params.fileName); + const int line = m_params.lineNumber; + if (m_marker && (file != m_marker->fileName() || line != m_marker->lineNumber())) + destroyMarker(); + + if (!m_marker && !file.isEmpty() && line > 0) + m_marker = new GlobalBreakpointMarker(this, file, line); + + if (m_marker) + m_marker->setToolTip(toolTip()); +} + +void GlobalBreakpointItem::setEnabled(bool enabled) +{ + QTC_CHECK(m_params.enabled != enabled); + m_params.enabled = enabled; + updateMarkerIcon(); + update(); + + for (QPointer engine : EngineManager::engines()) { + for (Breakpoint bp : engine->breakHandler()->breakpoints()) { + if (bp->globalBreakpoint() == this) + bp->setEnabled(enabled); + } + } +} + +void GlobalBreakpointItem::destroyMarker() +{ + delete m_marker; + m_marker = nullptr; +} + +QString GlobalBreakpointItem::toolTip() const +{ + QString rc; + QTextStream str(&rc); + str << "" << BreakpointItem::tr("Unclaimed Breakpoint") << "" + << "" + //<< "" + << ""; + if (m_params.type == BreakpointByFunction) { + str << ""; + } + if (m_params.type == BreakpointByFileAndLine) { + str << "" + << ""; + } + str << ""; + if (!m_params.command.isEmpty()) + str << ""; + if (!m_params.message.isEmpty()) + str << ""; + if (!m_params.condition.isEmpty()) + str << ""; + if (m_params.ignoreCount) + str << ""; + if (m_params.threadSpec >= 0) + str << ""; + + str << "
" << tr("ID:") << "" << m_id << "
" << BreakpointItem::tr("State:") + << "" << (m_params.enabled ? BreakpointItem::tr("Enabled") : BreakpointItem::tr("Disabled")) + << "
" << BreakpointItem::tr("Breakpoint Type:") + << "" << typeToString(m_params.type) << "
" << BreakpointItem::tr("Function Name:") + << "" << m_params.functionName + << "
" << BreakpointItem::tr("File Name:") + << "" << QDir::toNativeSeparators(m_params.fileName) + << "
" << BreakpointItem::tr("Line Number:") + << "" << m_params.lineNumber; + } + if (m_params.type == BreakpointByFunction || m_params.type == BreakpointByFileAndLine) { + str << "
" << BreakpointItem::tr("Module:") + << "" << m_params.module + << "
" << BreakpointItem::tr("Breakpoint Address:") << ""; + formatAddress(str, m_params.address); + str << "
" << BreakpointItem::tr("Command:") << "" << m_params.command << "
" << BreakpointItem::tr("Message:") << "" << m_params.message << "
" << BreakpointItem::tr("Condition:") << "" << m_params.condition << "
" << BreakpointItem::tr("Ignore Count:") << "" << m_params.ignoreCount << "
" << BreakpointItem::tr("Thread Specification:") << "" << m_params.threadSpec << "

"; + return rc; +} + +// +// BreakpointManager +// + +BreakpointManager::BreakpointManager() +{ + theBreakpointManager = this; + setHeader({tr("Debuggee"), tr("Function"), tr("File"), tr("Line"), tr("Address"), + tr("Condition"), tr("Ignore"), tr("Threads")}); +} + +QAbstractItemModel *BreakpointManager::model() +{ + return theBreakpointManager; +} + +const GlobalBreakpoints BreakpointManager::globalBreakpoints() +{ + GlobalBreakpoints items; + theBreakpointManager->forItemsAtLevel<1>([&items](GlobalBreakpointItem *b) { items.append(b); }); + return items; +} + +GlobalBreakpoint BreakpointManager::findSimilarBreakpoint(const BreakpointParameters &needle) +{ + // Search a breakpoint we might refer to. + return theBreakpointManager->findItemAtLevel<1>([needle](const GlobalBreakpoint &gbp) { + return gbp && isSimilarTo(gbp->m_params, needle); + }); +} + +void BreakpointManager::claimBreakpointsForEngine(DebuggerEngine *engine) +{ + theBreakpointManager->forItemsAtLevel<1>([&](GlobalBreakpoint gbp) { + engine->breakHandler()->tryClaimBreakpoint(gbp); + gbp->updateMarker(); + }); +} + +GlobalBreakpoint BreakpointManager::createBreakpointHelper(const BreakpointParameters ¶ms) +{ + GlobalBreakpoint gbp = new GlobalBreakpointItem; + gbp->m_params = params; + gbp->updateMarker(); + theBreakpointManager->rootItem()->appendChild(gbp); + return gbp; +} + +GlobalBreakpoint BreakpointManager::findBreakpointByIndex(const QModelIndex &index) +{ + return theBreakpointManager->itemForIndexAtLevel<1>(index); +} + +GlobalBreakpoints BreakpointManager::findBreakpointsByIndex(const QList &list) +{ + QSet items; + for (const QModelIndex &index : list) { + if (GlobalBreakpoint gbp = findBreakpointByIndex(index)) + items.insert(gbp); + } + return items.toList(); +} + +GlobalBreakpoint BreakpointManager::createBreakpoint(const BreakpointParameters ¶ms) +{ + GlobalBreakpoint gbp = createBreakpointHelper(params); + for (QPointer engine : EngineManager::engines()) + engine->breakHandler()->tryClaimBreakpoint(gbp); + return gbp; +} + +void BreakpointManager::createBreakpointForEngine(const BreakpointParameters ¶ms, DebuggerEngine *engine) +{ + GlobalBreakpoint gbp = createBreakpointHelper(params); + engine->breakHandler()->tryClaimBreakpoint(gbp); +} + +void BreakpointManager::toggleBreakpoint(const ContextData &location, const QString &tracePointMessage) +{ + QTC_ASSERT(location.isValid(), return); + GlobalBreakpoint gbp = findBreakpointByLocation(location); + + if (gbp) { + gbp->deleteBreakpoint(); + } else { + BreakpointParameters data; + if (location.type == LocationByFile) { + data.type = BreakpointByFileAndLine; + if (boolSetting(BreakpointsFullPathByDefault)) + data.pathUsage = BreakpointUseFullPath; + data.tracepoint = !tracePointMessage.isEmpty(); + data.message = tracePointMessage; + data.fileName = location.fileName; + data.lineNumber = location.lineNumber; + } else if (location.type == LocationByAddress) { + data.type = BreakpointByAddress; + data.tracepoint = !tracePointMessage.isEmpty(); + data.message = tracePointMessage; + data.address = location.address; + } + BreakpointManager::createBreakpoint(data); + } +} + +GlobalBreakpoint BreakpointManager::findBreakpointByLocation(const ContextData &location) +{ + return theBreakpointManager->findItemAtLevel<1>([&location](GlobalBreakpoint bp) { + if (location.type == LocationByFile) + return bp->m_params.isLocatedAt(location.fileName, location.lineNumber, QString()); + if (location.type == LocationByAddress) + return bp->m_params.address == location.address; + return false; + }); + return {}; +} + +void BreakpointManager::executeAddBreakpointDialog() +{ + BreakpointParameters data(BreakpointByFileAndLine); + BreakpointParts parts = NoParts; + BreakpointDialog dialog(nullptr, ICore::dialogParent()); + dialog.setWindowTitle(tr("Add Breakpoint")); + if (dialog.showDialog(&data, &parts)) + BreakpointManager::createBreakpoint(data); +} + +QVariant BreakpointManager::data(const QModelIndex &idx, int role) const +{ + if (role == BaseTreeView::ItemDelegateRole) + return QVariant::fromValue(new LeftElideDelegate); + + return BreakpointManagerModel::data(idx, role); +} + +bool BreakpointManager::setData(const QModelIndex &idx, const QVariant &value, int role) +{ + if (role == BaseTreeView::ItemActivatedRole) { + if (GlobalBreakpoint bp = findBreakpointByIndex(idx)) + gotoLocation(bp); + return true; + } + + if (role == BaseTreeView::ItemViewEventRole) { + ItemViewEvent ev = value.value(); + + if (ev.as()) + return contextMenuEvent(ev); + + if (auto kev = ev.as(QEvent::KeyPress)) { + if (kev->key() == Qt::Key_Delete) { + QModelIndexList si = ev.currentOrSelectedRows(); + const GlobalBreakpoints gbps = findBreakpointsByIndex(si); + for (GlobalBreakpoint gbp : gbps) + gbp->deleteBreakpoint(); +// int row = qMin(rowCount() - ids.size() - 1, idx.row()); +// setCurrentIndex(index(row, 0)); FIXME + return true; + } +// if (kev->key() == Qt::Key_Space) { +// const QModelIndexList selectedIds = ev.selectedRows(); +// if (!selectedIds.isEmpty()) { +// const GlobalBreakpoints gbps = findBreakpointsByIndex(selectedIds); +// const bool isEnabled = gbps.isEmpty() || gbps.at(0)->isEnabled(); +// for (GlobalBreakpoint gbp : gbps) +// gbp->m_parameters.enabled = isEnabled; +// scheduleSynchronization(); +// return true; +// } +// } + } + + if (ev.as(QEvent::MouseButtonDblClick)) { + if (GlobalBreakpoint gbp = findBreakpointByIndex(idx)) { +// if (idx.column() >= BreakpointAddressColumn) +// editBreakpoints({gbp}, ev.view()); +// else + gotoLocation(gbp); + } else { + BreakpointManager::executeAddBreakpointDialog(); + } + return true; + } + } + + return false; +} + +bool BreakpointManager::contextMenuEvent(const ItemViewEvent &ev) +{ + const QModelIndexList selectedIndices = ev.selectedRows(); + + const GlobalBreakpoints selectedBreakpoints = findBreakpointsByIndex(selectedIndices); + const bool breakpointsEnabled = selectedBreakpoints.isEmpty() || selectedBreakpoints.at(0)->isEnabled(); + +// QList selectedLocations; +// const bool locationsEnabled = selectedLocations.isEmpty() || selectedLocations.at(0)->params.enabled; + + auto menu = new QMenu; + + addAction(menu, tr("Add Breakpoint..."), true, &BreakpointManager::executeAddBreakpointDialog); + + addAction(menu, tr("Delete Selected Breakpoints"), + !selectedBreakpoints.isEmpty(), + [selectedBreakpoints] { + for (GlobalBreakpoint gbp : selectedBreakpoints) + gbp->deleteBreakpoint(); + }); + +// addAction(menu, tr("Edit Selected Breakpoints..."), +// !selectedBreakpoints.isEmpty(), +// [this, selectedBreakpoints, ev] { editBreakpoints(selectedBreakpoints, ev.view()); }); + + addAction(menu, + selectedBreakpoints.size() > 1 + ? breakpointsEnabled ? tr("Disable Selected Breakpoints") : tr("Enable Selected Breakpoints") + : breakpointsEnabled ? tr("Disable Breakpoint") : tr("Enable Breakpoint"), + !selectedBreakpoints.isEmpty(), + [selectedBreakpoints, breakpointsEnabled] { + for (GlobalBreakpoint gbp : selectedBreakpoints) + gbp->setEnabled(!breakpointsEnabled); + } + ); + + menu->addSeparator(); + + addAction(menu, tr("Delete All Breakpoints"), + rowCount() > 0, + &BreakpointManager::executeDeleteAllBreakpointsDialog); + + // Delete by file: Find indices of breakpoints of the same file. + GlobalBreakpoints breakpointsInFile; + QString file; + if (GlobalBreakpoint gbp = itemForIndexAtLevel<1>(ev.index())) { + if (!file.isEmpty()) { + for (int i = 0; i != rowCount(); ++i) + if (gbp->markerFileName() == file) + breakpointsInFile.append(gbp); + } + } + addAction(menu, tr("Delete Breakpoints of \"%1\"").arg(file), + tr("Delete Breakpoints of File"), + breakpointsInFile.size() > 1, + [breakpointsInFile] { + for (GlobalBreakpoint gbp : breakpointsInFile) + gbp->deleteBreakpoint(); + }); + + menu->addSeparator(); + + menu->addAction(action(UseToolTipsInBreakpointsView)); + menu->addAction(action(UseAddressInBreakpointsView)); + menu->addSeparator(); + menu->addAction(action(SettingsDialog)); + + menu->popup(ev.globalPos()); + + return true; +} + +void BreakpointManager::gotoLocation(const GlobalBreakpoint &gbp) const +{ + QTC_ASSERT(gbp, return); + const QString file = QDir::cleanPath(gbp->markerFileName()); + if (IEditor *editor = EditorManager::openEditor(file)) + editor->gotoLine(gbp->markerLineNumber(), 0); +} + +void BreakpointManager::executeDeleteAllBreakpointsDialog() +{ + QDialogButtonBox::StandardButton pressed = + CheckableMessageBox::doNotAskAgainQuestion(ICore::dialogParent(), + tr("Remove All Breakpoints"), + tr("Are you sure you want to remove all breakpoints " + "from all files in the current session?"), + ICore::settings(), + "RemoveAllBreakpoints"); + if (pressed != QDialogButtonBox::Yes) + return; + + for (GlobalBreakpoint gbp : globalBreakpoints()) + gbp->deleteBreakpoint(); +} + +void BreakpointManager::saveSessionData() { QList list; - forItemsAtLevel<1>([&list](BreakpointItem *b) { - const BreakpointParameters ¶ms = b->m_params; + theBreakpointManager->forItemsAtLevel<1>([&list](const GlobalBreakpoint &bp) { + const BreakpointParameters ¶ms = bp->m_params; QMap map; if (params.type != BreakpointByFileAndLine) map.insert("type", params.type); @@ -1062,8 +2707,16 @@ void BreakHandler::saveBreakpoints() setSessionValue("Breakpoints", list); } -void BreakHandler::loadBreakpoints() +void BreakpointManager::aboutToUnloadSession() { + saveSessionData(); + theBreakpointManager->clear(); +} + +void BreakpointManager::loadSessionData() +{ + theBreakpointManager->clear(); + const QVariant value = sessionValue("Breakpoints"); const QList list = value.toList(); for (const QVariant &var : list) { @@ -1118,1379 +2771,11 @@ void BreakHandler::loadBreakpoints() if (v.isValid()) params.message = v.toString(); if (params.isValid()) - appendBreakpointInternal(params); + BreakpointManager::createBreakpoint(params); else qWarning("Not restoring invalid breakpoint: %s", qPrintable(params.toString())); } } -void BreakHandler::updateMarkers() -{ - forItemsAtLevel<1>([](BreakpointItem *b) { b->updateMarker(); }); -} - -Breakpoint BreakHandler::findBreakpointByIndex(const QModelIndex &index) const -{ - return Breakpoint(itemForIndexAtLevel<1>(index)); -} - -Breakpoints BreakHandler::findBreakpointsByIndex(const QList &list) const -{ - QSet ids; - for (const QModelIndex &index : list) { - if (Breakpoint b = findBreakpointByIndex(index)) - ids.insert(b); - } - return ids.toList(); -} - -QString BreakHandler::displayFromThreadSpec(int spec) -{ - return spec == -1 ? BreakHandler::tr("(all)") : QString::number(spec); -} - -int BreakHandler::threadSpecFromDisplay(const QString &str) -{ - bool ok = false; - int result = str.toInt(&ok); - return ok ? result : -1; -} - -const QString empty(QLatin1Char('-')); - -QVariant BreakpointItem::data(int column, int role) const -{ - bool orig = false; - switch (m_state) { - case BreakpointInsertRequested: - case BreakpointInsertProceeding: - case BreakpointChangeRequested: - case BreakpointChangeProceeding: - case BreakpointInserted: - case BreakpointRemoveRequested: - case BreakpointRemoveProceeding: - break; - case BreakpointNew: - case BreakpointDead: - orig = true; - break; - }; - - if (role == Qt::ForegroundRole) { - static const QVariant gray(QColor(140, 140, 140)); - switch (m_state) { - case BreakpointInsertRequested: - case BreakpointInsertProceeding: - case BreakpointChangeRequested: - case BreakpointChangeProceeding: - case BreakpointRemoveRequested: - case BreakpointRemoveProceeding: - return gray; - case BreakpointInserted: - case BreakpointNew: - case BreakpointDead: - break; - }; - } - - switch (column) { - case BreakpointNumberColumn: - if (role == Qt::DisplayRole) - return m_id.toString(); - if (role == Qt::DecorationRole) - return icon(); - break; - case BreakpointFunctionColumn: - if (role == Qt::DisplayRole) { - if (!m_response.functionName.isEmpty()) - return simplifyType(m_response.functionName); - if (!m_params.functionName.isEmpty()) - return m_params.functionName; - if (m_params.type == BreakpointAtMain - || m_params.type == BreakpointAtThrow - || m_params.type == BreakpointAtCatch - || m_params.type == BreakpointAtFork - || m_params.type == BreakpointAtExec - //|| m_params.type == BreakpointAtVFork - || m_params.type == BreakpointAtSysCall) - return typeToString(m_params.type); - if (m_params.type == WatchpointAtAddress) { - quint64 address = m_response.address ? m_response.address : m_params.address; - return BreakHandler::tr("Data at 0x%1").arg(address, 0, 16); - } - if (m_params.type == WatchpointAtExpression) { - QString expression = !m_response.expression.isEmpty() - ? m_response.expression : m_params.expression; - return BreakHandler::tr("Data at %1").arg(expression); - } - return empty; - } - break; - case BreakpointFileColumn: - if (role == Qt::DisplayRole) { - QString str; - if (!m_response.fileName.isEmpty()) - str = m_response.fileName; - if (str.isEmpty() && !m_params.fileName.isEmpty()) - str = m_params.fileName; - if (str.isEmpty()) { - QString s = FileName::fromString(str).fileName(); - if (!s.isEmpty()) - str = s; - } - // FIXME: better? - //if (params.multiple && str.isEmpty() && !response.fileName.isEmpty()) - // str = response.fileName; - if (!str.isEmpty()) - return QDir::toNativeSeparators(str); - return empty; - } - break; - case BreakpointLineColumn: - if (role == Qt::DisplayRole) { - if (m_response.lineNumber > 0) - return m_response.lineNumber; - if (m_params.lineNumber > 0) - return m_params.lineNumber; - return empty; - } - if (role == Qt::UserRole + 1) - return m_params.lineNumber; - break; - case BreakpointAddressColumn: - if (role == Qt::DisplayRole) { - const quint64 address = orig ? m_params.address : m_response.address; - if (address) - return QString("0x%1").arg(address, 0, 16); - return QVariant(); - } - break; - case BreakpointConditionColumn: - if (role == Qt::DisplayRole) - return orig ? m_params.condition : m_response.condition; - if (role == Qt::ToolTipRole) - return BreakHandler::tr("Breakpoint will only be hit if this condition is met."); - if (role == Qt::UserRole + 1) - return m_params.condition; - break; - case BreakpointIgnoreColumn: - if (role == Qt::DisplayRole) { - const int ignoreCount = - orig ? m_params.ignoreCount : m_response.ignoreCount; - return ignoreCount ? QVariant(ignoreCount) : QVariant(QString()); - } - if (role == Qt::ToolTipRole) - return BreakHandler::tr("Breakpoint will only be hit after being ignored so many times."); - if (role == Qt::UserRole + 1) - return m_params.ignoreCount; - break; - case BreakpointThreadsColumn: - if (role == Qt::DisplayRole) - return BreakHandler::displayFromThreadSpec(orig ? m_params.threadSpec : m_response.threadSpec); - if (role == Qt::ToolTipRole) - return BreakHandler::tr("Breakpoint will only be hit in the specified thread(s)."); - if (role == Qt::UserRole + 1) - return BreakHandler::displayFromThreadSpec(m_params.threadSpec); - break; - } - - if (role == Qt::ToolTipRole && boolSetting(UseToolTipsInBreakpointsView)) - return toToolTip(); - - return QVariant(); -} - -#define PROPERTY(type, getter, setter) \ -\ -type Breakpoint::getter() const \ -{ \ - return parameters().getter; \ -} \ -\ -void Breakpoint::setter(const type &value) \ -{ \ - QTC_ASSERT(b, return); \ - if (b->m_params.getter == value) \ - return; \ - b->m_params.getter = value; \ - if (b->m_state != BreakpointNew) { \ - b->m_state = BreakpointChangeRequested; \ - b->scheduleSynchronization(); \ - } \ -} - -PROPERTY(BreakpointPathUsage, pathUsage, setPathUsage) -PROPERTY(QString, fileName, setFileName) -PROPERTY(QString, functionName, setFunctionName) -PROPERTY(BreakpointType, type, setType) -PROPERTY(int, threadSpec, setThreadSpec) -PROPERTY(QString, condition, setCondition) -PROPERTY(QString, command, setCommand) -PROPERTY(quint64, address, setAddress) -PROPERTY(QString, expression, setExpression) -PROPERTY(QString, message, setMessage) -PROPERTY(int, ignoreCount, setIgnoreCount) - -void BreakpointItem::scheduleSynchronization() -{ - m_handler->scheduleSynchronization(); -} - -const BreakpointParameters &Breakpoint::parameters() const -{ - static BreakpointParameters p; - QTC_ASSERT(b, return p); - return b->m_params; -} - -void Breakpoint::addToCommand(DebuggerCommand *cmd) const -{ - cmd->arg("modelid", id().toString()); - cmd->arg("id", int(response().id.majorPart())); - cmd->arg("type", type()); - cmd->arg("ignorecount", ignoreCount()); - cmd->arg("condition", toHex(condition())); - cmd->arg("command", toHex(command())); - cmd->arg("function", functionName()); - cmd->arg("oneshot", isOneShot()); - cmd->arg("enabled", isEnabled()); - cmd->arg("file", fileName()); - cmd->arg("line", lineNumber()); - cmd->arg("address", address()); - cmd->arg("expression", expression()); -} - -BreakpointState Breakpoint::state() const -{ - QTC_ASSERT(b, return BreakpointState()); - return b->m_state; -} - -int Breakpoint::lineNumber() const { return parameters().lineNumber; } - -bool Breakpoint::isEnabled() const { return parameters().enabled; } - -bool Breakpoint::isWatchpoint() const { return parameters().isWatchpoint(); } - -bool Breakpoint::isTracepoint() const { return parameters().isTracepoint(); } - -QIcon Breakpoint::icon() const { return b ? b->icon() : QIcon(); } - -DebuggerEngine *Breakpoint::engine() const -{ - return b ? b->m_engine : nullptr; -} - -const BreakpointResponse &Breakpoint::response() const -{ - static BreakpointResponse r; - return b ? b->m_response : r; -} - -bool Breakpoint::isOneShot() const { return parameters().oneShot; } - -void Breakpoint::removeAlienBreakpoint() -{ - b->m_state = BreakpointRemoveProceeding; - b->deleteThis(); -} - -void Breakpoint::removeBreakpoint() const -{ - if (b) - b->removeBreakpoint(); -} - -Breakpoint::Breakpoint(BreakpointItem *b) - : b(b) -{} - -void Breakpoint::setEnabled(bool on) const -{ - QTC_ASSERT(b, return); - if (b->m_params.enabled == on) - return; - b->m_params.enabled = on; - b->updateMarkerIcon(); - b->update(); - if (b->m_engine) { - b->m_state = BreakpointChangeRequested; - b->scheduleSynchronization(); - } -} - -void Breakpoint::setMarkerFileAndLine(const QString &fileName, int lineNumber) -{ - if (b) - b->setMarkerFileAndLine(fileName, lineNumber); -} - -void Breakpoint::setTracepoint(bool on) -{ - if (b->m_params.tracepoint == on) - return; - b->m_params.tracepoint = on; - b->updateMarkerIcon(); - - if (b->m_engine) { - b->m_state = BreakpointChangeRequested; - b->scheduleSynchronization(); - } -} - -void BreakpointItem::setMarkerFileAndLine(const QString &fileName, int lineNumber) -{ - if (m_response.fileName == fileName && m_response.lineNumber == lineNumber) - return; - m_response.fileName = fileName; - m_response.lineNumber = lineNumber; - destroyMarker(); - updateMarker(); - update(); -} - -void Breakpoint::setEngine(DebuggerEngine *value) -{ - QTC_ASSERT(b->m_state == BreakpointNew, qDebug() << "STATE: " << b->m_state << b->m_id); - QTC_ASSERT(!b->m_engine, qDebug() << "NO ENGINE" << b->m_id; return); - b->m_engine = value; - b->m_state = BreakpointInsertRequested; - b->m_response = BreakpointResponse(); - b->updateMarker(); - //b->scheduleSynchronization(); -} - -static bool isAllowedTransition(BreakpointState from, BreakpointState to) -{ - switch (from) { - case BreakpointNew: - return to == BreakpointInsertRequested - || to == BreakpointDead; - case BreakpointInsertRequested: - return to == BreakpointInsertProceeding; - case BreakpointInsertProceeding: - return to == BreakpointInserted - || to == BreakpointDead - || to == BreakpointChangeRequested - || to == BreakpointRemoveRequested; - case BreakpointChangeRequested: - return to == BreakpointChangeProceeding; - case BreakpointChangeProceeding: - return to == BreakpointInserted - || to == BreakpointDead; - case BreakpointInserted: - return to == BreakpointChangeRequested - || to == BreakpointRemoveRequested; - case BreakpointRemoveRequested: - return to == BreakpointRemoveProceeding; - case BreakpointRemoveProceeding: - return to == BreakpointDead; - case BreakpointDead: - return false; - } - qDebug() << "UNKNOWN BREAKPOINT STATE:" << from; - return false; -} - -bool BreakpointItem::isEngineRunning() const -{ - if (!m_engine) - return false; - const DebuggerState state = m_engine->state(); - return state != DebuggerFinished && state != DebuggerNotReady; -} - -void BreakpointItem::setState(BreakpointState state) -{ - //qDebug() << "BREAKPOINT STATE TRANSITION, ID: " << m_id - // << " FROM: " << state << " TO: " << state; - if (!isAllowedTransition(m_state, state)) { - qDebug() << "UNEXPECTED BREAKPOINT STATE TRANSITION" << m_state << state; - QTC_CHECK(false); - } - - if (m_state == state) { - qDebug() << "STATE UNCHANGED: " << m_id << m_state; - return; - } - - m_state = state; - - // FIXME: updateMarker() should recognize the need for icon changes. - if (state == BreakpointInserted) { - destroyMarker(); - updateMarker(); - } - update(); -} - -void BreakpointItem::deleteThis() -{ - setState(BreakpointDead); - destroyMarker(); - - // This is called from b directly. So delay deletion of b. - ExtensionSystem::InvokerBase invoker; - invoker.addArgument(m_id); - invoker.setConnectionType(Qt::QueuedConnection); - invoker.invoke(m_handler, "deletionHelper"); - QTC_CHECK(invoker.wasSuccessful()); -} - -void Breakpoint::gotoState(BreakpointState target, BreakpointState assumedCurrent) -{ - QTC_ASSERT(b, return); - QTC_ASSERT(b->m_state == assumedCurrent, qDebug() << b->m_state); - b->setState(target); -} - -void Breakpoint::notifyBreakpointChangeAfterInsertNeeded() -{ - gotoState(BreakpointChangeRequested, BreakpointInsertProceeding); -} - -void Breakpoint::notifyBreakpointInsertProceeding() -{ - gotoState(BreakpointInsertProceeding, BreakpointInsertRequested); -} - -void Breakpoint::notifyBreakpointInsertOk() -{ - gotoState(BreakpointInserted, BreakpointInsertProceeding); - if (b->m_engine) - b->m_engine->updateBreakpointMarker(*this); -} - -void Breakpoint::notifyBreakpointInsertFailed() -{ - gotoState(BreakpointDead, BreakpointInsertProceeding); -} - -void Breakpoint::notifyBreakpointRemoveProceeding() -{ - gotoState(BreakpointRemoveProceeding, BreakpointRemoveRequested); -} - -void Breakpoint::notifyBreakpointRemoveOk() -{ - QTC_ASSERT(b, return); - QTC_ASSERT(b->m_state == BreakpointRemoveProceeding, qDebug() << b->m_state); - if (b->m_engine) - b->m_engine->removeBreakpointMarker(*this); - b->deleteThis(); -} - -void Breakpoint::notifyBreakpointRemoveFailed() -{ - QTC_ASSERT(b, return); - QTC_ASSERT(b->m_state == BreakpointRemoveProceeding, qDebug() << b->m_state); - if (b->m_engine) - b->m_engine->removeBreakpointMarker(*this); - b->deleteThis(); -} - -void Breakpoint::notifyBreakpointChangeProceeding() -{ - gotoState(BreakpointChangeProceeding, BreakpointChangeRequested); -} - -void Breakpoint::notifyBreakpointChangeOk() -{ - gotoState(BreakpointInserted, BreakpointChangeProceeding); -} - -void Breakpoint::notifyBreakpointChangeFailed() -{ - gotoState(BreakpointDead, BreakpointChangeProceeding); -} - -void Breakpoint::notifyBreakpointReleased() -{ - QTC_ASSERT(b, return); - b->removeChildren(); - //QTC_ASSERT(b->m_state == BreakpointChangeProceeding, qDebug() << b->m_state); - b->m_state = BreakpointNew; - b->m_engine = nullptr; - b->m_response = BreakpointResponse(); - b->destroyMarker(); - b->updateMarker(); - if (b->m_params.type == WatchpointAtAddress - || b->m_params.type == WatchpointAtExpression - || b->m_params.type == BreakpointByAddress) - b->m_params.enabled = false; - else - b->m_params.address = 0; - b->update(); -} - -void Breakpoint::notifyBreakpointAdjusted(const BreakpointParameters ¶ms) -{ - QTC_ASSERT(b, return); - QTC_ASSERT(b->m_state == BreakpointInserted, qDebug() << b->m_state); - b->m_params = params; - //if (b->needsChange()) - // b->setState(BreakpointChangeRequested); -} - -void Breakpoint::notifyBreakpointNeedsReinsertion() -{ - QTC_ASSERT(b, return); - QTC_ASSERT(b->m_state == BreakpointChangeProceeding, qDebug() << b->m_state); - b->m_state = BreakpointInsertRequested; -} - -void BreakpointItem::removeBreakpoint() -{ - switch (m_state) { - case BreakpointRemoveRequested: - break; - case BreakpointInserted: - case BreakpointInsertProceeding: - setState(BreakpointRemoveRequested); - scheduleSynchronization(); - break; - case BreakpointNew: - deleteThis(); - break; - default: - qWarning("Warning: Cannot remove breakpoint %s in state '%s'.", - qPrintable(m_id.toString()), qPrintable(stateToString(m_state))); - m_state = BreakpointRemoveRequested; - break; - } -} - -void BreakHandler::appendBreakpoint(const BreakpointParameters ¶ms) -{ - appendBreakpointInternal(params); - scheduleSynchronization(); -} - -void BreakHandler::appendBreakpointInternal(const BreakpointParameters ¶ms) -{ - if (!params.isValid()) { - qWarning("Not adding invalid breakpoint: %s", qPrintable(params.toString())); - return; - } - - auto b = new BreakpointItem(this); - b->m_params = params; - b->updateMarker(); - rootItem()->appendChild(b); -} - -void BreakHandler::handleAlienBreakpoint(const BreakpointResponse &response, DebuggerEngine *engine) -{ - Breakpoint b = findSimilarBreakpoint(response); - if (b) { - if (response.id.isMinor()) - b.insertSubBreakpoint(response); - else - b.setResponse(response); - } else { - auto b = new BreakpointItem(this); - b->m_params = response; - b->m_response = response; - b->m_state = BreakpointInserted; - b->m_engine = engine; - b->updateMarker(); - rootItem()->appendChild(b); - } -} - -void Breakpoint::insertSubBreakpoint(const BreakpointResponse ¶ms) -{ - QTC_ASSERT(b, return); - b->insertSubBreakpoint(params); -} - -void BreakpointItem::insertSubBreakpoint(const BreakpointResponse ¶ms) -{ - QTC_ASSERT(params.id.isMinor(), return); - - int minorPart = params.id.minorPart(); - - LocationItem *l = findFirstLevelChild([minorPart](LocationItem *l) { - return l->params.id.minorPart() == minorPart; - }); - if (l) { - // This modifies an existing sub-breakpoint. - l->params = params; - l->update(); - } else { - // This is a new sub-breakpoint. - l = new LocationItem; - l->params = params; - appendChild(l); - expand(); - } -} - -void BreakHandler::saveSessionData() -{ - saveBreakpoints(); -} - -void BreakHandler::loadSessionData() -{ - clear(); - loadBreakpoints(); -} - -void BreakHandler::breakByFunction(const QString &functionName) -{ - // One breakpoint per function is enough for now. This does not handle - // combinations of multiple conditions and ignore counts, though. - bool found = findItemAtLevel<1>([functionName](BreakpointItem *b) { - const BreakpointParameters ¶ms = b->m_params; - return params.functionName == functionName - && params.condition.isEmpty() - && params.ignoreCount == 0; - }); - if (found) - return; - - BreakpointParameters params(BreakpointByFunction); - params.functionName = functionName; - appendBreakpoint(params); -} - -void BreakHandler::scheduleSynchronization() -{ - if (m_syncTimerId == -1) - m_syncTimerId = startTimer(10); -} - -void BreakHandler::timerEvent(QTimerEvent *event) -{ - QTC_ASSERT(event->timerId() == m_syncTimerId, return); - killTimer(m_syncTimerId); - m_syncTimerId = -1; - saveBreakpoints(); // FIXME: remove? - Internal::synchronizeBreakpoints(); -} - -void Breakpoint::gotoLocation() const -{ - if (DebuggerEngine *engine = currentEngine()) { - if (b->m_params.type == BreakpointByAddress) { - engine->gotoLocation(b->m_params.address); - } else { - // Don't use gotoLocation unconditionally as this ends up in - // disassembly if OperateByInstruction is on. But fallback - // to disassembly if we can't open the file. - const QString file = QDir::cleanPath(b->markerFileName()); - if (IEditor *editor = EditorManager::openEditor(file)) - editor->gotoLine(b->markerLineNumber(), 0); - else - engine->openDisassemblerView(Location(b->m_response.address)); - } - } -} - -void BreakpointItem::updateFileNameFromMarker(const QString &fileName) -{ - m_params.fileName = fileName; - update(); -} - -void BreakpointItem::updateLineNumberFromMarker(int lineNumber) -{ - // Ignore updates to the "real" line number while the debugger is - // running, as this can be triggered by moving the breakpoint to - // the next line that generated code. - if (m_params.lineNumber == lineNumber) - ; // Nothing - else if (isEngineRunning()) - m_params.lineNumber += lineNumber - m_response.lineNumber; - else - m_params.lineNumber = lineNumber; - updateMarker(); - update(); -} - -void BreakpointItem::changeLineNumberFromMarker(int lineNumber) -{ - m_params.lineNumber = lineNumber; - - // We need to delay this as it is called from a marker which will be destroyed. - ExtensionSystem::InvokerBase invoker; - invoker.addArgument(m_id); - invoker.setConnectionType(Qt::QueuedConnection); - invoker.invoke(m_handler, "changeLineNumberFromMarkerHelper"); - QTC_CHECK(invoker.wasSuccessful()); -} - -void BreakHandler::changeLineNumberFromMarkerHelper(BreakpointModelId id) -{ - Breakpoint b = breakpointById(id); - QTC_ASSERT(b, return); - BreakpointParameters params = b.parameters(); - destroyItem(b.b); - appendBreakpoint(params); -} - -const Breakpoints BreakHandler::allBreakpoints() const -{ - Breakpoints items; - forItemsAtLevel<1>([&items](BreakpointItem *b) { items.append(Breakpoint(b)); }); - return items; -} - -const Breakpoints BreakHandler::unclaimedBreakpoints() const -{ - return engineBreakpoints(nullptr); -} - -const Breakpoints BreakHandler::engineBreakpoints(DebuggerEngine *engine) const -{ - Breakpoints items; - forItemsAtLevel<1>([&items, engine](BreakpointItem *b) { - if (b->m_engine == engine) - items.append(Breakpoint(b)); - }); - return items; -} - -QStringList BreakHandler::engineBreakpointPaths(DebuggerEngine *engine) const -{ - QSet set; - forItemsAtLevel<1>([&set, engine](BreakpointItem *b) { - if (b->m_engine == engine) { - if (b->m_params.type == BreakpointByFileAndLine) - set.insert(QFileInfo(b->m_params.fileName).dir().path()); - } - }); - return set.toList(); -} - -void Breakpoint::setResponse(const BreakpointResponse &response) -{ - QTC_ASSERT(b, return); - b->m_response = response; - b->destroyMarker(); - b->updateMarker(); - // Take over corrected values from response. - if ((b->m_params.type == BreakpointByFileAndLine - || b->m_params.type == BreakpointByFunction) - && !response.module.isEmpty()) - b->m_params.module = response.module; -} - -bool Internal::Breakpoint::needsChange() const -{ - return b && b->needsChange(); -} - -void Breakpoint::changeBreakpointData(const BreakpointParameters ¶ms) -{ - if (!b) - return; - if (params == b->m_params) - return; - b->m_params = params; - if (b->m_engine) - b->m_engine->updateBreakpointMarker(*this); - b->destroyMarker(); - b->updateMarker(); - b->update(); - if (b->needsChange() && b->m_engine && b->m_state != BreakpointNew) { - b->setState(BreakpointChangeRequested); - b->m_handler->scheduleSynchronization(); - } -} - -bool BreakHandler::setData(const QModelIndex &idx, const QVariant &value, int role) -{ - if (role == BaseTreeView::ItemActivatedRole) { - if (Breakpoint bp = findBreakpointByIndex(idx)) - bp.gotoLocation(); - return true; - } - - if (role == BaseTreeView::ItemViewEventRole) { - ItemViewEvent ev = value.value(); - - if (ev.as()) - return contextMenuEvent(ev); - - if (auto kev = ev.as(QEvent::KeyPress)) { - if (kev->key() == Qt::Key_Delete) { - QModelIndexList si = ev.currentOrSelectedRows(); - const Breakpoints ids = findBreakpointsByIndex(si); -// int row = qMin(rowCount() - ids.size() - 1, idx.row()); - deleteBreakpoints(ids); -// setCurrentIndex(index(row, 0)); FIXME - return true; - } - if (kev->key() == Qt::Key_Space) { - const QModelIndexList selectedIds = ev.selectedRows(); - if (!selectedIds.isEmpty()) { - const Breakpoints items = findBreakpointsByIndex(selectedIds); - const bool isEnabled = items.isEmpty() || items.at(0).isEnabled(); - setBreakpointsEnabled(items, !isEnabled); -// FIXME -// for (const QModelIndex &id : selectedIds) -// update(id); - return true; - } - } - } - - if (ev.as(QEvent::MouseButtonDblClick)) { - if (Breakpoint b = findBreakpointByIndex(idx)) { - if (idx.column() >= BreakpointAddressColumn) - editBreakpoints({b}, ev.view()); - else - b.gotoLocation(); - } else if (LocationItem *l = itemForIndexAtLevel<2>(idx)) { - Breakpoint(l->parent()).gotoLocation(); - } else { - addBreakpoint(); - } - return true; - } - } - - return false; -} - -bool BreakHandler::contextMenuEvent(const ItemViewEvent &ev) -{ - const QModelIndexList selectedIndices = ev.selectedRows(); - - const Breakpoints selectedBreakpoints = findBreakpointsByIndex(selectedIndices); - const bool breakpointsEnabled = selectedBreakpoints.isEmpty() || selectedBreakpoints.at(0).isEnabled(); - - QList selectedLocations; - bool handlesIndividualLocations = false; - for (const QModelIndex &index : selectedIndices) { - if (LocationItem *location = itemForIndexAtLevel<2>(index)) { - if (selectedLocations.contains(location)) - continue; - selectedLocations.append(location); - DebuggerEngine *engine = location->parent()->m_engine; - if (engine && engine->hasCapability(BreakIndividualLocationsCapability)) - handlesIndividualLocations = true; - } - } - const bool locationsEnabled = selectedLocations.isEmpty() || selectedLocations.at(0)->params.enabled; - - auto menu = new QMenu; - - addAction(menu, tr("Add Breakpoint..."), true, [this] { addBreakpoint(); }); - - addAction(menu, tr("Delete Selected Breakpoints"), - !selectedBreakpoints.isEmpty(), - [this, selectedBreakpoints] { deleteBreakpoints(selectedBreakpoints); }); - - addAction(menu, tr("Edit Selected Breakpoints..."), - !selectedBreakpoints.isEmpty(), - [this, selectedBreakpoints, ev] { editBreakpoints(selectedBreakpoints, ev.view()); }); - - - // FIXME BP: m_engine->threadsHandler()->currentThreadId(); - // int threadId = 0; - // addAction(menu, - // threadId == -1 ? tr("Associate Breakpoint with All Threads") - // : tr("Associate Breakpoint with Thread %1").arg(threadId), - // !selectedItems.isEmpty(), - // [this, selectedItems, threadId] { - // for (Breakpoint bp : selectedItems) - // bp.setThreadSpec(threadId); - // }); - - addAction(menu, - selectedBreakpoints.size() > 1 - ? breakpointsEnabled ? tr("Disable Selected Breakpoints") : tr("Enable Selected Breakpoints") - : breakpointsEnabled ? tr("Disable Breakpoint") : tr("Enable Breakpoint"), - !selectedBreakpoints.isEmpty(), - [this, selectedBreakpoints, breakpointsEnabled] { - setBreakpointsEnabled(selectedBreakpoints, !breakpointsEnabled); - } - ); - - addAction(menu, - selectedLocations.size() > 1 - ? locationsEnabled ? tr("Disable Selected Locations") : tr("Enable Selected Locations") - : locationsEnabled ? tr("Disable Location") : tr("Enable Location"), - !selectedLocations.isEmpty() && handlesIndividualLocations, - [selectedLocations, locationsEnabled] { - for (LocationItem *location : selectedLocations) { - location->params.enabled = !locationsEnabled; - location->update(); - BreakpointItem *bp = location->parent(); - if (bp->m_engine) - bp->m_engine->enableSubBreakpoint(location->params.id.toString(), !locationsEnabled); - } - } - ); - - menu->addSeparator(); - - addAction(menu, tr("Delete All Breakpoints"), - rowCount() > 0, - [this] { deleteAllBreakpoints(); }); - - // Delete by file: Find indices of breakpoints of the same file. - BreakpointItem *item = itemForIndexAtLevel<1>(ev.index()); - Breakpoints breakpointsInFile; - QString file; - if (item) { - const QModelIndex index = ev.index().sibling(ev.index().row(), BreakpointFileColumn); - if (!file.isEmpty()) { - for (int i = 0; i != rowCount(); ++i) - if (index.data().toString() == file) - breakpointsInFile.append(findBreakpointByIndex(index)); - } - } - addAction(menu, tr("Delete Breakpoints of \"%1\"").arg(file), - tr("Delete Breakpoints of File"), - breakpointsInFile.size() > 1, - [this, breakpointsInFile] { deleteBreakpoints(breakpointsInFile); }); - - menu->addSeparator(); - - addAction(menu, tr("Synchronize Breakpoints"), - Internal::hasSnapshots(), - [] { Internal::synchronizeBreakpoints(); }); - - menu->addSeparator(); - menu->addAction(action(UseToolTipsInBreakpointsView)); - if (currentEngine()->hasCapability(MemoryAddressCapability)) - menu->addAction(action(UseAddressInBreakpointsView)); - menu->addSeparator(); - menu->addAction(action(SettingsDialog)); - - menu->popup(ev.globalPos()); - - return true; -} - -void BreakHandler::setBreakpointsEnabled(const Breakpoints &bps, bool enabled) -{ - for (Breakpoint b : bps) - b.setEnabled(enabled); -} - -void BreakHandler::deleteAllBreakpoints() -{ - QDialogButtonBox::StandardButton pressed = - CheckableMessageBox::doNotAskAgainQuestion(ICore::dialogParent(), - tr("Remove All Breakpoints"), - tr("Are you sure you want to remove all breakpoints " - "from all files in the current session?"), - ICore::settings(), - "RemoveAllBreakpoints"); - if (pressed == QDialogButtonBox::Yes) - deleteBreakpoints(breakHandler()->allBreakpoints()); -} - -void BreakHandler::deleteBreakpoints(const Breakpoints &bps) -{ - for (Breakpoint bp : bps) - bp.removeBreakpoint(); -} - -void BreakHandler::editBreakpoint(Breakpoint bp, QWidget *parent) -{ - BreakpointParameters data = bp.parameters(); - BreakpointParts parts = NoParts; - - BreakpointDialog dialog(bp, parent); - if (!dialog.showDialog(&data, &parts)) - return; - - bp.changeBreakpointData(data); -} - -void BreakHandler::addBreakpoint() -{ - BreakpointParameters data(BreakpointByFileAndLine); - BreakpointParts parts = NoParts; - BreakpointDialog dialog(Breakpoint(), ICore::dialogParent()); - dialog.setWindowTitle(tr("Add Breakpoint")); - if (dialog.showDialog(&data, &parts)) - appendBreakpoint(data); -} - -void BreakHandler::editBreakpoints(const Breakpoints &bps, QWidget *parent) -{ - QTC_ASSERT(!bps.isEmpty(), return); - - const Breakpoint bp = bps.at(0); - - if (bps.size() == 1) { - editBreakpoint(bp, parent); - return; - } - - // This allows to change properties of multiple breakpoints at a time. - if (!bp) - return; - - MultiBreakPointsDialog dialog; - dialog.setCondition(bp.condition()); - dialog.setIgnoreCount(bp.ignoreCount()); - dialog.setThreadSpec(bp.threadSpec()); - - if (dialog.exec() == QDialog::Rejected) - return; - - const QString newCondition = dialog.condition(); - const int newIgnoreCount = dialog.ignoreCount(); - const int newThreadSpec = dialog.threadSpec(); - - for (Breakpoint bp : bps) { - if (bp) { - bp.setCondition(newCondition); - bp.setIgnoreCount(newIgnoreCount); - bp.setThreadSpec(newThreadSpec); - } - } -} - -////////////////////////////////////////////////////////////////// -// -// Storage -// -////////////////////////////////////////////////////////////////// - -// Ok to be not thread-safe. The order does not matter and only the gui -// produces authoritative ids. -static int currentId = 0; - -BreakpointItem::BreakpointItem(BreakHandler *handler) - : m_handler(handler), m_id(++currentId), m_state(BreakpointNew), m_engine(nullptr), m_marker(nullptr) -{} - -BreakpointItem::~BreakpointItem() -{ - delete m_marker; -} - -void BreakpointItem::destroyMarker() -{ - if (m_marker) { - BreakpointMarker *m = m_marker; - m->m_bp = nullptr; - m_marker = nullptr; - delete m; - } -} - -QString BreakpointItem::markerFileName() const -{ - // Some heuristics to find a "good" file name. - if (!m_params.fileName.isEmpty()) { - QFileInfo fi(m_params.fileName); - if (fi.exists()) - return fi.absoluteFilePath(); - } - if (!m_response.fileName.isEmpty()) { - QFileInfo fi(m_response.fileName); - if (fi.exists()) - return fi.absoluteFilePath(); - } - if (m_response.fileName.endsWith(m_params.fileName)) - return m_response.fileName; - if (m_params.fileName.endsWith(m_response.fileName)) - return m_params.fileName; - return m_response.fileName.size() > m_params.fileName.size() - ? m_response.fileName : m_params.fileName; -} - -int BreakpointItem::markerLineNumber() const -{ - return m_response.lineNumber ? m_response.lineNumber : m_params.lineNumber; -} - -static void formatAddress(QTextStream &str, quint64 address) -{ - if (address) { - str << "0x"; - str.setIntegerBase(16); - str << address; - str.setIntegerBase(10); - } -} - -bool BreakpointItem::needsChange() const -{ - if (!m_params.conditionsMatch(m_response.condition)) - return true; - if (m_params.ignoreCount != m_response.ignoreCount) - return true; - if (m_params.enabled != m_response.enabled) - return true; - if (m_params.threadSpec != m_response.threadSpec) - return true; - if (m_params.command != m_response.command) - return true; - if (m_params.type == BreakpointByFileAndLine && m_params.lineNumber != m_response.lineNumber) - return true; - // FIXME: Too strict, functions may have parameter lists, or not. - // if (m_params.type == BreakpointByFunction && m_params.functionName != m_response.functionName) - // return true; - // if (m_params.type == BreakpointByAddress && m_params.address != m_response.address) - // return true; - return false; -} - -bool BreakpointItem::isLocatedAt - (const QString &fileName, int lineNumber, bool useMarkerPosition) const -{ - int line = useMarkerPosition ? m_response.lineNumber : m_params.lineNumber; - return lineNumber == line - && (fileNameMatch(fileName, m_response.fileName) - || fileNameMatch(fileName, markerFileName())); -} - -void BreakpointItem::updateMarkerIcon() -{ - if (m_marker) { - m_marker->setIcon(icon()); - m_marker->updateMarker(); - } -} - -void BreakpointItem::updateMarker() -{ - FileName file = FileName::fromString(markerFileName()); - int line = markerLineNumber(); - if (m_marker && (file != m_marker->fileName() || line != m_marker->lineNumber())) - destroyMarker(); - - if (!m_marker && !file.isEmpty() && line > 0) - m_marker = new BreakpointMarker(this, file, line); - - if (m_marker) { - QString toolTip; - auto addToToolTipText = [&toolTip](const QString &info, const QString &label) { - if (info.isEmpty()) - return; - if (!toolTip.isEmpty()) - toolTip += ' '; - toolTip += label + ": '" + info + '\''; - }; - addToToolTipText(m_params.condition, tr("Breakpoint Condition")); - addToToolTipText(m_params.command, tr("Debugger Command")); - m_marker->setToolTip(toolTip); - } -} - -QIcon BreakpointItem::icon() const -{ - // FIXME: This seems to be called on each cursor blink as soon as the - // cursor is near a line with a breakpoint marker (+/- 2 lines or so). - if (m_params.isTracepoint()) - return Icons::TRACEPOINT.icon(); - if (m_params.type == WatchpointAtAddress) - return Icons::WATCHPOINT.icon(); - if (m_params.type == WatchpointAtExpression) - return Icons::WATCHPOINT.icon(); - if (!m_params.enabled) - return Icons::BREAKPOINT_DISABLED.icon(); - if (m_state == BreakpointInserted && !m_response.pending) - return Icons::BREAKPOINT.icon(); - return Icons::BREAKPOINT_PENDING.icon(); -} - -QString BreakpointItem::toToolTip() const -{ - QString rc; - QTextStream str(&rc); - str << "" - //<< "" - << ""; - if (m_engine) { - str << ""; - } - if (!m_response.pending) { - str << ""; - } - str << "" - << "" - << "" - << "" - << "
" << tr("ID:") << "" << m_id << "
" << tr("State:") - << "" << (m_params.enabled ? tr("Enabled") : tr("Disabled")); - if (m_response.pending) - str << tr(", pending"); - str << ", " << m_state << " (" << stateToString(m_state) << ")
" << tr("Engine:") - << "" << m_engine->objectName() << "
" << tr("Breakpoint Number:") - << "" << m_response.id.toString() << "
" << tr("Breakpoint Type:") - << "" << typeToString(m_params.type) << "
" << tr("Marker File:") - << "" << QDir::toNativeSeparators(markerFileName()) << "
" << tr("Marker Line:") - << "" << markerLineNumber() << "
" << tr("Hit Count:") - << "" << m_response.hitCount << "


" - << "" - << ""; - if (m_params.type == BreakpointByFunction) { - str << ""; - } - if (m_params.type == BreakpointByFileAndLine) { - str << "" - << "" - << ""; - } - if (m_params.type == BreakpointByFunction || m_params.type == BreakpointByFileAndLine) { - str << ""; - } - str << ""; - if (m_response.multiple) { - str << ""; - } - if (!m_params.command.isEmpty() || !m_response.command.isEmpty()) { - str << ""; - } - if (!m_params.message.isEmpty() || !m_response.message.isEmpty()) { - str << ""; - } - if (!m_params.condition.isEmpty() || !m_response.condition.isEmpty()) { - str << ""; - } - if (m_params.ignoreCount || m_response.ignoreCount) { - str << ""; - } - if (m_params.threadSpec >= 0 || m_response.threadSpec >= 0) { - str << ""; - } - str << "
" << tr("Property") - << "" << tr("Requested") - << "" << tr("Obtained") << "
" << tr("Internal Number:") - << "" << m_response.id.toString() << "
" << tr("Function Name:") - << "" << m_params.functionName - << "" << m_response.functionName - << "
" << tr("File Name:") - << "" << QDir::toNativeSeparators(m_params.fileName) - << "" << QDir::toNativeSeparators(m_response.fileName) - << "
" << tr("Line Number:") - << "" << m_params.lineNumber - << "" << m_response.lineNumber << "
" << tr("Corrected Line Number:") - << "-" - << "" << m_response.correctedLineNumber << "
" << tr("Module:") - << "" << m_params.module - << "" << m_response.module - << "
" << tr("Breakpoint Address:") - << ""; - formatAddress(str, m_params.address); - str << ""; - formatAddress(str, m_response.address); - str << "
" << tr("Multiple Addresses:") - << "" - << "
" << tr("Command:") - << "" << m_params.command - << "" << m_response.command - << "
" << tr("Message:") - << "" << m_params.message - << "" << m_response.message - << "
" << tr("Condition:") - << "" << m_params.condition - << "" << m_response.condition - << "
" << tr("Ignore Count:") << ""; - if (m_params.ignoreCount) - str << m_params.ignoreCount; - str << ""; - if (m_response.ignoreCount) - str << m_response.ignoreCount; - str << "
" << tr("Thread Specification:") - << ""; - if (m_params.threadSpec >= 0) - str << m_params.threadSpec; - str << ""; - if (m_response.threadSpec >= 0) - str << m_response.threadSpec; - str << "
"; - return rc; -} - -void BreakHandler::setWatchpointAtAddress(quint64 address, unsigned size) -{ - BreakpointParameters params(WatchpointAtAddress); - params.address = address; - params.size = size; - if (findWatchpoint(params)) { - qDebug() << "WATCHPOINT EXISTS"; - // removeBreakpoint(index); - return; - } - appendBreakpoint(params); -} - -void BreakHandler::setWatchpointAtExpression(const QString &exp) -{ - BreakpointParameters params(WatchpointAtExpression); - params.expression = exp; - if (findWatchpoint(params)) { - qDebug() << "WATCHPOINT EXISTS"; - // removeBreakpoint(index); - return; - } - appendBreakpoint(params); -} - -bool Breakpoint::isValid() const -{ - return b && b->m_id.isValid(); -} - -uint Breakpoint::hash() const -{ - return b ? 0 : qHash(b->m_id); -} - -BreakpointModelId Breakpoint::id() const -{ - return b ? b->m_id : BreakpointModelId(); -} - -QString Breakpoint::msgWatchpointByExpressionTriggered(const int number, const QString &expr) const -{ - return id() - ? tr("Data breakpoint %1 (%2) at %3 triggered.") - .arg(id().toString()).arg(number).arg(expr) - : tr("Internal data breakpoint %1 at %2 triggered.") - .arg(number).arg(expr); -} - -QString Breakpoint::msgWatchpointByExpressionTriggered(const int number, const QString &expr, - const QString &threadId) const -{ - return id() - ? tr("Data breakpoint %1 (%2) at %3 in thread %4 triggered.") - .arg(id().toString()).arg(number).arg(expr).arg(threadId) - : tr("Internal data breakpoint %1 at %2 in thread %3 triggered.") - .arg(number).arg(expr).arg(threadId); -} - -QString Breakpoint::msgWatchpointByAddressTriggered(const int number, quint64 address) const -{ - return id() - ? tr("Data breakpoint %1 (%2) at 0x%3 triggered.") - .arg(id().toString()).arg(number).arg(address, 0, 16) - : tr("Internal data breakpoint %1 at 0x%2 triggered.") - .arg(number).arg(address, 0, 16); -} - -QString Breakpoint::msgWatchpointByAddressTriggered( - const int number, quint64 address, const QString &threadId) const -{ - return id() - ? tr("Data breakpoint %1 (%2) at 0x%3 in thread %4 triggered.") - .arg(id().toString()).arg(number).arg(address, 0, 16).arg(threadId) - : tr("Internal data breakpoint %1 at 0x%2 in thread %3 triggered.") - .arg(id().toString()).arg(number).arg(address, 0, 16).arg(threadId); -} - -QString Breakpoint::msgBreakpointTriggered(const int number, const QString &threadId) const -{ - return id() - ? tr("Stopped at breakpoint %1 (%2) in thread %3.") - .arg(id().toString()).arg(number).arg(threadId) - : tr("Stopped at internal breakpoint %1 in thread %2.") - .arg(number).arg(threadId); -} - } // namespace Internal } // namespace Debugger diff --git a/src/plugins/debugger/breakhandler.h b/src/plugins/debugger/breakhandler.h index 09461ba0816..91a47649b17 100644 --- a/src/plugins/debugger/breakhandler.h +++ b/src/plugins/debugger/breakhandler.h @@ -28,6 +28,7 @@ #include "breakpoint.h" #include "debuggerprotocol.h" +#include #include #include @@ -38,203 +39,295 @@ namespace Utils { class ItemViewEvent; } namespace Debugger { namespace Internal { -class LocationItem; class BreakpointItem; +class BreakpointMarker; class BreakHandler; class DebuggerCommand; class DebuggerEngine; +class BreakpointManager; +class GlobalBreakpointMarker; -// Non-owning "deletion-safe" wrapper around a BreakpointItem * -class Breakpoint +class SubBreakpointItem : public QObject, public Utils::TypedTreeItem +{ +public: + QVariant data(int column, int role) const final; + + BreakpointItem *breakpoint() const { return Utils::TypedTreeItem::parent(); } + void setParameters(const BreakpointParameters &pars) { params = pars; } + BreakpointParameters params; + QString responseId; //!< Breakpoint number assigned by the debugger engine. + QString displayName; //!< Breakpoint number assigned by the debugger engine. +}; + +using SubBreakpoint = QPointer; + +class GlobalBreakpointItem : public QObject, public Utils::TreeItem { Q_DECLARE_TR_FUNCTIONS(Debugger::Internal::BreakHandler) public: - Breakpoint() = default; - - bool isValid() const; - operator const void *() const { return isValid() ? this : nullptr; } - bool operator!() const { return !isValid(); } - - uint hash() const; - - const BreakpointParameters ¶meters() const; - void addToCommand(DebuggerCommand *cmd) const; - - BreakpointModelId id() const; - bool isLocatedAt(const QString &fileName, int lineNumber, - bool useMarkerPosition) const; + explicit GlobalBreakpointItem(); + ~GlobalBreakpointItem() override; + QVariant data(int column, int role) const override; QIcon icon() const; - BreakpointState state() const; - void setEngine(DebuggerEngine *engine); - // State transitions. - void notifyBreakpointChangeAfterInsertNeeded(); - void notifyBreakpointInsertProceeding(); - void notifyBreakpointInsertOk(); - void notifyBreakpointInsertFailed(); - void notifyBreakpointChangeOk(); - void notifyBreakpointChangeProceeding(); - void notifyBreakpointChangeFailed(); - void notifyBreakpointPending(); - void notifyBreakpointRemoveProceeding(); - void notifyBreakpointRemoveOk(); - void notifyBreakpointRemoveFailed(); - void notifyBreakpointReleased(); - void notifyBreakpointNeedsReinsertion(); - void notifyBreakpointAdjusted(const BreakpointParameters ¶ms); + void deleteBreakpoint(); + void removeBreakpointFromModel(); - void update(); + void updateLineNumber(int lineNumber); + void updateFileName(const Utils::FileName &fileName); - void gotoLocation() const; + bool isLocatedAt(const QString &fileName, int lineNumber, bool useMarkerPosition) const; - // Getter retrieves property value. - // Setter sets property value and triggers update if changed. - // Only use setters when it is safe to assume that the breakpoint still - // exist. That's not the case if the event loop could run after you - // obtained the BreakpointItem pointer. - BreakpointPathUsage pathUsage() const; - void setPathUsage(const BreakpointPathUsage &u); - QString condition() const; - void setCondition(const QString &condition); - int ignoreCount() const; - void setIgnoreCount(const int &count); - int threadSpec() const; - void setThreadSpec(const int &spec); - QString fileName() const; - void setFileName(const QString &fileName); - QString functionName() const; - void setFunctionName(const QString &functionName); - QString expression() const; - void setExpression(const QString &expression); - QString message() const; - QString command() const; - void setCommand(const QString &command); - void setMessage(const QString &m); - BreakpointType type() const; - void setType(const BreakpointType &type); - quint64 address() const; - void setAddress(const quint64 &address); - int lineNumber() const; - void changeBreakpointData(const BreakpointParameters &data); - bool isEnabled() const; - void setEnabled(bool on) const; - void updateFileNameFromMarker(const QString &fileName); - void updateLineNumberFromMarker(int lineNumber); - void changeLineNumberFromMarker(int lineNumber); - void setMarkerFileAndLine(const QString &fileName, int lineNumber); - bool isWatchpoint() const; - bool isTracepoint() const; - void setTracepoint(bool on); - DebuggerEngine *engine() const; - const BreakpointResponse &response() const; - void setResponse(const BreakpointResponse &data); - bool needsChange() const; - bool needsChildren() const; + QString displayName() const; + QString markerFileName() const; + QString toolTip() const; + int markerLineNumber() const; + int modelId() const; - bool isOneShot() const; - void insertSubBreakpoint(const BreakpointResponse &data); - void removeAlienBreakpoint(); - void removeBreakpoint() const; + bool isEnabled() const { return m_params.enabled; } + void setEnabled(bool enabled); - QString msgWatchpointByAddressTriggered(int number, quint64 address) const; - QString msgWatchpointByAddressTriggered( - int number, quint64 address, const QString &threadId) const; - QString msgWatchpointByExpressionTriggered(int number, const QString &expr) const; - QString msgWatchpointByExpressionTriggered( - int number, const QString &expr, const QString &threadId) const; - QString msgBreakpointTriggered(int number, const QString &threadId) const; + const BreakpointParameters ¶meters() const { return m_params; } private: - void gotoState(BreakpointState target, BreakpointState assumedCurrent); - friend class BreakHandler; - explicit Breakpoint(BreakpointItem *b); + friend class BreakpointManager; + friend class BreakpointMarker; + friend class GlobalBreakpointMarker; - QPointer b; + void updateMarker(); + void updateMarkerIcon(); + void destroyMarker(); + void scheduleSynchronization(); + QPointer usingEngine() const; + + bool isEngineRunning() const; + + const int m_modelId; + BreakpointParameters m_params; + GlobalBreakpointMarker *m_marker = nullptr; // The primary marker set by the user. }; -inline uint qHash(const Debugger::Internal::Breakpoint &b) { return b.hash(); } +using GlobalBreakpoint = QPointer; +using GlobalBreakpoints = QList; -using Breakpoints = QList; +class BreakpointItem : public QObject, public Utils::TypedTreeItem +{ + Q_DECLARE_TR_FUNCTIONS(Debugger::Internal::BreakHandler) -using BreakModel = Utils::TreeModel, BreakpointItem, LocationItem>; +public: + explicit BreakpointItem(const GlobalBreakpoint &gbp); + ~BreakpointItem() final; -class BreakHandler : public BreakModel + QVariant data(int column, int role) const final; + + QIcon icon() const; + + void setMarkerFileAndLine(const QString &fileName, int lineNumber); + bool needsChange() const; + + SubBreakpoint findOrCreateSubBreakpoint(const QString &responseId); + QString markerFileName() const; + int markerLineNumber() const; + + const BreakpointParameters &requestedParameters() const; + void addToCommand(DebuggerCommand *cmd) const; + void updateFromGdbOutput(const GdbMi &bkpt); + + int modelId() const; + QString responseId() const { return m_responseId; } + QString displayName() const { return m_displayName; } + QString toolTip() const; + QString shortToolTip() const; + BreakpointState state() const { return m_state; } + BreakpointType type() const { return m_parameters.type; } + BreakpointPathUsage pathUsage() const; + const BreakpointParameters parameters() const { return m_parameters; } + + QString condition() const { return m_parameters.condition; } + int ignoreCount() const { return m_parameters.ignoreCount; } + int threadSpec() const { return m_parameters.threadSpec; } + QString fileName() const { return m_parameters.fileName; } + QString functionName() const { return m_parameters.functionName; } + QString expression() const { return m_parameters.expression; } + QString message() const { return m_parameters.message; } + QString command() const { return m_parameters.command; } + quint64 address() const { return m_parameters.address; } + int lineNumber() const { return m_parameters.lineNumber; } + bool isEnabled() const { return m_parameters.enabled; } + bool isWatchpoint() const { return m_parameters.isWatchpoint(); } + bool isTracepoint() const { return m_parameters.isTracepoint(); } + bool isOneShot() const { return m_parameters.oneShot; } + bool isPending() const { return m_parameters.pending; } + + void setLineNumber(int lineNumber) { m_parameters.lineNumber = lineNumber; } + void setFileName(const QString &fileName) { m_parameters.fileName = fileName; } + void setFunctionName(const QString &functionName) { m_parameters.functionName = functionName; } + void setPending(bool pending); + void setResponseId(const QString &str) { m_responseId = str; } + void setDisplayName(const QString &name) { m_displayName = name; } + void setParameters(const BreakpointParameters &value); + void setAddress(quint64 address) { m_parameters.address = address; } + void setEnabled(bool on); + void setHitCount(int hitCount) { m_parameters.hitCount = hitCount; } + void setThreadSpec(int threadSpec) { m_parameters.threadSpec = threadSpec; } + void setIgnoreCount(int count) { m_parameters.ignoreCount = count; } + void setCommand(const QString &command) { m_parameters.command = command; } + void setCondition(const QString &condition) { m_parameters.condition = condition; } + + QString msgWatchpointByAddressTriggered(quint64 address) const; + QString msgWatchpointByAddressTriggered(quint64 address, const QString &threadId) const; + QString msgWatchpointByExpressionTriggered(const QString &expr) const; + QString msgWatchpointByExpressionTriggered(const QString &expr, const QString &threadId) const; + QString msgBreakpointTriggered(const QString &threadId) const; + + friend class BreakpointManager; + friend class BreakHandler; + friend class DebuggerEngine; + + void adjustMarker(); + + void deleteBreakpoint(); + void deleteGlobalOrThisBreakpoint(); + + void updateLineNumber(int lineNumber); + void updateFileName(const Utils::FileName &fileName); + + const GlobalBreakpoint globalBreakpoint() const; + void gotoState(BreakpointState target, BreakpointState assumedCurrent); + +private: + void destroyMarker(); + void updateMarker(); + void updateMarkerIcon(); + void setState(BreakpointState state); + + const GlobalBreakpoint m_globalBreakpoint; // Origin, or null for aliens. + BreakpointParameters m_requestedParameters; // May differ from global value over lifetime of breakpoint. + BreakpointParameters m_parameters; + BreakpointState m_state = BreakpointNew; // Current state of breakpoint. + BreakpointMarker *m_marker = nullptr; + QString m_responseId; //!< Breakpoint number or id assigne by or used in the debugger backend. + QString m_displayName; +}; + +using Breakpoint = QPointer; +using Breakpoints = const QList; +using SubBreakpoints = const QList; + +using BreakHandlerModel = Utils::TreeModel, BreakpointItem, SubBreakpointItem>; +using BreakpointManagerModel = Utils::TreeModel, GlobalBreakpointItem>; + +inline uint qHash(const Debugger::Internal::SubBreakpoint &b) { return qHash(b.data()); } +inline uint qHash(const Debugger::Internal::Breakpoint &b) { return qHash(b.data()); } +inline uint qHash(const Debugger::Internal::GlobalBreakpoint &b) { return qHash(b.data()); } + +class BreakHandler : public BreakHandlerModel { Q_OBJECT public: - BreakHandler(); + explicit BreakHandler(DebuggerEngine *engine); + + QAbstractItemModel *model() { return this; } + const Breakpoints breakpoints() const; void loadSessionData(); void saveSessionData(); - QAbstractItemModel *model() { return this; } + bool tryClaimBreakpoint(const GlobalBreakpoint &gbp); + void releaseAllBreakpoints(); - // The only way to add a new breakpoint. - void appendBreakpoint(const BreakpointParameters &data); - void handleAlienBreakpoint(const BreakpointResponse &response, DebuggerEngine *engine); + void handleAlienBreakpoint(const QString &responseId, const BreakpointParameters &response); + void removeAlienBreakpoint(const QString &responseId); + void requestBreakpointInsertion(const Breakpoint &bp); + void requestBreakpointUpdate(const Breakpoint &bp); + void requestBreakpointRemoval(const Breakpoint &bp); + void requestBreakpointEnabling(const Breakpoint &bp, bool enabled); + void requestSubBreakpointEnabling(const SubBreakpoint &sbp, bool enabled); - const Breakpoints allBreakpoints() const; - const Breakpoints engineBreakpoints(DebuggerEngine *engine) const; - const Breakpoints unclaimedBreakpoints() const; - QStringList engineBreakpointPaths(DebuggerEngine *engine) const; + void removeBreakpoint(const Breakpoint &bp); + void editBreakpoint(const Breakpoint &bp, QWidget *parent); - // Find a breakpoint matching approximately the data in needle. - Breakpoint findSimilarBreakpoint(const BreakpointResponse &needle) const; - Breakpoint findBreakpointByResponseId(const BreakpointResponseId &resultId) const; + Breakpoint findBreakpointByResponseId(const QString &responseId) const; + SubBreakpoint findSubBreakpointByResponseId(const QString &responseId) const; Breakpoint findWatchpoint(const BreakpointParameters &data) const; - Breakpoint findBreakpointByFunction(const QString &functionName) const; - Breakpoint findBreakpointByIndex(const QModelIndex &index) const; - Breakpoints findBreakpointsByIndex(const QList &list) const; - void updateMarkers(); + Breakpoint findBreakpointByModelId(int modelId) const; - Breakpoint findBreakpointByFileAndLine(const QString &fileName, - int lineNumber, bool useMarkerPosition = true); - Breakpoint findBreakpointByAddress(quint64 address) const; - - void breakByFunction(const QString &functionName); static QString displayFromThreadSpec(int spec); static int threadSpecFromDisplay(const QString &str); // Convenience. void setWatchpointAtAddress(quint64 address, unsigned size); void setWatchpointAtExpression(const QString &exp); + void setBreakpointEnabled(const Breakpoint &bp, bool on); - Breakpoint breakpointById(BreakpointModelId id) const; - void editBreakpoint(Breakpoint bp, QWidget *parent); + void updateDisassemblerMarker(const Breakpoint &bp); + void removeDisassemblerMarker(const Breakpoint &bp); private: - QVariant data(const QModelIndex &idx, int role) const override; - bool setData(const QModelIndex &idx, const QVariant &value, int role) override; - void timerEvent(QTimerEvent *event) override; + Breakpoint findBreakpointByIndex(const QModelIndex &index) const; + Breakpoints findBreakpointsByIndex(const QList &list) const; + SubBreakpoint findSubBreakpointByIndex(const QModelIndex &index) const; + SubBreakpoints findSubBreakpointsByIndex(const QList &list) const; + void editBreakpoints(const Breakpoints &bps, QWidget *parent); + + void gotoState(Breakpoint bp, BreakpointState target, BreakpointState assumedCurrent); + void gotoLocation(const Breakpoint &bp) const; + + QVariant data(const QModelIndex &idx, int role) const final; + bool setData(const QModelIndex &idx, const QVariant &value, int role) final; bool contextMenuEvent(const Utils::ItemViewEvent &ev); friend class BreakpointItem; - friend class Breakpoint; - void loadBreakpoints(); - void saveBreakpoints(); + DebuggerEngine * const m_engine; +}; - void appendBreakpointInternal(const BreakpointParameters &data); - void deleteBreakpoints(const Breakpoints &bps); - void deleteAllBreakpoints(); - void setBreakpointsEnabled(const Breakpoints &bps, bool enabled); - void addBreakpoint(); - void editBreakpoints(const Breakpoints &bps, QWidget *parent); +class BreakpointManager : public BreakpointManagerModel +{ + Q_OBJECT - Q_SLOT void changeLineNumberFromMarkerHelper(Debugger::Internal::BreakpointModelId id); - Q_SLOT void deletionHelper(Debugger::Internal::BreakpointModelId id); +public: + BreakpointManager(); - void scheduleSynchronization(); + static QAbstractItemModel *model(); - int m_syncTimerId; + static const GlobalBreakpoints globalBreakpoints(); + static void loadSessionData(); + static void saveSessionData(); + static void aboutToUnloadSession(); + + static GlobalBreakpoint createBreakpoint(const BreakpointParameters &data); + + static GlobalBreakpoint findBreakpointByLocation(const ContextData &location); + // Find a breakpoint matching approximately the data in needle. + static GlobalBreakpoint findSimilarBreakpoint(const BreakpointParameters &needle); + static GlobalBreakpoint findWatchpoint(const BreakpointParameters &data); + static GlobalBreakpoint findBreakpointByFunction(const QString &functionName); + + static void claimBreakpointsForEngine(DebuggerEngine *engine); + static void toggleBreakpoint(const ContextData &location, const QString &tracePointMessage = QString()); + static void createBreakpointForEngine(const BreakpointParameters &data, DebuggerEngine *engine); + + static void executeAddBreakpointDialog(); + static void executeDeleteAllBreakpointsDialog(); + +private: + static GlobalBreakpoint createBreakpointHelper(const BreakpointParameters &data); + static GlobalBreakpoint findBreakpointByIndex(const QModelIndex &index); + static GlobalBreakpoints findBreakpointsByIndex(const QList &list); + + QVariant data(const QModelIndex &idx, int role) const final; + bool setData(const QModelIndex &idx, const QVariant &value, int role) final; + + bool contextMenuEvent(const Utils::ItemViewEvent &ev); + void gotoLocation(const GlobalBreakpoint &gbp) const; }; } // namespace Internal } // namespace Debugger -Q_DECLARE_METATYPE(Debugger::Internal::Breakpoint) diff --git a/src/plugins/debugger/breakpoint.cpp b/src/plugins/debugger/breakpoint.cpp index be1287d1d52..dc1e43559ff 100644 --- a/src/plugins/debugger/breakpoint.cpp +++ b/src/plugins/debugger/breakpoint.cpp @@ -25,89 +25,20 @@ #include "breakpoint.h" +#include "debuggeractions.h" +#include "debuggercore.h" +#include "debuggerprotocol.h" + +#include #include #include #include +#include namespace Debugger { namespace Internal { -/*! - \class Debugger::Internal::BreakpointIdBase - - Convenience base class for BreakpointModelId and - BreakpointResponseId. -*/ - -QDebug operator<<(QDebug d, const BreakpointIdBase &id) -{ - d << qPrintable(id.toString()); - return d; -} - -QString BreakpointIdBase::toString() const -{ - if (!isValid()) - return QLatin1String(""); - if (isMinor()) - return QString("%1.%2").arg(m_majorPart).arg(m_minorPart); - return QString::number(m_majorPart); -} - - -/*! - \class Debugger::Internal::BreakpointModelId - - This identifies a breakpoint in the \c BreakHandler. The - major parts are strictly increasing over time. - - The minor part identifies a multiple breakpoint - set for example by gdb in constructors. -*/ - - -BreakpointModelId::BreakpointModelId(const QString &ba) -{ - int pos = ba.indexOf('\''); - if (pos == -1) { - m_majorPart = ba.toUShort(); - m_minorPart = 0; - } else { - m_majorPart = ba.left(pos).toUShort(); - m_minorPart = ba.mid(pos + 1).toUShort(); - } -} - -/*! - \class Debugger::Internal::BreakpointResponseId - - This is what the external debuggers use to identify a breakpoint. - It is only valid for one debugger run. - - In gdb, the breakpoint number is used, which is constant - during a session. CDB's breakpoint numbers vary if breakpoints - are deleted, so, the ID is used. -*/ - -BreakpointResponseId::BreakpointResponseId(const QString &ba) -{ - int pos = ba.indexOf('.'); - if (pos == -1) { - m_majorPart = ba.toInt(); - m_minorPart = 0; - } else { - m_majorPart = ba.left(pos).toInt(); - m_minorPart = ba.mid(pos + 1).toInt(); - } -} - -////////////////////////////////////////////////////////////////// -// -// BreakpointParameters -// -////////////////////////////////////////////////////////////////// - /*! \class Debugger::Internal::BreakpointParameters @@ -215,25 +146,32 @@ void BreakpointParameters::updateLocation(const QString &location) } } +bool BreakpointParameters::isQmlFileAndLineBreakpoint() const +{ + if (type != BreakpointByFileAndLine) + return false; + + QString qmlExtensionString = QString::fromLocal8Bit(qgetenv("QTC_QMLDEBUGGER_FILEEXTENSIONS")); + if (qmlExtensionString.isEmpty()) + qmlExtensionString = ".qml;.js"; + + const auto qmlFileExtensions = qmlExtensionString.splitRef(';', QString::SkipEmptyParts); + for (QStringRef extension : qmlFileExtensions) { + if (fileName.endsWith(extension, Qt::CaseInsensitive)) + return true; + } + return false; +} + bool BreakpointParameters::isCppBreakpoint() const { // Qml specific breakpoint types. - if (type == BreakpointAtJavaScriptThrow - || type == BreakpointOnQmlSignalEmit) + if (type == BreakpointAtJavaScriptThrow || type == BreakpointOnQmlSignalEmit) return false; // Qml is currently only file. - if (type == BreakpointByFileAndLine) { - auto qmlExtensionString = QString::fromLocal8Bit(qgetenv("QTC_QMLDEBUGGER_FILEEXTENSIONS")); - if (qmlExtensionString.isEmpty()) - qmlExtensionString = ".qml;.js"; - - const auto qmlFileExtensions = qmlExtensionString.splitRef(';', QString::SkipEmptyParts); - for (QStringRef extension : qmlFileExtensions) { - if (fileName.endsWith(extension, Qt::CaseInsensitive)) - return false; - } - } + if (type == BreakpointByFileAndLine) + return !isQmlFileAndLineBreakpoint(); return true; } @@ -283,55 +221,176 @@ QString BreakpointParameters::toString() const ts << " Command: " << command; if (!message.isEmpty()) ts << " Message: " << message; - return result; -} -////////////////////////////////////////////////////////////////// -// -// BreakpointResponse -// -////////////////////////////////////////////////////////////////// - -/*! - \class Debugger::Internal::BreakpointResponse - - This is what debuggers produce in response to the attempt to - insert a breakpoint. The data might differ from the requested bits. -*/ - -BreakpointResponse::BreakpointResponse() -{ - pending = true; - hitCount = 0; - multiple = false; - correctedLineNumber = 0; -} - -QString BreakpointResponse::toString() const -{ - QString result = BreakpointParameters::toString(); - QTextStream ts(&result); - ts << " Number: " << id.toString(); if (pending) ts << " [pending]"; if (!functionName.isEmpty()) ts << " Function: " << functionName; - if (multiple) - ts << " Multiple: " << multiple; - if (correctedLineNumber) - ts << " CorrectedLineNumber: " << correctedLineNumber; ts << " Hit: " << hitCount << " times"; ts << ' '; - return result + BreakpointParameters::toString(); + + return result; } -void BreakpointResponse::fromParameters(const BreakpointParameters &p) +static QString cleanupFullName(const QString &fileName) { - BreakpointParameters::operator=(p); - id = BreakpointResponseId(); - multiple = false; - correctedLineNumber = 0; - hitCount = 0; + QString cleanFilePath = fileName; + + // Gdb running on windows often delivers "fullnames" which + // (a) have no drive letter and (b) are not normalized. + if (ProjectExplorer::Abi::hostAbi().os() == ProjectExplorer::Abi::WindowsOS) { + if (fileName.isEmpty()) + return QString(); + QFileInfo fi(fileName); + if (fi.isReadable()) + cleanFilePath = QDir::cleanPath(fi.absoluteFilePath()); + } + +// if (!boolSetting(AutoEnrichParameters)) +// return cleanFilePath; + +// const QString sysroot = runParameters().sysRoot; +// if (QFileInfo(cleanFilePath).isReadable()) +// return cleanFilePath; +// if (!sysroot.isEmpty() && fileName.startsWith('/')) { +// cleanFilePath = sysroot + fileName; +// if (QFileInfo(cleanFilePath).isReadable()) +// return cleanFilePath; +// } +// if (m_baseNameToFullName.isEmpty()) { +// QString debugSource = sysroot + "/usr/src/debug"; +// if (QFileInfo(debugSource).isDir()) { +// QDirIterator it(debugSource, QDirIterator::Subdirectories); +// while (it.hasNext()) { +// it.next(); +// QString name = it.fileName(); +// if (!name.startsWith('.')) { +// QString path = it.filePath(); +// m_baseNameToFullName.insert(name, path); +// } +// } +// } +// } + +// cleanFilePath.clear(); +// const QString base = FileName::fromString(fileName).fileName(); + +// QMap::const_iterator jt = m_baseNameToFullName.constFind(base); +// while (jt != m_baseNameToFullName.constEnd() && jt.key() == base) { +// // FIXME: Use some heuristics to find the "best" match. +// return jt.value(); +// //++jt; +// } + + return cleanFilePath; +} +void BreakpointParameters::updateFromGdbOutput(const GdbMi &bkpt) +{ + QTC_ASSERT(bkpt.isValid(), return); + + QString originalLocation; + QString file; + QString fullName; + QString internalId; + + enabled = true; + pending = false; + condition.clear(); + for (const GdbMi &child : bkpt.children()) { + if (child.hasName("number")) { + // Handled on caller side. + } else if (child.hasName("func")) { + functionName = child.data(); + } else if (child.hasName("addr")) { + // happens in constructors, inline functions, and + // at other places like 'foreach' lines. In this case there are + // fields named "addr" in the response and/or the address + // is called . + //qDebug() << "ADDR: " << child.data() << (child.data() == ""); + if (child.data().startsWith("0x")) + address = child.toAddress(); + } else if (child.hasName("file")) { + file = child.data(); + } else if (child.hasName("fullname")) { + fullName = child.data(); + } else if (child.hasName("line")) { + lineNumber = child.toInt(); + } else if (child.hasName("cond")) { + // gdb 6.3 likes to "rewrite" conditions. Just accept that fact. + condition = child.data(); + } else if (child.hasName("enabled")) { + enabled = (child.data() == "y"); + } else if (child.hasName("disp")) { + oneShot = child.data() == "del"; + } else if (child.hasName("pending")) { + // Any content here would be interesting only if we did accept + // spontaneously appearing breakpoints (user using gdb commands). + if (file.isEmpty()) + file = child.data(); + pending = true; + } else if (child.hasName("at")) { + // Happens with gdb 6.4 symbianelf. + QString ba = child.data(); + if (ba.startsWith('<') && ba.endsWith('>')) + ba = ba.mid(1, ba.size() - 2); + functionName = ba; + } else if (child.hasName("thread")) { + threadSpec = child.toInt(); + } else if (child.hasName("type")) { + // "breakpoint", "hw breakpoint", "tracepoint", "hw watchpoint" + // {bkpt={number="2",type="hw watchpoint",disp="keep",enabled="y", + // what="*0xbfffed48",times="0",original-location="*0xbfffed48"}} + if (child.data().contains("tracepoint")) { + tracepoint = true; + } else if (child.data() == "hw watchpoint" || child.data() == "watchpoint") { + QString what = bkpt["what"].data(); + if (what.startsWith("*0x")) { + type = WatchpointAtAddress; + address = what.mid(1).toULongLong(0, 0); + } else { + type = WatchpointAtExpression; + expression = what; + } + } else if (child.data() == "breakpoint") { + QString catchType = bkpt["catch-type"].data(); + if (catchType == "throw") + type = BreakpointAtThrow; + else if (catchType == "catch") + type = BreakpointAtCatch; + else if (catchType == "fork") + type = BreakpointAtFork; + else if (catchType == "exec") + type = BreakpointAtExec; + else if (catchType == "syscall") + type = BreakpointAtSysCall; + } + } else if (child.hasName("times")) { + hitCount = child.toInt(); + } else if (child.hasName("original-location")) { + originalLocation = child.data(); + } + // This field is not present. Contents needs to be parsed from + // the plain "ignore" + //else if (child.hasName("ignore")) + // ignoreCount = child.data(); + } + + QString name; + if (!fullName.isEmpty()) { + name = cleanupFullName(fullName); + fileName = name; + //if (data->markerFileName().isEmpty()) + // data->setMarkerFileName(name); + } else { + name = file; + // Use fullName() once we have a mapping which is more complete than + // gdb's own. No point in assigning markerFileName for now. + } + if (!name.isEmpty()) + fileName = name; + + if (fileName.isEmpty()) + updateLocation(originalLocation); } } // namespace Internal diff --git a/src/plugins/debugger/breakpoint.h b/src/plugins/debugger/breakpoint.h index 45c9a11205f..26e118b8bbb 100644 --- a/src/plugins/debugger/breakpoint.h +++ b/src/plugins/debugger/breakpoint.h @@ -31,52 +31,7 @@ namespace Debugger { namespace Internal { -////////////////////////////////////////////////////////////////// -// -// BreakpointIds -// -////////////////////////////////////////////////////////////////// - -class BreakpointIdBase -{ -public: - BreakpointIdBase() = default; - - bool isValid() const { return m_majorPart != 0; } - bool isMajor() const { return m_majorPart != 0 && m_minorPart == 0; } - bool isMinor() const { return m_majorPart != 0 && m_minorPart != 0; } - bool operator!() const { return !isValid(); } - operator const void*() const { return isValid() ? this : nullptr; } - quint32 toInternalId() const { return m_majorPart | (m_minorPart << 16); } - QString toString() const; - bool operator==(const BreakpointIdBase &id) const - { return m_majorPart == id.m_majorPart && m_minorPart == id.m_minorPart; } - quint16 majorPart() const { return m_majorPart; } - quint16 minorPart() const { return m_minorPart; } - -protected: - quint16 m_majorPart = 0; - quint16 m_minorPart = 0; -}; - -class BreakpointModelId : public BreakpointIdBase -{ -public: - BreakpointModelId() { m_majorPart = m_minorPart = 0; } - explicit BreakpointModelId(quint16 ma) { m_majorPart = ma; m_minorPart = 0; } - BreakpointModelId(quint16 ma, quint16 mi) { m_majorPart = ma; m_minorPart = mi; } - explicit BreakpointModelId(const QString &ba); // "21.2" -}; - -class BreakpointResponseId : public BreakpointIdBase -{ -public: - BreakpointResponseId() { m_majorPart = m_minorPart = 0; } - explicit BreakpointResponseId(quint16 ma) { m_majorPart = ma; m_minorPart = 0; } - BreakpointResponseId(quint16 ma, quint16 mi) { m_majorPart = ma; m_minorPart = mi; } - explicit BreakpointResponseId(const QString &ba); // "21.2" -}; - +class GdbMi; ////////////////////////////////////////////////////////////////// // @@ -86,7 +41,7 @@ public: //! \enum Debugger::Internal::BreakpointType -// Note: Keep synchronized with similar definitions in bridge.py +// Note: Keep synchronized with similar definitions in dumper.py enum BreakpointType { UnknownBreakpointType, @@ -110,11 +65,11 @@ enum BreakpointType enum BreakpointState { BreakpointNew, - BreakpointInsertRequested, //!< Inferior was told about bp, not ack'ed. - BreakpointInsertProceeding, - BreakpointChangeRequested, - BreakpointChangeProceeding, + BreakpointInsertionRequested, //!< Inferior was told about bp, not ack'ed. + BreakpointInsertionProceeding, BreakpointInserted, + BreakpointUpdateRequested, + BreakpointUpdateProceeding, BreakpointRemoveRequested, BreakpointRemoveProceeding, BreakpointDead @@ -185,12 +140,15 @@ public: bool conditionsMatch(const QString &other) const; bool isWatchpoint() const { return type == WatchpointAtAddress || type == WatchpointAtExpression; } + bool isLocatedAt(const QString &fileName, int lineNumber, const QString &markerFileName) const; // Enough for now. bool isBreakpoint() const { return !isWatchpoint() && !isTracepoint(); } bool isTracepoint() const { return tracepoint; } bool isCppBreakpoint() const; + bool isQmlFileAndLineBreakpoint() const; QString toString() const; void updateLocation(const QString &location); // file.cpp:42 + void updateFromGdbOutput(const GdbMi &bkpt); bool operator==(const BreakpointParameters &p) const { return equals(p); } bool operator!=(const BreakpointParameters &p) const { return !equals(p); } @@ -214,31 +172,10 @@ public: QString message; //!< message bool tracepoint; bool oneShot; //!< Should this breakpoint trigger only once? + + bool pending = true; //!< Breakpoint not fully resolved. + int hitCount = 0; //!< Number of times this has been hit. }; -class BreakpointResponse : public BreakpointParameters -{ -public: - BreakpointResponse(); - QString toString() const; - -public: - void fromParameters(const BreakpointParameters &p); - - BreakpointResponseId id; //!< Breakpoint number assigned by the debugger engine. - bool pending; //!< Breakpoint not fully resolved. - int hitCount; //!< Number of times this has been hit. - bool multiple; //!< Happens in constructors/gdb. - int correctedLineNumber; //!< Line number as seen by gdb. -}; - -inline uint qHash(const Debugger::Internal::BreakpointModelId &id) -{ - return id.toInternalId(); -} - } // namespace Internal } // namespace Debugger - -Q_DECLARE_METATYPE(Debugger::Internal::BreakpointModelId) -Q_DECLARE_METATYPE(Debugger::Internal::BreakpointResponseId) diff --git a/src/plugins/debugger/cdb/cdbengine.cpp b/src/plugins/debugger/cdb/cdbengine.cpp index 55dbc5c8fb2..d9ca62d7d1e 100644 --- a/src/plugins/debugger/cdb/cdbengine.cpp +++ b/src/plugins/debugger/cdb/cdbengine.cpp @@ -182,6 +182,7 @@ CdbEngine::CdbEngine() : m_extensionCommandPrefix("!" QT_CREATOR_CDB_EXT ".") { setObjectName("CdbEngine"); + setDebuggerName("CDB"); DisplayFormats stringFormats; stringFormats.append(SimpleFormat); @@ -225,7 +226,7 @@ void CdbEngine::init() m_stopMode = NoStopRequested; m_nextCommandToken = 0; m_currentBuiltinResponseToken = -1; - m_operateByInstruction = true; + m_operateByInstruction = action(OperateByInstruction)->isChecked(); m_hasDebuggee = false; m_sourceStepInto = false; m_watchPointX = m_watchPointY = 0; @@ -238,8 +239,6 @@ void CdbEngine::init() m_currentBuiltinResponse.clear(); m_extensionMessageBuffer.clear(); m_pendingBreakpointMap.clear(); - m_insertSubBreakpointMap.clear(); - m_pendingSubBreakpointMap.clear(); m_interrupCallbacks.clear(); m_symbolAddressCache.clear(); m_coreStopReason.reset(); @@ -526,15 +525,17 @@ void CdbEngine::handleInitialSessionIdle() operateByInstructionTriggered(action(OperateByInstruction)->isChecked()); // QmlCppEngine expects the QML engine to be connected before any breakpoints are hit // (attemptBreakpointSynchronization() will be directly called then) - attemptBreakpointSynchronization(); if (rp.breakOnMain) { - const BreakpointParameters bp(BreakpointAtMain); - BreakpointModelId id(quint16(-1)); - QString function = cdbAddBreakpointCommand(bp, m_sourcePathMappings, id, true); - runCommand({function, BuiltinCommand, - [this, id](const DebuggerResponse &r) { handleBreakInsert(r, id); }}); + // FIXME: +// const BreakpointParameters bp(BreakpointAtMain); +// BreakpointModelId id(quint16(-1)); +// QString function = cdbAddBreakpointCommand(bp, m_sourcePathMappings, id, true); +// runCommand({function, BuiltinCommand, +// [this, id](const DebuggerResponse &r) { handleBreakInsert(r, id); }}); } + // Take ownership of the breakpoint. Requests insertion. TODO: Cpp only? + BreakpointManager::claimBreakpointsForEngine(this); runCommand({".symopt+0x8000"}); // disable searching public symbol table - improving the symbol lookup speed runCommand({"sxn 0x4000001f", NoFlags}); // Do not break on WowX86 exceptions. runCommand({"sxn ibp", NoFlags}); // Do not break on initial breakpoints. @@ -608,7 +609,7 @@ void CdbEngine::runEngine() runCommand({"sxe " + breakEvent, NoFlags}); // Break functions: each function must be fully qualified, // else the debugger will slow down considerably. - const auto cb = [this](const DebuggerResponse &r) { handleBreakInsert(r, BreakpointModelId()); }; + const auto cb = [this](const DebuggerResponse &r) { handleBreakInsert(r, Breakpoint()); }; if (boolSetting(CdbBreakOnCrtDbgReport)) { Abi::OSFlavor flavor = runParameters().toolChainAbi.osFlavor(); // CrtDebugReport can not be safely resolved for vc 19 @@ -853,13 +854,8 @@ void CdbEngine::doInterruptInferior(const InterruptCallback &callback) showMessage(QString("Interrupting process %1...").arg(inferiorPid()), LogMisc); QTC_ASSERT(!m_signalOperation, notifyInferiorStopFailed(); return); - if (DebuggerRunTool *rt = runTool()) { - IDevice::ConstPtr device = rt->device(); - if (!device) - device = runParameters().inferior.device; - if (device) - m_signalOperation = device->signalOperation(); - } + QTC_ASSERT(device(), notifyInferiorRunFailed(); return); + m_signalOperation = device()->signalOperation(); QTC_ASSERT(m_signalOperation, notifyInferiorStopFailed(); return;); connect(m_signalOperation.data(), &DeviceProcessSignalOperation::finished, this, &CdbEngine::handleDoInterruptInferior); @@ -881,8 +877,8 @@ void CdbEngine::executeRunToLine(const ContextData &data) bp.lineNumber = data.lineNumber; } - runCommand({cdbAddBreakpointCommand(bp, m_sourcePathMappings, BreakpointModelId(), true), BuiltinCommand, - [this](const DebuggerResponse &r) { handleBreakInsert(r, BreakpointModelId()); }}); + runCommand({cdbAddBreakpointCommand(bp, m_sourcePathMappings, {}, true), BuiltinCommand, + [this](const DebuggerResponse &r) { handleBreakInsert(r, Breakpoint()); }}); continueInferior(); } @@ -891,8 +887,8 @@ void CdbEngine::executeRunToFunction(const QString &functionName) // Add one-shot breakpoint BreakpointParameters bp(BreakpointByFunction); bp.functionName = functionName; - runCommand({cdbAddBreakpointCommand(bp, m_sourcePathMappings, BreakpointModelId(), true), BuiltinCommand, - [this](const DebuggerResponse &r) { handleBreakInsert(r, BreakpointModelId()); }}); + runCommand({cdbAddBreakpointCommand(bp, m_sourcePathMappings, {}, true), BuiltinCommand, + [this](const DebuggerResponse &r) { handleBreakInsert(r, Breakpoint()); }}); continueInferior(); } @@ -1014,10 +1010,9 @@ void CdbEngine::handleThreads(const DebuggerResponse &response) } } -void CdbEngine::executeDebuggerCommand(const QString &command, DebuggerLanguages languages) +void CdbEngine::executeDebuggerCommand(const QString &command) { - if (languages & CppLanguage) - runCommand({command, NoFlags}); + runCommand({command, NoFlags}); } // Post command to the cdb process @@ -1465,8 +1460,7 @@ void CdbEngine::fetchMemory(MemoryAgent *agent, quint64 address, quint64 length) StringInputStream str(args); str << address << ' ' << length; cmd.args = args; - cmd.callback = [this, agent = QPointer(agent), address, length] - (const DebuggerResponse &response) { + cmd.callback = [&](const DebuggerResponse &response) { if (!agent) return; if (response.resultClass == ResultDone) { @@ -1507,7 +1501,8 @@ void CdbEngine::requestModuleSymbols(const QString &moduleName) void CdbEngine::reloadRegisters() { - QTC_ASSERT(threadsHandler()->currentThreadIndex() >= 0, return); + if (!(threadsHandler()->currentThreadIndex() >= 0)) + return; runCommand({"registers", ExtensionCommand, CB(handleRegistersExt)}); } @@ -1680,19 +1675,18 @@ enum StopActionFlags StopShutdownInProgress = 0x80 // Shutdown in progress }; -static inline QString msgTracePointTriggered(BreakpointModelId id, const int number, +static inline QString msgTracePointTriggered(const Breakpoint &, const QString &displayName, const QString &threadId) { - return CdbEngine::tr("Trace point %1 (%2) in thread %3 triggered.") - .arg(id.toString()).arg(number).arg(threadId); + return CdbEngine::tr("Trace point %1 in thread %2 triggered.") + .arg(displayName).arg(threadId); } -static inline QString msgCheckingConditionalBreakPoint(BreakpointModelId id, const int number, - const QString &condition, +static inline QString msgCheckingConditionalBreakPoint(const Breakpoint &bp, const QString &displayName, const QString &threadId) { - return CdbEngine::tr("Conditional breakpoint %1 (%2) in thread %3 triggered, examining expression \"%4\".") - .arg(id.toString()).arg(number).arg(threadId, condition); + return CdbEngine::tr("Conditional breakpoint %1 in thread %2 triggered, examining expression \"%3\".") + .arg(displayName).arg(threadId, bp->condition()); } unsigned CdbEngine::examineStopReason(const GdbMi &stopReason, @@ -1722,52 +1716,55 @@ unsigned CdbEngine::examineStopReason(const GdbMi &stopReason, if (reason == "breakpoint") { // Note: Internal breakpoints (run to line) are reported with id=0. // Step out creates temporary breakpoints with id 10000. - int number = 0; - BreakpointModelId id = cdbIdToBreakpointModelId(stopReason["breakpointId"]); - Breakpoint bp = breakHandler()->breakpointById(id); - if (bp) { - if (bp.engine() == this) { - const BreakpointResponse parameters = bp.response(); - if (!parameters.message.isEmpty()) { - showMessage(parameters.message + '\n', AppOutput); - showMessage(parameters.message, LogMisc); - } - // Trace point? Just report. - number = parameters.id.majorPart(); - if (parameters.tracepoint) { - *message = msgTracePointTriggered(id, number, QString::number(threadId)); - return StopReportLog|StopIgnoreContinue; - } - // Trigger evaluation of BP expression unless we are already in the response. - if (!conditionalBreakPointTriggered && !parameters.condition.isEmpty()) { - *message = msgCheckingConditionalBreakPoint(id, number, parameters.condition, - QString::number(threadId)); - QString args = parameters.condition; - if (args.contains(' ') && !args.startsWith('"')) { - args.prepend('"'); - args.append('"'); - } - DebuggerCommand cmd("expression", ExtensionCommand); - cmd.args = args; - cmd.callback = [this, id, stopReason](const DebuggerResponse &response) { - handleExpression(response, id, stopReason); - }; - runCommand(cmd); - return StopReportLog; - } - } else { - bp = Breakpoint(); + const QString responseId = stopReason["breakpointId"].data(); + QString displayName; + Breakpoint bp = breakHandler()->findBreakpointByResponseId(responseId); + if (!bp) { + if (const SubBreakpoint sub = breakHandler()->findSubBreakpointByResponseId(responseId)) { + bp = sub->breakpoint(); + displayName = sub->displayName; } + } else { + displayName = bp->displayName(); + } + if (bp) { + if (!bp->message().isEmpty()) { + showMessage(bp->message() + '\n', AppOutput); + showMessage(bp->message(), LogMisc); + } + // Trace point? Just report. + if (bp->isTracepoint()) { + *message = msgTracePointTriggered(bp, displayName, QString::number(threadId)); + return StopReportLog|StopIgnoreContinue; + } + // Trigger evaluation of BP expression unless we are already in the response. + if (!conditionalBreakPointTriggered && !bp->condition().isEmpty()) { + *message = msgCheckingConditionalBreakPoint(bp, displayName, QString::number(threadId)); + QString args = bp->condition(); + if (args.contains(' ') && !args.startsWith('"')) { + args.prepend('"'); + args.append('"'); + } + DebuggerCommand cmd("expression", ExtensionCommand); + cmd.args = args; + cmd.callback = [this, bp, stopReason](const DebuggerResponse &response) { + handleExpression(response, bp, stopReason); + }; + runCommand(cmd); + + return StopReportLog; + } + + QString tid = QString::number(threadId); + if (bp->type() == WatchpointAtAddress) + *message = bp->msgWatchpointByAddressTriggered(bp->address(), tid); + else if (bp->type() == WatchpointAtExpression) + *message = bp->msgWatchpointByExpressionTriggered(bp->expression(), tid); + else + *message = bp->msgBreakpointTriggered(tid); + rc |= StopReportStatusMessage|StopNotifyStop; } - QString tid = QString::number(threadId); - if (bp.type() == WatchpointAtAddress) - *message = bp.msgWatchpointByAddressTriggered(number, bp.address(), tid); - else if (bp.type() == WatchpointAtExpression) - *message = bp.msgWatchpointByExpressionTriggered(number, bp.expression(), tid); - else - *message = bp.msgBreakpointTriggered(number, tid); - rc |= StopReportStatusMessage|StopNotifyStop; return rc; } if (reason == "exception") { @@ -1881,11 +1878,11 @@ void CdbEngine::processStop(const GdbMi &stopReason, bool conditionalBreakPointT showMessage(stopReason["threaderror"].data(), LogError); } // Fire off remaining commands asynchronously - if (!m_pendingBreakpointMap.isEmpty() && !m_pendingSubBreakpointMap.isEmpty()) + if (!m_pendingBreakpointMap.isEmpty()) listBreakpoints(); - if (Internal::isRegistersWindowVisible()) + if (isRegistersWindowVisible()) reloadRegisters(); - if (Internal::isModulesWindowVisible()) + if (isModulesWindowVisible()) reloadModules(); } // After the sequence has been sent off and CDB is pondering the commands, @@ -1894,7 +1891,7 @@ void CdbEngine::processStop(const GdbMi &stopReason, bool conditionalBreakPointT showStoppedByExceptionMessageBox(exceptionBoxMessage); } -void CdbEngine::handleBreakInsert(const DebuggerResponse &response, const BreakpointModelId &bpId) +void CdbEngine::handleBreakInsert(const DebuggerResponse &response, const Breakpoint &bp) { const QStringList reply = response.data.data().split('\n'); if (reply.isEmpty()) @@ -1913,16 +1910,19 @@ void CdbEngine::handleBreakInsert(const DebuggerResponse &response, const Breakp // Matched: untitled123!::operator QString (__cdecl*)(void) (000007f6`be2f27b0) // Matched: untitled123!:: (000007f6`be2f27d0) // Matched: untitled123!::operator QString (__vectorcall*)(void) (000007f6`be2f2850) - // Ambiguous symbol error at '`untitled2!C:\dev\src\tmp\untitled2\main.cpp:18`' + // Ambiguous symbol error at '`untitled2!C:\dev\src\tmp\unttitled2\main.cpp:18`' // ^ Extra character error in 'bu1004 `untitled2!C:\dev\src\tmp\untitled2\main.cpp:18`' - if (!bpId.isValid()) + // Happens regularly for Run to Line and Jump to Line. + if (!bp) return; - Breakpoint bp = breakHandler()->breakpointById(bpId); + // add break point for every match + const int parentResponseId = bp->responseId().toInt(); quint16 subBreakPointID = 0; + const QLatin1String matchPrefix("Matched: "); for (auto line = reply.constBegin(), end = reply.constEnd(); line != end; ++line) { - if (!line->startsWith("Matched: ")) + if (!line->startsWith(matchPrefix)) continue; const int addressStartPos = line->lastIndexOf('(') + 1; const int addressEndPos = line->indexOf(')', addressStartPos); @@ -1936,16 +1936,25 @@ void CdbEngine::handleBreakInsert(const DebuggerResponse &response, const Breakp if (!ok) continue; - BreakpointModelId id(bpId.majorPart(), ++subBreakPointID); - BreakpointResponse res = bp.response(); - res.type = BreakpointByAddress; - res.address = address; - m_insertSubBreakpointMap.insert(id, res); + ++subBreakPointID; + const QString responseId(QString::number(parentResponseId + subBreakPointID)); + SubBreakpoint sub = bp->findOrCreateSubBreakpoint(responseId); + sub->responseId = responseId; + sub->params = bp->parameters(); + sub->params.type = BreakpointByAddress; + sub->params.address = address; + QString functionName(line->mid(matchPrefix.size(), + addressStartPos - 1 - matchPrefix.size())); + const int functionStart = functionName.indexOf('!') + 1; + const int functionOffset = functionName.lastIndexOf('+'); + if (functionOffset > 0) + functionName.truncate(functionOffset); + if (functionStart > 0) + functionName = functionName.mid(functionStart); + sub->params.functionName = functionName; + sub->displayName = bp->displayName() + '.' + QString::number(subBreakPointID); + runCommand({cdbAddBreakpointCommand(sub->params, m_sourcePathMappings, sub->responseId, false), NoFlags}); } - if (subBreakPointID == 0) - return; - - attemptBreakpointSynchronization(); } void CdbEngine::handleCheckWow64(const DebuggerResponse &response, const GdbMi &stack) @@ -2413,10 +2422,10 @@ bool CdbEngine::stateAcceptsBreakpointChanges() const return false; } -bool CdbEngine::acceptsBreakpoint(Breakpoint bp) const +bool CdbEngine::acceptsBreakpoint(const BreakpointParameters &bp) const { - if (bp.parameters().isCppBreakpoint()) { - switch (bp.type()) { + if (bp.isCppBreakpoint()) { + switch (bp.type) { case UnknownBreakpointType: case LastBreakpointType: case BreakpointAtFork: @@ -2490,139 +2499,90 @@ unsigned BreakpointCorrectionContext::fixLineNumber(const QString &fileName, return correctedLine; } -void CdbEngine::attemptBreakpointSynchronization() +void CdbEngine::insertBreakpoint(const Breakpoint &bp) { - if (debug) - qDebug("attemptBreakpointSynchronization in %s", qPrintable(stateName(state()))); - // Check if there is anything to be done at all. - BreakHandler *handler = breakHandler(); - // Take ownership of the breakpoint. Requests insertion. TODO: Cpp only? - for (Breakpoint bp : handler->unclaimedBreakpoints()) - if (acceptsBreakpoint(bp)) - bp.setEngine(this); - - // Quick check: is there a need to change something? - Populate module cache - bool changed = !m_insertSubBreakpointMap.isEmpty(); - const Breakpoints bps = handler->engineBreakpoints(this); - if (!changed) { - for (Breakpoint bp : bps) { - switch (bp.state()) { - case BreakpointInsertRequested: - case BreakpointRemoveRequested: - case BreakpointChangeRequested: - changed = true; - break; - case BreakpointInserted: { - // Collect the new modules matching the files. - // In the future, that information should be obtained from the build system. - const BreakpointParameters &data = bp.parameters(); - if (data.type == BreakpointByFileAndLine && !data.module.isEmpty()) - m_fileNameModuleHash.insert(data.fileName, data.module); - } - break; - default: - break; - } - } + BreakpointParameters parameters = bp->requestedParameters(); + const auto handleBreakInsertCB = [this, bp](const DebuggerResponse &r) { handleBreakInsert(r, bp); }; + BreakpointParameters response = parameters; + auto responseId = QString::number(breakPointIdToCdbId(bp)); + QScopedPointer lineCorrection( + new BreakpointCorrectionContext(m_codeModelSnapshot, CppTools::CppModelManager::instance()->workingCopy())); + if (!m_autoBreakPointCorrection + && parameters.type == BreakpointByFileAndLine + && boolSetting(CdbBreakPointCorrection)) { + response.lineNumber = int(lineCorrection->fixLineNumber( + parameters.fileName, unsigned(parameters.lineNumber))); + QString cmd = cdbAddBreakpointCommand(response, m_sourcePathMappings, responseId, false); + runCommand({cmd, BuiltinCommand, handleBreakInsertCB}); + } else { + QString cmd = cdbAddBreakpointCommand(parameters, m_sourcePathMappings, responseId, false); + runCommand({cmd, BuiltinCommand, handleBreakInsertCB}); } - + if (!parameters.enabled) + runCommand({"bd " + responseId, NoFlags}); + // Ensure enabled/disabled is correct in handler and line number is there. + bp->setParameters(response); + bp->setResponseId(responseId); + bp->setDisplayName(QString::number(bp->modelId())); + notifyBreakpointInsertProceeding(bp); + notifyBreakpointInsertOk(bp); + m_pendingBreakpointMap.insert(bp); if (debugBreakpoints) - qDebug("attemptBreakpointSynchronizationI %dms accessible=%d, %s %d breakpoints, changed=%d", - elapsedLogTime(), m_accessible, qPrintable(stateName(state())), bps.size(), changed); - if (!changed) - return; + qDebug("Adding %d %s\n", bp->modelId(), qPrintable(response.toString())); + listBreakpoints(); +} - // Add/Change breakpoints and store pending ones in map, since - // Breakhandler::setResponse() on pending breakpoints clears the pending flag. - // handleBreakPoints will the complete that information and set it on the break handler. - bool addedChanged = false; - QScopedPointer lineCorrection; - for (Breakpoint bp : bps) { - BreakpointParameters parameters = bp.parameters(); - BreakpointModelId id = bp.id(); - const auto handleBreakInsertCB = [this, id](const DebuggerResponse &r) { handleBreakInsert(r, id); }; - BreakpointResponse response; - response.fromParameters(parameters); - response.id = BreakpointResponseId(id.majorPart(), id.minorPart()); - // If we encountered that file and have a module for it: Add it. - if (parameters.type == BreakpointByFileAndLine && parameters.module.isEmpty()) { - const QHash::const_iterator it = m_fileNameModuleHash.constFind(parameters.fileName); - if (it != m_fileNameModuleHash.constEnd()) - parameters.module = it.value(); - } - switch (bp.state()) { - case BreakpointInsertRequested: - if (!m_autoBreakPointCorrection - && parameters.type == BreakpointByFileAndLine - && boolSetting(CdbBreakPointCorrection)) { - if (lineCorrection.isNull()) - lineCorrection.reset(new BreakpointCorrectionContext(m_codeModelSnapshot, - CppTools::CppModelManager::instance()->workingCopy())); - response.lineNumber = int(lineCorrection->fixLineNumber( - parameters.fileName, unsigned(parameters.lineNumber))); - QString cmd = cdbAddBreakpointCommand(response, m_sourcePathMappings, id, false); - runCommand({cmd, BuiltinCommand, handleBreakInsertCB}); - } else { - QString cmd = cdbAddBreakpointCommand(parameters, m_sourcePathMappings, id, false); - runCommand({cmd, BuiltinCommand, handleBreakInsertCB}); - } - if (!parameters.enabled) - runCommand({"bd " + QString::number(breakPointIdToCdbId(id)), NoFlags}); - bp.notifyBreakpointInsertProceeding(); - bp.notifyBreakpointInsertOk(); - m_pendingBreakpointMap.insert(id, response); - addedChanged = true; - // Ensure enabled/disabled is correct in handler and line number is there. - bp.setResponse(response); - if (debugBreakpoints) - qDebug("Adding %d %s\n", id.toInternalId(), - qPrintable(response.toString())); - break; - case BreakpointChangeRequested: - bp.notifyBreakpointChangeProceeding(); - if (debugBreakpoints) - qDebug("Changing %d:\n %s\nTo %s\n", id.toInternalId(), - qPrintable(bp.response().toString()), - qPrintable(parameters.toString())); - if (parameters.enabled != bp.response().enabled) { - // Change enabled/disabled breakpoints without triggering update. - if (parameters.enabled) - runCommand({"be " + QString::number(breakPointIdToCdbId(id)), NoFlags}); - else - runCommand({"bd " + QString::number(breakPointIdToCdbId(id)), NoFlags}); - response.pending = false; - response.enabled = parameters.enabled; - bp.setResponse(response); - } else { - // Delete and re-add, triggering update - addedChanged = true; - runCommand({cdbClearBreakpointCommand(id), NoFlags}); - QString cmd(cdbAddBreakpointCommand(parameters, m_sourcePathMappings, id, false)); - runCommand({cmd, BuiltinCommand, handleBreakInsertCB}); - m_pendingBreakpointMap.insert(id, response); - } - bp.notifyBreakpointChangeOk(); - break; - case BreakpointRemoveRequested: - runCommand({cdbClearBreakpointCommand(id), NoFlags}); - bp.notifyBreakpointRemoveProceeding(); - bp.notifyBreakpointRemoveOk(); - m_pendingBreakpointMap.remove(id); - break; - default: - break; - } - } - foreach (BreakpointModelId id, m_insertSubBreakpointMap.keys()) { - addedChanged = true; - const BreakpointResponse &response = m_insertSubBreakpointMap.value(id); - runCommand({cdbAddBreakpointCommand(response, m_sourcePathMappings, id, false), NoFlags}); - m_insertSubBreakpointMap.remove(id); - m_pendingSubBreakpointMap.insert(id, response); - } - // List breakpoints and send responses - if (addedChanged) +void CdbEngine::removeBreakpoint(const Breakpoint &bp) +{ + runCommand({cdbClearBreakpointCommand(bp), NoFlags}); + notifyBreakpointRemoveProceeding(bp); + notifyBreakpointRemoveOk(bp); + m_pendingBreakpointMap.remove(bp); +} + +static QString enableBreakpointCommand(const QString &responseId, bool on) +{ + const QString command(on ? QString("be") : QString("bd")); + return command + ' ' + responseId; +} + +void CdbEngine::updateBreakpoint(const Breakpoint &bp) +{ + BreakpointParameters parameters = bp->requestedParameters(); + const auto handleBreakInsertCB = [this, bp](const DebuggerResponse &r) { handleBreakInsert(r, bp); }; + BreakpointParameters response = parameters; + auto responseId = QString::number(breakPointIdToCdbId(bp)); + notifyBreakpointChangeProceeding(bp); + if (debugBreakpoints) + qDebug("Changing %d:\n %s\nTo %s\n", bp->modelId(), + qPrintable(bp->parameters().toString()), + qPrintable(parameters.toString())); + if (parameters.enabled != bp->isEnabled()) { + // Change enabled/disabled breakpoints without triggering update. + bp->forFirstLevelChildren([this, parameters](SubBreakpointItem *sbp){ + breakHandler()->requestSubBreakpointEnabling({sbp}, parameters.enabled); + }); + if (!bp->hasChildren()) + runCommand({enableBreakpointCommand(bp->responseId(), parameters.enabled), NoFlags}); + response.pending = false; + response.enabled = parameters.enabled; + bp->setParameters(response); + } else { + // Delete and re-add, triggering update + runCommand({cdbClearBreakpointCommand(bp), NoFlags}); + QString cmd = cdbAddBreakpointCommand(parameters, m_sourcePathMappings, responseId, false); + runCommand({cmd, BuiltinCommand, handleBreakInsertCB}); + m_pendingBreakpointMap.insert(bp); listBreakpoints(); + } + notifyBreakpointChangeOk(bp); +} + +void CdbEngine::enableSubBreakpoint(const SubBreakpoint &sbp, bool on) +{ + runCommand({enableBreakpointCommand(sbp->responseId, on), NoFlags}); + if (on && !sbp->breakpoint()->isEnabled()) + sbp->breakpoint()->setEnabled(true); } // Pass a file name through source mapping and normalize upper/lower case (for the editor @@ -2852,7 +2812,7 @@ void CdbEngine::handleStackTrace(const DebuggerResponse &response) } } -void CdbEngine::handleExpression(const DebuggerResponse &response, BreakpointModelId id, const GdbMi &stopReason) +void CdbEngine::handleExpression(const DebuggerResponse &response, const Breakpoint &bp, const GdbMi &stopReason) { int value = 0; if (response.resultClass == ResultDone) @@ -2862,9 +2822,9 @@ void CdbEngine::handleExpression(const DebuggerResponse &response, BreakpointMod // Is this a conditional breakpoint? const QString message = value ? tr("Value %1 obtained from evaluating the condition of breakpoint %2, stopping."). - arg(value).arg(id.toString()) : + arg(value).arg(bp->displayName()) : tr("Value 0 obtained from evaluating the condition of breakpoint %1, continuing."). - arg(id.toString()); + arg(bp->displayName()); showMessage(message, LogMisc); // Stop if evaluation is true, else continue if (value) @@ -2905,10 +2865,9 @@ void CdbEngine::handleWidgetAt(const DebuggerResponse &response) m_watchPointX = m_watchPointY = 0; } -static inline void formatCdbBreakPointResponse(BreakpointModelId id, const BreakpointResponse &r, - QTextStream &str) +static void formatCdbBreakPointResponse(int modelId, const QString &responseId, const BreakpointParameters &r, QTextStream &str) { - str << "Obtained breakpoint " << id << " (#" << r.id.majorPart() << ')'; + str << "Obtained breakpoint " << modelId << " (#" << responseId << ')'; if (r.pending) { str << ", pending"; } else { @@ -2946,46 +2905,62 @@ void CdbEngine::handleBreakPoints(const DebuggerResponse &response) QTextStream str(&message); BreakHandler *handler = breakHandler(); foreach (const GdbMi &breakPointG, response.data.children()) { - BreakpointResponse reportedResponse; + // Might not be valid if there is not id + const QString responseId = breakPointG["id"].data(); + BreakpointParameters reportedResponse; parseBreakPoint(breakPointG, &reportedResponse); if (debugBreakpoints) - qDebug(" Parsed %d: pending=%d %s\n", reportedResponse.id.majorPart(), + qDebug(" Parsed %s: pending=%d %s\n", qPrintable(responseId), reportedResponse.pending, qPrintable(reportedResponse.toString())); - if (reportedResponse.id.isValid() && !reportedResponse.pending) { - Breakpoint bp = handler->findBreakpointByResponseId(reportedResponse.id); + if (!responseId.isEmpty() && !reportedResponse.pending) { + Breakpoint bp = handler->findBreakpointByResponseId(responseId); if (!bp && reportedResponse.type == BreakpointByFunction) continue; // Breakpoints from options, CrtDbgReport() and others. - QTC_ASSERT(bp, continue); - const auto it = m_pendingBreakpointMap.find(bp.id()); - const auto subIt = m_pendingSubBreakpointMap.find( - BreakpointModelId(reportedResponse.id.majorPart(), - reportedResponse.id.minorPart())); - if (it != m_pendingBreakpointMap.end() || subIt != m_pendingSubBreakpointMap.end()) { + + if (bp) { + if (!bp->isPending()) + continue; + QTC_ASSERT(m_pendingBreakpointMap.contains(bp), continue); // Complete the response and set on handler. - BreakpointResponse currentResponse = it != m_pendingBreakpointMap.end() - ? it.value() - : subIt.value(); - currentResponse.id = reportedResponse.id; + BreakpointParameters currentResponse = bp->parameters(); currentResponse.address = reportedResponse.address; currentResponse.module = reportedResponse.module; currentResponse.pending = reportedResponse.pending; currentResponse.enabled = reportedResponse.enabled; currentResponse.fileName = reportedResponse.fileName; currentResponse.lineNumber = reportedResponse.lineNumber; - formatCdbBreakPointResponse(bp.id(), currentResponse, str); + formatCdbBreakPointResponse(bp->modelId(), responseId, currentResponse, str); if (debugBreakpoints) - qDebug(" Setting for %d: %s\n", currentResponse.id.majorPart(), + qDebug(" Setting for %s: %s\n", qPrintable(responseId), qPrintable(currentResponse.toString())); - if (it != m_pendingBreakpointMap.end()) { - bp.setResponse(currentResponse); - m_pendingBreakpointMap.erase(it); - } - if (subIt != m_pendingSubBreakpointMap.end()) { - bp.insertSubBreakpoint(currentResponse); - m_pendingSubBreakpointMap.erase(subIt); - } + bp->setParameters(currentResponse); + m_pendingBreakpointMap.remove(bp); + continue; } + SubBreakpoint sub = handler->findSubBreakpointByResponseId(responseId); + if (sub) { + BreakpointParameters currentResponse = sub->params; + currentResponse.address = reportedResponse.address; + currentResponse.module = reportedResponse.module; + currentResponse.pending = reportedResponse.pending; + currentResponse.enabled = reportedResponse.enabled; + currentResponse.fileName = reportedResponse.fileName; + currentResponse.lineNumber = reportedResponse.lineNumber; + Breakpoint bp = sub->breakpoint(); + QTC_ASSERT(bp, continue); + formatCdbBreakPointResponse(bp->modelId(), responseId, currentResponse, str); + m_pendingBreakpointMap.remove(bp); + if (bp->isPending() && !reportedResponse.pending) + bp->setPending(false); + if (debugBreakpoints) + qDebug(" Setting for %s: %s\n", qPrintable(responseId), + qPrintable(currentResponse.toString())); +// SubBreakpointItem *loc = bp->findOrCreateSubBreakpoint(reportedResponse.responseId); + sub->setParameters(currentResponse); + continue; + } + QTC_ASSERT(false, qDebug() << "bp not found in either of the pending maps"); } // not pending reported } // foreach if (m_pendingBreakpointMap.empty()) diff --git a/src/plugins/debugger/cdb/cdbengine.h b/src/plugins/debugger/cdb/cdbengine.h index 2ef4c3c8765..cfc822c658f 100644 --- a/src/plugins/debugger/cdb/cdbengine.h +++ b/src/plugins/debugger/cdb/cdbengine.h @@ -52,12 +52,8 @@ public: explicit CdbEngine(); ~CdbEngine() override; - // Factory function that returns 0 if the debug engine library cannot be found. - bool canHandleToolTip(const DebuggerToolTipContext &context) const override; - DebuggerEngine *cppEngine() override { return this; } - void setupEngine() override; void runEngine() override; void shutdownInferior() override; @@ -81,14 +77,18 @@ public: void executeRunToFunction(const QString &functionName) override; void executeJumpToLine(const ContextData &data) override; void assignValueInDebugger(WatchItem *w, const QString &expr, const QVariant &value) override; - void executeDebuggerCommand(const QString &command, DebuggerLanguages languages) override; + void executeDebuggerCommand(const QString &command) override; void activateFrame(int index) override; void selectThread(ThreadId threadId) override; bool stateAcceptsBreakpointChanges() const override; - bool acceptsBreakpoint(Breakpoint bp) const override; - void attemptBreakpointSynchronization() override; + bool acceptsBreakpoint(const BreakpointParameters ¶ms) const override; + + void insertBreakpoint(const Breakpoint &bp) override; + void removeBreakpoint(const Breakpoint &bp) override; + void updateBreakpoint(const Breakpoint &bp) override; + void enableSubBreakpoint(const SubBreakpoint &sbp, bool on) override; void fetchDisassembler(DisassemblerAgent *agent) override; void fetchMemory(MemoryAgent *, quint64 addr, quint64 length) override; @@ -119,8 +119,7 @@ private: void handleDoInterruptInferior(const QString &errorMessage); - using PendingBreakPointMap = QHash; - using SourcePathMapping = QPair; + typedef QPair SourcePathMapping; struct NormalizedSourceFileName // Struct for caching mapped/normalized source files. { NormalizedSourceFileName(const QString &fn = QString(), bool e = false) : fileName(fn), exists(e) {} @@ -176,10 +175,10 @@ private: void handleStackTrace(const DebuggerResponse &); void handleRegisters(const DebuggerResponse &); void handleJumpToLineAddressResolution(const DebuggerResponse &response, const ContextData &context); - void handleExpression(const DebuggerResponse &command, BreakpointModelId id, const GdbMi &stopReason); + void handleExpression(const DebuggerResponse &command, const Breakpoint &bp, const GdbMi &stopReason); void handleResolveSymbol(const DebuggerResponse &command, const QString &symbol, DisassemblerAgent *agent); void handleResolveSymbolHelper(const QList &addresses, DisassemblerAgent *agent); - void handleBreakInsert(const DebuggerResponse &response, const BreakpointModelId &bpId); + void handleBreakInsert(const DebuggerResponse &response, const Breakpoint &bp); void handleCheckWow64(const DebuggerResponse &response, const GdbMi &stack); void ensureUsing32BitStackInWow64(const DebuggerResponse &response, const GdbMi &stack); void handleSwitchWow64Stack(const DebuggerResponse &response); @@ -232,11 +231,8 @@ private: bool m_sourceStepInto = false; int m_watchPointX = 0; int m_watchPointY = 0; - PendingBreakPointMap m_pendingBreakpointMap; - PendingBreakPointMap m_insertSubBreakpointMap; - PendingBreakPointMap m_pendingSubBreakpointMap; + QSet m_pendingBreakpointMap; bool m_autoBreakPointCorrection = false; - QHash m_fileNameModuleHash; QMultiHash m_symbolAddressCache; bool m_ignoreCdbOutput = false; QList m_interrupCallbacks; diff --git a/src/plugins/debugger/cdb/cdbparsehelpers.cpp b/src/plugins/debugger/cdb/cdbparsehelpers.cpp index fccd1c00864..415d9c2de7e 100644 --- a/src/plugins/debugger/cdb/cdbparsehelpers.cpp +++ b/src/plugins/debugger/cdb/cdbparsehelpers.cpp @@ -27,6 +27,7 @@ #include "stringinputstream.h" +#include #include #include #include @@ -77,14 +78,14 @@ QString cdbSourcePathMapping(QString fileName, // Determine file name to be used for breakpoints. Convert to native and, unless short path // is set, perform reverse lookup in the source path mappings. -static inline QString cdbBreakPointFileName(const BreakpointParameters &bp, +static inline QString cdbBreakPointFileName(const BreakpointParameters ¶ms, const QList > &sourcePathMapping) { - if (bp.fileName.isEmpty()) - return bp.fileName; - if (bp.pathUsage == BreakpointUseShortPath) - return Utils::FileName::fromString(bp.fileName).fileName(); - return cdbSourcePathMapping(QDir::toNativeSeparators(bp.fileName), sourcePathMapping, SourceToDebugger); + if (params.fileName.isEmpty()) + return params.fileName; + if (params.pathUsage == BreakpointUseShortPath) + return Utils::FileName::fromString(params.fileName).fileName(); + return cdbSourcePathMapping(QDir::toNativeSeparators(params.fileName), sourcePathMapping, SourceToDebugger); } static BreakpointParameters fixWinMSVCBreakpoint(const BreakpointParameters &p) @@ -127,69 +128,46 @@ static BreakpointParameters fixWinMSVCBreakpoint(const BreakpointParameters &p) return p; } -int breakPointIdToCdbId(const BreakpointModelId &id) +int breakPointIdToCdbId(const Breakpoint &bp) { - return cdbBreakPointStartId + id.majorPart() * cdbBreakPointIdMinorPart + id.minorPart(); +// return cdbBreakPointStartId + bp.majorPart() * cdbBreakPointIdMinorPart + bp.minorPart(); + if (!bp->responseId().isEmpty()) + return bp->responseId().toInt(); + return cdbBreakPointStartId + bp->modelId() * cdbBreakPointIdMinorPart; } -template -inline ModelId cdbIdToBreakpointId(const int &id) -{ - if (id >= cdbBreakPointStartId) { - int major = (id - cdbBreakPointStartId) / cdbBreakPointIdMinorPart; - int minor = id % cdbBreakPointIdMinorPart; - if (minor) - return ModelId(major, minor); - else - return ModelId(major); - } - return ModelId(); -} - -template -inline ModelId cdbIdToBreakpointId(const GdbMi &data) -{ - if (data.isValid()) { // Might not be valid if there is not id - bool ok; - const int id = data.data().toInt(&ok); - if (ok) - return cdbIdToBreakpointId(id); - } - return ModelId(); -} - -BreakpointModelId cdbIdToBreakpointModelId(const GdbMi &id) -{ - return cdbIdToBreakpointId(id); -} - -BreakpointResponseId cdbIdToBreakpointResponseId(const GdbMi &id) -{ - return cdbIdToBreakpointId(id); -} +//static int cdbIdToBreakpointModel(int cdbid) +//{ +// if (cdbid >= cdbBreakPointStartId) { +// int major = (cdbid - cdbBreakPointStartId) / cdbBreakPointIdMinorPart; +// int minor = cdbid % cdbBreakPointIdMinorPart; +// (void) minor; +// return major; +// } +// return 0; +//} QString cdbAddBreakpointCommand(const BreakpointParameters &bpIn, const QList > &sourcePathMapping, - BreakpointModelId id /* = BreakpointId() */, + const QString &responseId, bool oneshot) { - const BreakpointParameters bp = fixWinMSVCBreakpoint(bpIn); + const BreakpointParameters params = fixWinMSVCBreakpoint(bpIn); QString rc; StringInputStream str(rc); - if (bp.threadSpec >= 0) - str << '~' << bp.threadSpec << ' '; + if (params.threadSpec >= 0) + str << '~' << params.threadSpec << ' '; // Currently use 'bu' so that the offset expression (including file name) // is kept when reporting back breakpoints (which is otherwise discarded // when resolving). - str << (bp.type == WatchpointAtAddress ? "ba" : "bu"); - if (id.isValid()) - str << breakPointIdToCdbId(id); - str << ' '; + str << (params.type == WatchpointAtAddress ? "ba" : "bu") + << responseId + << ' '; if (oneshot) str << "/1 "; - switch (bp.type) { + switch (params.type) { case BreakpointAtFork: case BreakpointAtExec: case WatchpointAtExpression: @@ -204,39 +182,41 @@ QString cdbAddBreakpointCommand(const BreakpointParameters &bpIn, QTC_ASSERT(false, return QString()); break; case BreakpointByAddress: - str << hex << hexPrefixOn << bp.address << hexPrefixOff << dec; + str << hex << hexPrefixOn << params.address << hexPrefixOff << dec; break; case BreakpointByFunction: - if (!bp.module.isEmpty()) - str << bp.module << '!'; - str << bp.functionName; + if (!params.module.isEmpty()) + str << params.module << '!'; + str << params.functionName; break; case BreakpointByFileAndLine: str << '`'; - if (!bp.module.isEmpty()) - str << bp.module << '!'; - str << cdbBreakPointFileName(bp, sourcePathMapping) << ':' << bp.lineNumber << '`'; + if (!params.module.isEmpty()) + str << params.module << '!'; + str << cdbBreakPointFileName(params, sourcePathMapping) << ':' << params.lineNumber << '`'; break; case WatchpointAtAddress: { // Read/write, no space here - const unsigned size = bp.size ? bp.size : 1; - str << 'r' << size << ' ' << hex << hexPrefixOn << bp.address << hexPrefixOff << dec; + const unsigned size = params.size ? params.size : 1; + str << 'r' << size << ' ' << hex << hexPrefixOn << params.address << hexPrefixOff << dec; } break; } - if (bp.ignoreCount) - str << " 0n" << (bp.ignoreCount + 1); + if (params.ignoreCount) + str << " 0n" << (params.ignoreCount + 1); // Condition currently unsupported. - if (!bp.command.isEmpty()) - str << " \"" << bp.command << '"'; + if (!params.command.isEmpty()) + str << " \"" << params.command << '"'; return rc; } -QString cdbClearBreakpointCommand(const BreakpointModelId &id) +QString cdbClearBreakpointCommand(const Breakpoint &bp) { - const int firstBreakPoint = breakPointIdToCdbId(id); - if (id.isMinor()) - return "bc " + QString::number(firstBreakPoint); +// FIME: Check +// const int firstBreakPoint = breakPointIdToCdbId(id); +// if (id.isMinor()) +// return "bc " + QString::number(firstBreakPoint); // If this is a major break point we also want to delete all sub break points + const int firstBreakPoint = cdbBreakPointStartId + bp->modelId() * cdbBreakPointIdMinorPart; const int lastBreakPoint = firstBreakPoint + cdbBreakPointIdMinorPart - 1; return "bc " + QString::number(firstBreakPoint) + '-' + QString::number(lastBreakPoint); } @@ -270,14 +250,11 @@ static inline bool gdbmiChildToBool(const GdbMi &parent, const char *childName, // Parse extension command listing breakpoints. // Note that not all fields are returned, since file, line, function are encoded // in the expression (that is in addition deleted on resolving for a bp-type breakpoint). -void parseBreakPoint(const GdbMi &gdbmi, BreakpointResponse *r, +void parseBreakPoint(const GdbMi &gdbmi, BreakpointParameters *r, QString *expression /* = 0 */) { gdbmiChildToBool(gdbmi, "enabled", &(r->enabled)); gdbmiChildToBool(gdbmi, "deferred", &(r->pending)); - r->id = BreakpointResponseId(); - // Might not be valid if there is not id - r->id = cdbIdToBreakpointResponseId(gdbmi["id"]); const GdbMi moduleG = gdbmi["module"]; if (moduleG.isValid()) r->module = moduleG.data(); diff --git a/src/plugins/debugger/cdb/cdbparsehelpers.h b/src/plugins/debugger/cdb/cdbparsehelpers.h index f9973c4de8f..73b480b69e3 100644 --- a/src/plugins/debugger/cdb/cdbparsehelpers.h +++ b/src/plugins/debugger/cdb/cdbparsehelpers.h @@ -25,7 +25,7 @@ #pragma once -#include +#include #include @@ -36,7 +36,6 @@ QT_END_NAMESPACE namespace Debugger { namespace Internal { -class BreakpointData; class BreakpointParameters; struct ThreadData; class Register; @@ -54,19 +53,17 @@ QString cdbSourcePathMapping(QString fileName, enum { cdbBreakPointStartId = 100000, cdbBreakPointIdMinorPart = 100}; -int breakPointIdToCdbId(const BreakpointModelId &id); -BreakpointModelId cdbIdToBreakpointModelId(const GdbMi &id); -BreakpointResponseId cdbIdToBreakpointResponseId(const GdbMi &id); +int breakPointIdToCdbId(const Breakpoint &bp); // Convert breakpoint in CDB syntax (applying source path mappings using native paths). QString cdbAddBreakpointCommand(const BreakpointParameters &d, const QList > &sourcePathMapping, - BreakpointModelId id = BreakpointModelId(quint16(-1)), bool oneshot = false); -QString cdbClearBreakpointCommand(const BreakpointModelId &id); + const QString &responseId = QString(), bool oneshot = false); +QString cdbClearBreakpointCommand(const Breakpoint &bp); // Parse extension command listing breakpoints. // Note that not all fields are returned, since file, line, function are encoded // in the expression (that is in addition deleted on resolving for a bp-type breakpoint). -void parseBreakPoint(const GdbMi &gdbmi, BreakpointResponse *r, QString *expression = nullptr); +void parseBreakPoint(const GdbMi &gdbmi, BreakpointParameters *r, QString *expression = nullptr); // Write memory (f ...). QString cdbWriteMemoryCommand(quint64 addr, const QByteArray &data); diff --git a/src/plugins/debugger/debugger.pro b/src/plugins/debugger/debugger.pro index f5a34aa0d5f..7b7b97b2807 100644 --- a/src/plugins/debugger/debugger.pro +++ b/src/plugins/debugger/debugger.pro @@ -41,7 +41,6 @@ HEADERS += \ procinterrupt.h \ registerhandler.h \ snapshothandler.h \ - snapshotwindow.h \ sourceagent.h \ sourcefileshandler.h \ sourceutils.h \ @@ -89,7 +88,6 @@ SOURCES += \ procinterrupt.cpp \ registerhandler.cpp \ snapshothandler.cpp \ - snapshotwindow.cpp \ sourceagent.cpp \ sourcefileshandler.cpp \ sourceutils.cpp \ diff --git a/src/plugins/debugger/debugger.qbs b/src/plugins/debugger/debugger.qbs index 4f53499ee7f..674e3c7ff91 100644 --- a/src/plugins/debugger/debugger.qbs +++ b/src/plugins/debugger/debugger.qbs @@ -72,7 +72,6 @@ Project { "procinterrupt.cpp", "procinterrupt.h", "registerhandler.cpp", "registerhandler.h", "snapshothandler.cpp", "snapshothandler.h", - "snapshotwindow.cpp", "snapshotwindow.h", "sourceagent.cpp", "sourceagent.h", "sourcefileshandler.cpp", "sourcefileshandler.h", "sourceutils.cpp", "sourceutils.h", @@ -143,7 +142,6 @@ Project { prefix: "qml/" files: [ "interactiveinterpreter.cpp", "interactiveinterpreter.h", - "qmlcppengine.cpp", "qmlcppengine.h", "qmlengine.cpp", "qmlengine.h", "qmlengineutils.cpp", "qmlengineutils.h", "qmlinspectoragent.cpp", "qmlinspectoragent.h", diff --git a/src/plugins/debugger/debuggeractions.cpp b/src/plugins/debugger/debuggeractions.cpp index d148fc92028..cfb35146834 100644 --- a/src/plugins/debugger/debuggeractions.cpp +++ b/src/plugins/debugger/debuggeractions.cpp @@ -101,8 +101,12 @@ void GlobalDebuggerOptions::fromSettings() // ////////////////////////////////////////////////////////////////////////// +static DebuggerSettings *theDebuggerSettings = nullptr; + DebuggerSettings::DebuggerSettings() { + theDebuggerSettings = this; + const QString debugModeGroup = QLatin1String(debugModeSettingsGroupC); const QString cdbSettingsGroup = QLatin1String(cdbSettingsGroupC); @@ -580,6 +584,7 @@ DebuggerSettings::DebuggerSettings() item->setText(tr("Enable Reverse Debugging")); item->setCheckable(true); item->setDefaultValue(false); + item->setIcon(Icons::REVERSE_MODE.icon()); insertItem(EnableReverseDebugging, item); #ifdef Q_OS_WIN @@ -693,10 +698,10 @@ SavedAction *DebuggerSettings::item(int code) const return m_items.value(code, 0); } -QString DebuggerSettings::dump() const +QString DebuggerSettings::dump() { QStringList settings; - foreach (SavedAction *item, m_items) { + foreach (SavedAction *item, theDebuggerSettings->m_items) { QString key = item->settingsKey(); if (!key.isEmpty()) { const QString current = item->value().toString(); diff --git a/src/plugins/debugger/debuggeractions.h b/src/plugins/debugger/debuggeractions.h index 15f2e73d993..39d3571d45e 100644 --- a/src/plugins/debugger/debuggeractions.h +++ b/src/plugins/debugger/debuggeractions.h @@ -61,7 +61,7 @@ public: void insertItem(int code, Utils::SavedAction *item); Utils::SavedAction *item(int code) const; - QString dump() const; + static QString dump(); void readSettings(); void writeSettings() const; diff --git a/src/plugins/debugger/debuggerconstants.h b/src/plugins/debugger/debuggerconstants.h index f688662f653..f294555c7ae 100644 --- a/src/plugins/debugger/debuggerconstants.h +++ b/src/plugins/debugger/debuggerconstants.h @@ -38,8 +38,8 @@ const char C_DEBUGMODE[] = "Debugger.DebugMode"; const char C_CPPDEBUGGER[] = "Gdb Debugger"; const char C_QMLDEBUGGER[] = "Qml/JavaScript Debugger"; -const char CppPerspectiveId[] = "Debugger.Perspective.Cpp"; -const char QmlPerspectiveId[] = "Debugger.Perspective.Qml"; +const char PRESET_PERSPRECTIVE_ID[] = "Debugger.Perspective.Preset"; +const char PERSPECTIVE_ID[] = "Debugger.Perspective"; // Menu Groups const char G_GENERAL[] = "Debugger.Group.General"; diff --git a/src/plugins/debugger/debuggercore.h b/src/plugins/debugger/debuggercore.h index c9eb7c0f7e8..3fd58068a1a 100644 --- a/src/plugins/debugger/debuggercore.h +++ b/src/plugins/debugger/debuggercore.h @@ -27,6 +27,7 @@ #include "debuggerconstants.h" +#include #include #include @@ -43,7 +44,10 @@ QT_END_NAMESPACE namespace CPlusPlus { class Snapshot; } -namespace Utils { class SavedAction; } +namespace Utils { +class BaseTreeView; +class SavedAction; +} namespace Debugger { @@ -51,12 +55,9 @@ class DebuggerRunTool; namespace Internal { -class BreakHandler; -class DebuggerEngine; class Symbol; class Section; class GlobalDebuggerOptions; -class WatchTreeView; enum TestCases { @@ -65,34 +66,14 @@ enum TestCases }; // Some convenience. -void updateState(DebuggerRunTool *runTool); -void updateLocalsWindow(bool showReturn); -bool hasSnapshots(); void openTextEditor(const QString &titlePattern, const QString &contents); -// void runTest(const QString &fileName); -void showMessage(const QString &msg, int channel, int timeout = -1); - -bool isReverseDebugging(); -void runControlStarted(DebuggerRunTool *runTool); -void runControlFinished(DebuggerRunTool *runTool); -void displayDebugger(DebuggerRunTool *runTool); -void synchronizeBreakpoints(); - -void saveModeToRestore(); QWidget *mainWindow(); -void raiseWatchersWindow(); -bool isRegistersWindowVisible(); -bool isModulesWindowVisible(); void showModuleSymbols(const QString &moduleName, const QVector &symbols); void showModuleSections(const QString &moduleName, const QVector §ions); -void openMemoryEditor(); - -void setThreadBoxContents(const QStringList &list, int index); QSharedPointer globalDebuggerOptions(); -WatchTreeView *inspectorView(); QVariant sessionValue(const QByteArray &name); void setSessionValue(const QByteArray &name, const QVariant &value); QVariant configValue(const QString &name); @@ -105,9 +86,6 @@ bool boolSetting(int code); QString stringSetting(int code); QStringList stringListSetting(int code); -BreakHandler *breakHandler(); -DebuggerEngine *currentEngine(); - QMessageBox *showMessageBox(int icon, const QString &title, const QString &text, int buttons = 0); @@ -124,6 +102,7 @@ QAction *addCheckableAction(QMenu *menu, const QString &display, bool on, bool c QStringList qtBuildPaths(); void addDebugInfoTask(unsigned id, const QString &cmd); +QWidget *addSearch(Utils::BaseTreeView *treeView); } // namespace Internal } // namespace Debugger diff --git a/src/plugins/debugger/debuggerengine.cpp b/src/plugins/debugger/debuggerengine.cpp index c0618ee818c..028fa92211b 100644 --- a/src/plugins/debugger/debuggerengine.cpp +++ b/src/plugins/debugger/debuggerengine.cpp @@ -28,25 +28,34 @@ #include "debuggerinternalconstants.h" #include "debuggeractions.h" #include "debuggercore.h" +#include "debuggerdialogs.h" #include "debuggericons.h" #include "debuggerruncontrol.h" #include "debuggertooltipmanager.h" +#include "analyzer/analyzermanager.h" #include "breakhandler.h" #include "disassembleragent.h" +#include "localsandexpressionswindow.h" #include "logwindow.h" +#include "debuggermainwindow.h" #include "memoryagent.h" #include "moduleshandler.h" #include "registerhandler.h" #include "sourcefileshandler.h" #include "sourceutils.h" #include "stackhandler.h" +#include "stackwindow.h" +#include "snapshothandler.h" #include "terminal.h" #include "threadshandler.h" #include "watchhandler.h" +#include "watchutils.h" +#include "watchwindow.h" #include "debugger/shared/peutils.h" #include "console/console.h" +#include #include #include #include @@ -59,18 +68,29 @@ #include #include +#include +#include +#include #include #include #include #include #include #include +#include +#include +#include +#include #include -#include -#include #include +#include +#include +#include +#include +#include +#include #include #include @@ -113,6 +133,33 @@ QDebug operator<<(QDebug str, const DebuggerRunParameters &sp) namespace Internal { +static bool debuggerActionsEnabledHelper(DebuggerState state) +{ + switch (state) { + case InferiorRunOk: + case InferiorUnrunnable: + case InferiorStopOk: + return true; + case InferiorStopRequested: + case InferiorRunRequested: + case InferiorRunFailed: + case DebuggerNotReady: + case EngineSetupRequested: + case EngineSetupOk: + case EngineSetupFailed: + case EngineRunRequested: + case EngineRunFailed: + case InferiorStopFailed: + case InferiorShutdownRequested: + case InferiorShutdownFinished: + case EngineShutdownRequested: + case EngineShutdownFinished: + case DebuggerFinished: + return false; + } + return false; +} + Location::Location(const StackFrame &frame, bool marker) { m_fileName = frame.file; @@ -128,13 +175,22 @@ Location::Location(const StackFrame &frame, bool marker) LocationMark::LocationMark(DebuggerEngine *engine, const FileName &file, int line) : TextMark(file, line, Constants::TEXT_MARK_CATEGORY_LOCATION), m_engine(engine) { - setIcon(Icons::LOCATION.icon()); setPriority(TextMark::HighPriority); + updateIcon(); +} + +void LocationMark::updateIcon() +{ + const Icon *icon = &Icons::WATCHPOINT; + if (m_engine && EngineManager::currentEngine() == m_engine) + icon = m_engine->isReverseDebugging() ? &Icons::REVERSE_LOCATION : &Icons::LOCATION; + setIcon(icon->icon()); + updateMarker(); } bool LocationMark::isDraggable() const { - return m_engine->hasCapability(JumpToLineCapability); + return m_engine && m_engine->hasCapability(JumpToLineCapability); } void LocationMark::dragToLine(int line) @@ -213,41 +269,108 @@ class DebuggerEnginePrivate : public QObject public: DebuggerEnginePrivate(DebuggerEngine *engine) - : m_engine(engine), - m_modulesHandler(engine), - m_registerHandler(engine), - m_sourceFilesHandler(engine), - m_stackHandler(engine), - m_threadsHandler(engine), - m_watchHandler(engine), - m_disassemblerAgent(engine) + : m_engine(engine), + m_breakHandler(engine), + m_modulesHandler(engine), + m_registerHandler(engine), + m_sourceFilesHandler(engine), + m_stackHandler(engine), + m_threadsHandler(engine), + m_watchHandler(engine), + m_disassemblerAgent(engine) { - connect(&m_locationTimer, &QTimer::timeout, - this, &DebuggerEnginePrivate::resetLocation); + m_logWindow = new LogWindow(m_engine); // Needed before start() + m_logWindow->setObjectName(QLatin1String(DOCKWIDGET_OUTPUT)); + m_debuggerName = DebuggerEngine::tr("Debugger"); + + connect(action(EnableReverseDebugging), &SavedAction::valueChanged, + this, [this] { updateState(true); }); } - void doShutdownEngine(); - void doShutdownInferior(); + + ~DebuggerEnginePrivate() + { + destroyPerspective(); + + delete m_logWindow; + delete m_breakWindow; + delete m_returnWindow; + delete m_localsWindow; + delete m_watchersWindow; + delete m_inspectorWindow; + delete m_registerWindow; + delete m_modulesWindow; + delete m_sourceFilesWindow; + delete m_stackWindow; + delete m_threadsWindow; + + delete m_breakView; + delete m_returnView; + delete m_localsView; + delete m_watchersView; + delete m_inspectorView; + delete m_registerView; + delete m_modulesView; + delete m_sourceFilesView; + delete m_stackView; + delete m_threadsView; + } + + void setupViews(); + + void destroyPerspective() + { + if (!m_perspective) + return; + + EngineManager::unregisterEngine(m_engine); + + // Give up ownership on claimed breakpoints. + m_breakHandler.releaseAllBreakpoints(); + DebuggerToolTipManager::deregisterEngine(m_engine); + m_memoryAgents.handleDebuggerFinished(); + + m_perspective->destroy(); + m_perspective = nullptr; + + setBusyCursor(false); + } + + void updateReturnViewHeader(int section, int, int newSize) + { + if (m_perspective && m_returnView && m_returnView->header()) + m_returnView->header()->resizeSection(section, newSize); + } + + void doShutdownEngine() + { + m_engine->setState(EngineShutdownRequested); + m_engine->startDying(); + m_engine->showMessage("CALL: SHUTDOWN ENGINE"); + m_engine->shutdownEngine(); + } + + void doShutdownInferior() + { + m_engine->setState(InferiorShutdownRequested); + //QTC_ASSERT(isMasterEngine(), return); + resetLocation(); + m_engine->showMessage("CALL: SHUTDOWN INFERIOR"); + m_engine->shutdownInferior(); + } void doFinishDebugger() { - QTC_ASSERT(state() == EngineShutdownFinished, qDebug() << state()); - m_engine->setState(DebuggerFinished); + QTC_ASSERT(m_state == EngineShutdownFinished, qDebug() << m_state); resetLocation(); - if (isMasterEngine()) { - if (m_runTool) { - m_progress.setProgressValue(1000); - m_progress.reportFinished(); - m_modulesHandler.removeAll(); - m_stackHandler.removeAll(); - m_threadsHandler.removeAll(); - m_watchHandler.cleanup(); - Internal::runControlFinished(m_runTool); - m_runTool->reportStopped(); - m_runTool->appendMessage(tr("Debugging has finished"), NormalMessageFormat); - m_runTool.clear(); - } - } + m_progress.setProgressValue(1000); + m_progress.reportFinished(); + m_modulesHandler.removeAll(); + m_stackHandler.removeAll(); + m_threadsHandler.removeAll(); + m_watchHandler.cleanup(); + m_engine->showMessage(tr("Debugger finished."), StatusBar); + m_engine->setState(DebuggerFinished); // Also destroys views. } void scheduleResetLocation() @@ -272,13 +395,45 @@ public: DebuggerToolTipManager::resetLocation(); } + void selectThread(int index) + { + ThreadId id = m_engine->threadsHandler()->threadAt(index); + m_engine->selectThread(id); + } + + void handleOperateByInstructionTriggered(bool on) + { + // Go to source only if we have the file. + // if (DebuggerEngine *cppEngine = m_engine->cppEngine()) { + if (m_stackHandler.currentIndex() >= 0) { + const StackFrame frame = m_stackHandler.currentFrame(); + if (on || frame.isUsable()) + m_engine->gotoLocation(Location(frame, true)); + } + // } + } + + bool operatesByInstruction() const + { + return m_operateByInstructionAction.isChecked(); + } + public: - DebuggerState state() const { return m_state; } - bool isMasterEngine() const { return m_engine->isMasterEngine(); } + void setInitialActionStates(); + void setBusyCursor(bool on); + void cleanupViews(); + void updateState(bool alsoUpdateCompanion); + void updateReverseActions(); DebuggerEngine *m_engine = nullptr; // Not owned. - DebuggerEngine *m_masterEngine = nullptr; // Not owned - QPointer m_runTool; // Not owned. + QPointer m_runConfiguration; // Not owned. + QString m_debuggerName; + Perspective *m_perspective = nullptr; + DebuggerRunParameters m_runParameters; + IDevice::ConstPtr m_device; + + QPointer m_companionEngine; + bool m_isPrimaryEngine = true; // The current state. DebuggerState m_state = DebuggerNotReady; @@ -286,6 +441,7 @@ public: // Terminal m_terminal; ProcessHandle m_inferiorPid; + BreakHandler m_breakHandler; ModulesHandler m_modulesHandler; RegisterHandler m_registerHandler; SourceFilesHandler m_sourceFilesHandler; @@ -305,8 +461,330 @@ public: // Safety net to avoid infinite lookups. QSet m_lookupRequests; // FIXME: Integrate properly. QPointer m_alertBox; + + QPointer m_breakView; + QPointer m_returnView; + QPointer m_localsView; + QPointer m_watchersView; + QPointer m_inspectorView; + QPointer m_registerView; + QPointer m_modulesView; + QPointer m_sourceFilesView; + QPointer m_stackView; + QPointer m_threadsView; + QPointer m_breakWindow; + QPointer m_returnWindow; + QPointer m_localsWindow; + QPointer m_watchersWindow; + QPointer m_inspectorWindow; + QPointer m_registerWindow; + QPointer m_modulesWindow; + QPointer m_sourceFilesWindow; + QPointer m_stackWindow; + QPointer m_threadsWindow; + QPointer m_logWindow; + QPointer m_localsAndInspectorWindow; + + QPointer m_threadLabel; + QPointer m_threadBox; + + bool m_busy = false; + bool m_isDying = false; + + QAction m_debugWithoutDeployAction; + QAction m_attachToQmlPortAction; + QAction m_attachToRemoteServerAction; + QAction m_startRemoteCdbAction; + QAction m_attachToCoreAction; + QAction m_detachAction; + OptionalAction m_continueAction{tr("Continue")}; + QAction m_exitAction{tr("Stop Debugger")}; // On application output button if "Stop" is possible + OptionalAction m_interruptAction{tr("Interrupt")}; // On the fat debug button if "Pause" is possible + QAction m_abortAction{tr("Abort Debugging")}; + QAction m_stepIntoAction{tr("Step Into")}; + QAction m_stepOutAction{tr("Step Out")}; + QAction m_runToLineAction{tr("Run to Line")}; // In the debug menu + QAction m_runToSelectedFunctionAction{tr("Run to Selected Function")}; + QAction m_jumpToLineAction{tr("Jump to Line")}; + // In the Debug menu. + QAction m_returnFromFunctionAction{tr("Immediately Return From Inner Function")}; + QAction m_stepOverAction{tr("Step Over")}; + QAction m_watchAction{tr("Add Expression Evaluator")}; + QAction m_breakAction{tr("Toggle Breakpoint")}; + QAction m_resetAction{tr("Restart Debugging")}; + OptionalAction m_operateByInstructionAction{tr("Operate by Instruction")}; + QAction m_recordForReverseOperationAction{tr("Record information to allpow reversal of Direction")}; + OptionalAction m_operateInReverseDirectionAction{tr("Reverse Direction")}; + OptionalAction m_snapshotAction{tr("Take Snapshot of Process State")}; + + QPointer m_terminalRunner; }; +void DebuggerEnginePrivate::setupViews() +{ + const DebuggerRunParameters &rp = m_engine->runParameters(); + + QTC_CHECK(!m_perspective); + const QString id = Constants::PERSPECTIVE_ID + m_debuggerName + rp.displayName; + m_perspective = new Perspective(id, m_engine->displayName()); + + m_progress.setProgressRange(0, 1000); + FutureProgress *fp = ProgressManager::addTask(m_progress.future(), + tr("Launching Debugger"), "Debugger.Launcher"); + connect(fp, &FutureProgress::canceled, m_engine, &DebuggerEngine::quitDebugger); + fp->setKeepOnFinish(FutureProgress::HideOnFinish); + m_progress.reportStarted(); + + m_inferiorPid = rp.attachPID.isValid() ? rp.attachPID : ProcessHandle(); +// if (m_inferiorPid.isValid()) +// m_runControl->setApplicationProcessHandle(m_inferiorPid); + + m_operateByInstructionAction.setEnabled(true); + m_operateByInstructionAction.setVisible(m_engine->hasCapability(DisassemblerCapability)); + m_operateByInstructionAction.setIcon(Debugger::Icons::SINGLE_INSTRUCTION_MODE.icon()); + m_operateByInstructionAction.setCheckable(true); + m_operateByInstructionAction.setChecked(boolSetting(OperateByInstruction)); + connect(&m_operateByInstructionAction, &QAction::triggered, + this, &DebuggerEnginePrivate::handleOperateByInstructionTriggered); + + QTC_ASSERT(m_state == DebuggerNotReady || m_state == DebuggerFinished, qDebug() << m_state); + m_progress.setProgressValue(200); + +// m_terminal.setup(); +// if (m_terminal.isUsable()) { +// connect(&m_terminal, &Terminal::stdOutReady, [this](const QString &msg) { +// m_engine->showMessage(msg, Utils::StdOutFormatSameLine); +// }); +// connect(&m_terminal, &Terminal::stdErrReady, [this](const QString &msg) { +// m_engine->showMessage(msg, Utils::StdErrFormatSameLine); +// }); +// connect(&m_terminal, &Terminal::error, [this](const QString &msg) { +// m_engine->showMessage(msg, Utils::ErrorMessageFormat); +// }); +// } + + connect(&m_locationTimer, &QTimer::timeout, + this, &DebuggerEnginePrivate::resetLocation); + + QSettings *settings = ICore::settings(); + const QString perspectiveId = m_perspective->id(); + + m_modulesView = new BaseTreeView; + m_modulesView->setModel(m_modulesHandler.model()); + m_modulesView->setSortingEnabled(true); + m_modulesView->setSettings(settings, "Debugger.ModulesView"); + connect(m_modulesView, &BaseTreeView::aboutToShow, + m_engine, &DebuggerEngine::reloadModules, + Qt::QueuedConnection); + m_modulesWindow = addSearch(m_modulesView); + m_modulesWindow->setObjectName(DOCKWIDGET_MODULES + perspectiveId); + m_modulesWindow->setWindowTitle(tr("&Modules")); + + m_registerView = new BaseTreeView; + m_registerView->setModel(m_registerHandler.model()); + m_registerView->setRootIsDecorated(true); + m_registerView->setSettings(settings, "Debugger.RegisterView"); + connect(m_registerView, &BaseTreeView::aboutToShow, + m_engine, &DebuggerEngine::reloadRegisters, + Qt::QueuedConnection); + m_registerWindow = addSearch(m_registerView); + m_registerWindow->setObjectName(DOCKWIDGET_REGISTER + m_perspective->id()); + m_registerWindow->setWindowTitle(tr("Reg&isters")); + + m_stackView = new BaseTreeView; + m_stackView->setModel(m_stackHandler.model()); + m_stackView->setSettings(settings, "Debugger.StackView"); + m_stackView->setIconSize(QSize(10, 10)); + m_stackWindow = addSearch(m_stackView); + m_stackWindow->setObjectName(DOCKWIDGET_STACK + m_perspective->id()); + m_stackWindow->setWindowTitle(tr("&Stack")); + + m_sourceFilesView = new BaseTreeView; + m_sourceFilesView->setModel(m_sourceFilesHandler.model()); + m_sourceFilesView->setSortingEnabled(true); + m_sourceFilesView->setSettings(settings, "Debugger.SourceFilesView"); + connect(m_sourceFilesView, &BaseTreeView::aboutToShow, + m_engine, &DebuggerEngine::reloadSourceFiles, + Qt::QueuedConnection); + m_sourceFilesWindow = addSearch(m_sourceFilesView); + m_sourceFilesWindow->setObjectName(DOCKWIDGET_SOURCE_FILES + m_perspective->id()); + m_sourceFilesWindow->setWindowTitle(tr("Source Files")); + + m_threadsView = new BaseTreeView; + m_threadsView->setModel(m_threadsHandler.model()); + m_threadsView->setSortingEnabled(true); + m_threadsView->setSettings(settings, "Debugger.ThreadsView"); + m_threadsView->setIconSize(QSize(10, 10)); + m_threadsWindow = addSearch(m_threadsView); + m_threadsWindow->setObjectName(DOCKWIDGET_THREADS + m_perspective->id()); + m_threadsWindow->setWindowTitle(tr("&Threads")); + + m_returnView = new WatchTreeView{ReturnType}; + m_returnView->setModel(m_watchHandler.model()); + m_returnWindow = addSearch(m_returnView); + m_returnWindow->setObjectName("CppDebugReturn" + m_perspective->id()); + m_returnWindow->setWindowTitle(tr("Locals")); + m_returnWindow->setVisible(false); + + m_localsView = new WatchTreeView{LocalsType}; + m_localsView->setModel(m_watchHandler.model()); + m_localsView->setSettings(settings, "Debugger.LocalsView"); + m_localsWindow = addSearch(m_localsView); + m_localsWindow->setObjectName("CppDebugLocals" + m_perspective->id()); + m_localsWindow->setWindowTitle(tr("Locals")); + + m_inspectorView = new WatchTreeView{InspectType}; + m_inspectorView->setModel(m_watchHandler.model()); + m_inspectorView->setSettings(settings, "Debugger.LocalsView"); // sic! same as locals view. + m_inspectorWindow = addSearch(m_inspectorView); + m_inspectorWindow->setObjectName("Inspector" + m_perspective->id()); + m_inspectorWindow->setWindowTitle(tr("Locals")); + + m_watchersView = new WatchTreeView{WatchersType}; + m_watchersView->setModel(m_watchHandler.model()); + m_watchersView->setSettings(settings, "Debugger.WatchersView"); + m_watchersWindow = addSearch(m_watchersView); + m_watchersWindow->setObjectName("CppDebugWatchers" + m_perspective->id()); + m_watchersWindow->setWindowTitle(tr("&Expressions")); + + m_localsAndInspectorWindow = new LocalsAndInspectorWindow( + m_localsWindow, m_inspectorWindow, m_returnWindow); + m_localsAndInspectorWindow->setObjectName(DOCKWIDGET_LOCALS_AND_INSPECTOR + m_perspective->id()); + m_localsAndInspectorWindow->setWindowTitle(m_localsWindow->windowTitle()); + + // Locals + connect(m_localsView->header(), &QHeaderView::sectionResized, + this, &DebuggerEnginePrivate::updateReturnViewHeader, Qt::QueuedConnection); + + m_breakView = new BaseTreeView; + m_breakView->setIconSize(QSize(10, 10)); + m_breakView->setWindowIcon(Icons::BREAKPOINTS.icon()); + m_breakView->setSelectionMode(QAbstractItemView::ExtendedSelection); + connect(action(UseAddressInBreakpointsView), &QAction::toggled, + this, [this](bool on) { m_breakView->setColumnHidden(BreakpointAddressColumn, !on); }); + m_breakView->setSettings(settings, "Debugger.BreakWindow"); + m_breakView->setModel(m_breakHandler.model()); + m_breakView->setRootIsDecorated(true); + m_breakWindow = addSearch(m_breakView); + m_breakWindow->setObjectName(DOCKWIDGET_BREAK + m_perspective->id()); + m_breakWindow->setWindowTitle(tr("&Breakpoints")); + + m_perspective->addToolBarWidget(EngineManager::engineChooser()); + + m_perspective->addToolBarAction(&m_continueAction); + m_perspective->addToolBarAction(&m_interruptAction); + + m_perspective->addToolBarAction(&m_exitAction); + m_perspective->addToolBarAction(&m_stepOverAction); + m_perspective->addToolBarAction(&m_stepIntoAction); + m_perspective->addToolBarAction(&m_stepOutAction); + m_perspective->addToolBarAction(&m_resetAction); + m_perspective->addToolBarAction(&m_operateByInstructionAction); + + m_continueAction.setIcon(Icons::DEBUG_CONTINUE_SMALL_TOOLBAR.icon()); + connect(&m_continueAction, &QAction::triggered, + m_engine, &DebuggerEngine::handleExecContinue); + + m_exitAction.setIcon(Icons::DEBUG_EXIT_SMALL.icon()); + connect(&m_exitAction, &QAction::triggered, + m_engine, &DebuggerEngine::requestRunControlStop); + + m_interruptAction.setIcon(Icons::DEBUG_INTERRUPT_SMALL_TOOLBAR.icon()); + connect(&m_interruptAction, &QAction::triggered, + m_engine, &DebuggerEngine::handleExecInterrupt); + + m_abortAction.setToolTip(tr("Aborts debugging and resets the debugger to the initial state.")); + connect(&m_abortAction, &QAction::triggered, + m_engine, &DebuggerEngine::abortDebugger); + + m_resetAction.setToolTip(tr("Restart the debugging session.")); + m_resetAction.setIcon(Icons::RESTART_TOOLBAR.icon()); + connect(&m_resetAction, &QAction::triggered, + m_engine, &DebuggerEngine::handleReset); + + m_stepOverAction.setIcon(Icons::STEP_OVER_TOOLBAR.icon()); + connect(&m_stepOverAction, &QAction::triggered, + m_engine, &DebuggerEngine::handleExecNext); + + m_stepIntoAction.setIcon(Icons::STEP_INTO_TOOLBAR.icon()); + connect(&m_stepIntoAction, &QAction::triggered, + m_engine, &DebuggerEngine::handleExecStep); + + m_stepOutAction.setIcon(Icons::STEP_OUT_TOOLBAR.icon()); + connect(&m_stepOutAction, &QAction::triggered, + m_engine, &DebuggerEngine::handleExecStepOut); + + connect(&m_runToLineAction, &QAction::triggered, + m_engine, &DebuggerEngine::handleExecRunToLine); + + connect(&m_runToSelectedFunctionAction, &QAction::triggered, + m_engine, &DebuggerEngine::handleExecRunToSelectedFunction); + + connect(&m_returnFromFunctionAction, &QAction::triggered, + m_engine, &DebuggerEngine::handleExecReturn); + + connect(&m_jumpToLineAction, &QAction::triggered, + m_engine, &DebuggerEngine::handleExecJumpToLine); + + m_perspective->addToolBarAction(&m_recordForReverseOperationAction); + connect(&m_recordForReverseOperationAction, &QAction::triggered, + m_engine, &DebuggerEngine::handleRecordReverse); + + m_perspective->addToolBarAction(&m_operateInReverseDirectionAction); + connect(&m_operateInReverseDirectionAction, &QAction::triggered, + m_engine, &DebuggerEngine::handleReverseDirection); + + m_perspective->addToolBarAction(&m_snapshotAction); + connect(&m_snapshotAction, &QAction::triggered, + m_engine, &DebuggerEngine::createSnapshot); + + m_perspective->addToolbarSeparator(); + + m_threadLabel = new QLabel(tr("Threads:")); + m_perspective->addToolBarWidget(m_threadLabel); + + m_threadBox = new QComboBox; + m_threadBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); + connect(m_threadBox, static_cast(&QComboBox::activated), + this, &DebuggerEnginePrivate::selectThread); + + m_perspective->addToolBarWidget(m_threadBox); + + connect(TextEditorSettings::instance(), &TextEditorSettings::fontSettingsChanged, + this, [this](const FontSettings &settings) { + if (!boolSetting(FontSizeFollowsEditor)) + return; + const qreal size = settings.fontZoom() * settings.fontSize() / 100.; + QFont font = m_breakWindow->font(); + font.setPointSizeF(size); + m_breakWindow->setFont(font); + m_logWindow->setFont(font); + m_localsWindow->setFont(font); + m_modulesWindow->setFont(font); + //m_consoleWindow->setFont(font); + m_registerWindow->setFont(font); + m_returnWindow->setFont(font); + m_sourceFilesWindow->setFont(font); + m_stackWindow->setFont(font); + m_threadsWindow->setFont(font); + m_watchersWindow->setFont(font); + m_inspectorWindow->setFont(font); + }); + + m_perspective->setParentPerspective(Debugger::Constants::PRESET_PERSPRECTIVE_ID); + m_perspective->addWindow(m_stackWindow, Perspective::SplitVertical, nullptr); + m_perspective->addWindow(m_breakWindow, Perspective::SplitHorizontal, m_stackWindow); + m_perspective->addWindow(m_threadsWindow, Perspective::AddToTab, m_breakWindow,false); + m_perspective->addWindow(m_modulesWindow, Perspective::AddToTab, m_threadsWindow, false); + m_perspective->addWindow(m_sourceFilesWindow, Perspective::AddToTab, m_modulesWindow, false); + m_perspective->addWindow(m_localsAndInspectorWindow, Perspective::AddToTab, nullptr, true, Qt::RightDockWidgetArea); + m_perspective->addWindow(m_watchersWindow, Perspective::AddToTab, m_localsAndInspectorWindow, true, Qt::RightDockWidgetArea); + m_perspective->addWindow(m_registerWindow, Perspective::AddToTab, m_watchersWindow, true, Qt::RightDockWidgetArea); + m_perspective->addWindow(m_logWindow, Perspective::AddToTab, nullptr, false, Qt::TopDockWidgetArea); + + Debugger::registerPerspective(m_perspective); + m_perspective->select(); +} ////////////////////////////////////////////////////////////////////// // @@ -317,14 +795,20 @@ public: DebuggerEngine::DebuggerEngine() : d(new DebuggerEnginePrivate(this)) { + updateState(false); } DebuggerEngine::~DebuggerEngine() { - disconnect(); +// EngineManager::unregisterEngine(this); delete d; } +void DebuggerEngine::setDebuggerName(const QString &name) +{ + d->m_debuggerName = name; +} + QString DebuggerEngine::stateName(int s) { # define SN(x) case x: return QLatin1String(#x); @@ -357,6 +841,31 @@ void DebuggerEngine::showStatusMessage(const QString &msg, int timeout) const showMessage(msg, StatusBar, timeout); } +void DebuggerEngine::updateLocalsWindow(bool showReturn) +{ + d->m_returnWindow->setVisible(showReturn); + d->m_localsView->resizeColumns(); +} + +bool DebuggerEngine::isRegistersWindowVisible() const +{ + return d->m_registerWindow->isVisible(); +} + +bool DebuggerEngine::isModulesWindowVisible() const +{ + return d->m_modulesWindow->isVisible(); +} + +void DebuggerEngine::setThreadBoxContents(const QStringList &list, int index) +{ + QSignalBlocker blocker(d->m_threadBox); + d->m_threadBox->clear(); + for (const QString &item : list) + d->m_threadBox->addItem(item); + d->m_threadBox->setCurrentIndex(index); +} + void DebuggerEngine::frameUp() { int currentIndex = stackHandler()->currentIndex(); @@ -375,74 +884,47 @@ void DebuggerEngine::doUpdateLocals(const UpdateParameters &) ModulesHandler *DebuggerEngine::modulesHandler() const { - return d->m_masterEngine - ? d->m_masterEngine->modulesHandler() - : &d->m_modulesHandler; + return &d->m_modulesHandler; } RegisterHandler *DebuggerEngine::registerHandler() const { - return d->m_masterEngine - ? d->m_masterEngine->registerHandler() - : &d->m_registerHandler; + return &d->m_registerHandler; } StackHandler *DebuggerEngine::stackHandler() const { - return d->m_masterEngine - ? d->m_masterEngine->stackHandler() - : &d->m_stackHandler; + return &d->m_stackHandler; } ThreadsHandler *DebuggerEngine::threadsHandler() const { - return d->m_masterEngine - ? d->m_masterEngine->threadsHandler() - : &d->m_threadsHandler; + return &d->m_threadsHandler; } WatchHandler *DebuggerEngine::watchHandler() const { - return d->m_masterEngine - ? d->m_masterEngine->watchHandler() - : &d->m_watchHandler; + return &d->m_watchHandler; } SourceFilesHandler *DebuggerEngine::sourceFilesHandler() const { - return d->m_masterEngine - ? d->m_masterEngine->sourceFilesHandler() - : &d->m_sourceFilesHandler; + return &d->m_sourceFilesHandler; } -QAbstractItemModel *DebuggerEngine::modulesModel() const +BreakHandler *DebuggerEngine::breakHandler() const { - return modulesHandler()->model(); + return &d->m_breakHandler; } -QAbstractItemModel *DebuggerEngine::registerModel() const +LogWindow *DebuggerEngine::logWindow() const { - return registerHandler()->model(); + return d->m_logWindow; } -QAbstractItemModel *DebuggerEngine::stackModel() const +DisassemblerAgent *DebuggerEngine::disassemblerAgent() const { - return stackHandler()->model(); -} - -QAbstractItemModel *DebuggerEngine::threadsModel() const -{ - return threadsHandler()->model(); -} - -QAbstractItemModel *DebuggerEngine::watchModel() const -{ - return watchHandler()->model(); -} - -QAbstractItemModel *DebuggerEngine::sourceFilesModel() const -{ - return sourceFilesHandler()->model(); + return &d->m_disassemblerAgent; } void DebuggerEngine::fetchMemory(MemoryAgent *, quint64 addr, quint64 length) @@ -463,48 +945,30 @@ void DebuggerEngine::setRegisterValue(const QString &name, const QString &value) Q_UNUSED(value); } +void DebuggerEngine::setRunParameters(const DebuggerRunParameters &runParameters) +{ + d->m_runParameters = runParameters; +} + void DebuggerEngine::setRunTool(DebuggerRunTool *runTool) { - QTC_ASSERT(!d->m_runTool, notifyEngineSetupFailed(); return); - d->m_runTool = runTool; + RunControl *runControl = runTool->runControl(); + d->m_runConfiguration = runControl->runConfiguration(); + d->m_device = runControl->device(); + if (!d->m_device) + d->m_device = d->m_runParameters.inferior.device; + d->m_terminalRunner = runTool->terminalRunner(); + + validateExecutable(); + + d->setupViews(); } void DebuggerEngine::start() { - QTC_ASSERT(d->m_runTool, notifyEngineSetupFailed(); return); - - d->m_progress.setProgressRange(0, 1000); - FutureProgress *fp = ProgressManager::addTask(d->m_progress.future(), - tr("Launching Debugger"), "Debugger.Launcher"); - connect(fp, &FutureProgress::canceled, this, &DebuggerEngine::quitDebugger); - fp->setKeepOnFinish(FutureProgress::HideOnFinish); - d->m_progress.reportStarted(); - - const DebuggerRunParameters &rp = runParameters(); - d->m_inferiorPid = rp.attachPID.isValid() ? rp.attachPID : ProcessHandle(); - if (d->m_inferiorPid.isValid()) - d->m_runTool->runControl()->setApplicationProcessHandle(d->m_inferiorPid); - - action(OperateByInstruction)->setEnabled(hasCapability(DisassemblerCapability)); - action(OperateByInstruction)->setChecked(boolSetting(OperateByInstruction)); - - QTC_ASSERT(state() == DebuggerNotReady || state() == DebuggerFinished, - qDebug() << state()); - d->m_progress.setProgressValue(200); - -// d->m_terminal.setup(); -// if (d->m_terminal.isUsable()) { -// connect(&d->m_terminal, &Terminal::stdOutReady, [this](const QString &msg) { -// d->m_runTool->appendMessage(msg, Utils::StdOutFormatSameLine); -// }); -// connect(&d->m_terminal, &Terminal::stdErrReady, [this](const QString &msg) { -// d->m_runTool->appendMessage(msg, Utils::StdErrFormatSameLine); -// }); -// connect(&d->m_terminal, &Terminal::error, [this](const QString &msg) { -// d->m_runTool->appendMessage(msg, Utils::ErrorMessageFormat); -// }); -// } - + EngineManager::registerEngine(this); + d->m_watchHandler.resetWatchers(); + d->setInitialActionStates(); setState(EngineSetupRequested); showMessage("CALL: SETUP ENGINE"); setupEngine(); @@ -521,7 +985,7 @@ void DebuggerEngine::gotoLocation(const Location &loc) d->resetLocation(); if (loc.canBeDisassembled() - && ((hasCapability(OperateByInstructionCapability) && boolSetting(OperateByInstruction)) + && ((hasCapability(OperateByInstructionCapability) && d->operatesByInstruction()) || !loc.hasDebugInfo()) ) { d->m_disassemblerAgent.setLocation(loc); @@ -546,13 +1010,37 @@ void DebuggerEngine::gotoLocation(const Location &loc) if (newEditor) editor->document()->setProperty(Constants::OPENED_BY_DEBUGGER, true); - if (loc.needsMarker()) + if (loc.needsMarker()) { d->m_locationMark.reset(new LocationMark(this, FileName::fromString(file), line)); + d->m_locationMark->setToolTip(tr("Current debugger location of %1").arg(displayName())); + } +} + +void DebuggerEngine::gotoCurrentLocation() +{ + int top = stackHandler()->currentIndex(); + if (top >= 0) + gotoLocation(stackHandler()->currentFrame()); } const DebuggerRunParameters &DebuggerEngine::runParameters() const { - return runTool()->runParameters(); + return d->m_runParameters; +} + +DebuggerRunParameters &DebuggerEngine::mutableRunParameters() const +{ + return d->m_runParameters; +} + +IDevice::ConstPtr DebuggerEngine::device() const +{ + return d->m_device; +} + +DebuggerEngine *DebuggerEngine::companionEngine() const +{ + return d->m_companionEngine; } DebuggerState DebuggerEngine::state() const @@ -560,6 +1048,21 @@ DebuggerState DebuggerEngine::state() const return d->m_state; } +void DebuggerEngine::abortDebugger() +{ + resetLocation(); + if (!d->m_isDying) { + // Be friendly the first time. This will change targetState(). + showMessage("ABORTING DEBUGGER. FIRST TIME."); + quitDebugger(); + } else { + // We already tried. Try harder. + showMessage("ABORTING DEBUGGER. SECOND TIME."); + abortDebuggerProcess(); + emit requestRunControlFinish(); + } +} + static bool isAllowedTransition(DebuggerState from, DebuggerState to) { switch (from) { @@ -621,20 +1124,12 @@ static bool isAllowedTransition(DebuggerState from, DebuggerState to) return false; } -void DebuggerEngine::setupSlaveEngine() -{ - QTC_CHECK(state() == DebuggerNotReady); - setState(EngineSetupRequested); - showMessage("CALL: SETUP SLAVE ENGINE"); - setupEngine(); -} - void DebuggerEngine::notifyEngineSetupFailed() { showMessage("NOTE: ENGINE SETUP FAILED"); QTC_ASSERT(state() == EngineSetupRequested, qDebug() << this << state()); setState(EngineSetupFailed); - if (isMasterEngine() && runTool()) { + if (d->m_isPrimaryEngine) { showMessage(tr("Debugging has failed"), NormalMessageFormat); d->m_progress.setProgressValue(900); d->m_progress.reportCanceled(); @@ -653,28 +1148,13 @@ void DebuggerEngine::notifyEngineSetupOk() d->m_progress.setProgressValue(250); QTC_ASSERT(state() == EngineSetupRequested, qDebug() << this << state()); setState(EngineSetupOk); - if (isMasterEngine()) { - if (runTool()) - runTool()->reportStarted(); - // Slaves will get called setupSlaveInferior() below. - setState(EngineRunRequested); - showMessage("CALL: RUN ENGINE"); - d->m_progress.setProgressValue(300); - runEngine(); - } -} - -void DebuggerEngine::runSlaveEngine() -{ - QTC_ASSERT(isSlaveEngine(), return); - QTC_CHECK(state() == EngineSetupOk); + // Slaves will get called setupSlaveInferior() below. setState(EngineRunRequested); - showMessage("CALL: RUN SLAVE ENGINE"); + showMessage("CALL: RUN ENGINE"); d->m_progress.setProgressValue(300); runEngine(); } - void DebuggerEngine::notifyEngineRunOkAndInferiorUnrunnable() { showMessage("NOTE: INFERIOR UNRUNNABLE"); @@ -769,7 +1249,7 @@ void DebuggerEngine::notifyInferiorStopOk() return; } QTC_ASSERT(state() == InferiorStopRequested, qDebug() << this << state()); - showStatusMessage(tr("Stopped.")); + showMessage(tr("Stopped."), StatusBar); setState(InferiorStopOk); } @@ -777,10 +1257,11 @@ void DebuggerEngine::notifyInferiorSpontaneousStop() { showMessage("NOTE: INFERIOR SPONTANEOUS STOP"); QTC_ASSERT(state() == InferiorRunOk, qDebug() << this << state()); - showStatusMessage(tr("Stopped.")); + showMessage(tr("Stopped."), StatusBar); setState(InferiorStopOk); if (boolSetting(RaiseOnInterrupt)) ICore::raiseWindow(Internal::mainWindow()); + EngineManager::activateEngine(this); } void DebuggerEngine::notifyInferiorStopFailed() @@ -791,13 +1272,277 @@ void DebuggerEngine::notifyInferiorStopFailed() d->doShutdownEngine(); } -void DebuggerEnginePrivate::doShutdownInferior() +void DebuggerEnginePrivate::setInitialActionStates() { - m_engine->setState(InferiorShutdownRequested); - //QTC_ASSERT(isMasterEngine(), return); - resetLocation(); - m_engine->showMessage("CALL: SHUTDOWN INFERIOR"); - m_engine->shutdownInferior(); + m_returnWindow->setVisible(false); + setBusyCursor(false); + + m_recordForReverseOperationAction.setCheckable(true); + m_recordForReverseOperationAction.setChecked(false); + m_recordForReverseOperationAction.setIcon(Icons::RECORD_OFF.icon()); + m_recordForReverseOperationAction.setToolTip(tr( + "

Record information to enable stepping backwards.

" + "Note: This feature is very slow and unstable on the GDB side. " + "It exhibits unpredictable behavior when going backwards over system " + "calls and is very likely to destroy your debugging session.

")); + + m_operateInReverseDirectionAction.setCheckable(true); + m_operateInReverseDirectionAction.setChecked(false); + m_operateInReverseDirectionAction.setIcon(Icons::DIRECTION_FORWARD.icon()); + + m_snapshotAction.setIcon(Utils::Icons::SNAPSHOT_TOOLBAR.icon()); + + m_attachToQmlPortAction.setEnabled(true); + m_attachToCoreAction.setEnabled(true); + m_attachToRemoteServerAction.setEnabled(true); + m_detachAction.setEnabled(false); + + m_watchAction.setEnabled(true); + m_breakAction.setEnabled(false); + m_snapshotAction.setEnabled(false); + m_operateByInstructionAction.setEnabled(false); + + m_exitAction.setEnabled(false); + m_abortAction.setEnabled(false); + m_resetAction.setEnabled(false); + + m_interruptAction.setEnabled(false); + m_continueAction.setEnabled(false); + + m_stepIntoAction.setEnabled(true); + m_stepOutAction.setEnabled(false); + m_runToLineAction.setEnabled(false); + m_runToSelectedFunctionAction.setEnabled(true); + m_returnFromFunctionAction.setEnabled(false); + m_jumpToLineAction.setEnabled(false); + m_stepOverAction.setEnabled(true); + + action(AutoDerefPointers)->setEnabled(true); + action(ExpandStack)->setEnabled(false); + + m_threadLabel->setEnabled(false); +} + +void DebuggerEnginePrivate::updateState(bool alsoUpdateCompanion) +{ + if (!m_perspective) + return; + QTC_ASSERT(m_threadBox, return); + m_threadBox->setCurrentIndex(m_threadsHandler.currentThreadIndex()); + + const DebuggerState state = m_state; + const bool companionPreventsAction = m_engine->companionPreventsActions(); + + // Fixme: hint tr("Debugger is Busy"); + // Exactly one of m_interuptAction and m_continueAction should be + // visible, possibly disabled. + if (state == DebuggerNotReady) { + // Happens when companion starts, otherwise this should not happen. + QTC_CHECK(m_companionEngine); + m_interruptAction.setVisible(true); + m_interruptAction.setEnabled(false); + m_continueAction.setVisible(false); + m_continueAction.setEnabled(false); + m_stepOverAction.setEnabled(true); + m_stepIntoAction.setEnabled(true); + m_stepOutAction.setEnabled(false); + m_exitAction.setEnabled(false); + m_debugWithoutDeployAction.setEnabled(true); + } else if (state == InferiorStopOk) { + // F5 continues, Shift-F5 kills. It is "continuable". + m_interruptAction.setVisible(false); + m_interruptAction.setEnabled(false); + m_continueAction.setVisible(true); + m_continueAction.setEnabled(!companionPreventsAction); + m_stepOverAction.setEnabled(!companionPreventsAction); + m_stepIntoAction.setEnabled(!companionPreventsAction); + m_stepOutAction.setEnabled(!companionPreventsAction); + m_exitAction.setEnabled(true); + m_debugWithoutDeployAction.setEnabled(false); + m_localsAndInspectorWindow->setShowLocals(true); + } else if (state == InferiorRunOk) { + // Shift-F5 interrupts. It is also "interruptible". + m_interruptAction.setVisible(true); + m_interruptAction.setEnabled(!companionPreventsAction); + m_continueAction.setVisible(false); + m_continueAction.setEnabled(false); + m_stepOverAction.setEnabled(false); + m_stepIntoAction.setEnabled(false); + m_stepOutAction.setEnabled(false); + m_exitAction.setEnabled(true); + m_debugWithoutDeployAction.setEnabled(false); + m_localsAndInspectorWindow->setShowLocals(false); + } else if (state == DebuggerFinished) { + const bool canRun = ProjectExplorerPlugin::canRunStartupProject(ProjectExplorer::Constants::DEBUG_RUN_MODE); + // We don't want to do anything anymore. + m_interruptAction.setVisible(true); + m_interruptAction.setEnabled(false); + m_continueAction.setVisible(false); + m_continueAction.setEnabled(false); + m_stepOverAction.setEnabled(false); + m_stepIntoAction.setEnabled(false); + m_stepOutAction.setEnabled(false); + m_exitAction.setEnabled(false); + m_debugWithoutDeployAction.setEnabled(canRun); + setBusyCursor(false); + cleanupViews(); + } else if (state == InferiorUnrunnable) { + // We don't want to do anything anymore. + m_interruptAction.setVisible(true); + m_interruptAction.setEnabled(false); + m_continueAction.setVisible(false); + m_continueAction.setEnabled(false); + m_stepOverAction.setEnabled(false); + m_stepIntoAction.setEnabled(false); + m_stepOutAction.setEnabled(false); + m_exitAction.setEnabled(true); + m_debugWithoutDeployAction.setEnabled(false); + // show locals in core dumps + m_localsAndInspectorWindow->setShowLocals(true); + } else { + // Everything else is "undisturbable". + m_interruptAction.setVisible(true); + m_interruptAction.setEnabled(false); + m_continueAction.setVisible(false); + m_continueAction.setEnabled(false); + m_stepOverAction.setEnabled(false); + m_stepIntoAction.setEnabled(false); + m_stepOutAction.setEnabled(false); + m_exitAction.setEnabled(false); + m_debugWithoutDeployAction.setEnabled(false); + } + + m_attachToQmlPortAction.setEnabled(true); + m_attachToCoreAction.setEnabled(true); + m_attachToRemoteServerAction.setEnabled(true); + + m_threadBox->setEnabled(state == InferiorStopOk || state == InferiorUnrunnable); + m_threadLabel->setEnabled(m_threadBox->isEnabled()); + + const bool isCore = m_engine->runParameters().startMode == AttachCore; + const bool stopped = state == InferiorStopOk; + const bool detachable = stopped && !isCore; + m_detachAction.setEnabled(detachable); + + if (stopped) + QApplication::alert(mainWindow(), 3000); + + updateReverseActions(); + + const bool canSnapshot = m_engine->hasCapability(SnapshotCapability); + m_snapshotAction.setVisible(canSnapshot); + m_snapshotAction.setEnabled(stopped && !isCore); + + m_watchAction.setEnabled(true); + m_breakAction.setEnabled(true); + + const bool canOperateByInstruction = m_engine->hasCapability(OperateByInstructionCapability); + m_operateByInstructionAction.setVisible(canOperateByInstruction); + m_operateByInstructionAction.setEnabled(canOperateByInstruction && (stopped || isCore)); + + m_abortAction.setEnabled(state != DebuggerNotReady + && state != DebuggerFinished); + m_resetAction.setEnabled((stopped || state == DebuggerNotReady) + && m_engine->hasCapability(ResetInferiorCapability)); + + m_stepIntoAction.setEnabled(stopped || state == DebuggerNotReady); + m_stepIntoAction.setToolTip(QString()); + + m_stepOverAction.setEnabled(stopped || state == DebuggerNotReady); + m_stepOverAction.setToolTip(QString()); + + m_stepOutAction.setEnabled(stopped); + m_runToLineAction.setEnabled(stopped && m_engine->hasCapability(RunToLineCapability)); + m_runToSelectedFunctionAction.setEnabled(stopped); + m_returnFromFunctionAction. + setEnabled(stopped && m_engine->hasCapability(ReturnFromFunctionCapability)); + + const bool canJump = stopped && m_engine->hasCapability(JumpToLineCapability); + m_jumpToLineAction.setEnabled(canJump); + + const bool actionsEnabled = m_engine->debuggerActionsEnabled(); + const bool canDeref = actionsEnabled && m_engine->hasCapability(AutoDerefPointersCapability); + action(AutoDerefPointers)->setEnabled(canDeref); + action(AutoDerefPointers)->setEnabled(true); + action(ExpandStack)->setEnabled(actionsEnabled); + + const bool notbusy = state == InferiorStopOk + || state == DebuggerNotReady + || state == DebuggerFinished + || state == InferiorUnrunnable; + setBusyCursor(!notbusy); + + if (alsoUpdateCompanion && m_companionEngine) + m_companionEngine->updateState(false); +} + +void DebuggerEnginePrivate::updateReverseActions() +{ + const bool stopped = m_state == InferiorStopOk; + const bool reverseEnabled = boolSetting(EnableReverseDebugging); + const bool canReverse = reverseEnabled && m_engine->hasCapability(ReverseSteppingCapability); + const bool doesRecord = m_recordForReverseOperationAction.isChecked(); + + m_recordForReverseOperationAction.setVisible(canReverse); + m_recordForReverseOperationAction.setEnabled(canReverse && stopped); + m_recordForReverseOperationAction.setIcon(doesRecord + ? Icons::RECORD_ON.icon() + : Icons::RECORD_OFF.icon()); + + m_operateInReverseDirectionAction.setVisible(canReverse); + m_operateInReverseDirectionAction.setEnabled(canReverse && stopped && doesRecord); + m_operateInReverseDirectionAction.setIcon(Icons::DIRECTION_BACKWARD.icon()); + m_operateInReverseDirectionAction.setText(DebuggerEngine::tr("Operate in reverse direction")); +} + +void DebuggerEnginePrivate::cleanupViews() +{ + const bool closeSource = boolSetting(CloseSourceBuffersOnExit); + const bool closeMemory = boolSetting(CloseMemoryBuffersOnExit); + + QList toClose; + foreach (IDocument *document, DocumentModel::openedDocuments()) { + const bool isMemory = document->property(Constants::OPENED_WITH_DISASSEMBLY).toBool(); + if (document->property(Constants::OPENED_BY_DEBUGGER).toBool()) { + bool keepIt = true; + if (document->isModified()) + keepIt = true; + else if (document->filePath().toString().contains("qeventdispatcher")) + keepIt = false; + else if (isMemory) + keepIt = !closeMemory; + else + keepIt = !closeSource; + + if (keepIt) + document->setProperty(Constants::OPENED_BY_DEBUGGER, false); + else + toClose.append(document); + } + } + EditorManager::closeDocuments(toClose); +} + +void DebuggerEnginePrivate::setBusyCursor(bool busy) +{ + //STATE_DEBUG("BUSY FROM: " << m_busy << " TO: " << busy); + if (m_isDying) + return; + if (busy == m_busy) + return; + m_busy = busy; + const QCursor cursor(busy ? Qt::BusyCursor : Qt::ArrowCursor); + m_breakWindow->setCursor(cursor); + //m_consoleWindow->setCursor(cursor); + m_localsWindow->setCursor(cursor); + m_modulesWindow->setCursor(cursor); + m_logWindow->setCursor(cursor); + m_registerWindow->setCursor(cursor); + m_returnWindow->setCursor(cursor); + m_sourceFilesWindow->setCursor(cursor); + m_stackWindow->setCursor(cursor); + m_threadsWindow->setCursor(cursor); + m_watchersWindow->setCursor(cursor); } void DebuggerEngine::notifyInferiorShutdownFinished() @@ -813,7 +1558,7 @@ void DebuggerEngine::notifyInferiorIll() showMessage("NOTE: INFERIOR ILL"); // This can be issued in almost any state. The inferior could still be // alive as some previous notifications might have been bogus. - runTool()->startDying(); + startDying(); if (state() == InferiorRunRequested) { // We asked for running, but did not see a response. // Assume the inferior is dead. @@ -824,25 +1569,6 @@ void DebuggerEngine::notifyInferiorIll() d->doShutdownInferior(); } -void DebuggerEngine::shutdownSlaveEngine() -{ - QTC_CHECK(isAllowedTransition(state(),EngineShutdownRequested)); - setState(EngineShutdownRequested); - shutdownEngine(); -} - -void DebuggerEnginePrivate::doShutdownEngine() -{ - // Slaves do not proceed by themselves. - if (!isMasterEngine()) - return; - m_engine->setState(EngineShutdownRequested); - QTC_ASSERT(m_runTool, return); - m_runTool->startDying(); - m_engine->showMessage("CALL: SHUTDOWN ENGINE"); - m_engine->shutdownEngine(); -} - void DebuggerEngine::notifyEngineShutdownFinished() { showMessage("NOTE: ENGINE SHUTDOWN FINISHED"); @@ -858,7 +1584,7 @@ void DebuggerEngine::notifyEngineIll() // CALLGRIND_DUMP_STATS; //#endif showMessage("NOTE: ENGINE ILL ******"); - runTool()->startDying(); + startDying(); switch (state()) { case InferiorRunRequested: case InferiorRunOk: @@ -891,8 +1617,7 @@ void DebuggerEngine::notifyEngineSpontaneousShutdown() #endif showMessage("NOTE: ENGINE SPONTANEOUS SHUTDOWN"); setState(EngineShutdownFinished, true); - if (isMasterEngine()) - d->doFinishDebugger(); + d->doFinishDebugger(); } void DebuggerEngine::notifyInferiorExited() @@ -907,6 +1632,53 @@ void DebuggerEngine::notifyInferiorExited() d->doShutdownEngine(); } +void DebuggerEngine::updateState(bool alsoUpdateCompanion) +{ + d->updateState(alsoUpdateCompanion); +} + +WatchTreeView *DebuggerEngine::inspectorView() +{ + return d->m_inspectorView; +} + +void DebuggerEngine::showMessage(const QString &msg, int channel, int timeout) const +{ + //qDebug() << "PLUGIN OUTPUT: " << channel << msg; + QTC_ASSERT(d->m_logWindow, qDebug() << "MSG: " << msg; return); + switch (channel) { + case StatusBar: + d->m_logWindow->showInput(LogMisc, msg); + d->m_logWindow->showOutput(LogMisc, msg); + Debugger::showStatusMessage(msg, timeout); + break; + case LogMiscInput: + d->m_logWindow->showInput(LogMisc, msg); + d->m_logWindow->showOutput(LogMisc, msg); + break; + case LogInput: + d->m_logWindow->showInput(LogInput, msg); + d->m_logWindow->showOutput(LogInput, msg); + break; + case LogError: + d->m_logWindow->showInput(LogError, QLatin1String("ERROR: ") + msg); + d->m_logWindow->showOutput(LogError, QLatin1String("ERROR: ") + msg); + break; + case AppOutput: + case AppStuff: + d->m_logWindow->showOutput(channel, msg); + emit appendMessageRequested(msg, StdOutFormatSameLine, false); + break; + case AppError: + d->m_logWindow->showOutput(channel, msg); + emit appendMessageRequested(msg, StdErrFormatSameLine, false); + break; + default: + d->m_logWindow->showOutput(channel, msg); + break; + } +} + void DebuggerEngine::notifyDebuggerProcessFinished(int exitCode, QProcess::ExitStatus exitStatus, const QString &backendName) { @@ -930,7 +1702,7 @@ void DebuggerEngine::notifyDebuggerProcessFinished(int exitCode, break; default: { // Initiate shutdown sequence - masterEngine()->notifyInferiorIll(); + notifyInferiorIll(); const QString msg = exitStatus == QProcess::CrashExit ? tr("The %1 process terminated.") : tr("The %2 process terminated unexpectedly (exit code %1).").arg(exitCode); @@ -941,15 +1713,7 @@ void DebuggerEngine::notifyDebuggerProcessFinished(int exitCode, } } -void DebuggerEngine::slaveEngineStateChanged(DebuggerEngine *slaveEngine, - DebuggerState state) -{ - Q_UNUSED(slaveEngine); - Q_UNUSED(state); -} - -static inline QString msgStateChanged(DebuggerState oldState, DebuggerState newState, - bool forced, bool master) +static QString msgStateChanged(DebuggerState oldState, DebuggerState newState, bool forced) { QString result; QTextStream str(&result); @@ -958,14 +1722,12 @@ static inline QString msgStateChanged(DebuggerState oldState, DebuggerState newS str << " BY FORCE"; str << " from " << DebuggerEngine::stateName(oldState) << '(' << oldState << ") to " << DebuggerEngine::stateName(newState) << '(' << newState << ')'; - if (master) - str << " [master]"; return result; } void DebuggerEngine::setState(DebuggerState state, bool forced) { - const QString msg = msgStateChanged(d->m_state, state, forced, isMasterEngine()); + const QString msg = msgStateChanged(d->m_state, state, forced); DebuggerState oldState = d->m_state; d->m_state = state; @@ -975,49 +1737,25 @@ void DebuggerEngine::setState(DebuggerState state, bool forced) if (state == EngineRunRequested) { DebuggerToolTipManager::registerEngine(this); - } - - if (state == DebuggerFinished) { - // Give up ownership on claimed breakpoints. - for (Breakpoint bp : breakHandler()->engineBreakpoints(this)) - bp.notifyBreakpointReleased(); - DebuggerToolTipManager::deregisterEngine(this); - d->m_memoryAgents.handleDebuggerFinished(); - prepareForRestart(); + emit engineStarted(); } showMessage(msg, LogDebug); - updateViews(); - if (isSlaveEngine()) - masterEngine()->slaveEngineStateChanged(this, state); + d->updateState(true); + + if (oldState != d->m_state) + emit EngineManager::instance()->engineStateChanged(this); + + if (state == DebuggerFinished) { + d->destroyPerspective(); + emit engineFinished(); + } } -void DebuggerEngine::updateViews() +bool DebuggerEngine::isPrimaryEngine() const { - // The slave engines are not entitled to change the view. Their wishes - // should be coordinated by their master engine. - Internal::updateState(runTool()); -} - -bool DebuggerEngine::isSlaveEngine() const -{ - return d->m_masterEngine != nullptr; -} - -bool DebuggerEngine::isMasterEngine() const -{ - return d->m_masterEngine == nullptr; -} - -void DebuggerEngine::setMasterEngine(DebuggerEngine *masterEngine) -{ - d->m_masterEngine = masterEngine; -} - -DebuggerEngine *DebuggerEngine::masterEngine() -{ - return d->m_masterEngine ? d->m_masterEngine : this; + return d->m_isPrimaryEngine; } bool DebuggerEngine::canDisplayTooltip() const @@ -1037,11 +1775,6 @@ QString DebuggerEngine::toFileInProject(const QUrl &fileUrl) return d->m_fileFinder.findFile(fileUrl); } -void DebuggerEngine::removeBreakpointMarker(const Breakpoint &bp) -{ - d->m_disassemblerAgent.removeBreakpointMarker(bp); -} - QString DebuggerEngine::expand(const QString &string) const { return runParameters().macroExpander->expand(string); @@ -1053,40 +1786,26 @@ QString DebuggerEngine::nativeStartupCommands() const runParameters().additionalStartupCommands}).join('\n')); } -void DebuggerEngine::updateBreakpointMarker(const Breakpoint &bp) +Perspective *DebuggerEngine::perspective() const { - d->m_disassemblerAgent.updateBreakpointMarker(bp); + return d->m_perspective; +} + +void DebuggerEngine::updateMarkers() +{ + if (d->m_locationMark) + d->m_locationMark->updateIcon(); + + d->m_disassemblerAgent.updateLocationMarker(); } bool DebuggerEngine::debuggerActionsEnabled() const { - return debuggerActionsEnabled(d->m_state); + return debuggerActionsEnabledHelper(d->m_state); } -bool DebuggerEngine::debuggerActionsEnabled(DebuggerState state) +bool DebuggerEngine::companionPreventsActions() const { - switch (state) { - case InferiorRunOk: - case InferiorUnrunnable: - case InferiorStopOk: - return true; - case InferiorStopRequested: - case InferiorRunRequested: - case InferiorRunFailed: - case DebuggerNotReady: - case EngineSetupRequested: - case EngineSetupOk: - case EngineSetupFailed: - case EngineRunRequested: - case EngineRunFailed: - case InferiorStopFailed: - case InferiorShutdownRequested: - case InferiorShutdownFinished: - case EngineShutdownRequested: - case EngineShutdownFinished: - case DebuggerFinished: - return false; - } return false; } @@ -1096,7 +1815,6 @@ void DebuggerEngine::notifyInferiorPid(const ProcessHandle &pid) return; d->m_inferiorPid = pid; if (pid.isValid()) { - d->m_runTool->runControl()->setApplicationProcessHandle(pid); showMessage(tr("Taking notice of pid %1").arg(pid.pid())); DebuggerStartMode sm = runParameters().startMode; if (sm == StartInternal || sm == StartExternal || sm == AttachExternal) @@ -1111,21 +1829,30 @@ qint64 DebuggerEngine::inferiorPid() const bool DebuggerEngine::isReverseDebugging() const { - return Internal::isReverseDebugging(); + return d->m_operateInReverseDirectionAction.isChecked(); } -void DebuggerEngine::showMessage(const QString &msg, int channel, int timeout) const +void DebuggerEngine::handleBeginOfRecordingReached() { - if (DebuggerRunTool *tool = runTool()) - tool->showMessage(msg, channel, timeout); + showStatusMessage(tr("Reverse-execution history exhausted. Going forward again.")); + d->m_operateInReverseDirectionAction.setChecked(false); + d->updateReverseActions(); +} + +void DebuggerEngine::handleRecordingFailed() +{ + showStatusMessage(tr("Reverse-execution recording failed..")); + d->m_operateInReverseDirectionAction.setChecked(false); + d->m_recordForReverseOperationAction.setChecked(false); + d->updateReverseActions(); + executeRecordReverse(false); } // Called by DebuggerRunControl. void DebuggerEngine::quitDebugger() { showMessage(QString("QUIT DEBUGGER REQUESTED IN STATE %1").arg(state())); - QTC_ASSERT(runTool(), return); - runTool()->startDying(); + startDying(); switch (state()) { case InferiorStopOk: case InferiorStopFailed: @@ -1134,7 +1861,7 @@ void DebuggerEngine::quitDebugger() break; case InferiorRunOk: setState(InferiorStopRequested); - showStatusMessage(tr("Attempting to interrupt.")); + showMessage(tr("Attempting to interrupt."), StatusBar); interruptInferior(); break; case EngineSetupRequested: @@ -1162,11 +1889,10 @@ void DebuggerEngine::quitDebugger() void DebuggerEngine::requestInterruptInferior() { - QTC_CHECK(isMasterEngine()); QTC_ASSERT(state() == InferiorRunOk, qDebug() << this << state()); setState(InferiorStopRequested); showMessage("CALL: INTERRUPT INFERIOR"); - showStatusMessage(tr("Attempting to interrupt.")); + showMessage(tr("Attempting to interrupt."), StatusBar); interruptInferior(); } @@ -1176,15 +1902,24 @@ void DebuggerEngine::progressPing() d->m_progress.setProgressValue(progress); } -DebuggerRunTool *DebuggerEngine::runTool() const +bool DebuggerEngine::isStartupRunConfiguration() const { - return d->m_runTool.data(); + return d->m_runConfiguration == RunConfiguration::startupRunConfiguration(); +} + +void DebuggerEngine::setCompanionEngine(DebuggerEngine *engine) +{ + d->m_companionEngine = engine; +} + +void DebuggerEngine::setSecondaryEngine() +{ + d->m_isPrimaryEngine = false; } TerminalRunner *DebuggerEngine::terminal() const { - QTC_ASSERT(d->m_runTool, return nullptr); - return d->m_runTool->terminalRunner(); + return d->m_terminalRunner; } void DebuggerEngine::selectWatchData(const QString &) @@ -1199,7 +1934,7 @@ void DebuggerEngine::watchPoint(const QPoint &pnt) cmd.callback = [this](const DebuggerResponse &response) { qulonglong addr = response.data["selected"].toAddress(); if (addr == 0) - showStatusMessage(tr("Could not find a widget.")); + showMessage(tr("Could not find a widget."), StatusBar); // Add the watcher entry nevertheless, as that's the place where // the user expects visual feedback. watchHandler()->watchExpression(response.data["expr"].data(), QString(), true); @@ -1297,112 +2032,42 @@ void DebuggerEngine::updateAll() { } -#if 0 - // FIXME: Remove explicit use of BreakpointData - if (!bp->engine && acceptsBreakpoint(id)) { - QTC_CHECK(state == BreakpointNew); - // Take ownership of the breakpoint. - bp->engine = this; - } -#endif - -void DebuggerEngine::attemptBreakpointSynchronization() +QString DebuggerEngine::displayName() const { - showMessage("ATTEMPT BREAKPOINT SYNCHRONIZATION"); - if (!stateAcceptsBreakpointChanges()) { - showMessage("BREAKPOINT SYNCHRONIZATION NOT POSSIBLE IN CURRENT STATE"); - return; - } - - BreakHandler *handler = breakHandler(); - - for (Breakpoint bp : handler->unclaimedBreakpoints()) { - // Take ownership of the breakpoint. Requests insertion. - if (acceptsBreakpoint(bp)) { - showMessage(QString("TAKING OWNERSHIP OF BREAKPOINT %1 IN STATE %2") - .arg(bp.id().toString()).arg(bp.state())); - bp.setEngine(this); - } else { - showMessage(QString("BREAKPOINT %1 IN STATE %2 IS NOT ACCEPTABLE") - .arg(bp.id().toString()).arg(bp.state())); - } - } - - bool done = true; - for (Breakpoint bp : handler->engineBreakpoints(this)) { - switch (bp.state()) { - case BreakpointNew: - // Should not happen once claimed. - QTC_CHECK(false); - continue; - case BreakpointInsertRequested: - done = false; - insertBreakpoint(bp); - continue; - case BreakpointChangeRequested: - done = false; - changeBreakpoint(bp); - continue; - case BreakpointRemoveRequested: - done = false; - removeBreakpoint(bp); - continue; - case BreakpointChangeProceeding: - case BreakpointInsertProceeding: - case BreakpointRemoveProceeding: - done = false; - //qDebug() << "BREAKPOINT " << id << " STILL IN PROGRESS, STATE" - // << handler->state(id); - continue; - case BreakpointInserted: - //qDebug() << "BREAKPOINT " << id << " IS GOOD"; - continue; - case BreakpointDead: - // Can happen temporarily during Breakpoint destruction. - // BreakpointItem::deleteThis() intentionally lets the event loop run, - // during which an attemptBreakpointSynchronization() might kick in. - continue; - } - } - - if (done) - showMessage("BREAKPOINTS ARE SYNCHRONIZED"); - else - showMessage("BREAKPOINTS ARE NOT FULLY SYNCHRONIZED"); + //: e.g. LLDB for "myproject", shows up i + return tr("%1 for \"%2\"").arg(d->m_debuggerName, runParameters().displayName); } -bool DebuggerEngine::acceptsBreakpoint(Breakpoint bp) const +void DebuggerEngine::insertBreakpoint(const Breakpoint &bp) { - Q_UNUSED(bp); - return false; -} - -void DebuggerEngine::insertBreakpoint(Breakpoint bp) -{ - BreakpointState state = bp.state(); - QTC_ASSERT(state == BreakpointInsertRequested, - qDebug() << bp.id() << this << state); + QTC_ASSERT(bp, return); + BreakpointState state = bp->state(); + QTC_ASSERT(state == BreakpointInsertionRequested, + qDebug() << bp->modelId() << this << state); QTC_CHECK(false); } -void DebuggerEngine::removeBreakpoint(Breakpoint bp) +void DebuggerEngine::removeBreakpoint(const Breakpoint &bp) { - BreakpointState state = bp.state(); + QTC_ASSERT(bp, return); + BreakpointState state = bp->state(); QTC_ASSERT(state == BreakpointRemoveRequested, - qDebug() << bp.id() << this << state); + qDebug() << bp->responseId() << this << state); QTC_CHECK(false); } -void DebuggerEngine::changeBreakpoint(Breakpoint bp) +void DebuggerEngine::updateBreakpoint(const Breakpoint &bp) { - BreakpointState state = bp.state(); - QTC_ASSERT(state == BreakpointChangeRequested, - qDebug() << bp.id() << this << state); + QTC_ASSERT(bp, return); + BreakpointState state = bp->state(); + QTC_ASSERT(state == BreakpointUpdateRequested, + qDebug() << bp->responseId() << this << state); QTC_CHECK(false); } -void DebuggerEngine::enableSubBreakpoint(const QString &, bool) +void DebuggerEngine::enableSubBreakpoint(const SubBreakpoint &sbp, bool) { + QTC_ASSERT(sbp, return); QTC_CHECK(false); } @@ -1411,67 +2076,27 @@ void DebuggerEngine::assignValueInDebugger(WatchItem *, { } -void DebuggerEngine::detachDebugger() +void DebuggerEngine::handleRecordReverse(bool record) { + executeRecordReverse(record); + d->updateReverseActions(); } -void DebuggerEngine::executeStep() +void DebuggerEngine::handleReverseDirection(bool reverse) { + executeReverse(reverse); + updateMarkers(); + d->updateReverseActions(); } -void DebuggerEngine::executeStepOut() +void DebuggerEngine::executeDebuggerCommand(const QString &) { -} - -void DebuggerEngine::executeNext() -{ -} - -void DebuggerEngine::executeStepI() -{ -} - -void DebuggerEngine::executeNextI() -{ -} - -void DebuggerEngine::executeReturn() -{ -} - -void DebuggerEngine::continueInferior() -{ -} - -void DebuggerEngine::interruptInferior() -{ -} - -void DebuggerEngine::executeRunToLine(const ContextData &) -{ -} - -void DebuggerEngine::executeRunToFunction(const QString &) -{ -} - -void DebuggerEngine::executeJumpToLine(const ContextData &) -{ -} - -void DebuggerEngine::executeDebuggerCommand(const QString &, DebuggerLanguages) -{ - showStatusMessage(tr("This debugger cannot handle user input.")); -} - -BreakHandler *DebuggerEngine::breakHandler() const -{ - return Internal::breakHandler(); + showMessage(tr("This debugger cannot handle user input."), StatusBar); } bool DebuggerEngine::isDying() const { - return !runTool() || runTool()->isDying(); + return d->m_isDying; } QString DebuggerEngine::msgStopped(const QString &reason) @@ -1535,165 +2160,31 @@ void DebuggerEngine::updateMemoryViews() void DebuggerEngine::openDisassemblerView(const Location &location) { - auto agent = new DisassemblerAgent(this); + DisassemblerAgent *agent = new DisassemblerAgent(this); agent->setLocation(location); } -void DebuggerRunParameters::validateExecutable() +void DebuggerEngine::raiseWatchersWindow() { - const bool warnOnRelease = boolSetting(WarnOnReleaseBuilds); - bool warnOnInappropriateDebugger = false; - QString detailedWarning; - switch (toolChainAbi.binaryFormat()) { - case Abi::PEFormat: { - QString preferredDebugger; - if (toolChainAbi.osFlavor() == Abi::WindowsMSysFlavor) { - if (cppEngineType == CdbEngineType) - preferredDebugger = "GDB"; - } else if (cppEngineType != CdbEngineType) { - // osFlavor() is MSVC, so the recommended debugger is CDB - preferredDebugger = "CDB"; - } - if (!preferredDebugger.isEmpty()) { - warnOnInappropriateDebugger = true; - detailedWarning = DebuggerEngine::tr( - "The inferior is in the Portable Executable format.\n" - "Selecting %1 as debugger would improve the debugging " - "experience for this binary format.").arg(preferredDebugger); - break; - } - if (warnOnRelease && cppEngineType == CdbEngineType) { - if (!symbolFile.endsWith(".exe", Qt::CaseInsensitive)) - symbolFile.append(".exe"); - QString errorMessage; - QStringList rc; - if (getPDBFiles(symbolFile, &rc, &errorMessage) && !rc.isEmpty()) - return; - if (!errorMessage.isEmpty()) { - detailedWarning.append('\n'); - detailedWarning.append(errorMessage); + if (d->m_watchersView) { + if (auto dock = qobject_cast(d->m_watchersView->parentWidget())) { + if (QAction *act = dock->toggleViewAction()) { + if (!act->isChecked()) + QTimer::singleShot(1, act, [act] { act->trigger(); }); + dock->raise(); } - } else { - return; } - break; } - case Abi::ElfFormat: { - if (cppEngineType == CdbEngineType) { - warnOnInappropriateDebugger = true; - detailedWarning = DebuggerEngine::tr( - "The inferior is in the ELF format.\n" - "Selecting GDB or LLDB as debugger would improve the debugging " - "experience for this binary format."); - break; - } +} - Utils::ElfReader reader(symbolFile); - const ElfData elfData = reader.readHeaders(); - const QString error = reader.errorString(); - - Internal::showMessage("EXAMINING " + symbolFile, LogDebug); - QByteArray msg = "ELF SECTIONS: "; - - static const QList interesting = { - ".debug_info", - ".debug_abbrev", - ".debug_line", - ".debug_str", - ".debug_loc", - ".debug_range", - ".gdb_index", - ".note.gnu.build-id", - ".gnu.hash", - ".gnu_debuglink" - }; - - QSet seen; - for (const ElfSectionHeader &header : elfData.sectionHeaders) { - msg.append(header.name); - msg.append(' '); - if (interesting.contains(header.name)) - seen.insert(header.name); - } - Internal::showMessage(QString::fromUtf8(msg), LogDebug); - - if (!error.isEmpty()) { - Internal::showMessage("ERROR WHILE READING ELF SECTIONS: " + error, LogDebug); - return; - } - - if (elfData.sectionHeaders.isEmpty()) { - Internal::showMessage("NO SECTION HEADERS FOUND. IS THIS AN EXECUTABLE?", LogDebug); - return; - } - - // Note: .note.gnu.build-id also appears in regular release builds. - // bool hasBuildId = elfData.indexOf(".note.gnu.build-id") >= 0; - bool hasEmbeddedInfo = elfData.indexOf(".debug_info") >= 0; - bool hasLink = elfData.indexOf(".gnu_debuglink") >= 0; - if (hasEmbeddedInfo) { - QSharedPointer options = Internal::globalDebuggerOptions(); - SourcePathRegExpMap globalRegExpSourceMap; - globalRegExpSourceMap.reserve(options->sourcePathRegExpMap.size()); - foreach (auto entry, options->sourcePathRegExpMap) { - const QString expanded = Utils::globalMacroExpander()->expand(entry.second); - if (!expanded.isEmpty()) - globalRegExpSourceMap.push_back(qMakePair(entry.first, expanded)); - } - if (globalRegExpSourceMap.isEmpty()) - return; - if (QSharedPointer mapper = reader.readSection(".debug_str")) { - const char *str = mapper->start; - const char *limit = str + mapper->fdlen; - bool found = false; - while (str < limit) { - const QString string = QString::fromUtf8(str); - for (auto itExp = globalRegExpSourceMap.begin(), itEnd = globalRegExpSourceMap.end(); - itExp != itEnd; - ++itExp) { - QRegExp exp = itExp->first; - int index = exp.indexIn(string); - if (index != -1) { - sourcePathMap.insert(string.left(index) + exp.cap(1), itExp->second); - found = true; - break; - } - } - if (found) - break; - - const int len = int(strlen(str)); - if (len == 0) - break; - str += len + 1; - } - } - } - if (hasEmbeddedInfo || hasLink) - return; - - foreach (const QByteArray &name, interesting) { - const QString found = seen.contains(name) ? DebuggerEngine::tr("Found.") - : DebuggerEngine::tr("Not found."); - detailedWarning.append('\n' + DebuggerEngine::tr("Section %1: %2").arg(QString::fromUtf8(name)).arg(found)); - } - break; - } - default: +void DebuggerEngine::openMemoryEditor() +{ + AddressDialog dialog; + if (dialog.exec() != QDialog::Accepted) return; - } - if (warnOnInappropriateDebugger) { - AsynchronousMessageBox::information(DebuggerEngine::tr("Warning"), - DebuggerEngine::tr("The selected debugger may be inappropriate for the inferior.\n" - "Examining symbols and setting breakpoints by file name and line number " - "may fail.\n") - + '\n' + detailedWarning); - } else if (warnOnRelease) { - AsynchronousMessageBox::information(DebuggerEngine::tr("Warning"), - DebuggerEngine::tr("This does not seem to be a \"Debug\" build.\n" - "Setting breakpoints by file name and line number may fail.") - + '\n' + detailedWarning); - } + MemoryViewSetupData data; + data.startAddress = dialog.address(); + openMemoryView(data); } void DebuggerEngine::updateLocalsView(const GdbMi &all) @@ -1715,7 +2206,7 @@ void DebuggerEngine::updateLocalsView(const GdbMi &all) static int count = 0; showMessage(QString("") .arg(++count).arg(LogWindow::logTimeStamp()), LogMiscInput); - showStatusMessage(tr("Finished retrieving data"), 400); + showMessage(tr("Finished retrieving data"), 400, StatusBar); DebuggerToolTipManager::updateEngine(this); @@ -1770,6 +2261,162 @@ void DebuggerEngine::expandItem(const QString &iname) updateItem(iname); } +void DebuggerEngine::handleExecDetach() +{ + resetLocation(); + detachDebugger(); +} + +void DebuggerEngine::handleExecContinue() +{ + resetLocation(); + continueInferior(); +} + +void DebuggerEngine::handleExecInterrupt() +{ + resetLocation(); + requestInterruptInferior(); +} + +void DebuggerEngine::handleReset() +{ + resetLocation(); + resetInferior(); +} + +void DebuggerEngine::handleExecStep() +{ + if (state() == DebuggerNotReady) { + DebuggerRunTool::setBreakOnMainNextTime(); + ProjectExplorerPlugin::runStartupProject(ProjectExplorer::Constants::DEBUG_RUN_MODE); + } else { + resetLocation(); + if (d->operatesByInstruction()) + executeStepI(); + else + executeStep(); + } +} + +void DebuggerEngine::handleExecNext() +{ + if (state() == DebuggerNotReady) { + DebuggerRunTool::setBreakOnMainNextTime(); + ProjectExplorerPlugin::runStartupProject(ProjectExplorer::Constants::DEBUG_RUN_MODE); + } else { + resetLocation(); + if (d->operatesByInstruction()) + executeNextI(); + else + executeNext(); + } +} + +void DebuggerEngine::handleExecStepOut() +{ + resetLocation(); + executeStepOut(); +} + +void DebuggerEngine::handleExecReturn() +{ + resetLocation(); + executeReturn(); +} + +void DebuggerEngine::handleExecJumpToLine() +{ + resetLocation(); + if (BaseTextEditor *textEditor = BaseTextEditor::currentTextEditor()) { + ContextData location = getLocationContext(textEditor->textDocument(), + textEditor->currentLine()); + if (location.isValid()) + executeJumpToLine(location); + } +} + +void DebuggerEngine::handleExecRunToLine() +{ + resetLocation(); + if (BaseTextEditor *textEditor = BaseTextEditor::currentTextEditor()) { + ContextData location = getLocationContext(textEditor->textDocument(), + textEditor->currentLine()); + if (location.isValid()) + executeRunToLine(location); + } +} + +void DebuggerEngine::handleExecRunToSelectedFunction() +{ + BaseTextEditor *textEditor = BaseTextEditor::currentTextEditor(); + QTC_ASSERT(textEditor, return); + QTextCursor cursor = textEditor->textCursor(); + QString functionName = cursor.selectedText(); + if (functionName.isEmpty()) { + const QTextBlock block = cursor.block(); + const QString line = block.text(); + foreach (const QString &str, line.trimmed().split(QLatin1Char('('))) { + QString a; + for (int i = str.size(); --i >= 0; ) { + if (!str.at(i).isLetterOrNumber()) + break; + a = str.at(i) + a; + } + if (!a.isEmpty()) { + functionName = a; + break; + } + } + } + + if (functionName.isEmpty()) { + showMessage(tr("No function selected."), StatusBar); + } else { + showMessage(tr("Running to function \"%1\".").arg(functionName), StatusBar); + resetLocation(); + executeRunToFunction(functionName); + } +} + +void DebuggerEngine::handleAddToWatchWindow() +{ + // Requires a selection, but that's the only case we want anyway. + BaseTextEditor *textEditor = BaseTextEditor::currentTextEditor(); + if (!textEditor) + return; + QTextCursor tc = textEditor->textCursor(); + QString exp; + if (tc.hasSelection()) { + exp = tc.selectedText(); + } else { + int line, column; + exp = cppExpressionAt(textEditor->editorWidget(), tc.position(), &line, &column); + } + if (hasCapability(WatchComplexExpressionsCapability)) + exp = removeObviousSideEffects(exp); + else + exp = fixCppExpression(exp); + exp = exp.trimmed(); + if (exp.isEmpty()) { + // Happens e.g. when trying to evaluate 'char' or 'return'. + AsynchronousMessageBox::warning(tr("Warning"), + tr("Select a valid expression to evaluate.")); + return; + } + watchHandler()->watchVariable(exp); +} + +void DebuggerEngine::handleFrameDown() +{ + frameDown(); +} + +void DebuggerEngine::handleFrameUp() +{ + frameUp(); +} + void DebuggerEngine::checkState(DebuggerState state, const char *file, int line) { const DebuggerState current = d->m_state; @@ -1785,9 +2432,7 @@ void DebuggerEngine::checkState(DebuggerState state, const char *file, int line) bool DebuggerEngine::isNativeMixedEnabled() const { - if (DebuggerRunTool *rt = runTool()) - return rt->runParameters().isNativeMixedDebugging(); - return false; + return d->m_runParameters.isNativeMixedDebugging(); } bool DebuggerEngine::isNativeMixedActive() const @@ -1805,11 +2450,18 @@ bool DebuggerEngine::isNativeMixedActiveFrame() const return frame.language == QmlLanguage; } +void DebuggerEngine::startDying() const +{ + d->m_isDying = true; + if (DebuggerEngine *other = d->m_companionEngine) + other->d->m_isDying = true; +} + bool DebuggerRunParameters::isCppDebugging() const { - return cppEngineType == CdbEngineType - || cppEngineType == GdbEngineType - || cppEngineType == LldbEngineType; + return cppEngineType == GdbEngineType + || cppEngineType == LldbEngineType + || cppEngineType == CdbEngineType; } bool DebuggerRunParameters::isNativeMixedDebugging() const @@ -1817,6 +2469,215 @@ bool DebuggerRunParameters::isNativeMixedDebugging() const return nativeMixedEnabled && isCppDebugging() && isQmlDebugging; } +QString DebuggerEngine::formatStartParameters() const +{ + const DebuggerRunParameters &sp = d->m_runParameters; + QString rc; + QTextStream str(&rc); + str << "Start parameters: '" << sp.displayName << "' mode: " << sp.startMode + << "\nABI: " << sp.toolChainAbi.toString() << '\n'; + str << "Languages: "; + if (sp.isCppDebugging()) + str << "c++ "; + if (sp.isQmlDebugging) + str << "qml"; + str << '\n'; + if (!sp.inferior.executable.isEmpty()) { + str << "Executable: " << QDir::toNativeSeparators(sp.inferior.executable) + << ' ' << sp.inferior.commandLineArguments; + if (d->m_terminalRunner) + str << " [terminal]"; + str << '\n'; + if (!sp.inferior.workingDirectory.isEmpty()) + str << "Directory: " << QDir::toNativeSeparators(sp.inferior.workingDirectory) + << '\n'; + } + QString cmd = sp.debugger.executable; + if (!cmd.isEmpty()) + str << "Debugger: " << QDir::toNativeSeparators(cmd) << '\n'; + if (!sp.coreFile.isEmpty()) + str << "Core: " << QDir::toNativeSeparators(sp.coreFile) << '\n'; + if (sp.attachPID.isValid()) + str << "PID: " << sp.attachPID.pid() << ' ' << sp.crashParameter << '\n'; + if (!sp.projectSourceDirectory.isEmpty()) { + str << "Project: " << QDir::toNativeSeparators(sp.projectSourceDirectory) << '\n'; + str << "Additional Search Directories:" + << sp.additionalSearchDirectories.join(QLatin1Char(' ')) << '\n'; + } + if (!sp.remoteChannel.isEmpty()) + str << "Remote: " << sp.remoteChannel << '\n'; + if (!sp.qmlServer.host().isEmpty()) + str << "QML server: " << sp.qmlServer.host() << ':' << sp.qmlServer.port() << '\n'; + str << "Sysroot: " << sp.sysRoot << '\n'; + str << "Debug Source Location: " << sp.debugSourceLocation.join(QLatin1Char(':')) << '\n'; + return rc; +} + +// CppDebuggerEngine + +Context CppDebuggerEngine::languageContext() const +{ + return Context(Constants::C_CPPDEBUGGER); +} + +void CppDebuggerEngine::validateExecutable() +{ + DebuggerRunParameters &rp = mutableRunParameters(); + const bool warnOnRelease = boolSetting(WarnOnReleaseBuilds); + bool warnOnInappropriateDebugger = false; + QString detailedWarning; + switch (rp.toolChainAbi.binaryFormat()) { + case Abi::PEFormat: { + QString preferredDebugger; + if (rp.toolChainAbi.osFlavor() == Abi::WindowsMSysFlavor) { + if (rp.cppEngineType == CdbEngineType) + preferredDebugger = "GDB"; + } else if (rp.cppEngineType != CdbEngineType) { + // osFlavor() is MSVC, so the recommended debugger is CDB + preferredDebugger = "CDB"; + } + if (!preferredDebugger.isEmpty()) { + warnOnInappropriateDebugger = true; + detailedWarning = DebuggerEngine::tr( + "The inferior is in the Portable Executable format.\n" + "Selecting %1 as debugger would improve the debugging " + "experience for this binary format.").arg(preferredDebugger); + break; + } + if (warnOnRelease && rp.cppEngineType == CdbEngineType) { + if (!rp.symbolFile.endsWith(".exe", Qt::CaseInsensitive)) + rp.symbolFile.append(".exe"); + QString errorMessage; + QStringList rc; + if (getPDBFiles(rp.symbolFile, &rc, &errorMessage) && !rc.isEmpty()) + return; + if (!errorMessage.isEmpty()) { + detailedWarning.append('\n'); + detailedWarning.append(errorMessage); + } + } else { + return; + } + break; + } + case Abi::ElfFormat: { + if (rp.cppEngineType == CdbEngineType) { + warnOnInappropriateDebugger = true; + detailedWarning = DebuggerEngine::tr( + "The inferior is in the ELF format.\n" + "Selecting GDB or LLDB as debugger would improve the debugging " + "experience for this binary format."); + break; + } + + Utils::ElfReader reader(rp.symbolFile); + const ElfData elfData = reader.readHeaders(); + const QString error = reader.errorString(); + + showMessage("EXAMINING " + rp.symbolFile, LogDebug); + QByteArray msg = "ELF SECTIONS: "; + + static const QList interesting = { + ".debug_info", + ".debug_abbrev", + ".debug_line", + ".debug_str", + ".debug_loc", + ".debug_range", + ".gdb_index", + ".note.gnu.build-id", + ".gnu.hash", + ".gnu_debuglink" + }; + + QSet seen; + for (const ElfSectionHeader &header : elfData.sectionHeaders) { + msg.append(header.name); + msg.append(' '); + if (interesting.contains(header.name)) + seen.insert(header.name); + } + showMessage(QString::fromUtf8(msg), LogDebug); + + if (!error.isEmpty()) { + showMessage("ERROR WHILE READING ELF SECTIONS: " + error, LogDebug); + return; + } + + if (elfData.sectionHeaders.isEmpty()) { + showMessage("NO SECTION HEADERS FOUND. IS THIS AN EXECUTABLE?", LogDebug); + return; + } + + // Note: .note.gnu.build-id also appears in regular release builds. + // bool hasBuildId = elfData.indexOf(".note.gnu.build-id") >= 0; + bool hasEmbeddedInfo = elfData.indexOf(".debug_info") >= 0; + bool hasLink = elfData.indexOf(".gnu_debuglink") >= 0; + if (hasEmbeddedInfo) { + QSharedPointer options = Internal::globalDebuggerOptions(); + SourcePathRegExpMap globalRegExpSourceMap; + globalRegExpSourceMap.reserve(options->sourcePathRegExpMap.size()); + foreach (auto entry, options->sourcePathRegExpMap) { + const QString expanded = Utils::globalMacroExpander()->expand(entry.second); + if (!expanded.isEmpty()) + globalRegExpSourceMap.push_back(qMakePair(entry.first, expanded)); + } + if (globalRegExpSourceMap.isEmpty()) + return; + if (QSharedPointer mapper = reader.readSection(".debug_str")) { + const char *str = mapper->start; + const char *limit = str + mapper->fdlen; + bool found = false; + while (str < limit) { + const QString string = QString::fromUtf8(str); + for (auto itExp = globalRegExpSourceMap.begin(), itEnd = globalRegExpSourceMap.end(); + itExp != itEnd; + ++itExp) { + QRegExp exp = itExp->first; + int index = exp.indexIn(string); + if (index != -1) { + rp.sourcePathMap.insert(string.left(index) + exp.cap(1), itExp->second); + found = true; + break; + } + } + if (found) + break; + + const int len = int(strlen(str)); + if (len == 0) + break; + str += len + 1; + } + } + } + if (hasEmbeddedInfo || hasLink) + return; + + foreach (const QByteArray &name, interesting) { + const QString found = seen.contains(name) ? DebuggerEngine::tr("Found.") + : DebuggerEngine::tr("Not found."); + detailedWarning.append('\n' + DebuggerEngine::tr("Section %1: %2").arg(QString::fromUtf8(name)).arg(found)); + } + break; + } + default: + return; + } + if (warnOnInappropriateDebugger) { + AsynchronousMessageBox::information(DebuggerEngine::tr("Warning"), + DebuggerEngine::tr("The selected debugger may be inappropriate for the inferior.\n" + "Examining symbols and setting breakpoints by file name and line number " + "may fail.\n") + + '\n' + detailedWarning); + } else if (warnOnRelease) { + AsynchronousMessageBox::information(DebuggerEngine::tr("Warning"), + DebuggerEngine::tr("This does not seem to be a \"Debug\" build.\n" + "Setting breakpoints by file name and line number may fail.") + + '\n' + detailedWarning); + } +} + } // namespace Internal } // namespace Debugger diff --git a/src/plugins/debugger/debuggerengine.h b/src/plugins/debugger/debuggerengine.h index 37d4f10a105..14634085837 100644 --- a/src/plugins/debugger/debuggerengine.h +++ b/src/plugins/debugger/debuggerengine.h @@ -29,7 +29,9 @@ #include "debuggerconstants.h" #include "debuggeritem.h" #include "debuggerprotocol.h" +#include "breakhandler.h" +#include #include #include #include @@ -41,12 +43,14 @@ QT_BEGIN_NAMESPACE class QDebug; class QPoint; -class QAbstractItemModel; QT_END_NAMESPACE namespace Core { class IOptionsPage; } -namespace Utils { class MacroExpander; } +namespace Utils { +class MacroExpander; +class Perspective; +} // Utils namespace Debugger { @@ -62,7 +66,9 @@ class DisassemblerAgent; class MemoryAgent; class WatchItem; class BreakHandler; +class BreakpointParameters; class LocationMark; +class LogWindow; class ModulesHandler; class RegisterHandler; class StackHandler; @@ -70,8 +76,7 @@ class StackFrame; class SourceFilesHandler; class ThreadsHandler; class WatchHandler; -class Breakpoint; -class QmlCppEngine; +class WatchTreeView; class DebuggerToolTipContext; class MemoryViewSetupData; class TerminalRunner; @@ -157,7 +162,6 @@ public: bool isCppDebugging() const; bool isNativeMixedDebugging() const; - void validateExecutable(); Utils::MacroExpander *macroExpander = nullptr; @@ -219,32 +223,21 @@ private: quint64 m_address = 0; }; -enum LocationType { UnknownLocation, LocationByFile, LocationByAddress }; - -class ContextData -{ -public: - bool isValid() const { return type != UnknownLocation; } - -public: - LocationType type = UnknownLocation; - QString fileName; - int lineNumber = 0; - quint64 address = 0; -}; - class DebuggerEngine : public QObject { Q_OBJECT public: - explicit DebuggerEngine(); + DebuggerEngine(); ~DebuggerEngine() override; - const DebuggerRunParameters &runParameters() const; + void setRunTool(DebuggerRunTool *runTool); + void setRunParameters(const DebuggerRunParameters &runParameters); - virtual void setRunTool(DebuggerRunTool *runTool); - DebuggerRunTool *runTool() const; + const DebuggerRunParameters &runParameters() const; + bool isStartupRunConfiguration() const; + void setCompanionEngine(DebuggerEngine *engine); + void setSecondaryEngine(); void start(); @@ -269,6 +262,7 @@ public: void updateWatchData(const QString &iname); // FIXME: Merge with above. virtual void selectWatchData(const QString &iname); + virtual void validateExecutable() {} virtual void prepareForRestart() {} virtual void abortDebuggerProcess() {} // second attempt @@ -308,39 +302,39 @@ public: virtual void updateAll(); virtual void updateLocals(); + virtual Core::Context languageContext() const { return {}; } + virtual QString displayName() const; + virtual bool stateAcceptsBreakpointChanges() const { return true; } - virtual void attemptBreakpointSynchronization(); - virtual bool acceptsBreakpoint(Breakpoint bp) const = 0; - virtual void insertBreakpoint(Breakpoint bp); // FIXME: make pure - virtual void removeBreakpoint(Breakpoint bp); // FIXME: make pure - virtual void changeBreakpoint(Breakpoint bp); // FIXME: make pure - virtual void enableSubBreakpoint(const QString &locid, bool on); + virtual bool acceptsBreakpoint(const BreakpointParameters &bp) const = 0; + virtual void insertBreakpoint(const Breakpoint &bp) = 0; + virtual void removeBreakpoint(const Breakpoint &bp) = 0; + virtual void updateBreakpoint(const Breakpoint &bp) = 0; + virtual void enableSubBreakpoint(const SubBreakpoint &sbp, bool enabled); virtual bool acceptsDebuggerCommands() const { return true; } - virtual void executeDebuggerCommand(const QString &command, DebuggerLanguages languages); + virtual void executeDebuggerCommand(const QString &command); virtual void assignValueInDebugger(WatchItem *item, const QString &expr, const QVariant &value); virtual void selectThread(Internal::ThreadId threadId) = 0; - virtual Internal::ModulesHandler *modulesHandler() const; - virtual Internal::RegisterHandler *registerHandler() const; - virtual Internal::StackHandler *stackHandler() const; - virtual Internal::ThreadsHandler *threadsHandler() const; - virtual Internal::WatchHandler *watchHandler() const; - virtual Internal::SourceFilesHandler *sourceFilesHandler() const; - virtual Internal::BreakHandler *breakHandler() const; + virtual void executeRecordReverse(bool) {} + virtual void executeReverse(bool) {} - virtual QAbstractItemModel *modulesModel() const; - virtual QAbstractItemModel *registerModel() const; - virtual QAbstractItemModel *stackModel() const; - virtual QAbstractItemModel *threadsModel() const; - virtual QAbstractItemModel *watchModel() const; - virtual QAbstractItemModel *sourceFilesModel() const; + ModulesHandler *modulesHandler() const; + RegisterHandler *registerHandler() const; + StackHandler *stackHandler() const; + ThreadsHandler *threadsHandler() const; + WatchHandler *watchHandler() const; + SourceFilesHandler *sourceFilesHandler() const; + BreakHandler *breakHandler() const; + LogWindow *logWindow() const; + DisassemblerAgent *disassemblerAgent() const; void progressPing(); bool debuggerActionsEnabled() const; - static bool debuggerActionsEnabled(DebuggerState state); + virtual bool companionPreventsActions() const; DebuggerState state() const; bool isDying() const; @@ -349,7 +343,13 @@ public: void notifyInferiorPid(const Utils::ProcessHandle &pid); qint64 inferiorPid() const; + bool isReverseDebugging() const; + void handleBeginOfRecordingReached(); + void handleRecordingFailed(); + void handleRecordReverse(bool); + void handleReverseDirection(bool); + void handleCommand(int role, const QVariant &value); // Convenience @@ -359,30 +359,32 @@ public: virtual void resetLocation(); virtual void gotoLocation(const Internal::Location &location); + void gotoCurrentLocation(); virtual void quitDebugger(); // called when pressing the stop button + void abortDebugger(); - void updateViews(); - bool isSlaveEngine() const; - bool isMasterEngine() const; - DebuggerEngine *masterEngine(); - virtual DebuggerEngine *activeEngine() { return this; } - virtual DebuggerEngine *cppEngine() { return nullptr; } + bool isPrimaryEngine() const; virtual bool canDisplayTooltip() const; - virtual void notifyInferiorIll(); - QString toFileInProject(const QUrl &fileUrl); - void updateBreakpointMarker(const Breakpoint &bp); - void removeBreakpointMarker(const Breakpoint &bp); QString expand(const QString &string) const; QString nativeStartupCommands() const; + Utils::Perspective *perspective() const; + void updateMarkers(); + +signals: + void engineStarted(); + void engineFinished(); + void requestRunControlFinish(); + void requestRunControlStop(); + void attachToCoreRequested(const QString &coreFile); + void appendMessageRequested(const QString &msg, + Utils::OutputFormat format, + bool appendNewLine) const; protected: - // The base notify*() function implementation should be sufficient - // in most cases, but engines are free to override them to do some - // engine specific cleanup like stopping timers etc. void notifyEngineSetupOk(); void notifyEngineSetupFailed(); void notifyEngineRunFailed(); @@ -398,14 +400,60 @@ protected: void notifyInferiorRunOk(); void notifyInferiorRunFailed(); + void notifyInferiorIll(); + void notifyInferiorExited(); + void notifyInferiorStopOk(); void notifyInferiorSpontaneousStop(); void notifyInferiorStopFailed(); - public: // FIXME: Remove, currently needed for Android. - void notifyInferiorExited(); +public: + void updateState(bool alsoUpdateCompanion); + QString formatStartParameters() const; + WatchTreeView *inspectorView(); + void updateLocalsWindow(bool showReturn); + void raiseWatchersWindow(); - protected: + bool isRegistersWindowVisible() const; + bool isModulesWindowVisible() const; + + void setThreadBoxContents(const QStringList &list, int index); + + void openMemoryEditor(); + + void handleExecDetach(); + void handleExecContinue(); + void handleExecInterrupt(); + void handleUserStop(); + void handleAbort(); + void handleReset(); + void handleExecStep(); + void handleExecNext(); + void handleExecStepOut(); + void handleExecReturn(); + void handleExecJumpToLine(); + void handleExecRunToLine(); + void handleExecRunToSelectedFunction(); + void handleAddToWatchWindow(); + void handleFrameDown(); + void handleFrameUp(); + void handleOperateByInstructionTriggered(bool operateByInstructionTriggered); + + // Breakpoint state transitions + void notifyBreakpointInsertProceeding(const Breakpoint &bp); + void notifyBreakpointInsertOk(const Breakpoint &bp); + void notifyBreakpointInsertFailed(const Breakpoint &bp); + void notifyBreakpointChangeOk(const Breakpoint &bp); + void notifyBreakpointChangeProceeding(const Breakpoint &bp); + void notifyBreakpointChangeFailed(const Breakpoint &bp); + void notifyBreakpointPending(const Breakpoint &bp); + void notifyBreakpointRemoveProceeding(const Breakpoint &bp); + void notifyBreakpointRemoveOk(const Breakpoint &bp); + void notifyBreakpointRemoveFailed(const Breakpoint &bp); + void notifyBreakpointNeedsReinsertion(const Breakpoint &bp); + +protected: + void setDebuggerName(const QString &name); void notifyDebuggerProcessFinished(int exitCode, QProcess::ExitStatus exitStatus, const QString &backendName); @@ -424,29 +472,27 @@ protected: virtual void shutdownEngine() = 0; virtual void resetInferior() {} - virtual void detachDebugger(); - virtual void executeStep(); - virtual void executeStepOut(); - virtual void executeNext(); - virtual void executeStepI(); - virtual void executeNextI(); - virtual void executeReturn(); + virtual void detachDebugger() {} + virtual void executeStep() {} + virtual void executeStepOut() {} + virtual void executeNext() {} + virtual void executeStepI() {} + virtual void executeNextI() {} + virtual void executeReturn() {} - virtual void continueInferior(); - virtual void interruptInferior(); + virtual void continueInferior() {} + virtual void interruptInferior() {} void requestInterruptInferior(); - virtual void executeRunToLine(const Internal::ContextData &data); - virtual void executeRunToFunction(const QString &functionName); - virtual void executeJumpToLine(const Internal::ContextData &data); + virtual void executeRunToLine(const Internal::ContextData &) {} + virtual void executeRunToFunction(const QString &) {} + virtual void executeJumpToLine(const Internal::ContextData &) {} virtual void frameUp(); virtual void frameDown(); virtual void doUpdateLocals(const UpdateParameters ¶ms); - void setMasterEngine(DebuggerEngine *masterEngine); - TerminalRunner *terminal() const; static QString msgStopped(const QString &reason = QString()); @@ -457,35 +503,43 @@ protected: bool showStoppedBySignalMessageBox(const QString meaning, QString name); void showStoppedByExceptionMessageBox(const QString &description); - virtual void setupSlaveEngine(); - virtual void runSlaveEngine(); - virtual void shutdownSlaveEngine(); - - virtual void slaveEngineStateChanged(DebuggerEngine *engine, - DebuggerState state); - void updateLocalsView(const GdbMi &all); void checkState(DebuggerState state, const char *file, int line); bool isNativeMixedEnabled() const; bool isNativeMixedActive() const; bool isNativeMixedActiveFrame() const; + void startDying() const; + +protected: + DebuggerRunParameters &mutableRunParameters() const; + ProjectExplorer::IDevice::ConstPtr device() const; + DebuggerEngine *companionEngine() const; private: - // Wrapper engine needs access to state of its subengines. - friend class QmlCppEngine; friend class DebuggerPluginPrivate; - friend class DebuggerEnginePrivate; friend class LocationMark; DebuggerEnginePrivate *d; }; +class CppDebuggerEngine : public DebuggerEngine +{ +public: + CppDebuggerEngine() {} + ~CppDebuggerEngine() override {} + + void validateExecutable() override; + Core::Context languageContext() const override; +}; + class LocationMark : public TextEditor::TextMark { public: LocationMark(DebuggerEngine *engine, const Utils::FileName &file, int line); void removedFromEditor() override { updateLineNumber(0); } + void updateIcon(); + private: bool isDraggable() const override; void dragToLine(int line) override; diff --git a/src/plugins/debugger/debuggericons.cpp b/src/plugins/debugger/debuggericons.cpp index 15d480e3f7c..776e54afe12 100644 --- a/src/plugins/debugger/debuggericons.cpp +++ b/src/plugins/debugger/debuggericons.cpp @@ -81,9 +81,18 @@ const Icon DEBUG_EXIT_SMALL_TOOLBAR({ const Icon LOCATION({ {":/debugger/images/location_background.png", Theme::IconsCodeModelOverlayForegroundColor}, {":/debugger/images/location.png", Theme::IconsWarningToolBarColor}}, Icon::Tint); +const Icon REVERSE_LOCATION({ + {":/debugger/images/debugger_reversemode_background.png", Theme::IconsCodeModelOverlayForegroundColor}, + {":/debugger/images/debugger_reversemode.png", Theme::IconsWarningToolBarColor}}, Icon::Tint); const Icon REVERSE_MODE({ {":/debugger/images/debugger_reversemode_background.png", Theme::IconsCodeModelOverlayForegroundColor}, {":/debugger/images/debugger_reversemode.png", Theme::IconsInfoColor}}, Icon::Tint); +const Icon DIRECTION_BACKWARD({ + {":/debugger/images/debugger_reversemode_background.png", Theme::IconsCodeModelOverlayForegroundColor}, + {":/debugger/images/debugger_reversemode.png", Theme::IconsInfoColor}}, Icon::Tint); +const Icon DIRECTION_FORWARD({ + {":/debugger/images/location_background.png", Theme::IconsCodeModelOverlayForegroundColor}, + {":/debugger/images/location.png", Theme::IconsInfoColor}}, Icon::Tint); const Icon APP_ON_TOP({ {":/utils/images/app-on-top.png", Theme::PanelTextColorMid}}, Icon::MenuTintedStyle); const Icon APP_ON_TOP_TOOLBAR({ diff --git a/src/plugins/debugger/debuggericons.h b/src/plugins/debugger/debuggericons.h index 7d8bf804c1a..0030597dbfa 100644 --- a/src/plugins/debugger/debuggericons.h +++ b/src/plugins/debugger/debuggericons.h @@ -55,7 +55,10 @@ extern const Utils::Icon DEBUG_INTERRUPT_SMALL_TOOLBAR; extern const Utils::Icon DEBUG_EXIT_SMALL; extern const Utils::Icon DEBUG_EXIT_SMALL_TOOLBAR; extern const Utils::Icon LOCATION; +extern const Utils::Icon REVERSE_LOCATION; extern const Utils::Icon REVERSE_MODE; +extern const Utils::Icon DIRECTION_FORWARD; +extern const Utils::Icon DIRECTION_BACKWARD; extern const Utils::Icon APP_ON_TOP; extern const Utils::Icon APP_ON_TOP_TOOLBAR; extern const Utils::Icon SELECT; diff --git a/src/plugins/debugger/debuggerinternalconstants.h b/src/plugins/debugger/debuggerinternalconstants.h index 0ec6df1111c..cb9591e8ad9 100644 --- a/src/plugins/debugger/debuggerinternalconstants.h +++ b/src/plugins/debugger/debuggerinternalconstants.h @@ -31,16 +31,18 @@ namespace Debugger { namespace Internal { // DebuggerMainWindow dock widget names -const char DOCKWIDGET_BREAK[] = "Debugger.Docks.Break"; -const char DOCKWIDGET_MODULES[] = "Debugger.Docks.Modules"; -const char DOCKWIDGET_REGISTER[] = "Debugger.Docks.Register"; -const char DOCKWIDGET_OUTPUT[] = "Debugger.Docks.Output"; -const char DOCKWIDGET_SNAPSHOTS[] = "Debugger.Docks.Snapshots"; -const char DOCKWIDGET_STACK[] = "Debugger.Docks.Stack"; -const char DOCKWIDGET_SOURCE_FILES[] = "Debugger.Docks.SourceFiles"; -const char DOCKWIDGET_THREADS[] = "Debugger.Docks.Threads"; +const char DOCKWIDGET_BREAKPOINTMANAGER[] = "Debugger.Docks.BreakpointManager"; +const char DOCKWIDGET_ENGINEMANAGER[] = "Debugger.Docks.Snapshots"; +const char DOCKWIDGET_GLOBALLOG[] = "Debugger.Docks.GlobalLog"; + +const char DOCKWIDGET_BREAK[] = "Debugger.Docks.Break"; +const char DOCKWIDGET_MODULES[] = "Debugger.Docks.Modules"; +const char DOCKWIDGET_REGISTER[] = "Debugger.Docks.Register"; +const char DOCKWIDGET_OUTPUT[] = "Debugger.Docks.Output"; +const char DOCKWIDGET_STACK[] = "Debugger.Docks.Stack"; +const char DOCKWIDGET_SOURCE_FILES[] = "Debugger.Docks.SourceFiles"; +const char DOCKWIDGET_THREADS[] = "Debugger.Docks.Threads"; const char DOCKWIDGET_LOCALS_AND_INSPECTOR[] = "Debugger.Docks.LocalsAndInspector"; -const char DOCKWIDGET_WATCHERS[] = "Debugger.Docks.Watchers"; } // namespace Internal diff --git a/src/plugins/debugger/debuggermainwindow.cpp b/src/plugins/debugger/debuggermainwindow.cpp index c2ae3fa0dda..ed950d4ca28 100644 --- a/src/plugins/debugger/debuggermainwindow.cpp +++ b/src/plugins/debugger/debuggermainwindow.cpp @@ -49,6 +49,7 @@ #include #include +#include #include #include #include @@ -64,196 +65,154 @@ namespace Utils { const char LAST_PERSPECTIVE_KEY[] = "LastPerspective"; -DebuggerMainWindow::DebuggerMainWindow() +static DebuggerMainWindow *theMainWindow = nullptr; + +class ToolbarOperation +{ +public: + void attachToToolbar(QWidget *parent) + { + QLayout *layout = parent->layout(); + if (toolBarWidget) { + toolBarWidget->setParent(parent); + layout->addWidget(toolBarWidget); + } + if (toolBarAction) { + toolBarAction->m_toolButton = new QToolButton(parent); + toolBarAction->m_toolButton->setToolButtonStyle(toolBarAction->m_toolButtonStyle); + toolBarAction->m_toolButton->setProperty("panelwidget", true); + toolBarAction->m_toolButton->setDefaultAction(toolBarAction); + toolBarAction->m_toolButton->setVisible(toolBarAction->isVisible()); + layout->addWidget(toolBarAction->m_toolButton); + } + if (toolBarPlainAction) { + toolBarPlainActionButton = new QToolButton(parent); + toolBarPlainActionButton->setProperty("panelwidget", true); + toolBarPlainActionButton->setDefaultAction(toolBarPlainAction); + layout->addWidget(toolBarPlainActionButton); + } + if (separator) { + separator->setParent(parent); + layout->addWidget(separator); + } + } + + void detachFromToolbar() + { + if (toolBarWidget) { + toolBarWidget->setParent(nullptr); + } + if (toolBarAction) { + delete toolBarAction->m_toolButton; + toolBarAction->m_toolButton = nullptr; + } + if (toolBarPlainAction) { + delete toolBarPlainActionButton; + toolBarPlainActionButton = nullptr; + } + if (separator) { + separator->setParent(nullptr); + } + } + + QPointer toolBarPlainAction; // Owned by plugin if present. + QPointer toolBarAction; // Owned by plugin if present. + QPointer toolBarWidget; // Owned by plugin if present. + + // Helper/wrapper widget owned by us if present. + QPointer toolBarPlainActionButton; + QPointer toolBarWidgetParent; + QPointer separator; +}; + +class DockOperation +{ +public: + QPointer widget; + QByteArray anchorDockId; + Perspective::OperationType operationType = Perspective::Raise; + bool visibleByDefault = true; + Qt::DockWidgetArea area = Qt::BottomDockWidgetArea; +}; + +class PerspectivePrivate +{ +public: + ~PerspectivePrivate() { destroyToolBar(); } + + void showToolBar(); + void hideToolBar(); + void destroyToolBar(); + + QString m_id; + QString m_name; + QByteArray m_parentPerspective; + QVector m_dockOperations; + QVector m_toolBarOperations; + QPointer m_centralWidget; + Perspective::Callback m_aboutToActivateCallback; + QPointer m_toolButtonBox; +}; + +class DebuggerMainWindowPrivate : public QObject +{ +public: + DebuggerMainWindowPrivate(DebuggerMainWindow *parent); + ~DebuggerMainWindowPrivate(); + + void ensureToolBarDockExists(); + + void restorePerspective(Perspective *perspective); + void loadPerspectiveHelper(Perspective *perspective, bool fromStoredSettings = true); + void savePerspectiveHelper(const Perspective *perspective); + void destroyPerspective(Perspective *perspective); + void registerPerspective(Perspective *perspective); + void increaseChooserWidthIfNecessary(const QString &visibleName); + void resetCurrentPerspective(); + Perspective *findPerspective(const QByteArray &perspectiveId) const; + int indexInChooser(Perspective *perspective) const; + + DebuggerMainWindow *q = nullptr; + Perspective *m_currentPerspective = nullptr; + QComboBox *m_perspectiveChooser = nullptr; + QStackedWidget *m_centralWidgetStack = nullptr; + QHBoxLayout *m_toolBarLayout = nullptr; + QWidget *m_editorPlaceHolder = nullptr; + Utils::StatusLabel *m_statusLabel = nullptr; + QDockWidget *m_toolBarDock = nullptr; + + QHash m_dockForDockId; + QList m_perspectives; +}; + +DebuggerMainWindowPrivate::DebuggerMainWindowPrivate(DebuggerMainWindow *parent) + : q(parent) { m_centralWidgetStack = new QStackedWidget; m_statusLabel = new Utils::StatusLabel; + m_statusLabel->setProperty("panelwidget", true); m_editorPlaceHolder = new EditorManagerPlaceHolder; m_perspectiveChooser = new QComboBox; m_perspectiveChooser->setObjectName(QLatin1String("PerspectiveChooser")); + m_perspectiveChooser->setProperty("panelwidget", true); connect(m_perspectiveChooser, static_cast(&QComboBox::activated), this, [this](int item) { - restorePerspective(findPerspective(m_perspectiveChooser->itemData(item).toByteArray())); + restorePerspective(q->findPerspective(m_perspectiveChooser->itemData(item).toByteArray())); }); +} + +DebuggerMainWindow::DebuggerMainWindow() + : d(new DebuggerMainWindowPrivate(this)) +{ + theMainWindow = this; setDockNestingEnabled(true); setDockActionsVisible(false); setDocumentMode(true); connect(this, &FancyMainWindow::resetLayout, - this, &DebuggerMainWindow::resetCurrentPerspective); -} - -DebuggerMainWindow::~DebuggerMainWindow() -{ - savePerspectiveHelper(m_currentPerspective); - - delete m_editorPlaceHolder; - m_editorPlaceHolder = nullptr; - // As we have to setParent(0) on dock widget that are not selected, - // we keep track of all and make sure we don't leak any - foreach (QDockWidget *dock, m_dockForDockId) { - if (dock && !dock->parentWidget()) - delete dock; - } - - foreach (const Perspective *perspective, m_perspectives) - delete perspective; -} - -void DebuggerMainWindow::registerPerspective(Perspective *perspective) -{ - m_perspectives.append(perspective); - QByteArray parentPerspective = perspective->parentPerspective(); - // Add "main" perspectives to the chooser. - if (parentPerspective.isEmpty()) { - m_perspectiveChooser->addItem(perspective->name(), perspective->m_id); - increaseChooserWidthIfNecessary(perspective->name()); - } -} - -void DebuggerMainWindow::increaseChooserWidthIfNecessary(const QString &visibleName) -{ - const int oldWidth = m_perspectiveChooser->width(); - const int contentWidth = m_perspectiveChooser->fontMetrics().width(visibleName); - QStyleOptionComboBox option; - option.initFrom(m_perspectiveChooser); - const QSize sz(contentWidth, 1); - const int width = m_perspectiveChooser->style()->sizeFromContents( - QStyle::CT_ComboBox, &option, sz).width(); - if (width > oldWidth) - m_perspectiveChooser->setFixedWidth(width); -} - -void DebuggerMainWindow::destroyDynamicPerspective(Perspective *perspective) -{ - QTC_ASSERT(perspective, return); - savePerspectiveHelper(perspective); - - m_perspectives.removeAll(perspective); - // Dynamic perspectives are currently not visible in the chooser. - // This might change in the future, make sure we notice. - const int idx = indexInChooser(perspective); - QTC_ASSERT(idx == -1, m_perspectiveChooser->removeItem(idx)); - QByteArray parentPerspective = perspective->parentPerspective(); - delete perspective; - // All dynamic perspectives currently have a static parent perspective. - // This might change in the future, make sure we notice. - QTC_CHECK(!parentPerspective.isEmpty()); - restorePerspective(findPerspective(parentPerspective)); -} - -void DebuggerMainWindow::showStatusMessage(const QString &message, int timeoutMS) -{ - m_statusLabel->showStatusMessage(message, timeoutMS); -} - -void DebuggerMainWindow::raiseDock(const QByteArray &dockId) -{ - QDockWidget *dock = m_dockForDockId.value(dockId); - QTC_ASSERT(dock, return); - QAction *act = dock->toggleViewAction(); - if (!act->isChecked()) - QTimer::singleShot(1, act, [act] { act->trigger(); }); - dock->raise(); -} - -QByteArray DebuggerMainWindow::currentPerspective() const -{ - return m_currentPerspective ? m_currentPerspective->m_id : QByteArray(); -} - -void DebuggerMainWindow::onModeChanged(Core::Id mode) -{ - if (mode == Debugger::Constants::MODE_DEBUG) { - setDockActionsVisible(true); - restorePerspective(nullptr); - } else { - setDockActionsVisible(false); - - // Hide dock widgets manually in case they are floating. - foreach (QDockWidget *dockWidget, dockWidgets()) { - if (dockWidget->isFloating()) - dockWidget->hide(); - } - } -} - -void DebuggerMainWindow::setPerspectiveEnabled(const QByteArray &perspectiveId, bool enabled) -{ - Perspective *perspective = findPerspective(perspectiveId); - const int index = indexInChooser(perspective); - QTC_ASSERT(index != -1, return); - auto model = qobject_cast(m_perspectiveChooser->model()); - QTC_ASSERT(model, return); - QStandardItem *item = model->item(index, 0); - item->setFlags(enabled ? item->flags() | Qt::ItemIsEnabled : item->flags() & ~Qt::ItemIsEnabled ); -} - -Perspective *DebuggerMainWindow::findPerspective(const QByteArray &perspectiveId) const -{ - return Utils::findOr(m_perspectives, nullptr, [&](Perspective *perspective) { - return perspective->m_id == perspectiveId; - }); -} - -void DebuggerMainWindow::resetCurrentPerspective() -{ - loadPerspectiveHelper(m_currentPerspective, false); -} - -int DebuggerMainWindow::indexInChooser(Perspective *perspective) const -{ - return perspective ? m_perspectiveChooser->findData(perspective->m_id) : -1; -} - -void DebuggerMainWindow::restorePerspective(Perspective *perspective) -{ - loadPerspectiveHelper(perspective, true); - - const int index = indexInChooser(m_currentPerspective); - if (index != -1) - m_perspectiveChooser->setCurrentIndex(index); -} - -void DebuggerMainWindow::finalizeSetup() -{ - auto viewButton = new QToolButton; - viewButton->setText(tr("&Views")); - - auto closeButton = new QToolButton(); - closeButton->setIcon(Utils::Icons::CLOSE_SPLIT_BOTTOM.icon()); - closeButton->setToolTip(tr("Leave Debug Mode")); - - auto toolbuttonBox = new QWidget; - m_toolbuttonBoxLayout = new QHBoxLayout(toolbuttonBox); - m_toolbuttonBoxLayout->setMargin(0); - m_toolbuttonBoxLayout->setSpacing(0); - - auto toolbar = new Utils::StyledBar; - toolbar->setProperty("topBorder", true); - auto hbox = new QHBoxLayout(toolbar); - hbox->setMargin(0); - hbox->setSpacing(0); - hbox->addWidget(m_perspectiveChooser); - hbox->addWidget(toolbuttonBox); - hbox->addStretch(3); - hbox->addWidget(m_statusLabel); - hbox->addStretch(1); - hbox->addWidget(new Utils::StyledSeparator); - hbox->addWidget(viewButton); - hbox->addWidget(closeButton); - - connect(viewButton, &QAbstractButton::clicked, [this, viewButton] { - QMenu menu; - addDockActionsToMenu(&menu); - menu.exec(viewButton->mapToGlobal(QPoint())); - }); - - connect(closeButton, &QAbstractButton::clicked, [] { - ModeManager::activateMode(Core::Constants::MODE_EDIT); - }); + d, &DebuggerMainWindowPrivate::resetCurrentPerspective); Context debugcontext(Debugger::Constants::C_DEBUGMODE); @@ -279,6 +238,156 @@ void DebuggerMainWindow::finalizeSetup() "Debugger.Views.ResetSimple", debugcontext); cmd->setAttribute(Command::CA_Hide); viewsMenu->addAction(cmd, Core::Constants::G_DEFAULT_THREE); +} + +DebuggerMainWindow::~DebuggerMainWindow() +{ + delete d; + theMainWindow = nullptr; +} + +DebuggerMainWindowPrivate::~DebuggerMainWindowPrivate() +{ + savePerspectiveHelper(m_currentPerspective); + + delete m_editorPlaceHolder; + m_editorPlaceHolder = nullptr; +} + +void DebuggerMainWindow::registerPerspective(Perspective *perspective) +{ + d->registerPerspective(perspective); +} + +void DebuggerMainWindowPrivate::registerPerspective(Perspective *perspective) +{ + m_perspectives.append(perspective); + QByteArray parentPerspective = perspective->d->m_parentPerspective; + // Add "main" perspectives to the chooser. + if (parentPerspective.isEmpty()) { + m_perspectiveChooser->addItem(perspective->name(), perspective->id()); + increaseChooserWidthIfNecessary(perspective->name()); + } +} + +void DebuggerMainWindowPrivate::increaseChooserWidthIfNecessary(const QString &visibleName) +{ + const int oldWidth = m_perspectiveChooser->width(); + const int contentWidth = m_perspectiveChooser->fontMetrics().width(visibleName); + QStyleOptionComboBox option; + option.initFrom(m_perspectiveChooser); + const QSize sz(contentWidth, 1); + const int width = m_perspectiveChooser->style()->sizeFromContents( + QStyle::CT_ComboBox, &option, sz).width(); + if (width > oldWidth) + m_perspectiveChooser->setFixedWidth(width); +} + +void DebuggerMainWindowPrivate::destroyPerspective(Perspective *perspective) +{ + savePerspectiveHelper(perspective); + m_perspectives.removeAll(perspective); + + // Dynamic perspectives are currently not visible in the chooser. + // This might change in the future, make sure we notice. + const int idx = indexInChooser(perspective); + QTC_ASSERT(idx == -1, m_perspectiveChooser->removeItem(idx)); + + // All dynamic perspectives currently have a static parent perspective. + // This might change in the future, make sure we notice. + QTC_CHECK(!perspective->d->m_parentPerspective.isEmpty()); + restorePerspective(findPerspective(perspective->d->m_parentPerspective)); +} + +void DebuggerMainWindow::showStatusMessage(const QString &message, int timeoutMS) +{ + d->m_statusLabel->showStatusMessage(message, timeoutMS); +} + +void DebuggerMainWindow::onModeChanged(Core::Id mode) +{ + if (mode == Debugger::Constants::MODE_DEBUG) { + setDockActionsVisible(true); + d->restorePerspective(nullptr); + } else { + setDockActionsVisible(false); + + // Hide dock widgets manually in case they are floating. + foreach (QDockWidget *dockWidget, dockWidgets()) { + if (dockWidget->isFloating()) + dockWidget->hide(); + } + } +} + +Perspective *DebuggerMainWindow::findPerspective(const QByteArray &perspectiveId) const +{ + return d->findPerspective(perspectiveId); +} + +QWidget *DebuggerMainWindow::centralWidgetStack() +{ + return d->m_centralWidgetStack; +} + +Perspective *DebuggerMainWindowPrivate::findPerspective(const QByteArray &perspectiveId) const +{ + return Utils::findOr(m_perspectives, nullptr, [&](Perspective *perspective) { + return perspective->d->m_id.toUtf8() == perspectiveId; + }); +} + +void DebuggerMainWindow::closeEvent(QCloseEvent *) +{ + d->savePerspectiveHelper(d->m_currentPerspective); +} + +void DebuggerMainWindowPrivate::resetCurrentPerspective() +{ + loadPerspectiveHelper(m_currentPerspective, false); +} + +int DebuggerMainWindowPrivate::indexInChooser(Perspective *perspective) const +{ + return perspective ? m_perspectiveChooser->findData(perspective->d->m_id) : -1; +} + +void DebuggerMainWindowPrivate::restorePerspective(Perspective *perspective) +{ + loadPerspectiveHelper(perspective, true); + + const int index = indexInChooser(m_currentPerspective); + if (index != -1) + m_perspectiveChooser->setCurrentIndex(index); +} + +void DebuggerMainWindowPrivate::ensureToolBarDockExists() +{ + if (m_toolBarDock) + return; + + auto viewButton = new QToolButton; + viewButton->setText(tr("&Views")); + + auto closeButton = new QToolButton(); + closeButton->setIcon(Utils::Icons::CLOSE_SPLIT_BOTTOM.icon()); + closeButton->setToolTip(tr("Leave Debug Mode")); + + auto toolbar = new Utils::StyledBar; + toolbar->setProperty("topBorder", true); + auto hbox = new QHBoxLayout(toolbar); + m_toolBarLayout = hbox; + hbox->setMargin(0); + hbox->setSpacing(0); + hbox->addWidget(m_perspectiveChooser); + // <- All perspective toolbars will get inserted here, but only + // the current perspective's toolbar is set visible. + hbox->addStretch(3); + hbox->addWidget(m_statusLabel); + hbox->addStretch(1); + hbox->addWidget(new Utils::StyledSeparator); + hbox->addWidget(viewButton); + hbox->addWidget(closeButton); auto dock = new QDockWidget(tr("Toolbar")); dock->setObjectName(QLatin1String("Toolbar")); @@ -287,9 +396,18 @@ void DebuggerMainWindow::finalizeSetup() dock->setTitleBarWidget(new QWidget(dock)); // hide title bar dock->setProperty("managed_dockwidget", QLatin1String("true")); dock->setWidget(toolbar); - m_toolbarDock = dock; + m_toolBarDock = dock; + q->addDockWidget(Qt::BottomDockWidgetArea, m_toolBarDock); - addDockWidget(Qt::BottomDockWidgetArea, dock); + connect(viewButton, &QAbstractButton::clicked, [this, viewButton] { + QMenu menu; + q->addDockActionsToMenu(&menu); + menu.exec(viewButton->mapToGlobal(QPoint())); + }); + + connect(closeButton, &QAbstractButton::clicked, [] { + ModeManager::activateMode(Core::Constants::MODE_EDIT); + }); } QWidget *createModeWindow(const Core::Id &mode, DebuggerMainWindow *mainWindow) @@ -342,13 +460,12 @@ QWidget *createModeWindow(const Core::Id &mode, DebuggerMainWindow *mainWindow) return splitter; } -void DebuggerMainWindow::loadPerspectiveHelper(Perspective *perspective, bool fromStoredSettings) +void DebuggerMainWindowPrivate::loadPerspectiveHelper(Perspective *perspective, bool fromStoredSettings) { // Clean up old perspective. if (m_currentPerspective) { savePerspectiveHelper(m_currentPerspective); for (QDockWidget *dock : m_dockForDockId) { - QTC_ASSERT(dock, continue); dock->setParent(nullptr); dock->widget()->setParent(nullptr); ActionManager::unregisterAction(dock->toggleViewAction(), @@ -357,22 +474,11 @@ void DebuggerMainWindow::loadPerspectiveHelper(Perspective *perspective, bool fr } m_dockForDockId.clear(); - ICore::removeAdditionalContext(Context(Id::fromName(m_currentPerspective->m_id))); + ICore::removeAdditionalContext(m_currentPerspective->context()); QWidget *central = m_currentPerspective->centralWidget(); m_centralWidgetStack->removeWidget(central ? central : m_editorPlaceHolder); - // Detach potentially re-used widgets to prevent deletion. - for (const Perspective::ToolbarOperation &op : m_currentPerspective->m_toolbarOperations) { - if (op.widget) - op.widget->setParent(nullptr); - if (op.toolbutton) - op.toolbutton->setParent(nullptr); - if (op.separator) - op.separator->setParent(nullptr); - } - - while (QLayoutItem *item = m_toolbuttonBoxLayout->takeAt(0)) - delete item; + m_currentPerspective->d->destroyToolBar(); } if (perspective) { @@ -380,201 +486,294 @@ void DebuggerMainWindow::loadPerspectiveHelper(Perspective *perspective, bool fr } else { const QSettings *settings = ICore::settings(); m_currentPerspective = findPerspective(settings->value(QLatin1String(LAST_PERSPECTIVE_KEY)).toByteArray()); - if (!m_currentPerspective) - m_currentPerspective = findPerspective(Debugger::Constants::CppPerspectiveId); + // If we don't find a perspective with the stored name, pick any. + // This can happen e.g. when a plugin was disabled that provided + // the stored perspective, or when the save file was modified externally. if (!m_currentPerspective && !m_perspectives.isEmpty()) m_currentPerspective = m_perspectives.first(); } QTC_ASSERT(m_currentPerspective, return); - ICore::addAdditionalContext(Context(Id::fromName(m_currentPerspective->m_id))); + ICore::addAdditionalContext(m_currentPerspective->context()); m_currentPerspective->aboutToActivate(); - for (const Perspective::ToolbarOperation &op : m_currentPerspective->m_toolbarOperations) { - if (op.widget) - m_toolbuttonBoxLayout->addWidget(op.widget); - if (op.toolbutton) - m_toolbuttonBoxLayout->addWidget(op.toolbutton); - if (op.separator) - m_toolbuttonBoxLayout->addWidget(op.separator); - } - - for (const Perspective::Operation &op : m_currentPerspective->m_operations) { + for (const DockOperation &op : m_currentPerspective->d->m_dockOperations) { QTC_ASSERT(op.widget, continue); const QByteArray dockId = op.widget->objectName().toUtf8(); QDockWidget *dock = m_dockForDockId.value(dockId); if (!dock) { QTC_CHECK(!dockId.isEmpty()); - dock = addDockForWidget(op.widget); + dock = q->addDockForWidget(op.widget); m_dockForDockId[dockId] = dock; QAction *toggleViewAction = dock->toggleViewAction(); toggleViewAction->setText(dock->windowTitle()); Command *cmd = ActionManager::registerAction(toggleViewAction, - Id("Dock.").withSuffix(dock->objectName()), - Context(Id::fromName(m_currentPerspective->m_id))); + Id("Dock.").withSuffix(dock->objectName()), + m_currentPerspective->context()); cmd->setAttribute(Command::CA_Hide); ActionManager::actionContainer(Core::Constants::M_WINDOW_VIEWS)->addAction(cmd); } // Restore parent/child relation, so that the widget hierarchy is clear. - dock->setParent(this); + dock->setParent(q); if (op.operationType == Perspective::Raise) { dock->raise(); continue; } - addDockWidget(op.area, dock); + q->addDockWidget(op.area, dock); QDockWidget *anchor = m_dockForDockId.value(op.anchorDockId); - if (!anchor && op.area == Qt::BottomDockWidgetArea) - anchor = m_toolbarDock; + if (!anchor && op.area == Qt::BottomDockWidgetArea) { + ensureToolBarDockExists(); + anchor = m_toolBarDock; + } + if (anchor) { switch (op.operationType) { case Perspective::AddToTab: - tabifyDockWidget(anchor, dock); + q->tabifyDockWidget(anchor, dock); break; case Perspective::SplitHorizontal: - splitDockWidget(anchor, dock, Qt::Horizontal); + q->splitDockWidget(anchor, dock, Qt::Horizontal); break; case Perspective::SplitVertical: - splitDockWidget(anchor, dock, Qt::Vertical); + q->splitDockWidget(anchor, dock, Qt::Vertical); break; default: break; } } - if (!op.visibleByDefault) - dock->hide(); - else - dock->show(); + dock->setVisible(op.visibleByDefault); } + m_currentPerspective->d->showToolBar(); + if (fromStoredSettings) { QSettings *settings = ICore::settings(); - settings->beginGroup(QString::fromLatin1(m_currentPerspective->m_id)); + settings->beginGroup(m_currentPerspective->d->m_id); if (settings->value(QLatin1String("ToolSettingsSaved"), false).toBool()) - restoreSettings(settings); + q->restoreSettings(settings); settings->endGroup(); } else { // By default, show the central widget - showCentralWidgetAction()->setChecked(true); + q->showCentralWidgetAction()->setChecked(true); } QWidget *central = m_currentPerspective->centralWidget(); m_centralWidgetStack->addWidget(central ? central : m_editorPlaceHolder); - showCentralWidgetAction()->setText(central ? central->windowTitle() : tr("Editor")); + q->showCentralWidgetAction()->setText(central ? central->windowTitle() : tr("Editor")); m_statusLabel->clear(); } -void DebuggerMainWindow::savePerspectiveHelper(const Perspective *perspective) +void DebuggerMainWindowPrivate::savePerspectiveHelper(const Perspective *perspective) { if (!perspective) return; QSettings *settings = ICore::settings(); - settings->beginGroup(QString::fromLatin1(perspective->m_id)); - saveSettings(settings); + settings->beginGroup(perspective->d->m_id); + q->saveSettings(settings); settings->setValue(QLatin1String("ToolSettingsSaved"), true); settings->endGroup(); - settings->setValue(QLatin1String(LAST_PERSPECTIVE_KEY), perspective->m_id); + settings->setValue(QLatin1String(LAST_PERSPECTIVE_KEY), perspective->d->m_id); } // Perspective -Perspective::Perspective(const QByteArray &id, const QString &name) - : m_id(id), m_name(name) +Perspective::Perspective(const QString &id, const QString &name) + : d(new PerspectivePrivate) { + d->m_id = id; + d->m_name = name; } Perspective::~Perspective() { - for (const ToolbarOperation &op : m_toolbarOperations) { - // op.widget and op.actions are owned by the plugins - delete op.toolbutton; - delete op.separator; - } + delete d; } void Perspective::setCentralWidget(QWidget *centralWidget) { - QTC_ASSERT(m_centralWidget == nullptr, return); - m_centralWidget = centralWidget; + QTC_ASSERT(d->m_centralWidget == nullptr, return); + d->m_centralWidget = centralWidget; } QString Perspective::name() const { - return m_name; + return d->m_name; +} + +QString Perspective::id() const +{ + return d->m_id; } void Perspective::setAboutToActivateCallback(const Perspective::Callback &cb) { - m_aboutToActivateCallback = cb; + d->m_aboutToActivateCallback = cb; } void Perspective::aboutToActivate() const { - if (m_aboutToActivateCallback) - m_aboutToActivateCallback(); -} - -QByteArray Perspective::parentPerspective() const -{ - return m_parentPerspective; + if (d->m_aboutToActivateCallback) + d->m_aboutToActivateCallback(); } void Perspective::setParentPerspective(const QByteArray &parentPerspective) { - m_parentPerspective = parentPerspective; + d->m_parentPerspective = parentPerspective; } -QToolButton *Perspective::addToolbarAction(QAction *action, const QIcon &toolbarIcon) +void Perspective::setEnabled(bool enabled) { - ToolbarOperation op; - op.action = action; - op.icon = toolbarIcon; - op.toolbutton = new QToolButton; - // QStyle::polish is called before it is added to the toolbar, explicitly make it a panel widget - op.toolbutton->setProperty("panelwidget", true); - op.toolbutton->setDefaultAction(toolbarIcon.isNull() - ? action : ProxyAction::proxyActionWithIcon(action, toolbarIcon)); - m_toolbarOperations.append(op); - - return op.toolbutton; + const int index = theMainWindow->d->indexInChooser(this); + QTC_ASSERT(index != -1, return); + auto model = qobject_cast(theMainWindow->d->m_perspectiveChooser->model()); + QTC_ASSERT(model, return); + QStandardItem *item = model->item(index, 0); + item->setFlags(enabled ? item->flags() | Qt::ItemIsEnabled : item->flags() & ~Qt::ItemIsEnabled ); } -void Perspective::addToolbarWidget(QWidget *widget) +void Perspective::addToolBarAction(QAction *action) +{ + auto toolButton = new QToolButton; + ToolbarOperation op; + op.toolBarPlainAction = action; + op.toolBarPlainActionButton = toolButton; + d->m_toolBarOperations.append(op); +} + +void Perspective::addToolBarAction(OptionalAction *action) { ToolbarOperation op; - op.widget = widget; + op.toolBarAction = action; + d->m_toolBarOperations.append(op); +} + +void Perspective::addToolBarWidget(QWidget *widget) +{ + ToolbarOperation op; + op.toolBarWidget = widget; // QStyle::polish is called before it is added to the toolbar, explicitly make it a panel widget - op.widget->setProperty("panelwidget", true); - m_toolbarOperations.append(op); + op.toolBarWidget->setProperty("panelwidget", true); + d->m_toolBarOperations.append(op); } void Perspective::addToolbarSeparator() { ToolbarOperation op; op.separator = new StyledSeparator; - m_toolbarOperations.append(op); + d->m_toolBarOperations.append(op); } QWidget *Perspective::centralWidget() const { - return m_centralWidget; + return d->m_centralWidget; } -void Perspective::addWindow(QWidget *widget, OperationType type, QWidget *anchorWidget, - bool visibleByDefault, Qt::DockWidgetArea area) +void Perspective::destroy() { - Operation op; + theMainWindow->d->destroyPerspective(this); +} + +Perspective *Perspective::currentPerspective() +{ + return theMainWindow ? theMainWindow->d->m_currentPerspective : nullptr; +} + +Context Perspective::context() const +{ + return Context(Id::fromName(d->m_id.toUtf8())); +} + +void PerspectivePrivate::showToolBar() +{ + if (!m_toolButtonBox) { + m_toolButtonBox = new QWidget; + theMainWindow->d->m_toolBarLayout->insertWidget(1, m_toolButtonBox); + + auto hbox = new QHBoxLayout(m_toolButtonBox); + hbox->setMargin(0); + hbox->setSpacing(0); + for (ToolbarOperation &op : m_toolBarOperations) + op.attachToToolbar(m_toolButtonBox); + } + + for (ToolbarOperation &op : m_toolBarOperations) { + if (op.toolBarAction) + op.toolBarAction->m_toolButton->setVisible(op.toolBarAction->isVisible()); + } + + m_toolButtonBox->setVisible(true); +} + +void PerspectivePrivate::hideToolBar() +{ + if (m_toolButtonBox) + m_toolButtonBox->setVisible(false); + +} +void PerspectivePrivate::destroyToolBar() +{ + // Detach potentially re-used widgets to prevent deletion. + for (ToolbarOperation &op : m_toolBarOperations) + op.detachFromToolbar(); + + delete m_toolButtonBox; + m_toolButtonBox = nullptr; +} + +void Perspective::addWindow(QWidget *widget, + Perspective::OperationType type, + QWidget *anchorWidget, + bool visibleByDefault, + Qt::DockWidgetArea area) +{ + DockOperation op; op.widget = widget; if (anchorWidget) op.anchorDockId = anchorWidget->objectName().toUtf8(); op.operationType = type; op.visibleByDefault = visibleByDefault; op.area = area; - m_operations.append(op); + d->m_dockOperations.append(op); +} + +void Perspective::select() +{ + if (ModeManager::currentModeId() == Debugger::Constants::MODE_DEBUG && currentPerspective() == this) { + // Prevents additional show events triggering modules and register updates. + return; + } + + ModeManager::activateMode(Debugger::Constants::MODE_DEBUG); + theMainWindow->d->restorePerspective(this); +} + +// ToolbarAction + +OptionalAction::OptionalAction(const QString &text) + : QAction(text) +{ +} + +OptionalAction::~OptionalAction() +{ + delete m_toolButton; +} + +void OptionalAction::setVisible(bool on) +{ + QAction::setVisible(on); + if (m_toolButton) + m_toolButton->setVisible(on); +} + +void OptionalAction::setToolButtonStyle(Qt::ToolButtonStyle style) +{ + m_toolButtonStyle = style; } } // Utils diff --git a/src/plugins/debugger/debuggermainwindow.h b/src/plugins/debugger/debuggermainwindow.h index 30e646ccea2..0721d5b5c5d 100644 --- a/src/plugins/debugger/debuggermainwindow.h +++ b/src/plugins/debugger/debuggermainwindow.h @@ -30,28 +30,44 @@ #include #include -#include +#include #include -#include +#include #include -QT_BEGIN_NAMESPACE -class QComboBox; -class QToolButton; -class QStackedWidget; -QT_END_NAMESPACE - -namespace Core { class Id; } +namespace Core { +class Context; +class Id; +} // Core namespace Utils { -class DEBUGGER_EXPORT Perspective +// To be used for actions that need hideable toolbuttons. +class DEBUGGER_EXPORT OptionalAction : public QAction { + Q_OBJECT + +public: + OptionalAction(const QString &text = QString()); + ~OptionalAction() override; + + void setVisible(bool on); + void setToolButtonStyle(Qt::ToolButtonStyle style); + +public: + QPointer m_toolButton; + Qt::ToolButtonStyle m_toolButtonStyle = Qt::ToolButtonIconOnly; +}; + +class DEBUGGER_EXPORT Perspective : public QObject +{ + Q_OBJECT + public: enum OperationType { SplitVertical, SplitHorizontal, AddToTab, Raise }; - explicit Perspective(const QByteArray &id, const QString &name); + explicit Perspective(const QString &id, const QString &name); ~Perspective(); void setCentralWidget(QWidget *centralWidget); @@ -61,54 +77,36 @@ public: bool visibleByDefault = true, Qt::DockWidgetArea area = Qt::BottomDockWidgetArea); - QToolButton *addToolbarAction(QAction *action, const QIcon &toolbarIcon = QIcon()); - void addToolbarWidget(QWidget *widget); + void addToolBarAction(QAction *action); + void addToolBarAction(OptionalAction *action); + void addToolBarWidget(QWidget *widget); void addToolbarSeparator(); QWidget *centralWidget() const; QString name() const; + QString id() const; using Callback = std::function; void setAboutToActivateCallback(const Callback &cb); void aboutToActivate() const; - QByteArray parentPerspective() const; void setParentPerspective(const QByteArray &parentPerspective); + void setEnabled(bool enabled); + + void destroy(); + void select(); + static Perspective *currentPerspective(); + + Core::Context context() const; + + void showToolBar(); + void hideToolBar(); private: - Perspective(const Perspective &) = delete; - void operator=(const Perspective &) = delete; - friend class DebuggerMainWindow; + friend class DebuggerMainWindowPrivate; class PerspectivePrivate *d = nullptr; - class Operation - { - public: - QPointer widget; - QByteArray anchorDockId; - OperationType operationType = Raise; - bool visibleByDefault = true; - Qt::DockWidgetArea area = Qt::BottomDockWidgetArea; - }; - - class ToolbarOperation - { - public: - QPointer widget; // Owned by plugin if present - QPointer action; // Owned by plugin if present - QPointer toolbutton; // Owned here in case action is used - QPointer separator; - QIcon icon; - }; - - const QByteArray m_id; - QString m_name; - QByteArray m_parentPerspective; - QVector m_operations; - QPointer m_centralWidget; - Callback m_aboutToActivateCallback; - QVector m_toolbarOperations; }; class DEBUGGER_EXPORT DebuggerMainWindow : public FancyMainWindow @@ -120,42 +118,18 @@ public: ~DebuggerMainWindow() override; void registerPerspective(Perspective *perspective); - void destroyDynamicPerspective(Perspective *perspective); - - void resetCurrentPerspective(); - void restorePerspective(Perspective *perspective); - - void finalizeSetup(); - void showStatusMessage(const QString &message, int timeoutMS); - void raiseDock(const QByteArray &dockId); - QByteArray currentPerspective() const; - QStackedWidget *centralWidgetStack() const { return m_centralWidgetStack; } - void onModeChanged(Core::Id mode); - void setPerspectiveEnabled(const QByteArray &perspectiveId, bool enabled); - Perspective *findPerspective(const QByteArray &perspectiveId) const; + QWidget *centralWidgetStack(); private: - void closeEvent(QCloseEvent *) final { savePerspectiveHelper(m_currentPerspective); } + void closeEvent(QCloseEvent *) final; - void loadPerspectiveHelper(Perspective *perspective, bool fromStoredSettings = true); - void savePerspectiveHelper(const Perspective *perspective); - void increaseChooserWidthIfNecessary(const QString &visibleName); - int indexInChooser(Perspective *perspective) const; - - Perspective *m_currentPerspective = nullptr; - QComboBox *m_perspectiveChooser = nullptr; - QHBoxLayout *m_toolbuttonBoxLayout = nullptr; - QStackedWidget *m_centralWidgetStack = nullptr; - QWidget *m_editorPlaceHolder = nullptr; - Utils::StatusLabel *m_statusLabel = nullptr; - QDockWidget *m_toolbarDock = nullptr; - - QHash m_dockForDockId; - QList m_perspectives; + friend class Perspective; + friend class PerspectivePrivate; + class DebuggerMainWindowPrivate *d = nullptr; }; DEBUGGER_EXPORT QWidget *createModeWindow(const Core::Id &mode, DebuggerMainWindow *mainWindow); diff --git a/src/plugins/debugger/debuggerplugin.cpp b/src/plugins/debugger/debuggerplugin.cpp index 3c94e2339b5..2df8a751410 100644 --- a/src/plugins/debugger/debuggerplugin.cpp +++ b/src/plugins/debugger/debuggerplugin.cpp @@ -25,7 +25,6 @@ #include "debuggerplugin.h" -#include "debuggermainwindow.h" #include "debuggeractions.h" #include "debuggerinternalconstants.h" #include "debuggercore.h" @@ -44,7 +43,6 @@ #include "disassemblerlines.h" #include "logwindow.h" #include "moduleshandler.h" -#include "snapshotwindow.h" #include "stackhandler.h" #include "stackwindow.h" #include "watchhandler.h" @@ -55,6 +53,7 @@ #include "localsandexpressionswindow.h" #include "loadcoredialog.h" #include "sourceutils.h" +#include "snapshothandler.h" #include "shared/hostutils.h" #include "console/console.h" @@ -399,40 +398,37 @@ namespace Internal { void addCdbOptionPages(QList *opts); void addGdbOptionPages(QList *opts); -static QIcon visibleStartIcon(Id id, bool toolBarStyle) +static QIcon startIcon(bool toolBarStyle) { - if (id == Id(Constants::DEBUG)) { - 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; - } else if (id == Id(Constants::CONTINUE)) { - 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; - } else if (id == Id(Constants::INTERRUPT)) { - 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; - } - return QIcon(); + 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 void setProxyAction(ProxyAction *proxy, Id id) +static QIcon continueIcon(bool toolBarStyle) { - proxy->setAction(ActionManager::command(id)->action()); - proxy->setIcon(visibleStartIcon(id, true)); + 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, @@ -459,50 +455,6 @@ QAction *addCheckableAction(QMenu *menu, const QString &display, bool on, bool c return act; } -/////////////////////////////////////////////////////////////////////// -// -// DummyEngine -// -/////////////////////////////////////////////////////////////////////// - -class DummyEngine : public DebuggerEngine -{ -public: - DummyEngine() = default; - ~DummyEngine() override = default; - - void setupEngine() override {} - void runEngine() override {} - void shutdownEngine() override {} - void shutdownInferior() override {} - bool hasCapability(unsigned cap) const override; - bool acceptsBreakpoint(Breakpoint) const override { return false; } - bool acceptsDebuggerCommands() const override { return false; } - void selectThread(ThreadId) override {} -}; - -bool DummyEngine::hasCapability(unsigned cap) const -{ - // This can only be a first approximation of what to expect when running. - Project *project = ProjectTree::currentProject(); - if (!project) - return false; - Target *target = project->activeTarget(); - QTC_ASSERT(target, return 0); - RunConfiguration *activeRc = target->activeRunConfiguration(); - QTC_ASSERT(activeRc, return 0); - - // This is a non-started Cdb or Gdb engine: - if (activeRc->extraAspect()->useCppDebugger()) - return cap & (WatchpointByAddressCapability - | BreakConditionCapability - | TracePointCapability - | OperateByInstructionCapability); - - // This is a Qml or unknown engine. - return cap & AddWatcherCapability; -} - /////////////////////////////////////////////////////////////////////// // // DebugMode @@ -530,18 +482,14 @@ public: // /////////////////////////////////////////////////////////////////////// -static QWidget *addSearch(BaseTreeView *treeView, const QString &title, - const QString &objectName) +QWidget *addSearch(BaseTreeView *treeView) { QAction *act = action(UseAlternatingRowColors); treeView->setAlternatingRowColors(act->isChecked()); QObject::connect(act, &QAction::toggled, treeView, &BaseTreeView::setAlternatingRowColors); - QWidget *widget = ItemViewFind::createSearchableWrapper(treeView); - widget->setObjectName(objectName); - widget->setWindowTitle(title); - return widget; + return ItemViewFind::createSearchableWrapper(treeView); } static Kit::Predicate cdbPredicate(char wordWidth = 0) @@ -626,6 +574,35 @@ static DebuggerPluginPrivate *dd = nullptr; Implementation of DebuggerCore. */ +struct Callback +{ + Callback() + : cb([]{}) + {} + Callback(void (DebuggerEngine::*func)()) + : cb([func] { if (DebuggerEngine *engine = EngineManager::currentEngine()) (engine->*func)(); }) + {} + + std::function cb; +}; + +struct Action : public QAction +{ + Action(const QString &name, const QIcon &icon = {}) : QAction(name) { setIcon(icon); } + Action(const QString &name, const QIcon &icon, Callback cb, const QString &toolTip = {}) + : Action(name, icon) + { + m_cb = cb; + setToolTip(toolTip); + connect(this, &QAction::triggered, this, &Action::onTriggered); + } + void onTriggered() + { + m_cb.cb(); + } + Callback m_cb; +}; + class DebuggerPluginPrivate : public QObject { Q_OBJECT @@ -639,19 +616,6 @@ public: void aboutToShutdown(); void doShutdown(); - void connectEngine(DebuggerRunTool *runTool); - void disconnectEngine() { connectEngine(nullptr); } - DebuggerEngine *dummyEngine(); - - void setThreadBoxContents(const QStringList &list, int index) - { - QSignalBlocker blocker(m_threadBox); - m_threadBox->clear(); - for (const QString &item : list) - m_threadBox->addItem(item); - m_threadBox->setCurrentIndex(index); - } - RunControl *attachToRunningProcess(Kit *kit, DeviceProcessItem process, bool contAfterAttach); void writeSettings() @@ -660,15 +624,6 @@ public: // writeWindowSettings(); } - void selectThread(int index) - { - QTC_ASSERT(m_currentRunTool, return); - DebuggerEngine *engine = m_currentRunTool->activeEngine(); - QTC_ASSERT(engine, return); - ThreadId id = engine->threadsHandler()->threadAt(index); - engine->selectThread(id); - } - void breakpointSetMarginActionTriggered(bool isMessageOnly, const ContextData &data) { QString message; @@ -693,41 +648,17 @@ public: return; message = dialog.textValue(); } - toggleBreakpoint(data, message); + BreakpointManager::toggleBreakpoint(data, message); } - void updateReturnViewHeader(int section, int, int newSize) - { - if (m_shuttingDown) - return; - m_returnView->header()->resizeSection(section, newSize); - } - - void synchronizeBreakpoints() - { - showMessage(QLatin1String("ATTEMPT SYNC"), LogDebug); - for (int i = 0, n = m_snapshotHandler->size(); i != n; ++i) { - if (DebuggerEngine *engine = m_snapshotHandler->at(i)->engine()) - engine->attemptBreakpointSynchronization(); - } - } - - void reloadSourceFiles() { if (m_currentRunTool) m_currentRunTool->engine()->reloadSourceFiles(); } - void reloadRegisters() { if (m_currentRunTool) m_currentRunTool->engine()->reloadRegisters(); } - void reloadModules() { if (m_currentRunTool) m_currentRunTool->engine()->reloadModules(); } - void editorOpened(IEditor *editor); void updateBreakMenuItem(IEditor *editor); - void setBusyCursor(bool busy); void requestMark(TextEditorWidget *widget, int lineNumber, TextMarkRequestKind kind); void requestContextMenu(TextEditorWidget *widget, int lineNumber, QMenu *menu); - void activatePreviousMode(); - void activateDebugMode(); void toggleBreakpointHelper(); - void toggleBreakpoint(const ContextData &location, const QString &tracePointMessage = QString()); void onModeChanged(Id mode); void updateDebugWithoutDeployMenu(); @@ -738,225 +669,26 @@ public: void runScheduled(); void attachCore(); - void enableReverseDebuggingTriggered(const QVariant &value); - void showStatusMessage(const QString &msg, int timeout = -1); - - void runControlStarted(DebuggerRunTool *runTool); void runControlFinished(DebuggerRunTool *runTool); void remoteCommand(const QStringList &options); - void displayDebugger(DebuggerRunTool *runTool); - void dumpLog(); - void cleanupViews(); void setInitialState(); - void fontSettingsChanged(const FontSettings &settings); - - void updateState(DebuggerRunTool *runTool); - void onCurrentProjectChanged(Project *project); + void onStartupProjectChanged(Project *project); void sessionLoaded(); void aboutToUnloadSession(); void aboutToSaveSession(); -public: - void updateDebugActions(); - - void handleExecDetach() - { - currentEngine()->resetLocation(); - currentEngine()->detachDebugger(); - } - - void handleExecContinue() - { - currentEngine()->resetLocation(); - currentEngine()->continueInferior(); - } - - void handleExecInterrupt() - { - currentEngine()->resetLocation(); - currentEngine()->requestInterruptInferior(); - } - - void handleAbort() - { - if (dd->m_currentRunTool) - dd->m_currentRunTool->abortDebugger(); - } - - void handleReset() - { - currentEngine()->resetLocation(); - currentEngine()->resetInferior(); - } - - void handleExecStep() - { - if (currentEngine()->state() == DebuggerNotReady) { - DebuggerRunTool::setBreakOnMainNextTime(); - ProjectExplorerPlugin::runStartupProject(ProjectExplorer::Constants::DEBUG_RUN_MODE); - } else { - currentEngine()->resetLocation(); - if (boolSetting(OperateByInstruction)) - currentEngine()->executeStepI(); - else - currentEngine()->executeStep(); - } - } - - void handleExecNext() - { - if (currentEngine()->state() == DebuggerNotReady) { - DebuggerRunTool::setBreakOnMainNextTime(); - ProjectExplorerPlugin::runStartupProject(ProjectExplorer::Constants::DEBUG_RUN_MODE); - } else { - currentEngine()->resetLocation(); - if (boolSetting(OperateByInstruction)) - currentEngine()->executeNextI(); - else - currentEngine()->executeNext(); - } - } - - void handleExecStepOut() - { - currentEngine()->resetLocation(); - currentEngine()->executeStepOut(); - } - - void handleExecReturn() - { - currentEngine()->resetLocation(); - currentEngine()->executeReturn(); - } - - void handleExecJumpToLine() - { - currentEngine()->resetLocation(); - if (BaseTextEditor *textEditor = BaseTextEditor::currentTextEditor()) { - ContextData location = getLocationContext(textEditor->textDocument(), - textEditor->currentLine()); - if (location.isValid()) - currentEngine()->executeJumpToLine(location); - } - } - - void handleExecRunToLine() - { - currentEngine()->resetLocation(); - if (BaseTextEditor *textEditor = BaseTextEditor::currentTextEditor()) { - ContextData location = getLocationContext(textEditor->textDocument(), - textEditor->currentLine()); - if (location.isValid()) - currentEngine()->executeRunToLine(location); - } - } - - void handleExecRunToSelectedFunction() - { - BaseTextEditor *textEditor = BaseTextEditor::currentTextEditor(); - QTC_ASSERT(textEditor, return); - QTextCursor cursor = textEditor->textCursor(); - QString functionName = cursor.selectedText(); - if (functionName.isEmpty()) { - const QTextBlock block = cursor.block(); - const QString line = block.text(); - foreach (const QString &str, line.trimmed().split(QLatin1Char('('))) { - QString a; - for (int i = str.size(); --i >= 0; ) { - if (!str.at(i).isLetterOrNumber()) - break; - a = str.at(i) + a; - } - if (!a.isEmpty()) { - functionName = a; - break; - } - } - } - - if (functionName.isEmpty()) { - showStatusMessage(tr("No function selected.")); - } else { - showStatusMessage(tr("Running to function \"%1\".") - .arg(functionName)); - currentEngine()->resetLocation(); - currentEngine()->executeRunToFunction(functionName); - } - } - - void handleAddToWatchWindow() - { - // Requires a selection, but that's the only case we want anyway. - BaseTextEditor *textEditor = BaseTextEditor::currentTextEditor(); - if (!textEditor) - return; - QTextCursor tc = textEditor->textCursor(); - QString exp; - if (tc.hasSelection()) { - exp = tc.selectedText(); - } else { - int line, column; - exp = cppExpressionAt(textEditor->editorWidget(), tc.position(), &line, &column); - } - if (currentEngine()->hasCapability(WatchComplexExpressionsCapability)) - exp = removeObviousSideEffects(exp); - else - exp = fixCppExpression(exp); - exp = exp.trimmed(); - if (exp.isEmpty()) { - // Happens e.g. when trying to evaluate 'char' or 'return'. - AsynchronousMessageBox::warning(tr("Warning"), - tr("Select a valid expression to evaluate.")); - return; - } - currentEngine()->watchHandler()->watchVariable(exp); - } - - void stopDebugger() - { - QTC_ASSERT(dd->m_currentRunTool, return); - dd->m_currentRunTool->runControl()->initiateStop(); - } - - void handleFrameDown() - { - currentEngine()->frameDown(); - } - - void handleFrameUp() - { - currentEngine()->frameUp(); - } - - void handleOperateByInstructionTriggered(bool operateByInstructionTriggered) - { - // Go to source only if we have the file. - if (DebuggerEngine *cppEngine = currentEngine()->cppEngine()) { - if (cppEngine->stackHandler()->currentIndex() >= 0) { - const StackFrame frame = cppEngine->stackHandler()->currentFrame(); - if (operateByInstructionTriggered || frame.isUsable()) - cppEngine->gotoLocation(Location(frame, true)); - } - } - } - - void showMessage(const QString &msg, int channel, int timeout = -1); + void handleOperateByInstructionTriggered(bool operateByInstructionTriggered); bool parseArgument(QStringList::const_iterator &it, const QStringList::const_iterator &cend, QString *errorMessage); bool parseArguments(const QStringList &args, QString *errorMessage); void parseCommandLineArguments(); - // Called when all dependent plugins have loaded. - void initialize(); - - void updateUiForProject(ProjectExplorer::Project *project); - void updateUiForTarget(ProjectExplorer::Target *target); - void updateActiveLanguages(); + void updatePresetState(); public: QPointer m_mainWindow; @@ -965,93 +697,67 @@ public: ActionContainer *m_menu = nullptr; - Project *m_previousProject = nullptr; - QPointer m_previousTarget; - QPointer m_previousRunConfiguration; - - Id m_previousMode; QVector m_scheduledStarts; - ProxyAction *m_visibleStartAction = nullptr; - ProxyAction *m_hiddenStopAction = nullptr; - QAction *m_startAction = nullptr; - QAction *m_debugWithoutDeployAction = nullptr; - QAction *m_startAndDebugApplicationAction = nullptr; - QAction *m_attachToRunningApplication = nullptr; - QAction *m_attachToUnstartedApplication = nullptr; - QAction *m_attachToQmlPortAction = nullptr; - QAction *m_attachToRemoteServerAction = nullptr; - QAction *m_startRemoteCdbAction = nullptr; - QAction *m_attachToCoreAction = nullptr; - QAction *m_detachAction = nullptr; - QAction *m_continueAction = nullptr; - QAction *m_exitAction = nullptr; // On application output button if "Stop" is possible - QAction *m_interruptAction = nullptr; // On the fat debug button if "Pause" is possible - QAction *m_undisturbableAction = nullptr; // On the fat debug button if nothing can be done - QAction *m_abortAction = nullptr; - QAction *m_stepAction = nullptr; - QAction *m_stepOutAction = nullptr; - QAction *m_runToLineAction = nullptr; // In the debug menu - QAction *m_runToSelectedFunctionAction = nullptr; - QAction *m_jumpToLineAction = nullptr; // In the Debug menu. - QAction *m_returnFromFunctionAction = nullptr; - QAction *m_nextAction = nullptr; - QAction *m_watchAction = nullptr; + 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...")}; + + Action m_detachAction{tr("Detach Debugger"), {}, &DebuggerEngine::handleExecDetach}; + Action m_continueAction{tr("Continue"), continueIcon(false), &DebuggerEngine::handleExecContinue}; + + // On application output button if "Stop" is possible + Action m_exitAction{tr("Stop Debugger"), Icons::DEBUG_EXIT_SMALL.icon(), &DebuggerEngine::quitDebugger}; + + // On the fat debug button if "Pause" is possible + Action m_interruptAction{tr("Interrupt"), interruptIcon(false), &DebuggerEngine::handleExecInterrupt}; + Action m_abortAction{tr("Abort Debugging"), {}, &DebuggerEngine::abortDebugger, + tr("Aborts debugging and resets the debugger to the initial state.")}; + QAction m_stepAction{tr("Step Into")}; + Action m_stepOutAction{tr("Step Out"), Icons::STEP_OUT.icon(), &DebuggerEngine::handleExecStepOut}; + + Action m_runToLineAction{tr("Run to Line"), {}, &DebuggerEngine::handleExecRunToLine}; + Action m_runToSelectedFunctionAction{tr("Run to Selected Function"), {}, &DebuggerEngine::handleExecRunToSelectedFunction}; + Action m_jumpToLineAction{tr("Jump to Line"), {}, &DebuggerEngine::handleExecJumpToLine}; + // In the Debug menu. + Action m_returnFromFunctionAction{tr("Immediately Return From Inner Function"), {}, &DebuggerEngine::executeReturn}; + QAction m_nextAction{tr("Step Over")}; + Action m_watchAction{tr("Add Expression Evaluator"), {}, &DebuggerEngine::handleAddToWatchWindow}; Command *m_watchCommand = nullptr; - QAction *m_breakAction = nullptr; - QAction *m_reverseDirectionAction = nullptr; - QAction *m_frameUpAction = nullptr; - QAction *m_frameDownAction = nullptr; - QAction *m_resetAction = nullptr; - QAction *m_operateByInstructionAction = nullptr; + QAction m_breakAction{tr("Toggle Breakpoint")}; + Action m_frameUpAction{tr("Move to Calling Frame"), {}, &DebuggerEngine::handleFrameDown}; + Action m_frameDownAction{tr("Move to Called Frame"), {}, &DebuggerEngine::handleFrameUp}; + Action m_resetAction{tr("Restart Debugging"), Icons::RESTART_TOOLBAR.icon(), &DebuggerEngine::handleReset, + tr("Restart the debugging session.")}; + Action m_openMemoryEditorAction{tr("Memory..."), {}, &DebuggerEngine::openMemoryEditor}; - QToolButton *m_reverseToolButton = nullptr; + BreakpointManager m_breakpointManager; + QPointer m_breakpointManagerView; + QPointer m_breakpointManagerWindow; - QLabel *m_threadLabel = nullptr; - QComboBox *m_threadBox = nullptr; + QPointer m_engineManagerView; + QPointer m_engineManagerWindow; + QPointer m_globalLogWindow; - BaseTreeView *m_breakView = nullptr; - BaseTreeView *m_returnView = nullptr; - BaseTreeView *m_localsView = nullptr; - BaseTreeView *m_watchersView = nullptr; - WatchTreeView *m_inspectorView = nullptr; - BaseTreeView *m_registerView = nullptr; - BaseTreeView *m_modulesView = nullptr; - BaseTreeView *m_snapshotView = nullptr; - BaseTreeView *m_sourceFilesView = nullptr; - BaseTreeView *m_stackView = nullptr; - BaseTreeView *m_threadsView = nullptr; - - BreakHandler *m_breakHandler = nullptr; - - QPointer m_returnWindow; - QPointer m_localsWindow; - QPointer m_watchersWindow; - QPointer m_inspectorWindow; - QPointer m_localsAndInspectorWindow; - QPointer m_breakWindow; - QPointer m_registerWindow; - QPointer m_modulesWindow; - QPointer m_snapshotWindow; - QPointer m_sourceFilesWindow; - QPointer m_stackWindow; - QPointer m_threadsWindow; - QPointer m_logWindow; - - bool m_busy = false; QString m_lastPermanentStatusMessage; DebuggerPlugin *m_plugin = nullptr; - SnapshotHandler *m_snapshotHandler = nullptr; + EngineManager m_engineManager; QTimer m_shutdownTimer; bool m_shuttingDown = false; - QPointer m_previouslyActiveEngine; - QPointer m_currentRunTool; DebuggerSettings *m_debuggerSettings = nullptr; QStringList m_arguments; DebuggerToolTipManager m_toolTipManager; - DummyEngine *m_dummyEngine = nullptr; const QSharedPointer m_globalDebuggerOptions; DebuggerItemManager m_debuggerItemManager; @@ -1083,36 +789,6 @@ DebuggerPluginPrivate::~DebuggerPluginPrivate() delete m_debuggerSettings; m_debuggerSettings = nullptr; - - delete m_snapshotHandler; - m_snapshotHandler = nullptr; - - delete m_breakHandler; - m_breakHandler = nullptr; - - delete m_returnWindow; - delete m_localsWindow; - delete m_watchersWindow; - delete m_inspectorWindow; - delete m_localsAndInspectorWindow; - delete m_breakWindow; - delete m_registerWindow; - delete m_modulesWindow; - delete m_snapshotWindow; - delete m_sourceFilesWindow; - delete m_stackWindow; - delete m_threadsWindow; - delete m_logWindow; -} - -DebuggerEngine *DebuggerPluginPrivate::dummyEngine() -{ - if (!m_dummyEngine) { - m_dummyEngine = new DummyEngine; - m_dummyEngine->setParent(this); - m_dummyEngine->setObjectName("DummyEngine"); - } - return m_dummyEngine; } static QString msgParameterMissing(const QString &a) @@ -1318,12 +994,7 @@ bool DebuggerPluginPrivate::initialize(const QStringList &arguments, // Populate Windows->Views menu with standard actions. Context debugcontext(Constants::C_DEBUGMODE); - auto openMemoryEditorAction = new QAction(this); - openMemoryEditorAction->setText(DebuggerPluginPrivate::tr("Memory...")); - connect(openMemoryEditorAction, &QAction::triggered, - this, &Internal::openMemoryEditor); - - Command *cmd = ActionManager::registerAction(openMemoryEditorAction, + Command *cmd = ActionManager::registerAction(&m_openMemoryEditorAction, "Debugger.Views.OpenMemoryEditor", debugcontext); cmd->setAttribute(Command::CA_Hide); @@ -1332,8 +1003,6 @@ bool DebuggerPluginPrivate::initialize(const QStringList &arguments, TaskHub::addCategory(TASK_CATEGORY_DEBUGGER_RUNTIME, tr("Debugger Runtime")); - const QKeySequence debugKey = QKeySequence(useMacShortcuts ? tr("Ctrl+Y") : tr("F5")); - QSettings *settings = ICore::settings(); m_debuggerSettings = new DebuggerSettings; @@ -1342,215 +1011,70 @@ bool DebuggerPluginPrivate::initialize(const QStringList &arguments, const Context cppDebuggercontext(C_CPPDEBUGGER); const Context qmljsDebuggercontext(C_QMLDEBUGGER); - m_busy = false; - - m_logWindow = new LogWindow; - m_logWindow->setObjectName(QLatin1String(DOCKWIDGET_OUTPUT)); - - m_breakHandler = new BreakHandler; - m_breakView = new BaseTreeView; - m_breakView->setIconSize(QSize(10, 10)); - m_breakView->setWindowIcon(Icons::BREAKPOINTS.icon()); - m_breakView->setSelectionMode(QAbstractItemView::ExtendedSelection); - connect(action(UseAddressInBreakpointsView), &QAction::toggled, - this, [this](bool on) { m_breakView->setColumnHidden(BreakpointAddressColumn, !on); }); - m_breakView->setSettings(settings, "Debugger.BreakWindow"); - m_breakView->setModel(m_breakHandler->model()); - m_breakView->setRootIsDecorated(true); - m_breakWindow = addSearch(m_breakView, tr("&Breakpoints"), DOCKWIDGET_BREAK); - - m_modulesView = new BaseTreeView; - m_modulesView->setSortingEnabled(true); - m_modulesView->setSettings(settings, "Debugger.ModulesView"); - connect(m_modulesView, &BaseTreeView::aboutToShow, - this, &DebuggerPluginPrivate::reloadModules, - Qt::QueuedConnection); - m_modulesWindow = addSearch(m_modulesView, tr("&Modules"), DOCKWIDGET_MODULES); - - m_registerView = new BaseTreeView; - m_registerView->setRootIsDecorated(true); - m_registerView->setSettings(settings, "Debugger.RegisterView"); - connect(m_registerView, &BaseTreeView::aboutToShow, - this, &DebuggerPluginPrivate::reloadRegisters, - Qt::QueuedConnection); - m_registerWindow = addSearch(m_registerView, tr("Reg&isters"), DOCKWIDGET_REGISTER); - - m_stackView = new StackTreeView; - m_stackView->setSettings(settings, "Debugger.StackView"); - m_stackView->setIconSize(QSize(10, 10)); - m_stackWindow = addSearch(m_stackView, tr("&Stack"), DOCKWIDGET_STACK); - - m_sourceFilesView = new BaseTreeView; - m_sourceFilesView->setSortingEnabled(true); - m_sourceFilesView->setSettings(settings, "Debugger.SourceFilesView"); - connect(m_sourceFilesView, &BaseTreeView::aboutToShow, - this, &DebuggerPluginPrivate::reloadSourceFiles, - Qt::QueuedConnection); - m_sourceFilesWindow = addSearch(m_sourceFilesView, tr("Source Files"), DOCKWIDGET_SOURCE_FILES); - - m_threadsView = new BaseTreeView; - m_threadsView->setSortingEnabled(true); - m_threadsView->setSettings(settings, "Debugger.ThreadsView"); - m_threadsView->setIconSize(QSize(10, 10)); - m_threadsWindow = addSearch(m_threadsView, tr("&Threads"), DOCKWIDGET_THREADS); - - m_returnView = new WatchTreeView(ReturnType); // No settings. - m_returnWindow = addSearch(m_returnView, tr("Locals"), "CppDebugReturn"); - - m_localsView = new WatchTreeView(LocalsType); - m_localsView->setSettings(settings, "Debugger.LocalsView"); - m_localsWindow = addSearch(m_localsView, tr("Locals"), "CppDebugLocals"); - - m_inspectorView = new WatchTreeView(InspectType); // No settings. - m_inspectorView->setSettings(settings, "Debugger.LocalsView"); // sic! same as locals view. - m_inspectorWindow = addSearch(m_inspectorView, tr("Locals"), "Inspector"); - - m_watchersView = new WatchTreeView(WatchersType); - m_watchersView->setSettings(settings, "Debugger.WatchersView"); - m_watchersWindow = addSearch(m_watchersView, tr("&Expressions"), "CppDebugWatchers"); + m_breakpointManagerView = new BaseTreeView; + 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_breakpointManagerWindow = addSearch(m_breakpointManagerView); + m_breakpointManagerWindow->setWindowTitle(tr("Breakpoint Preset")); + m_breakpointManagerWindow->setObjectName(DOCKWIDGET_BREAKPOINTMANAGER); // Snapshot - m_snapshotHandler = new SnapshotHandler; - m_snapshotView = new SnapshotTreeView(m_snapshotHandler); - m_snapshotView->setSettings(settings, "Debugger.SnapshotView"); - m_snapshotView->setIconSize(QSize(10, 10)); - m_snapshotView->setModel(m_snapshotHandler->model()); - m_snapshotWindow = addSearch(m_snapshotView, tr("Snapshots"), DOCKWIDGET_SNAPSHOTS); - - // Locals - connect(m_localsView->header(), &QHeaderView::sectionResized, - this, &DebuggerPluginPrivate::updateReturnViewHeader, Qt::QueuedConnection); - - auto act = m_continueAction = new QAction(tr("Continue"), this); - act->setIcon(visibleStartIcon(Id(Constants::CONTINUE), false)); - connect(act, &QAction::triggered, this, &DebuggerPluginPrivate::handleExecContinue); - - act = m_exitAction = new QAction(tr("Stop Debugger"), this); - act->setIcon(Icons::DEBUG_EXIT_SMALL.icon()); - connect(act, &QAction::triggered, this, &DebuggerPluginPrivate::stopDebugger); - - act = m_interruptAction = new QAction(tr("Interrupt"), this); - act->setIcon(visibleStartIcon(Id(Constants::INTERRUPT), false)); - connect(act, &QAction::triggered, this, &DebuggerPluginPrivate::handleExecInterrupt); - - // A "disabled pause" seems to be a good choice. - act = m_undisturbableAction = new QAction(tr("Debugger is Busy"), this); - act->setIcon(visibleStartIcon(Id(Constants::INTERRUPT), false)); - act->setEnabled(false); - - act = m_abortAction = new QAction(tr("Abort Debugging"), this); - act->setToolTip(tr("Aborts debugging and " - "resets the debugger to the initial state.")); - connect(act, &QAction::triggered, this, &DebuggerPluginPrivate::handleAbort); - - act = m_resetAction = new QAction(tr("Restart Debugging"),this); - act->setToolTip(tr("Restart the debugging session.")); - act->setIcon(Icons::RESTART_TOOLBAR.icon()); - connect(act, &QAction::triggered, this, &DebuggerPluginPrivate::handleReset); - - act = m_nextAction = new QAction(tr("Step Over"), this); - act->setIcon(Icons::STEP_OVER.icon()); - connect(act, &QAction::triggered, this, &DebuggerPluginPrivate::handleExecNext); - - act = m_stepAction = new QAction(tr("Step Into"), this); - act->setIcon(Icons::STEP_INTO.icon()); - connect(act, &QAction::triggered, this, &DebuggerPluginPrivate::handleExecStep); - - act = m_stepOutAction = new QAction(tr("Step Out"), this); - act->setIcon(Icons::STEP_OUT.icon()); - connect(act, &QAction::triggered, this, &DebuggerPluginPrivate::handleExecStepOut); - - act = m_runToLineAction = new QAction(tr("Run to Line"), this); - connect(act, &QAction::triggered, this, &DebuggerPluginPrivate::handleExecRunToLine); - - act = m_runToSelectedFunctionAction = new QAction(tr("Run to Selected Function"), this); - connect(act, &QAction::triggered, this, &DebuggerPluginPrivate::handleExecRunToSelectedFunction); - - act = m_returnFromFunctionAction = - new QAction(tr("Immediately Return From Inner Function"), this); - connect(act, &QAction::triggered, this, &DebuggerPluginPrivate::handleExecReturn); - - act = m_jumpToLineAction = new QAction(tr("Jump to Line"), this); - connect(act, &QAction::triggered, this, &DebuggerPluginPrivate::handleExecJumpToLine); - - m_breakAction = new QAction(tr("Toggle Breakpoint"), this); - - act = m_watchAction = new QAction(tr("Add Expression Evaluator"), this); - connect(act, &QAction::triggered, this, &DebuggerPluginPrivate::handleAddToWatchWindow); + 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("Snapshots")); + m_engineManagerWindow->setObjectName(DOCKWIDGET_ENGINEMANAGER); //act = m_snapshotAction = new QAction(tr("Create Snapshot"), this); //act->setProperty(Role, RequestCreateSnapshotRole); //act->setIcon(Icons::SNAPSHOT.icon()); - act = m_reverseDirectionAction = new QAction(tr("Reverse Direction"), this); - act->setCheckable(true); - act->setChecked(false); - act->setCheckable(false); - act->setIcon(Icons::REVERSE_MODE.icon()); - act->setIconVisibleInMenu(false); +// connect(act, &QAction::triggered, +// this, &DebuggerPluginPrivate::handleOperateByInstructionTriggered); - act = m_frameDownAction = new QAction(tr("Move to Called Frame"), this); - connect(act, &QAction::triggered, this, &DebuggerPluginPrivate::handleFrameDown); - - act = m_frameUpAction = new QAction(tr("Move to Calling Frame"), this); - connect(act, &QAction::triggered, this, &DebuggerPluginPrivate::handleFrameUp); - - act = m_operateByInstructionAction = action(OperateByInstruction); - connect(act, &QAction::triggered, - this, &DebuggerPluginPrivate::handleOperateByInstructionTriggered); + // Logging + m_globalLogWindow = new GlobalLogWindow; ActionContainer *debugMenu = ActionManager::actionContainer(PE::M_DEBUG); - m_localsAndInspectorWindow = new LocalsAndInspectorWindow - (m_localsWindow, m_inspectorWindow, m_returnWindow); - m_localsAndInspectorWindow->setObjectName(DOCKWIDGET_LOCALS_AND_INSPECTOR); - m_localsAndInspectorWindow->setWindowTitle(m_localsWindow->windowTitle()); - RunConfiguration::registerAspect(); - // The main "Start Debugging" action. - act = m_startAction = new QAction(this); - act->setIcon(visibleStartIcon(Id(Constants::DEBUG), false)); - act->setText(tr("Start Debugging")); - connect(act, &QAction::triggered, [] { ProjectExplorerPlugin::runStartupProject(ProjectExplorer::Constants::DEBUG_RUN_MODE); }); + // The main "Start Debugging" action. Acts as "Continue" at times. + connect(&m_startAction, &QAction::triggered, this, [] { + ProjectExplorerPlugin::runStartupProject(ProjectExplorer::Constants::DEBUG_RUN_MODE, false); + }); - act = m_debugWithoutDeployAction = new QAction(this); - act->setText(tr("Start Debugging Without Deployment")); - connect(act, &QAction::triggered, [] { ProjectExplorerPlugin::runStartupProject(ProjectExplorer::Constants::DEBUG_RUN_MODE, true); }); + connect(&m_debugWithoutDeployAction, &QAction::triggered, this, [] { + ProjectExplorerPlugin::runStartupProject(ProjectExplorer::Constants::DEBUG_RUN_MODE, true); + }); - act = m_startAndDebugApplicationAction = new QAction(this); - act->setText(tr("Start and Debug External Application...")); - connect(act, &QAction::triggered, this, &StartApplicationDialog::startAndDebugApplication); + connect(&m_startAndDebugApplicationAction, &QAction::triggered, + this, &StartApplicationDialog::startAndDebugApplication); - act = m_attachToCoreAction = new QAction(this); - act->setText(tr("Load Core File...")); - connect(act, &QAction::triggered, this, &DebuggerPluginPrivate::attachCore); + connect(&m_attachToCoreAction, &QAction::triggered, + this, &DebuggerPluginPrivate::attachCore); - act = m_attachToRemoteServerAction = new QAction(this); - act->setText(tr("Attach to Running Debug Server...")); - connect(act, &QAction::triggered, this, &StartApplicationDialog::attachToRemoteServer); + connect(&m_attachToRemoteServerAction, &QAction::triggered, + this, &StartApplicationDialog::attachToRemoteServer); - act = m_attachToRunningApplication = new QAction(this); - act->setText(tr("Attach to Running Application...")); - connect(act, &QAction::triggered, this, &DebuggerPluginPrivate::attachToRunningApplication); + connect(&m_attachToRunningApplication, &QAction::triggered, + this, &DebuggerPluginPrivate::attachToRunningApplication); - act = m_attachToUnstartedApplication = new QAction(this); - act->setText(tr("Attach to Unstarted Application...")); - connect(act, &QAction::triggered, this, &DebuggerPluginPrivate::attachToUnstartedApplicationDialog); + connect(&m_attachToUnstartedApplication, &QAction::triggered, + this, &DebuggerPluginPrivate::attachToUnstartedApplicationDialog); - act = m_attachToQmlPortAction = new QAction(this); - act->setText(tr("Attach to QML Port...")); - connect(act, &QAction::triggered, this, &DebuggerPluginPrivate::attachToQmlPort); + connect(&m_attachToQmlPortAction, &QAction::triggered, + this, &DebuggerPluginPrivate::attachToQmlPort); - if (HostOsInfo::isWindowsHost()) { - m_startRemoteCdbAction = new QAction(tr("Attach to Remote CDB Session..."), this); - connect(m_startRemoteCdbAction, &QAction::triggered, - this, &DebuggerPluginPrivate::startRemoteCdbSession); - } - - act = m_detachAction = new QAction(this); - act->setText(tr("Detach Debugger")); - connect(act, &QAction::triggered, this, &DebuggerPluginPrivate::handleExecDetach); + connect(&m_startRemoteCdbAction, &QAction::triggered, + this, &DebuggerPluginPrivate::startRemoteCdbSession); // "Start Debugging" sub-menu // groups: @@ -1561,50 +1085,57 @@ bool DebuggerPluginPrivate::initialize(const QStringList &arguments, ActionContainer *mstart = ActionManager::actionContainer(PE::M_DEBUG_STARTDEBUGGING); - cmd = ActionManager::registerAction(m_startAction, Constants::DEBUG); + cmd = ActionManager::registerAction(&m_visibleStartAction, Constants::DEBUG); + cmd->setDescription(tr("Start Debugging or Continue")); + cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Ctrl+Y") : tr("F5"))); + 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->setDefaultKeySequence(debugKey); cmd->setAttribute(Command::CA_UpdateText); mstart->addAction(cmd, CC::G_DEFAULT_ONE); - m_visibleStartAction = new ProxyAction(this); - m_visibleStartAction->initialize(m_startAction); - m_visibleStartAction->setAttribute(ProxyAction::UpdateText); - m_visibleStartAction->setAction(m_startAction); - ModeManager::addAction(m_visibleStartAction, Constants::P_ACTION_DEBUG); + m_visibleStartAction.initialize(&m_startAction); + m_visibleStartAction.setAttribute(ProxyAction::UpdateText); + m_visibleStartAction.setAction(&m_startAction); + m_visibleStartAction.setIcon(startIcon(true)); - cmd = ActionManager::registerAction(m_debugWithoutDeployAction, + ModeManager::addAction(&m_visibleStartAction, Constants::P_ACTION_DEBUG); + + cmd = ActionManager::registerAction(&m_debugWithoutDeployAction, "Debugger.DebugWithoutDeploy"); cmd->setAttribute(Command::CA_Hide); mstart->addAction(cmd, CC::G_DEFAULT_ONE); - cmd = ActionManager::registerAction(m_attachToRunningApplication, + cmd = ActionManager::registerAction(&m_attachToRunningApplication, "Debugger.AttachToRemoteProcess"); cmd->setDescription(tr("Attach to Running Application")); mstart->addAction(cmd, G_GENERAL); - cmd = ActionManager::registerAction(m_attachToUnstartedApplication, + cmd = ActionManager::registerAction(&m_attachToUnstartedApplication, "Debugger.AttachToUnstartedProcess"); cmd->setDescription(tr("Attach to Unstarted Application")); mstart->addAction(cmd, G_GENERAL); - cmd = ActionManager::registerAction(m_startAndDebugApplicationAction, + cmd = ActionManager::registerAction(&m_startAndDebugApplicationAction, "Debugger.StartAndDebugApplication"); cmd->setAttribute(Command::CA_Hide); mstart->addAction(cmd, G_GENERAL); - cmd = ActionManager::registerAction(m_attachToCoreAction, - "Debugger.AttachCore"); + cmd = ActionManager::registerAction(&m_attachToCoreAction, + "Debugger.AttachCore"); cmd->setAttribute(Command::CA_Hide); mstart->addAction(cmd, Constants::G_GENERAL); - cmd = ActionManager::registerAction(m_attachToRemoteServerAction, - "Debugger.AttachToRemoteServer"); + cmd = ActionManager::registerAction(&m_attachToRemoteServerAction, + "Debugger.AttachToRemoteServer"); cmd->setAttribute(Command::CA_Hide); mstart->addAction(cmd, Constants::G_SPECIAL); - if (m_startRemoteCdbAction) { - cmd = ActionManager::registerAction(m_startRemoteCdbAction, + if (HostOsInfo::isWindowsHost()) { + cmd = ActionManager::registerAction(&m_startRemoteCdbAction, "Debugger.AttachRemoteCdb"); cmd->setAttribute(Command::CA_Hide); mstart->addAction(cmd, Constants::G_SPECIAL); @@ -1612,66 +1143,84 @@ bool DebuggerPluginPrivate::initialize(const QStringList &arguments, mstart->addSeparator(Context(CC::C_GLOBAL), Constants::G_START_QML); - cmd = ActionManager::registerAction(m_attachToQmlPortAction, "Debugger.AttachToQmlPort"); + cmd = ActionManager::registerAction(&m_attachToQmlPortAction, "Debugger.AttachToQmlPort"); cmd->setAttribute(Command::CA_Hide); mstart->addAction(cmd, Constants::G_START_QML); - cmd = ActionManager::registerAction(m_detachAction, "Debugger.Detach"); + cmd = ActionManager::registerAction(&m_detachAction, "Debugger.Detach"); cmd->setAttribute(Command::CA_Hide); debugMenu->addAction(cmd, CC::G_DEFAULT_ONE); - cmd = ActionManager::registerAction(m_interruptAction, Constants::INTERRUPT); + cmd = ActionManager::registerAction(&m_interruptAction, Constants::INTERRUPT); cmd->setDescription(tr("Interrupt Debugger")); debugMenu->addAction(cmd, CC::G_DEFAULT_ONE); - cmd = ActionManager::registerAction(m_continueAction, Constants::CONTINUE); - cmd->setDefaultKeySequence(debugKey); + cmd = ActionManager::registerAction(&m_continueAction, Constants::CONTINUE); +// cmd->setDefaultKeySequence(debugKey); debugMenu->addAction(cmd, CC::G_DEFAULT_ONE); - cmd = ActionManager::registerAction(m_exitAction, Constants::STOP); + cmd = ActionManager::registerAction(&m_exitAction, Constants::STOP); debugMenu->addAction(cmd, CC::G_DEFAULT_ONE); - m_hiddenStopAction = new ProxyAction(this); - m_hiddenStopAction->initialize(cmd->action()); - m_hiddenStopAction->setAttribute(ProxyAction::UpdateText); - m_hiddenStopAction->setAttribute(ProxyAction::UpdateIcon); + m_hiddenStopAction.initialize(cmd->action()); + m_hiddenStopAction.setAttribute(ProxyAction::UpdateText); + m_hiddenStopAction.setAttribute(ProxyAction::UpdateIcon); - cmd = ActionManager::registerAction(m_hiddenStopAction, Constants::HIDDEN_STOP); + cmd = ActionManager::registerAction(&m_hiddenStopAction, Constants::HIDDEN_STOP); cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Shift+Ctrl+Y") : tr("Shift+F5"))); - cmd = ActionManager::registerAction(m_abortAction, Constants::ABORT); + cmd = ActionManager::registerAction(&m_abortAction, Constants::ABORT); cmd->setDescription(tr("Reset Debugger")); debugMenu->addAction(cmd, CC::G_DEFAULT_ONE); - cmd = ActionManager::registerAction(m_resetAction, Constants::RESET); + cmd = ActionManager::registerAction(&m_resetAction, Constants::RESET); cmd->setDescription(tr("Restart Debugging")); debugMenu->addAction(cmd, CC::G_DEFAULT_ONE); debugMenu->addSeparator(); - cmd = ActionManager::registerAction(m_nextAction, Constants::NEXT); + cmd = ActionManager::registerAction(&m_nextAction, Constants::NEXT); cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Ctrl+Shift+O") : tr("F10"))); cmd->setAttribute(Command::CA_Hide); cmd->setAttribute(Command::CA_UpdateText); debugMenu->addAction(cmd); + m_nextAction.setIcon(Icons::STEP_OVER.icon()); + connect(&m_nextAction, &QAction::triggered, this, [this] { + if (DebuggerEngine *engine = EngineManager::currentEngine()) { + engine->executeNext(); + } else { + DebuggerRunTool::setBreakOnMainNextTime(); + ProjectExplorerPlugin::runStartupProject(ProjectExplorer::Constants::DEBUG_RUN_MODE, false); + } + }); - cmd = ActionManager::registerAction(m_stepAction, Constants::STEP); + cmd = ActionManager::registerAction(&m_stepAction, Constants::STEP); cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Ctrl+Shift+I") : tr("F11"))); cmd->setAttribute(Command::CA_Hide); cmd->setAttribute(Command::CA_UpdateText); debugMenu->addAction(cmd); + m_stepAction.setIcon(Icons::STEP_OVER.icon()); + connect(&m_stepAction, &QAction::triggered, this, [this] { + if (DebuggerEngine *engine = EngineManager::currentEngine()) { + engine->executeStep(); + } else { + DebuggerRunTool::setBreakOnMainNextTime(); + ProjectExplorerPlugin::runStartupProject(ProjectExplorer::Constants::DEBUG_RUN_MODE, false); + } + }); - cmd = ActionManager::registerAction(m_stepOutAction, Constants::STEPOUT); + + cmd = ActionManager::registerAction(&m_stepOutAction, Constants::STEPOUT); cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Ctrl+Shift+T") : tr("Shift+F11"))); cmd->setAttribute(Command::CA_Hide); debugMenu->addAction(cmd); - cmd = ActionManager::registerAction(m_runToLineAction, + cmd = ActionManager::registerAction(&m_runToLineAction, "Debugger.RunToLine", cppDebuggercontext); cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Shift+F8") : tr("Ctrl+F10"))); cmd->setAttribute(Command::CA_Hide); debugMenu->addAction(cmd); - cmd = ActionManager::registerAction(m_runToSelectedFunctionAction, + cmd = ActionManager::registerAction(&m_runToSelectedFunctionAction, "Debugger.RunToSelectedFunction", cppDebuggercontext); cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+F6"))); cmd->setAttribute(Command::CA_Hide); @@ -1679,24 +1228,16 @@ bool DebuggerPluginPrivate::initialize(const QStringList &arguments, // and text up-to-date is a lot of hassle. // debugMenu->addAction(cmd); - cmd = ActionManager::registerAction(m_jumpToLineAction, + cmd = ActionManager::registerAction(&m_jumpToLineAction, "Debugger.JumpToLine", cppDebuggercontext); cmd->setAttribute(Command::CA_Hide); debugMenu->addAction(cmd); - cmd = ActionManager::registerAction(m_returnFromFunctionAction, + cmd = ActionManager::registerAction(&m_returnFromFunctionAction, "Debugger.ReturnFromFunction", cppDebuggercontext); cmd->setAttribute(Command::CA_Hide); debugMenu->addAction(cmd); - if (isReverseDebuggingEnabled()) { - cmd = ActionManager::registerAction(m_reverseDirectionAction, - Constants::REVERSE, cppDebuggercontext); - cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? QString() : tr("F12"))); - cmd->setAttribute(Command::CA_Hide); - debugMenu->addAction(cmd); - } - debugMenu->addSeparator(); //cmd = ActionManager::registerAction(m_snapshotAction, @@ -1705,20 +1246,20 @@ bool DebuggerPluginPrivate::initialize(const QStringList &arguments, //cmd->setAttribute(Command::CA_Hide); //debugMenu->addAction(cmd); - ActionManager::registerAction(m_frameDownAction, + ActionManager::registerAction(&m_frameDownAction, "Debugger.FrameDown", cppDebuggercontext); - ActionManager::registerAction(m_frameUpAction, + ActionManager::registerAction(&m_frameUpAction, "Debugger.FrameUp", cppDebuggercontext); - cmd = ActionManager::registerAction(m_operateByInstructionAction, + cmd = ActionManager::registerAction(action(OperateByInstruction), Constants::OPERATE_BY_INSTRUCTION, cppDebuggercontext); cmd->setAttribute(Command::CA_Hide); debugMenu->addAction(cmd); - cmd = ActionManager::registerAction(m_breakAction, "Debugger.ToggleBreak"); + cmd = ActionManager::registerAction(&m_breakAction, "Debugger.ToggleBreak"); cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("F8") : tr("F9"))); debugMenu->addAction(cmd); - connect(m_breakAction, &QAction::triggered, + connect(&m_breakAction, &QAction::triggered, this, &DebuggerPluginPrivate::toggleBreakpointHelper); debugMenu->addSeparator(); @@ -1747,7 +1288,7 @@ bool DebuggerPluginPrivate::initialize(const QStringList &arguments, debugMenu->addSeparator(); - cmd = m_watchCommand = ActionManager::registerAction(m_watchAction, "Debugger.AddToWatch", + cmd = m_watchCommand = ActionManager::registerAction(&m_watchAction, "Debugger.AddToWatch", Context(CppEditor::Constants::CPPEDITOR_ID, QmlJSEditor::Constants::C_QMLJSEDITOR_ID)); //cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+D,Ctrl+W"))); debugMenu->addAction(cmd); @@ -1758,13 +1299,9 @@ bool DebuggerPluginPrivate::initialize(const QStringList &arguments, connect(ModeManager::instance(), &ModeManager::currentModeChanged, this, &DebuggerPluginPrivate::onModeChanged); - connect(ModeManager::instance(), &ModeManager::currentModeChanged, - m_mainWindow.data(), &DebuggerMainWindow::onModeChanged); connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::settingsChanged, this, &DebuggerPluginPrivate::updateDebugWithoutDeployMenu); - m_mainWindow->finalizeSetup(); - // Debug mode setup m_mode = new DebugMode; m_modeWindow = createModeWindow(Constants::MODE_DEBUG, m_mainWindow); @@ -1774,9 +1311,6 @@ bool DebuggerPluginPrivate::initialize(const QStringList &arguments, m_debugModeContext.setWidget(m_modeWindow); ICore::addContextObject(&m_debugModeContext); - connect(SessionManager::instance(), &SessionManager::startupProjectChanged, - this, &DebuggerPluginPrivate::updateUiForProject); - // // Connections // @@ -1787,7 +1321,17 @@ bool DebuggerPluginPrivate::initialize(const QStringList &arguments, // TextEditor connect(TextEditorSettings::instance(), &TextEditorSettings::fontSettingsChanged, - this, &DebuggerPluginPrivate::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_snapshotWindow->setFont(font); + }); + // ProjectExplorer connect(SessionManager::instance(), &SessionManager::sessionLoaded, @@ -1797,7 +1341,7 @@ bool DebuggerPluginPrivate::initialize(const QStringList &arguments, connect(SessionManager::instance(), &SessionManager::aboutToUnloadSession, this, &DebuggerPluginPrivate::aboutToUnloadSession); connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::updateRunActions, - this, &DebuggerPluginPrivate::updateDebugActions); + this, &DebuggerPluginPrivate::updatePresetState); // EditorManager connect(EditorManager::instance(), &EditorManager::editorOpened, @@ -1809,78 +1353,42 @@ bool DebuggerPluginPrivate::initialize(const QStringList &arguments, connect(action(SettingsDialog), &QAction::triggered, [] { ICore::showOptionsDialog(DEBUGGER_COMMON_SETTINGS_ID); }); - if (isReverseDebuggingEnabled()) { - m_reverseToolButton = new QToolButton; - m_reverseToolButton->setDefaultAction(m_reverseDirectionAction); - } - - m_threadLabel = new QLabel(tr("Threads:")); - - m_threadBox = new QComboBox; - m_threadBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); - connect(m_threadBox, static_cast(&QComboBox::activated), - this, &DebuggerPluginPrivate::selectThread); - -// ToolbarDescription qmlToolbar -// qmlToolbar.addAction(qmlUpdateOnSaveDummyAction); -// qmlToolbar.addAction(qmlShowAppOnTopDummyAction, Icons::APP_ON_TOP_TOOLBAR.icon()); -// qmlToolbar.addWidget(new StyledSeparator); -// qmlToolbar.addAction(qmlSelectDummyAction, Icons::SELECT_TOOLBAR.icon()); -// qmlToolbar.addWidget(new StyledSeparator); - - auto perspective = new Perspective("Debugger", tr("Debugger")); - perspective->addWindow(m_stackWindow, Perspective::SplitVertical, nullptr); - perspective->addWindow(m_breakWindow, Perspective::SplitHorizontal, m_stackWindow); - perspective->addWindow(m_threadsWindow, Perspective::AddToTab, m_breakWindow, false); - perspective->addWindow(m_modulesWindow, Perspective::AddToTab, m_threadsWindow, false); - perspective->addWindow(m_sourceFilesWindow, Perspective::AddToTab, m_modulesWindow, false); - perspective->addWindow(m_snapshotWindow, Perspective::AddToTab, m_sourceFilesWindow, false); - perspective->addWindow(m_localsAndInspectorWindow, Perspective::AddToTab, nullptr, true, - Qt::RightDockWidgetArea); - perspective->addWindow(m_watchersWindow, Perspective::AddToTab, m_localsAndInspectorWindow, true, - Qt::RightDockWidgetArea); - perspective->addWindow(m_logWindow, Perspective::AddToTab, nullptr, false, Qt::TopDockWidgetArea); - perspective->addWindow(m_breakWindow, Perspective::Raise, nullptr); - perspective->addWindow(m_registerWindow, Perspective::AddToTab, m_snapshotWindow, false); - - perspective->addToolbarAction(m_visibleStartAction); - perspective->addToolbarAction(ActionManager::command(Constants::STOP)->action(), Icons::DEBUG_EXIT_SMALL_TOOLBAR.icon()); - perspective->addToolbarAction(ActionManager::command(Constants::NEXT)->action(), Icons::STEP_OVER_TOOLBAR.icon()); - perspective->addToolbarAction(ActionManager::command(Constants::STEP)->action(), Icons::STEP_INTO_TOOLBAR.icon()); - perspective->addToolbarAction(ActionManager::command(Constants::STEPOUT)->action(), Icons::STEP_OUT_TOOLBAR.icon()); - perspective->addToolbarAction(ActionManager::command(Constants::RESET)->action(), Icons::RESTART_TOOLBAR.icon()); - perspective->addToolbarAction(ActionManager::command(Constants::OPERATE_BY_INSTRUCTION)->action()); - - if (isReverseDebuggingEnabled()) - perspective->addToolbarWidget(m_reverseToolButton); - - perspective->addToolbarSeparator(); - perspective->addToolbarWidget(m_threadLabel); - perspective->addToolbarWidget(m_threadBox); - + auto perspective = new Perspective(Constants::PRESET_PERSPRECTIVE_ID, tr("Debugger")); Debugger::registerPerspective(perspective); -// Perspective *qmlPerspective = createBasePerspective(); -// qmlPerspective->setName(tr("QML Debugger")); -// qmlPerspective->addOperation({ DOCKWIDGET_REGISTER, DOCKWIDGET_SNAPSHOTS, -// Perspective::AddToTab, false }); -// -// Debugger::registerToolbar(QmlPerspectiveId, toolbarContainer); -// Debugger::registerPerspective(QmlPerspectiveId, qmlPerspective); + perspective->addToolBarWidget(EngineManager::engineChooser()); + perspective->addToolBarAction(&m_startAction); - connect(action(EnableReverseDebugging), &SavedAction::valueChanged, - this, &DebuggerPluginPrivate::enableReverseDebuggingTriggered); +// QAction *operateByInstructionAction = action(OperateByInstruction); +// operateByInstructionAction->setText(tr("Start in Operate by Instruction mode")); + + +// QAction *enableReverseDebuggingAction = action(EnableReverseDebugging); +// enableReverseDebuggingAction->setText(tr("Start with recording information to reverse step if possible")); +//// Icons::SINGLE_INSTRUCTION_MODE.icon()}; +// Action m_enableReverseDebuggingAction{tr("Start with recording information to reverse step if possible"), +// Icons::REVERSE_MODE.icon()} +// perspective->addToolbarAction(operateByInstructionAction); + + perspective->addWindow(m_breakpointManagerWindow, Perspective::SplitVertical, nullptr); + perspective->addWindow(m_globalLogWindow, Perspective::SplitHorizontal, m_breakpointManagerWindow); + perspective->addWindow(m_engineManagerWindow, Perspective::AddToTab, m_globalLogWindow); + +// connect(action(EnableReverseDebugging), &SavedAction::valueChanged, +// this, &DebuggerPluginPrivate::enableReverseDebuggingTriggered); setInitialState(); - connectEngine(nullptr); connect(SessionManager::instance(), &SessionManager::startupProjectChanged, - this, &DebuggerPluginPrivate::onCurrentProjectChanged); + 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(); - m_returnWindow->setVisible(false); return true; } @@ -1895,7 +1403,203 @@ QVariant configValue(const QString &name) return ICore::settings()->value("DebugMode/" + name); } -void DebuggerPluginPrivate::onCurrentProjectChanged(Project *project) +void DebuggerPluginPrivate::updatePresetState() +{ + if (m_shuttingDown) + return; + + Project *startupProject = SessionManager::startupProject(); + RunConfiguration *startupRunConfig = RunConfiguration::startupRunConfiguration(); + DebuggerEngine *currentEngine = EngineManager::currentEngine(); + + QString whyNot; + const bool canRun = startupProject + && ProjectExplorerPlugin::canRunStartupProject(ProjectExplorer::Constants::DEBUG_RUN_MODE, &whyNot); + + if (!currentEngine || !currentEngine->isStartupRunConfiguration()) { + // No engine running -- or -- we have a running engine but it does not + // correspond to the current start up project. + QString startupRunConfigName; + if (startupRunConfig) + startupRunConfigName = startupRunConfig->displayName(); + if (startupRunConfigName.isEmpty() && startupProject) + startupRunConfigName = startupProject->displayName(); + + QString startToolTip = canRun ? tr("Start debugging of \"%1\"").arg(startupRunConfigName) : whyNot; + QString stepToolTip = canRun ? tr("Start \"%1\" and break at function \"main\"").arg(startupRunConfigName) : whyNot; + // Step into/next: Start and break at 'main' unless a debugger is running. + m_stepAction.setEnabled(canRun); + m_stepAction.setToolTip(stepToolTip); + m_nextAction.setEnabled(canRun); + m_nextAction.setToolTip(stepToolTip); + m_startAction.setEnabled(canRun); + m_startAction.setToolTip(startToolTip); + m_startAction.setText(startToolTip); + m_startAction.setIcon(startIcon(false)); + m_startAction.setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + m_startAction.setVisible(true); + m_interruptAction.setEnabled(false); + m_continueAction.setEnabled(false); + m_exitAction.setEnabled(false); + m_debugWithoutDeployAction.setEnabled(canRun); + m_visibleStartAction.setIcon(startIcon(true)); + m_hiddenStopAction.setAction(&m_undisturbableAction); + m_detachAction.setEnabled(false); + m_jumpToLineAction.setEnabled(false); + m_returnFromFunctionAction.setEnabled(false); + return; + } + + QTC_ASSERT(currentEngine, return); + + // We have a current engine, and it belongs to the startup runconfig. + m_stepAction.setToolTip(QString()); + m_nextAction.setToolTip(QString()); + + // The 'state' bits only affect the fat debug button, not the preset start button. + m_startAction.setText(QString()); + m_startAction.setToolTip(whyNot); + m_startAction.setIcon(startIcon(false)); + m_startAction.setEnabled(false); + m_startAction.setVisible(false); + + QString currentDisplayName = currentEngine->displayName(); + m_interruptAction.setToolTip(tr("Interrupt \"%1\"").arg(currentDisplayName)); + m_continueAction.setToolTip(tr("Continue \"%1\"").arg(currentDisplayName)); + + m_debugWithoutDeployAction.setEnabled(canRun); + + // Global actions are redirected to running, active engine if possible. + + const bool isCore = currentEngine->runParameters().startMode == AttachCore; + + const DebuggerState state = currentEngine->state(); + const bool companionPreventsAction = currentEngine->companionPreventsActions(); + + if (state == InferiorStopOk) { + // F5 continues, Shift-F5 kills. It is "continuable". + m_startAction.setEnabled(false); + m_interruptAction.setEnabled(false); + m_continueAction.setEnabled(!companionPreventsAction); + m_exitAction.setEnabled(true); + m_debugWithoutDeployAction.setEnabled(false); + m_visibleStartAction.setAction(&m_continueAction); + m_visibleStartAction.setIcon(continueIcon(true)); + m_hiddenStopAction.setAction(&m_exitAction); + m_stepAction.setEnabled(!companionPreventsAction); + m_nextAction.setEnabled(!companionPreventsAction); + m_jumpToLineAction.setEnabled(currentEngine->hasCapability(JumpToLineCapability)); + m_returnFromFunctionAction.setEnabled(currentEngine->hasCapability(ReturnFromFunctionCapability)); + m_detachAction.setEnabled(!isCore); + m_abortAction.setEnabled(true); + m_resetAction.setEnabled(currentEngine->hasCapability(ResetInferiorCapability)); + m_stepOutAction.setEnabled(!companionPreventsAction); + m_runToLineAction.setEnabled(currentEngine->hasCapability(RunToLineCapability)); + m_runToSelectedFunctionAction.setEnabled(true); + } else if (state == InferiorRunOk) { + // Shift-F5 interrupts. It is also "interruptible". + m_startAction.setEnabled(false); + m_interruptAction.setEnabled(!companionPreventsAction); + m_continueAction.setEnabled(false); + m_exitAction.setEnabled(true); + m_debugWithoutDeployAction.setEnabled(false); + m_visibleStartAction.setAction(&m_interruptAction); + m_visibleStartAction.setIcon(interruptIcon(true)); + m_hiddenStopAction.setAction(&m_interruptAction); + m_stepAction.setEnabled(false); + m_nextAction.setEnabled(false); + m_jumpToLineAction.setEnabled(false); + m_returnFromFunctionAction.setEnabled(false); + m_detachAction.setEnabled(false); + m_abortAction.setEnabled(true); + m_resetAction.setEnabled(false); + m_stepOutAction.setEnabled(false); + m_runToLineAction.setEnabled(false); + m_runToSelectedFunctionAction.setEnabled(false); + } else if (state == DebuggerFinished) { + // We don't want to do anything anymore. + m_startAction.setEnabled(canRun); + m_interruptAction.setEnabled(false); + m_continueAction.setEnabled(false); + m_exitAction.setEnabled(false); + m_debugWithoutDeployAction.setEnabled(canRun); + m_visibleStartAction.setAction(&m_startAction); + m_visibleStartAction.setIcon(startIcon(true)); + m_hiddenStopAction.setAction(&m_undisturbableAction); + m_stepAction.setEnabled(false); + m_nextAction.setEnabled(false); + m_jumpToLineAction.setEnabled(false); + m_returnFromFunctionAction.setEnabled(false); + m_detachAction.setEnabled(false); + m_abortAction.setEnabled(false); + m_resetAction.setEnabled(false); + m_stepOutAction.setEnabled(false); + m_runToLineAction.setEnabled(false); + m_runToSelectedFunctionAction.setEnabled(false); + } else if (state == InferiorUnrunnable) { + // We don't want to do anything anymore. + m_startAction.setEnabled(false); + m_interruptAction.setEnabled(false); + m_continueAction.setEnabled(false); + m_exitAction.setEnabled(true); + m_debugWithoutDeployAction.setEnabled(false); + m_visibleStartAction.setAction(&m_undisturbableAction); + m_visibleStartAction.setIcon(startIcon(true)); + m_hiddenStopAction.setAction(&m_exitAction); + m_stepAction.setEnabled(false); + m_nextAction.setEnabled(false); + m_jumpToLineAction.setEnabled(false); + m_returnFromFunctionAction.setEnabled(false); + m_detachAction.setEnabled(false); + m_abortAction.setEnabled(true); + m_resetAction.setEnabled(false); + m_stepOutAction.setEnabled(false); + m_runToLineAction.setEnabled(false); + m_runToSelectedFunctionAction.setEnabled(false); + } else if (state == DebuggerNotReady) { + // The startup phase should be over once we are here + QTC_CHECK(false); + } else { + // Everything else is "undisturbable". + m_startAction.setEnabled(false); + m_interruptAction.setEnabled(false); + m_continueAction.setEnabled(false); + m_exitAction.setEnabled(false); + m_debugWithoutDeployAction.setEnabled(false); + m_visibleStartAction.setAction(&m_undisturbableAction); + m_visibleStartAction.setIcon(startIcon(true)); + m_hiddenStopAction.setAction(&m_undisturbableAction); + m_stepAction.setEnabled(false); + m_nextAction.setEnabled(false); + m_jumpToLineAction.setEnabled(false); + m_returnFromFunctionAction.setEnabled(false); + m_detachAction.setEnabled(false); + m_abortAction.setEnabled(true); + m_resetAction.setEnabled(false); + m_stepOutAction.setEnabled(false); + m_runToLineAction.setEnabled(false); + m_runToSelectedFunctionAction.setEnabled(false); + } + +// 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(true); + m_breakAction.setEnabled(true); +} + +void DebuggerPluginPrivate::onStartupProjectChanged(Project *project) { RunConfiguration *activeRc = nullptr; if (project) { @@ -1905,34 +1609,12 @@ void DebuggerPluginPrivate::onCurrentProjectChanged(Project *project) if (!activeRc) return; } - for (int i = 0, n = m_snapshotHandler->size(); i != n; ++i) { + for (DebuggerEngine *engine : EngineManager::engines()) { // Run controls might be deleted during exit. - if (DebuggerRunTool *runTool = m_snapshotHandler->at(i)) { - if (RunControl *runControl = runTool->runControl()) { - RunConfiguration *rc = runControl->runConfiguration(); - if (rc == activeRc) { - m_snapshotHandler->setCurrentIndex(i); - updateState(runTool); - return; - } - } - } + engine->updateState(false); } - // If we have a running debugger, don't touch it. - if (m_snapshotHandler->size()) - return; - - // No corresponding debugger found. So we are ready to start one. - m_interruptAction->setEnabled(false); - m_continueAction->setEnabled(false); - m_exitAction->setEnabled(false); - QString whyNot; - const bool canRun = ProjectExplorerPlugin::canRunStartupProject(ProjectExplorer::Constants::DEBUG_RUN_MODE, &whyNot); - m_startAction->setEnabled(canRun); - m_startAction->setToolTip(whyNot); - m_debugWithoutDeployAction->setEnabled(canRun); - setProxyAction(m_visibleStartAction, Id(Constants::DEBUG)); + updatePresetState(); } void DebuggerPluginPrivate::attachCore() @@ -2134,13 +1816,15 @@ void DebuggerPlugin::getEnginesState(QByteArray *json) const }; QVariantMap states; - for (int i = 0; i < dd->m_snapshotHandler->size(); ++i) { - const DebuggerEngine *engine = dd->m_snapshotHandler->at(i)->engine(); + int i = 0; + DebuggerEngine *currentEngine = EngineManager::currentEngine(); + for (DebuggerEngine *engine : EngineManager::engines()) { states[QString::number(i)] = QVariantMap({ - {"current", dd->m_snapshotHandler->currentIndex() == i}, + {"current", engine == currentEngine}, {"pid", engine->inferiorPid()}, {"state", engine->state()} }); + ++i; } if (!states.isEmpty()) @@ -2188,14 +1872,6 @@ void DebuggerPluginPrivate::attachToQmlPort() debugger->startRunControl(); } -void DebuggerPluginPrivate::enableReverseDebuggingTriggered(const QVariant &value) -{ - QTC_ASSERT(m_reverseToolButton, return); - m_reverseToolButton->setVisible(value.toBool()); - m_reverseDirectionAction->setChecked(false); - m_reverseDirectionAction->setEnabled(value.toBool()); -} - void DebuggerPluginPrivate::runScheduled() { for (DebuggerRunTool *debugger : m_scheduledStarts) @@ -2215,50 +1891,47 @@ void DebuggerPluginPrivate::editorOpened(IEditor *editor) void DebuggerPluginPrivate::updateBreakMenuItem(IEditor *editor) { - auto textEditor = qobject_cast(editor); - m_breakAction->setEnabled(textEditor != nullptr); + BaseTextEditor *textEditor = qobject_cast(editor); + m_breakAction.setEnabled(textEditor != nullptr); } void DebuggerPluginPrivate::requestContextMenu(TextEditorWidget *widget, int lineNumber, QMenu *menu) { - Breakpoint bp; + GlobalBreakpoint gbp; TextDocument *document = widget->textDocument(); ContextData args = getLocationContext(document, lineNumber); if (args.type == LocationByAddress) { - BreakpointResponse needle; + BreakpointParameters needle; needle.type = BreakpointByAddress; needle.address = args.address; needle.lineNumber = -1; - bp = breakHandler()->findSimilarBreakpoint(needle); + gbp = BreakpointManager::findSimilarBreakpoint(needle); } else if (args.type == LocationByFile) { - bp = breakHandler()->findBreakpointByFileAndLine(args.fileName, lineNumber); - if (!bp) - bp = breakHandler()->findBreakpointByFileAndLine(args.fileName, lineNumber, false); + gbp = BreakpointManager::findBreakpointByLocation(args); } - if (bp) { - QString id = bp.id().toString(); + if (gbp) { // Remove existing breakpoint. - auto act = menu->addAction(tr("Remove Breakpoint %1").arg(id)); - connect(act, &QAction::triggered, [bp] { bp.removeBreakpoint(); }); + auto act = menu->addAction(tr("Remove Breakpoint")); + connect(act, &QAction::triggered, [gbp] { gbp->deleteBreakpoint(); }); - // Enable/disable existing breakpoint. - if (bp.isEnabled()) { - act = menu->addAction(tr("Disable Breakpoint %1").arg(id)); - connect(act, &QAction::triggered, [bp] { bp.setEnabled(false); }); - } else { - act = menu->addAction(tr("Enable Breakpoint %1").arg(id)); - connect(act, &QAction::triggered, [bp] { bp.setEnabled(true); }); - } +// // Enable/disable existing breakpoint. +// if (gbp->isEnabled()) { +// act = menu->addAction(tr("Disable Breakpoint %1").arg(id)); +// connect(act, &QAction::triggered, [gbp] { gbp.setEnabled(false); }); +// } else { +// act = menu->addAction(tr("Enable Breakpoint %1").arg(id)); +// connect(act, &QAction::triggered, [gbp] { gbp.setEnabled(true); }); +// } - // Edit existing breakpoint. - act = menu->addAction(tr("Edit Breakpoint %1...").arg(id)); - connect(act, &QAction::triggered, [bp] { - breakHandler()->editBreakpoint(bp, ICore::dialogParent()); - }); +// // Edit existing breakpoint. +// act = menu->addAction(tr("Edit Breakpoint %1...").arg(id)); +// connect(act, &QAction::triggered, [gbp] { +// globalBreakHandler()->editBreakpoint(gbp, ICore::dialogParent()); +// }); } else { // Handle non-existing breakpoint. @@ -2283,85 +1956,47 @@ void DebuggerPluginPrivate::requestContextMenu(TextEditorWidget *widget, } // Run to, jump to line below in stopped state. - DebuggerEngine *engine = currentEngine(); - QTC_ASSERT(engine, return); - 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] { - DebuggerEngine *engine = currentEngine(); - 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] { - DebuggerEngine *engine = currentEngine(); - 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] { - DebuggerEngine *engine = currentEngine(); + for (const QPointer 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->openDisassemblerView(Location(frame)); + engine->executeRunToLine(args); }); - menu->addAction(act); + } + 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::toggleBreakpoint(const ContextData &location, const QString &tracePointMessage) -{ - QTC_ASSERT(location.isValid(), return); - BreakHandler *handler = m_breakHandler; - Breakpoint bp; - if (location.type == LocationByFile) { - bp = handler->findBreakpointByFileAndLine(location.fileName, location.lineNumber, true); - if (!bp) - bp = handler->findBreakpointByFileAndLine(location.fileName, location.lineNumber, false); - } else if (location.type == LocationByAddress) { - bp = handler->findBreakpointByAddress(location.address); - } - - if (bp) { - bp.removeBreakpoint(); - } else { - BreakpointParameters data; - if (location.type == LocationByFile) { - data.type = BreakpointByFileAndLine; - if (boolSetting(BreakpointsFullPathByDefault)) - data.pathUsage = BreakpointUseFullPath; - data.tracepoint = !tracePointMessage.isEmpty(); - data.message = tracePointMessage; - data.fileName = location.fileName; - data.lineNumber = location.lineNumber; - } else if (location.type == LocationByAddress) { - data.type = BreakpointByAddress; - data.tracepoint = !tracePointMessage.isEmpty(); - data.message = tracePointMessage; - data.address = location.address; - } - handler->appendBreakpoint(data); - } -} - void DebuggerPluginPrivate::toggleBreakpointHelper() { BaseTextEditor *textEditor = BaseTextEditor::currentTextEditor(); @@ -2369,7 +2004,7 @@ void DebuggerPluginPrivate::toggleBreakpointHelper() const int lineNumber = textEditor->currentLine(); ContextData location = getLocationContext(textEditor->textDocument(), lineNumber); if (location.isValid()) - toggleBreakpoint(location); + BreakpointManager::toggleBreakpoint(location); } void DebuggerPluginPrivate::requestMark(TextEditorWidget *widget, int lineNumber, @@ -2378,360 +2013,60 @@ void DebuggerPluginPrivate::requestMark(TextEditorWidget *widget, int lineNumber if (kind == BreakpointRequest) { ContextData location = getLocationContext(widget->textDocument(), lineNumber); if (location.isValid()) - toggleBreakpoint(location); + BreakpointManager::toggleBreakpoint(location); } } -// If updateEngine is set, the engine will update its threads/modules and so forth. -void DebuggerPluginPrivate::displayDebugger(DebuggerRunTool *runTool) -{ - QTC_ASSERT(runTool, return); - DebuggerEngine *engine = runTool ? runTool->engine() : dummyEngine(); - QTC_ASSERT(engine, return); - - disconnectEngine(); - connectEngine(runTool); - engine->updateAll(); - engine->updateViews(); -} - -void DebuggerPluginPrivate::connectEngine(DebuggerRunTool *runTool) -{ - if (m_shuttingDown) - return; - - m_currentRunTool = runTool; - DebuggerEngine *engine = currentEngine(); - QTC_ASSERT(engine, return); - - if (m_previouslyActiveEngine == engine) - return; - - if (m_previouslyActiveEngine) - m_previouslyActiveEngine->resetLocation(); - - m_previouslyActiveEngine = engine; - - m_localsView->setModel(engine->watchModel()); - m_modulesView->setModel(engine->modulesModel()); - m_registerView->setModel(engine->registerModel()); - m_returnView->setModel(engine->watchModel()); - m_sourceFilesView->setModel(engine->sourceFilesModel()); - m_stackView->setModel(engine->stackModel()); - m_threadsView->setModel(engine->threadsModel()); - m_watchersView->setModel(engine->watchModel()); - m_inspectorView->setModel(engine->watchModel()); - - engine->watchHandler()->resetWatchers(); - m_localsView->hideProgressIndicator(); - updateActiveLanguages(); -} - -static void changeFontSize(QWidget *widget, qreal size) -{ - QFont font = widget->font(); - font.setPointSizeF(size); - widget->setFont(font); -} - -void DebuggerPluginPrivate::fontSettingsChanged(const FontSettings &settings) -{ - if (!boolSetting(FontSizeFollowsEditor)) - return; - qreal size = settings.fontZoom() * settings.fontSize() / 100.; - changeFontSize(m_breakWindow, size); - changeFontSize(m_logWindow, size); - changeFontSize(m_localsWindow, size); - changeFontSize(m_modulesWindow, size); - //changeFontSize(m_consoleWindow, size); - changeFontSize(m_registerWindow, size); - changeFontSize(m_returnWindow, size); - changeFontSize(m_sourceFilesWindow, size); - changeFontSize(m_stackWindow, size); - changeFontSize(m_threadsWindow, size); - changeFontSize(m_watchersWindow, size); - changeFontSize(m_inspectorWindow, size); -} - -void DebuggerPluginPrivate::cleanupViews() -{ - m_reverseDirectionAction->setChecked(false); - m_reverseDirectionAction->setEnabled(false); - - const bool closeSource = boolSetting(CloseSourceBuffersOnExit); - const bool closeMemory = boolSetting(CloseMemoryBuffersOnExit); - - QList toClose; - foreach (IDocument *document, DocumentModel::openedDocuments()) { - const bool isMemory = document->property(Constants::OPENED_WITH_DISASSEMBLY).toBool(); - if (document->property(Constants::OPENED_BY_DEBUGGER).toBool()) { - bool keepIt = true; - if (document->isModified()) - keepIt = true; - else if (document->filePath().toString().contains("qeventdispatcher")) - keepIt = false; - else if (isMemory) - keepIt = !closeMemory; - else - keepIt = !closeSource; - - if (keepIt) - document->setProperty(Constants::OPENED_BY_DEBUGGER, false); - else - toClose.append(document); - } - } - EditorManager::closeDocuments(toClose); -} - -void DebuggerPluginPrivate::setBusyCursor(bool busy) -{ - //STATE_DEBUG("BUSY FROM: " << m_busy << " TO: " << busy); - if (busy == m_busy) - return; - m_busy = busy; - QCursor cursor(busy ? Qt::BusyCursor : Qt::ArrowCursor); - m_breakWindow->setCursor(cursor); - //m_consoleWindow->setCursor(cursor); - m_localsWindow->setCursor(cursor); - m_modulesWindow->setCursor(cursor); - m_logWindow->setCursor(cursor); - m_registerWindow->setCursor(cursor); - m_returnWindow->setCursor(cursor); - m_sourceFilesWindow->setCursor(cursor); - m_stackWindow->setCursor(cursor); - m_threadsWindow->setCursor(cursor); - m_watchersWindow->setCursor(cursor); - m_snapshotWindow->setCursor(cursor); -} - void DebuggerPluginPrivate::setInitialState() { - m_returnWindow->setVisible(false); - setBusyCursor(false); - m_reverseDirectionAction->setChecked(false); - m_reverseDirectionAction->setEnabled(false); m_toolTipManager.closeAllToolTips(); - 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_detachAction->setEnabled(false); + 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_detachAction.setEnabled(false); - m_watchAction->setEnabled(true); - m_breakAction->setEnabled(false); - //m_snapshotAction->setEnabled(false); - m_operateByInstructionAction->setEnabled(false); + m_watchAction.setEnabled(true); + m_breakAction.setEnabled(false); + //m_snapshotAction.setEnabled(false); + action(OperateByInstruction)->setChecked(false); - m_exitAction->setEnabled(false); - m_abortAction->setEnabled(false); - m_resetAction->setEnabled(false); + m_exitAction.setEnabled(false); + m_abortAction.setEnabled(false); + m_resetAction.setEnabled(false); - m_interruptAction->setEnabled(false); - m_continueAction->setEnabled(false); + m_interruptAction.setEnabled(false); + m_continueAction.setEnabled(false); - m_stepAction->setEnabled(true); - m_stepOutAction->setEnabled(false); - m_runToLineAction->setEnabled(false); - m_runToSelectedFunctionAction->setEnabled(true); - m_returnFromFunctionAction->setEnabled(false); - m_jumpToLineAction->setEnabled(false); - m_nextAction->setEnabled(true); + m_stepAction.setEnabled(true); + m_stepOutAction.setEnabled(false); + m_runToLineAction.setEnabled(false); + m_runToSelectedFunctionAction.setEnabled(true); + m_returnFromFunctionAction.setEnabled(false); + m_jumpToLineAction.setEnabled(false); + m_nextAction.setEnabled(true); action(AutoDerefPointers)->setEnabled(true); action(ExpandStack)->setEnabled(false); - - m_threadLabel->setEnabled(false); -} - -void DebuggerPluginPrivate::updateState(DebuggerRunTool *runTool) -{ - if (m_shuttingDown) - return; - QTC_ASSERT(runTool, return); - DebuggerEngine *engine = runTool->engine(); - QTC_ASSERT(engine, return); - QTC_ASSERT(m_watchersView->model(), return); - QTC_ASSERT(m_returnView->model(), return); - QTC_ASSERT(!engine->isSlaveEngine(), return); - - m_threadBox->setCurrentIndex(engine->threadsHandler()->currentThreadIndex()); - - const DebuggerState state = engine->state(); - //showMessage(QString::fromLatin1("PLUGIN SET STATE: ") - // + DebuggerEngine::stateName(state), LogStatus); - //qDebug() << "PLUGIN SET STATE: " << state; - - static DebuggerState previousState = DebuggerNotReady; - if (state == previousState) - return; - - bool actionsEnabled = DebuggerEngine::debuggerActionsEnabled(state); - - if (state == DebuggerNotReady) { - QTC_ASSERT(false, /* We use the Core's m_debugAction here */); - // F5 starts debugging. It is "startable". - m_interruptAction->setEnabled(false); - m_continueAction->setEnabled(false); - m_exitAction->setEnabled(false); - m_startAction->setEnabled(true); - m_debugWithoutDeployAction->setEnabled(true); - setProxyAction(m_visibleStartAction, Id(Constants::DEBUG)); - m_hiddenStopAction->setAction(m_undisturbableAction); - } else if (state == InferiorStopOk) { - // F5 continues, Shift-F5 kills. It is "continuable". - m_interruptAction->setEnabled(false); - m_continueAction->setEnabled(true); - m_exitAction->setEnabled(true); - m_startAction->setEnabled(false); - m_debugWithoutDeployAction->setEnabled(false); - setProxyAction(m_visibleStartAction, Id(Constants::CONTINUE)); - m_hiddenStopAction->setAction(m_exitAction); - m_localsAndInspectorWindow->setShowLocals(true); - } else if (state == InferiorRunOk) { - // Shift-F5 interrupts. It is also "interruptible". - m_interruptAction->setEnabled(true); - m_continueAction->setEnabled(false); - m_exitAction->setEnabled(true); - m_startAction->setEnabled(false); - m_debugWithoutDeployAction->setEnabled(false); - setProxyAction(m_visibleStartAction, Id(Constants::INTERRUPT)); - m_hiddenStopAction->setAction(m_interruptAction); - m_localsAndInspectorWindow->setShowLocals(false); - } else if (state == DebuggerFinished) { - const bool canRun = ProjectExplorerPlugin::canRunStartupProject(ProjectExplorer::Constants::DEBUG_RUN_MODE); - // We don't want to do anything anymore. - m_interruptAction->setEnabled(false); - m_continueAction->setEnabled(false); - m_exitAction->setEnabled(false); - m_startAction->setEnabled(canRun); - m_debugWithoutDeployAction->setEnabled(canRun); - setProxyAction(m_visibleStartAction, Id(Constants::DEBUG)); - m_hiddenStopAction->setAction(m_undisturbableAction); - setBusyCursor(false); - cleanupViews(); - } else if (state == InferiorUnrunnable) { - // We don't want to do anything anymore. - m_interruptAction->setEnabled(false); - m_continueAction->setEnabled(false); - m_exitAction->setEnabled(true); - m_startAction->setEnabled(false); - m_debugWithoutDeployAction->setEnabled(false); - m_visibleStartAction->setAction(m_undisturbableAction); - m_hiddenStopAction->setAction(m_exitAction); - // show locals in core dumps - m_localsAndInspectorWindow->setShowLocals(true); - } else { - // Everything else is "undisturbable". - m_interruptAction->setEnabled(false); - m_continueAction->setEnabled(false); - m_exitAction->setEnabled(false); - m_startAction->setEnabled(false); - m_debugWithoutDeployAction->setEnabled(false); - m_visibleStartAction->setAction(m_undisturbableAction); - m_hiddenStopAction->setAction(m_undisturbableAction); - } - - 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_threadBox->setEnabled(state == InferiorStopOk || state == InferiorUnrunnable); - m_threadLabel->setEnabled(m_threadBox->isEnabled()); - - const bool isCore = runTool->runParameters().startMode == AttachCore; - const bool stopped = state == InferiorStopOk; - const bool detachable = stopped && !isCore; - m_detachAction->setEnabled(detachable); - - if (stopped) - QApplication::alert(mainWindow(), 3000); - - const bool canReverse = engine->hasCapability(ReverseSteppingCapability) - && boolSetting(EnableReverseDebugging); - m_reverseDirectionAction->setEnabled(canReverse); - - m_watchAction->setEnabled(true); - m_breakAction->setEnabled(true); - - const bool canOperateByInstruction = engine->hasCapability(OperateByInstructionCapability) - && (stopped || isCore); - m_operateByInstructionAction->setEnabled(canOperateByInstruction); - - m_abortAction->setEnabled(state != DebuggerNotReady - && state != DebuggerFinished); - m_resetAction->setEnabled((stopped || state == DebuggerNotReady) - && engine->hasCapability(ResetInferiorCapability)); - - m_stepAction->setEnabled(stopped || state == DebuggerNotReady); - m_nextAction->setEnabled(stopped || state == DebuggerNotReady); - m_stepAction->setToolTip(QString()); - m_nextAction->setToolTip(QString()); - - m_stepOutAction->setEnabled(stopped); - m_runToLineAction->setEnabled(stopped && engine->hasCapability(RunToLineCapability)); - m_runToSelectedFunctionAction->setEnabled(stopped); - m_returnFromFunctionAction-> - setEnabled(stopped && engine->hasCapability(ReturnFromFunctionCapability)); - - const bool canJump = stopped && engine->hasCapability(JumpToLineCapability); - m_jumpToLineAction->setEnabled(canJump); - - const bool canDeref = actionsEnabled && engine->hasCapability(AutoDerefPointersCapability); - action(AutoDerefPointers)->setEnabled(canDeref); - action(AutoDerefPointers)->setEnabled(true); - action(ExpandStack)->setEnabled(actionsEnabled); - - const bool notbusy = state == InferiorStopOk - || state == DebuggerNotReady - || state == DebuggerFinished - || state == InferiorUnrunnable; - setBusyCursor(!notbusy); -} - -void DebuggerPluginPrivate::updateDebugActions() -{ - if (m_shuttingDown) - return; - //if we're currently debugging the actions are controlled by engine - if (m_currentRunTool && m_currentRunTool->engine()->state() != DebuggerNotReady) - return; - - QString whyNot; - const bool canRun = ProjectExplorerPlugin::canRunStartupProject(ProjectExplorer::Constants::DEBUG_RUN_MODE, &whyNot); - m_startAction->setEnabled(canRun); - m_startAction->setToolTip(whyNot); - m_debugWithoutDeployAction->setEnabled(canRun); - - // Step into/next: Start and break at 'main' unless a debugger is running. - if (m_snapshotHandler->currentIndex() < 0) { - m_stepAction->setEnabled(canRun); - m_nextAction->setEnabled(canRun); - if (canRun) { - Project *project = SessionManager::startupProject(); - QTC_ASSERT(project, return); - whyNot = tr("Start \"%1\" and break at function \"main()\"") - .arg(project->displayName()); - } - m_stepAction->setToolTip(whyNot); - m_nextAction->setToolTip(whyNot); - } } void DebuggerPluginPrivate::updateDebugWithoutDeployMenu() { const bool state = ProjectExplorerPlugin::projectExplorerSettings().deployBeforeRun; - m_debugWithoutDeployAction->setVisible(state); + 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()) @@ -2739,63 +2074,34 @@ void DebuggerPluginPrivate::dumpLog() FileSaver saver(fileName); if (!saver.hasError()) { QTextStream ts(saver.file()); - ts << m_logWindow->inputContents(); + ts << logWindow->inputContents(); ts << "\n\n=======================================\n\n"; - ts << m_logWindow->combinedContents(); + ts << logWindow->combinedContents(); saver.setResult(&ts); } saver.finalize(ICore::mainWindow()); } -/*! Activates the previous mode when the current mode is the debug mode. */ -void DebuggerPluginPrivate::activatePreviousMode() -{ - if (ModeManager::currentModeId() == MODE_DEBUG && m_previousMode.isValid()) { - // If stopping the application also makes Qt Creator active (as the - // "previously active application"), doing the switch synchronously - // leads to funny effects with floating dock widgets - const Core::Id mode = m_previousMode; - QTimer::singleShot(0, this, [mode]() { ModeManager::activateMode(mode); }); - m_previousMode = Id(); - } -} - -void DebuggerPluginPrivate::activateDebugMode() -{ - m_reverseDirectionAction->setChecked(false); - m_reverseDirectionAction->setEnabled(false); - ModeManager::activateMode(MODE_DEBUG); -} - void DebuggerPluginPrivate::sessionLoaded() { - m_breakHandler->loadSessionData(); - dummyEngine()->watchHandler()->loadSessionData(); + BreakpointManager::loadSessionData(); + WatchHandler::loadSessionData(); DebuggerToolTipManager::loadSessionData(); } void DebuggerPluginPrivate::aboutToUnloadSession() { + BreakpointManager::aboutToUnloadSession(); m_toolTipManager.sessionAboutToChange(); } void DebuggerPluginPrivate::aboutToSaveSession() { - dummyEngine()->watchHandler()->saveSessionData(); - m_breakHandler->saveSessionData(); + WatchHandler::saveSessionData(); + BreakpointManager::saveSessionData(); DebuggerToolTipManager::saveSessionData(); } -void DebuggerPluginPrivate::showStatusMessage(const QString &msg0, int timeout) -{ - if (m_shuttingDown) - return; - showMessage(msg0, LogStatus); - QString msg = msg0; - msg.replace(QChar::LineFeed, QLatin1String("; ")); - m_mainWindow->showStatusMessage(msg, timeout); -} - void DebuggerPluginPrivate::aboutToShutdown() { m_shuttingDown = true; @@ -2805,9 +2111,9 @@ void DebuggerPluginPrivate::aboutToShutdown() m_shutdownTimer.setInterval(0); m_shutdownTimer.setSingleShot(true); connect(&m_shutdownTimer, &QTimer::timeout, this, &DebuggerPluginPrivate::doShutdown); - if (dd->m_currentRunTool) { - if (dd->m_currentRunTool->engine()->state() != Debugger::DebuggerNotReady) { - dd->m_currentRunTool->abortDebugger(); + for (DebuggerEngine *engine : m_engineManager.engines()) { + if (engine && engine->state() != Debugger::DebuggerNotReady) { + engine->abortDebugger(); m_shutdownTimer.setInterval(3000); } } @@ -2824,40 +2130,6 @@ QVariant sessionValue(const QByteArray &key) return SessionManager::value(QString::fromUtf8(key)); } -WatchTreeView *inspectorView() -{ - return dd->m_inspectorView; -} - -void DebuggerPluginPrivate::showMessage(const QString &msg, int channel, int timeout) -{ - if (m_shuttingDown) - return; - //qDebug() << "PLUGIN OUTPUT: " << channel << msg; - QTC_ASSERT(m_logWindow, return); - switch (channel) { - case StatusBar: - // This will append to m_logWindow's output pane, too. - showStatusMessage(msg, timeout); - break; - case LogMiscInput: - m_logWindow->showInput(LogMisc, msg); - m_logWindow->showOutput(LogMisc, msg); - break; - case LogInput: - m_logWindow->showInput(LogInput, msg); - m_logWindow->showOutput(LogInput, msg); - break; - case LogError: - m_logWindow->showInput(LogError, QLatin1String("ERROR: ") + msg); - m_logWindow->showOutput(LogError, QLatin1String("ERROR: ") + msg); - break; - default: - m_logWindow->showOutput(channel, msg); - break; - } -} - static void createNewDock(QWidget *widget) { auto dockWidget = new QDockWidget; @@ -2867,84 +2139,6 @@ static void createNewDock(QWidget *widget) dockWidget->show(); } -static QString formatStartParameters(const DebuggerRunTool *debugger) -{ - const DebuggerRunParameters &sp = debugger->runParameters(); - QString rc; - QTextStream str(&rc); - str << "Start parameters: '" << sp.displayName << "' mode: " << sp.startMode - << "\nABI: " << sp.toolChainAbi.toString() << '\n'; - str << "Languages: "; - if (sp.isCppDebugging()) - str << "c++ "; - if (sp.isQmlDebugging) - str << "qml"; - str << '\n'; - if (!sp.inferior.executable.isEmpty()) { - str << "Executable: " << QDir::toNativeSeparators(sp.inferior.executable) - << ' ' << sp.inferior.commandLineArguments; - if (debugger->terminalRunner()) - str << " [terminal]"; - str << '\n'; - if (!sp.inferior.workingDirectory.isEmpty()) - str << "Directory: " << QDir::toNativeSeparators(sp.inferior.workingDirectory) - << '\n'; - } - QString cmd = sp.debugger.executable; - if (!cmd.isEmpty()) - str << "Debugger: " << QDir::toNativeSeparators(cmd) << '\n'; - if (!sp.coreFile.isEmpty()) - str << "Core: " << QDir::toNativeSeparators(sp.coreFile) << '\n'; - if (sp.attachPID.isValid()) - str << "PID: " << sp.attachPID.pid() << ' ' << sp.crashParameter << '\n'; - if (!sp.projectSourceDirectory.isEmpty()) { - str << "Project: " << QDir::toNativeSeparators(sp.projectSourceDirectory) << '\n'; - str << "Additional Search Directories:" - << sp.additionalSearchDirectories.join(QLatin1Char(' ')) << '\n'; - } - if (!sp.remoteChannel.isEmpty()) - str << "Remote: " << sp.remoteChannel << '\n'; - if (!sp.qmlServer.host().isEmpty()) - str << "QML server: " << sp.qmlServer.host() << ':' << sp.qmlServer.port() << '\n'; - str << "Sysroot: " << sp.sysRoot << '\n'; - str << "Debug Source Location: " << sp.debugSourceLocation.join(QLatin1Char(':')) << '\n'; - return rc; -} - -void DebuggerPluginPrivate::runControlStarted(DebuggerRunTool *runTool) -{ - activateDebugMode(); - const QString message = tr("Starting debugger \"%1\" for ABI \"%2\"...") - .arg(runTool->engine()->objectName()) - .arg(runTool->runParameters().toolChainAbi.toString()); - showStatusMessage(message); - showMessage(formatStartParameters(runTool), LogDebug); - showMessage(m_debuggerSettings->dump(), LogDebug); - m_snapshotHandler->appendSnapshot(runTool); - connectEngine(runTool); -} - -void DebuggerPluginPrivate::runControlFinished(DebuggerRunTool *runTool) -{ - showStatusMessage(tr("Debugger finished.")); - m_snapshotHandler->removeSnapshot(runTool); - if (m_snapshotHandler->size() == 0) { - if (m_shuttingDown) { - doShutdown(); - return; - } - // Last engine quits. - disconnectEngine(); - if (boolSetting(SwitchModeOnExit)) - activatePreviousMode(); - } else { - // Connect to some existing engine. - m_snapshotHandler->activateSnapshot(0); - } - m_operateByInstructionAction->setChecked(false); - m_logWindow->clearUndoRedoStacks(); -} - void DebuggerPluginPrivate::remoteCommand(const QStringList &options) { if (options.isEmpty()) @@ -2978,15 +2172,12 @@ void addDebugInfoTask(unsigned id, const QString &cmd) bool isReverseDebuggingEnabled() { + return true; + static bool enabled = qEnvironmentVariableIsSet("QTC_DEBUGGER_ENABLE_REVERSE"); return enabled; } -bool isReverseDebugging() -{ - return isReverseDebuggingEnabled() && dd->m_reverseDirectionAction->isChecked(); -} - void DebuggerPluginPrivate::extensionsInitialized() { // If the CppEditor or QmlJS editor plugin is there, we want to add something to @@ -3021,14 +2212,6 @@ void DebuggerPluginPrivate::extensionsInitialized() (ProjectExplorer::Constants::DEBUG_RUN_MODE, constraint); } -DebuggerEngine *currentEngine() -{ - DebuggerEngine *engine = nullptr; - if (dd->m_currentRunTool) - engine = dd->m_currentRunTool->engine(); - return engine ? engine : dd->dummyEngine(); -} - SavedAction *action(int code) { return dd->m_debuggerSettings->item(code); @@ -3049,11 +2232,6 @@ QStringList stringListSetting(int code) return dd->m_debuggerSettings->item(code)->value().toStringList(); } -BreakHandler *breakHandler() -{ - return dd->m_breakHandler; -} - void showModuleSymbols(const QString &moduleName, const Symbols &symbols) { auto w = new QTreeWidget; @@ -3126,22 +2304,6 @@ void DebuggerPluginPrivate::doShutdown() emit m_plugin->asynchronousShutdownFinished(); } -void updateState(DebuggerRunTool *runTool) -{ - dd->updateState(runTool); -} - -void updateLocalsWindow(bool showReturn) -{ - dd->m_returnWindow->setVisible(showReturn); - dd->m_localsView->resizeColumns(); -} - -bool hasSnapshots() -{ - return dd->m_snapshotHandler->size(); -} - void openTextEditor(const QString &titlePattern0, const QString &contents) { if (dd->m_shuttingDown) @@ -3159,68 +2321,11 @@ void openTextEditor(const QString &titlePattern0, const QString &contents) QTC_ASSERT(editor, return); } -// void runTest(const QString &fileName); -void showMessage(const QString &msg, int channel, int timeout) -{ - dd->showMessage(msg, channel, timeout); -} - -void runControlStarted(DebuggerRunTool *runTool) -{ - dd->runControlStarted(runTool); -} - -void runControlFinished(DebuggerRunTool *runTool) -{ - dd->runControlFinished(runTool); -} - -void displayDebugger(DebuggerRunTool *runTool) -{ - dd->displayDebugger(runTool); -} - -void synchronizeBreakpoints() -{ - dd->synchronizeBreakpoints(); -} - QWidget *mainWindow() { return dd->m_mainWindow; } -void raiseWatchersWindow() -{ - if (currentEngine()->state() != DebuggerNotReady) - dd->m_mainWindow->raiseDock(DOCKWIDGET_WATCHERS); -} - -bool isRegistersWindowVisible() -{ - return dd->m_registerWindow->isVisible(); -} - -bool isModulesWindowVisible() -{ - return dd->m_modulesWindow->isVisible(); -} - -void openMemoryEditor() -{ - AddressDialog dialog; - if (dialog.exec() == QDialog::Accepted) { - MemoryViewSetupData data; - data.startAddress = dialog.address(); - currentEngine()->openMemoryView(data); - } -} - -void setThreadBoxContents(const QStringList &list, int index) -{ - dd->setThreadBoxContents(list, index); -} - QSharedPointer globalDebuggerOptions() { return dd->m_globalDebuggerOptions; @@ -3309,82 +2414,26 @@ void DebuggerPlugin::extensionsInitialized() dd->extensionsInitialized(); } -void DebuggerPluginPrivate::updateUiForProject(Project *project) -{ - if (m_previousProject) { - disconnect(m_previousProject, &Project::activeTargetChanged, - this, &DebuggerPluginPrivate::updateUiForTarget); - } - m_previousProject = project; - if (!project) { - updateUiForTarget(nullptr); - return; - } - connect(project, &Project::activeTargetChanged, - this, &DebuggerPluginPrivate::updateUiForTarget); - updateUiForTarget(project->activeTarget()); -} - -void DebuggerPluginPrivate::updateUiForTarget(Target *target) -{ - if (m_previousTarget) { - disconnect(m_previousTarget.data(), &Target::activeRunConfigurationChanged, - this, &DebuggerPluginPrivate::updateActiveLanguages); - } - - m_previousTarget = target; - - if (!target) { - updateActiveLanguages(); - return; - } - - connect(target, &Target::activeRunConfigurationChanged, - this, &DebuggerPluginPrivate::updateActiveLanguages); - updateActiveLanguages(); -} - -void DebuggerPluginPrivate::updateActiveLanguages() -{ - if (!dd->m_currentRunTool) - return; - const DebuggerRunParameters &rp = dd->m_currentRunTool->runParameters(); -// Id perspective = (languages & QmlLanguage) && !(languages & CppLanguage) -// ? QmlPerspectiveId : CppPerspectiveId; -// m_mainWindow->restorePerspective(perspective); - if (rp.isCppDebugging()) - ICore::addAdditionalContext(Context(C_CPPDEBUGGER)); - else - ICore::removeAdditionalContext(Context(C_CPPDEBUGGER)); - - if (rp.isQmlDebugging) - ICore::addAdditionalContext(Context(C_QMLDEBUGGER)); - else - ICore::removeAdditionalContext(Context(C_QMLDEBUGGER)); -} - void DebuggerPluginPrivate::onModeChanged(Id mode) { + m_mainWindow->onModeChanged(mode); // FIXME: This one gets always called, even if switching between modes // different then the debugger mode. E.g. Welcome and Help mode and // also on shutdown. if (mode == MODE_DEBUG) { +// if (EngineManager::engines().isEmpty()) +// m_mainWindow->restorePerspective(Constants::PRESET_PERSPRECTIVE_ID); + EngineManager::selectUiForCurrentEngine(); if (IEditor *editor = EditorManager::currentEditor()) editor->widget()->setFocus(); m_toolTipManager.debugModeEntered(); - updateActiveLanguages(); } else { m_toolTipManager.leavingDebugMode(); } } -void saveModeToRestore() -{ - dd->m_previousMode = ModeManager::currentModeId(); -} - } // namespace Internal static bool buildTypeAccepted(QFlags toolMode, BuildConfiguration::BuildType buildType) @@ -3507,28 +2556,10 @@ void registerPerspective(Perspective *perspective) dd->m_mainWindow->registerPerspective(perspective); } -void setPerspectiveEnabled(const QByteArray &perspectiveId, bool enabled) -{ - dd->m_mainWindow->setPerspectiveEnabled(perspectiveId, enabled); -} - void selectPerspective(const QByteArray &perspectiveId) { - if (ModeManager::currentModeId() == MODE_DEBUG - && dd->m_mainWindow->currentPerspective() == perspectiveId) { - return; - } - - // FIXME: Work-around aslong as the GammaRay integration does not use the same setup, - if (perspectiveId.isEmpty()) - return; - ModeManager::activateMode(MODE_DEBUG); - dd->m_mainWindow->restorePerspective(dd->m_mainWindow->findPerspective(perspectiveId)); -} - -QByteArray currentPerspective() -{ - return dd->m_mainWindow->currentPerspective(); + if (auto perspective = dd->m_mainWindow->findPerspective(perspectiveId)) + perspective->select(); } QWidget *mainWindow() diff --git a/src/plugins/debugger/debuggerprotocol.h b/src/plugins/debugger/debuggerprotocol.h index 68fefbaceaa..481cdaff59b 100644 --- a/src/plugins/debugger/debuggerprotocol.h +++ b/src/plugins/debugger/debuggerprotocol.h @@ -301,6 +301,20 @@ const char DisplayImageFile[] = "imagefile:separate"; const char DisplayPlotData[] = "plotdata:separate"; const char DisplayArrayData[] = "arraydata:separate"; +enum LocationType { UnknownLocation, LocationByFile, LocationByAddress }; + +class ContextData +{ +public: + bool isValid() const { return type != UnknownLocation; } + +public: + LocationType type = UnknownLocation; + QString fileName; + int lineNumber = 0; + quint64 address = 0; +}; + } // namespace Internal } // namespace Debugger diff --git a/src/plugins/debugger/debuggerruncontrol.cpp b/src/plugins/debugger/debuggerruncontrol.cpp index 6c66f7eead4..0f4908b4d0a 100644 --- a/src/plugins/debugger/debuggerruncontrol.cpp +++ b/src/plugins/debugger/debuggerruncontrol.cpp @@ -36,6 +36,7 @@ #include "debuggerrunconfigurationaspect.h" #include "breakhandler.h" #include "shared/peutils.h" +#include "snapshothandler.h" #include #include @@ -60,9 +61,11 @@ #include #include +#include #include #include #include + #include #include @@ -70,7 +73,9 @@ #include #include +#include +using namespace Core; using namespace Debugger::Internal; using namespace ProjectExplorer; using namespace Utils; @@ -84,7 +89,6 @@ DebuggerEngine *createCdbEngine(); DebuggerEngine *createGdbEngine(); DebuggerEngine *createPdbEngine(); DebuggerEngine *createQmlEngine(); -DebuggerEngine *createQmlCppEngine(DebuggerEngine *cppEngine); DebuggerEngine *createLldbEngine(); class LocalProcessRunner : public RunWorker @@ -92,8 +96,8 @@ class LocalProcessRunner : public RunWorker Q_DECLARE_TR_FUNCTIONS(Debugger::Internal::LocalProcessRunner) public: - LocalProcessRunner(RunControl *runControl, const Runnable &runnable) - : RunWorker(runControl), m_runnable(runnable) + LocalProcessRunner(DebuggerRunTool *runTool, const Runnable &runnable) + : RunWorker(runTool->runControl()), m_runTool(runTool), m_runnable(runnable) { connect(&m_proc, &QProcess::errorOccurred, this, &LocalProcessRunner::handleError); @@ -120,16 +124,14 @@ public: { const QByteArray ba = m_proc.readAllStandardOutput(); const QString msg = QString::fromLocal8Bit(ba, ba.length()); - showMessage(msg, LogOutput); - showMessage(msg, AppOutput); + m_runTool->appendMessage(msg, StdOutFormatSameLine); } void handleStandardError() { const QByteArray ba = m_proc.readAllStandardError(); const QString msg = QString::fromLocal8Bit(ba, ba.length()); - showMessage(msg, LogOutput); - showMessage(msg, AppError); + m_runTool->appendMessage(msg, StdErrFormatSameLine); } void handleFinished() @@ -172,10 +174,11 @@ public: "This is the default return value of error()."); } - showMessage(msg, StatusBar); + m_runTool->showMessage(msg, StatusBar); Core::AsynchronousMessageBox::critical(tr("Error"), msg); } + QPointer m_runTool; Runnable m_runnable; Utils::QtcProcess m_proc; }; @@ -245,10 +248,14 @@ private: class DebuggerRunToolPrivate { public: - QPointer terminalRunner; + bool useTerminal = false; QPointer coreUnpacker; QPointer portsGatherer; bool addQmlServerInferiorCommandLineArgumentIfNeeded = false; + TerminalRunner *terminalRunner = nullptr; + int snapshotCounter = 0; + int engineStartsNeeded = 0; + int engineStopsNeeded = 0; }; } // namespace Internal @@ -372,7 +379,7 @@ void DebuggerRunTool::setUseTerminal(bool on) && boolSetting(UseCdbConsole); if (on && !d->terminalRunner && !useCdbConsole) { - d->terminalRunner = new TerminalRunner(this); + d->terminalRunner = new TerminalRunner(runControl(), m_runParameters.inferior); addStartDependency(d->terminalRunner); } if (!on && d->terminalRunner) { @@ -398,7 +405,7 @@ void DebuggerRunTool::setServerStartScript(const QString &serverStartScript) serverStarter.executable = serverStartScript; QtcProcess::addArg(&serverStarter.commandLineArguments, m_runParameters.inferior.executable); QtcProcess::addArg(&serverStarter.commandLineArguments, m_runParameters.remoteChannel); - addStartDependency(new LocalProcessRunner(runControl(), serverStarter)); + addStartDependency(new LocalProcessRunner(this, serverStarter)); } } @@ -509,8 +516,6 @@ void DebuggerRunTool::addSearchDirectory(const QString &dir) void DebuggerRunTool::start() { - Debugger::Internal::saveModeToRestore(); - Debugger::selectPerspective(Debugger::Constants::CppPerspectiveId); TaskHub::clearTasks(Debugger::Constants::TASK_CATEGORY_DEBUGGER_DEBUGINFO); TaskHub::clearTasks(Debugger::Constants::TASK_CATEGORY_DEBUGGER_RUNTIME); @@ -560,25 +565,25 @@ void DebuggerRunTool::start() runControl()->setDisplayName(m_runParameters.displayName); - DebuggerEngine *cppEngine = nullptr; if (!m_engine) { if (m_runParameters.isCppDebugging()) { switch (m_runParameters.cppEngineType) { case GdbEngineType: - cppEngine = createGdbEngine(); + m_engine = createGdbEngine(); break; case CdbEngineType: if (!HostOsInfo::isWindowsHost()) { reportFailure(tr("Unsupported CDB host system.")); return; } - cppEngine = createCdbEngine(); + m_engine = createCdbEngine(); break; case LldbEngineType: - cppEngine = createLldbEngine(); + m_engine = createLldbEngine(); break; case PdbEngineType: // FIXME: Yes, Python counts as C++... - cppEngine = createPdbEngine(); + QTC_CHECK(false); // Called from DebuggerRunTool constructor already. +// m_engine = createPdbEngine(); break; default: if (!m_runParameters.isQmlDebugging) { @@ -592,12 +597,11 @@ void DebuggerRunTool::start() } if (m_runParameters.isQmlDebugging) { - if (cppEngine) - m_engine = createQmlCppEngine(cppEngine); - else + if (m_engine) { + m_engine2 = createQmlEngine(); + } else { m_engine = createQmlEngine(); - } else { - m_engine = cppEngine; + } } } @@ -607,12 +611,57 @@ void DebuggerRunTool::start() } m_engine->setRunTool(this); + m_engine->setRunParameters(m_runParameters); + m_engine->setCompanionEngine(m_engine2); + connect(m_engine, &DebuggerEngine::requestRunControlFinish, + runControl(), &RunControl::initiateFinish); + connect(m_engine, &DebuggerEngine::requestRunControlStop, + runControl(), &RunControl::initiateStop); + connect(m_engine, &DebuggerEngine::engineStarted, + this, [this] { handleEngineStarted(m_engine); }); + connect(m_engine, &DebuggerEngine::engineFinished, + this, [this] { handleEngineFinished(m_engine); }); + connect(m_engine, &DebuggerEngine::appendMessageRequested, + this, &DebuggerRunTool::appendMessage); + ++d->engineStartsNeeded; + ++d->engineStopsNeeded; + + connect(m_engine, &DebuggerEngine::attachToCoreRequested, this, [this](const QString &coreFile) { + auto runConfig = runControl()->runConfiguration(); + QTC_ASSERT(runConfig, return); + auto rc = new RunControl(runConfig, ProjectExplorer::Constants::DEBUG_RUN_MODE); + auto name = QString(tr("%1 - Snapshot %2").arg(runControl()->displayName()).arg(++d->snapshotCounter)); + auto debugger = new DebuggerRunTool(rc); + debugger->setStartMode(AttachCore); + debugger->setRunControlName(name); + debugger->setCoreFileName(coreFile, true); + debugger->startRunControl(); + }); + + if (m_engine2) { + m_engine2->setRunTool(this); + m_engine2->setRunParameters(m_runParameters); + m_engine2->setCompanionEngine(m_engine); + m_engine2->setSecondaryEngine(); + connect(m_engine2, &DebuggerEngine::requestRunControlFinish, + runControl(), &RunControl::initiateFinish); + connect(m_engine2, &DebuggerEngine::requestRunControlStop, + runControl(), &RunControl::initiateStop); + connect(m_engine2, &DebuggerEngine::engineStarted, + this, [this] { handleEngineStarted(m_engine2); }); + connect(m_engine2, &DebuggerEngine::engineFinished, + this, [this] { handleEngineFinished(m_engine2); }); + connect(m_engine2, &DebuggerEngine::appendMessageRequested, + this, &DebuggerRunTool::appendMessage); + ++d->engineStartsNeeded; + ++d->engineStopsNeeded; + } if (m_runParameters.startMode == StartInternal) { QStringList unhandledIds; - for (const Breakpoint &bp : breakHandler()->allBreakpoints()) { - if (bp.isEnabled() && !m_engine->acceptsBreakpoint(bp)) - unhandledIds.append(bp.id().toString()); + for (const GlobalBreakpoint bp : BreakpointManager::globalBreakpoints()) { +// if (bp->isEnabled() && !m_engine->acceptsBreakpoint(bp)) +// unhandledIds.append(bp.id().toString()); } if (!unhandledIds.isEmpty()) { QString warningMessage = @@ -621,7 +670,7 @@ void DebuggerRunTool::start() "Affected are breakpoints %1") .arg(unhandledIds.join(QLatin1String(", "))); - Internal::showMessage(warningMessage, LogWarning); + showMessage(warningMessage, LogWarning); static bool checked = true; if (checked) @@ -634,20 +683,53 @@ void DebuggerRunTool::start() } appendMessage(tr("Debugging starts"), NormalMessageFormat); - Internal::runControlStarted(this); + QString debuggerName = m_engine->objectName(); + if (m_engine2) + debuggerName += ' ' + m_engine2->objectName(); + const QString message = tr("Starting debugger \"%1\" for ABI \"%2\"...") + .arg(debuggerName).arg(m_runParameters.toolChainAbi.toString()); + showStatusMessage(message); + + showMessage(m_engine->formatStartParameters(), LogDebug); + showMessage(DebuggerSettings::dump(), LogDebug); + + if (m_engine2) + m_engine2->start(); m_engine->start(); } void DebuggerRunTool::stop() { - m_isDying = true; QTC_ASSERT(m_engine, reportStopped(); return); + if (m_engine2) + m_engine2->quitDebugger(); m_engine->quitDebugger(); } -const DebuggerRunParameters &DebuggerRunTool::runParameters() const +void DebuggerRunTool::handleEngineStarted(DebuggerEngine *engine) { - return m_runParameters; + EngineManager::activateEngine(engine); + + // Correct: +// if (--d->engineStartsNeeded == 0) { +// EngineManager::activateDebugMode(); +// reportStarted(); +// } + + // Feels better, as the QML Engine might attach late or not at all. + if (engine == m_engine) { + EngineManager::activateDebugMode(); + reportStarted(); + } +} + +void DebuggerRunTool::handleEngineFinished(DebuggerEngine *engine) +{ + engine->prepareForRestart(); + if (--d->engineStopsNeeded == 0) { + appendMessage(tr("Debugging has finished"), NormalMessageFormat); + reportStopped(); + } } bool DebuggerRunTool::isCppDebugging() const @@ -684,36 +766,9 @@ void DebuggerRunTool::setSolibSearchPath(const QStringList &list) m_runParameters.solibSearchPath = list; } -void DebuggerRunTool::notifyInferiorIll() -{ - m_engine->notifyInferiorIll(); -} - -void DebuggerRunTool::notifyInferiorExited() -{ - m_engine->notifyInferiorExited(); -} - void DebuggerRunTool::quitDebugger() { - m_isDying = true; - m_engine->quitDebugger(); -} - -void DebuggerRunTool::abortDebugger() -{ - m_engine->resetLocation(); - if (!m_isDying) { - // Be friendly the first time. This will change targetState(). - showMessage("ABORTING DEBUGGER. FIRST TIME."); - quitDebugger(); - } else { - // We already tried. Try harder. - showMessage("ABORTING DEBUGGER. SECOND TIME."); - m_engine->abortDebuggerProcess(); - if (runControl()) - runControl()->initiateFinish(); - } + initiateStop(); } bool DebuggerRunTool::fixupParameters() @@ -807,9 +862,6 @@ bool DebuggerRunTool::fixupParameters() if (rp.isNativeMixedDebugging()) rp.inferior.environment.set("QV4_FORCE_INTERPRETER", "1"); - if (rp.isCppDebugging() && !rp.skipExecutableValidation) - rp.validateExecutable(); - return true; } @@ -912,11 +964,6 @@ DebuggerRunTool::DebuggerRunTool(RunControl *runControl, Kit *kit, bool allowTer } } -DebuggerEngine *DebuggerRunTool::activeEngine() const -{ - return m_engine ? m_engine->activeEngine() : nullptr; -} - void DebuggerRunTool::startRunControl() { ProjectExplorerPlugin::startRunControl(runControl()); @@ -931,13 +978,14 @@ void DebuggerRunTool::addSolibSearchDir(const QString &str) DebuggerRunTool::~DebuggerRunTool() { - disconnect(); - if (m_engine) { - DebuggerEngine *engine = m_engine; - m_engine = nullptr; - engine->disconnect(); - delete engine; - } + if (m_runParameters.isSnapshot && !m_runParameters.coreFile.isEmpty()) + QFile::remove(m_runParameters.coreFile); + + delete m_engine2; + m_engine2 = nullptr; + delete m_engine; + m_engine = nullptr; + delete d; } @@ -946,7 +994,9 @@ void DebuggerRunTool::showMessage(const QString &msg, int channel, int timeout) if (channel == ConsoleOutput) debuggerConsole()->printItem(ConsoleItem::DefaultType, msg); - Internal::showMessage(msg, channel, timeout); + m_engine->showMessage(msg, channel, timeout); + if (m_engine2) + m_engine->showMessage(msg, channel, timeout); switch (channel) { case AppOutput: appendMessage(msg, StdOutFormatSameLine); diff --git a/src/plugins/debugger/debuggerruncontrol.h b/src/plugins/debugger/debuggerruncontrol.h index dd42db024ac..0692df7da7b 100644 --- a/src/plugins/debugger/debuggerruncontrol.h +++ b/src/plugins/debugger/debuggerruncontrol.h @@ -28,6 +28,7 @@ #include "debugger_global.h" #include "debuggerconstants.h" #include "debuggerengine.h" +#include "terminal.h" #include #include @@ -51,9 +52,6 @@ public: bool allowTerminal = true); ~DebuggerRunTool() override; - Internal::DebuggerEngine *engine() const { return m_engine; } - Internal::DebuggerEngine *activeEngine() const; - void startRunControl(); void showMessage(const QString &msg, int channel = LogDebug, int timeout = -1); @@ -61,15 +59,8 @@ public: void start() override; void stop() override; - void notifyInferiorIll(); - Q_SLOT void notifyInferiorExited(); // Called from Android. void quitDebugger(); - void abortDebugger(); - const Internal::DebuggerRunParameters &runParameters() const; - - void startDying() { m_isDying = true; } - bool isDying() const { return m_isDying; } bool isCppDebugging() const; bool isQmlDebugging() const; int portsUsedByDebugger() const; @@ -138,11 +129,13 @@ public: private: bool fixupParameters(); + void handleEngineStarted(Internal::DebuggerEngine *engine); + void handleEngineFinished(Internal::DebuggerEngine *engine); Internal::DebuggerRunToolPrivate *d; - QPointer m_engine; // Master engine + QPointer m_engine; + QPointer m_engine2; Internal::DebuggerRunParameters m_runParameters; - bool m_isDying = false; }; class DEBUGGER_EXPORT GdbServerPortsGatherer : public ProjectExplorer::ChannelProvider diff --git a/src/plugins/debugger/debuggertooltipmanager.cpp b/src/plugins/debugger/debuggertooltipmanager.cpp index 7ef8d4b8c90..d89e1ef1de8 100644 --- a/src/plugins/debugger/debuggertooltipmanager.cpp +++ b/src/plugins/debugger/debuggertooltipmanager.cpp @@ -28,6 +28,7 @@ #include "debuggerengine.h" #include "debuggerprotocol.h" #include "debuggeractions.h" +#include "snapshothandler.h" #include "stackhandler.h" #include "debuggercore.h" #include "watchhandler.h" @@ -1138,7 +1139,7 @@ static void slotTooltipOverrideRequested return; const TextDocument *document = editorWidget->textDocument(); - DebuggerEngine *engine = currentEngine(); + DebuggerEngine *engine = EngineManager::currentEngine(); if (!engine || !engine->canDisplayTooltip()) return; diff --git a/src/plugins/debugger/disassembleragent.cpp b/src/plugins/debugger/disassembleragent.cpp index d79be06f578..7dc4f65d630 100644 --- a/src/plugins/debugger/disassembleragent.cpp +++ b/src/plugins/debugger/disassembleragent.cpp @@ -68,12 +68,17 @@ public: DisassemblerBreakpointMarker(const Breakpoint &bp, int lineNumber) : TextMark(Utils::FileName(), lineNumber, Constants::TEXT_MARK_CATEGORY_BREAKPOINT), m_bp(bp) { - setIcon(bp.icon()); + setIcon(bp->icon()); setPriority(TextMark::NormalPriority); } - bool isClickable() const override { return true; } - void clicked() override { m_bp.removeBreakpoint(); } + bool isClickable() const final { return true; } + + void clicked() final + { + QTC_ASSERT(m_bp, return); + m_bp->deleteGlobalOrThisBreakpoint(); + } public: Breakpoint m_bp; @@ -331,8 +336,8 @@ void DisassemblerAgent::setContentsToDocument(const DisassemblerLines &contents) d->document->setPreferredDisplayName(QString("Disassembler (%1)") .arg(d->location.functionName())); - const Breakpoints bps = breakHandler()->engineBreakpoints(d->engine); - for (Breakpoint bp : bps) + const Breakpoints bps = d->engine->breakHandler()->breakpoints(); + for (const Breakpoint bp : bps) updateBreakpointMarker(bp); updateLocationMarker(); @@ -340,7 +345,9 @@ void DisassemblerAgent::setContentsToDocument(const DisassemblerLines &contents) void DisassemblerAgent::updateLocationMarker() { - QTC_ASSERT(d->document, return); + if (!d->document) + return; + int lineNumber = d->lineForAddress(d->location.address()); if (d->location.needsMarker()) { d->document->removeMark(&d->locationMark); @@ -348,6 +355,8 @@ void DisassemblerAgent::updateLocationMarker() d->document->addMark(&d->locationMark); } + d->locationMark.updateIcon(); + // Center cursor. if (EditorManager::currentDocument() == d->document) if (auto textEditor = qobject_cast(EditorManager::currentEditor())) @@ -359,9 +368,8 @@ void DisassemblerAgent::removeBreakpointMarker(const Breakpoint &bp) if (!d->document) return; - BreakpointModelId id = bp.id(); - foreach (DisassemblerBreakpointMarker *marker, d->breakpointMarks) { - if (marker->m_bp.id() == id) { + for (DisassemblerBreakpointMarker *marker : d->breakpointMarks) { + if (marker->m_bp == bp) { d->breakpointMarks.removeOne(marker); d->document->removeMark(marker); delete marker; @@ -373,7 +381,7 @@ void DisassemblerAgent::removeBreakpointMarker(const Breakpoint &bp) void DisassemblerAgent::updateBreakpointMarker(const Breakpoint &bp) { removeBreakpointMarker(bp); - const quint64 address = bp.response().address; + const quint64 address = bp->address(); if (!address) return; @@ -384,7 +392,7 @@ void DisassemblerAgent::updateBreakpointMarker(const Breakpoint &bp) // HACK: If it's a FileAndLine breakpoint, and there's a source line // above, move the marker up there. That allows setting and removing // normal breakpoints from within the disassembler view. - if (bp.type() == BreakpointByFileAndLine) { + if (bp->type() == BreakpointByFileAndLine) { ContextData context = getLocationContext(d->document, lineNumber - 1); if (context.type == LocationByFile) --lineNumber; diff --git a/src/plugins/debugger/disassembleragent.h b/src/plugins/debugger/disassembleragent.h index ff6c80c7e2c..aef71ebeaf1 100644 --- a/src/plugins/debugger/disassembleragent.h +++ b/src/plugins/debugger/disassembleragent.h @@ -25,12 +25,13 @@ #pragma once +#include "breakhandler.h" + #include namespace Debugger { namespace Internal { -class Breakpoint; class DebuggerEngine; class DisassemblerAgentPrivate; class DisassemblerLines; diff --git a/src/plugins/debugger/gdb/gdbengine.cpp b/src/plugins/debugger/gdb/gdbengine.cpp index d16121f22ce..be3fa49049e 100644 --- a/src/plugins/debugger/gdb/gdbengine.cpp +++ b/src/plugins/debugger/gdb/gdbengine.cpp @@ -139,6 +139,7 @@ static bool isMostlyHarmlessMessage(const QStringRef &msg) GdbEngine::GdbEngine() { setObjectName("GdbEngine"); + setDebuggerName("GDB"); m_gdbOutputCodec = QTextCodec::codecForLocale(); m_inferiorOutputCodec = QTextCodec::codecForLocale(); @@ -323,6 +324,10 @@ void GdbEngine::handleResponse(const QString &buff) } m_pendingConsoleStreamOutput += data; + // Fragile, but it's all we have. + if (data.contains("\nNo more reverse-execution history.\n")) + handleBeginOfRecordingReached(); + // Show some messages to give the impression something happens. if (data.startsWith("Reading symbols from ")) { showStatusMessage(tr("Reading %1...").arg(data.mid(21)), 1000); @@ -559,25 +564,23 @@ void GdbEngine::handleAsyncOutput(const QString &asyncClass, const GdbMi &result res.fromString(ba); BreakHandler *handler = breakHandler(); Breakpoint bp; - BreakpointResponse br; for (const GdbMi &bkpt : res.children()) { const QString nr = bkpt["number"].data(); - BreakpointResponseId rid(nr); if (nr.contains('.')) { // A sub-breakpoint. - BreakpointResponse sub; - updateResponse(sub, bkpt); - sub.id = rid; - sub.type = br.type; - bp.insertSubBreakpoint(sub); + QTC_ASSERT(bp, continue); + SubBreakpoint loc = bp->findOrCreateSubBreakpoint(nr); + loc->params.updateFromGdbOutput(bkpt); + loc->params.type = bp->type(); } else { // A primary breakpoint. - bp = handler->findBreakpointByResponseId(rid); - br = bp.response(); - updateResponse(br, bkpt); - bp.setResponse(br); + bp = handler->findBreakpointByResponseId(nr); + if (bp) + bp->updateFromGdbOutput(bkpt); } } + if (bp) + bp->adjustMarker(); } else if (asyncClass == "breakpoint-created") { // "{bkpt={number="1",type="breakpoint",disp="del",enabled="y", // addr="",pending="main",times="0", @@ -586,24 +589,22 @@ void GdbEngine::handleAsyncOutput(const QString &asyncClass, const GdbMi &result // what="*0xbfffed48",times="0",original-location="*0xbfffed48"}} BreakHandler *handler = breakHandler(); for (const GdbMi &bkpt : result.children()) { - BreakpointResponse br; + const QString nr = bkpt["number"].data(); + BreakpointParameters br; br.type = BreakpointByFileAndLine; - updateResponse(br, bkpt); - handler->handleAlienBreakpoint(br, this); + br.updateFromGdbOutput(bkpt); + handler->handleAlienBreakpoint(nr, br); } } else if (asyncClass == "breakpoint-deleted") { // "breakpoint-deleted" "{id="1"}" // New in FSF gdb since 2011-04-27. - QString nr = result["id"].data(); - BreakpointResponseId rid(nr); - if (Breakpoint bp = breakHandler()->findBreakpointByResponseId(rid)) { - // This also triggers when a temporary breakpoint is hit. - // We do not really want that, as this loses all information. - // FIXME: Use a special marker for this case? - // if (!bp.isOneShot()) ... is not sufficient. - // It keeps temporary "Jump" breakpoints alive. - bp.removeAlienBreakpoint(); - } + const QString nr = result["id"].data(); + // This also triggers when a temporary breakpoint is hit. + // We do not really want that, as this loses all information. + // FIXME: Use a special marker for this case? + // if (!bp.isOneShot()) ... is not sufficient. + // It keeps temporary "Jump" breakpoints alive. + breakHandler()->removeAlienBreakpoint(nr); } else if (asyncClass == "cmd-param-changed") { // New since 2012-08-09 // "{param="debug remote",value="1"}" @@ -696,11 +697,9 @@ void GdbEngine::interruptInferior() showStatusMessage(tr("Stop requested..."), 5000); showMessage("TRYING TO INTERRUPT INFERIOR"); if (HostOsInfo::isWindowsHost() && !m_isQnxGdb) { - IDevice::ConstPtr device = runTool()->device(); - if (!device) - device = runParameters().inferior.device; - QTC_ASSERT(device, notifyInferiorStopFailed(); return); - DeviceProcessSignalOperation::Ptr signalOperation = device->signalOperation(); + IDevice::ConstPtr dev = device(); + QTC_ASSERT(dev, notifyInferiorStopFailed(); return); + DeviceProcessSignalOperation::Ptr signalOperation = dev->signalOperation(); QTC_ASSERT(signalOperation, notifyInferiorStopFailed(); return); connect(signalOperation.data(), &DeviceProcessSignalOperation::finished, this, [this, signalOperation](const QString &error) { @@ -927,6 +926,14 @@ void GdbEngine::handleResultRecord(DebuggerResponse *response) QTC_CHECK(state() == InferiorRunOk); notifyInferiorSpontaneousStop(); notifyEngineIll(); + } else if (msg.startsWith("Process record: failed to record execution log.")) { + // Reverse execution recording failed. Full message is something like + // ~"Process record does not support instruction 0xfae64 at address 0x7ffff7dec6f8.\n" + notifyInferiorSpontaneousStop(); + handleRecordingFailed(); + } else if (msg.startsWith("Target multi-thread does not support this command.")) { + notifyInferiorSpontaneousStop(); + handleRecordingFailed(); } else if (isGdbConnectionError(msg)) { notifyInferiorExited(); } else { @@ -1026,10 +1033,8 @@ bool GdbEngine::acceptsDebuggerCommands() const // || state() == InferiorUnrunnable; } -void GdbEngine::executeDebuggerCommand(const QString &command, DebuggerLanguages languages) +void GdbEngine::executeDebuggerCommand(const QString &command) { - if (!(languages & CppLanguage)) - return; QTC_CHECK(acceptsDebuggerCommands()); runCommand({command, NativeCommand}); } @@ -1194,7 +1199,7 @@ void GdbEngine::handleStopResponse(const GdbMi &data) return; } - BreakpointResponseId rid(data["bkptno"].data()); + const QString nr = data["bkptno"].data(); int lineNumber = 0; QString fullName; QString function; @@ -1215,17 +1220,15 @@ void GdbEngine::handleStopResponse(const GdbMi &data) showMessage("INVALID STOPPED REASON", LogWarning); } - if (rid.isValid() && frame.isValid()) { + if (!nr.isEmpty() && frame.isValid()) { // Use opportunity to update the breakpoint marker position. - Breakpoint bp = breakHandler()->findBreakpointByResponseId(rid); - const BreakpointResponse &response = bp.response(); - QString fileName = response.fileName; - if (fileName.isEmpty()) - fileName = bp.fileName(); - if (fileName.isEmpty()) - fileName = fullName; - if (!fileName.isEmpty()) - bp.setMarkerFileAndLine(fileName, lineNumber); + if (Breakpoint bp = breakHandler()->findBreakpointByResponseId(nr)) { + QString fileName = bp->fileName(); + if (fileName.isEmpty()) + fileName = fullName; + if (!fileName.isEmpty()) + bp->setMarkerFileAndLine(fileName, lineNumber); + } } //qDebug() << "BP " << rid << data.toString(); @@ -1406,14 +1409,16 @@ void GdbEngine::handleStop2(const GdbMi &data) // {name="p",value="0x0"}],file="x.h",fullname="/home/.../x.h",line="95"}, // thread-id="1",stopped-threads="all",core="2" const GdbMi wpt = data["wpt"]; - const BreakpointResponseId rid(wpt["number"].data()); + const QString rid = wpt["number"].data(); const Breakpoint bp = breakHandler()->findBreakpointByResponseId(rid); const quint64 bpAddress = wpt["exp"].data().mid(1).toULongLong(nullptr, 0); QString msg; - if (bp.type() == WatchpointAtExpression) - msg = bp.msgWatchpointByExpressionTriggered(rid.majorPart(), bp.expression()); - if (bp.type() == WatchpointAtAddress) - msg = bp.msgWatchpointByAddressTriggered(rid.majorPart(), bpAddress); + if (bp) { + if (bp->type() == WatchpointAtExpression) + msg = bp->msgWatchpointByExpressionTriggered(bp->expression()); + if (bp->type() == WatchpointAtAddress) + msg = bp->msgWatchpointByAddressTriggered(bpAddress); + } GdbMi value = data["value"]; GdbMi oldValue = value["old"]; GdbMi newValue = value["new"]; @@ -1427,10 +1432,10 @@ void GdbEngine::handleStop2(const GdbMi &data) GdbMi gNumber = data["bkptno"]; // 'number' or 'bkptno'? if (!gNumber.isValid()) gNumber = data["number"]; - const BreakpointResponseId rid(gNumber.data()); + const QString rid = gNumber.data(); const QString threadId = data["thread-id"].data(); - const Breakpoint bp = breakHandler()->findBreakpointByResponseId(rid); - showStatusMessage(bp.msgBreakpointTriggered(rid.majorPart(), threadId)); + if (const Breakpoint bp = breakHandler()->findBreakpointByResponseId(rid)) + showStatusMessage(bp->msgBreakpointTriggered(threadId)); m_currentThread = threadId; } else { QString reasontr = msgStopped(reason); @@ -1575,7 +1580,7 @@ void GdbEngine::handleExecuteContinue(const DebuggerResponse &response) CHECK_STATE(InferiorStopOk); // FIXME: Fix translation in master. showStatusMessage(msg, 5000); - gotoLocation(stackHandler()->currentFrame()); + gotoCurrentLocation(); } else if (msg.startsWith("Cannot execute this command while the selected thread is running.")) { showExecutionError(msg); notifyInferiorRunFailed() ; @@ -1649,6 +1654,10 @@ QString GdbEngine::cleanupFullName(const QString &fileName) void GdbEngine::shutdownInferior() { CHECK_STATE(InferiorShutdownRequested); + if (runParameters().startMode == AttachCore) { + notifyInferiorShutdownFinished(); + return; + } DebuggerCommand cmd; cmd.function = QLatin1String(runParameters().closeMode == DetachAtClose ? "detach " : "kill "); cmd.callback = CB(handleInferiorShutdown); @@ -1748,45 +1757,49 @@ static QString msgNoGdbBinaryForToolChain(const Abi &tc) bool GdbEngine::hasCapability(unsigned cap) const { - if (cap & (ReverseSteppingCapability - | AutoDerefPointersCapability - | DisassemblerCapability - | RegisterCapability - | ShowMemoryCapability - | JumpToLineCapability - | ReloadModuleCapability - | ReloadModuleSymbolsCapability - | BreakOnThrowAndCatchCapability - | BreakConditionCapability - | BreakIndividualLocationsCapability - | TracePointCapability - | ReturnFromFunctionCapability - | CreateFullBacktraceCapability - | WatchpointByAddressCapability - | WatchpointByExpressionCapability - | AddWatcherCapability - | AddWatcherWhileRunningCapability - | WatchWidgetsCapability - | ShowModuleSymbolsCapability - | ShowModuleSectionsCapability - | CatchCapability - | OperateByInstructionCapability - | RunToLineCapability - | WatchComplexExpressionsCapability - | MemoryAddressCapability - | AdditionalQmlStackCapability - | NativeMixedCapability - | ResetInferiorCapability)) - return true; + if (runParameters().startMode == AttachCore) { + return cap & (AutoDerefPointersCapability + | DisassemblerCapability + | RegisterCapability + | ShowMemoryCapability + | CreateFullBacktraceCapability + | ShowModuleSymbolsCapability + | ShowModuleSectionsCapability + | WatchComplexExpressionsCapability + | MemoryAddressCapability + | AdditionalQmlStackCapability); + } - if (runParameters().startMode == AttachCore) - return false; - - // FIXME: Remove in case we have gdb 7.x on macOS. - if (runParameters().toolChainAbi.os() == Abi::DarwinOS) - return false; - - return cap == SnapshotCapability; + return cap & (AutoDerefPointersCapability + | DisassemblerCapability + | RegisterCapability + | ShowMemoryCapability + | JumpToLineCapability + | ReloadModuleCapability + | ReloadModuleSymbolsCapability + | BreakOnThrowAndCatchCapability + | BreakConditionCapability + | BreakIndividualLocationsCapability + | TracePointCapability + | ReturnFromFunctionCapability + | CreateFullBacktraceCapability + | WatchpointByAddressCapability + | WatchpointByExpressionCapability + | AddWatcherCapability + | AddWatcherWhileRunningCapability + | WatchWidgetsCapability + | ShowModuleSymbolsCapability + | ShowModuleSectionsCapability + | CatchCapability + | OperateByInstructionCapability + | RunToLineCapability + | WatchComplexExpressionsCapability + | MemoryAddressCapability + | AdditionalQmlStackCapability + | NativeMixedCapability + | ResetInferiorCapability + | SnapshotCapability + | ReverseSteppingCapability); } @@ -1828,7 +1841,9 @@ void GdbEngine::executeStep() } else { DebuggerCommand cmd; cmd.flags = RunRequest|NeedsFlush; - cmd.function = QLatin1String(isReverseDebugging() ? "reverse-step" : "-exec-step"); + cmd.function = "-exec-step"; + if (isReverseDebugging()) + cmd.function += " --reverse"; cmd.callback = CB(handleExecuteStep); runCommand(cmd); } @@ -1878,7 +1893,9 @@ void GdbEngine::executeStepI() showStatusMessage(tr("Step by instruction requested..."), 5000); DebuggerCommand cmd; cmd.flags = RunRequest|NeedsFlush; - cmd.function = QLatin1String(isReverseDebugging() ? "reverse-stepi" : "-exec-step-instruction"); + cmd.function = "-exec-step-instruction"; + if (isReverseDebugging()) + cmd.function += "--reverse"; cmd.callback = CB(handleExecuteContinue); runCommand(cmd); } @@ -1912,7 +1929,9 @@ void GdbEngine::executeNext() } else { DebuggerCommand cmd; cmd.flags = RunRequest; - cmd.function = QLatin1String(isReverseDebugging() ? "reverse-next" : "-exec-next"); + cmd.function = "-exec-next"; + if (isReverseDebugging()) + cmd.function += " --reverse"; cmd.callback = CB(handleExecuteNext); runCommand(cmd); } @@ -1942,10 +1961,13 @@ void GdbEngine::handleExecuteNext(const DebuggerResponse &response) } else if (msg.startsWith("Cannot execute this command while the selected thread is running.")) { showExecutionError(msg); notifyInferiorRunFailed(); + } else if (msg.startsWith("Target multi-thread does not support this command.")) { + notifyInferiorRunFailed(); + handleRecordingFailed(); } else { - AsynchronousMessageBox::critical(tr("Execution Error"), + AsynchronousMessageBox::warning(tr("Execution Error"), tr("Cannot continue debugged process:") + '\n' + msg); - notifyInferiorIll(); + //notifyInferiorIll(); } } @@ -1957,7 +1979,9 @@ void GdbEngine::executeNextI() showStatusMessage(tr("Step next instruction requested..."), 5000); DebuggerCommand cmd; cmd.flags = RunRequest; - cmd.function = QLatin1String(isReverseDebugging() ? "reverse-nexti" : "-exec-next-instruction"); + cmd.function = "-exec-next-instruction"; + if (isReverseDebugging()) + cmd.function += " --reverse"; cmd.callback = CB(handleExecuteContinue); runCommand(cmd); } @@ -2028,6 +2052,14 @@ void GdbEngine::executeReturn() runCommand({"-exec-return", RunRequest, CB(handleExecuteReturn)}); } +void GdbEngine::executeRecordReverse(bool record) +{ + if (record) + runCommand({"record full"}); + else + runCommand({"record stop"}); +} + void GdbEngine::handleExecuteReturn(const DebuggerResponse &response) { if (response.resultClass == ResultDone) { @@ -2074,120 +2106,6 @@ void GdbEngine::setTokenBarrier() // ////////////////////////////////////////////////////////////////////// -void GdbEngine::updateResponse(BreakpointResponse &response, const GdbMi &bkpt) -{ - QTC_ASSERT(bkpt.isValid(), return); - - QString originalLocation; - QString file; - QString fullName; - - response.multiple = false; - response.enabled = true; - response.pending = false; - response.condition.clear(); - for (const GdbMi &child : bkpt.children()) { - if (child.hasName("number")) { - response.id = BreakpointResponseId(child.data()); - } else if (child.hasName("func")) { - response.functionName = child.data(); - } else if (child.hasName("addr")) { - // happens in constructors, inline functions, and - // at other places like 'foreach' lines. In this case there are - // fields named "addr" in the response and/or the address - // is called . - //qDebug() << "ADDR: " << child.data() << (child.data() == ""); - if (child.data() == "") - response.multiple = true; - if (child.data().startsWith("0x")) - response.address = child.toAddress(); - } else if (child.hasName("file")) { - file = child.data(); - } else if (child.hasName("fullname")) { - fullName = child.data(); - } else if (child.hasName("line")) { - // The line numbers here are the uncorrected ones. So don't - // change it if we know better already. - if (response.correctedLineNumber == 0) - response.lineNumber = child.toInt(); - } else if (child.hasName("cond")) { - // gdb 6.3 likes to "rewrite" conditions. Just accept that fact. - response.condition = child.data(); - } else if (child.hasName("enabled")) { - response.enabled = (child.data() == "y"); - } else if (child.hasName("disp")) { - response.oneShot = child.data() == "del"; - } else if (child.hasName("pending")) { - // Any content here would be interesting only if we did accept - // spontaneously appearing breakpoints (user using gdb commands). - if (file.isEmpty()) - file = child.data(); - response.pending = true; - } else if (child.hasName("at")) { - // Happens with gdb 6.4 symbianelf. - QString ba = child.data(); - if (ba.startsWith('<') && ba.endsWith('>')) - ba = ba.mid(1, ba.size() - 2); - response.functionName = ba; - } else if (child.hasName("thread")) { - response.threadSpec = child.toInt(); - } else if (child.hasName("type")) { - // "breakpoint", "hw breakpoint", "tracepoint", "hw watchpoint" - // {bkpt={number="2",type="hw watchpoint",disp="keep",enabled="y", - // what="*0xbfffed48",times="0",original-location="*0xbfffed48"}} - if (child.data().contains("tracepoint")) { - response.tracepoint = true; - } else if (child.data() == "hw watchpoint" || child.data() == "watchpoint") { - QString what = bkpt["what"].data(); - if (what.startsWith("*0x")) { - response.type = WatchpointAtAddress; - response.address = what.mid(1).toULongLong(nullptr, 0); - } else { - response.type = WatchpointAtExpression; - response.expression = what; - } - } else if (child.data() == "breakpoint") { - QString catchType = bkpt["catch-type"].data(); - if (catchType == "throw") - response.type = BreakpointAtThrow; - else if (catchType == "catch") - response.type = BreakpointAtCatch; - else if (catchType == "fork") - response.type = BreakpointAtFork; - else if (catchType == "exec") - response.type = BreakpointAtExec; - else if (catchType == "syscall") - response.type = BreakpointAtSysCall; - } - } else if (child.hasName("times")) { - response.hitCount = child.toInt(); - } else if (child.hasName("original-location")) { - originalLocation = child.data(); - } - // This field is not present. Contents needs to be parsed from - // the plain "ignore" response. - //else if (child.hasName("ignore")) - // response.ignoreCount = child.data(); - } - - QString name; - if (!fullName.isEmpty()) { - name = cleanupFullName(fullName); - response.fileName = name; - //if (data->markerFileName().isEmpty()) - // data->setMarkerFileName(name); - } else { - name = file; - // Use fullName() once we have a mapping which is more complete than - // gdb's own. No point in assigning markerFileName for now. - } - if (!name.isEmpty()) - response.fileName = name; - - if (response.fileName.isEmpty()) - response.updateLocation(originalLocation); -} - QString GdbEngine::breakLocation(const QString &file) const { QString where = m_fullToShortName.value(file); @@ -2234,84 +2152,76 @@ QString GdbEngine::breakpointLocation2(const BreakpointParameters &data) return GdbMi::escapeCString(fileName) + ':' + QString::number(data.lineNumber); } -void GdbEngine::handleInsertInterpreterBreakpoint(const DebuggerResponse &response, Breakpoint bp) +void GdbEngine::handleInsertInterpreterBreakpoint(const DebuggerResponse &response, + const Breakpoint &bp) { - BreakpointResponse br = bp.response(); - bool pending = response.data["pending"].toInt(); + QTC_ASSERT(bp, return); + const bool pending = response.data["pending"].toInt(); if (pending) { - bp.notifyBreakpointInsertOk(); + notifyBreakpointInsertOk(bp); } else { - br.id = BreakpointResponseId(response.data["number"].data()); - updateResponse(br, response.data); - bp.setResponse(br); - bp.notifyBreakpointInsertOk(); + bp->setResponseId(response.data["number"].data()); + bp->updateFromGdbOutput(response.data); + notifyBreakpointInsertOk(bp); } } void GdbEngine::handleInterpreterBreakpointModified(const GdbMi &data) { - BreakpointModelId id(data["modelid"].data()); - Breakpoint bp = breakHandler()->breakpointById(id); - BreakpointResponse br = bp.response(); - updateResponse(br, data); - bp.setResponse(br); + int modelId = data["modelid"].toInt(); + Breakpoint bp = breakHandler()->findBreakpointByModelId(modelId); + QTC_ASSERT(bp, return); + bp->updateFromGdbOutput(data); } -void GdbEngine::handleWatchInsert(const DebuggerResponse &response, Breakpoint bp) +void GdbEngine::handleWatchInsert(const DebuggerResponse &response, const Breakpoint &bp) { if (bp && response.resultClass == ResultDone) { - BreakpointResponse br = bp.response(); // "Hardware watchpoint 2: *0xbfffed40\n" QString ba = response.consoleStreamOutput; GdbMi wpt = response.data["wpt"]; if (wpt.isValid()) { // Mac yields: //>32^done,wpt={number="4",exp="*4355182176"} - br.id = BreakpointResponseId(wpt["number"].data()); + bp->setResponseId(wpt["number"].data()); QString exp = wpt["exp"].data(); if (exp.startsWith('*')) - br.address = exp.mid(1).toULongLong(nullptr, 0); - bp.setResponse(br); - QTC_CHECK(!bp.needsChange()); - bp.notifyBreakpointInsertOk(); + bp->setAddress(exp.mid(1).toULongLong(nullptr, 0)); + QTC_CHECK(!bp->needsChange()); + notifyBreakpointInsertOk(bp); } else if (ba.startsWith("Hardware watchpoint ") || ba.startsWith("Watchpoint ")) { // Non-Mac: "Hardware watchpoint 2: *0xbfffed40\n" const int end = ba.indexOf(':'); const int begin = ba.lastIndexOf(' ', end) + 1; const QString address = ba.mid(end + 2).trimmed(); - br.id = BreakpointResponseId(ba.mid(begin, end - begin)); + bp->setResponseId(ba.mid(begin, end - begin)); if (address.startsWith('*')) - br.address = address.mid(1).toULongLong(nullptr, 0); - bp.setResponse(br); - QTC_CHECK(!bp.needsChange()); - bp.notifyBreakpointInsertOk(); + bp->setAddress(address.mid(1).toULongLong(nullptr, 0)); + QTC_CHECK(!bp->needsChange()); + notifyBreakpointInsertOk(bp); } else { showMessage("CANNOT PARSE WATCHPOINT FROM " + ba); } } } -void GdbEngine::handleCatchInsert(const DebuggerResponse &response, Breakpoint bp) +void GdbEngine::handleCatchInsert(const DebuggerResponse &response, const Breakpoint &bp) { - if (bp && response.resultClass == ResultDone) - bp.notifyBreakpointInsertOk(); + if (response.resultClass == ResultDone) + notifyBreakpointInsertOk(bp); } -void GdbEngine::handleBkpt(const GdbMi &bkpt, Breakpoint bp) +void GdbEngine::handleBkpt(const GdbMi &bkpt, const Breakpoint &bp) { - BreakpointResponse br = bp.response(); QTC_ASSERT(bp, return); const QString nr = bkpt["number"].data(); - const BreakpointResponseId rid(nr); - QTC_ASSERT(rid.isValid(), return); if (nr.contains('.')) { // A sub-breakpoint. - BreakpointResponse sub; - updateResponse(sub, bkpt); - sub.id = rid; - sub.type = bp.type(); - bp.insertSubBreakpoint(sub); + SubBreakpoint sub = bp->findOrCreateSubBreakpoint(nr); + QTC_ASSERT(sub, return); + sub->params.updateFromGdbOutput(bkpt); + sub->params.type = bp->type(); return; } @@ -2319,35 +2229,33 @@ void GdbEngine::handleBkpt(const GdbMi &bkpt, Breakpoint bp) // http://permalink.gmane.org/gmane.comp.gdb.patches/83936 const GdbMi locations = bkpt["locations"]; if (locations.isValid()) { - for (const GdbMi &loc : locations.children()) { + for (const GdbMi &location : locations.children()) { // A sub-breakpoint. - const QString subnr = loc["number"].data(); - const BreakpointResponseId subrid(subnr); - BreakpointResponse sub; - updateResponse(sub, loc); - sub.id = subrid; - sub.type = br.type; - bp.insertSubBreakpoint(sub); + const QString subnr = location["number"].data(); + SubBreakpoint sub = bp->findOrCreateSubBreakpoint(subnr); + QTC_ASSERT(sub, return); + sub->params.updateFromGdbOutput(location); + sub->params.type = bp->type(); } } // A (the?) primary breakpoint. - updateResponse(br, bkpt); - br.id = rid; - bp.setResponse(br); + bp->setResponseId(nr); + bp->updateFromGdbOutput(bkpt); } -void GdbEngine::handleBreakInsert1(const DebuggerResponse &response, Breakpoint bp) +void GdbEngine::handleBreakInsert1(const DebuggerResponse &response, const Breakpoint &bp) { - if (bp.state() == BreakpointRemoveRequested) { + QTC_ASSERT(bp, return); + if (bp->state() == BreakpointRemoveRequested) { if (response.resultClass == ResultDone) { // This delete was deferred. Act now. const GdbMi mainbkpt = response.data["bkpt"]; - bp.notifyBreakpointRemoveProceeding(); + notifyBreakpointRemoveProceeding(bp); DebuggerCommand cmd("-break-delete " + mainbkpt["number"].data()); cmd.flags = NeedsTemporaryStop; runCommand(cmd); - bp.notifyBreakpointRemoveOk(); + notifyBreakpointRemoveOk(bp); return; } } @@ -2359,17 +2267,17 @@ void GdbEngine::handleBreakInsert1(const DebuggerResponse &response, Breakpoint // iterate over all items to update main- and sub-data. for (const GdbMi &bkpt : response.data.children()) handleBkpt(bkpt, bp); - if (bp.needsChange()) { - bp.notifyBreakpointChangeAfterInsertNeeded(); - changeBreakpoint(bp); + if (bp->needsChange()) { + bp->gotoState(BreakpointUpdateRequested, BreakpointInsertionProceeding); + updateBreakpoint(bp); } else { - bp.notifyBreakpointInsertOk(); + notifyBreakpointInsertOk(bp); } } else if (response.data["msg"].data().contains("Unknown option")) { // Older version of gdb don't know the -a option to set tracepoints // ^error,msg="mi_cmd_break_insert: Unknown option ``a''" - const QString fileName = bp.fileName(); - const int lineNumber = bp.lineNumber(); + const QString fileName = bp->fileName(); + const int lineNumber = bp->lineNumber(); DebuggerCommand cmd("trace \"" + GdbMi::escapeCString(fileName) + "\":" + QString::number(lineNumber), NeedsTemporaryStop); @@ -2378,18 +2286,17 @@ void GdbEngine::handleBreakInsert1(const DebuggerResponse &response, Breakpoint // Some versions of gdb like "GNU gdb (GDB) SUSE (6.8.91.20090930-2.4)" // know how to do pending breakpoints using CLI but not MI. So try // again with MI. - DebuggerCommand cmd("break " + breakpointLocation2(bp.parameters()), + DebuggerCommand cmd("break " + breakpointLocation2(bp->requestedParameters()), NeedsTemporaryStop); cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakInsert2(r, bp); }; runCommand(cmd); } } -void GdbEngine::handleBreakInsert2(const DebuggerResponse &response, Breakpoint bp) +void GdbEngine::handleBreakInsert2(const DebuggerResponse &response, const Breakpoint &bp) { if (response.resultClass == ResultDone) { - QTC_ASSERT(bp, return); - bp.notifyBreakpointInsertOk(); + notifyBreakpointInsertOk(bp); } else { // Note: gdb < 60800 doesn't "do" pending breakpoints. // Not much we can do about it except implementing the @@ -2398,49 +2305,49 @@ void GdbEngine::handleBreakInsert2(const DebuggerResponse &response, Breakpoint } } -void GdbEngine::handleBreakDisable(const DebuggerResponse &response, Breakpoint bp) +void GdbEngine::handleBreakDisable(const DebuggerResponse &response, const Breakpoint &bp) { - QTC_CHECK(response.resultClass == ResultDone); - // This should only be the requested state. - QTC_ASSERT(!bp.isEnabled(), /* Prevent later recursion */); - BreakpointResponse br = bp.response(); - br.enabled = false; - bp.setResponse(br); - changeBreakpoint(bp); // Maybe there's more to do. + if (response.resultClass == ResultDone) { + // This should only be the requested state. + QTC_ASSERT(bp, return); + bp->setEnabled(false); + // GDB does *not* touch the subbreakpoints in that case + // bp->forFirstLevelChildren([&](const SubBreakpoint &l) { l->params.enabled = false; }); + updateBreakpoint(bp); // Maybe there's more to do. + } } -void GdbEngine::handleBreakEnable(const DebuggerResponse &response, Breakpoint bp) +void GdbEngine::handleBreakEnable(const DebuggerResponse &response, const Breakpoint &bp) { - QTC_CHECK(response.resultClass == ResultDone); - // This should only be the requested state. - QTC_ASSERT(bp.isEnabled(), /* Prevent later recursion */); - BreakpointResponse br = bp.response(); - br.enabled = true; - bp.setResponse(br); - changeBreakpoint(bp); // Maybe there's more to do. + if (response.resultClass == ResultDone) { + QTC_ASSERT(bp, return); + // This should only be the requested state. + bp->setEnabled(true); + // GDB does *not* touch the subbreakpoints in that case + //bp->forFirstLevelChildren([&](const SubBreakpoint &l) { l->params.enabled = true; }); + updateBreakpoint(bp); // Maybe there's more to do. + } } -void GdbEngine::handleBreakThreadSpec(const DebuggerResponse &response, Breakpoint bp) +void GdbEngine::handleBreakThreadSpec(const DebuggerResponse &response, const Breakpoint &bp) { QTC_CHECK(response.resultClass == ResultDone); - BreakpointResponse br = bp.response(); - br.threadSpec = bp.threadSpec(); - bp.setResponse(br); - bp.notifyBreakpointNeedsReinsertion(); + QTC_ASSERT(bp, return); + // Parsing is fragile. Assume we got what we asked for instead. + bp->setThreadSpec(bp->requestedParameters().threadSpec); + notifyBreakpointNeedsReinsertion(bp); insertBreakpoint(bp); } -void GdbEngine::handleBreakLineNumber(const DebuggerResponse &response, Breakpoint bp) +void GdbEngine::handleBreakLineNumber(const DebuggerResponse &response, const Breakpoint &bp) { QTC_CHECK(response.resultClass == ResultDone); - BreakpointResponse br = bp.response(); - br.lineNumber = bp.lineNumber(); - bp.setResponse(br); - bp.notifyBreakpointNeedsReinsertion(); + QTC_ASSERT(bp, return); + notifyBreakpointNeedsReinsertion(bp); insertBreakpoint(bp); } -void GdbEngine::handleBreakIgnore(const DebuggerResponse &response, Breakpoint bp) +void GdbEngine::handleBreakIgnore(const DebuggerResponse &response, const Breakpoint &bp) { // gdb 6.8: // ignore 2 0: @@ -2453,22 +2360,21 @@ void GdbEngine::handleBreakIgnore(const DebuggerResponse &response, Breakpoint b // // gdb 6.3 does not produce any console output QTC_CHECK(response.resultClass == ResultDone); + QTC_ASSERT(bp, return); //QString msg = _(response.consoleStreamOutput); - BreakpointResponse br = bp.response(); //if (msg.contains(__("Will stop next time breakpoint"))) // response.ignoreCount = _("0"); //else if (msg.contains(__("Will ignore next"))) // response.ignoreCount = data->ignoreCount; // FIXME: this assumes it is doing the right thing... - const BreakpointParameters ¶meters = bp.parameters(); - br.ignoreCount = parameters.ignoreCount; - br.command = parameters.command; - bp.setResponse(br); - changeBreakpoint(bp); // Maybe there's more to do. + bp->setIgnoreCount(bp->requestedParameters().ignoreCount); + bp->setCommand(bp->requestedParameters().command); + updateBreakpoint(bp); // Maybe there's more to do. } -void GdbEngine::handleBreakCondition(const DebuggerResponse &, Breakpoint bp) +void GdbEngine::handleBreakCondition(const DebuggerResponse &, const Breakpoint &bp) { + QTC_ASSERT(bp, return); // Can happen at invalid condition strings. //QTC_CHECK(response.resultClass == ResultDone) // We just assume it was successful. Otherwise we had to parse @@ -2477,10 +2383,8 @@ void GdbEngine::handleBreakCondition(const DebuggerResponse &, Breakpoint bp) // QByteArray msg = response.data.findChild("msg").data(); // if (msg.startsWith("Error parsing breakpoint condition. " // " Will try again when we hit the breakpoint.")) - BreakpointResponse br = bp.response(); - br.condition = bp.condition(); - bp.setResponse(br); - changeBreakpoint(bp); // Maybe there's more to do. + bp->setCondition(bp->requestedParameters().condition); + updateBreakpoint(bp); // Maybe there's more to do. } bool GdbEngine::stateAcceptsBreakpointChanges() const @@ -2497,27 +2401,28 @@ bool GdbEngine::stateAcceptsBreakpointChanges() const } } -bool GdbEngine::acceptsBreakpoint(Breakpoint bp) const +bool GdbEngine::acceptsBreakpoint(const BreakpointParameters &bp) const { if (runParameters().startMode == AttachCore) return false; - if (bp.parameters().isCppBreakpoint()) + if (bp.isCppBreakpoint()) return true; return isNativeMixedEnabled(); } -void GdbEngine::insertBreakpoint(Breakpoint bp) +void GdbEngine::insertBreakpoint(const Breakpoint &bp) { // Set up fallback in case of pending breakpoints which aren't handled - // by the MI interface. - QTC_CHECK(bp.state() == BreakpointInsertRequested); - bp.notifyBreakpointInsertProceeding(); + // by the MI interface.A + QTC_ASSERT(bp, return); + QTC_CHECK(bp->state() == BreakpointInsertionRequested); + notifyBreakpointInsertProceeding(bp); - const BreakpointParameters &data = bp.parameters(); + const BreakpointParameters &requested = bp->requestedParameters(); - if (!data.isCppBreakpoint()) { + if (!requested.isCppBreakpoint()) { DebuggerCommand cmd("insertInterpreterBreakpoint", NeedsTemporaryStop); - bp.addToCommand(&cmd); + bp->addToCommand(&cmd); cmd.callback = [this, bp](const DebuggerResponse &r) { handleInsertInterpreterBreakpoint(r, bp); }; runCommand(cmd); return; @@ -2526,14 +2431,14 @@ void GdbEngine::insertBreakpoint(Breakpoint bp) const auto handleWatch = [this, bp](const DebuggerResponse &r) { handleWatchInsert(r, bp); }; const auto handleCatch = [this, bp](const DebuggerResponse &r) { handleCatchInsert(r, bp); }; - BreakpointType type = bp.type(); + BreakpointType type = requested.type; DebuggerCommand cmd; if (type == WatchpointAtAddress) { - cmd.function = "watch " + addressSpec(bp.address()); + cmd.function = "watch " + addressSpec(requested.address); cmd.callback = handleWatch; } else if (type == WatchpointAtExpression) { - cmd.function = "watch " + bp.expression(); + cmd.function = "watch " + requested.expression; cmd.callback = handleWatch; } else if (type == BreakpointAtFork) { cmd.function = "catch fork"; @@ -2549,61 +2454,61 @@ void GdbEngine::insertBreakpoint(Breakpoint bp) cmd.function = "catch syscall"; cmd.callback = handleCatch; } else { - if (bp.isTracepoint()) { + if (requested.isTracepoint()) { cmd.function = "-break-insert -a -f "; } else { - int spec = bp.threadSpec(); + int spec = requested.threadSpec; cmd.function = "-break-insert "; if (spec >= 0) cmd.function += "-p " + QString::number(spec); cmd.function += " -f "; } - if (bp.isOneShot()) + if (requested.oneShot) cmd.function += "-t "; - if (!bp.isEnabled()) + if (!requested.enabled) cmd.function += "-d "; - if (int ignoreCount = bp.ignoreCount()) + if (int ignoreCount = requested.ignoreCount) cmd.function += "-i " + QString::number(ignoreCount) + ' '; - QString condition = bp.condition(); + QString condition = requested.condition; if (!condition.isEmpty()) cmd.function += " -c \"" + condition.replace('"', "\\\"") + "\" "; - cmd.function += breakpointLocation(bp.parameters()); + cmd.function += breakpointLocation(requested); cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakInsert1(r, bp); }; } cmd.flags = NeedsTemporaryStop; runCommand(cmd); } -void GdbEngine::changeBreakpoint(Breakpoint bp) +void GdbEngine::updateBreakpoint(const Breakpoint &bp) { - const BreakpointParameters &data = bp.parameters(); - QTC_ASSERT(data.type != UnknownBreakpointType, return); - const BreakpointResponse &response = bp.response(); - QTC_ASSERT(response.id.isValid(), return); - const QString bpnr = response.id.toString(); - const BreakpointState state = bp.state(); - if (state == BreakpointChangeRequested) - bp.notifyBreakpointChangeProceeding(); - const BreakpointState state2 = bp.state(); - QTC_ASSERT(state2 == BreakpointChangeProceeding, qDebug() << state2); + QTC_ASSERT(bp, return); + const BreakpointParameters &requested = bp->requestedParameters(); + QTC_ASSERT(requested.type != UnknownBreakpointType, return); + QTC_ASSERT(!bp->responseId().isEmpty(), return); + const QString bpnr = bp->responseId(); + const BreakpointState state = bp->state(); + if (state == BreakpointUpdateRequested) + notifyBreakpointChangeProceeding(bp); + const BreakpointState state2 = bp->state(); + QTC_ASSERT(state2 == BreakpointUpdateProceeding, qDebug() << state2); DebuggerCommand cmd; - if (!response.pending && data.threadSpec != response.threadSpec) { + if (!bp->isPending() && requested.threadSpec != bp->threadSpec()) { // The only way to change this seems to be to re-set the bp completely. cmd.function = "-break-delete " + bpnr; cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakThreadSpec(r, bp); }; - } else if (!response.pending && data.lineNumber != response.lineNumber) { + } else if (!bp->isPending() && requested.lineNumber != bp->lineNumber()) { // The only way to change this seems to be to re-set the bp completely. cmd.function = "-break-delete " + bpnr; cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakLineNumber(r, bp); }; - } else if (data.command != response.command) { + } else if (requested.command != bp->command()) { cmd.function = "-break-commands " + bpnr; - for (QString command : data.command.split('\n')) { + for (QString command : requested.command.split('\n')) { if (!command.isEmpty()) { // escape backslashes and quotes command.replace('\\', "\\\\"); @@ -2612,58 +2517,59 @@ void GdbEngine::changeBreakpoint(Breakpoint bp) } } cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakIgnore(r, bp); }; - } else if (!data.conditionsMatch(response.condition)) { - cmd.function = "condition " + bpnr + ' ' + data.condition; + } else if (!requested.conditionsMatch(bp->condition())) { + cmd.function = "condition " + bpnr + ' ' + requested.condition; cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakCondition(r, bp); }; - } else if (data.ignoreCount != response.ignoreCount) { - cmd.function = "ignore " + bpnr + ' ' + QString::number(data.ignoreCount); + } else if (requested.ignoreCount != bp->ignoreCount()) { + cmd.function = "ignore " + bpnr + ' ' + QString::number(requested.ignoreCount); cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakIgnore(r, bp); }; - } else if (!data.enabled && response.enabled) { + } else if (!requested.enabled && bp->isEnabled()) { cmd.function = "-break-disable " + bpnr; cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakDisable(r, bp); }; - } else if (data.enabled && !response.enabled) { + } else if (requested.enabled && !bp->isEnabled()) { cmd.function = "-break-enable " + bpnr; cmd.callback = [this, bp](const DebuggerResponse &r) { handleBreakEnable(r, bp); }; } else { - bp.notifyBreakpointChangeOk(); + notifyBreakpointChangeOk(bp); return; } cmd.flags = NeedsTemporaryStop; runCommand(cmd); } -void GdbEngine::enableSubBreakpoint(const QString &locId, bool on) +void GdbEngine::enableSubBreakpoint(const SubBreakpoint &sbp, bool on) { - DebuggerCommand cmd((on ? "-break-enable " : "-break-disable ") + locId); + QTC_ASSERT(sbp, return); + DebuggerCommand cmd((on ? "-break-enable " : "-break-disable ") + sbp->responseId); runCommand(cmd); } -void GdbEngine::removeBreakpoint(Breakpoint bp) +void GdbEngine::removeBreakpoint(const Breakpoint &bp) { - QTC_CHECK(bp.state() == BreakpointRemoveRequested); - BreakpointResponse br = bp.response(); + QTC_ASSERT(bp, return); + QTC_CHECK(bp->state() == BreakpointRemoveRequested); - const BreakpointParameters &data = bp.parameters(); - if (!data.isCppBreakpoint()) { + const BreakpointParameters &requested = bp->requestedParameters(); + if (!requested.isCppBreakpoint()) { DebuggerCommand cmd("removeInterpreterBreakpoint"); - bp.addToCommand(&cmd); + bp->addToCommand(&cmd); runCommand(cmd); - bp.notifyBreakpointRemoveOk(); + notifyBreakpointRemoveOk(bp); return; } - if (br.id.isValid()) { + if (!bp->responseId().isEmpty()) { // We already have a fully inserted breakpoint. - bp.notifyBreakpointRemoveProceeding(); - showMessage(QString("DELETING BP %1 IN %2").arg(br.id.toString()).arg(bp.fileName())); - DebuggerCommand cmd("-break-delete " + br.id.toString(), NeedsTemporaryStop); + notifyBreakpointRemoveProceeding(bp); + showMessage(QString("DELETING BP %1 IN %2").arg(bp->responseId()).arg(bp->fileName())); + DebuggerCommand cmd("-break-delete " + bp->responseId(), NeedsTemporaryStop); runCommand(cmd); // Pretend it succeeds without waiting for response. Feels better. // Otherwise, clicking in the gutter leaves the breakpoint visible // for quite some time, so the user assumes a mis-click and clicks // again, effectivly re-introducing the breakpoint. - bp.notifyBreakpointRemoveOk(); + notifyBreakpointRemoveOk(bp); } else { // Breakpoint was scheduled to be inserted, but we haven't had @@ -3060,7 +2966,7 @@ void GdbEngine::activateFrame(int frameIndex) QTC_ASSERT(frameIndex < handler->stackSize(), return); handler->setCurrentIndex(frameIndex); - gotoLocation(stackHandler()->currentFrame()); + gotoCurrentLocation(); if (handler->frameAt(frameIndex).language != QmlLanguage) { // Assuming the command always succeeds this saves a roundtrip. @@ -3086,7 +2992,7 @@ void GdbEngine::handleThreadInfo(const DebuggerResponse &response) if (other.isValid()) selectThread(other); } - updateViews(); // Adjust Threads combobox. + updateState(false); // Adjust Threads combobox. if (boolSetting(ShowThreadNames)) { runCommand({"threadnames " + action(MaximalStackDepth)->value().toString(), Discardable, CB(handleThreadNames)}); @@ -3125,7 +3031,7 @@ void GdbEngine::handleThreadNames(const DebuggerResponse &response) thread.name = decodeData(name["value"].data(), name["valueencoded"].data()); handler->updateThread(thread); } - updateViews(); + updateState(false); } } @@ -3156,21 +3062,7 @@ void GdbEngine::createSnapshot() void GdbEngine::handleMakeSnapshot(const DebuggerResponse &response, const QString &coreFile) { if (response.resultClass == ResultDone) { - //snapshot.setDate(QDateTime::currentDateTime()); - StackFrames frames = stackHandler()->frames(); - QString function = ""; - if (!frames.isEmpty()) { - const StackFrame &frame = frames.at(0); - function = frame.function + ":" + QString::number(frame.line); - } - auto runConfig = runTool()->runControl()->runConfiguration(); - QTC_ASSERT(runConfig, return); - auto rc = new RunControl(runConfig, ProjectExplorer::Constants::DEBUG_RUN_MODE); - auto debugger = new DebuggerRunTool(rc); - debugger->setStartMode(AttachCore); - debugger->setRunControlName(function + ": " + QDateTime::currentDateTime().toString()); - debugger->setCoreFileName(coreFile, true); - debugger->startRunControl(); + emit attachToCoreRequested(coreFile); } else { QString msg = response.data["msg"].data(); AsynchronousMessageBox::critical(tr("Snapshot Creation Error"), @@ -3187,7 +3079,7 @@ void GdbEngine::handleMakeSnapshot(const DebuggerResponse &response, const QStri void GdbEngine::reloadRegisters() { - if (!Internal::isRegistersWindowVisible()) + if (!isRegistersWindowVisible()) return; if (state() != InferiorStopOk && state() != InferiorUnrunnable) @@ -3912,10 +3804,7 @@ void GdbEngine::reloadDebuggingHelpers() void GdbEngine::handleGdbError(QProcess::ProcessError error) { - QString program; - // avoid accessing invalid memory if the process crashed - if (runTool()) - program = runParameters().debugger.executable; + const QString program = runParameters().debugger.executable; QString msg = RunWorker::userMessageForProcessError(error, program); QString errorString = m_gdbProc.errorString(); if (!errorString.isEmpty()) @@ -4203,7 +4092,7 @@ void GdbEngine::setupInferior() if (rp.startMode != AttachCore) { showStatusMessage(tr("Setting breakpoints...")); showMessage(tr("Setting breakpoints...")); - attemptBreakpointSynchronization(); + BreakpointManager::claimBreakpointsForEngine(this); } if (rp.startMode == AttachToRemoteProcess) { @@ -4593,9 +4482,6 @@ void GdbEngine::handleExecRun(const DebuggerResponse &response) //showStatusMessage(tr("Running...")); showMessage("INFERIOR STARTED"); showMessage(msgInferiorSetupOk(), StatusBar); - // FIXME: That's the wrong place for it. - if (boolSetting(EnableReverseDebugging)) - runCommand({"target record"}); } else { QString msg = response.data["msg"].data(); //QTC_CHECK(status() == InferiorRunOk); diff --git a/src/plugins/debugger/gdb/gdbengine.h b/src/plugins/debugger/gdb/gdbengine.h index aa16c0c4017..dbd437ffd8a 100644 --- a/src/plugins/debugger/gdb/gdbengine.h +++ b/src/plugins/debugger/gdb/gdbengine.h @@ -47,7 +47,6 @@ namespace Debugger { namespace Internal { class BreakpointParameters; -class BreakpointResponse; class DebugInfoTask; class DebugInfoTaskHandler; class DebuggerResponse; @@ -65,7 +64,7 @@ struct CoreInfo const QString &coreFile); }; -class GdbEngine : public DebuggerEngine +class GdbEngine : public CppDebuggerEngine { Q_OBJECT @@ -74,8 +73,6 @@ public: ~GdbEngine() final; private: ////////// General Interface ////////// - DebuggerEngine *cppEngine() final { return this; } - void handleGdbStartFailed(); void prepareForRestart() final; @@ -86,7 +83,7 @@ private: ////////// General Interface ////////// void resetInferior() final; bool acceptsDebuggerCommands() const final; - void executeDebuggerCommand(const QString &command, DebuggerLanguages languages) final; + void executeDebuggerCommand(const QString &command) final; ////////// General State ////////// @@ -189,11 +186,11 @@ private: ////////// General Interface ////////// // This should be always the last call in a function. bool stateAcceptsBreakpointChanges() const final; - bool acceptsBreakpoint(Breakpoint bp) const final; - void insertBreakpoint(Breakpoint bp) final; - void removeBreakpoint(Breakpoint bp) final; - void changeBreakpoint(Breakpoint bp) final; - void enableSubBreakpoint(const QString &locId, bool on) final; + bool acceptsBreakpoint(const BreakpointParameters &bp) const final; + void insertBreakpoint(const Breakpoint &bp) final; + void removeBreakpoint(const Breakpoint &bp) final; + void updateBreakpoint(const Breakpoint &bp) final; + void enableSubBreakpoint(const SubBreakpoint &sbp, bool on) final; void executeStep() final; void executeStepOut() final; @@ -209,6 +206,7 @@ private: ////////// General Interface ////////// void executeRunToFunction(const QString &functionName) final; void executeJumpToLine(const ContextData &data) final; void executeReturn() final; + void executeRecordReverse(bool reverse); void handleExecuteContinue(const DebuggerResponse &response); void handleExecuteStep(const DebuggerResponse &response); @@ -223,26 +221,24 @@ private: ////////// General Interface ////////// void selectThread(ThreadId threadId) final; void activateFrame(int index) final; - void handleAutoContinueInferior(); // // Breakpoint specific stuff // void handleBreakModifications(const GdbMi &bkpts); - void handleBreakIgnore(const DebuggerResponse &response, Breakpoint bp); - void handleBreakDisable(const DebuggerResponse &response, Breakpoint bp); - void handleBreakEnable(const DebuggerResponse &response, Breakpoint bp); - void handleBreakInsert1(const DebuggerResponse &response, Breakpoint bp); - void handleBreakInsert2(const DebuggerResponse &response, Breakpoint bp); - void handleBreakCondition(const DebuggerResponse &response, Breakpoint bp); - void handleBreakThreadSpec(const DebuggerResponse &response, Breakpoint bp); - void handleBreakLineNumber(const DebuggerResponse &response, Breakpoint bp); - void handleInsertInterpreterBreakpoint(const DebuggerResponse &response, Breakpoint bp); + void handleBreakIgnore(const DebuggerResponse &response, const Breakpoint &bp); + void handleBreakDisable(const DebuggerResponse &response, const Breakpoint &bp); + void handleBreakEnable(const DebuggerResponse &response, const Breakpoint &bp); + void handleBreakInsert1(const DebuggerResponse &response, const Breakpoint &bp); + void handleBreakInsert2(const DebuggerResponse &response, const Breakpoint &bp); + void handleBreakCondition(const DebuggerResponse &response, const Breakpoint &bp); + void handleBreakThreadSpec(const DebuggerResponse &response, const Breakpoint &bp); + void handleBreakLineNumber(const DebuggerResponse &response, const Breakpoint &bp); + void handleInsertInterpreterBreakpoint(const DebuggerResponse &response, const Breakpoint &bp); void handleInterpreterBreakpointModified(const GdbMi &data); - void handleWatchInsert(const DebuggerResponse &response, Breakpoint bp); - void handleCatchInsert(const DebuggerResponse &response, Breakpoint bp); - void handleBkpt(const GdbMi &bkpt, Breakpoint bp); - void updateResponse(BreakpointResponse &response, const GdbMi &bkpt); + void handleWatchInsert(const DebuggerResponse &response, const Breakpoint &bp); + void handleCatchInsert(const DebuggerResponse &response, const Breakpoint &bp); + void handleBkpt(const GdbMi &bkpt, const Breakpoint &bp); QString breakpointLocation(const BreakpointParameters &data); // For gdb/MI. QString breakpointLocation2(const BreakpointParameters &data); // For gdb/CLI fallback. QString breakLocation(const QString &file) const; diff --git a/src/plugins/debugger/lldb/lldbengine.cpp b/src/plugins/debugger/lldb/lldbengine.cpp index 32f0a2cbf7b..952505e51fc 100644 --- a/src/plugins/debugger/lldb/lldbengine.cpp +++ b/src/plugins/debugger/lldb/lldbengine.cpp @@ -84,6 +84,7 @@ static int ¤tToken() LldbEngine::LldbEngine() { setObjectName("LldbEngine"); + setDebuggerName("LLDB"); connect(action(AutoDerefPointers), &SavedAction::valueChanged, this, &LldbEngine::updateLocals); @@ -114,7 +115,7 @@ LldbEngine::~LldbEngine() m_lldbProc.disconnect(); } -void LldbEngine::executeDebuggerCommand(const QString &command, DebuggerLanguages) +void LldbEngine::executeDebuggerCommand(const QString &command) { DebuggerCommand cmd("executeDebuggerCommand"); cmd.arg("command", command); @@ -175,13 +176,18 @@ void LldbEngine::shutdownInferior() void LldbEngine::shutdownEngine() { QTC_ASSERT(state() == EngineShutdownRequested, qDebug() << state()); - m_lldbProc.kill(); - notifyEngineShutdownFinished(); + if (m_lldbProc.state() == QProcess::Running) + m_lldbProc.terminate(); + else + notifyEngineShutdownFinished(); } void LldbEngine::abortDebuggerProcess() { - m_lldbProc.kill(); + if (m_lldbProc.state() == QProcess::Running) + m_lldbProc.kill(); + else + notifyEngineShutdownFinished(); } void LldbEngine::setupEngine() @@ -290,17 +296,9 @@ void LldbEngine::setupEngine() } cmd2.callback = [this](const DebuggerResponse &response) { - bool success = response.data["success"].toInt(); + const bool success = response.data["success"].toInt(); if (success) { - for (Breakpoint bp : breakHandler()->unclaimedBreakpoints()) { - if (acceptsBreakpoint(bp)) { - bp.setEngine(this); - insertBreakpoint(bp); - } else { - showMessage(QString("BREAKPOINT %1 IN STATE %2 IS NOT ACCEPTABLE") - .arg(bp.id().toString()).arg(bp.state())); - } - } + BreakpointManager::claimBreakpointsForEngine(this); } else { notifyEngineSetupFailed(); } @@ -442,7 +440,7 @@ void LldbEngine::activateFrame(int frameIndex) QTC_ASSERT(frameIndex < handler->stackSize(), return); handler->setCurrentIndex(frameIndex); - gotoLocation(handler->currentFrame()); + gotoCurrentLocation(); DebuggerCommand cmd("activateFrame"); cmd.arg("index", frameIndex); @@ -477,104 +475,103 @@ bool LldbEngine::stateAcceptsBreakpointChanges() const } } -bool LldbEngine::acceptsBreakpoint(Breakpoint bp) const +bool LldbEngine::acceptsBreakpoint(const BreakpointParameters &bp) const { if (runParameters().startMode == AttachCore) return false; - if (bp.parameters().isCppBreakpoint()) + if (bp.isCppBreakpoint()) return true; return isNativeMixedEnabled(); } -void LldbEngine::insertBreakpoint(Breakpoint bp) +void LldbEngine::insertBreakpoint(const Breakpoint &bp) { + QTC_ASSERT(bp, return); DebuggerCommand cmd("insertBreakpoint"); cmd.callback = [this, bp](const DebuggerResponse &response) { - QTC_CHECK(bp.state() == BreakpointInsertProceeding); + QTC_CHECK(bp && bp->state() == BreakpointInsertionProceeding); updateBreakpointData(bp, response.data, true); }; - bp.addToCommand(&cmd); - bp.notifyBreakpointInsertProceeding(); + bp->addToCommand(&cmd); + notifyBreakpointInsertProceeding(bp); runCommand(cmd); } -void LldbEngine::changeBreakpoint(Breakpoint bp) +void LldbEngine::updateBreakpoint(const Breakpoint &bp) { - const BreakpointResponse &response = bp.response(); + QTC_ASSERT(bp, return); DebuggerCommand cmd("changeBreakpoint"); - cmd.arg("lldbid", response.id.toString()); + cmd.arg("lldbid", bp->responseId()); cmd.callback = [this, bp](const DebuggerResponse &response) { - QTC_CHECK(!bp.isValid() || bp.state() == BreakpointChangeProceeding); + QTC_CHECK(bp && bp->state() == BreakpointUpdateProceeding); updateBreakpointData(bp, response.data, false); }; - bp.addToCommand(&cmd); - bp.notifyBreakpointChangeProceeding(); + bp->addToCommand(&cmd); + notifyBreakpointChangeProceeding(bp); runCommand(cmd); } -void LldbEngine::removeBreakpoint(Breakpoint bp) +void LldbEngine::removeBreakpoint(const Breakpoint &bp) { - const BreakpointResponse &response = bp.response(); - if (response.id.isValid()) { + QTC_ASSERT(bp, return); + if (!bp->responseId().isEmpty()) { DebuggerCommand cmd("removeBreakpoint"); - cmd.arg("lldbid", response.id.toString()); - cmd.callback = [bp](const DebuggerResponse &) { - QTC_CHECK(bp.state() == BreakpointRemoveProceeding); - Breakpoint bp0 = bp; - bp0.notifyBreakpointRemoveOk(); - }; - bp.notifyBreakpointRemoveProceeding(); + cmd.arg("lldbid", bp->responseId()); + notifyBreakpointRemoveProceeding(bp); runCommand(cmd); + + // Pretend it succeeds without waiting for response. Feels better. + // Otherwise, clicking in the gutter leaves the breakpoint visible + // for quite some time, so the user assumes a mis-click and clicks + // again, effectivly re-introducing the breakpoint. + notifyBreakpointRemoveOk(bp); } } -void LldbEngine::updateBreakpointData(Breakpoint bp, const GdbMi &bkpt, bool added) +void LldbEngine::updateBreakpointData(const Breakpoint &bp, const GdbMi &bkpt, bool added) { - BreakHandler *handler = breakHandler(); - BreakpointResponseId rid = BreakpointResponseId(bkpt["lldbid"].data()); - if (!bp.isValid()) - bp = handler->findBreakpointByResponseId(rid); - BreakpointResponse response = bp.response(); + QTC_ASSERT(bp, return); + QString rid = bkpt["lldbid"].data(); + QTC_ASSERT(bp, return); if (added) - response.id = rid; - QTC_CHECK(response.id == rid); - response.address = 0; - response.enabled = bkpt["enabled"].toInt(); - response.ignoreCount = bkpt["ignorecount"].toInt(); - response.condition = fromHex(bkpt["condition"].data()); - response.hitCount = bkpt["hitcount"].toInt(); - response.fileName = bkpt["file"].data(); - response.lineNumber = bkpt["line"].toInt(); + bp->setResponseId(rid); + QTC_CHECK(bp->responseId() == rid); + bp->setAddress(0); + bp->setEnabled(bkpt["enabled"].toInt()); + bp->setIgnoreCount(bkpt["ignorecount"].toInt()); + bp->setCondition(fromHex(bkpt["condition"].data())); + bp->setHitCount(bkpt["hitcount"].toInt()); + bp->setFileName(bkpt["file"].data()); + bp->setLineNumber(bkpt["line"].toInt()); GdbMi locations = bkpt["locations"]; const int numChild = int(locations.children().size()); if (numChild > 1) { for (const GdbMi &location : locations.children()) { - const int locid = location["locid"].toInt(); - BreakpointResponse sub; - sub.id = BreakpointResponseId(rid.majorPart(), locid); - sub.type = response.type; - sub.address = location["addr"].toAddress(); - sub.functionName = location["func"].data(); - sub.fileName = location["file"].data(); - sub.lineNumber = location["line"].toInt(); - bp.insertSubBreakpoint(sub); + const QString locid = QString("%1.%2").arg(rid).arg(location["locid"].data()); + SubBreakpoint loc = bp->findOrCreateSubBreakpoint(locid); + QTC_ASSERT(loc, continue); + loc->params.type = bp->type(); + loc->params.address = location["addr"].toAddress(); + loc->params.functionName = location["function"].data(); + loc->params.fileName = location["file"].data(); + loc->params.lineNumber = location["line"].toInt(); } - response.pending = false; + bp->setPending(false); } else if (numChild == 1) { const GdbMi location = locations.childAt(0); - response.address = location["addr"].toAddress(); - response.functionName = location["func"].data(); - response.pending = false; + bp->setAddress(location["addr"].toAddress()); + bp->setFunctionName(location["function"].data()); + bp->setPending(false); } else { // This can happen for pending breakpoints. - showMessage(QString("NO LOCATIONS (YET) FOR BP %1").arg(response.toString())); + showMessage(QString("NO LOCATIONS (YET) FOR BP %1").arg(bp->parameters().toString())); } - bp.setResponse(response); + bp->adjustMarker(); if (added) - bp.notifyBreakpointInsertOk(); + notifyBreakpointInsertOk(bp); else - bp.notifyBreakpointChangeOk(); + notifyBreakpointChangeOk(bp); } void LldbEngine::handleOutputNotification(const GdbMi &output) @@ -896,7 +893,7 @@ void LldbEngine::handleLocationNotification(const GdbMi &reportedLocation) void LldbEngine::reloadRegisters() { - if (!Internal::isRegistersWindowVisible()) + if (!isRegistersWindowVisible()) return; if (state() != InferiorStopOk && state() != InferiorUnrunnable) @@ -1012,7 +1009,8 @@ void LldbEngine::setRegisterValue(const QString &name, const QString &value) bool LldbEngine::hasCapability(unsigned cap) const { - if (cap & (ReverseSteppingCapability + if (cap & (0 + //| ReverseSteppingCapability | AutoDerefPointersCapability | DisassemblerCapability | RegisterCapability diff --git a/src/plugins/debugger/lldb/lldbengine.h b/src/plugins/debugger/lldb/lldbengine.h index 55bd6b603fe..a6487de3bc0 100644 --- a/src/plugins/debugger/lldb/lldbengine.h +++ b/src/plugins/debugger/lldb/lldbengine.h @@ -41,17 +41,14 @@ #include #include - namespace Debugger { namespace Internal { -class GdbMi; - /* A debugger engine interfacing the LLDB debugger * using its Python interface. */ -class LldbEngine : public DebuggerEngine +class LldbEngine : public CppDebuggerEngine { Q_OBJECT @@ -63,8 +60,6 @@ signals: void outputReady(const QString &data); private: - DebuggerEngine *cppEngine() override { return this; } - void executeStep() override; void executeStepOut() override; void executeNext() override; @@ -92,13 +87,13 @@ private: // This should be always the last call in a function. bool stateAcceptsBreakpointChanges() const override; - bool acceptsBreakpoint(Breakpoint bp) const override; - void insertBreakpoint(Breakpoint bp) override; - void removeBreakpoint(Breakpoint bp) override; - void changeBreakpoint(Breakpoint bp) override; + bool acceptsBreakpoint(const BreakpointParameters &bp) const override; + void insertBreakpoint(const Breakpoint &bp) override; + void removeBreakpoint(const Breakpoint &bp) override; + void updateBreakpoint(const Breakpoint &bp) override; void assignValueInDebugger(WatchItem *item, const QString &expr, const QVariant &value) override; - void executeDebuggerCommand(const QString &command, DebuggerLanguages languages) override; + void executeDebuggerCommand(const QString &command) override; void loadSymbols(const QString &moduleName) override; void loadAllSymbols() override; @@ -130,7 +125,7 @@ private: void handleResponse(const QString &data); void updateAll() override; void doUpdateLocals(const UpdateParameters ¶ms) override; - void updateBreakpointData(Breakpoint bp, const GdbMi &bkpt, bool added); + void updateBreakpointData(const Breakpoint &bp, const GdbMi &bkpt, bool added); void fetchStack(int limit); void runCommand(const DebuggerCommand &cmd) override; diff --git a/src/plugins/debugger/logwindow.cpp b/src/plugins/debugger/logwindow.cpp index 9b309bade88..dca4aae5381 100644 --- a/src/plugins/debugger/logwindow.cpp +++ b/src/plugins/debugger/logwindow.cpp @@ -58,6 +58,52 @@ namespace Debugger { namespace Internal { +GlobalLogWindow *theGlobalLog = nullptr; + +static LogChannel channelForChar(QChar c) +{ + switch (c.unicode()) { + case 'd': return LogDebug; + case 'w': return LogWarning; + case 'e': return LogError; + case '<': return LogInput; + case '>': return LogOutput; + case 's': return LogStatus; + case 't': return LogTime; + default: return LogMisc; + } +} + +QChar static charForChannel(int channel) +{ + switch (channel) { + case LogDebug: return QLatin1Char('d'); + case LogWarning: return QLatin1Char('w'); + case LogError: return QLatin1Char('e'); + case LogInput: return QLatin1Char('<'); + case LogOutput: return QLatin1Char('>'); + case LogStatus: return QLatin1Char('s'); + case LogTime: return QLatin1Char('t'); + case LogMisc: + default: return QLatin1Char(' '); + } +} + +static bool writeLogContents(const QPlainTextEdit *editor, QWidget *parent) +{ + bool success = false; + while (!success) { + const QString fileName = QFileDialog::getSaveFileName(parent, LogWindow::tr("Log File")); + if (fileName.isEmpty()) + break; + Utils::FileSaver saver(fileName, QIODevice::Text); + saver.write(editor->toPlainText().toUtf8()); + if (saver.finalize(parent)) + success = true; + } + return success; +} + ///////////////////////////////////////////////////////////////////// // // OutputHighlighter @@ -77,7 +123,7 @@ private: using Utils::Theme; QTextCharFormat format; Theme *theme = Utils::creatorTheme(); - switch (LogWindow::channelForChar(text.isEmpty() ? QChar() : text.at(0))) { + switch (channelForChar(text.isEmpty() ? QChar() : text.at(0))) { case LogInput: format.setForeground(theme->color(Theme::Debugger_LogWindow_LogInput)); setFormat(1, text.size(), format); @@ -149,15 +195,14 @@ class DebuggerPane : public QPlainTextEdit Q_OBJECT public: - DebuggerPane(LogWindow *parent) - : QPlainTextEdit(parent) + explicit DebuggerPane() { setFrameStyle(QFrame::NoFrame); + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + m_clearContentsAction = new QAction(this); m_clearContentsAction->setText(tr("Clear Contents")); m_clearContentsAction->setEnabled(true); - connect(m_clearContentsAction, &QAction::triggered, - parent, &LogWindow::clearContents); m_saveContentsAction = new QAction(this); m_saveContentsAction->setText(tr("Save Contents")); @@ -168,8 +213,6 @@ public: m_reloadDebuggingHelpersAction = new QAction(this); m_reloadDebuggingHelpersAction->setText(tr("Reload Debugging Helpers")); m_reloadDebuggingHelpersAction->setEnabled(true); - connect(m_reloadDebuggingHelpersAction, &QAction::triggered, - this, &DebuggerPane::reloadDebuggingHelpers); } void contextMenuEvent(QContextMenuEvent *ev) override @@ -212,24 +255,18 @@ public: setUndoRedoEnabled(true); } + QAction *clearContentsAction() const { return m_clearContentsAction; } + QAction *reloadDebuggingHelpersAction() const { return m_reloadDebuggingHelpersAction; } + private: - void saveContents(); - void reloadDebuggingHelpers(); + void saveContents() { writeLogContents(this, this); } QAction *m_clearContentsAction; QAction *m_saveContentsAction; QAction *m_reloadDebuggingHelpersAction; }; -void DebuggerPane::saveContents() -{ - LogWindow::writeLogContents(this, this); -} -void DebuggerPane::reloadDebuggingHelpers() -{ - currentEngine()->reloadDebuggingHelpers(); -} ///////////////////////////////////////////////////////////////////// // @@ -241,9 +278,12 @@ class InputPane : public DebuggerPane { Q_OBJECT public: - InputPane(LogWindow *parent) - : DebuggerPane(parent) + InputPane(LogWindow *logWindow) { + connect(clearContentsAction(), &QAction::triggered, + logWindow, &LogWindow::clearContents); + connect(reloadDebuggingHelpersAction(), &QAction::triggered, + logWindow->engine(), &DebuggerEngine::reloadDebuggingHelpers); (void) new InputHighlighter(this); } @@ -307,10 +347,13 @@ class CombinedPane : public DebuggerPane { Q_OBJECT public: - CombinedPane(LogWindow *parent) - : DebuggerPane(parent) + CombinedPane(LogWindow *logWindow) { (void) new OutputHighlighter(this); + connect(clearContentsAction(), &QAction::triggered, + logWindow, &LogWindow::clearContents); + connect(reloadDebuggingHelpersAction(), &QAction::triggered, + logWindow->engine(), &DebuggerEngine::reloadDebuggingHelpers); } void gotoResult(int i) @@ -347,8 +390,8 @@ public: // ///////////////////////////////////////////////////////////////////// -LogWindow::LogWindow(QWidget *parent) - : QWidget(parent) +LogWindow::LogWindow(DebuggerEngine *engine) + : m_engine(engine) { setWindowTitle(tr("Debugger &Log")); setObjectName(QLatin1String("Log")); @@ -362,14 +405,10 @@ LogWindow::LogWindow(QWidget *parent) m_combinedText = new CombinedPane(this); m_combinedText->setReadOnly(true); m_combinedText->setReadOnly(false); - m_combinedText->setSizePolicy(QSizePolicy::MinimumExpanding, - QSizePolicy::MinimumExpanding); // Input only. m_inputText = new InputPane(this); m_inputText->setReadOnly(false); - m_inputText->setSizePolicy(QSizePolicy::MinimumExpanding, - QSizePolicy::MinimumExpanding); m_commandEdit = new Utils::FancyLineEdit(this); m_commandEdit->setFrame(false); @@ -445,23 +484,33 @@ LogWindow::LogWindow(QWidget *parent) .arg(Core::Constants::IDE_DISPLAY_NAME)); } +LogWindow::~LogWindow() +{ + disconnect(&m_outputTimer, &QTimer::timeout, this, &LogWindow::doOutput); + m_outputTimer.stop(); + doOutput(); +} + void LogWindow::executeLine() { m_ignoreNextInputEcho = true; - currentEngine()-> - executeDebuggerCommand(m_inputText->textCursor().block().text(), CppLanguage); + m_engine->executeDebuggerCommand(m_inputText->textCursor().block().text()); } void LogWindow::repeatLastCommand() { - currentEngine()->debugLastCommand(); + m_engine->debugLastCommand(); +} + +DebuggerEngine *LogWindow::engine() const +{ + return m_engine; } void LogWindow::sendCommand() { - DebuggerEngine *engine = currentEngine(); - if (engine->acceptsDebuggerCommands()) - engine->executeDebuggerCommand(m_commandEdit->text(), CppLanguage); + if (m_engine->acceptsDebuggerCommands()) + m_engine->executeDebuggerCommand(m_commandEdit->text()); else showOutput(LogError, tr("User commands are not accepted in the current state.")); } @@ -512,6 +561,8 @@ void LogWindow::doOutput() if (m_queuedOutput.isEmpty()) return; + theGlobalLog->doOutput(m_queuedOutput); + QTextCursor cursor = m_combinedText->textCursor(); const bool atEnd = cursor.atEnd(); @@ -543,6 +594,8 @@ void LogWindow::showInput(int channel, const QString &input) cursor.movePosition(QTextCursor::End); m_inputText->setTextCursor(cursor); m_inputText->ensureCursorVisible(); + + theGlobalLog->doInput(input); } void LogWindow::clearContents() @@ -597,48 +650,100 @@ QString LogWindow::logTimeStamp() return lastTimeStamp; } -bool LogWindow::writeLogContents(const QPlainTextEdit *editor, QWidget *parent) +///////////////////////////////////////////////////////////////////// +// +// GlobalLogWindow +// +///////////////////////////////////////////////////////////////////// + +GlobalLogWindow::GlobalLogWindow() { - bool success = false; - while (!success) { - const QString fileName = QFileDialog::getSaveFileName(parent, tr("Log File")); - if (fileName.isEmpty()) - break; - Utils::FileSaver saver(fileName, QIODevice::Text); - saver.write(editor->toPlainText().toUtf8()); - if (saver.finalize(parent)) - success = true; - } - return success; + theGlobalLog = this; + + setWindowTitle(tr("Global Debugger &Log")); + setObjectName("GlobalLog"); + + auto m_splitter = new Core::MiniSplitter(Qt::Horizontal); + m_splitter->setParent(this); + + m_rightPane = new DebuggerPane; + m_rightPane->setReadOnly(true); + + m_leftPane = new DebuggerPane; + m_leftPane->setReadOnly(true); + + m_splitter->addWidget(m_leftPane); + m_splitter->addWidget(m_rightPane); + m_splitter->setStretchFactor(0, 1); + m_splitter->setStretchFactor(1, 3); + + auto layout = new QVBoxLayout(this); + layout->setMargin(0); + layout->setSpacing(0); + layout->addWidget(m_splitter); + layout->addWidget(new Core::FindToolBarPlaceHolder(this)); + setLayout(layout); + + auto aggregate = new Aggregation::Aggregate; + aggregate->add(m_rightPane); + aggregate->add(new Core::BaseTextFind(m_rightPane)); + + aggregate = new Aggregation::Aggregate; + aggregate->add(m_leftPane); + aggregate->add(new Core::BaseTextFind(m_leftPane)); + + connect(m_leftPane->clearContentsAction(), &QAction::triggered, + this, &GlobalLogWindow::clearContents); + connect(m_rightPane->clearContentsAction(), &QAction::triggered, + this, &GlobalLogWindow::clearContents); } -QChar LogWindow::charForChannel(int channel) +GlobalLogWindow::~GlobalLogWindow() { - switch (channel) { - case LogDebug: return QLatin1Char('d'); - case LogWarning: return QLatin1Char('w'); - case LogError: return QLatin1Char('e'); - case LogInput: return QLatin1Char('<'); - case LogOutput: return QLatin1Char('>'); - case LogStatus: return QLatin1Char('s'); - case LogTime: return QLatin1Char('t'); - case LogMisc: - default: return QLatin1Char(' '); +} + +void GlobalLogWindow::doOutput(const QString &output) +{ + QTextCursor cursor = m_rightPane->textCursor(); + const bool atEnd = cursor.atEnd(); + + m_rightPane->append(output); + + if (atEnd) { + cursor.movePosition(QTextCursor::End); + m_rightPane->setTextCursor(cursor); + m_rightPane->ensureCursorVisible(); } } -LogChannel LogWindow::channelForChar(QChar c) +void GlobalLogWindow::doInput(const QString &input) { - switch (c.unicode()) { - case 'd': return LogDebug; - case 'w': return LogWarning; - case 'e': return LogError; - case '<': return LogInput; - case '>': return LogOutput; - case 's': return LogStatus; - case 't': return LogTime; - default: return LogMisc; - } + if (boolSetting(LogTimeStamps)) + m_leftPane->append(LogWindow::logTimeStamp()); + m_leftPane->append(input); + QTextCursor cursor = m_leftPane->textCursor(); + cursor.movePosition(QTextCursor::End); + m_leftPane->setTextCursor(cursor); + m_leftPane->ensureCursorVisible(); +} + +void GlobalLogWindow::clearContents() +{ + m_rightPane->clear(); + m_leftPane->clear(); +} + +void GlobalLogWindow::setCursor(const QCursor &cursor) +{ + m_rightPane->viewport()->setCursor(cursor); + m_leftPane->viewport()->setCursor(cursor); + QWidget::setCursor(cursor); +} + +void GlobalLogWindow::clearUndoRedoStacks() +{ + m_leftPane->clearUndoRedoStacks(); + m_rightPane->clearUndoRedoStacks(); } } // namespace Internal diff --git a/src/plugins/debugger/logwindow.h b/src/plugins/debugger/logwindow.h index f5024d2014f..b800837d09e 100644 --- a/src/plugins/debugger/logwindow.h +++ b/src/plugins/debugger/logwindow.h @@ -40,6 +40,8 @@ namespace Utils { class FancyLineEdit; } namespace Debugger { namespace Internal { +class DebuggerEngine; +class DebuggerPane; class CombinedPane; class InputPane; @@ -48,7 +50,10 @@ class LogWindow : public QWidget Q_OBJECT public: - explicit LogWindow(QWidget *parent = nullptr); + explicit LogWindow(DebuggerEngine *engine); + ~LogWindow() final; + + DebuggerEngine *engine() const; void setCursor(const QCursor &cursor); @@ -59,11 +64,6 @@ public: static QString logTimeStamp(); - static bool writeLogContents(const QPlainTextEdit *editor, QWidget *parent = nullptr); - - static QChar charForChannel(int channel); - static LogChannel channelForChar(QChar c); - void clearContents(); void sendCommand(); void executeLine(); @@ -73,7 +73,6 @@ public: void repeatLastCommand(); signals: - void showPage(); void statusMessageRequested(const QString &msg, int); private: @@ -83,6 +82,27 @@ private: QString m_queuedOutput; Utils::FancyLineEdit *m_commandEdit; bool m_ignoreNextInputEcho; + DebuggerEngine *m_engine; +}; + +class GlobalLogWindow : public QWidget +{ + Q_OBJECT + +public: + explicit GlobalLogWindow(); + ~GlobalLogWindow() final; + + void setCursor(const QCursor &cursor); + + void clearUndoRedoStacks(); + void clearContents(); + void doInput(const QString &input); + void doOutput(const QString &output); + +private: + DebuggerPane *m_rightPane; // everything + DebuggerPane *m_leftPane; // combined input }; } // namespace Internal diff --git a/src/plugins/debugger/pdb/pdbengine.cpp b/src/plugins/debugger/pdb/pdbengine.cpp index 1ff061bf48b..981951894d6 100644 --- a/src/plugins/debugger/pdb/pdbengine.cpp +++ b/src/plugins/debugger/pdb/pdbengine.cpp @@ -65,12 +65,11 @@ namespace Internal { PdbEngine::PdbEngine() { setObjectName("PdbEngine"); + setDebuggerName("PDB"); } -void PdbEngine::executeDebuggerCommand(const QString &command, DebuggerLanguages languages) +void PdbEngine::executeDebuggerCommand(const QString &command) { - if (!(languages & CppLanguage)) - return; QTC_ASSERT(state() == InferiorStopOk, qDebug() << state()); if (state() == DebuggerNotReady) { showMessage("PDB PROCESS NOT RUNNING, PLAIN CMD IGNORED: " + command); @@ -157,7 +156,7 @@ void PdbEngine::runEngine() { QTC_ASSERT(state() == EngineRunRequested, qDebug() << state()); showStatusMessage(tr("Running requested..."), 5000); - attemptBreakpointSynchronization(); + BreakpointManager::claimBreakpointsForEngine(this); notifyEngineRunAndInferiorStopOk(); updateAll(); } @@ -246,35 +245,44 @@ void PdbEngine::selectThread(ThreadId threadId) Q_UNUSED(threadId) } -bool PdbEngine::acceptsBreakpoint(Breakpoint bp) const +bool PdbEngine::acceptsBreakpoint(const BreakpointParameters &bp) const { - const QString fileName = bp.fileName(); + const QString fileName = bp.fileName; return fileName.endsWith(".py"); } -void PdbEngine::insertBreakpoint(Breakpoint bp) +void PdbEngine::insertBreakpoint(const Breakpoint &bp) { - QTC_CHECK(bp.state() == BreakpointInsertRequested); - bp.notifyBreakpointInsertProceeding(); + QTC_ASSERT(bp, return); + QTC_CHECK(bp->state() == BreakpointInsertionRequested); + notifyBreakpointInsertProceeding(bp); QString loc; - if (bp.type() == BreakpointByFunction) - loc = bp.functionName(); + const BreakpointParameters ¶ms = bp->requestedParameters(); + if (params.type == BreakpointByFunction) + loc = params.functionName; else - loc = bp.fileName() + ':' + QString::number(bp.lineNumber()); + loc = params.fileName + ':' + QString::number(params.lineNumber); postDirectCommand("break " + loc); } -void PdbEngine::removeBreakpoint(Breakpoint bp) +void PdbEngine::updateBreakpoint(const Breakpoint &bp) { - QTC_CHECK(bp.state() == BreakpointRemoveRequested); - bp.notifyBreakpointRemoveProceeding(); - BreakpointResponse br = bp.response(); - showMessage(QString("DELETING BP %1 IN %2").arg(br.id.toString()).arg(bp.fileName())); - postDirectCommand("clear " + br.id.toString()); + Q_UNUSED(bp); + QTC_CHECK(false); +} + +void PdbEngine::removeBreakpoint(const Breakpoint &bp) +{ + QTC_ASSERT(bp, return); + QTC_CHECK(bp->state() == BreakpointRemoveRequested); + notifyBreakpointRemoveProceeding(bp); + showMessage(QString("DELETING BP %1 IN %2") + .arg(bp->responseId()).arg(bp->fileName())); + postDirectCommand("clear " + bp->responseId()); // Pretend it succeeds without waiting for response. - bp.notifyBreakpointRemoveOk(); + notifyBreakpointRemoveOk(bp); } void PdbEngine::loadSymbols(const QString &moduleName) @@ -459,8 +467,8 @@ void PdbEngine::handleOutput(const QString &data) void PdbEngine::handleOutput2(const QString &data) { - foreach (QString line, data.split('\n')) { - + const QStringList lines = data.split('\n'); + for (const QString &line : lines) { GdbMi item; item.fromString(line); @@ -479,21 +487,25 @@ void PdbEngine::handleOutput2(const QString &data) } else if (line.startsWith("state=")) { refreshState(item); } else if (line.startsWith("Breakpoint")) { - int pos1 = line.indexOf(" at "); + const int pos1 = line.indexOf(" at "); QTC_ASSERT(pos1 != -1, continue); - QString bpnr = line.mid(11, pos1 - 11); - int pos2 = line.lastIndexOf(':'); + const QString bpnr = line.mid(11, pos1 - 11); + const int pos2 = line.lastIndexOf(':'); QTC_ASSERT(pos2 != -1, continue); - BreakpointResponse br; - br.id = BreakpointResponseId(bpnr); - br.fileName = line.mid(pos1 + 4, pos2 - pos1 - 4); - br.lineNumber = line.mid(pos2 + 1).toInt(); - Breakpoint bp = breakHandler()->findBreakpointByFileAndLine(br.fileName, br.lineNumber, false); - if (bp.isValid()) { - bp.setResponse(br); - QTC_CHECK(!bp.needsChange()); - bp.notifyBreakpointInsertOk(); - } + const QString fileName = line.mid(pos1 + 4, pos2 - pos1 - 4); + const int lineNumber = line.mid(pos2 + 1).toInt(); + const Breakpoint bp = Utils::findOrDefault(breakHandler()->breakpoints(), [&](const Breakpoint &bp) { + return bp->parameters().isLocatedAt(fileName, lineNumber, bp->markerFileName()) + || bp->requestedParameters().isLocatedAt(fileName, lineNumber, bp->markerFileName()); + }); + QTC_ASSERT(bp, continue); + bp->setResponseId(bpnr); + bp->setFileName(fileName); + bp->setLineNumber(lineNumber); + bp->adjustMarker(); + bp->setPending(false); + QTC_CHECK(!bp->needsChange()); + notifyBreakpointInsertOk(bp); } } } diff --git a/src/plugins/debugger/pdb/pdbengine.h b/src/plugins/debugger/pdb/pdbengine.h index ccef9d1e479..09966f229ed 100644 --- a/src/plugins/debugger/pdb/pdbengine.h +++ b/src/plugins/debugger/pdb/pdbengine.h @@ -71,13 +71,14 @@ private: void activateFrame(int index) override; void selectThread(ThreadId threadId) override; - bool acceptsBreakpoint(Breakpoint bp) const override; - void insertBreakpoint(Breakpoint bp) override; - void removeBreakpoint(Breakpoint bp) override; + bool acceptsBreakpoint(const BreakpointParameters &bp) const override; + void insertBreakpoint(const Breakpoint &bp) override; + void updateBreakpoint(const Breakpoint &bp) override; + void removeBreakpoint(const Breakpoint &bp) override; void assignValueInDebugger(WatchItem *item, const QString &expr, const QVariant &value) override; - void executeDebuggerCommand(const QString &command, DebuggerLanguages languages) override; + void executeDebuggerCommand(const QString &command) override; void loadSymbols(const QString &moduleName) override; void loadAllSymbols() override; diff --git a/src/plugins/debugger/qml/qml.pri b/src/plugins/debugger/qml/qml.pri index 7a76d64e6ce..d8d2edf36ff 100644 --- a/src/plugins/debugger/qml/qml.pri +++ b/src/plugins/debugger/qml/qml.pri @@ -1,7 +1,6 @@ HEADERS += \ $$PWD/qmlengine.h \ $$PWD/qmlengineutils.h \ - $$PWD/qmlcppengine.h \ $$PWD/interactiveinterpreter.h \ $$PWD/qmlv8debuggerclientconstants.h \ $$PWD/qmlinspectoragent.h @@ -9,6 +8,5 @@ HEADERS += \ SOURCES += \ $$PWD/qmlengine.cpp \ $$PWD/qmlengineutils.cpp \ - $$PWD/qmlcppengine.cpp \ $$PWD/interactiveinterpreter.cpp \ $$PWD/qmlinspectoragent.cpp diff --git a/src/plugins/debugger/qml/qmlengine.cpp b/src/plugins/debugger/qml/qmlengine.cpp index 62034f245f8..8f8816ff449 100644 --- a/src/plugins/debugger/qml/qmlengine.cpp +++ b/src/plugins/debugger/qml/qmlengine.cpp @@ -169,10 +169,10 @@ public: void setBreakpoint(const QString type, const QString target, bool enabled = true,int line = 0, int column = 0, const QString condition = QString(), int ignoreCount = -1); - void clearBreakpoint(int breakpoint); + void clearBreakpoint(const Breakpoint &bp); bool canChangeBreakpoint() const; - void changeBreakpoint(int breakpoint, bool enabled); + void changeBreakpoint(const Breakpoint &bp, bool enabled); void setExceptionBreak(Exceptions type, bool enabled = false); @@ -204,9 +204,8 @@ public: QHash refVals; // The mapping of target object handles to retrieved values. int sequence = -1; QmlEngine *engine; - QHash breakpoints; - QHash breakpointsSync; - QList breakpointsTemp; + QHash breakpointsSync; + QList breakpointsTemp; LookupItems currentlyLookingUp; // Id -> inames @@ -260,14 +259,14 @@ QmlEngine::QmlEngine() : d(new QmlEnginePrivate(this, new QmlDebugConnection(this))) { setObjectName("QmlEngine"); + setDebuggerName(tr("QML Debugger")); + QmlDebugConnection *connection = d->connection(); connect(stackHandler(), &StackHandler::stackChanged, this, &QmlEngine::updateCurrentContext); connect(stackHandler(), &StackHandler::currentIndexChanged, this, &QmlEngine::updateCurrentContext); - connect(inspectorView(), &WatchTreeView::currentIndexChanged, - this, &QmlEngine::updateCurrentContext); connect(&d->applicationLauncher, &ApplicationLauncher::processExited, this, &QmlEngine::disconnected); @@ -278,7 +277,7 @@ QmlEngine::QmlEngine() debuggerConsole()->populateFileFinder(); debuggerConsole()->setScriptEvaluator([this](const QString &expr) { - executeDebuggerCommand(expr, QmlLanguage); + executeDebuggerCommand(expr); }); d->connectionTimer.setInterval(4000); @@ -336,7 +335,7 @@ void QmlEngine::handleLauncherStarted() { // FIXME: The QmlEngine never calls notifyInferiorPid() triggering the // raising, so do it here manually for now. - runTool()->runControl()->applicationProcessHandle().activate(); + ProcessHandle(inferiorPid()).activate(); tryToConnect(); } @@ -347,7 +346,10 @@ void QmlEngine::appMessage(const QString &msg, Utils::OutputFormat /* format */) void QmlEngine::connectionEstablished() { - attemptBreakpointSynchronization(); + connect(inspectorView(), &WatchTreeView::currentIndexChanged, + this, &QmlEngine::updateCurrentContext); + + BreakpointManager::claimBreakpointsForEngine(this); if (state() == EngineRunRequested) notifyEngineRunAndInferiorRunOk(); @@ -435,7 +437,7 @@ void QmlEngine::appStartupFailed(const QString &errorMessage) { QString error = tr("Could not connect to the in-process QML debugger. %1").arg(errorMessage); - if (isMasterEngine()) { + if (companionEngine()) { auto infoBox = new QMessageBox(ICore::mainWindow()); infoBox->setIcon(QMessageBox::Critical); infoBox->setWindowTitle(Core::Constants::IDE_DISPLAY_NAME); @@ -522,7 +524,8 @@ void QmlEngine::runEngine() QTC_ASSERT(state() == EngineRunRequested, qDebug() << state()); - if (!isSlaveEngine()) { + if (isPrimaryEngine()) { + // QML only. if (runParameters().startMode == AttachToRemoteServer) tryToConnect(); else if (runParameters().startMode == AttachToRemoteProcess) @@ -538,10 +541,9 @@ void QmlEngine::startApplicationLauncher() { if (!d->applicationLauncher.isRunning()) { const Runnable runnable = runParameters().inferior; - runTool()->appendMessage(tr("Starting %1 %2").arg( - QDir::toNativeSeparators(runnable.executable), - runnable.commandLineArguments), - Utils::NormalMessageFormat); + showMessage(tr("Starting %1 %2").arg(QDir::toNativeSeparators(runnable.executable), + runnable.commandLineArguments), + Utils::NormalMessageFormat); d->applicationLauncher.start(runnable); } } @@ -565,8 +567,7 @@ void QmlEngine::shutdownInferior() // } d->runCommand({DISCONNECT}); - if (isSlaveEngine()) - resetLocation(); + resetLocation(); stopApplicationLauncher(); closeConnection(); @@ -583,8 +584,7 @@ void QmlEngine::shutdownEngine() stopApplicationLauncher(); notifyEngineShutdownFinished(); - if (!isSlaveEngine()) - showMessage(QString(), StatusBar); + showMessage(QString(), StatusBar); } void QmlEngine::setupEngine() @@ -689,34 +689,31 @@ void QmlEngine::selectThread(ThreadId threadId) Q_UNUSED(threadId) } -void QmlEngine::insertBreakpoint(Breakpoint bp) +void QmlEngine::insertBreakpoint(const Breakpoint &bp) { - BreakpointState state = bp.state(); - QTC_ASSERT(state == BreakpointInsertRequested, qDebug() << bp << this << state); - bp.notifyBreakpointInsertProceeding(); + QTC_ASSERT(bp, return); + const BreakpointState state = bp->state(); + QTC_ASSERT(state == BreakpointInsertionRequested, qDebug() << bp << this << state); + notifyBreakpointInsertProceeding(bp); - const BreakpointParameters ¶ms = bp.parameters(); - if (params.type == BreakpointAtJavaScriptThrow) { - BreakpointResponse br = bp.response(); - br.pending = false; - bp.setResponse(br); - bp.notifyBreakpointInsertOk(); - d->setExceptionBreak(AllExceptions, params.enabled); + const BreakpointParameters &requested = bp->requestedParameters(); + if (requested.type == BreakpointAtJavaScriptThrow) { + bp->setPending(false); + notifyBreakpointInsertOk(bp); + d->setExceptionBreak(AllExceptions, requested.enabled); - } else if (params.type == BreakpointByFileAndLine) { - d->setBreakpoint(SCRIPTREGEXP, params.fileName, - params.enabled, params.lineNumber, 0, - params.condition, params.ignoreCount); + } else if (requested.type == BreakpointByFileAndLine) { + d->setBreakpoint(SCRIPTREGEXP, requested.fileName, + requested.enabled, requested.lineNumber, 0, + requested.condition, requested.ignoreCount); - } else if (params.type == BreakpointOnQmlSignalEmit) { - d->setBreakpoint(EVENT, params.functionName, params.enabled); - BreakpointResponse br = bp.response(); - br.pending = false; - bp.setResponse(br); - bp.notifyBreakpointInsertOk(); + } else if (requested.type == BreakpointOnQmlSignalEmit) { + d->setBreakpoint(EVENT, requested.functionName, requested.enabled); + bp->setPending(false); + notifyBreakpointInsertOk(bp); } - d->breakpointsSync.insert(d->sequence, bp.id()); + d->breakpointsSync.insert(d->sequence, bp); } void QmlEngine::resetLocation() @@ -725,114 +722,64 @@ void QmlEngine::resetLocation() d->currentlyLookingUp.clear(); } -void QmlEngine::removeBreakpoint(Breakpoint bp) +void QmlEngine::removeBreakpoint(const Breakpoint &bp) { - const BreakpointParameters ¶ms = bp.parameters(); + QTC_ASSERT(bp, return); + const BreakpointParameters ¶ms = bp->requestedParameters(); - BreakpointState state = bp.state(); + const BreakpointState state = bp->state(); QTC_ASSERT(state == BreakpointRemoveRequested, qDebug() << bp << this << state); - bp.notifyBreakpointRemoveProceeding(); - - int breakpoint = d->breakpoints.value(bp.id()); - d->breakpoints.remove(bp.id()); + notifyBreakpointRemoveProceeding(bp); if (params.type == BreakpointAtJavaScriptThrow) d->setExceptionBreak(AllExceptions); else if (params.type == BreakpointOnQmlSignalEmit) d->setBreakpoint(EVENT, params.functionName, false); else - d->clearBreakpoint(breakpoint); + d->clearBreakpoint(bp); - if (bp.state() == BreakpointRemoveProceeding) - bp.notifyBreakpointRemoveOk(); + if (bp->state() == BreakpointRemoveProceeding) + notifyBreakpointRemoveOk(bp); } -void QmlEngine::changeBreakpoint(Breakpoint bp) +void QmlEngine::updateBreakpoint(const Breakpoint &bp) { - BreakpointState state = bp.state(); - QTC_ASSERT(state == BreakpointChangeRequested, qDebug() << bp << this << state); - bp.notifyBreakpointChangeProceeding(); + QTC_ASSERT(bp, return); + const BreakpointState state = bp->state(); + QTC_ASSERT(state == BreakpointUpdateRequested, qDebug() << bp << this << state); + notifyBreakpointChangeProceeding(bp); - const BreakpointParameters ¶ms = bp.parameters(); + const BreakpointParameters &requested = bp->requestedParameters(); - BreakpointResponse br = bp.response(); - if (params.type == BreakpointAtJavaScriptThrow) { - d->setExceptionBreak(AllExceptions, params.enabled); - } else if (params.type == BreakpointOnQmlSignalEmit) { - d->setBreakpoint(EVENT, params.functionName, params.enabled); + if (requested.type == BreakpointAtJavaScriptThrow) { + d->setExceptionBreak(AllExceptions, requested.enabled); + bp->setEnabled(requested.enabled); + } else if (requested.type == BreakpointOnQmlSignalEmit) { + d->setBreakpoint(EVENT, requested.functionName, requested.enabled); + bp->setEnabled(requested.enabled); } else if (d->canChangeBreakpoint()) { - d->changeBreakpoint(d->breakpoints.value(bp.id()), params.enabled); + d->changeBreakpoint(bp, requested.enabled); } else { - d->clearBreakpoint(d->breakpoints.take(bp.id())); - d->setBreakpoint(SCRIPTREGEXP, params.fileName, - params.enabled, params.lineNumber, 0, - params.condition, params.ignoreCount); - d->breakpointsSync.insert(d->sequence, bp.id()); + d->clearBreakpoint(bp); + d->setBreakpoint(SCRIPTREGEXP, requested.fileName, + requested.enabled, requested.lineNumber, 0, + requested.condition, requested.ignoreCount); + d->breakpointsSync.insert(d->sequence, bp); } - br.enabled = params.enabled; - bp.setResponse(br); - if (bp.state() == BreakpointChangeProceeding) - bp.notifyBreakpointChangeOk(); + if (bp->state() == BreakpointUpdateProceeding) + notifyBreakpointChangeOk(bp); } -void QmlEngine::attemptBreakpointSynchronization() +bool QmlEngine::acceptsBreakpoint(const BreakpointParameters &bp) const { - if (!stateAcceptsBreakpointChanges()) { - showMessage("BREAKPOINT SYNCHRONIZATION NOT POSSIBLE IN CURRENT STATE"); - return; - } - - BreakHandler *handler = breakHandler(); - - DebuggerEngine *bpOwner = masterEngine(); - for (Breakpoint bp : handler->unclaimedBreakpoints()) { - // Take ownership of the breakpoint. Requests insertion. - if (acceptsBreakpoint(bp)) - bp.setEngine(bpOwner); - } - - for (Breakpoint bp : handler->engineBreakpoints(bpOwner)) { - switch (bp.state()) { - case BreakpointNew: - // Should not happen once claimed. - QTC_CHECK(false); - continue; - case BreakpointInsertRequested: - insertBreakpoint(bp); - continue; - case BreakpointChangeRequested: - changeBreakpoint(bp); - continue; - case BreakpointRemoveRequested: - removeBreakpoint(bp); - continue; - case BreakpointChangeProceeding: - case BreakpointInsertProceeding: - case BreakpointRemoveProceeding: - case BreakpointInserted: - case BreakpointDead: - continue; - } - QTC_ASSERT(false, qDebug() << "UNKNOWN STATE" << bp << state()); - } - - DebuggerEngine::attemptBreakpointSynchronization(); -} - -bool QmlEngine::acceptsBreakpoint(Breakpoint bp) const -{ - if (!bp.parameters().isCppBreakpoint()) - return true; - - //If it is a Cpp Breakpoint query if the type can be also handled by the debugger client //TODO: enable setting of breakpoints before start of debug session //For now, the event breakpoint can be set after the activeDebuggerClient is known //This is because the older client does not support BreakpointOnQmlSignalHandler - BreakpointType type = bp.type(); - return type == BreakpointOnQmlSignalEmit - || type == BreakpointByFileAndLine - || type == BreakpointAtJavaScriptThrow; + if (bp.type == BreakpointOnQmlSignalEmit || bp.type == BreakpointAtJavaScriptThrow) + return true; + + return bp.isQmlFileAndLineBreakpoint(); } void QmlEngine::loadSymbols(const QString &moduleName) @@ -1041,6 +988,11 @@ void QmlEngine::doUpdateLocals(const UpdateParameters ¶ms) d->updateLocals(); } +Context QmlEngine::languageContext() const +{ + return Context(Constants::C_QMLDEBUGGER); +} + void QmlEngine::disconnected() { showMessage(tr("QML Debugger disconnected."), StatusBar); @@ -1084,11 +1036,8 @@ void QmlEngine::updateCurrentContext() + (context.isEmpty() ? tr("Global QML Context") : context)); } -void QmlEngine::executeDebuggerCommand(const QString &command, DebuggerLanguages languages) +void QmlEngine::executeDebuggerCommand(const QString &command) { - if (!(languages & QmlLanguage)) - return; - if (state() == InferiorStopOk) { StackHandler *handler = stackHandler(); if (handler->isContentsValid() && handler->currentFrame().isUsable()) { @@ -1116,6 +1065,16 @@ void QmlEngine::executeDebuggerCommand(const QString &command, DebuggerLanguages } } +bool QmlEngine::companionPreventsActions() const +{ + // We need a C++ Engine in a Running state to do anything sensible + // as otherwise the debugger services in the debuggee are unresponsive. + if (DebuggerEngine *companion = companionEngine()) + return companion->state() != InferiorRunOk; + + return false; +} + void QmlEnginePrivate::updateScriptSource(const QString &fileName, int lineOffset, int columnOffset, const QString &source) { @@ -1181,11 +1140,8 @@ void QmlEngine::connectionFailed() // this is only an error if we are already connected and something goes wrong. if (isConnected()) { showMessage(tr("QML Debugger: Connection failed."), StatusBar); - - if (!isSlaveEngine()) { // normal flow for slave engine when gdb exits - notifyInferiorSpontaneousStop(); - notifyInferiorIll(); - } + notifyInferiorSpontaneousStop(); + notifyInferiorIll(); } else { d->connectionTimer.stop(); connectionStartupFailed(); @@ -1492,7 +1448,7 @@ void QmlEnginePrivate::setBreakpoint(const QString type, const QString target, } } -void QmlEnginePrivate::clearBreakpoint(int breakpoint) +void QmlEnginePrivate::clearBreakpoint(const Breakpoint &bp) { // { "seq" : , // "type" : "request", @@ -1502,7 +1458,7 @@ void QmlEnginePrivate::clearBreakpoint(int breakpoint) // } DebuggerCommand cmd(CLEARBREAKPOINT); - cmd.arg(BREAKPOINT, breakpoint); + cmd.arg(BREAKPOINT, bp->responseId().toInt()); runCommand(cmd); } @@ -1511,10 +1467,10 @@ bool QmlEnginePrivate::canChangeBreakpoint() const return supportChangeBreakpoint; } -void QmlEnginePrivate::changeBreakpoint(int breakpoint, bool enabled) +void QmlEnginePrivate::changeBreakpoint(const Breakpoint &bp, bool enabled) { DebuggerCommand cmd(CHANGEBREAKPOINT); - cmd.arg(BREAKPOINT, breakpoint); + cmd.arg(BREAKPOINT, bp->responseId().toInt()); cmd.arg(ENABLED, enabled); runCommand(cmd); } @@ -1780,27 +1736,26 @@ void QmlEnginePrivate::messageReceived(const QByteArray &data) int seq = resp.value("request_seq").toInt(); const QVariantMap breakpointData = resp.value(BODY).toMap(); - int index = breakpointData.value("breakpoint").toInt(); + const QString index = QString::number(breakpointData.value("breakpoint").toInt()); if (breakpointsSync.contains(seq)) { - BreakpointModelId id = breakpointsSync.take(seq); - breakpoints.insert(id, index); + Breakpoint bp = breakpointsSync.take(seq); + QTC_ASSERT(bp, return); + bp->setParameters(bp->requestedParameters()); // Assume it worked. + bp->setResponseId(index); //Is actual position info present? Then breakpoint was //accepted const QVariantList actualLocations = breakpointData.value("actual_locations").toList(); + const int line = breakpointData.value("line").toInt() + 1; if (actualLocations.count()) { //The breakpoint requested line should be same as //actual line - BreakHandler *handler = engine->breakHandler(); - Breakpoint bp = handler->breakpointById(id); - if (bp.state() != BreakpointInserted) { - BreakpointResponse br = bp.response(); - br.lineNumber = breakpointData.value("line").toInt() + 1; - br.pending = false; - bp.setResponse(br); - bp.notifyBreakpointInsertOk(); + if (bp && bp->state() != BreakpointInserted) { + bp->setLineNumber(line); + bp->setPending(false); + engine->notifyBreakpointInsertOk(bp); } } @@ -1900,14 +1855,18 @@ void QmlEnginePrivate::messageReceived(const QByteArray &data) bool inferiorStop = true; - QList v8BreakpointIds; - { - const QVariantList v8BreakpointIdList = breakData.value("breakpoints").toList(); - for (const QVariant &breakpointId : v8BreakpointIdList) - v8BreakpointIds << breakpointId.toInt(); + QList v8Breakpoints; + + const QVariantList v8BreakpointIdList = breakData.value("breakpoints").toList(); + for (const QVariant &breakpointId : v8BreakpointIdList) { + const QString x = breakpointId.toString(); + const QString responseId = QString::number(breakpointId.toInt()); + Breakpoint bp = engine->breakHandler()->findBreakpointByResponseId(responseId); + QTC_ASSERT(bp, continue); + v8Breakpoints << bp; } - if (!v8BreakpointIds.isEmpty() && invocationText.startsWith("[anonymous]()") + if (!v8Breakpoints.isEmpty() && invocationText.startsWith("[anonymous]()") && scriptUrl.endsWith(".qml") && sourceLineText.trimmed().startsWith('(')) { @@ -1915,24 +1874,20 @@ void QmlEnginePrivate::messageReceived(const QByteArray &data) // -> relocate the breakpoint to column: 1 and continue int newColumn = sourceLineText.indexOf('(') + 1; - BreakHandler *handler = engine->breakHandler(); - for (int v8Id : v8BreakpointIds) { - const BreakpointModelId id = breakpoints.key(v8Id); - Breakpoint bp = handler->breakpointById(id); - if (bp.isValid()) { - const BreakpointParameters ¶ms = bp.parameters(); + for (const Breakpoint &bp : v8Breakpoints) { + QTC_ASSERT(bp, continue); + const BreakpointParameters ¶ms = bp->requestedParameters(); - clearBreakpoint(v8Id); - setBreakpoint(SCRIPTREGEXP, - params.fileName, - params.enabled, - params.lineNumber, - newColumn, - params.condition, - params.ignoreCount); - breakpointsSync.insert(sequence, id); - } + clearBreakpoint(bp); + setBreakpoint(SCRIPTREGEXP, + params.fileName, + params.enabled, + params.lineNumber, + newColumn, + params.condition, + params.ignoreCount); + breakpointsSync.insert(sequence, bp); } continueDebugging(Continue); inferiorStop = false; @@ -1946,29 +1901,23 @@ void QmlEnginePrivate::messageReceived(const QByteArray &data) if (inferiorStop) { //Update breakpoint data - BreakHandler *handler = engine->breakHandler(); - for (int v8Id : v8BreakpointIds) { - const BreakpointModelId id = breakpoints.key(v8Id); - Breakpoint bp = handler->breakpointById(id); - if (bp) { - BreakpointResponse br = bp.response(); - if (br.functionName.isEmpty()) { - br.functionName = invocationText; - bp.setResponse(br); - } - if (bp.state() != BreakpointInserted) { - br.lineNumber = breakData.value("sourceLine").toInt() + 1; - br.pending = false; - bp.setResponse(br); - bp.notifyBreakpointInsertOk(); - } + for (const Breakpoint &bp : v8Breakpoints) { + QTC_ASSERT(bp, continue); + if (bp->functionName().isEmpty()) { + bp->setFunctionName(invocationText); + } + if (bp->state() != BreakpointInserted) { + bp->setLineNumber(breakData.value("sourceLine").toInt() + 1); + bp->setPending(false); + engine->notifyBreakpointInsertOk(bp); } } if (engine->state() == InferiorRunOk) { - for (const QVariant &breakpointId : v8BreakpointIds) { - if (breakpointsTemp.contains(breakpointId.toInt())) - clearBreakpoint(breakpointId.toInt()); + for (const Breakpoint &bp : v8Breakpoints) { + QTC_ASSERT(bp, continue); + if (breakpointsTemp.contains(bp->responseId())) + clearBreakpoint(bp); } engine->notifyInferiorSpontaneousStop(); backtrace(); diff --git a/src/plugins/debugger/qml/qmlengine.h b/src/plugins/debugger/qml/qmlengine.h index bdd6b2d64cd..76b0452c160 100644 --- a/src/plugins/debugger/qml/qmlengine.h +++ b/src/plugins/debugger/qml/qmlengine.h @@ -34,7 +34,6 @@ namespace Debugger { namespace Internal { -class WatchItem; class QmlEnginePrivate; class QmlInspectorAgent; @@ -68,7 +67,6 @@ private: void setState(DebuggerState state, bool forced) override; void gotoLocation(const Internal::Location &location) override; - void insertBreakpoint(Breakpoint bp) override; bool canDisplayTooltip() const override { return false; } @@ -97,10 +95,10 @@ private: void activateFrame(int index) override; void selectThread(ThreadId threadId) override; - void attemptBreakpointSynchronization() override; - void removeBreakpoint(Breakpoint bp) override; - void changeBreakpoint(Breakpoint bp) override; - bool acceptsBreakpoint(Breakpoint bp) const override; + bool acceptsBreakpoint(const BreakpointParameters &bp) const final; + void insertBreakpoint(const Breakpoint &bp) final; + void removeBreakpoint(const Breakpoint &bp) final; + void updateBreakpoint(const Breakpoint &bp) final; void assignValueInDebugger(WatchItem *item, const QString &expr, const QVariant &value) override; @@ -117,12 +115,14 @@ private: void updateItem(const QString &iname) override; void expandItem(const QString &iname) override; void selectWatchData(const QString &iname) override; - void executeDebuggerCommand(const QString &command, DebuggerLanguages languages) override; + void executeDebuggerCommand(const QString &command) override; + bool companionPreventsActions() const override; bool hasCapability(unsigned) const override; void quitDebugger() override; void doUpdateLocals(const UpdateParameters ¶ms) override; + Core::Context languageContext() const override; void closeConnection(); void startApplicationLauncher(); @@ -135,7 +135,6 @@ private: bool isConnected() const; private: - friend class QmlCppEngine; friend class QmlEnginePrivate; friend class QmlInspectorAgent; QmlEnginePrivate *d; diff --git a/src/plugins/debugger/qml/qmlinspectoragent.cpp b/src/plugins/debugger/qml/qmlinspectoragent.cpp index 3057a753e09..de47d33eff0 100644 --- a/src/plugins/debugger/qml/qmlinspectoragent.cpp +++ b/src/plugins/debugger/qml/qmlinspectoragent.cpp @@ -67,7 +67,8 @@ Q_LOGGING_CATEGORY(qmlInspectorLog, "qtc.dbg.qmlinspector") QmlInspectorAgent::QmlInspectorAgent(QmlEngine *engine, QmlDebugConnection *connection) : m_qmlEngine(engine) , m_objectToSelect(WatchItem::InvalidId) - , m_masterEngine(engine->masterEngine()) + , m_toolsClient(nullptr) + , m_targetToSync(NoTarget) , m_debugIdToSelect(WatchItem::InvalidId) , m_currentSelectedDebugId(WatchItem::InvalidId) , m_inspectorToolsContext("Debugger.QmlInspector") @@ -786,7 +787,7 @@ void QmlInspectorAgent::toolsClientStateChanged(QmlDebugClient::State state) Core::ICore::addAdditionalContext(m_inspectorToolsContext); m_toolsClientConnected = true; - enableTools(m_masterEngine->state() == InferiorRunOk); + enableTools(m_qmlEngine->state() == InferiorRunOk); if (m_showAppOnTopAction->isChecked()) m_toolsClient->showAppOnTop(true); @@ -901,7 +902,7 @@ void QmlInspectorAgent::setActiveEngineClient(BaseEngineDebugClient *client) void QmlInspectorAgent::jumpToObjectDefinitionInEditor( const FileReference &objSource, int debugId) { - const QString fileName = m_masterEngine->toFileInProject(objSource.url()); + const QString fileName = m_qmlEngine->toFileInProject(objSource.url()); Core::EditorManager::openEditorAt(fileName, objSource.lineNumber()); if (debugId != WatchItem::InvalidId && debugId != m_currentSelectedDebugId) { diff --git a/src/plugins/debugger/qml/qmlinspectoragent.h b/src/plugins/debugger/qml/qmlinspectoragent.h index 4d76c5f2a8b..516bcf6c629 100644 --- a/src/plugins/debugger/qml/qmlinspectoragent.h +++ b/src/plugins/debugger/qml/qmlinspectoragent.h @@ -134,7 +134,6 @@ private: QList m_fetchDataIds; QTimer m_delayQueryTimer; - DebuggerEngine *m_masterEngine; QHash m_engineClients; QmlDebug::BaseToolsClient *m_toolsClient = nullptr; diff --git a/src/plugins/debugger/registerhandler.cpp b/src/plugins/debugger/registerhandler.cpp index 1ee023aa311..c3be1132d86 100644 --- a/src/plugins/debugger/registerhandler.cpp +++ b/src/plugins/debugger/registerhandler.cpp @@ -676,8 +676,8 @@ bool RegisterHandler::setData(const QModelIndex &idx, const QVariant &data, int bool RegisterHandler::contextMenuEvent(const ItemViewEvent &ev) { - const bool actionsEnabled = m_engine->debuggerActionsEnabled(); const DebuggerState state = m_engine->state(); + const bool actionsEnabled = m_engine->debuggerActionsEnabled(); RegisterItem *registerItem = itemForIndexAtLevel<1>(ev.index()); RegisterSubItem *registerSubItem = itemForIndexAtLevel<2>(ev.index()); diff --git a/src/plugins/debugger/snapshothandler.cpp b/src/plugins/debugger/snapshothandler.cpp index 21dcaf9d1e8..619a3f5fdde 100644 --- a/src/plugins/debugger/snapshothandler.cpp +++ b/src/plugins/debugger/snapshothandler.cpp @@ -25,15 +25,28 @@ #include "snapshothandler.h" +#include "analyzer/analyzermanager.h" +#include "debuggeractions.h" #include "debuggerinternalconstants.h" #include "debuggericons.h" #include "debuggercore.h" #include "debuggerruncontrol.h" +#include "stackhandler.h" +#include +#include +#include + +#include +#include #include #include -#include +#include +#include + +using namespace Core; +using namespace Utils; namespace Debugger { namespace Internal { @@ -102,177 +115,300 @@ QDebug operator<<(QDebug d, const SnapshotData &f) } #endif +class EngineItem : public QObject, public TreeItem +{ +public: + QVariant data(int column, int role) const final; + bool setData(int row, const QVariant &data, int role) final; + + const bool m_isPreset = false; + QPointer m_engine; +}; + +class EngineManagerPrivate : public QObject +{ +public: + EngineManagerPrivate() + { + m_engineModel.setHeader({EngineManager::tr("Name"), EngineManager::tr("File")}); + m_engineModel.rootItem()->appendChild(new EngineItem); // The preset case. + + m_engineChooser = new QComboBox; + m_engineChooser->setVisible(false); + m_engineChooser->setModel(&m_engineModel); + connect(m_engineChooser, static_cast(&QComboBox::activated), + this, &EngineManagerPrivate::activateEngineByIndex); + } + + ~EngineManagerPrivate() + { + delete m_engineChooser; + } + + EngineItem *findEngineItem(DebuggerEngine *engine); + void activateEngine(DebuggerEngine *engine); + void activateEngineItem(EngineItem *engineItem); + void activateEngineByIndex(int index); + void selectUiForCurrentEngine(); + void updateEngineChooserVisibility(); + + TreeModel, EngineItem> m_engineModel; + QPointer m_currentItem; + Core::Id m_previousMode; + QPointer m_engineChooser; +}; + //////////////////////////////////////////////////////////////////////// // -// SnapshotHandler +// EngineManager // //////////////////////////////////////////////////////////////////////// /*! - \class Debugger::Internal::SnapshotHandler - \brief The SnapshotHandler class provides a model to represent the - snapshots in a QTreeView. - - A snapshot represents a debugging session. + \class Debugger::Internal::EngineManager + \brief The EngineManager manages running debugger engines. */ -SnapshotHandler::SnapshotHandler() = default; +static EngineManager *theEngineManager = nullptr; +static EngineManagerPrivate *d = nullptr; -SnapshotHandler::~SnapshotHandler() +EngineManager::EngineManager() { - for (int i = m_snapshots.size(); --i >= 0; ) { - if (DebuggerRunTool *runTool = at(i)) { - const DebuggerRunParameters &rp = runTool->runParameters(); - if (rp.isSnapshot && !rp.coreFile.isEmpty()) - QFile::remove(rp.coreFile); + theEngineManager = this; + d = new EngineManagerPrivate; +} + +QWidget *EngineManager::engineChooser() +{ + return d->m_engineChooser; +} + +EngineManager::~EngineManager() +{ + theEngineManager = nullptr; + delete d; +} + +EngineManager *EngineManager::instance() +{ + return theEngineManager; +} + +QAbstractItemModel *EngineManager::model() +{ + return &d->m_engineModel; +} + +void EngineManager::activateEngine(DebuggerEngine *engine) +{ + d->activateEngine(engine); +} + +QVariant EngineItem::data(int column, int role) const +{ + if (m_engine) { + if (role == SnapshotCapabilityRole) + return m_engine->hasCapability(SnapshotCapability); + + const DebuggerRunParameters &rp = m_engine->runParameters(); + + switch (role) { + case Qt::DisplayRole: + switch (column) { + case 0: + return m_engine->displayName(); + case 1: + return rp.coreFile.isEmpty() ? rp.inferior.executable : rp.coreFile; + } + return QVariant(); + + case Qt::ToolTipRole: + return QVariant(); + + case Qt::DecorationRole: + // Return icon that indicates whether this is the active engine + if (column == 0) + return d->m_currentItem == this ? Icons::LOCATION.icon() : Icons::EMPTY.icon(); + + default: + break; } - } -} - -int SnapshotHandler::rowCount(const QModelIndex &parent) const -{ - // Since the stack is not a tree, row count is 0 for any valid parent - return parent.isValid() ? 0 : m_snapshots.size(); -} - -int SnapshotHandler::columnCount(const QModelIndex &parent) const -{ - return parent.isValid() ? 0 : 2; -} - -QVariant SnapshotHandler::data(const QModelIndex &index, int role) const -{ - if (!index.isValid() || index.row() >= m_snapshots.size()) - return QVariant(); - - const DebuggerRunTool *runTool = at(index.row()); - - if (role == SnapshotCapabilityRole) - return runTool && runTool->activeEngine()->hasCapability(SnapshotCapability); - - if (!runTool) - return QLatin1String(""); - - const DebuggerRunParameters &rp = runTool->runParameters(); - - switch (role) { - case Qt::DisplayRole: - switch (index.column()) { - case 0: - return rp.displayName; - case 1: - return rp.coreFile.isEmpty() ? rp.inferior.executable : rp.coreFile; + } else { + switch (role) { + case Qt::DisplayRole: + return EngineManager::tr("Debugger Preset"); + default: + break; } - return QVariant(); - - case Qt::ToolTipRole: - return QVariant(); - - case Qt::DecorationRole: - // Return icon that indicates whether this is the active stack frame. - if (index.column() == 0) - return (index.row() == m_currentIndex) ? Icons::LOCATION.icon() : Icons::EMPTY.icon(); - - default: - break; } return QVariant(); } -QVariant SnapshotHandler::headerData(int section, Qt::Orientation orientation, int role) const +bool EngineItem::setData(int row, const QVariant &value, int role) { - if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { - switch (section) { - case 0: return tr("Name"); - case 1: return tr("File"); - }; + Q_UNUSED(row); + if (!m_engine) + return false; + + if (role == BaseTreeView::ItemActivatedRole) { + EngineItem *engineItem = d->findEngineItem(m_engine); + d->activateEngineItem(engineItem); + return true; } - return QVariant(); + + if (role == BaseTreeView::ItemViewEventRole) { + ItemViewEvent ev = value.value(); + + if (auto cmev = ev.as()) { + + auto menu = new QMenu(ev.view()); + + QAction *actCreate = menu->addAction(tr("Create Snapshot")); + actCreate->setEnabled(m_engine->hasCapability(SnapshotCapabilityRole)); + menu->addSeparator(); + + QAction *actRemove = menu->addAction(tr("Abort Debugger")); + actRemove->setEnabled(true); + + QAction *act = menu->exec(cmev->globalPos()); + + if (act == actCreate && m_engine) + m_engine->createSnapshot(); + else if (act == actRemove && m_engine) + m_engine->quitDebugger(); + + return true; + } + + if (auto kev = ev.as(QEvent::KeyPress)) { + if (kev->key() == Qt::Key_Delete && m_engine) { + m_engine->quitDebugger(); + } else if (kev->key() == Qt::Key_Return || kev->key() == Qt::Key_Enter) { + d->activateEngineByIndex(row); + } + return true; + } + } + + return false; } -Qt::ItemFlags SnapshotHandler::flags(const QModelIndex &index) const +void EngineManagerPrivate::activateEngineByIndex(int index) { - if (index.row() >= m_snapshots.size()) - return nullptr; - if (index.row() == m_snapshots.size()) - return QAbstractTableModel::flags(index); - return true ? QAbstractTableModel::flags(index) : Qt::ItemFlags({}); + activateEngineItem(m_engineModel.rootItem()->childAt(index)); } -void SnapshotHandler::activateSnapshot(int index) +void EngineManagerPrivate::activateEngineItem(EngineItem *engineItem) { - beginResetModel(); - m_currentIndex = index; - //qDebug() << "ACTIVATING INDEX: " << m_currentIndex << " OF " << size(); - Internal::displayDebugger(at(index)); - endResetModel(); + if (m_currentItem) { + if (DebuggerEngine *engine = m_currentItem->m_engine) { + const Context context = engine->languageContext(); + ICore::removeAdditionalContext(context); + } + } + + m_currentItem = engineItem; + + if (m_currentItem) { + if (DebuggerEngine *engine = m_currentItem->m_engine) { + const Context context = engine->languageContext(); + ICore::addAdditionalContext(context); + engine->gotoCurrentLocation(); + } + } + + selectUiForCurrentEngine(); } -void SnapshotHandler::createSnapshot(int index) +void EngineManagerPrivate::selectUiForCurrentEngine() { - DebuggerRunTool *runTool = at(index); - QTC_ASSERT(runTool, return); - runTool->engine()->createSnapshot(); + Perspective *perspective = nullptr; + int row = 0; + + if (m_currentItem && m_currentItem->m_engine) { + perspective = m_currentItem->m_engine->perspective(); + row = m_engineModel.rootItem()->indexOf(m_currentItem); + } + + m_engineChooser->setCurrentIndex(row); + + if (perspective) + perspective->select(); + else + selectPerspective(Debugger::Constants::PRESET_PERSPRECTIVE_ID); + + m_engineModel.rootItem()->forFirstLevelChildren([this](EngineItem *engineItem) { + if (engineItem && engineItem->m_engine) + engineItem->m_engine->updateMarkers(); + }); + + emit theEngineManager->currentEngineChanged(); } -void SnapshotHandler::removeSnapshot(int index) +void EngineManager::selectUiForCurrentEngine() { - DebuggerRunTool *runTool = at(index); - //qDebug() << "REMOVING " << runTool; - QTC_ASSERT(runTool, return); -#if 0 - // See http://sourceware.org/bugzilla/show_bug.cgi?id=11241. - setState(EngineSetupRequested); - postCommand("set stack-cache off"); -#endif - //QString fileName = runTool->startParameters().coreFile; - //if (!fileName.isEmpty()) - // QFile::remove(fileName); - beginResetModel(); - m_snapshots.removeAt(index); - if (index == m_currentIndex) - m_currentIndex = -1; - else if (index < m_currentIndex) - --m_currentIndex; - //runTool->quitDebugger(); - endResetModel(); + d->selectUiForCurrentEngine(); } - -void SnapshotHandler::removeAll() +EngineItem *EngineManagerPrivate::findEngineItem(DebuggerEngine *engine) { - beginResetModel(); - m_snapshots.clear(); - m_currentIndex = -1; - endResetModel(); + return m_engineModel.rootItem()->findFirstLevelChild([engine](EngineItem *engineItem) { + return engineItem->m_engine == engine; + }); } -void SnapshotHandler::appendSnapshot(DebuggerRunTool *runTool) +void EngineManagerPrivate::activateEngine(DebuggerEngine *engine) { - beginResetModel(); - m_snapshots.append(runTool); - m_currentIndex = size() - 1; - endResetModel(); + EngineItem *engineItem = findEngineItem(engine); + activateEngineItem(engineItem); } -void SnapshotHandler::removeSnapshot(DebuggerRunTool *runTool) +void EngineManagerPrivate::updateEngineChooserVisibility() +{ + // Show it if there's more than one option (i.e. not the the preset engine only) + const int count = m_engineModel.rootItem()->childCount(); + m_engineChooser->setVisible(count >= 2); +} + +void EngineManager::registerEngine(DebuggerEngine *engine) +{ + auto engineItem = new EngineItem; + engineItem->m_engine = engine; + d->m_engineModel.rootItem()->appendChild(engineItem); + d->updateEngineChooserVisibility(); +} + +void EngineManager::activateDebugMode() +{ + if (ModeManager::currentModeId() != Constants::MODE_DEBUG) { + d->m_previousMode = ModeManager::currentModeId(); + ModeManager::activateMode(Constants::MODE_DEBUG); + } +} + +void EngineManager::unregisterEngine(DebuggerEngine *engine) { // Could be that the run controls died before it was appended. - int index = m_snapshots.indexOf(runTool); - if (index != -1) - removeSnapshot(index); + if (auto engineItem = d->findEngineItem(engine)) + d->m_engineModel.destroyItem(engineItem); + + d->updateEngineChooserVisibility(); + emit theEngineManager->currentEngineChanged(); } -void SnapshotHandler::setCurrentIndex(int index) +QList> EngineManager::engines() { - beginResetModel(); - m_currentIndex = index; - endResetModel(); + QList> result; + d->m_engineModel.forItemsAtLevel<1>([&result](EngineItem *engineItem) { + if (DebuggerEngine *engine = engineItem->m_engine) + result.append(engine); + }); + return result; } -DebuggerRunTool *SnapshotHandler::at(int i) const +QPointer EngineManager::currentEngine() { - return m_snapshots.at(i).data(); + return d->m_currentItem ? d->m_currentItem->m_engine : nullptr; } } // namespace Internal diff --git a/src/plugins/debugger/snapshothandler.h b/src/plugins/debugger/snapshothandler.h index 6b93b20e324..080d4dfbe16 100644 --- a/src/plugins/debugger/snapshothandler.h +++ b/src/plugins/debugger/snapshothandler.h @@ -25,47 +25,43 @@ #pragma once +#include + #include +#include #include namespace Debugger { - -class DebuggerRunTool; - namespace Internal { -class SnapshotHandler : public QAbstractTableModel +class DebuggerEngine; + +class EngineManager : public QObject { Q_OBJECT public: - explicit SnapshotHandler(); - ~SnapshotHandler() override; + explicit EngineManager(); + ~EngineManager() final; - // Called from SnapshotHandler after a new snapshot has been added - void removeAll(); - QAbstractItemModel *model() { return this; } - int currentIndex() const { return m_currentIndex; } - void appendSnapshot(DebuggerRunTool *runTool); - void removeSnapshot(DebuggerRunTool *runTool); - void setCurrentIndex(int index); - int size() const { return m_snapshots.size(); } - DebuggerRunTool *at(int index) const; + static EngineManager *instance(); + static QAbstractItemModel *model(); - void createSnapshot(int index); - void activateSnapshot(int index); - void removeSnapshot(int index); + static void registerEngine(DebuggerEngine *engine); + static void unregisterEngine(DebuggerEngine *engine); + static void activateEngine(DebuggerEngine *engine); + static void activateDebugMode(); -private: - // QAbstractTableModel - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex &parent) const override; - QVariant data(const QModelIndex &index, int role) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; + static QList > engines(); + static QPointer currentEngine(); - int m_currentIndex = -1; - QList< QPointer > m_snapshots; + static void selectUiForCurrentEngine(); + + static QWidget *engineChooser(); + +signals: + void engineStateChanged(DebuggerEngine *engine); + void currentEngineChanged(); }; } // namespace Internal diff --git a/src/plugins/debugger/snapshotwindow.cpp b/src/plugins/debugger/snapshotwindow.cpp deleted file mode 100644 index 777d152e47e..00000000000 --- a/src/plugins/debugger/snapshotwindow.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/**************************************************************************** -** -** 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 "snapshotwindow.h" -#include "snapshothandler.h" - -#include "debuggeractions.h" -#include "debuggerinternalconstants.h" -#include "debuggercore.h" -#include "debuggerruncontrol.h" - -#include -#include - -#include - -#include -#include - -namespace Debugger { -namespace Internal { - -/////////////////////////////////////////////////////////////////////// -// -// SnapshotWindow -// -/////////////////////////////////////////////////////////////////////// - -SnapshotTreeView::SnapshotTreeView(SnapshotHandler *handler) -{ - m_snapshotHandler = handler; - setWindowTitle(tr("Snapshots")); -} - -void SnapshotTreeView::rowActivated(const QModelIndex &index) -{ - m_snapshotHandler->activateSnapshot(index.row()); -} - -void SnapshotTreeView::keyPressEvent(QKeyEvent *ev) -{ - if (ev->key() == Qt::Key_Delete) { - QItemSelectionModel *sm = selectionModel(); - QTC_ASSERT(sm, return); - QModelIndexList si = sm->selectedIndexes(); - if (si.isEmpty()) - si.append(currentIndex().sibling(currentIndex().row(), 0)); - - foreach (const QModelIndex &idx, si) - if (idx.column() == 0) - removeSnapshot(idx.row()); - } - BaseTreeView::keyPressEvent(ev); -} - -void SnapshotTreeView::contextMenuEvent(QContextMenuEvent *ev) -{ - QModelIndex idx = indexAt(ev->pos()); - - QMenu menu; - - QAction *actCreate = menu.addAction(tr("Create Snapshot")); - actCreate->setEnabled(idx.data(SnapshotCapabilityRole).toBool()); - menu.addSeparator(); - - QAction *actRemove = menu.addAction(tr("Remove Snapshot")); - actRemove->setEnabled(idx.isValid()); - - menu.addSeparator(); - menu.addAction(action(SettingsDialog)); - - QAction *act = menu.exec(ev->globalPos()); - - if (act == actCreate) - m_snapshotHandler->createSnapshot(idx.row()); - else if (act == actRemove) - removeSnapshot(idx.row()); -} - -void SnapshotTreeView::removeSnapshot(int i) -{ - m_snapshotHandler->at(i)->quitDebugger(); -} - -} // namespace Internal -} // namespace Debugger diff --git a/src/plugins/debugger/snapshotwindow.h b/src/plugins/debugger/snapshotwindow.h deleted file mode 100644 index a80284bacd3..00000000000 --- a/src/plugins/debugger/snapshotwindow.h +++ /dev/null @@ -1,52 +0,0 @@ -/**************************************************************************** -** -** 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. -** -****************************************************************************/ - -#pragma once - -#include - -namespace Debugger { -namespace Internal { - -class SnapshotHandler; - -class SnapshotTreeView : public Utils::BaseTreeView -{ - Q_OBJECT - -public: - explicit SnapshotTreeView(SnapshotHandler *handler); - -private: - void rowActivated(const QModelIndex &index); - void removeSnapshot(int i); - void keyPressEvent(QKeyEvent *ev) override; - void contextMenuEvent(QContextMenuEvent *ev) override; - - SnapshotHandler *m_snapshotHandler; -}; - -} // namespace Internal -} // namespace Debugger diff --git a/src/plugins/debugger/sourcefileshandler.cpp b/src/plugins/debugger/sourcefileshandler.cpp index c7761c7b290..3de9b483607 100644 --- a/src/plugins/debugger/sourcefileshandler.cpp +++ b/src/plugins/debugger/sourcefileshandler.cpp @@ -121,7 +121,7 @@ bool SourceFilesHandler::setData(const QModelIndex &idx, const QVariant &data, i QModelIndex index = idx.sibling(idx.row(), 0); QString name = index.data().toString(); - auto addAction = [this, menu](const QString &display, bool on, const std::function &onTriggered) { + auto addAction = [menu](const QString &display, bool on, const std::function &onTriggered) { QAction *act = menu->addAction(display); act->setEnabled(on); QObject::connect(act, &QAction::triggered, onTriggered); diff --git a/src/plugins/debugger/terminal.cpp b/src/plugins/debugger/terminal.cpp index a1acd42769e..12dd4664220 100644 --- a/src/plugins/debugger/terminal.cpp +++ b/src/plugins/debugger/terminal.cpp @@ -25,17 +25,17 @@ #include "terminal.h" -#include "debuggerruncontrol.h" - -#include -#include -#include +#include #include #include #include +#include +#include +#include + #ifdef Q_OS_UNIX # define DEBUGGER_USE_TERMINAL #endif @@ -168,13 +168,12 @@ void Terminal::onSlaveReaderActivated(int fd) #endif } -TerminalRunner::TerminalRunner(DebuggerRunTool *debugger) - : RunWorker(debugger->runControl()) +TerminalRunner::TerminalRunner(RunControl *runControl, const Runnable &stubRunnable) + : RunWorker(runControl) { setDisplayName("TerminalRunner"); - const DebuggerRunParameters &rp = debugger->runParameters(); - m_stubRunnable = rp.inferior; + m_stubRunnable = stubRunnable; connect(&m_stubProc, &ConsoleProcess::processError, this, &TerminalRunner::stubError); diff --git a/src/plugins/debugger/terminal.h b/src/plugins/debugger/terminal.h index 509c8350e3c..d6caa55f98e 100644 --- a/src/plugins/debugger/terminal.h +++ b/src/plugins/debugger/terminal.h @@ -71,7 +71,8 @@ private: class TerminalRunner : public ProjectExplorer::RunWorker { public: - explicit TerminalRunner(DebuggerRunTool *runControl); + TerminalRunner(ProjectExplorer::RunControl *runControl, + const ProjectExplorer::Runnable &stubRunnable); qint64 applicationPid() const { return m_applicationPid; } qint64 applicationMainThreadId() const { return m_applicationMainThreadId; } diff --git a/src/plugins/debugger/threadshandler.cpp b/src/plugins/debugger/threadshandler.cpp index 4ba1284fdbd..a4fd57dad61 100644 --- a/src/plugins/debugger/threadshandler.cpp +++ b/src/plugins/debugger/threadshandler.cpp @@ -367,7 +367,7 @@ void ThreadsHandler::updateThreadBox() forItemsAtLevel<1>([&list](ThreadItem *item) { list.append(QString::fromLatin1("#%1 %2").arg(item->threadData.id.raw()).arg(item->threadData.name)); }); - Internal::setThreadBoxContents(list, indexForThreadId(this, m_currentId)); + m_engine->setThreadBoxContents(list, indexForThreadId(this, m_currentId)); } ThreadData ThreadsHandler::thread(ThreadId id) const diff --git a/src/plugins/debugger/watchhandler.cpp b/src/plugins/debugger/watchhandler.cpp index 3b72e428d89..6bd122e9aed 100644 --- a/src/plugins/debugger/watchhandler.cpp +++ b/src/plugins/debugger/watchhandler.cpp @@ -1298,7 +1298,7 @@ void WatchModel::timerEvent(QTimerEvent *event) } ungrabWidget(); } - showMessage(msg, StatusBar); + m_engine->showMessage(msg, StatusBar); } else { WatchModelBase::timerEvent(event); } @@ -1951,8 +1951,10 @@ QString WatchModel::nameForFormat(int format) /////////////////////////////////////////////////////////////////////// WatchHandler::WatchHandler(DebuggerEngine *engine) + : m_engine(engine) { m_model = new WatchModel(this, engine); + loadSessionDataForEngine(); } WatchHandler::~WatchHandler() @@ -2072,7 +2074,13 @@ void WatchHandler::resetValueCache() void WatchHandler::resetWatchers() { - loadSessionData(); + loadFormats(); + theWatcherNames.clear(); + theWatcherCount = 0; + const QStringList watchers = sessionValue("Watchers").toStringList(); + m_model->m_watchRoot->removeChildren(); + for (const QString &exp : watchers) + watchExpression(exp.trimmed()); } void WatchHandler::notifyUpdateStarted(const UpdateParameters &updateParameters) @@ -2173,7 +2181,7 @@ void WatchHandler::watchExpression(const QString &exp, const QString &name, bool m_model->m_engine->updateWatchData(item->iname); } updateLocalsWindow(); - Internal::raiseWatchersWindow(); + m_engine->raiseWatchersWindow(); } void WatchHandler::updateWatchExpression(WatchItem *item, const QString &newExp) @@ -2358,7 +2366,7 @@ void WatchHandler::updateLocalsWindow() { // Force show/hide of return view. bool showReturn = m_model->m_returnRoot->childCount() != 0; - Internal::updateLocalsWindow(showReturn); + m_engine->updateLocalsWindow(showReturn); } QStringList WatchHandler::watchedExpressions() @@ -2382,6 +2390,11 @@ void WatchHandler::saveSessionData() } void WatchHandler::loadSessionData() +{ + // Handled by loadSesseionDataForEngine. +} + +void WatchHandler::loadSessionDataForEngine() { loadFormats(); theWatcherNames.clear(); diff --git a/src/plugins/debugger/watchhandler.h b/src/plugins/debugger/watchhandler.h index 9a289ef7c46..10e8825cdcf 100644 --- a/src/plugins/debugger/watchhandler.h +++ b/src/plugins/debugger/watchhandler.h @@ -76,8 +76,10 @@ public: WatchItem *findItem(const QString &iname) const; const WatchItem *findCppLocalVariable(const QString &name) const; - void loadSessionData(); - void saveSessionData(); + void loadSessionDataForEngine(); + + static void loadSessionData(); + static void saveSessionData(); bool isExpandedIName(const QString &iname) const; QSet expandedINames() const; @@ -120,6 +122,7 @@ public: void recordTypeInfo(const GdbMi &typeInfo); private: + DebuggerEngine * const m_engine; // Not owned WatchModel *m_model; // Owned. }; diff --git a/src/plugins/qmlprofiler/qmlprofilertool.cpp b/src/plugins/qmlprofiler/qmlprofilertool.cpp index 8003788d905..a496012d103 100644 --- a/src/plugins/qmlprofiler/qmlprofilertool.cpp +++ b/src/plugins/qmlprofiler/qmlprofilertool.cpp @@ -230,13 +230,13 @@ QmlProfilerTool::QmlProfilerTool() QObject::connect(d->m_startAction, &QAction::triggered, this, &QmlProfilerTool::profileStartupProject); Utils::Perspective *perspective = d->m_viewContainer->perspective(); - perspective->addToolbarAction(d->m_startAction); - perspective->addToolbarAction(d->m_stopAction); - perspective->addToolbarWidget(d->m_recordButton); - perspective->addToolbarWidget(d->m_clearButton); - perspective->addToolbarWidget(d->m_searchButton); - perspective->addToolbarWidget(d->m_displayFeaturesButton); - perspective->addToolbarWidget(d->m_timeLabel); + perspective->addToolBarAction(d->m_startAction); + perspective->addToolBarAction(d->m_stopAction); + perspective->addToolBarWidget(d->m_recordButton); + perspective->addToolBarWidget(d->m_clearButton); + perspective->addToolBarWidget(d->m_searchButton); + perspective->addToolBarWidget(d->m_displayFeaturesButton); + perspective->addToolBarWidget(d->m_timeLabel); connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::updateRunActions, this, &QmlProfilerTool::updateRunActions); diff --git a/src/plugins/qmlprofiler/qmlprofilerviewmanager.cpp b/src/plugins/qmlprofiler/qmlprofilerviewmanager.cpp index 2dabf618582..0692d80e9cb 100644 --- a/src/plugins/qmlprofiler/qmlprofilerviewmanager.cpp +++ b/src/plugins/qmlprofiler/qmlprofilerviewmanager.cpp @@ -82,17 +82,17 @@ QmlProfilerViewManager::QmlProfilerViewManager(QObject *parent, m_flameGraphView = new FlameGraphView(m_profilerModelManager); prepareEventsView(m_flameGraphView); - QWidget *anchor = nullptr; + QWidget *anchorDock = nullptr; if (m_traceView->isUsable()) { - anchor = m_traceView; + anchorDock = m_traceView; m_perspective->addWindow(m_traceView, Perspective::SplitVertical, nullptr); - m_perspective->addWindow(m_flameGraphView, Perspective::AddToTab, anchor); + m_perspective->addWindow(m_flameGraphView, Perspective::AddToTab, m_traceView); } else { - anchor = m_flameGraphView; + anchorDock = m_flameGraphView; m_perspective->addWindow(m_flameGraphView, Perspective::SplitVertical, nullptr); } - m_perspective->addWindow(m_statisticsView, Perspective::AddToTab, anchor); - m_perspective->addWindow(anchor, Perspective::Raise, nullptr); + m_perspective->addWindow(m_statisticsView, Perspective::AddToTab, anchorDock); + m_perspective->addWindow(anchorDock, Perspective::Raise, nullptr); Debugger::registerPerspective(m_perspective); } @@ -102,6 +102,7 @@ QmlProfilerViewManager::~QmlProfilerViewManager() delete m_traceView; delete m_flameGraphView; delete m_statisticsView; + delete m_perspective; } void QmlProfilerViewManager::clear() diff --git a/src/plugins/valgrind/callgrindtool.cpp b/src/plugins/valgrind/callgrindtool.cpp index faa08371e73..a0dfbd96b67 100644 --- a/src/plugins/valgrind/callgrindtool.cpp +++ b/src/plugins/valgrind/callgrindtool.cpp @@ -420,17 +420,17 @@ CallgrindTool::CallgrindTool() updateEventCombo(); auto perspective = new Perspective(CallgrindPerspectiveId, tr("Callgrind")); - perspective->addToolbarAction(m_startAction); - perspective->addToolbarAction(m_stopAction); - perspective->addToolbarAction(m_loadExternalLogFile); - perspective->addToolbarAction(m_dumpAction); - perspective->addToolbarAction(m_resetAction); - perspective->addToolbarAction(m_pauseAction); - perspective->addToolbarAction(m_discardAction); - perspective->addToolbarAction(m_goBack); - perspective->addToolbarAction(m_goNext); + perspective->addToolBarAction(m_startAction); + perspective->addToolBarAction(m_stopAction); + perspective->addToolBarAction(m_loadExternalLogFile); + perspective->addToolBarAction(m_dumpAction); + perspective->addToolBarAction(m_resetAction); + perspective->addToolBarAction(m_pauseAction); + perspective->addToolBarAction(m_discardAction); + perspective->addToolBarAction(m_goBack); + perspective->addToolBarAction(m_goNext); perspective->addToolbarSeparator(); - perspective->addToolbarWidget(m_eventCombo); + perspective->addToolBarWidget(m_eventCombo); // Cost formatting { @@ -463,7 +463,7 @@ CallgrindTool::CallgrindTool() button->setPopupMode(QToolButton::InstantPopup); button->setText(QLatin1String("$")); button->setToolTip(tr("Cost Format")); - perspective->addToolbarWidget(button); + perspective->addToolBarWidget(button); } ValgrindGlobalSettings *settings = ValgrindPlugin::globalSettings(); @@ -500,10 +500,10 @@ CallgrindTool::CallgrindTool() setCostFormat(settings->costFormat()); enableCycleDetection(settings->detectCycles()); - perspective->addToolbarAction(m_cycleDetection); - perspective->addToolbarAction(m_shortenTemplates); - perspective->addToolbarAction(m_filterProjectCosts); - perspective->addToolbarWidget(m_searchFilter); + perspective->addToolBarAction(m_cycleDetection); + perspective->addToolBarAction(m_shortenTemplates); + perspective->addToolBarAction(m_filterProjectCosts); + perspective->addToolBarWidget(m_searchFilter); perspective->addWindow(m_flatView, Perspective::SplitVertical, nullptr); perspective->addWindow(m_calleesView, Perspective::SplitVertical, nullptr); diff --git a/src/plugins/valgrind/memchecktool.cpp b/src/plugins/valgrind/memchecktool.cpp index 534de63d3da..59f8a95f7e3 100644 --- a/src/plugins/valgrind/memchecktool.cpp +++ b/src/plugins/valgrind/memchecktool.cpp @@ -683,13 +683,13 @@ MemcheckTool::MemcheckTool() ProjectExplorerPlugin::startRunControl(rc); }); - perspective->addToolbarAction(m_startAction); + perspective->addToolBarAction(m_startAction); //toolbar.addAction(m_startWithGdbAction); - perspective->addToolbarAction(m_stopAction); - perspective->addToolbarAction(m_loadExternalLogFile); - perspective->addToolbarAction(m_goBack); - perspective->addToolbarAction(m_goNext); - perspective->addToolbarWidget(filterButton); + perspective->addToolBarAction(m_stopAction); + perspective->addToolBarAction(m_loadExternalLogFile); + perspective->addToolBarAction(m_goBack); + perspective->addToolBarAction(m_goNext); + perspective->addToolBarWidget(filterButton); Debugger::registerPerspective(perspective); updateFromSettings();