forked from qt-creator/qt-creator
The location marker is updated very regularly and especially while stepping moves one line at a time. Resizing this marker when it enters a line that already contains a marker is visually distracting. To prevent this paint the marker over all other markers in that line. Change-Id: I63ad72384e77eeae4dc6d2e2c2ac77c88da92c56 Reviewed-by: Eike Ziller <eike.ziller@qt.io>
2899 lines
96 KiB
C++
2899 lines
96 KiB
C++
// Copyright (C) 2016 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
|
|
|
#include "debuggerengine.h"
|
|
|
|
#include "debuggerinternalconstants.h"
|
|
#include "debuggeractions.h"
|
|
#include "debuggercore.h"
|
|
#include "debuggerdialogs.h"
|
|
#include "debuggericons.h"
|
|
#include "debuggerruncontrol.h"
|
|
#include "debuggertooltipmanager.h"
|
|
#include "debuggertr.h"
|
|
|
|
#include "breakhandler.h"
|
|
#include "disassembleragent.h"
|
|
#include "localsandexpressionswindow.h"
|
|
#include "logwindow.h"
|
|
#include "debuggermainwindow.h"
|
|
#include "enginemanager.h"
|
|
#include "memoryagent.h"
|
|
#include "moduleshandler.h"
|
|
#include "registerhandler.h"
|
|
#include "peripheralregisterhandler.h"
|
|
#include "sourcefileshandler.h"
|
|
#include "sourceutils.h"
|
|
#include "stackhandler.h"
|
|
#include "stackwindow.h"
|
|
#include "terminal.h"
|
|
#include "threadshandler.h"
|
|
#include "watchhandler.h"
|
|
#include "watchutils.h"
|
|
#include "watchwindow.h"
|
|
#include "debugger/shared/peutils.h"
|
|
|
|
#include <coreplugin/actionmanager/actionmanager.h>
|
|
#include <coreplugin/editormanager/editormanager.h>
|
|
#include <coreplugin/editormanager/ieditor.h>
|
|
#include <coreplugin/icore.h>
|
|
#include <coreplugin/idocument.h>
|
|
#include <coreplugin/messagebox.h>
|
|
#include <coreplugin/modemanager.h>
|
|
#include <coreplugin/progressmanager/progressmanager.h>
|
|
#include <coreplugin/progressmanager/futureprogress.h>
|
|
|
|
#include <projectexplorer/devicesupport/devicemanager.h>
|
|
#include <projectexplorer/projectexplorer.h>
|
|
#include <projectexplorer/taskhub.h>
|
|
|
|
#include <texteditor/texteditor.h>
|
|
#include <texteditor/texteditorsettings.h>
|
|
#include <texteditor/fontsettings.h>
|
|
|
|
#include <utils/algorithm.h>
|
|
#include <utils/basetreeview.h>
|
|
#include <utils/checkablemessagebox.h>
|
|
#include <utils/macroexpander.h>
|
|
#include <utils/processhandle.h>
|
|
#include <utils/processinterface.h>
|
|
#include <utils/qtcassert.h>
|
|
#include <utils/qtcprocess.h>
|
|
#include <utils/styledbar.h>
|
|
#include <utils/utilsicons.h>
|
|
|
|
#include <QApplication>
|
|
#include <QComboBox>
|
|
#include <QDebug>
|
|
#include <QDir>
|
|
#include <QDockWidget>
|
|
#include <QFileInfo>
|
|
#include <QHeaderView>
|
|
#include <QTextBlock>
|
|
#include <QTimer>
|
|
#include <QToolButton>
|
|
|
|
#include <QJsonArray>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QJsonValue>
|
|
|
|
using namespace Core;
|
|
using namespace Debugger::Internal;
|
|
using namespace ProjectExplorer;
|
|
using namespace TextEditor;
|
|
using namespace Utils;
|
|
|
|
//#define WITH_BENCHMARK
|
|
#ifdef WITH_BENCHMARK
|
|
#include <valgrind/callgrind.h>
|
|
#endif
|
|
|
|
namespace Debugger {
|
|
|
|
QDebug operator<<(QDebug d, DebuggerState state)
|
|
{
|
|
//return d << DebuggerEngine::stateName(state) << '(' << int(state) << ')';
|
|
return d << DebuggerEngine::stateName(state);
|
|
}
|
|
|
|
QDebug operator<<(QDebug str, const DebuggerRunParameters &sp)
|
|
{
|
|
QDebug nospace = str.nospace();
|
|
nospace << "executable=" << sp.inferior.command.executable()
|
|
<< " coreFile=" << sp.coreFile
|
|
<< " processArgs=" << sp.inferior.command.arguments()
|
|
<< " inferior environment=<" << sp.inferior.environment.toStringList().size() << " variables>"
|
|
<< " debugger environment=<" << sp.debugger.environment.toStringList().size() << " variables>"
|
|
<< " workingDir=" << sp.inferior.workingDirectory
|
|
<< " attachPID=" << sp.attachPID.pid()
|
|
<< " remoteChannel=" << sp.remoteChannel
|
|
<< " abi=" << sp.toolChainAbi.toString() << '\n';
|
|
return str;
|
|
}
|
|
|
|
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 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;
|
|
m_lineNumber = frame.line;
|
|
m_needsMarker = marker;
|
|
m_functionName = frame.function;
|
|
m_hasDebugInfo = frame.isUsable();
|
|
m_address = frame.address;
|
|
m_from = frame.module;
|
|
}
|
|
|
|
|
|
LocationMark::LocationMark(DebuggerEngine *engine, const FilePath &file, int line)
|
|
: TextMark(file, line, Constants::TEXT_MARK_CATEGORY_LOCATION), m_engine(engine)
|
|
{
|
|
setPriority(TextMark::HighPriority);
|
|
setIsLocationMarker(true);
|
|
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());
|
|
}
|
|
|
|
bool LocationMark::isDraggable() const
|
|
{
|
|
return m_engine && m_engine->hasCapability(JumpToLineCapability);
|
|
}
|
|
|
|
void LocationMark::dragToLine(int line)
|
|
{
|
|
if (m_engine) {
|
|
if (BaseTextEditor *textEditor = BaseTextEditor::currentTextEditor()) {
|
|
ContextData location = getLocationContext(textEditor->textDocument(), line);
|
|
if (location.isValid())
|
|
m_engine->executeJumpToLine(location);
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
// MemoryAgentSet
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
class MemoryAgentSet
|
|
{
|
|
public:
|
|
~MemoryAgentSet()
|
|
{
|
|
qDeleteAll(m_agents);
|
|
m_agents.clear();
|
|
}
|
|
|
|
// Called by engine to create a new view.
|
|
void createBinEditor(const MemoryViewSetupData &data, DebuggerEngine *engine)
|
|
{
|
|
auto agent = new MemoryAgent(data, engine);
|
|
if (agent->isUsable()) {
|
|
m_agents.push_back(agent);
|
|
} else {
|
|
delete agent;
|
|
AsynchronousMessageBox::warning(
|
|
Tr::tr("No Memory Viewer Available"),
|
|
Tr::tr("The memory contents cannot be shown as no viewer plugin "
|
|
"for binary data has been loaded."));
|
|
}
|
|
}
|
|
|
|
// On stack frame completed and on request.
|
|
void updateContents()
|
|
{
|
|
for (MemoryAgent *agent : m_agents) {
|
|
if (agent)
|
|
agent->updateContents();
|
|
}
|
|
}
|
|
|
|
void handleDebuggerFinished()
|
|
{
|
|
for (MemoryAgent *agent : m_agents) {
|
|
if (agent)
|
|
agent->setFinished(); // Prevent triggering updates, etc.
|
|
}
|
|
}
|
|
|
|
private:
|
|
std::vector<MemoryAgent *> m_agents;
|
|
};
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
// DebuggerEnginePrivate
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
class DebuggerEnginePrivate : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
DebuggerEnginePrivate(DebuggerEngine *engine)
|
|
: m_engine(engine),
|
|
m_breakHandler(engine),
|
|
m_modulesHandler(engine),
|
|
m_registerHandler(engine),
|
|
m_peripheralRegisterHandler(engine),
|
|
m_sourceFilesHandler(engine),
|
|
m_stackHandler(engine),
|
|
m_threadsHandler(engine),
|
|
m_watchHandler(engine),
|
|
m_disassemblerAgent(engine),
|
|
m_toolTipManager(engine)
|
|
{
|
|
m_debuggerName = Tr::tr("Debugger");
|
|
|
|
m_logWindow = new LogWindow(m_engine); // Needed before start()
|
|
m_logWindow->setObjectName("Debugger.Dock.Output");
|
|
|
|
connect(&debuggerSettings()->enableReverseDebugging, &BaseAspect::changed, this, [this] {
|
|
updateState();
|
|
if (m_companionEngine)
|
|
m_companionEngine->d->updateState();
|
|
});
|
|
static int contextCount = 0;
|
|
m_context = Context(Id("Debugger.Engine.").withSuffix(++contextCount));
|
|
|
|
ActionManager::registerAction(&m_continueAction, Constants::CONTINUE, m_context);
|
|
ActionManager::registerAction(&m_exitAction, Constants::STOP, m_context);
|
|
ActionManager::registerAction(&m_interruptAction, Constants::INTERRUPT, m_context);
|
|
ActionManager::registerAction(&m_abortAction, Constants::ABORT, m_context);
|
|
ActionManager::registerAction(&m_stepOverAction, Constants::NEXT, m_context);
|
|
ActionManager::registerAction(&m_stepIntoAction, Constants::STEP, m_context);
|
|
ActionManager::registerAction(&m_stepOutAction, Constants::STEPOUT, m_context);
|
|
ActionManager::registerAction(&m_runToLineAction, Constants::RUNTOLINE, m_context);
|
|
ActionManager::registerAction(&m_runToSelectedFunctionAction, Constants::RUNTOSELECTEDFUNCTION, m_context);
|
|
ActionManager::registerAction(&m_jumpToLineAction, Constants::JUMPTOLINE, m_context);
|
|
ActionManager::registerAction(&m_returnFromFunctionAction, Constants::RETURNFROMFUNCTION, m_context);
|
|
ActionManager::registerAction(&m_detachAction, Constants::DETACH, m_context);
|
|
ActionManager::registerAction(&m_resetAction, Constants::RESET, m_context);
|
|
ActionManager::registerAction(&m_watchAction, Constants::WATCH, m_context);
|
|
ActionManager::registerAction(&m_operateByInstructionAction, Constants::OPERATE_BY_INSTRUCTION, m_context);
|
|
ActionManager::registerAction(&m_openMemoryEditorAction, Constants::OPEN_MEMORY_EDITOR, m_context);
|
|
ActionManager::registerAction(&m_frameUpAction, Constants::FRAME_UP, m_context);
|
|
ActionManager::registerAction(&m_frameDownAction, Constants::FRAME_DOWN, m_context);
|
|
}
|
|
|
|
~DebuggerEnginePrivate()
|
|
{
|
|
ActionManager::unregisterAction(&m_continueAction, Constants::CONTINUE);
|
|
ActionManager::unregisterAction(&m_exitAction, Constants::STOP);
|
|
ActionManager::unregisterAction(&m_interruptAction, Constants::INTERRUPT);
|
|
ActionManager::unregisterAction(&m_abortAction, Constants::ABORT);
|
|
ActionManager::unregisterAction(&m_stepOverAction, Constants::NEXT);
|
|
ActionManager::unregisterAction(&m_stepIntoAction, Constants::STEP);
|
|
ActionManager::unregisterAction(&m_stepOutAction, Constants::STEPOUT);
|
|
ActionManager::unregisterAction(&m_runToLineAction, Constants::RUNTOLINE);
|
|
ActionManager::unregisterAction(&m_runToSelectedFunctionAction, Constants::RUNTOSELECTEDFUNCTION);
|
|
ActionManager::unregisterAction(&m_jumpToLineAction, Constants::JUMPTOLINE);
|
|
ActionManager::unregisterAction(&m_returnFromFunctionAction, Constants::RETURNFROMFUNCTION);
|
|
ActionManager::unregisterAction(&m_detachAction, Constants::DETACH);
|
|
ActionManager::unregisterAction(&m_resetAction, Constants::RESET);
|
|
ActionManager::unregisterAction(&m_watchAction, Constants::WATCH);
|
|
ActionManager::unregisterAction(&m_operateByInstructionAction, Constants::OPERATE_BY_INSTRUCTION);
|
|
ActionManager::unregisterAction(&m_openMemoryEditorAction, Constants::OPEN_MEMORY_EDITOR);
|
|
ActionManager::unregisterAction(&m_frameUpAction, Constants::FRAME_UP);
|
|
ActionManager::unregisterAction(&m_frameDownAction, Constants::FRAME_DOWN);
|
|
destroyPerspective();
|
|
|
|
delete m_logWindow;
|
|
delete m_breakWindow;
|
|
delete m_returnWindow;
|
|
delete m_localsWindow;
|
|
delete m_watchersWindow;
|
|
delete m_inspectorWindow;
|
|
delete m_registerWindow;
|
|
delete m_peripheralRegisterWindow;
|
|
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_peripheralRegisterView;
|
|
delete m_modulesView;
|
|
delete m_sourceFilesView;
|
|
delete m_stackView;
|
|
delete m_threadsView;
|
|
}
|
|
|
|
void updateActionToolTips()
|
|
{
|
|
// update tooltips that are visible on the button in the mode selector
|
|
const QString displayName = m_engine->displayName();
|
|
m_continueAction.setToolTip(Tr::tr("Continue %1").arg(displayName));
|
|
m_interruptAction.setToolTip(Tr::tr("Interrupt %1").arg(displayName));
|
|
}
|
|
|
|
void setupViews();
|
|
|
|
void destroyPerspective()
|
|
{
|
|
if (!m_perspective)
|
|
return;
|
|
|
|
Perspective *perspective = m_perspective;
|
|
m_perspective = nullptr;
|
|
|
|
EngineManager::unregisterEngine(m_engine);
|
|
|
|
// This triggers activity in the EngineManager which
|
|
// recognizes the rampdown by the m_perpective == nullptr above.
|
|
perspective->destroy();
|
|
|
|
// disconnect the follow font size connection
|
|
TextEditorSettings::instance()->disconnect(this);
|
|
|
|
delete perspective;
|
|
}
|
|
|
|
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(m_state == EngineShutdownFinished, qDebug() << m_state);
|
|
resetLocation();
|
|
m_progress.setProgressValue(1000);
|
|
m_progress.reportFinished();
|
|
m_modulesHandler.removeAll();
|
|
m_stackHandler.removeAll();
|
|
m_threadsHandler.removeAll();
|
|
m_watchHandler.cleanup();
|
|
m_engine->showMessage(Tr::tr("Debugger finished."), StatusBar);
|
|
m_engine->setState(DebuggerFinished); // Also destroys views.
|
|
if (debuggerSettings()->switchModeOnExit.value())
|
|
EngineManager::deactivateDebugMode();
|
|
}
|
|
|
|
void scheduleResetLocation()
|
|
{
|
|
m_stackHandler.scheduleResetLocation();
|
|
m_watchHandler.scheduleResetLocation();
|
|
m_disassemblerAgent.scheduleResetLocation();
|
|
m_locationTimer.setSingleShot(true);
|
|
m_locationTimer.start(80);
|
|
}
|
|
|
|
void resetLocation()
|
|
{
|
|
m_lookupRequests.clear();
|
|
m_locationTimer.stop();
|
|
m_locationMark.reset();
|
|
m_stackHandler.resetLocation();
|
|
m_disassemblerAgent.resetLocation();
|
|
m_toolTipManager.resetLocation();
|
|
m_breakHandler.resetLocation();
|
|
}
|
|
|
|
public:
|
|
void setInitialActionStates();
|
|
void setBusyCursor(bool on);
|
|
void cleanupViews();
|
|
void updateState();
|
|
void updateReverseActions();
|
|
|
|
DebuggerEngine *m_engine = nullptr; // Not owned.
|
|
QString m_runId;
|
|
QString m_debuggerName;
|
|
QPointer<Perspective> m_perspective;
|
|
DebuggerRunParameters m_runParameters;
|
|
IDevice::ConstPtr m_device;
|
|
|
|
QPointer<DebuggerEngine> m_companionEngine;
|
|
bool m_isPrimaryEngine = true;
|
|
|
|
// The current state.
|
|
DebuggerState m_state = DebuggerNotReady;
|
|
|
|
// Terminal m_terminal;
|
|
ProcessHandle m_inferiorPid;
|
|
|
|
BreakHandler m_breakHandler;
|
|
ModulesHandler m_modulesHandler;
|
|
RegisterHandler m_registerHandler;
|
|
PeripheralRegisterHandler m_peripheralRegisterHandler;
|
|
SourceFilesHandler m_sourceFilesHandler;
|
|
StackHandler m_stackHandler;
|
|
ThreadsHandler m_threadsHandler;
|
|
WatchHandler m_watchHandler;
|
|
QFutureInterface<void> m_progress;
|
|
|
|
DisassemblerAgent m_disassemblerAgent;
|
|
MemoryAgentSet m_memoryAgents;
|
|
QScopedPointer<LocationMark> m_locationMark;
|
|
QTimer m_locationTimer;
|
|
|
|
QString m_qtNamespace;
|
|
|
|
// Safety net to avoid infinite lookups.
|
|
QSet<QString> m_lookupRequests; // FIXME: Integrate properly.
|
|
QPointer<QWidget> m_alertBox;
|
|
|
|
QPointer<BaseTreeView> m_breakView;
|
|
QPointer<BaseTreeView> m_returnView;
|
|
QPointer<BaseTreeView> m_localsView;
|
|
QPointer<BaseTreeView> m_watchersView;
|
|
QPointer<WatchTreeView> m_inspectorView;
|
|
QPointer<BaseTreeView> m_registerView;
|
|
QPointer<BaseTreeView> m_peripheralRegisterView;
|
|
QPointer<BaseTreeView> m_modulesView;
|
|
QPointer<BaseTreeView> m_sourceFilesView;
|
|
QPointer<BaseTreeView> m_stackView;
|
|
QPointer<BaseTreeView> m_threadsView;
|
|
QPointer<QWidget> m_breakWindow;
|
|
QPointer<QWidget> m_returnWindow;
|
|
QPointer<QWidget> m_localsWindow;
|
|
QPointer<QWidget> m_watchersWindow;
|
|
QPointer<QWidget> m_inspectorWindow;
|
|
QPointer<QWidget> m_registerWindow;
|
|
QPointer<QWidget> m_peripheralRegisterWindow;
|
|
QPointer<QWidget> m_modulesWindow;
|
|
QPointer<QWidget> m_sourceFilesWindow;
|
|
QPointer<QWidget> m_stackWindow;
|
|
QPointer<QWidget> m_threadsWindow;
|
|
QPointer<LogWindow> m_logWindow;
|
|
QPointer<LocalsAndInspectorWindow> m_localsAndInspectorWindow;
|
|
|
|
QPointer<QLabel> m_threadLabel;
|
|
|
|
bool m_busy = false;
|
|
bool m_isDying = false;
|
|
|
|
QAction m_detachAction;
|
|
OptionalAction m_continueAction{Tr::tr("Continue")};
|
|
QAction m_exitAction{Tr::tr("Stop Debugger")}; // On application output button if "Stop" is possible
|
|
OptionalAction m_interruptAction{Tr::tr("Interrupt")}; // On the fat debug button if "Pause" is possible
|
|
QAction m_abortAction{Tr::tr("Abort Debugging")};
|
|
QAction m_stepIntoAction{Tr::tr("Step Into")};
|
|
QAction m_stepOutAction{Tr::tr("Step Out")};
|
|
QAction m_runToLineAction{Tr::tr("Run to Line")}; // In the debug menu
|
|
QAction m_runToSelectedFunctionAction{Tr::tr("Run to Selected Function")};
|
|
QAction m_jumpToLineAction{Tr::tr("Jump to Line")};
|
|
QAction m_frameUpAction{Tr::tr("Move to Calling Frame")};
|
|
QAction m_frameDownAction{Tr::tr("Move to Called Frame")};
|
|
QAction m_openMemoryEditorAction{Tr::tr("Memory...")};
|
|
// In the Debug menu.
|
|
QAction m_returnFromFunctionAction{Tr::tr("Immediately Return From Inner Function")};
|
|
QAction m_stepOverAction{Tr::tr("Step Over")};
|
|
QAction m_watchAction{Tr::tr("Add Expression Evaluator")};
|
|
QAction m_setOrRemoveBreakpointAction{Tr::tr("Set or Remove Breakpoint")};
|
|
QAction m_enableOrDisableBreakpointAction{Tr::tr("Enable or Disable Breakpoint")};
|
|
QAction m_resetAction{Tr::tr("Restart Debugging")};
|
|
OptionalAction m_operateByInstructionAction{Tr::tr("Operate by Instruction")};
|
|
QAction m_recordForReverseOperationAction{Tr::tr("Record Information to Allow Reversal of Direction")};
|
|
OptionalAction m_operateInReverseDirectionAction{Tr::tr("Reverse Direction")};
|
|
OptionalAction m_snapshotAction{Tr::tr("Take Snapshot of Process State")};
|
|
|
|
QPointer<TerminalRunner> m_terminalRunner;
|
|
DebuggerToolTipManager m_toolTipManager;
|
|
Context m_context;
|
|
};
|
|
|
|
void DebuggerEnginePrivate::setupViews()
|
|
{
|
|
const DebuggerRunParameters &rp = m_runParameters;
|
|
const QString engineId = EngineManager::registerEngine(m_engine);
|
|
|
|
QTC_CHECK(!m_perspective);
|
|
|
|
const QString perspectiveId = "Debugger.Perspective." + m_runId + '.' + m_debuggerName;
|
|
const QString settingsId = "Debugger.Perspective." + m_debuggerName;
|
|
|
|
m_perspective = new Perspective(perspectiveId,
|
|
m_engine->displayName(),
|
|
Debugger::Constants::PRESET_PERSPECTIVE_ID,
|
|
settingsId);
|
|
|
|
m_progress.setProgressRange(0, 1000);
|
|
FutureProgress *fp = ProgressManager::addTask(m_progress.future(),
|
|
Tr::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(false);
|
|
m_operateByInstructionAction.setToolTip("<p>" + Tr::tr("Switches the debugger to instruction-wise "
|
|
"operation mode. In this mode, stepping operates on single "
|
|
"instructions and the source location view also shows the "
|
|
"disassembled instructions."));
|
|
m_operateByInstructionAction.setIconVisibleInMenu(false);
|
|
connect(&m_operateByInstructionAction, &QAction::triggered,
|
|
m_engine, &DebuggerEngine::operateByInstructionTriggered);
|
|
|
|
m_frameDownAction.setEnabled(true);
|
|
connect(&m_frameDownAction, &QAction::triggered,
|
|
m_engine, &DebuggerEngine::handleFrameDown);
|
|
|
|
m_frameUpAction.setEnabled(true);
|
|
connect(&m_frameUpAction, &QAction::triggered,
|
|
m_engine, &DebuggerEngine::handleFrameUp);
|
|
|
|
m_openMemoryEditorAction.setEnabled(true);
|
|
m_openMemoryEditorAction.setVisible(m_engine->hasCapability(ShowMemoryCapability));
|
|
connect(&m_openMemoryEditorAction, &QAction::triggered,
|
|
m_engine, &DebuggerEngine::openMemoryEditor);
|
|
|
|
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();
|
|
|
|
m_modulesView = new BaseTreeView;
|
|
m_modulesView->setModel(m_modulesHandler.model());
|
|
m_modulesView->setSortingEnabled(true);
|
|
m_modulesView->setSettings(settings, "Debugger.ModulesView");
|
|
m_modulesView->enableColumnHiding();
|
|
connect(m_modulesView, &BaseTreeView::aboutToShow,
|
|
m_engine, &DebuggerEngine::reloadModules,
|
|
Qt::QueuedConnection);
|
|
m_modulesWindow = addSearch(m_modulesView);
|
|
m_modulesWindow->setObjectName("Debugger.Dock.Modules." + engineId);
|
|
m_modulesWindow->setWindowTitle(Tr::tr("&Modules"));
|
|
|
|
m_registerView = new BaseTreeView;
|
|
m_registerView->setModel(m_registerHandler.model());
|
|
m_registerView->setRootIsDecorated(true);
|
|
m_registerView->setSettings(settings, "Debugger.RegisterView");
|
|
m_registerView->enableColumnHiding();
|
|
connect(m_registerView, &BaseTreeView::aboutToShow,
|
|
m_engine, &DebuggerEngine::reloadRegisters,
|
|
Qt::QueuedConnection);
|
|
m_registerWindow = addSearch(m_registerView);
|
|
m_registerWindow->setObjectName("Debugger.Dock.Register." + engineId);
|
|
m_registerWindow->setWindowTitle(Tr::tr("Reg&isters"));
|
|
|
|
m_peripheralRegisterView = new BaseTreeView;
|
|
m_peripheralRegisterView->setModel(m_peripheralRegisterHandler.model());
|
|
m_peripheralRegisterView->setRootIsDecorated(true);
|
|
m_peripheralRegisterView->setSettings(settings, "Debugger.PeripheralRegisterView");
|
|
m_peripheralRegisterView->enableColumnHiding();
|
|
connect(m_peripheralRegisterView, &BaseTreeView::aboutToShow,
|
|
m_engine, &DebuggerEngine::reloadPeripheralRegisters,
|
|
Qt::QueuedConnection);
|
|
m_peripheralRegisterWindow = addSearch(m_peripheralRegisterView);
|
|
m_peripheralRegisterWindow->setObjectName("Debugger.Dock.PeripheralRegister." + engineId);
|
|
m_peripheralRegisterWindow->setWindowTitle(Tr::tr("Peripheral Reg&isters"));
|
|
|
|
m_stackView = new StackTreeView;
|
|
m_stackView->setModel(m_stackHandler.model());
|
|
m_stackView->setSettings(settings, "Debugger.StackView");
|
|
m_stackView->setIconSize(QSize(10, 10));
|
|
m_stackView->enableColumnHiding();
|
|
m_stackWindow = addSearch(m_stackView);
|
|
m_stackWindow->setObjectName("Debugger.Dock.Stack." + engineId);
|
|
m_stackWindow->setWindowTitle(Tr::tr("&Stack"));
|
|
|
|
m_sourceFilesView = new BaseTreeView;
|
|
m_sourceFilesView->setModel(m_sourceFilesHandler.model());
|
|
m_sourceFilesView->setSortingEnabled(true);
|
|
m_sourceFilesView->setSettings(settings, "Debugger.SourceFilesView");
|
|
m_sourceFilesView->enableColumnHiding();
|
|
connect(m_sourceFilesView, &BaseTreeView::aboutToShow,
|
|
m_engine, &DebuggerEngine::reloadSourceFiles,
|
|
Qt::QueuedConnection);
|
|
m_sourceFilesWindow = addSearch(m_sourceFilesView);
|
|
m_sourceFilesWindow->setObjectName("Debugger.Dock.SourceFiles." + engineId);
|
|
m_sourceFilesWindow->setWindowTitle(Tr::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_threadsView->setSpanColumn(ThreadData::FunctionColumn);
|
|
m_threadsView->enableColumnHiding();
|
|
m_threadsWindow = addSearch(m_threadsView);
|
|
m_threadsWindow->setObjectName("Debugger.Dock.Threads." + engineId);
|
|
m_threadsWindow->setWindowTitle(Tr::tr("&Threads"));
|
|
|
|
m_returnView = new WatchTreeView{ReturnType};
|
|
m_returnView->setModel(m_watchHandler.model());
|
|
m_returnWindow = addSearch(m_returnView);
|
|
m_returnWindow->setObjectName("CppDebugReturn");
|
|
m_returnWindow->setWindowTitle(Tr::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("Debugger.Dock.Locals." + engineId);
|
|
m_localsWindow->setWindowTitle(Tr::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("Debugger.Dock.Inspector." + engineId);
|
|
m_inspectorWindow->setWindowTitle(Tr::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("Debugger.Dock.Watchers." + engineId);
|
|
m_watchersWindow->setWindowTitle(Tr::tr("&Expressions"));
|
|
|
|
m_localsAndInspectorWindow = new LocalsAndInspectorWindow(
|
|
m_localsWindow, m_inspectorWindow, m_returnWindow);
|
|
m_localsAndInspectorWindow->setObjectName("Debugger.Dock.LocalsAndInspector." + engineId);
|
|
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);
|
|
m_breakView->setSpanColumn(BreakpointFunctionColumn);
|
|
m_breakView->setSettings(settings, "Debugger.BreakWindow");
|
|
m_breakView->setModel(m_breakHandler.model());
|
|
m_breakView->setRootIsDecorated(true);
|
|
m_breakView->enableColumnHiding();
|
|
m_breakWindow = addSearch(m_breakView);
|
|
m_breakWindow->setObjectName("Debugger.Dock.Break." + engineId);
|
|
m_breakWindow->setWindowTitle(Tr::tr("&Breakpoints"));
|
|
|
|
m_perspective->useSubPerspectiveSwitcher(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);
|
|
|
|
connect(&m_detachAction, &QAction::triggered, m_engine, &DebuggerEngine::handleExecDetach);
|
|
|
|
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_TOOLBAR.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::tr("Aborts debugging and resets the debugger to the initial state."));
|
|
connect(&m_abortAction, &QAction::triggered,
|
|
m_engine, &DebuggerEngine::abortDebugger);
|
|
|
|
m_resetAction.setToolTip(Tr::tr("Restarts 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::handleExecStepOver);
|
|
|
|
m_stepIntoAction.setIcon(Icons::STEP_INTO_TOOLBAR.icon());
|
|
connect(&m_stepIntoAction, &QAction::triggered,
|
|
m_engine, &DebuggerEngine::handleExecStepIn);
|
|
|
|
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);
|
|
|
|
connect(&m_watchAction, &QAction::triggered,
|
|
m_engine, &DebuggerEngine::handleAddToWatchWindow);
|
|
|
|
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::tr("Threads:"));
|
|
m_perspective->addToolBarWidget(m_threadLabel);
|
|
m_perspective->addToolBarWidget(m_threadsHandler.threadSwitcher());
|
|
|
|
connect(TextEditorSettings::instance(), &TextEditorSettings::fontSettingsChanged,
|
|
this, [this](const FontSettings &settings) {
|
|
if (!debuggerSettings()->fontSizeFollowsEditor.value())
|
|
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_peripheralRegisterWindow->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->addWindow(m_stackWindow, Perspective::SplitVertical, nullptr);
|
|
m_perspective->addWindow(m_breakWindow, Perspective::SplitHorizontal, m_stackWindow);
|
|
m_perspective->addWindow(m_threadsWindow, Perspective::AddToTab, m_breakWindow);
|
|
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::SplitVertical, m_localsAndInspectorWindow, true, Qt::RightDockWidgetArea);
|
|
m_perspective->addWindow(m_registerWindow, Perspective::AddToTab, m_localsAndInspectorWindow, false, Qt::RightDockWidgetArea);
|
|
m_perspective->addWindow(m_peripheralRegisterWindow, Perspective::AddToTab, m_localsAndInspectorWindow, false, Qt::RightDockWidgetArea);
|
|
m_perspective->addWindow(m_logWindow, Perspective::AddToTab, nullptr, false, Qt::TopDockWidgetArea);
|
|
|
|
m_perspective->select();
|
|
m_watchHandler.loadSessionDataForEngine();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
// DebuggerEngine
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
DebuggerEngine::DebuggerEngine()
|
|
: d(new DebuggerEnginePrivate(this))
|
|
{
|
|
}
|
|
|
|
DebuggerEngine::~DebuggerEngine()
|
|
{
|
|
// EngineManager::unregisterEngine(this);
|
|
delete d;
|
|
}
|
|
|
|
void DebuggerEngine::setDebuggerName(const QString &name)
|
|
{
|
|
d->m_debuggerName = name;
|
|
d->updateActionToolTips();
|
|
}
|
|
|
|
QString DebuggerEngine::debuggerName() const
|
|
{
|
|
return d->m_debuggerName;
|
|
}
|
|
|
|
QString DebuggerEngine::stateName(int s)
|
|
{
|
|
# define SN(x) case x: return QLatin1String(#x);
|
|
switch (s) {
|
|
SN(DebuggerNotReady)
|
|
SN(EngineSetupRequested)
|
|
SN(EngineSetupFailed)
|
|
SN(EngineRunFailed)
|
|
SN(EngineRunRequested)
|
|
SN(InferiorRunRequested)
|
|
SN(InferiorRunOk)
|
|
SN(InferiorRunFailed)
|
|
SN(InferiorUnrunnable)
|
|
SN(InferiorStopRequested)
|
|
SN(InferiorStopOk)
|
|
SN(InferiorStopFailed)
|
|
SN(InferiorShutdownRequested)
|
|
SN(InferiorShutdownFinished)
|
|
SN(EngineShutdownRequested)
|
|
SN(EngineShutdownFinished)
|
|
SN(DebuggerFinished)
|
|
}
|
|
return QLatin1String("<unknown>");
|
|
# undef SN
|
|
}
|
|
|
|
void DebuggerEngine::notifyExitCode(int code)
|
|
{
|
|
d->m_runParameters.exitCode = code;
|
|
}
|
|
|
|
void DebuggerEngine::showStatusMessage(const QString &msg, int timeout) const
|
|
{
|
|
showMessage(msg, StatusBar, timeout);
|
|
}
|
|
|
|
void DebuggerEngine::updateLocalsWindow(bool showReturn)
|
|
{
|
|
QTC_ASSERT(d->m_returnWindow, return);
|
|
QTC_ASSERT(d->m_localsView, return);
|
|
d->m_returnWindow->setVisible(showReturn);
|
|
d->m_localsView->resizeColumns();
|
|
}
|
|
|
|
bool DebuggerEngine::isRegistersWindowVisible() const
|
|
{
|
|
QTC_ASSERT(d->m_registerWindow, return false);
|
|
return d->m_registerWindow->isVisible();
|
|
}
|
|
|
|
bool DebuggerEngine::isPeripheralRegistersWindowVisible() const
|
|
{
|
|
QTC_ASSERT(d->m_peripheralRegisterWindow, return false);
|
|
return d->m_peripheralRegisterWindow->isVisible();
|
|
}
|
|
|
|
bool DebuggerEngine::isModulesWindowVisible() const
|
|
{
|
|
QTC_ASSERT(d->m_modulesWindow, return false);
|
|
return d->m_modulesWindow->isVisible();
|
|
}
|
|
|
|
void DebuggerEngine::frameUp()
|
|
{
|
|
int currentIndex = stackHandler()->currentIndex();
|
|
activateFrame(qMin(currentIndex + 1, stackHandler()->stackSize() - 1));
|
|
}
|
|
|
|
void DebuggerEngine::frameDown()
|
|
{
|
|
int currentIndex = stackHandler()->currentIndex();
|
|
activateFrame(qMax(currentIndex - 1, 0));
|
|
}
|
|
|
|
void DebuggerEngine::doUpdateLocals(const UpdateParameters &)
|
|
{
|
|
}
|
|
|
|
ModulesHandler *DebuggerEngine::modulesHandler() const
|
|
{
|
|
return &d->m_modulesHandler;
|
|
}
|
|
|
|
RegisterHandler *DebuggerEngine::registerHandler() const
|
|
{
|
|
return &d->m_registerHandler;
|
|
}
|
|
|
|
PeripheralRegisterHandler *DebuggerEngine::peripheralRegisterHandler() const
|
|
{
|
|
return &d->m_peripheralRegisterHandler;
|
|
}
|
|
|
|
StackHandler *DebuggerEngine::stackHandler() const
|
|
{
|
|
return &d->m_stackHandler;
|
|
}
|
|
|
|
ThreadsHandler *DebuggerEngine::threadsHandler() const
|
|
{
|
|
return &d->m_threadsHandler;
|
|
}
|
|
|
|
WatchHandler *DebuggerEngine::watchHandler() const
|
|
{
|
|
return &d->m_watchHandler;
|
|
}
|
|
|
|
SourceFilesHandler *DebuggerEngine::sourceFilesHandler() const
|
|
{
|
|
return &d->m_sourceFilesHandler;
|
|
}
|
|
|
|
BreakHandler *DebuggerEngine::breakHandler() const
|
|
{
|
|
return &d->m_breakHandler;
|
|
}
|
|
|
|
LogWindow *DebuggerEngine::logWindow() const
|
|
{
|
|
return d->m_logWindow;
|
|
}
|
|
|
|
DisassemblerAgent *DebuggerEngine::disassemblerAgent() const
|
|
{
|
|
return &d->m_disassemblerAgent;
|
|
}
|
|
|
|
void DebuggerEngine::fetchMemory(MemoryAgent *, quint64 addr, quint64 length)
|
|
{
|
|
Q_UNUSED(addr)
|
|
Q_UNUSED(length)
|
|
}
|
|
|
|
void DebuggerEngine::changeMemory(MemoryAgent *, quint64 addr, const QByteArray &data)
|
|
{
|
|
Q_UNUSED(addr)
|
|
Q_UNUSED(data)
|
|
}
|
|
|
|
void DebuggerEngine::setRegisterValue(const QString &name, const QString &value)
|
|
{
|
|
Q_UNUSED(name)
|
|
Q_UNUSED(value)
|
|
}
|
|
|
|
void DebuggerEngine::setPeripheralRegisterValue(quint64 address, quint64 value)
|
|
{
|
|
Q_UNUSED(address)
|
|
Q_UNUSED(value)
|
|
}
|
|
|
|
void DebuggerEngine::setRunParameters(const DebuggerRunParameters &runParameters)
|
|
{
|
|
d->m_runParameters = runParameters;
|
|
d->updateActionToolTips();
|
|
}
|
|
|
|
void DebuggerEngine::setRunId(const QString &id)
|
|
{
|
|
d->m_runId = id;
|
|
}
|
|
|
|
void DebuggerEngine::setRunTool(DebuggerRunTool *runTool)
|
|
{
|
|
d->m_device = runTool->device();
|
|
|
|
IDevice::ConstPtr debuggerDevice =
|
|
DeviceManager::deviceForPath(d->m_runParameters.debugger.command.executable());
|
|
if (QTC_GUARD(debuggerDevice))
|
|
d->m_runParameters.dumperPath = debuggerDevice->debugDumperPath();
|
|
|
|
d->m_terminalRunner = runTool->terminalRunner();
|
|
|
|
validateRunParameters(d->m_runParameters);
|
|
|
|
d->setupViews();
|
|
}
|
|
|
|
void DebuggerEngine::start()
|
|
{
|
|
d->m_watchHandler.resetWatchers();
|
|
d->setInitialActionStates();
|
|
setState(EngineSetupRequested);
|
|
showMessage("CALL: SETUP ENGINE");
|
|
setupEngine();
|
|
}
|
|
|
|
void DebuggerEngine::resetLocation()
|
|
{
|
|
// Do it after some delay to avoid flicker.
|
|
d->scheduleResetLocation();
|
|
}
|
|
|
|
void DebuggerEngine::gotoLocation(const Location &loc)
|
|
{
|
|
d->resetLocation();
|
|
|
|
if (loc.canBeDisassembled()
|
|
&& ((hasCapability(OperateByInstructionCapability) && operatesByInstruction())
|
|
|| !loc.hasDebugInfo()) )
|
|
{
|
|
d->m_disassemblerAgent.setLocation(loc);
|
|
return;
|
|
}
|
|
|
|
if (loc.fileName().isEmpty()) {
|
|
showMessage("CANNOT GO TO THIS LOCATION");
|
|
return;
|
|
}
|
|
const FilePath file = loc.fileName();
|
|
const int line = loc.lineNumber();
|
|
bool newEditor = false;
|
|
IEditor *editor = EditorManager::openEditor(file,
|
|
Id(),
|
|
EditorManager::IgnoreNavigationHistory
|
|
| EditorManager::DoNotSwitchToDesignMode
|
|
| EditorManager::SwitchSplitIfAlreadyVisible,
|
|
&newEditor);
|
|
QTC_ASSERT(editor, return); // Unreadable file?
|
|
|
|
editor->gotoLine(line, 0, !debuggerSettings()->stationaryEditorWhileStepping.value());
|
|
|
|
if (newEditor)
|
|
editor->document()->setProperty(Constants::OPENED_BY_DEBUGGER, true);
|
|
|
|
if (loc.needsMarker()) {
|
|
d->m_locationMark.reset(new LocationMark(this, loc.fileName(), line));
|
|
d->m_locationMark->setToolTip(Tr::tr("Current debugger location of %1").arg(displayName()));
|
|
}
|
|
|
|
d->m_breakHandler.setLocation(loc);
|
|
d->m_watchHandler.setLocation(loc);
|
|
}
|
|
|
|
void DebuggerEngine::gotoCurrentLocation()
|
|
{
|
|
if (d->m_state == InferiorStopOk || d->m_state == InferiorUnrunnable) {
|
|
int top = stackHandler()->currentIndex();
|
|
if (top >= 0)
|
|
gotoLocation(stackHandler()->currentFrame());
|
|
}
|
|
}
|
|
|
|
const DebuggerRunParameters &DebuggerEngine::runParameters() 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
|
|
{
|
|
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();
|
|
}
|
|
}
|
|
|
|
void DebuggerEngine::updateUi(bool isCurrentEngine)
|
|
{
|
|
updateState();
|
|
if (isCurrentEngine) {
|
|
gotoCurrentLocation();
|
|
} else {
|
|
d->m_locationMark.reset();
|
|
d->m_disassemblerAgent.resetLocation();
|
|
}
|
|
}
|
|
|
|
static bool isAllowedTransition(DebuggerState from, DebuggerState to)
|
|
{
|
|
switch (from) {
|
|
case DebuggerNotReady:
|
|
return to == EngineSetupRequested;
|
|
|
|
case EngineSetupRequested:
|
|
return to == EngineRunRequested
|
|
|| to == EngineSetupFailed
|
|
|| to == EngineShutdownRequested;
|
|
case EngineSetupFailed:
|
|
// In is the engine's task to go into a proper "Shutdown"
|
|
// state before calling notifyEngineSetupFailed
|
|
return to == DebuggerFinished;
|
|
|
|
case EngineRunRequested:
|
|
return to == EngineRunFailed
|
|
|| to == InferiorRunRequested
|
|
|| to == InferiorRunOk
|
|
|| to == InferiorStopOk
|
|
|| to == InferiorUnrunnable;
|
|
case EngineRunFailed:
|
|
return to == EngineShutdownRequested;
|
|
|
|
case InferiorRunRequested:
|
|
return to == InferiorRunOk || to == InferiorRunFailed;
|
|
case InferiorRunFailed:
|
|
return to == InferiorStopOk;
|
|
case InferiorRunOk:
|
|
return to == InferiorStopRequested
|
|
|| to == InferiorStopOk // A spontaneous stop.
|
|
|| to == InferiorShutdownFinished; // A spontaneous exit.
|
|
|
|
case InferiorStopRequested:
|
|
return to == InferiorStopOk || to == InferiorStopFailed;
|
|
case InferiorStopOk:
|
|
return to == InferiorRunRequested || to == InferiorShutdownRequested
|
|
|| to == InferiorStopOk || to == InferiorShutdownFinished;
|
|
case InferiorStopFailed:
|
|
return to == EngineShutdownRequested;
|
|
|
|
case InferiorUnrunnable:
|
|
return to == InferiorShutdownRequested;
|
|
case InferiorShutdownRequested:
|
|
return to == InferiorShutdownFinished;
|
|
case InferiorShutdownFinished:
|
|
return to == EngineShutdownRequested;
|
|
|
|
case EngineShutdownRequested:
|
|
return to == EngineShutdownFinished;
|
|
case EngineShutdownFinished:
|
|
return to == DebuggerFinished;
|
|
|
|
case DebuggerFinished:
|
|
return to == EngineSetupRequested; // Happens on restart.
|
|
}
|
|
|
|
qDebug() << "UNKNOWN DEBUGGER STATE:" << from;
|
|
return false;
|
|
}
|
|
|
|
void DebuggerEngine::notifyEngineSetupFailed()
|
|
{
|
|
showMessage("NOTE: ENGINE SETUP FAILED");
|
|
QTC_ASSERT(state() == EngineSetupRequested, qDebug() << this << state());
|
|
setState(EngineSetupFailed);
|
|
if (d->m_isPrimaryEngine) {
|
|
showMessage(Tr::tr("Debugging has failed."), NormalMessageFormat);
|
|
d->m_progress.setProgressValue(900);
|
|
d->m_progress.reportCanceled();
|
|
d->m_progress.reportFinished();
|
|
}
|
|
|
|
setState(DebuggerFinished);
|
|
}
|
|
|
|
void DebuggerEngine::notifyEngineSetupOk()
|
|
{
|
|
//#ifdef WITH_BENCHMARK
|
|
// CALLGRIND_START_INSTRUMENTATION;
|
|
//#endif
|
|
showMessage("NOTE: ENGINE SETUP OK");
|
|
QTC_ASSERT(state() == EngineSetupRequested, qDebug() << this << state());
|
|
setState(EngineRunRequested);
|
|
showMessage("CALL: RUN ENGINE");
|
|
d->m_progress.setProgressValue(300);
|
|
}
|
|
|
|
void DebuggerEngine::notifyEngineRunOkAndInferiorUnrunnable()
|
|
{
|
|
showMessage("NOTE: INFERIOR UNRUNNABLE");
|
|
d->m_progress.setProgressValue(1000);
|
|
d->m_progress.reportFinished();
|
|
QTC_ASSERT(state() == EngineRunRequested, qDebug() << this << state());
|
|
showStatusMessage(Tr::tr("Loading finished."));
|
|
setState(InferiorUnrunnable);
|
|
}
|
|
|
|
void DebuggerEngine::notifyEngineRunFailed()
|
|
{
|
|
showMessage("NOTE: ENGINE RUN FAILED");
|
|
QTC_ASSERT(state() == EngineRunRequested, qDebug() << this << state());
|
|
d->m_progress.setProgressValue(900);
|
|
d->m_progress.reportCanceled();
|
|
d->m_progress.reportFinished();
|
|
showStatusMessage(Tr::tr("Run failed."));
|
|
setState(EngineRunFailed);
|
|
d->doShutdownEngine();
|
|
}
|
|
|
|
void DebuggerEngine::notifyEngineRunAndInferiorRunOk()
|
|
{
|
|
showMessage("NOTE: ENGINE RUN AND INFERIOR RUN OK");
|
|
d->m_progress.setProgressValue(1000);
|
|
d->m_progress.reportFinished();
|
|
QTC_ASSERT(state() == EngineRunRequested, qDebug() << this << state());
|
|
showStatusMessage(Tr::tr("Running."));
|
|
setState(InferiorRunOk);
|
|
}
|
|
|
|
void DebuggerEngine::notifyEngineRunAndInferiorStopOk()
|
|
{
|
|
showMessage("NOTE: ENGINE RUN AND INFERIOR STOP OK");
|
|
d->m_progress.setProgressValue(1000);
|
|
d->m_progress.reportFinished();
|
|
QTC_ASSERT(state() == EngineRunRequested, qDebug() << this << state());
|
|
showStatusMessage(Tr::tr("Stopped."));
|
|
setState(InferiorStopOk);
|
|
}
|
|
|
|
void DebuggerEngine::notifyInferiorRunRequested()
|
|
{
|
|
showMessage("NOTE: INFERIOR RUN REQUESTED");
|
|
QTC_ASSERT(state() == InferiorStopOk, qDebug() << this << state());
|
|
showStatusMessage(Tr::tr("Run requested..."));
|
|
setState(InferiorRunRequested);
|
|
}
|
|
|
|
void DebuggerEngine::notifyInferiorRunOk()
|
|
{
|
|
if (state() == InferiorRunOk) {
|
|
showMessage("NOTE: INFERIOR RUN OK - REPEATED.");
|
|
return;
|
|
}
|
|
showMessage("NOTE: INFERIOR RUN OK");
|
|
showStatusMessage(Tr::tr("Running."));
|
|
// Transition from StopRequested can happen in remotegdbadapter.
|
|
QTC_ASSERT(state() == InferiorRunRequested
|
|
|| state() == InferiorStopOk
|
|
|| state() == InferiorStopRequested, qDebug() << this << state());
|
|
setState(InferiorRunOk);
|
|
}
|
|
|
|
void DebuggerEngine::notifyInferiorRunFailed()
|
|
{
|
|
showMessage("NOTE: INFERIOR RUN FAILED");
|
|
QTC_ASSERT(state() == InferiorRunRequested, qDebug() << this << state());
|
|
setState(InferiorRunFailed);
|
|
setState(InferiorStopOk);
|
|
if (isDying())
|
|
d->doShutdownInferior();
|
|
}
|
|
|
|
void DebuggerEngine::notifyInferiorStopOk()
|
|
{
|
|
showMessage("NOTE: INFERIOR STOP OK");
|
|
// Ignore spurious notifications after we are set to die.
|
|
if (isDying()) {
|
|
showMessage("NOTE: ... WHILE DYING. ");
|
|
// Forward state to "StopOk" if needed.
|
|
if (state() == InferiorStopRequested
|
|
|| state() == InferiorRunRequested
|
|
|| state() == InferiorRunOk) {
|
|
showMessage("NOTE: ... FORWARDING TO 'STOP OK'. ");
|
|
setState(InferiorStopOk);
|
|
}
|
|
if (state() == InferiorStopOk || state() == InferiorStopFailed)
|
|
d->doShutdownInferior();
|
|
showMessage("NOTE: ... IGNORING STOP MESSAGE");
|
|
return;
|
|
}
|
|
QTC_ASSERT(state() == InferiorStopRequested, qDebug() << this << state());
|
|
showMessage(Tr::tr("Stopped."), StatusBar);
|
|
setState(InferiorStopOk);
|
|
}
|
|
|
|
void DebuggerEngine::notifyInferiorSpontaneousStop()
|
|
{
|
|
showMessage("NOTE: INFERIOR SPONTANEOUS STOP");
|
|
QTC_ASSERT(state() == InferiorRunOk, qDebug() << this << state());
|
|
if (QTC_GUARD(d->m_perspective))
|
|
d->m_perspective->select();
|
|
showMessage(Tr::tr("Stopped."), StatusBar);
|
|
setState(InferiorStopOk);
|
|
if (debuggerSettings()->raiseOnInterrupt.value())
|
|
ICore::raiseWindow(DebuggerMainWindow::instance());
|
|
}
|
|
|
|
void DebuggerEngine::notifyInferiorStopFailed()
|
|
{
|
|
showMessage("NOTE: INFERIOR STOP FAILED");
|
|
QTC_ASSERT(state() == InferiorStopRequested, qDebug() << this << state());
|
|
setState(InferiorStopFailed);
|
|
d->doShutdownEngine();
|
|
}
|
|
|
|
void DebuggerEnginePrivate::setInitialActionStates()
|
|
{
|
|
m_returnWindow->setVisible(false);
|
|
setBusyCursor(false);
|
|
|
|
m_recordForReverseOperationAction.setCheckable(true);
|
|
m_recordForReverseOperationAction.setChecked(false);
|
|
m_recordForReverseOperationAction.setIcon(Icons::RECORD_OFF.icon());
|
|
m_recordForReverseOperationAction.setToolTip(QString("<html><head/><body><p>%1</p><p>"
|
|
"<b>%2</b>%3</p></body></html>").arg(
|
|
Tr::tr("Record information to enable stepping backwards."),
|
|
Tr::tr("Note: "),
|
|
Tr::tr("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_detachAction.setEnabled(false);
|
|
|
|
m_watchAction.setEnabled(true);
|
|
m_setOrRemoveBreakpointAction.setEnabled(false);
|
|
m_enableOrDisableBreakpointAction.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_runToLineAction.setVisible(false);
|
|
m_runToSelectedFunctionAction.setEnabled(true);
|
|
m_returnFromFunctionAction.setEnabled(false);
|
|
m_jumpToLineAction.setEnabled(false);
|
|
m_jumpToLineAction.setVisible(false);
|
|
m_stepOverAction.setEnabled(true);
|
|
|
|
debuggerSettings()->autoDerefPointers.setEnabled(true);
|
|
debuggerSettings()->expandStack.setEnabled(false);
|
|
|
|
m_threadLabel->setEnabled(false);
|
|
}
|
|
|
|
void DebuggerEnginePrivate::updateState()
|
|
{
|
|
// Can happen in mixed debugging.
|
|
if (!m_threadLabel)
|
|
return;
|
|
QTC_ASSERT(m_threadLabel, return);
|
|
|
|
const DebuggerState state = m_state;
|
|
const bool companionPreventsAction = m_engine->companionPreventsActions();
|
|
|
|
// Fixme: hint Tr::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);
|
|
} 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_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_localsAndInspectorWindow->setShowLocals(false);
|
|
} else if (state == DebuggerFinished) {
|
|
// 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);
|
|
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);
|
|
// 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);
|
|
}
|
|
|
|
const bool threadsEnabled = state == InferiorStopOk || state == InferiorUnrunnable;
|
|
m_threadsHandler.threadSwitcher()->setEnabled(threadsEnabled);
|
|
m_threadLabel->setEnabled(threadsEnabled);
|
|
|
|
const bool isCore = m_engine->runParameters().startMode == AttachToCore;
|
|
const bool stopped = state == InferiorStopOk;
|
|
const bool detachable = stopped && !isCore;
|
|
m_detachAction.setEnabled(detachable);
|
|
|
|
updateReverseActions();
|
|
|
|
const bool canSnapshot = m_engine->hasCapability(SnapshotCapability);
|
|
m_snapshotAction.setVisible(canSnapshot);
|
|
m_snapshotAction.setEnabled(stopped && !isCore);
|
|
|
|
m_watchAction.setEnabled(true);
|
|
m_setOrRemoveBreakpointAction.setEnabled(true);
|
|
m_enableOrDisableBreakpointAction.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);
|
|
|
|
const bool canRunToLine = m_engine->hasCapability(RunToLineCapability);
|
|
m_runToLineAction.setVisible(canRunToLine);
|
|
m_runToLineAction.setEnabled(stopped && canRunToLine);
|
|
|
|
m_runToSelectedFunctionAction.setEnabled(stopped);
|
|
|
|
const bool canReturnFromFunction = m_engine->hasCapability(ReturnFromFunctionCapability);
|
|
m_returnFromFunctionAction.setVisible(canReturnFromFunction);
|
|
m_returnFromFunctionAction.setEnabled(stopped && canReturnFromFunction);
|
|
|
|
const bool canJump = m_engine->hasCapability(JumpToLineCapability);
|
|
m_jumpToLineAction.setVisible(canJump);
|
|
m_jumpToLineAction.setEnabled(stopped && canJump);
|
|
|
|
const bool actionsEnabled = m_engine->debuggerActionsEnabled();
|
|
const bool canDeref = actionsEnabled && m_engine->hasCapability(AutoDerefPointersCapability);
|
|
debuggerSettings()->autoDerefPointers.setEnabled(canDeref);
|
|
debuggerSettings()->autoDerefPointers.setEnabled(true);
|
|
debuggerSettings()->expandStack.setEnabled(actionsEnabled);
|
|
|
|
const bool notbusy = state == InferiorStopOk
|
|
|| state == DebuggerNotReady
|
|
|| state == DebuggerFinished
|
|
|| state == InferiorUnrunnable;
|
|
setBusyCursor(!notbusy);
|
|
}
|
|
|
|
void DebuggerEnginePrivate::updateReverseActions()
|
|
{
|
|
const bool stopped = m_state == InferiorStopOk;
|
|
const bool reverseEnabled = debuggerSettings()->enableReverseDebugging.value();
|
|
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(Tr::tr("Operate in Reverse Direction"));
|
|
}
|
|
|
|
void DebuggerEnginePrivate::cleanupViews()
|
|
{
|
|
const bool closeSource = debuggerSettings()->closeSourceBuffersOnExit.value();
|
|
const bool closeMemory = debuggerSettings()->closeMemoryBuffersOnExit.value();
|
|
|
|
QList<IDocument *> toClose;
|
|
const QList<IDocument *> documents = DocumentModel::openedDocuments();
|
|
for (IDocument *document : documents) {
|
|
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_peripheralRegisterWindow->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()
|
|
{
|
|
showMessage("INFERIOR FINISHED SHUT DOWN");
|
|
QTC_ASSERT(state() == InferiorShutdownRequested, qDebug() << this << state());
|
|
setState(InferiorShutdownFinished);
|
|
d->doShutdownEngine();
|
|
}
|
|
|
|
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.
|
|
startDying();
|
|
if (state() == InferiorRunRequested) {
|
|
// We asked for running, but did not see a response.
|
|
// Assume the inferior is dead.
|
|
// FIXME: Use timeout?
|
|
setState(InferiorRunFailed);
|
|
setState(InferiorStopOk);
|
|
}
|
|
d->doShutdownInferior();
|
|
}
|
|
|
|
void DebuggerEngine::notifyEngineShutdownFinished()
|
|
{
|
|
showMessage("NOTE: ENGINE SHUTDOWN FINISHED");
|
|
QTC_ASSERT(state() == EngineShutdownRequested, qDebug() << this << state());
|
|
setState(EngineShutdownFinished);
|
|
d->doFinishDebugger();
|
|
}
|
|
|
|
void DebuggerEngine::notifyEngineIll()
|
|
{
|
|
//#ifdef WITH_BENCHMARK
|
|
// CALLGRIND_STOP_INSTRUMENTATION;
|
|
// CALLGRIND_DUMP_STATS;
|
|
//#endif
|
|
showMessage("NOTE: ENGINE ILL ******");
|
|
startDying();
|
|
switch (state()) {
|
|
case InferiorRunRequested:
|
|
case InferiorRunOk:
|
|
// The engine does not look overly ill right now, so attempt to
|
|
// properly interrupt at least once. If that fails, we are on the
|
|
// shutdown path due to d->m_targetState anyways.
|
|
setState(InferiorStopRequested, true);
|
|
showMessage("ATTEMPT TO INTERRUPT INFERIOR");
|
|
interruptInferior();
|
|
break;
|
|
case InferiorStopRequested:
|
|
notifyInferiorStopFailed();
|
|
break;
|
|
case InferiorStopOk:
|
|
showMessage("FORWARDING STATE TO InferiorShutdownFinished");
|
|
setState(InferiorShutdownFinished, true);
|
|
d->doShutdownEngine();
|
|
break;
|
|
default:
|
|
d->doShutdownEngine();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void DebuggerEngine::notifyEngineSpontaneousShutdown()
|
|
{
|
|
#ifdef WITH_BENCHMARK
|
|
CALLGRIND_STOP_INSTRUMENTATION;
|
|
CALLGRIND_DUMP_STATS;
|
|
#endif
|
|
showMessage("NOTE: ENGINE SPONTANEOUS SHUTDOWN");
|
|
setState(EngineShutdownFinished, true);
|
|
d->doFinishDebugger();
|
|
}
|
|
|
|
void DebuggerEngine::notifyInferiorExited()
|
|
{
|
|
#ifdef WITH_BENCHMARK
|
|
CALLGRIND_STOP_INSTRUMENTATION;
|
|
CALLGRIND_DUMP_STATS;
|
|
#endif
|
|
showMessage("NOTE: INFERIOR EXITED");
|
|
d->resetLocation();
|
|
setState(InferiorShutdownFinished);
|
|
d->doShutdownEngine();
|
|
}
|
|
|
|
void DebuggerEngine::updateState()
|
|
{
|
|
d->updateState();
|
|
}
|
|
|
|
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);
|
|
DebuggerMainWindow::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, "ERROR: " + msg);
|
|
d->m_logWindow->showOutput(LogError, "ERROR: " + msg);
|
|
break;
|
|
case AppOutput:
|
|
case AppStuff:
|
|
d->m_logWindow->showOutput(channel, msg);
|
|
emit appendMessageRequested(msg, StdOutFormat, false);
|
|
break;
|
|
case AppError:
|
|
d->m_logWindow->showOutput(channel, msg);
|
|
emit appendMessageRequested(msg, StdErrFormat, false);
|
|
break;
|
|
default:
|
|
d->m_logWindow->showOutput(channel, msg);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void DebuggerEngine::notifyDebuggerProcessFinished(const ProcessResultData &result,
|
|
const QString &backendName)
|
|
{
|
|
showMessage(QString("%1 PROCESS FINISHED, status %2, exit code %3 (0x%4)")
|
|
.arg(backendName)
|
|
.arg(result.m_exitStatus)
|
|
.arg(result.m_exitCode)
|
|
.arg(QString::number(result.m_exitCode, 16)));
|
|
|
|
switch (state()) {
|
|
case DebuggerFinished:
|
|
// Nothing to do.
|
|
break;
|
|
case EngineShutdownRequested:
|
|
case InferiorShutdownRequested:
|
|
notifyEngineShutdownFinished();
|
|
break;
|
|
case InferiorRunOk:
|
|
// This could either be a real gdb/lldb crash or a quickly exited inferior
|
|
// in the terminal adapter. In this case the stub proc will die soon,
|
|
// too, so there's no need to act here.
|
|
showMessage(QString("The %1 process exited somewhat unexpectedly.").arg(backendName));
|
|
notifyEngineSpontaneousShutdown();
|
|
break;
|
|
default: {
|
|
// Initiate shutdown sequence
|
|
notifyInferiorIll();
|
|
const QString msg = result.m_exitStatus == QProcess::CrashExit ?
|
|
Tr::tr("The %1 process terminated.") :
|
|
Tr::tr("The %2 process terminated unexpectedly (exit code %1).").arg(result.m_exitCode);
|
|
AsynchronousMessageBox::critical(Tr::tr("Unexpected %1 Exit").arg(backendName),
|
|
msg.arg(backendName));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static QString msgStateChanged(DebuggerState oldState, DebuggerState newState, bool forced)
|
|
{
|
|
QString result;
|
|
QTextStream str(&result);
|
|
str << "State changed";
|
|
if (forced)
|
|
str << " BY FORCE";
|
|
str << " from " << DebuggerEngine::stateName(oldState) << '(' << oldState
|
|
<< ") to " << DebuggerEngine::stateName(newState) << '(' << newState << ')';
|
|
return result;
|
|
}
|
|
|
|
void DebuggerEngine::setState(DebuggerState state, bool forced)
|
|
{
|
|
const QString msg = msgStateChanged(d->m_state, state, forced);
|
|
|
|
DebuggerState oldState = d->m_state;
|
|
d->m_state = state;
|
|
|
|
if (!forced && !isAllowedTransition(oldState, state))
|
|
qDebug() << "*** UNEXPECTED STATE TRANSITION: " << this << msg;
|
|
|
|
if (state == EngineRunRequested) {
|
|
emit engineStarted();
|
|
if (d->m_perspective)
|
|
d->m_perspective->select();
|
|
}
|
|
|
|
showMessage(msg, LogDebug);
|
|
|
|
d->updateState();
|
|
if (d->m_companionEngine)
|
|
d->m_companionEngine->d->updateState();
|
|
|
|
if (oldState != d->m_state)
|
|
emit EngineManager::instance()->engineStateChanged(this);
|
|
|
|
if (state == DebuggerFinished) {
|
|
d->setBusyCursor(false);
|
|
|
|
// Give up ownership on claimed breakpoints.
|
|
d->m_breakHandler.releaseAllBreakpoints();
|
|
d->m_toolTipManager.deregisterEngine();
|
|
d->m_memoryAgents.handleDebuggerFinished();
|
|
|
|
d->destroyPerspective();
|
|
emit engineFinished();
|
|
}
|
|
}
|
|
|
|
bool DebuggerEngine::isPrimaryEngine() const
|
|
{
|
|
return d->m_isPrimaryEngine;
|
|
}
|
|
|
|
bool DebuggerEngine::canDisplayTooltip() const
|
|
{
|
|
return state() == InferiorStopOk;
|
|
}
|
|
|
|
QString DebuggerEngine::expand(const QString &string) const
|
|
{
|
|
return runParameters().macroExpander->expand(string);
|
|
}
|
|
|
|
QString DebuggerEngine::nativeStartupCommands() const
|
|
{
|
|
QStringList lines = debuggerSettings()->gdbStartupCommands.value().split('\n');
|
|
lines += runParameters().additionalStartupCommands.split('\n');
|
|
|
|
lines = Utils::filtered(lines, [](const QString line) {
|
|
const QString trimmed = line.trimmed();
|
|
return !trimmed.isEmpty() && !trimmed.startsWith('#');
|
|
});
|
|
|
|
return expand(lines.join('\n'));
|
|
}
|
|
|
|
Perspective *DebuggerEngine::perspective() const
|
|
{
|
|
return d->m_perspective;
|
|
}
|
|
|
|
void DebuggerEngine::updateMarkers()
|
|
{
|
|
if (d->m_locationMark)
|
|
d->m_locationMark->updateIcon();
|
|
|
|
d->m_disassemblerAgent.updateLocationMarker();
|
|
}
|
|
|
|
void DebuggerEngine::updateToolTips()
|
|
{
|
|
d->m_toolTipManager.updateToolTips();
|
|
}
|
|
|
|
DebuggerToolTipManager *DebuggerEngine::toolTipManager()
|
|
{
|
|
return &d->m_toolTipManager;
|
|
}
|
|
|
|
bool DebuggerEngine::operatesByInstruction() const
|
|
{
|
|
return d->m_operateByInstructionAction.isChecked();
|
|
}
|
|
|
|
bool DebuggerEngine::debuggerActionsEnabled() const
|
|
{
|
|
return debuggerActionsEnabledHelper(d->m_state);
|
|
}
|
|
|
|
void DebuggerEngine::operateByInstructionTriggered(bool on)
|
|
{
|
|
// Go to source only if we have the file.
|
|
// if (DebuggerEngine *cppEngine = m_engine->cppEngine()) {
|
|
d->m_stackHandler.rootItem()->updateAll();
|
|
if (d->m_stackHandler.currentIndex() >= 0) {
|
|
const StackFrame frame = d->m_stackHandler.currentFrame();
|
|
if (on || frame.isUsable())
|
|
gotoLocation(Location(frame, true));
|
|
}
|
|
// }
|
|
}
|
|
|
|
bool DebuggerEngine::companionPreventsActions() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void DebuggerEngine::notifyInferiorPid(const ProcessHandle &pid)
|
|
{
|
|
if (d->m_inferiorPid == pid)
|
|
return;
|
|
d->m_inferiorPid = pid;
|
|
if (pid.isValid()) {
|
|
showMessage(Tr::tr("Taking notice of pid %1").arg(pid.pid()));
|
|
DebuggerStartMode sm = runParameters().startMode;
|
|
if (sm == StartInternal || sm == StartExternal || sm == AttachToLocalProcess)
|
|
d->m_inferiorPid.activate();
|
|
}
|
|
}
|
|
|
|
qint64 DebuggerEngine::inferiorPid() const
|
|
{
|
|
return d->m_inferiorPid.pid();
|
|
}
|
|
|
|
bool DebuggerEngine::isReverseDebugging() const
|
|
{
|
|
return d->m_operateInReverseDirectionAction.isChecked();
|
|
}
|
|
|
|
void DebuggerEngine::handleBeginOfRecordingReached()
|
|
{
|
|
showStatusMessage(Tr::tr("Reverse-execution history exhausted. Going forward again."));
|
|
d->m_operateInReverseDirectionAction.setChecked(false);
|
|
d->updateReverseActions();
|
|
}
|
|
|
|
void DebuggerEngine::handleRecordingFailed()
|
|
{
|
|
showStatusMessage(Tr::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()));
|
|
startDying();
|
|
switch (state()) {
|
|
case InferiorStopOk:
|
|
case InferiorStopFailed:
|
|
case InferiorUnrunnable:
|
|
d->doShutdownInferior();
|
|
break;
|
|
case InferiorRunOk:
|
|
setState(InferiorStopRequested);
|
|
showMessage(Tr::tr("Attempting to interrupt."), StatusBar);
|
|
interruptInferior();
|
|
break;
|
|
case EngineSetupRequested:
|
|
notifyEngineSetupFailed();
|
|
break;
|
|
case EngineRunRequested:
|
|
notifyEngineRunFailed();
|
|
break;
|
|
case EngineShutdownRequested:
|
|
case InferiorShutdownRequested:
|
|
break;
|
|
case EngineRunFailed:
|
|
case DebuggerFinished:
|
|
case InferiorShutdownFinished:
|
|
break;
|
|
default:
|
|
// FIXME: We should disable the actions connected to that.
|
|
notifyInferiorIll();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void DebuggerEngine::requestInterruptInferior()
|
|
{
|
|
QTC_ASSERT(state() == InferiorRunOk, qDebug() << this << state());
|
|
setState(InferiorStopRequested);
|
|
showMessage("CALL: INTERRUPT INFERIOR");
|
|
showMessage(Tr::tr("Attempting to interrupt."), StatusBar);
|
|
interruptInferior();
|
|
}
|
|
|
|
void DebuggerEngine::progressPing()
|
|
{
|
|
int progress = qMin(d->m_progress.progressValue() + 2, 800);
|
|
d->m_progress.setProgressValue(progress);
|
|
}
|
|
|
|
void DebuggerEngine::setCompanionEngine(DebuggerEngine *engine)
|
|
{
|
|
d->m_companionEngine = engine;
|
|
}
|
|
|
|
void DebuggerEngine::setSecondaryEngine()
|
|
{
|
|
d->m_isPrimaryEngine = false;
|
|
}
|
|
|
|
TerminalRunner *DebuggerEngine::terminal() const
|
|
{
|
|
return d->m_terminalRunner;
|
|
}
|
|
|
|
void DebuggerEngine::selectWatchData(const QString &)
|
|
{
|
|
}
|
|
|
|
void DebuggerEngine::watchPoint(const QPoint &pnt)
|
|
{
|
|
DebuggerCommand cmd("watchPoint", NeedsFullStop);
|
|
cmd.arg("x", pnt.x());
|
|
cmd.arg("y", pnt.y());
|
|
cmd.callback = [this](const DebuggerResponse &response) {
|
|
qulonglong addr = response.data["selected"].toAddress();
|
|
if (addr == 0)
|
|
showMessage(Tr::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);
|
|
};
|
|
runCommand(cmd);
|
|
}
|
|
|
|
void DebuggerEngine::runCommand(const DebuggerCommand &)
|
|
{
|
|
// Overridden in the engines that use the interface.
|
|
QTC_CHECK(false);
|
|
}
|
|
|
|
void DebuggerEngine::fetchDisassembler(DisassemblerAgent *)
|
|
{
|
|
}
|
|
|
|
void DebuggerEngine::activateFrame(int)
|
|
{
|
|
}
|
|
|
|
void DebuggerEngine::reloadModules()
|
|
{
|
|
}
|
|
|
|
void DebuggerEngine::examineModules()
|
|
{
|
|
}
|
|
|
|
void DebuggerEngine::loadSymbols(const QString &)
|
|
{
|
|
}
|
|
|
|
void DebuggerEngine::loadAllSymbols()
|
|
{
|
|
}
|
|
|
|
void DebuggerEngine::loadSymbolsForStack()
|
|
{
|
|
}
|
|
|
|
void DebuggerEngine::requestModuleSymbols(const QString &)
|
|
{
|
|
}
|
|
|
|
void DebuggerEngine::requestModuleSections(const QString &)
|
|
{
|
|
}
|
|
|
|
void DebuggerEngine::reloadRegisters()
|
|
{
|
|
}
|
|
|
|
void DebuggerEngine::reloadPeripheralRegisters()
|
|
{
|
|
}
|
|
|
|
void DebuggerEngine::reloadSourceFiles()
|
|
{
|
|
}
|
|
|
|
void DebuggerEngine::reloadFullStack()
|
|
{
|
|
}
|
|
|
|
void DebuggerEngine::loadAdditionalQmlStack()
|
|
{
|
|
}
|
|
|
|
void DebuggerEngine::reloadDebuggingHelpers()
|
|
{
|
|
}
|
|
|
|
void DebuggerEngine::addOptionPages(QList<IOptionsPage*> *) const
|
|
{
|
|
}
|
|
|
|
QString DebuggerEngine::qtNamespace() const
|
|
{
|
|
return d->m_qtNamespace;
|
|
}
|
|
|
|
void DebuggerEngine::setQtNamespace(const QString &ns)
|
|
{
|
|
d->m_qtNamespace = ns;
|
|
}
|
|
|
|
void DebuggerEngine::createSnapshot()
|
|
{
|
|
}
|
|
|
|
void DebuggerEngine::updateLocals()
|
|
{
|
|
// if the engine is not running - do nothing
|
|
if (state() == DebuggerState::DebuggerFinished || state() == DebuggerState::DebuggerNotReady)
|
|
return;
|
|
|
|
watchHandler()->resetValueCache();
|
|
doUpdateLocals(UpdateParameters());
|
|
}
|
|
|
|
Context DebuggerEngine::debuggerContext() const
|
|
{
|
|
return d->m_context;
|
|
}
|
|
|
|
void DebuggerEngine::updateAll()
|
|
{
|
|
}
|
|
|
|
QString DebuggerEngine::displayName() const
|
|
{
|
|
//: e.g. LLDB for "myproject", shows up i
|
|
return Tr::tr("%1 for \"%2\"").arg(d->m_debuggerName, runParameters().displayName);
|
|
}
|
|
|
|
void DebuggerEngine::insertBreakpoint(const Breakpoint &bp)
|
|
{
|
|
QTC_ASSERT(bp, return);
|
|
BreakpointState state = bp->state();
|
|
QTC_ASSERT(state == BreakpointInsertionRequested,
|
|
qDebug() << bp->modelId() << this << state);
|
|
QTC_CHECK(false);
|
|
}
|
|
|
|
void DebuggerEngine::removeBreakpoint(const Breakpoint &bp)
|
|
{
|
|
QTC_ASSERT(bp, return);
|
|
BreakpointState state = bp->state();
|
|
QTC_ASSERT(state == BreakpointRemoveRequested,
|
|
qDebug() << bp->responseId() << this << state);
|
|
QTC_CHECK(false);
|
|
}
|
|
|
|
void DebuggerEngine::updateBreakpoint(const Breakpoint &bp)
|
|
{
|
|
QTC_ASSERT(bp, return);
|
|
BreakpointState state = bp->state();
|
|
QTC_ASSERT(state == BreakpointUpdateRequested,
|
|
qDebug() << bp->responseId() << this << state);
|
|
QTC_CHECK(false);
|
|
}
|
|
|
|
void DebuggerEngine::enableSubBreakpoint(const SubBreakpoint &sbp, bool)
|
|
{
|
|
QTC_ASSERT(sbp, return);
|
|
QTC_CHECK(false);
|
|
}
|
|
|
|
void DebuggerEngine::assignValueInDebugger(WatchItem *,
|
|
const QString &, const QVariant &)
|
|
{
|
|
}
|
|
|
|
void DebuggerEngine::handleRecordReverse(bool record)
|
|
{
|
|
executeRecordReverse(record);
|
|
d->updateReverseActions();
|
|
}
|
|
|
|
void DebuggerEngine::handleReverseDirection(bool reverse)
|
|
{
|
|
executeReverse(reverse);
|
|
updateMarkers();
|
|
d->updateReverseActions();
|
|
}
|
|
|
|
void DebuggerEngine::executeDebuggerCommand(const QString &)
|
|
{
|
|
showMessage(Tr::tr("This debugger cannot handle user input."), StatusBar);
|
|
}
|
|
|
|
bool DebuggerEngine::isDying() const
|
|
{
|
|
return d->m_isDying;
|
|
}
|
|
|
|
QString DebuggerEngine::msgStopped(const QString &reason)
|
|
{
|
|
return reason.isEmpty() ? Tr::tr("Stopped.") : Tr::tr("Stopped: \"%1\".").arg(reason);
|
|
}
|
|
|
|
QString DebuggerEngine::msgStoppedBySignal(const QString &meaning,
|
|
const QString &name)
|
|
{
|
|
return Tr::tr("Stopped: %1 (Signal %2).").arg(meaning, name);
|
|
}
|
|
|
|
QString DebuggerEngine::msgStoppedByException(const QString &description,
|
|
const QString &threadId)
|
|
{
|
|
return Tr::tr("Stopped in thread %1 by: %2.").arg(threadId, description);
|
|
}
|
|
|
|
QString DebuggerEngine::msgInterrupted()
|
|
{
|
|
return Tr::tr("Interrupted.");
|
|
}
|
|
|
|
bool DebuggerEngine::showStoppedBySignalMessageBox(QString meaning, QString name)
|
|
{
|
|
if (d->m_alertBox)
|
|
return false;
|
|
|
|
if (name.isEmpty())
|
|
name = ' ' + Tr::tr("<Unknown>", "name") + ' ';
|
|
if (meaning.isEmpty())
|
|
meaning = ' ' + Tr::tr("<Unknown>", "meaning") + ' ';
|
|
const QString msg = Tr::tr("<p>The inferior stopped because it received a "
|
|
"signal from the operating system.<p>"
|
|
"<table><tr><td>Signal name : </td><td>%1</td></tr>"
|
|
"<tr><td>Signal meaning : </td><td>%2</td></tr></table>")
|
|
.arg(name, meaning);
|
|
|
|
d->m_alertBox = AsynchronousMessageBox::information(Tr::tr("Signal Received"), msg);
|
|
return true;
|
|
}
|
|
|
|
void DebuggerEngine::showStoppedByExceptionMessageBox(const QString &description)
|
|
{
|
|
const QString msg =
|
|
Tr::tr("<p>The inferior stopped because it triggered an exception.<p>%1").
|
|
arg(description);
|
|
AsynchronousMessageBox::information(Tr::tr("Exception Triggered"), msg);
|
|
}
|
|
|
|
void DebuggerEngine::openMemoryView(const MemoryViewSetupData &data)
|
|
{
|
|
d->m_memoryAgents.createBinEditor(data, this);
|
|
}
|
|
|
|
void DebuggerEngine::updateMemoryViews()
|
|
{
|
|
d->m_memoryAgents.updateContents();
|
|
}
|
|
|
|
void DebuggerEngine::openDisassemblerView(const Location &location)
|
|
{
|
|
DisassemblerAgent *agent = new DisassemblerAgent(this);
|
|
agent->setLocation(location);
|
|
}
|
|
|
|
void DebuggerEngine::raiseWatchersWindow()
|
|
{
|
|
if (d->m_watchersView && d->m_watchersWindow) {
|
|
auto currentPerspective = DebuggerMainWindow::currentPerspective();
|
|
QTC_ASSERT(currentPerspective, return);
|
|
// if a companion engine has taken over - do not raise the watchers
|
|
if (currentPerspective->name() != d->m_engine->displayName())
|
|
return;
|
|
|
|
if (auto dock = qobject_cast<QDockWidget *>(d->m_watchersWindow->parentWidget())) {
|
|
if (QAction *act = dock->toggleViewAction()) {
|
|
if (!act->isChecked())
|
|
QTimer::singleShot(1, act, [act] { act->trigger(); });
|
|
dock->raise();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DebuggerEngine::openMemoryEditor()
|
|
{
|
|
AddressDialog dialog;
|
|
if (dialog.exec() != QDialog::Accepted)
|
|
return;
|
|
MemoryViewSetupData data;
|
|
data.startAddress = dialog.address();
|
|
openMemoryView(data);
|
|
}
|
|
|
|
void DebuggerEngine::updateLocalsView(const GdbMi &all)
|
|
{
|
|
WatchHandler *handler = watchHandler();
|
|
|
|
const GdbMi typeInfo = all["typeinfo"];
|
|
handler->recordTypeInfo(typeInfo);
|
|
|
|
const GdbMi data = all["data"];
|
|
handler->insertItems(data);
|
|
|
|
const GdbMi ns = all["qtnamespace"];
|
|
if (ns.isValid()) {
|
|
setQtNamespace(ns.data());
|
|
showMessage("FOUND NAMESPACED QT: " + ns.data());
|
|
}
|
|
|
|
static int count = 0;
|
|
showMessage(QString("<Rebuild Watchmodel %1 @ %2 >")
|
|
.arg(++count).arg(LogWindow::logTimeStamp()), LogMiscInput);
|
|
showMessage(Tr::tr("Finished retrieving data."), 400, StatusBar);
|
|
|
|
d->m_toolTipManager.updateToolTips();
|
|
|
|
const bool partial = all["partial"].toInt();
|
|
if (!partial)
|
|
updateMemoryViews();
|
|
}
|
|
|
|
bool DebuggerEngine::canHandleToolTip(const DebuggerToolTipContext &context) const
|
|
{
|
|
return state() == InferiorStopOk && context.isCppEditor;
|
|
}
|
|
|
|
void DebuggerEngine::updateItem(const QString &iname)
|
|
{
|
|
if (d->m_lookupRequests.contains(iname)) {
|
|
showMessage(QString("IGNORING REPEATED REQUEST TO EXPAND " + iname));
|
|
WatchHandler *handler = watchHandler();
|
|
WatchItem *item = handler->findItem(iname);
|
|
QTC_CHECK(item);
|
|
WatchModelBase *model = handler->model();
|
|
QTC_CHECK(model);
|
|
if (item && !item->wantsChildren) {
|
|
updateToolTips();
|
|
return;
|
|
}
|
|
if (item && !model->hasChildren(model->indexForItem(item))) {
|
|
handler->notifyUpdateStarted(UpdateParameters(iname));
|
|
item->setValue(decodeData({}, "notaccessible"));
|
|
item->setHasChildren(false);
|
|
item->outdated = false;
|
|
item->update();
|
|
handler->notifyUpdateFinished();
|
|
return;
|
|
}
|
|
// We could legitimately end up here after expanding + closing + re-expaning an item.
|
|
}
|
|
d->m_lookupRequests.insert(iname);
|
|
|
|
UpdateParameters params;
|
|
params.partialVariable = iname;
|
|
doUpdateLocals(params);
|
|
}
|
|
|
|
void DebuggerEngine::updateWatchData(const QString &iname)
|
|
{
|
|
// This is used in cases where re-evaluation is ok for the same iname
|
|
// e.g. when changing the expression in a watcher.
|
|
UpdateParameters params;
|
|
params.partialVariable = iname;
|
|
doUpdateLocals(params);
|
|
}
|
|
|
|
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::handleExecStepIn()
|
|
{
|
|
resetLocation();
|
|
executeStepIn(operatesByInstruction());
|
|
}
|
|
|
|
void DebuggerEngine::handleExecStepOver()
|
|
{
|
|
resetLocation();
|
|
executeStepOver(operatesByInstruction());
|
|
}
|
|
|
|
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 QStringList lineList = block.text().trimmed().split('(');
|
|
for (const QString &str : lineList) {
|
|
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::tr("No function selected."), StatusBar);
|
|
} else {
|
|
showMessage(Tr::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::tr("Warning"),
|
|
Tr::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;
|
|
if (current == state)
|
|
return;
|
|
|
|
QString msg = QString("UNEXPECTED STATE: %1 WANTED: %2 IN %3:%4")
|
|
.arg(stateName(current)).arg(stateName(state)).arg(QLatin1String(file)).arg(line);
|
|
|
|
showMessage(msg, LogError);
|
|
qDebug("%s", qPrintable(msg));
|
|
}
|
|
|
|
bool DebuggerEngine::isNativeMixedEnabled() const
|
|
{
|
|
return d->m_runParameters.isNativeMixedDebugging();
|
|
}
|
|
|
|
bool DebuggerEngine::isNativeMixedActive() const
|
|
{
|
|
return isNativeMixedEnabled(); //&& boolSetting(OperateNativeMixed);
|
|
}
|
|
|
|
bool DebuggerEngine::isNativeMixedActiveFrame() const
|
|
{
|
|
if (!isNativeMixedActive())
|
|
return false;
|
|
if (stackHandler()->rowCount() == 0)
|
|
return false;
|
|
StackFrame frame = stackHandler()->frameAt(0);
|
|
return frame.language == QmlLanguage;
|
|
}
|
|
|
|
void DebuggerEngine::startDying() const
|
|
{
|
|
d->m_isDying = true;
|
|
}
|
|
|
|
QString DebuggerEngine::runId() const
|
|
{
|
|
return d->m_runId;
|
|
}
|
|
|
|
bool DebuggerRunParameters::isCppDebugging() const
|
|
{
|
|
return cppEngineType == GdbEngineType
|
|
|| cppEngineType == LldbEngineType
|
|
|| cppEngineType == CdbEngineType
|
|
|| cppEngineType == UvscEngineType;
|
|
}
|
|
|
|
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.command.isEmpty()) {
|
|
str << "Executable: " << sp.inferior.command.toUserOutput();
|
|
if (d->m_terminalRunner)
|
|
str << " [terminal]";
|
|
str << '\n';
|
|
if (!sp.inferior.workingDirectory.isEmpty())
|
|
str << "Directory: " << sp.inferior.workingDirectory.toUserOutput() << '\n';
|
|
}
|
|
if (!sp.debugger.command.isEmpty())
|
|
str << "Debugger: " << sp.debugger.command.toUserOutput() << '\n';
|
|
if (!sp.coreFile.isEmpty())
|
|
str << "Core: " << sp.coreFile.toUserOutput() << '\n';
|
|
if (sp.attachPID.isValid())
|
|
str << "PID: " << sp.attachPID.pid() << ' ' << sp.crashParameter << '\n';
|
|
if (!sp.projectSourceDirectory.isEmpty()) {
|
|
str << "Project: " << sp.projectSourceDirectory.toUserOutput() << '\n';
|
|
str << "Additional Search Directories:";
|
|
for (const FilePath &dir : sp.additionalSearchDirectories)
|
|
str << ' ' << dir;
|
|
str << '\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(':') << '\n';
|
|
return rc;
|
|
}
|
|
|
|
static void createNewDock(QWidget *widget)
|
|
{
|
|
auto dockWidget = new QDockWidget;
|
|
dockWidget->setWidget(widget);
|
|
dockWidget->setWindowTitle(widget->windowTitle());
|
|
dockWidget->setFeatures(QDockWidget::DockWidgetClosable);
|
|
dockWidget->show();
|
|
}
|
|
|
|
void DebuggerEngine::showModuleSymbols(const QString &moduleName, const Symbols &symbols)
|
|
{
|
|
auto w = new QTreeWidget;
|
|
w->setUniformRowHeights(true);
|
|
w->setColumnCount(5);
|
|
w->setRootIsDecorated(false);
|
|
w->setAlternatingRowColors(true);
|
|
w->setSortingEnabled(true);
|
|
w->setObjectName("Symbols." + moduleName);
|
|
QStringList header;
|
|
header.append(Tr::tr("Symbol"));
|
|
header.append(Tr::tr("Address"));
|
|
header.append(Tr::tr("Code"));
|
|
header.append(Tr::tr("Section"));
|
|
header.append(Tr::tr("Name"));
|
|
w->setHeaderLabels(header);
|
|
w->setWindowTitle(Tr::tr("Symbols in \"%1\"").arg(moduleName));
|
|
for (const Symbol &s : symbols) {
|
|
auto it = new QTreeWidgetItem;
|
|
it->setData(0, Qt::DisplayRole, s.name);
|
|
it->setData(1, Qt::DisplayRole, s.address);
|
|
it->setData(2, Qt::DisplayRole, s.state);
|
|
it->setData(3, Qt::DisplayRole, s.section);
|
|
it->setData(4, Qt::DisplayRole, s.demangled);
|
|
w->addTopLevelItem(it);
|
|
}
|
|
createNewDock(w);
|
|
}
|
|
|
|
void DebuggerEngine::showModuleSections(const QString &moduleName, const Sections §ions)
|
|
{
|
|
auto w = new QTreeWidget;
|
|
w->setUniformRowHeights(true);
|
|
w->setColumnCount(5);
|
|
w->setRootIsDecorated(false);
|
|
w->setAlternatingRowColors(true);
|
|
w->setSortingEnabled(true);
|
|
w->setObjectName("Sections." + moduleName);
|
|
QStringList header;
|
|
header.append(Tr::tr("Name"));
|
|
header.append(Tr::tr("From"));
|
|
header.append(Tr::tr("To"));
|
|
header.append(Tr::tr("Address"));
|
|
header.append(Tr::tr("Flags"));
|
|
w->setHeaderLabels(header);
|
|
w->setWindowTitle(Tr::tr("Sections in \"%1\"").arg(moduleName));
|
|
for (const Section &s : sections) {
|
|
auto it = new QTreeWidgetItem;
|
|
it->setData(0, Qt::DisplayRole, s.name);
|
|
it->setData(1, Qt::DisplayRole, s.from);
|
|
it->setData(2, Qt::DisplayRole, s.to);
|
|
it->setData(3, Qt::DisplayRole, s.address);
|
|
it->setData(4, Qt::DisplayRole, s.flags);
|
|
w->addTopLevelItem(it);
|
|
}
|
|
createNewDock(w);
|
|
}
|
|
|
|
// CppDebuggerEngine
|
|
|
|
Context CppDebuggerEngine::languageContext() const
|
|
{
|
|
return Context(Constants::C_CPPDEBUGGER);
|
|
}
|
|
|
|
void CppDebuggerEngine::validateRunParameters(DebuggerRunParameters &rp)
|
|
{
|
|
static const QString warnOnInappropriateDebuggerKey = "DebuggerWarnOnInappropriateDebugger";
|
|
QtcSettings *coreSettings = Core::ICore::settings();
|
|
|
|
const bool warnOnRelease = debuggerSettings()->warnOnReleaseBuilds.value()
|
|
&& rp.toolChainAbi.osFlavor() != Abi::AndroidLinuxFlavor;
|
|
bool warnOnInappropriateDebugger = false;
|
|
QString detailedWarning;
|
|
switch (rp.toolChainAbi.binaryFormat()) {
|
|
case Abi::PEFormat: {
|
|
if (CheckableMessageBox::shouldAskAgain(coreSettings, warnOnInappropriateDebuggerKey)) {
|
|
QString preferredDebugger;
|
|
if (rp.toolChainAbi.osFlavor() == Abi::WindowsMSysFlavor) {
|
|
if (rp.cppEngineType == CdbEngineType)
|
|
preferredDebugger = "GDB";
|
|
} else if (rp.cppEngineType != CdbEngineType && rp.cppEngineType != LldbEngineType) {
|
|
// osFlavor() is MSVC, so the recommended debugger is still CDB,
|
|
// but don't warn for LLDB which starts to be usable, too.
|
|
preferredDebugger = "CDB";
|
|
}
|
|
if (!preferredDebugger.isEmpty()) {
|
|
warnOnInappropriateDebugger = true;
|
|
detailedWarning = Tr::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
|
|
&& rp.startMode != AttachToRemoteServer) {
|
|
QTC_ASSERT(!rp.symbolFile.isEmpty(), return);
|
|
if (!rp.symbolFile.exists() && !rp.symbolFile.endsWith(".exe"))
|
|
rp.symbolFile = rp.symbolFile.stringAppended(".exe");
|
|
QString errorMessage;
|
|
QStringList rc;
|
|
if (getPDBFiles(rp.symbolFile.toString(), &rc, &errorMessage) && !rc.isEmpty())
|
|
return;
|
|
if (!errorMessage.isEmpty()) {
|
|
detailedWarning.append('\n');
|
|
detailedWarning.append(errorMessage);
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case Abi::ElfFormat: {
|
|
if (CheckableMessageBox::shouldAskAgain(coreSettings, warnOnInappropriateDebuggerKey)) {
|
|
if (rp.cppEngineType == CdbEngineType) {
|
|
warnOnInappropriateDebugger = true;
|
|
detailedWarning = Tr::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;
|
|
}
|
|
}
|
|
|
|
ElfReader reader(rp.symbolFile);
|
|
const ElfData elfData = reader.readHeaders();
|
|
const QString error = reader.errorString();
|
|
|
|
showMessage("EXAMINING " + rp.symbolFile.toString(), LogDebug);
|
|
QByteArray msg = "ELF SECTIONS: ";
|
|
|
|
static const QList<QByteArray> interesting = {
|
|
".debug_info",
|
|
".debug_abbrev",
|
|
".debug_line",
|
|
".debug_str",
|
|
".debug_loc",
|
|
".debug_range",
|
|
".gdb_index",
|
|
".note.gnu.build-id",
|
|
".gnu.hash",
|
|
".gnu_debuglink"
|
|
};
|
|
|
|
QSet<QByteArray> 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) {
|
|
const SourcePathMap sourcePathMap = debuggerSettings()->sourcePathMap.value();
|
|
QList<QPair<QRegularExpression, QString>> globalRegExpSourceMap;
|
|
globalRegExpSourceMap.reserve(sourcePathMap.size());
|
|
for (auto it = sourcePathMap.begin(), end = sourcePathMap.end(); it != end; ++it) {
|
|
if (it.key().startsWith('(')) {
|
|
const QString expanded = rp.macroExpander->expand(it.value());
|
|
if (!expanded.isEmpty())
|
|
globalRegExpSourceMap.push_back(
|
|
qMakePair(QRegularExpression(it.key()), expanded));
|
|
}
|
|
}
|
|
if (globalRegExpSourceMap.isEmpty())
|
|
return;
|
|
if (QSharedPointer<Utils::ElfMapper> 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 pair : qAsConst(globalRegExpSourceMap)) {
|
|
const QRegularExpressionMatch match = pair.first.match(string);
|
|
if (match.hasMatch()) {
|
|
rp.sourcePathMap.insert(string.left(match.capturedStart()) + match.captured(1),
|
|
pair.second);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (found)
|
|
break;
|
|
|
|
const int len = int(strlen(str));
|
|
if (len == 0)
|
|
break;
|
|
str += len + 1;
|
|
}
|
|
}
|
|
}
|
|
if (hasEmbeddedInfo || hasLink)
|
|
return;
|
|
|
|
for (const QByteArray &name : qAsConst(interesting)) {
|
|
const QString found = seen.contains(name) ? Tr::tr("Found.")
|
|
: Tr::tr("Not found.");
|
|
detailedWarning.append('\n' + Tr::tr("Section %1: %2").arg(QString::fromUtf8(name)).arg(found));
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
return;
|
|
}
|
|
if (warnOnInappropriateDebugger) {
|
|
CheckableMessageBox::doNotShowAgainInformation(
|
|
Core::ICore::dialogParent(),
|
|
Tr::tr("Warning"),
|
|
Tr::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,
|
|
Core::ICore::settings(),
|
|
warnOnInappropriateDebuggerKey);
|
|
} else if (warnOnRelease) {
|
|
AsynchronousMessageBox::information(Tr::tr("Warning"),
|
|
Tr::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
|
|
|
|
#include "debuggerengine.moc"
|