From 8477c7bc4f6bd229cba60c571cc45eeb7dc655d0 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint <Friedemann.Kleint@nokia.com> Date: Thu, 18 Nov 2010 13:51:15 +0100 Subject: [PATCH] Debugger: Add new CDB-engine. Rubber-stamped-by: hjk --- .../debugger/cdb2/bytearrayinputstream.cpp | 111 ++ .../debugger/cdb2/bytearrayinputstream.h | 105 + src/plugins/debugger/cdb2/cdb2.pri | 16 + src/plugins/debugger/cdb2/cdbengine2.cpp | 1698 +++++++++++++++++ src/plugins/debugger/cdb2/cdbengine2.h | 202 ++ src/plugins/debugger/cdb2/cdboptions2.cpp | 160 ++ src/plugins/debugger/cdb2/cdboptions2.h | 73 + src/plugins/debugger/cdb2/cdboptionspage2.cpp | 218 +++ src/plugins/debugger/cdb2/cdboptionspage2.h | 105 + .../debugger/cdb2/cdboptionspagewidget2.ui | 128 ++ src/plugins/debugger/cdb2/cdbparsehelpers.cpp | 355 ++++ src/plugins/debugger/cdb2/cdbparsehelpers.h | 95 + src/plugins/debugger/debugger.pro | 1 + src/plugins/debugger/debuggerplugin.cpp | 6 + src/plugins/debugger/debuggerrunner.cpp | 13 +- 15 files changed, 3284 insertions(+), 2 deletions(-) create mode 100644 src/plugins/debugger/cdb2/bytearrayinputstream.cpp create mode 100644 src/plugins/debugger/cdb2/bytearrayinputstream.h create mode 100644 src/plugins/debugger/cdb2/cdb2.pri create mode 100644 src/plugins/debugger/cdb2/cdbengine2.cpp create mode 100644 src/plugins/debugger/cdb2/cdbengine2.h create mode 100644 src/plugins/debugger/cdb2/cdboptions2.cpp create mode 100644 src/plugins/debugger/cdb2/cdboptions2.h create mode 100644 src/plugins/debugger/cdb2/cdboptionspage2.cpp create mode 100644 src/plugins/debugger/cdb2/cdboptionspage2.h create mode 100644 src/plugins/debugger/cdb2/cdboptionspagewidget2.ui create mode 100644 src/plugins/debugger/cdb2/cdbparsehelpers.cpp create mode 100644 src/plugins/debugger/cdb2/cdbparsehelpers.h diff --git a/src/plugins/debugger/cdb2/bytearrayinputstream.cpp b/src/plugins/debugger/cdb2/bytearrayinputstream.cpp new file mode 100644 index 00000000000..5c916cdd902 --- /dev/null +++ b/src/plugins/debugger/cdb2/bytearrayinputstream.cpp @@ -0,0 +1,111 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "bytearrayinputstream.h" + +namespace Debugger { +namespace Cdb { + +ByteArrayInputStream::ByteArrayInputStream(QByteArray &ba) : + m_target(ba), m_integerBase(10), m_hexPrefix(false), m_width(0) +{ +} + +void hexPrefixOn(ByteArrayInputStream &bs) +{ + bs.setHexPrefix(true); +} + +void hexPrefixOff(ByteArrayInputStream &bs) +{ + bs.setHexPrefix(false); +} + +void hex(ByteArrayInputStream &bs) +{ + bs.setIntegerBase(16); +} + +void dec(ByteArrayInputStream &bs) +{ + bs.setIntegerBase(10); +} + +QByteArray trimFront(QByteArray in) +{ + if (in.isEmpty()) + return in; + const int size = in.size(); + int pos = 0; + for ( ; pos < size && isspace(in.at(pos)); pos++) ; + if (pos) + in.remove(0, pos); + return in; +} + +QByteArray trimBack(QByteArray in) +{ + if (in.isEmpty()) + return in; + const int size = in.size(); + int pos = size - 1; + for ( ; pos >= 0 && isspace(in.at(pos)); pos--) ; + if (pos != size - 1) + in.truncate(pos + 1); + return in; +} + + +// Simplify: replace tabs, find all occurrences +// of 2 blanks, check further up for blanks and remove that bit. +QByteArray simplify(const QByteArray &inIn) +{ + if (inIn.isEmpty()) + return inIn; + QByteArray in = trimFront(trimBack(inIn)); + in.replace('\t', ' '); + in.replace('\n', ' '); + in.replace('\r', ' '); + const QByteArray twoBlanks = " "; + while (true) { + const int pos = in.indexOf(twoBlanks); + if (pos != -1) { + const int size = in.size(); + int endPos = pos + twoBlanks.size(); + for ( ; endPos < size && in.at(endPos) == ' '; endPos++) ; + in.remove(pos + 1, endPos - pos - 1); + } else { + break; + } + } + return in; +} + +} // namespace Cdb +} // namespace Debugger diff --git a/src/plugins/debugger/cdb2/bytearrayinputstream.h b/src/plugins/debugger/cdb2/bytearrayinputstream.h new file mode 100644 index 00000000000..a10962698c8 --- /dev/null +++ b/src/plugins/debugger/cdb2/bytearrayinputstream.h @@ -0,0 +1,105 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef BYTEARRAYINPUTSTREAM_H +#define BYTEARRAYINPUTSTREAM_H + +#include <QtCore/QByteArray> +#include <QtCore/QString> + +namespace Debugger { +namespace Cdb { + +class ByteArrayInputStream +{ + Q_DISABLE_COPY(ByteArrayInputStream) +public: + typedef void (ModifierFunc)(ByteArrayInputStream &s); + + explicit ByteArrayInputStream(QByteArray &ba); + + ByteArrayInputStream &operator<<(char a) { m_target.append(a); return *this; } + ByteArrayInputStream &operator<<(const QByteArray &a) { m_target.append(a); return *this; } + ByteArrayInputStream &operator<<(const char *a) { m_target.append(a); return *this; } + ByteArrayInputStream &operator<<(const QString &a) { m_target.append(a.toLatin1()); return *this; } + + ByteArrayInputStream &operator<<(int i) { appendInt(i); return *this; } + ByteArrayInputStream &operator<<(unsigned i) { appendInt(i); return *this; } + ByteArrayInputStream &operator<<(quint64 i) { appendInt(i); return *this; } + ByteArrayInputStream &operator<<(qint64 i) { appendInt(i); return *this; } + + // Stream a modifier by invoking it + ByteArrayInputStream &operator<<(ModifierFunc mf) { mf(*this); return *this; } + + void setHexPrefix(bool hp) { m_hexPrefix = hp; } + bool hexPrefix() const { return m_hexPrefix; } + void setIntegerBase(int b) { m_integerBase = b; } + int integerBase() const { return m_integerBase; } + +private: + template <class IntType> void appendInt(IntType i); + + QByteArray &m_target; + int m_integerBase; + bool m_hexPrefix; + int m_width; +}; + +template <class IntType> +void ByteArrayInputStream::appendInt(IntType i) +{ + const bool hexPrefix = m_integerBase == 16 && m_hexPrefix; + if (hexPrefix) + m_target.append("0x"); + const QByteArray n = QByteArray::number(i, m_integerBase); + if (m_width > 0) { + int pad = m_width - n.size(); + if (hexPrefix) + pad -= 2; + if (pad > 0) + m_target.append(QByteArray(pad, '0')); + } + m_target.append(n); +} + +// Streamable modifiers for ByteArrayInputStream +void hexPrefixOn(ByteArrayInputStream &bs); +void hexPrefixOff(ByteArrayInputStream &bs); +void hex(ByteArrayInputStream &bs); +void dec(ByteArrayInputStream &bs); + +// Bytearray parse helpers +QByteArray trimFront(QByteArray in); +QByteArray trimBack(QByteArray in); +QByteArray simplify(const QByteArray &inIn); + +} // namespace Cdb +} // namespace Debugger + +#endif // BYTEARRAYINPUTSTREAM_H diff --git a/src/plugins/debugger/cdb2/cdb2.pri b/src/plugins/debugger/cdb2/cdb2.pri new file mode 100644 index 00000000000..a9e3e1cc5b9 --- /dev/null +++ b/src/plugins/debugger/cdb2/cdb2.pri @@ -0,0 +1,16 @@ +HEADERS += $$PWD/cdbengine2.h \ + cdb2/bytearrayinputstream.h \ + cdb2/cdbparsehelpers.h \ + cdb2/cdboptions2.h \ + cdb2/cdboptionspage2.h + +SOURCES += $$PWD/cdbengine2.cpp \ + cdb2/bytearrayinputstream.cpp \ + cdb2/cdbparsehelpers.cpp \ + cdb2/cdboptions2.cpp \ + cdb2/cdboptionspage2.cpp + +FORMS += cdb2/cdboptionspagewidget2.ui + +INCLUDEPATH*=$$PWD +DEPENDPATH*=$$PWD diff --git a/src/plugins/debugger/cdb2/cdbengine2.cpp b/src/plugins/debugger/cdb2/cdbengine2.cpp new file mode 100644 index 00000000000..a737388589b --- /dev/null +++ b/src/plugins/debugger/cdb2/cdbengine2.cpp @@ -0,0 +1,1698 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "cdbengine2.h" +#include "cdboptions2.h" +#include "cdboptionspage2.h" +#include "bytearrayinputstream.h" +#include "breakpoint.h" +#include "breakhandler.h" +#include "stackframe.h" +#include "stackhandler.h" +#include "watchhandler.h" +#include "threadshandler.h" +#include "debuggeractions.h" +#include "debuggercore.h" +#include "registerhandler.h" +#include "debuggeragents.h" +#include "cdbparsehelpers.h" +#include "watchutils.h" +#include "gdb/gdbmi.h" + +#include <utils/winutils.h> +#include <utils/qtcassert.h> +#include <utils/savedaction.h> + +#include <QtCore/QCoreApplication> +#include <QtCore/QFileInfo> +#include <QtCore/QDir> +#include <QtCore/QDebug> +#include <QtCore/QTextStream> +#include <QtCore/QDateTime> + +#ifdef Q_OS_WIN +# include <utils/winutils.h> +# include "dbgwinutils.h" +#endif + +#include <cctype> + +Q_DECLARE_METATYPE(Debugger::Internal::DisassemblerViewAgent*) +Q_DECLARE_METATYPE(Debugger::Internal::MemoryViewAgent*) + +enum { debug = 0 }; +enum { debugBreakpoints = 0 }; + +#if 0 +# define STATE_DEBUG(func, line, notifyFunc) qDebug("%s at %s:%d", notifyFunc, func, line); +#else +# define STATE_DEBUG(func, line, notifyFunc) +#endif + +/* CdbEngine version 2: Run the CDB process on pipes and parse its output. + * The engine relies on a CDB extension Qt Creator provides as an extension + * library (32/64bit). It serves to: + * - Notify the engine about the state of the debugging session: + * + idle: (hooked with .idle_cmd) debuggee stopped + * + accessible: Debuggee stopped, cdb.exe accepts commands + * + inaccessible: Debuggee runs, no way to post commands + * + session active/inactive: Lost debuggee, terminating. + * - Hook up with output/event callbacks and produce formatted output + * - Provide some extension commands that produce output in a standardized (GDBMI) + * format that ends up in handleExtensionMessage(). + * + pid Return debuggee pid for interrupting. + * + locals Print locals from SymbolGroup + * + expandLocals Expand locals in symbol group + * + registers, modules, threads + * Commands can be posted: + * postCommand: Does not expect a reply + * postBuiltinCommand: Run a builtin-command producing free-format, multiline output + * that is captured by enclosing it in special tokens using the 'echo' command and + * then invokes a callback with a CdbBuiltinCommand structure. + * postExtensionCommand: Run a command provided by the extension producing + * one-line output and invoke a callback with a CdbExtensionCommand structure. */ + +namespace Debugger { +namespace Cdb { + +struct MemoryViewCookie { + explicit MemoryViewCookie(Debugger::Internal::MemoryViewAgent *a = 0, QObject *e = 0, + quint64 addr = 0, quint64 l = 0) : + agent(a), editorToken(e), address(addr), length(l) {} + + Debugger::Internal::MemoryViewAgent *agent; + QObject *editorToken; + quint64 address; + quint64 length; +}; + +struct SourceLocationCookie { + explicit SourceLocationCookie(const QString &f = QString(), int l = 0) : + fileName(f), lineNumber(l) {} + + QString fileName; + int lineNumber; +}; + +} // namespace Cdb +} // namespace Debugger + +Q_DECLARE_METATYPE(Debugger::Cdb::MemoryViewCookie) +Q_DECLARE_METATYPE(Debugger::Cdb::SourceLocationCookie) + +namespace Debugger { +namespace Cdb { + +// Base data structure for command queue entries with callback +struct CdbCommandBase +{ + typedef CdbEngine::BuiltinCommandHandler CommandHandler; + + CdbCommandBase(); + CdbCommandBase(const QByteArray &cmd, int token, unsigned flags, + unsigned nc, const QVariant &cookie); + + int token; + unsigned flags; + QByteArray command; + QVariant cookie; + // Continue with another commands as specified in CommandSequenceFlags + unsigned commandSequence; +}; + +CdbCommandBase::CdbCommandBase() : + token(0), flags(0), commandSequence(0) +{ +} + +CdbCommandBase::CdbCommandBase(const QByteArray &cmd, int t, unsigned f, + unsigned nc, const QVariant &c) : + token(t), flags(f), command(cmd), cookie(c), commandSequence(nc) +{ +} + +// Queue entry for builtin commands producing free-format +// line-by-line output. +struct CdbBuiltinCommand : public CdbCommandBase +{ + typedef CdbEngine::BuiltinCommandHandler CommandHandler; + + CdbBuiltinCommand() {} + CdbBuiltinCommand(const QByteArray &cmd, int token, unsigned flags, + CommandHandler h, + unsigned nc, const QVariant &cookie) : + CdbCommandBase(cmd, token, flags, nc, cookie), handler(h) {} + + + QByteArray joinedReply() const; + + CommandHandler handler; + QList<QByteArray> reply; +}; + +QByteArray CdbBuiltinCommand::joinedReply() const +{ + if (reply.isEmpty()) + return QByteArray(); + QByteArray answer; + answer.reserve(120 * reply.size()); + foreach (const QByteArray &l, reply) { + answer += l; + answer += '\n'; + } + return answer; +} + +// Queue entry for Qt Creator extension commands producing one-line +// output with success flag and error message. +struct CdbExtensionCommand : public CdbCommandBase +{ + typedef CdbEngine::ExtensionCommandHandler CommandHandler; + + CdbExtensionCommand() : success(false) {} + CdbExtensionCommand(const QByteArray &cmd, int token, unsigned flags, + CommandHandler h, + unsigned nc, const QVariant &cookie) : + CdbCommandBase(cmd, token, flags, nc, cookie), handler(h),success(false) {} + + CommandHandler handler; + QByteArray reply; + QByteArray errorMessage; + bool success; +}; + +template <class CommandPtrType> +int indexOfCommand(const QList<CommandPtrType> &l, int token) +{ + const int count = l.size(); + for (int i = 0; i < count; i++) + if (l.at(i)->token == token) + return i; + return -1; +} + +static inline bool validMode(DebuggerStartMode sm) +{ + switch (sm) { + case NoStartMode: + case AttachTcf: + case AttachCore: + case AttachToRemote: + case StartRemoteGdb: + return false; + default: + break; + } + return true; +} + +// Accessed by RunControlFactory +DebuggerEngine *createCdbEngine(const DebuggerStartParameters &sp, QString *errorMessage) +{ +#ifdef Q_OS_WIN + CdbOptionsPage *op = CdbOptionsPage::instance(); + QTC_ASSERT(op, return 0); + if (validMode(sp.startMode)) + return new CdbEngine(sp, op->options()); +#else + Q_UNUSED(sp) +#endif + *errorMessage = QString::fromLatin1("Unsuppported debug mode"); + return 0; +} + +bool isCdbEngineEnabled() +{ +#ifdef Q_OS_WIN + return CdbOptionsPage::instance() && CdbOptionsPage::instance()->options()->enabled; +#else + return false; +#endif +} + +void addCdb2OptionPages(QList<Core::IOptionsPage *> *opts) +{ + opts->push_back(new CdbOptionsPage); +} + +#define QT_CREATOR_CDB_EXT "qtcreatorcdbext" + +static inline Utils::SavedAction *theAssemblerAction() +{ + return Debugger::Internal::debuggerCore()->action(Debugger::Internal::OperateByInstruction); +} + +CdbEngine::CdbEngine(const DebuggerStartParameters &sp, const OptionsPtr &options) : + DebuggerEngine(sp), + m_creatorExtPrefix("<qtcreatorcdbext>|"), + m_tokenPrefix("<token>"), + m_options(options), + m_inferiorPid(0), + m_accessible(false), + m_specialStopMode(NoSpecialStop), + m_nextCommandToken(0), + m_nextBreakpointNumber(1), + m_currentBuiltinCommandIndex(-1), + m_extensionCommandPrefixBA("!"QT_CREATOR_CDB_EXT"."), + m_operateByInstructionPending(true), + m_operateByInstruction(true), // Default CDB setting + m_notifyEngineShutdownOnTermination(false), + m_hasDebuggee(false), + m_elapsedLogTime(0) +{ + Utils::SavedAction *assemblerAction = theAssemblerAction(); + m_operateByInstructionPending = assemblerAction->isChecked(); + connect(assemblerAction, SIGNAL(triggered(bool)), this, SLOT(operateByInstructionTriggered(bool))); + + setObjectName(QLatin1String("CdbEngine")); + connect(&m_process, SIGNAL(finished(int)), this, SLOT(processFinished())); + connect(&m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError())); + connect(&m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(readyReadStandardOut())); + connect(&m_process, SIGNAL(readyReadStandardError()), this, SLOT(readyReadStandardOut())); +} + +CdbEngine::~CdbEngine() +{ +} + +void CdbEngine::operateByInstructionTriggered(bool operateByInstruction) +{ + // To be set next time session becomes accessible + m_operateByInstructionPending = operateByInstruction; +} + +void CdbEngine::setToolTipExpression(const QPoint &mousePos, TextEditor::ITextEditor *editor, int cursorPos) +{ + Q_UNUSED(mousePos) + Q_UNUSED(editor) + Q_UNUSED(cursorPos) +} + +void CdbEngine::setupEngine() +{ + QString errorMessage; + if (!doSetupEngine(&errorMessage)) { // Start engine which will run until initial breakpoint + showMessage(errorMessage, LogError); + notifyEngineSetupFailed(); + } +} + +// Determine full path to the CDB extension library. +static inline QString extensionLibraryName(bool is64Bit) +{ + // Determine extension lib name and path to use + QString rc; + QTextStream(&rc) << QFileInfo(QCoreApplication::applicationDirPath()).path() + << "/lib/" << (is64Bit ? QT_CREATOR_CDB_EXT"64" : QT_CREATOR_CDB_EXT"32") + << '/' << QT_CREATOR_CDB_EXT << ".dll"; + return rc; +} + +// Determine environment for CDB.exe, start out with run config and +// add CDB extension path merged with system value should there be one. +static QStringList mergeEnvironment(QStringList runConfigEnvironment, + QString cdbExtensionPath) +{ + // Determine CDB extension path from Qt Creator + static const char cdbExtensionPathVariableC[] = "_NT_DEBUGGER_EXTENSION_PATH"; + const QByteArray oldCdbExtensionPath = qgetenv(cdbExtensionPathVariableC); + if (!oldCdbExtensionPath.isEmpty()) { + cdbExtensionPath.append(QLatin1Char(';')); + cdbExtensionPath.append(QString::fromLocal8Bit(oldCdbExtensionPath)); + } + // We do not assume someone sets _NT_DEBUGGER_EXTENSION_PATH in the run + // config, just to make sure, delete any existing entries + const QString cdbExtensionPathVariableAssign = + QLatin1String(cdbExtensionPathVariableC) + QLatin1Char('='); + for (QStringList::iterator it = runConfigEnvironment.begin(); it != runConfigEnvironment.end() ; ) { + if (it->startsWith(cdbExtensionPathVariableAssign)) { + it = runConfigEnvironment.erase(it); + break; + } else { + ++it; + } + } + runConfigEnvironment.append(cdbExtensionPathVariableAssign + + QDir::toNativeSeparators(cdbExtensionPath)); + return runConfigEnvironment; +} + +int CdbEngine::elapsedLogTime() const +{ + const int elapsed = m_logTime.elapsed(); + const int delta = elapsed - m_elapsedLogTime; + m_elapsedLogTime = elapsed; + return delta; +} + +bool CdbEngine::doSetupEngine(QString *errorMessage) +{ + m_logTime.start(); + // Determine extension lib name and path to use + // The extension is passed as relative name with the path variable set + //(does not work with absolute path names) + const QFileInfo extensionFi(extensionLibraryName(m_options->is64bit)); + if (!extensionFi.isFile()) { + *errorMessage = QString::fromLatin1("Internal error: The extension %1 cannot be found."). + arg(QDir::toNativeSeparators(extensionFi.absoluteFilePath())); + return false; + } + // Prepare arguments + const DebuggerStartParameters &sp = startParameters(); + QStringList arguments; + // Source line info/No terminal breakpoint / Pull extension + arguments << QLatin1String("-lines") << QLatin1String("-G") + << (QLatin1String("-a") + extensionFi.fileName()) + // register idle (debuggee stop) notification + << QLatin1String("-c") + << QString::fromAscii(".idle_cmd " + m_extensionCommandPrefixBA + "idle"); + if (sp.useTerminal) // Separate console + arguments << QLatin1String("-2"); + if (!m_options->symbolPaths.isEmpty()) + arguments << QLatin1String("-y") << m_options->symbolPaths.join(QString(QLatin1Char(';'))); + if (!m_options->sourcePaths.isEmpty()) + arguments << QLatin1String("-srcpath") << m_options->sourcePaths.join(QString(QLatin1Char(';'))); + switch (sp.startMode) { + case StartInternal: + case StartExternal: + arguments << sp.executable; + foreach(const QString &arg, sp.processArgs) + arguments << arg; // @TODO quoting/env + break; + case AttachExternal: + case AttachCrashedExternal: // @TODO: event handle for crashed? + arguments << QLatin1String("-p") << QString::number(sp.attachPID); + if (sp.startMode == AttachCrashedExternal) + arguments << QLatin1String("-e") << sp.crashParameter << QLatin1String("-g"); + break; + default: + *errorMessage = QString::fromLatin1("Internal error: Unsupported start mode %1.").arg(sp.startMode); + return false; + } + const QString executable = m_options->executable; + const QString msg = QString::fromLatin1("Launching %1 %2\nusing %3 of %4."). + arg(QDir::toNativeSeparators(executable), + arguments.join(QString(QLatin1Char(' '))), + QDir::toNativeSeparators(extensionFi.absoluteFilePath()), + extensionFi.lastModified().toString(Qt::SystemLocaleShortDate)); + showMessage(msg, LogMisc); + + m_outputBuffer.clear(); + m_process.setEnvironment(mergeEnvironment(sp.environment.toStringList(), extensionFi.absolutePath())); + m_process.start(executable, arguments); + if (!m_process.waitForStarted()) { + *errorMessage = QString::fromLatin1("Internal error: Cannot start process %1: %2"). + arg(QDir::toNativeSeparators(executable), m_process.errorString()); + return false; + } +#ifdef Q_OS_WIN + const unsigned long pid = Utils::winQPidToPid(m_process.pid()); +#else + const unsigned long pid = 0; +#endif + showMessage(QString::fromLatin1("%1 running as %2"). + arg(QDir::toNativeSeparators(executable)).arg(pid), LogMisc); + m_hasDebuggee = true; + return true; +} + +void CdbEngine::setupInferior() +{ + attemptBreakpointSynchronization(); + postExtensionCommand("pid", QByteArray(), 0, &CdbEngine::handlePid); +} + +void CdbEngine::runEngine() +{ + postCommand("g", 0); +} + +void CdbEngine::shutdownInferior() +{ + if (debug) + qDebug("CdbEngine::shutdownInferior in state '%s', process running %d", stateName(state()), + isCdbProcessRunning()); + + if (!isCdbProcessRunning()) { // Direct launch: Terminated with process. + if (debug) + qDebug("notifyInferiorShutdownOk"); + notifyInferiorShutdownOk(); + return; + } + if (m_accessible) { + if (startParameters().startMode == AttachExternal || startParameters().startMode == AttachCrashedExternal) + detachDebugger(); + if (debug) + qDebug("notifyInferiorShutdownOk"); + notifyInferiorShutdownOk(); + } else { + interruptInferior(); // Calls us again + } +} + +/* shutdownEngine/processFinished: + * Note that in the case of launching a process by the debugger, the debugger + * automatically quits a short time after reporting the session becoming + * inaccessible without debuggee (notifyInferiorExited). In that case, + * processFinished() must not report any arbitrarily notifyEngineShutdownOk() + * as not to confuse the state engine. + */ + +void CdbEngine::shutdownEngine() +{ + if (debug) + qDebug("CdbEngine::shutdownEngine in state '%s', process running %d", + stateName(state()), isCdbProcessRunning()); + + if (!isCdbProcessRunning()) { // Direct launch: Terminated with process. + if (debug) + qDebug("notifyEngineShutdownOk"); + notifyEngineShutdownOk(); + return; + } + + // detach: Wait for debugger to finish. + if (m_accessible) { + if (startParameters().startMode == AttachExternal) + detachDebugger(); + postCommand("q", 0); + m_notifyEngineShutdownOnTermination = true; + return; + } + // Lost debuggee, debugger should quit anytime now + if (!m_hasDebuggee) { + m_notifyEngineShutdownOnTermination = true; + return; + } + interruptInferior(); +} + +void CdbEngine::processFinished() +{ + if (debug) + qDebug("CdbEngine::processFinished %dms '%s' notify=%d (exit state=%d, ex=%d)", + elapsedLogTime(), stateName(state()), m_notifyEngineShutdownOnTermination, + m_process.exitStatus(), m_process.exitCode()); + + const bool crashed = m_process.exitStatus() == QProcess::CrashExit; + if (crashed) { + showMessage(tr("CDB crashed"), LogError); // not in your life. + } else { + showMessage(tr("CDB exited (%1)").arg(m_process.exitCode()), LogMisc); + } + + if (m_notifyEngineShutdownOnTermination) { + if (crashed) { + if (debug) + qDebug("notifyEngineIll"); + notifyEngineIll(); + } else { + if (debug) + qDebug("notifyEngineShutdownOk"); + notifyEngineShutdownOk(); + } + } else { + if (debug) + qDebug("notifyEngineSpontaneousShutdown"); + notifyEngineSpontaneousShutdown(); + } +} + +void CdbEngine::detachDebugger() +{ + postCommand(".detach", 0); +} + +void CdbEngine::updateWatchData(const Debugger::Internal::WatchData &dataIn, + const Debugger::Internal::WatchUpdateFlags & flags) +{ + if (debug) + qDebug("CdbEngine::updateWatchData() %dms %s incr=%d: %s", + elapsedLogTime(), stateName(state()), + flags.tryIncremental, + qPrintable(dataIn.toString())); + + if (!dataIn.hasChildren) { + Debugger::Internal::WatchData data = dataIn; + data.setAllUnneeded(); + watchHandler()->insertData(data); + return; + } + updateLocalVariable(dataIn.iname); +} + +void CdbEngine::updateLocalVariable(const QByteArray &iname) +{ + const int stackFrame = stackHandler()->currentIndex(); + if (stackFrame >= 0) { + QByteArray localsArguments; + ByteArrayInputStream str(localsArguments); + str << stackFrame << ' ' << iname; + postExtensionCommand("locals", localsArguments, 0, &CdbEngine::handleLocals); + } else { + qWarning("Internal error; no stack frame in updateLocalVariable"); + } +} + +unsigned CdbEngine::debuggerCapabilities() const +{ + return DisassemblerCapability | RegisterCapability | ShowMemoryCapability + |WatchpointCapability|JumpToLineCapability + |BreakOnThrowAndCatchCapability; // Sort-of: Can break on throw(). +} + +void CdbEngine::executeStep() +{ + postCommand(QByteArray("t"), 0); // Step into-> t (trace) + notifyInferiorRunRequested(); +} + +void CdbEngine::executeStepOut() +{ + postCommand(QByteArray("gu"), 0); // Step out-> gu (go up) + notifyInferiorRunRequested(); +} + +void CdbEngine::executeNext() +{ + postCommand(QByteArray("p"), 0); // Step over -> p + notifyInferiorRunRequested(); +} + +void CdbEngine::executeStepI() +{ + executeStep(); +} + +void CdbEngine::executeNextI() +{ + executeNext(); +} + +void CdbEngine::continueInferior() +{ + notifyInferiorRunRequested(); + doContinueInferior(); +} + +void CdbEngine::doContinueInferior() +{ + postCommand(QByteArray("g"), 0); +} + +void CdbEngine::interruptInferior() +{ + doInterruptInferior(NoSpecialStop); +} + +void CdbEngine::doInterruptInferior(SpecialStopMode sm) +{ +#ifdef Q_OS_WIN + const SpecialStopMode oldSpecialMode = m_specialStopMode; + m_specialStopMode = sm; + QString errorMessage; + if (!Debugger::Internal::winDebugBreakProcess(m_inferiorPid, &errorMessage)) { + m_specialStopMode = oldSpecialMode; + showMessage(errorMessage, LogError); + } +#else + Q_UNUSED(sm) +#endif +} + +void CdbEngine::executeRunToLine(const QString &fileName, int lineNumber) +{ + // Add one-shot breakpoint + Debugger::Internal::BreakpointParameters bp(Debugger::Internal::BreakpointByFileAndLine); + bp.fileName = fileName; + bp.lineNumber = lineNumber; + postCommand(cdbAddBreakpointCommand(bp, true), 0); + continueInferior(); +} + +void CdbEngine::executeRunToFunction(const QString &functionName) +{ + // Add one-shot breakpoint + Debugger::Internal::BreakpointParameters bp(Debugger::Internal::BreakpointByFunction); + bp.functionName = functionName; + + postCommand(cdbAddBreakpointCommand(bp, true), 0); + continueInferior(); +} + +void CdbEngine::setRegisterValue(int regnr, const QString &value) +{ + const Debugger::Internal::Registers registers = registerHandler()->registers(); + QTC_ASSERT(regnr < registers.size(), return) + // Value is decimal or 0x-hex-prefixed + QByteArray cmd; + ByteArrayInputStream str(cmd); + str << "r " << registers.at(regnr).name << '=' << value; + postCommand(cmd, 0); + reloadRegisters(); +} + +void CdbEngine::executeJumpToLine(const QString & fileName, int lineNumber) +{ + QByteArray cmd; + ByteArrayInputStream str(cmd); + // Resolve source line address and go to that location + str << "? `" << QDir::toNativeSeparators(fileName) << ':' << lineNumber << '`'; + const QVariant cookie = qVariantFromValue(SourceLocationCookie(fileName, lineNumber)); + postBuiltinCommand(cmd, 0, &CdbEngine::handleJumpToLineAddressResolution, 0, cookie); +} + +void CdbEngine::handleJumpToLineAddressResolution(const CdbBuiltinCommandPtr &cmd) +{ + if (cmd->reply.isEmpty()) + return; + // Evaluate expression: 5365511549 = 00000001`3fcf357d + // Set register 'rip' to hex address and goto lcoation + QByteArray answer = cmd->reply.front(); + const int equalPos = answer.indexOf(" = "); + if (equalPos == -1) + return; + answer.remove(0, equalPos + 3); + QTC_ASSERT(qVariantCanConvert<SourceLocationCookie>(cmd->cookie), return ; ) + const SourceLocationCookie cookie = qvariant_cast<SourceLocationCookie>(cmd->cookie); + + QByteArray registerCmd; + ByteArrayInputStream str(registerCmd); + str << "r rip=0x" << answer; + postCommand(registerCmd, 0); + gotoLocation(cookie.fileName, cookie.lineNumber, true); +} + +void CdbEngine::assignValueInDebugger(const Debugger::Internal::WatchData *w, const QString &expr, const QVariant &value) +{ + if (debug) + qDebug() << "CdbEngine::assignValueInDebugger" << w->iname << expr << value; + + if (state() != InferiorStopOk || stackHandler()->currentIndex() < 0) { + qWarning("Internal error: assignValueInDebugger: Invalid state or no stack frame."); + return; + } + + QByteArray cmd; + ByteArrayInputStream str(cmd); + str << m_extensionCommandPrefixBA << "assign " << w->iname << '=' << value.toString(); + postCommand(cmd, 0); + updateLocalVariable(w->iname); +} + +void CdbEngine::handleThreads(const CdbExtensionCommandPtr &reply) +{ + if (debug) + qDebug("CdbEngine::handleThreads success=%d", reply->success); + if (reply->success) { + Debugger::Internal::GdbMi data; + data.fromString(reply->reply); + int currentThreadId; + Debugger::Internal::Threads threads = Debugger::Internal::ThreadsHandler::parseGdbmiThreads(data, ¤tThreadId); + threadsHandler()->setThreads(threads); + threadsHandler()->setCurrentThreadId(currentThreadId); + // Continue sequence + postCommandSequence(reply->commandSequence); + } else { + showMessage(QString::fromLatin1(reply->errorMessage), LogError); + } +} + +void CdbEngine::executeDebuggerCommand(const QString &command) +{ + postCommand(command.toLocal8Bit(), QuietCommand); +} + +// Post command without callback +void CdbEngine::postCommand(const QByteArray &cmd, unsigned flags) +{ + if (debug) + qDebug("CdbEngine::postCommand %dms '%s' %u %s\n", + elapsedLogTime(), cmd.constData(), flags, stateName(state())); + if (!(flags & QuietCommand)) + showMessage(QString::fromLocal8Bit(cmd), LogInput); + m_process.write(cmd + '\n'); +} + +// Post a built-in-command producing free-format output with a callback. +// In order to catch the output, it is enclosed in 'echo' commands +// printing a specially formatted token to be identifiable in the output. +void CdbEngine::postBuiltinCommand(const QByteArray &cmd, unsigned flags, + BuiltinCommandHandler handler, + unsigned nextCommandFlag, + const QVariant &cookie) +{ + if (!m_accessible) { + const QString msg = QString::fromLatin1("Attempt to issue builtin command '%1' to non-accessible session (%2)") + .arg(QString::fromLocal8Bit(cmd), QString::fromAscii(stateName(state()))); + showMessage(msg, LogError); + return; + } + if (!flags & QuietCommand) + showMessage(QString::fromLocal8Bit(cmd), LogInput); + + const int token = m_nextCommandToken++; + CdbBuiltinCommandPtr pendingCommand(new CdbBuiltinCommand(cmd, token, flags, handler, nextCommandFlag, cookie)); + + m_builtinCommandQueue.push_back(pendingCommand); + // Enclose command in echo-commands for token + QByteArray fullCmd; + ByteArrayInputStream str(fullCmd); + str << ".echo \"" << m_tokenPrefix << token << "<\"\n" + << cmd << "\n.echo \"" << m_tokenPrefix << token << ">\"\n"; + if (debug) + qDebug("CdbEngine::postBuiltinCommand %dms '%s' flags=%u token=%d %s next=%u, cookie='%s', pending=%d, sequence=0x%x", + elapsedLogTime(), cmd.constData(), flags, token, stateName(state()), nextCommandFlag, qPrintable(cookie.toString()), + m_builtinCommandQueue.size(), nextCommandFlag); + if (debug > 1) + qDebug("CdbEngine::postBuiltinCommand: resulting command '%s'\n", + fullCmd.constData()); + m_process.write(fullCmd); +} + +// Post an extension command producing one-line output with a callback, +// pass along token for identification in queue. +void CdbEngine::postExtensionCommand(const QByteArray &cmd, + const QByteArray &arguments, + unsigned flags, + ExtensionCommandHandler handler, + unsigned nextCommandFlag, + const QVariant &cookie) +{ + if (!m_accessible) { + const QString msg = QString::fromLatin1("Attempt to issue extension command '%1' to non-accessible session (%2)") + .arg(QString::fromLocal8Bit(cmd), QString::fromAscii(stateName(state()))); + showMessage(msg, LogError); + return; + } + if (!flags & QuietCommand) + showMessage(QString::fromLocal8Bit(cmd), LogInput); + + const int token = m_nextCommandToken++; + + // Format full command with token to be recognizeable in the output + QByteArray fullCmd; + ByteArrayInputStream str(fullCmd); + str << m_extensionCommandPrefixBA << cmd << " -t " << token; + if (!arguments.isEmpty()) + str << ' ' << arguments; + + CdbExtensionCommandPtr pendingCommand(new CdbExtensionCommand(fullCmd, token, flags, handler, nextCommandFlag, cookie)); + + m_extensionCommandQueue.push_back(pendingCommand); + // Enclose command in echo-commands for token + if (debug) + qDebug("CdbEngine::postExtensionCommand %dms '%s' flags=%u token=%d %s next=%u, cookie='%s', pending=%d, sequence=0x%x", + elapsedLogTime(), fullCmd.constData(), flags, token, stateName(state()), nextCommandFlag, qPrintable(cookie.toString()), + m_extensionCommandQueue.size(), nextCommandFlag); + m_process.write(fullCmd + '\n'); +} + +void CdbEngine::activateFrame(int index) +{ + // TODO: assembler,etc + if (index < 0) + return; + const Debugger::Internal::StackFrames &frames = stackHandler()->frames(); + QTC_ASSERT(index < frames.size(), return; ) + + if (debug) + qDebug("activateFrame idx=%d '%s' %d", index, + qPrintable(frames.at(index).file), frames.at(index).line); + stackHandler()->setCurrentIndex(index); + const bool showAssembler = !frames.at(index).isUsable(); + if (showAssembler) { // Assembly code: Clean out model and force instruction mode. + watchHandler()->beginCycle(); + watchHandler()->endCycle(); + QAction *assemblerAction = theAssemblerAction(); + if (assemblerAction->isChecked()) { + gotoLocation(frames.at(index), true); + } else { + assemblerAction->trigger(); // Seems to trigger update + } + return; + } + gotoLocation(frames.at(index), true); + // Watchers: Initial expand and query + const QSet<QByteArray> expanded = watchHandler()->expandedINames(); + if (!expanded.isEmpty()) { + QByteArray expandArguments; + ByteArrayInputStream expandStr(expandArguments); + expandStr << index << ' '; + int i = 0; + foreach(const QByteArray &e, expanded) { + if (i++) + expandStr << ','; + expandStr << e; + } + postExtensionCommand("expandlocals", expandArguments, 0, &CdbEngine::handleExpandLocals); + } + + watchHandler()->beginCycle(); + postExtensionCommand("locals", QByteArray::number(index), 0, &CdbEngine::handleLocals); +} + +void CdbEngine::selectThread(int index) +{ + if (index < 0 || index == threadsHandler()->currentThread()) + return; + + resetLocation(); + const int newThreadId = threadsHandler()->threads().at(index).id; + threadsHandler()->setCurrentThread(index); + + const QByteArray cmd = "~" + QByteArray::number(newThreadId) + " s"; + postBuiltinCommand(cmd, 0, &CdbEngine::dummyHandler, CommandListStack); +} + +void CdbEngine::fetchDisassembler(Debugger::Internal::DisassemblerViewAgent *agent) +{ + QTC_ASSERT(m_accessible, return;) + QByteArray cmd; + ByteArrayInputStream str(cmd); + str << "u " << hex << hexPrefixOn << agent->address() << " L40"; + const QVariant cookie = qVariantFromValue<Debugger::Internal::DisassemblerViewAgent*>(agent); + postBuiltinCommand(cmd, 0, &CdbEngine::handleDisassembler, 0, cookie); +} + +// Parse: "00000000`77606060 cc int 3" +void CdbEngine::handleDisassembler(const CdbBuiltinCommandPtr &command) +{ + QTC_ASSERT(qVariantCanConvert<Debugger::Internal::DisassemblerViewAgent*>(command->cookie), return;) + Debugger::Internal::DisassemblerViewAgent *agent = qvariant_cast<Debugger::Internal::DisassemblerViewAgent*>(command->cookie); + agent->setContents(formatCdbDisassembler(command->reply)); +} + +void CdbEngine::fetchMemory(Debugger::Internal::MemoryViewAgent *agent, QObject *editor, quint64 addr, quint64 length) +{ + QTC_ASSERT(m_accessible, return;) + if (debug) + qDebug("CdbEngine::fetchMemory %llu bytes from 0x%llx", length, addr); + + QByteArray args; + ByteArrayInputStream str(args); + str << addr << ' ' << length; + const QVariant cookie = qVariantFromValue<MemoryViewCookie>(MemoryViewCookie(agent, editor, addr, length)); + postExtensionCommand("memory", args, 0, &CdbEngine::handleMemory, 0, cookie); +} + +void CdbEngine::handleMemory(const CdbExtensionCommandPtr &command) +{ + QTC_ASSERT(qVariantCanConvert<MemoryViewCookie>(command->cookie), return;) + const MemoryViewCookie memViewCookie = qvariant_cast<MemoryViewCookie>(command->cookie); + if (command->success) { + const QByteArray data = QByteArray::fromBase64(command->reply); + if (unsigned(data.size()) == memViewCookie.length) + memViewCookie.agent->addLazyData(memViewCookie.editorToken, + memViewCookie.address, data); + } else { + showMessage(QString::fromLocal8Bit(command->errorMessage), LogError); + } +} + +void CdbEngine::reloadModules() +{ + postCommandSequence(CommandListModules); +} + +void CdbEngine::loadSymbols(const QString & /* moduleName */) +{ +} + +void CdbEngine::loadAllSymbols() +{ +} + +void CdbEngine::requestModuleSymbols(const QString &moduleName) +{ + Q_UNUSED(moduleName) +} + +void CdbEngine::reloadRegisters() +{ + postCommandSequence(CommandListRegisters); +} + +void CdbEngine::reloadSourceFiles() +{ +} + +void CdbEngine::reloadFullStack() +{ + if (debug) + qDebug("%s", Q_FUNC_INFO); + postCommandSequence(CommandListStack); +} + +void CdbEngine::handlePid(const CdbExtensionCommandPtr &reply) +{ + if (reply->success) { + m_inferiorPid = reply->reply.toUInt(); + showMessage(QString::fromLatin1("Inferior pid: %1."), LogMisc); + notifyInferiorSetupOk(); + } else { + showMessage(QString::fromLatin1("Failed to determine inferior pid: %1"). + arg(QLatin1String(reply->errorMessage)), LogError); + notifyInferiorSetupFailed(); + } +} + +// Parse CDB gdbmi register syntax +static inline Debugger::Internal::Register parseRegister(const Debugger::Internal::GdbMi &gdbmiReg) +{ + Debugger::Internal::Register reg; + reg.name = gdbmiReg.findChild("name").data(); + const Debugger::Internal::GdbMi description = gdbmiReg.findChild("description"); + if (description.type() != Debugger::Internal::GdbMi::Invalid) { + reg.name += " ("; + reg.name += description.data(); + reg.name += ')'; + } + reg.value = QString::fromAscii(gdbmiReg.findChild("value").data()); + return reg; +} + +void CdbEngine::handleModules(const CdbExtensionCommandPtr &reply) +{ + if (reply->success) { + Debugger::Internal::GdbMi value; + value.fromString(reply->reply); + if (value.type() == Debugger::Internal::GdbMi::List) { + Debugger::Internal::Modules modules; + modules.reserve(value.childCount()); + foreach (const Debugger::Internal::GdbMi &gdbmiModule, value.children()) { + Debugger::Internal::Module module; + module.moduleName = QString::fromAscii(gdbmiModule.findChild("name").data()); + module.modulePath = QString::fromAscii(gdbmiModule.findChild("image").data()); + module.startAddress = QString::fromAscii(gdbmiModule.findChild("start").data()); + module.endAddress = QString::fromAscii(gdbmiModule.findChild("end").data()); + if (gdbmiModule.findChild("deferred").type() == Debugger::Internal::GdbMi::Invalid) + module.symbolsRead = Debugger::Internal::Module::ReadOk; + modules.push_back(module); + } + modulesHandler()->setModules(modules); + } else { + showMessage(QString::fromLatin1("Parse error in modules response."), LogError); + qWarning("Parse error in modules response:\n%s", reply->reply.constData()); + } + } else { + showMessage(QString::fromLatin1("Failed to determine modules: %1"). + arg(QLatin1String(reply->errorMessage)), LogError); + } + postCommandSequence(reply->commandSequence); + +} + +void CdbEngine::handleRegisters(const CdbExtensionCommandPtr &reply) +{ + if (reply->success) { + Debugger::Internal::GdbMi value; + value.fromString(reply->reply); + if (value.type() == Debugger::Internal::GdbMi::List) { + Debugger::Internal::Registers registers; + registers.reserve(value.childCount()); + foreach (const Debugger::Internal::GdbMi &gdbmiReg, value.children()) + registers.push_back(parseRegister(gdbmiReg)); + registerHandler()->setRegisters(registers); + } else { + showMessage(QString::fromLatin1("Parse error in registers response."), LogError); + qWarning("Parse error in registers response:\n%s", reply->reply.constData()); + } + } else { + showMessage(QString::fromLatin1("Failed to determine registers: %1"). + arg(QLatin1String(reply->errorMessage)), LogError); + } + postCommandSequence(reply->commandSequence); +} + +void CdbEngine::handleLocals(const CdbExtensionCommandPtr &reply) +{ + if (reply->success) { + QList<Debugger::Internal::WatchData> watchData; + if (Debugger::Internal::QtDumperHelper::parseValue(reply->reply.constData(), &watchData)) { + for (int i = 0; i < watchData.size(); i++) + watchData[i].setAllUnneeded(); + if (debug > 1) { + QDebug nsp = qDebug().nospace(); + nsp << "Obtained " << watchData.size() << " items:\n"; + foreach (const Debugger::Internal::WatchData &wd, watchData) + nsp << wd.toString() <<'\n'; + } + watchHandler()->insertBulkData(watchData); + watchHandler()->endCycle(); + } else { + showMessage(QString::fromLatin1("Parse error in locals response."), LogError); + qWarning("Parse error in locals response:\n%s", reply->reply.constData()); + } + } else { + showMessage(QString::fromLatin1(reply->errorMessage), LogError); + } +} + +void CdbEngine::handleExpandLocals(const CdbExtensionCommandPtr &reply) +{ + if (!reply->success) + showMessage(QString::fromLatin1(reply->errorMessage), LogError); +} + +enum CdbExecutionStatus { +CDB_STATUS_NO_CHANGE=0, CDB_STATUS_GO = 1, CDB_STATUS_GO_HANDLED = 2, +CDB_STATUS_GO_NOT_HANDLED = 3, CDB_STATUS_STEP_OVER = 4, +CDB_STATUS_STEP_INTO = 5, CDB_STATUS_BREAK = 6, CDB_STATUS_NO_DEBUGGEE = 7, +CDB_STATUS_STEP_BRANCH = 8, CDB_STATUS_IGNORE_EVENT = 9, +CDB_STATUS_RESTART_REQUESTED = 10, CDB_STATUS_REVERSE_GO = 11, +CDB_STATUS_REVERSE_STEP_BRANCH = 12, CDB_STATUS_REVERSE_STEP_OVER = 13, +CDB_STATUS_REVERSE_STEP_INTO = 14 }; + +static const char *cdbStatusName(unsigned long s) +{ + switch (s) { + case CDB_STATUS_NO_CHANGE: + return "No change"; + case CDB_STATUS_GO: + return "go"; + case CDB_STATUS_GO_HANDLED: + return "go_handled"; + case CDB_STATUS_GO_NOT_HANDLED: + return "go_not_handled"; + case CDB_STATUS_STEP_OVER: + return "step_over"; + case CDB_STATUS_STEP_INTO: + return "step_into"; + case CDB_STATUS_BREAK: + return "break"; + case CDB_STATUS_NO_DEBUGGEE: + return "no_debuggee"; + case CDB_STATUS_STEP_BRANCH: + return "step_branch"; + case CDB_STATUS_IGNORE_EVENT: + return "ignore_event"; + case CDB_STATUS_RESTART_REQUESTED: + return "restart_requested"; + case CDB_STATUS_REVERSE_GO: + return "reverse_go"; + case CDB_STATUS_REVERSE_STEP_BRANCH: + return "reverse_step_branch"; + case CDB_STATUS_REVERSE_STEP_OVER: + return "reverse_step_over"; + case CDB_STATUS_REVERSE_STEP_INTO: + return "reverse_step_into"; + } + return "unknown"; +} + +void CdbEngine::handleSessionIdle(const QByteArray &message) +{ + if (!m_hasDebuggee) + return; + + if (debug) + qDebug("CdbEngine::handleSessionIdle %dms '%s' in state '%s', special mode %d", + elapsedLogTime(), message.constData(), + stateName(state()), m_specialStopMode); + + // Switch source level debugging + if (m_operateByInstructionPending != m_operateByInstruction) { + m_operateByInstruction = m_operateByInstructionPending; + postCommand(m_operateByInstruction ? QByteArray("l-t") : QByteArray("l+t"), 0); + postCommand(m_operateByInstruction ? QByteArray("l-s") : QByteArray("l+s"), 0); + } + + const SpecialStopMode specialStopMode = m_specialStopMode; + m_specialStopMode = NoSpecialStop; + + switch(specialStopMode) { + case SpecialStopSynchronizeBreakpoints: + if (debug) + qDebug("attemptBreakpointSynchronization in special stop"); + attemptBreakpointSynchronization(); + doContinueInferior(); + return; + case NoSpecialStop: + break; + } + switch(state()) { // Temporary stop at beginning + case EngineSetupRequested: + if (debug) + qDebug("notifyEngineSetupOk"); + notifyEngineSetupOk(); + return; + case InferiorSetupRequested: + return; + case InferiorStopRequested: + case InferiorRunOk: + break; // Proper stop of inferior handled below. + + default: + qWarning("WARNING: CdbEngine::handleSessionAccessible called in state %s", stateName(state())); + return; + } + // Handle stop. + if (state() == InferiorStopRequested) { + if (debug) + qDebug("notifyInferiorStopOk"); + notifyInferiorStopOk(); + } else { + if (debug) + qDebug("notifyInferiorSpontaneousStop"); + notifyInferiorSpontaneousStop(); + } + // Start sequence to get all relevant data. Hack: Avoid module reload? + unsigned sequence = CommandListStack|CommandListRegisters|CommandListThreads; + if (modulesHandler()->modules().size() == 0) + sequence |= CommandListModules; + postCommandSequence(sequence); + // Report stop reason (GDBMI) + Debugger::Internal::GdbMi stopReason; + stopReason.fromString(message); + if (debug) + qDebug("%s", stopReason.toString(true, 4).constData()); + const QByteArray reason = stopReason.findChild("reason").data(); + if (reason.isEmpty()) { + showStatusMessage(tr("Malformed stop response received."), LogError); + return; + } + const int threadId = stopReason.findChild("threadId").data().toInt(); + if (reason == "breakpoint") { + const int number = stopReason.findChild("breakpointId").data().toInt(); + const BreakpointId id = breakHandler()->findBreakpointByNumber(number); + if (id != BreakpointId(-1) && breakHandler()->type(id) == Debugger::Internal::Watchpoint) { + showStatusMessage(msgWatchpointTriggered(id, number, breakHandler()->address(id), QString::number(threadId))); + } else { + showStatusMessage(msgBreakpointTriggered(id, number, QString::number(threadId))); + } + return; + } + if (reason == "exception") { + WinException exception; + exception.fromGdbMI(stopReason); +#ifdef Q_OS_WIN + if (Debugger::Internal::isDebuggerWinException(exception.exceptionCode)) { + showStatusMessage(msgInterrupted()); + return; + } +#endif + const QString description = exception.toString(); + showStatusMessage(msgStoppedByException(description, QString::number(threadId))); + showStoppedByExceptionMessageBox(description); + return; + } + showStatusMessage(msgStopped(QLatin1String(reason))); +} + +void CdbEngine::handleSessionAccessible(unsigned long cdbExState) +{ + const DebuggerState s = state(); + if (!m_hasDebuggee || s == InferiorRunOk) // suppress reports + return; + + if (debug) + qDebug("CdbEngine::handleSessionAccessible %dms in state '%s'/'%s', special mode %d", + elapsedLogTime(), cdbStatusName(cdbExState), stateName(state()), m_specialStopMode); + + switch(s) { + case EngineShutdownRequested: + shutdownEngine(); + break; + case InferiorShutdownRequested: + shutdownInferior(); + break; + default: + break; + } +} + +void CdbEngine::handleSessionInaccessible(unsigned long cdbExState) +{ + const DebuggerState s = state(); + + // suppress reports + if (!m_hasDebuggee || (s == InferiorRunOk && cdbExState != CDB_STATUS_NO_DEBUGGEE)) + return; + + if (debug) + qDebug("CdbEngine::handleSessionInaccessible %dms in state '%s', '%s', special mode %d", + elapsedLogTime(), cdbStatusName(cdbExState), stateName(state()), m_specialStopMode); + + switch (state()) { + case EngineSetupRequested: + break; + case EngineRunRequested: + if (debug) + qDebug("notifyEngineRunAndInferiorRunOk"); + notifyEngineRunAndInferiorRunOk(); + break; + case InferiorRunOk: + case InferiorStopOk: + // Inaccessible without debuggee (exit breakpoint) + // We go for spontaneous engine shutdown instead. + if (cdbExState == CDB_STATUS_NO_DEBUGGEE) { + if (debug) + qDebug("Lost debuggeee"); + m_hasDebuggee = false; + } + break; + case InferiorRunRequested: + if (debug) + qDebug("notifyInferiorRunOk"); + notifyInferiorRunOk(); + resetLocation(); + break; + case EngineShutdownRequested: + break; + default: + break; + } +} + +void CdbEngine::handleExtensionMessage(char t, int token, const QByteArray &what, const QByteArray &message) +{ + if (debug > 1) { + QDebug nospace = qDebug().nospace(); + nospace << "handleExtensionMessage " << t << ' ' << token << ' ' << what + << ' ' << stateName(state()); + if (t == 'N' || debug > 1) { + nospace << ' ' << message; + } else { + nospace << ' ' << message.size() << " bytes"; + } + } + + // Is there a reply expected, some command queued? + if (t == 'R' || t == 'N') { + const int index = indexOfCommand(m_extensionCommandQueue, token); + if (index != -1) { + // Did the command finish? Take off queue and complete, invoke CB + const CdbExtensionCommandPtr command = m_extensionCommandQueue.takeAt(index); + if (t == 'R') { + command->success = true; + command->reply = message; + } else { + command->success = false; + command->errorMessage = message; + } + if (debug) + qDebug("### Completed extension command '%s', token=%d, pending=%d", + command->command.constData(), command->token, m_extensionCommandQueue.size()); + if (command->handler) + (this->*(command->handler))(command); + return; + } + } + + if (what == "debuggee_output") { + showMessage(StringFromBase64EncodedUtf16(message), AppOutput); + return; + } + + if (what == "event") { + showStatusMessage(QString::fromAscii(message), 5000); + return; + } + + if (what == "session_accessible") { + if (!m_accessible) { + m_accessible = true; + handleSessionAccessible(message.toULong()); + } + return; + } + + if (what == "session_inaccessible") { + if (m_accessible) { + m_accessible = false; + handleSessionInaccessible(message.toULong()); + } + return; + } + + if (what == "session_idle") { + handleSessionIdle(message); + return; + } + + if (what == "exception") { + WinException exception; + Debugger::Internal::GdbMi gdbmi; + gdbmi.fromString(message); + exception.fromGdbMI(gdbmi); + const QString message = exception.toString(true); + showStatusMessage(message); +#ifdef Q_OS_WIN // Report C++ exception in application output as well. + if (exception.exceptionCode == Debugger::Internal::winExceptionCppException) + showMessage(message + QLatin1Char('\n'), AppOutput); +#endif + return; + } + + return; +} + +// Check for a CDB prompt '0:000> ' ('process:thread> ')..no regexps for QByteArray... +enum { CdbPromptLength = 7 }; + +static inline bool isCdbPrompt(const QByteArray &c) +{ + return c.size() >= CdbPromptLength && c.at(6) == ' ' && c.at(5) == '>' && c.at(1) == ':' + && std::isdigit(c.at(0)) && std::isdigit(c.at(2)) && std::isdigit(c.at(3)) + && std::isdigit(c.at(4)); +} + +// Check for '<token>32>' or '<token>32<' +static inline bool checkCommandToken(const QByteArray &tokenPrefix, const QByteArray &c, + int *token, bool *isStart) +{ + *token = 0; + *isStart = false; + const int tokenPrefixSize = tokenPrefix.size(); + const int size = c.size(); + if (size < tokenPrefixSize + 2 || !std::isdigit(c.at(tokenPrefixSize))) + return false; + switch (c.at(size - 1)) { + case '>': + *isStart = false; + break; + case '<': + *isStart = true; + break; + default: + return false; + } + if (!c.startsWith(tokenPrefix)) + return false; + bool ok; + *token = c.mid(tokenPrefixSize, size - tokenPrefixSize - 1).toInt(&ok); + return ok; +} + +void CdbEngine::parseOutputLine(QByteArray line) +{ + // The hooked output callback in the extension suppresses prompts, + // it should happen only in initial and exit stages. Note however that + // if the output is not hooked, sequences of prompts are possible which + // can mix things up. + while (isCdbPrompt(line)) + line.remove(0, CdbPromptLength); + // An extension notification + if (line.startsWith(m_creatorExtPrefix)) { + // "<qtcreatorcdbext>|type_char|token|serviceName|message" + const char type = line.at(m_creatorExtPrefix.size()); + // integer token + const int tokenPos = m_creatorExtPrefix.size() + 2; + const int tokenEndPos = line.indexOf('|', tokenPos); + QTC_ASSERT(tokenEndPos != -1, return) + const int token = line.mid(tokenPos, tokenEndPos - tokenPos).toInt(); + // const char 'serviceName' + const int whatPos = tokenEndPos + 1; + const int whatEndPos = line.indexOf('|', whatPos); + QTC_ASSERT(whatEndPos != -1, return) + const QByteArray what = line.mid(whatPos, whatEndPos - whatPos); + // Message + const QByteArray message = line.mid(whatEndPos + 1); + handleExtensionMessage(type, token, what, message); + return; + } + // Check for command start/end tokens within which the builtin command + // output is enclosed + int token = 0; + bool isStartToken = false; + const bool isCommandToken = checkCommandToken(m_tokenPrefix, line, &token, &isStartToken); + if (debug > 1) + qDebug("Reading CDB stdout '%s',\n isCommand=%d, token=%d, isStart=%d, current=%d", + line.constData(), isCommandToken, token, isStartToken, m_currentBuiltinCommandIndex); + + // If there is a current command, wait for end of output indicated by token, + // command, trigger handler and finish, else append to its output. + if (m_currentBuiltinCommandIndex != -1) { + QTC_ASSERT(!isStartToken && m_currentBuiltinCommandIndex < m_builtinCommandQueue.size(), return; ); + const CdbBuiltinCommandPtr ¤tCommand = m_builtinCommandQueue.at(m_currentBuiltinCommandIndex); + if (isCommandToken) { + // Did the command finish? Invoke callback and remove from queue. + if (debug) + qDebug("### Completed builtin command '%s', token=%d, %d lines, pending=%d", + currentCommand->command.constData(), currentCommand->token, + currentCommand->reply.size(), m_builtinCommandQueue.size() - 1); + QTC_ASSERT(token == currentCommand->token, return; ); + if (currentCommand->handler) + (this->*(currentCommand->handler))(currentCommand); + m_builtinCommandQueue.removeAt(m_currentBuiltinCommandIndex); + m_currentBuiltinCommandIndex = -1; + } else { + // Record output of current command + currentCommand->reply.push_back(line); + } + return; + } // m_currentCommandIndex + if (isCommandToken) { + // Beginning command token encountered, start to record output. + const int index = indexOfCommand(m_builtinCommandQueue, token); + QTC_ASSERT(isStartToken && index != -1, return; ); + m_currentBuiltinCommandIndex = index; + const CdbBuiltinCommandPtr ¤tCommand = m_builtinCommandQueue.at(m_currentBuiltinCommandIndex); + if (debug) + qDebug("### Gathering output for '%s' token %d", currentCommand->command.constData(), currentCommand->token); + return; + } + + showMessage(QString::fromLocal8Bit(line), LogMisc); +} + +void CdbEngine::readyReadStandardOut() +{ + m_outputBuffer += m_process.readAllStandardOutput(); + // Split into lines and parse line by line. + while (true) { + const int endOfLinePos = m_outputBuffer.indexOf('\n'); + if (endOfLinePos == -1) { + break; + } else { + // Check for '\r\n' + QByteArray line = m_outputBuffer.left(endOfLinePos); + if (!line.isEmpty() && line.at(line.size() - 1) == '\r') + line.truncate(line.size() - 1); + parseOutputLine(line); + m_outputBuffer.remove(0, endOfLinePos + 1); + } + } +} + +void CdbEngine::readyReadStandardError() +{ + showMessage(QString::fromLocal8Bit(m_process.readAllStandardError()), LogError); +} + +void CdbEngine::processError() +{ + showMessage(m_process.errorString(), LogError); +} + +#if 0 +// Join breakpoint ids for a multi-breakpoint id commands like 'bc', 'be', 'bd' +static QByteArray multiBreakpointCommand(const char *cmdC, const Debugger::Internal::Breakpoints &bps) +{ + QByteArray cmd(cmdC); + ByteArrayInputStream str(cmd); + foreach(const Debugger::Internal::BreakpointData *bp, bps) + str << ' ' << bp->bpNumber; + return cmd; +} +#endif + +// Figure out what kind of changes are required to synchronize +enum BreakPointSyncType { + BreakpointsUnchanged, BreakpointsAdded, BreakpointsRemovedChanged +}; + +static inline BreakPointSyncType breakPointSyncType(const Debugger::Internal::BreakHandler *handler, + const Debugger::Internal::BreakpointIds ids) +{ + bool added = false; + foreach (BreakpointId id, ids) { + const Debugger::Internal::BreakpointState state = handler->state(id); + if (debugBreakpoints > 1) + qDebug(" Checking on breakpoint %llu, state %d\n", id, state); + switch (state) { + case Debugger::Internal::BreakpointInsertRequested: + added = true; + break; + case Debugger::Internal::BreakpointChangeRequested: + case Debugger::Internal::BreakpointRemoveRequested: + return BreakpointsRemovedChanged; + default: + break; + } + } + return added ? BreakpointsAdded : BreakpointsUnchanged; +} + +void CdbEngine::attemptBreakpointSynchronization() +{ + // Check if there is anything to be done at all. + Debugger::Internal::BreakHandler *handler = breakHandler(); + // Take ownership of the breakpoint. Requests insertion. TODO: Cpp only? + foreach (Debugger::BreakpointId id, handler->unclaimedBreakpointIds()) + if (acceptsBreakpoint(id)) + handler->setEngine(id, this); + + // Find out if there is a need to synchronize again + const Debugger::Internal::BreakpointIds ids = handler->engineBreakpointIds(this); + const BreakPointSyncType syncType = breakPointSyncType(handler, ids); + if (debugBreakpoints) + qDebug("attemptBreakpointSynchronizationI %dms accessible=%d, %s %d breakpoints, syncType=%d", + elapsedLogTime(), m_accessible, stateName(state()), ids.size(), syncType); + if (syncType == BreakpointsUnchanged) + return; + + if (!m_accessible) { + // No nested calls. + if (m_specialStopMode != SpecialStopSynchronizeBreakpoints) + doInterruptInferior(SpecialStopSynchronizeBreakpoints); + return; + } + + // If there are changes/removals, delete all breakpoints and re-insert + // all enabled breakpoints. This is the simplest + // way to apply changes since CDB ids shift when removing breakpoints and there is no + // easy way to re-match them. + + if (syncType == BreakpointsRemovedChanged) { // Need to clear out all? + postCommand("bc *", 0); + m_nextBreakpointNumber = 0; + } + + foreach (BreakpointId id, ids) { + Debugger::Internal::BreakpointResponse response; + const Debugger::Internal::BreakpointParameters &p = handler->breakpointData(id); + response.fromParameters(p); + switch (handler->state(id)) { + case Debugger::Internal::BreakpointInsertRequested: + response.number = m_nextBreakpointNumber++; + postCommand(cdbAddBreakpointCommand(p, false, response.number), 0); + handler->setState(id, Debugger::Internal::BreakpointInsertProceeding); + handler->notifyBreakpointInsertOk(id); + handler->setResponse(id, response); + break; + case Debugger::Internal::BreakpointChangeRequested: + // Skip disabled breakpoints, else add + handler->setState(id, Debugger::Internal::BreakpointChangeProceeding); + if (p.enabled) { + response.number = m_nextBreakpointNumber++; + postCommand(cdbAddBreakpointCommand(p, false, response.number), 0); + handler->notifyBreakpointChangeOk(id); + handler->setResponse(id, response); + } + break; + case Debugger::Internal::BreakpointRemoveRequested: + handler->notifyBreakpointRemoveOk(id); + break; + case Debugger::Internal::BreakpointInserted: + // Existing breakpoints were deleted due to change/removal, re-set + if (syncType == BreakpointsRemovedChanged) { + response.number = m_nextBreakpointNumber++;; + postCommand(cdbAddBreakpointCommand(p, false, response.number), 0); + handler->setResponse(id, response); + } + break; + default: + break; + } + } +} + +QString CdbEngine::normalizeFileName(const QString &f) +{ + QMap<QString, QString>::const_iterator it = m_normalizedFileCache.constFind(f); + if (it != m_normalizedFileCache.constEnd()) + return it.value(); + const QString winF = QDir::toNativeSeparators(f); +#ifdef Q_OS_WIN + QString normalized = Debugger::Internal::winNormalizeFileName(winF); +#else + QString normalized = winF; +#endif + if (normalized.isEmpty()) { // At least upper case drive letter + normalized = winF; + if (normalized.size() > 2 && normalized.at(1) == QLatin1Char(':')) + normalized[0] = normalized.at(0).toUpper(); + } + m_normalizedFileCache.insert(f, normalized); + return normalized; +} + +void CdbEngine::handleStackTrace(const CdbBuiltinCommandPtr &command) +{ + Debugger::Internal::StackFrames frames; + const int current = parseCdbStackTrace(command->reply, &frames); + if (debug) + qDebug("handleStackTrace %d of %d", current, frames.size()); + const Debugger::Internal::StackFrames::iterator end = frames.end(); + for (Debugger::Internal::StackFrames::iterator it = frames.begin(); it != end; ++it) { + if (!it->file.isEmpty()) + it->file = QDir::cleanPath(normalizeFileName(it->file)); + } + + stackHandler()->setFrames(frames); + activateFrame(current); + postCommandSequence(command->commandSequence); +} + +void CdbEngine::dummyHandler(const CdbBuiltinCommandPtr &command) +{ + postCommandSequence(command->commandSequence); +} + +// Post a sequence of standard commands: Trigger next once one completes successfully +void CdbEngine::postCommandSequence(unsigned mask) +{ + if (debug) + qDebug("postCommandSequence 0x%x\n", mask); + + if (!mask) + return; + if (mask & CommandListThreads) { + postExtensionCommand("threads", QByteArray(), 0, &CdbEngine::handleThreads, mask & ~CommandListThreads); + return; + } + if (mask & CommandListStack) { + postBuiltinCommand("k", 0, &CdbEngine::handleStackTrace, mask & ~CommandListStack); + return; + } + if (mask & CommandListRegisters) { + QTC_ASSERT(threadsHandler()->currentThread() >= 0, return; ) + postExtensionCommand("registers", QByteArray(), 0, &CdbEngine::handleRegisters, mask & ~CommandListRegisters); + return; + } + if (mask & CommandListModules) { + postExtensionCommand("modules", QByteArray(), 0, &CdbEngine::handleModules, mask & ~CommandListModules); + return; + } +} + +} // namespace Cdb +} // namespace Debugger diff --git a/src/plugins/debugger/cdb2/cdbengine2.h b/src/plugins/debugger/cdb2/cdbengine2.h new file mode 100644 index 00000000000..0737ce58d05 --- /dev/null +++ b/src/plugins/debugger/cdb2/cdbengine2.h @@ -0,0 +1,202 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef DEBUGGER_CDBENGINE_H +#define DEBUGGER_CDBENGINE_H + +#include "debuggerengine.h" + +#include <QtCore/QSharedPointer> +#include <QtCore/QProcess> +#include <QtCore/QVariant> +#include <QtCore/QMap> +#include <QtCore/QTime> + +namespace Debugger { +namespace Cdb { + +class DisassemblerViewAgent; +struct CdbBuiltinCommand; +struct CdbExtensionCommand; +struct CdbOptions; + +class CdbEngine : public Debugger::DebuggerEngine +{ + Q_OBJECT + +public: + typedef QSharedPointer<CdbOptions> OptionsPtr; + + enum CommandFlags { QuietCommand = 0x1 }; + // Flag bits for a sequence of commands + enum CommandSequenceFlags { + CommandListStack = 0x1, + CommandListThreads = 0x2, + CommandListRegisters = 0x4, + CommandListModules = 0x8 + }; + + typedef QSharedPointer<CdbBuiltinCommand> CdbBuiltinCommandPtr; + typedef QSharedPointer<CdbExtensionCommand> CdbExtensionCommandPtr; + typedef void (CdbEngine::*BuiltinCommandHandler)(const CdbBuiltinCommandPtr &); + typedef void (CdbEngine::*ExtensionCommandHandler)(const CdbExtensionCommandPtr &); + + explicit CdbEngine(const DebuggerStartParameters &sp, const OptionsPtr &options); + virtual ~CdbEngine(); + // Factory function that returns 0 if the debug engine library cannot be found. + + virtual void setToolTipExpression(const QPoint &mousePos, TextEditor::ITextEditor *editor, int cursorPos); + virtual void setupEngine(); + virtual void setupInferior(); + virtual void runEngine(); + virtual void shutdownInferior(); + virtual void shutdownEngine(); + virtual void detachDebugger(); + virtual void updateWatchData(const Debugger::Internal::WatchData &data, + const Debugger::Internal::WatchUpdateFlags & flags = Debugger::Internal::WatchUpdateFlags()); + virtual unsigned debuggerCapabilities() const; + virtual void setRegisterValue(int regnr, const QString &value); + + virtual void executeStep(); + virtual void executeStepOut(); + virtual void executeNext(); + virtual void executeStepI(); + virtual void executeNextI(); + + virtual void continueInferior(); + virtual void interruptInferior(); + + virtual void executeRunToLine(const QString &fileName, int lineNumber); + virtual void executeRunToFunction(const QString &functionName); + virtual void executeJumpToLine(const QString &fileName, int lineNumber); + virtual void assignValueInDebugger(const Debugger::Internal::WatchData *w, const QString &expr, const QVariant &value); + virtual void executeDebuggerCommand(const QString &command); + + virtual void activateFrame(int index); + virtual void selectThread(int index); + + virtual void attemptBreakpointSynchronization(); + + virtual void fetchDisassembler(Debugger::Internal::DisassemblerViewAgent *agent); + virtual void fetchMemory(Debugger::Internal::MemoryViewAgent *, QObject *, quint64 addr, quint64 length); + + virtual void reloadModules(); + virtual void loadSymbols(const QString &moduleName); + virtual void loadAllSymbols(); + virtual void requestModuleSymbols(const QString &moduleName); + + virtual void reloadRegisters(); + virtual void reloadSourceFiles(); + virtual void reloadFullStack(); + + //virtual bool isSynchronous() const { return true; } + +private slots: + void readyReadStandardOut(); + void readyReadStandardError(); + void processError(); + void processFinished(); + void postCommand(const QByteArray &cmd, unsigned flags); + void postBuiltinCommand(const QByteArray &cmd, + unsigned flags, + BuiltinCommandHandler handler, + unsigned nextCommandFlag = 0, + const QVariant &cookie = QVariant()); + + void postExtensionCommand(const QByteArray &cmd, + const QByteArray &arguments, + unsigned flags, + ExtensionCommandHandler handler, + unsigned nextCommandFlag = 0, + const QVariant &cookie = QVariant()); + + void postCommandSequence(unsigned mask); + void operateByInstructionTriggered(bool); + +private: + enum SpecialStopMode { NoSpecialStop, SpecialStopSynchronizeBreakpoints }; + + void handleExtensionMessage(char t, int token, const QByteArray &what, const QByteArray &message); + bool doSetupEngine(QString *errorMessage); + void handleSessionAccessible(unsigned long cdbExState); + void handleSessionInaccessible(unsigned long cdbExState); + void handleSessionIdle(const QByteArray &message); + void doInterruptInferior(SpecialStopMode sm); + void doContinueInferior(); + inline void parseOutputLine(QByteArray line); + inline bool isCdbProcessRunning() const { return m_process.state() != QProcess::NotRunning; } + + // Builtin commands + void dummyHandler(const CdbBuiltinCommandPtr &); + void handleStackTrace(const CdbBuiltinCommandPtr &); + void handleRegisters(const CdbBuiltinCommandPtr &); + void handleDisassembler(const CdbBuiltinCommandPtr &); + void handleJumpToLineAddressResolution(const CdbBuiltinCommandPtr &); + // Extension commands + void handleThreads(const CdbExtensionCommandPtr &); + void handlePid(const CdbExtensionCommandPtr &reply); + void handleLocals(const CdbExtensionCommandPtr &reply); + void handleExpandLocals(const CdbExtensionCommandPtr &reply); + void handleRegisters(const CdbExtensionCommandPtr &reply); + void handleModules(const CdbExtensionCommandPtr &reply); + void handleMemory(const CdbExtensionCommandPtr &); + + QString normalizeFileName(const QString &f); + void updateLocalVariable(const QByteArray &iname); + int elapsedLogTime() const; + + const QByteArray m_creatorExtPrefix; + const QByteArray m_tokenPrefix; + const OptionsPtr m_options; + + QProcess m_process; + QByteArray m_outputBuffer; + unsigned long m_inferiorPid; + // Debugger accessible (expecting commands) + bool m_accessible; + SpecialStopMode m_specialStopMode; + int m_nextCommandToken; + int m_nextBreakpointNumber; + QList<CdbBuiltinCommandPtr> m_builtinCommandQueue; + int m_currentBuiltinCommandIndex; // Current command whose output is recorded. + QList<CdbExtensionCommandPtr> m_extensionCommandQueue; + QMap<QString, QString> m_normalizedFileCache; + const QByteArray m_extensionCommandPrefixBA; // Library name used as prefix + bool m_operateByInstructionPending; // Creator operate by instruction action changed. + bool m_operateByInstruction; + bool m_notifyEngineShutdownOnTermination; + bool m_hasDebuggee; + QTime m_logTime; + mutable int m_elapsedLogTime; +}; + +} // namespace Cdb +} // namespace Debugger + +#endif // DEBUGGER_CDBENGINE_H diff --git a/src/plugins/debugger/cdb2/cdboptions2.cpp b/src/plugins/debugger/cdb2/cdboptions2.cpp new file mode 100644 index 00000000000..1eec117885f --- /dev/null +++ b/src/plugins/debugger/cdb2/cdboptions2.cpp @@ -0,0 +1,160 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "cdboptions2.h" + +#ifdef Q_OS_WIN +# include <utils/winutils.h> +#endif + +#include <QtCore/QSettings> +#include <QtCore/QDir> +#include <QtCore/QFileInfo> + +static const char settingsGroupC[] = "CDB2"; +static const char enabledKeyC[] = "Enabled"; +static const char pathKeyC[] = "Path"; +static const char symbolPathsKeyC[] = "SymbolPaths"; +static const char sourcePathsKeyC[] = "SourcePaths"; +static const char is64bitKeyC[] = "64bit"; + +namespace Debugger { +namespace Cdb { + +CdbOptions::CdbOptions() : + enabled(false), is64bit(false) +{ +} + +QString CdbOptions::settingsGroup() +{ + return QLatin1String(settingsGroupC); +} + +void CdbOptions::clear() +{ + is64bit = enabled = false; + executable.clear(); + symbolPaths.clear(); + sourcePaths.clear(); +} + +void CdbOptions::fromSettings(const QSettings *s) +{ + clear(); + // Is this the first time we are called -> + // try to find automatically + const QString keyRoot = QLatin1String(settingsGroupC) + QLatin1Char('/'); + const QString enabledKey = keyRoot + QLatin1String(enabledKeyC); +#if 0 // TODO: Enable autodetection after deprecating the old CDB engine only. + const bool firstTime = !s->contains(enabledKey); + if (firstTime) + CdbOptions::autoDetectExecutable(&executable, &is64bit); +#endif + enabled = s->value(enabledKey, false).toBool(); + is64bit = s->value(keyRoot + QLatin1String(is64bitKeyC), is64bit).toBool(); + executable = s->value(keyRoot + QLatin1String(pathKeyC), executable).toString(); + symbolPaths = s->value(keyRoot + QLatin1String(symbolPathsKeyC), QStringList()).toStringList(); + sourcePaths = s->value(keyRoot + QLatin1String(sourcePathsKeyC), QStringList()).toStringList(); +} + +void CdbOptions::toSettings(QSettings *s) const +{ + s->beginGroup(QLatin1String(settingsGroupC)); + s->setValue(QLatin1String(enabledKeyC), enabled); + s->setValue(QLatin1String(pathKeyC), executable); + s->setValue(QLatin1String(is64bitKeyC), is64bit); + s->setValue(QLatin1String(symbolPathsKeyC), symbolPaths); + s->setValue(QLatin1String(sourcePathsKeyC), sourcePaths); + s->endGroup(); +} + +bool CdbOptions::equals(const CdbOptions &rhs) const +{ + return enabled == rhs.enabled && is64bit == rhs.is64bit + && executable == rhs.executable + && symbolPaths == rhs.symbolPaths + && sourcePaths == rhs.sourcePaths; +} + +bool CdbOptions::autoDetectExecutable(QString *outPath, bool *is64bitIn /* = 0 */, + QStringList *checkedDirectories /* = 0 */) +{ + // Look for $ProgramFiles/"Debugging Tools For Windows <bit-idy>/cdb.exe" and its + // " (x86)", " (x64)" variations. + static const char *postFixes[] = {" (x64)", " 64-bit", " (x86)", " (x32)" }; + enum { first32bitIndex = 2 }; + + if (checkedDirectories) + checkedDirectories->clear(); + + outPath->clear(); + const QByteArray programDirB = qgetenv("ProgramFiles"); + if (programDirB.isEmpty()) + return false; + + const QString programDir = QString::fromLocal8Bit(programDirB) + QLatin1Char('/'); + const QString installDir = QLatin1String("Debugging Tools For Windows"); + const QString executable = QLatin1String("/cdb.exe"); + + QString path = programDir + installDir; + if (checkedDirectories) + checkedDirectories->push_back(path); + const QFileInfo fi(path + executable); + // Plain system installation + if (fi.isFile() && fi.isExecutable()) { + *outPath = fi.absoluteFilePath(); + if (is64bitIn) +#ifdef Q_OS_WIN + *is64bitIn = Utils::winIs64BitSystem(); +#else + *is64bitIn = false; +#endif + return true; + } + // Try the post fixes + const int rootLength = path.size(); + for (unsigned i = 0; i < sizeof(postFixes)/sizeof(const char*); i++) { + path.truncate(rootLength); + path += QLatin1String(postFixes[i]); + if (checkedDirectories) + checkedDirectories->push_back(path); + const QFileInfo fi2(path + executable); + if (fi2.isFile() && fi2.isExecutable()) { + if (is64bitIn) + *is64bitIn = i < first32bitIndex; + *outPath = fi2.absoluteFilePath(); + return true; + } + } + return false; +} + +} // namespace Internal +} // namespace Debugger diff --git a/src/plugins/debugger/cdb2/cdboptions2.h b/src/plugins/debugger/cdb2/cdboptions2.h new file mode 100644 index 00000000000..b27779fbad0 --- /dev/null +++ b/src/plugins/debugger/cdb2/cdboptions2.h @@ -0,0 +1,73 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef CDBSETTINGS_H +#define CDBSETTINGS_H + +#include <QtCore/QStringList> + +QT_BEGIN_NAMESPACE +class QSettings; +QT_END_NAMESPACE + +namespace Debugger { +namespace Cdb { + +struct CdbOptions +{ +public: + CdbOptions(); + void clear(); + + void fromSettings(const QSettings *s); + void toSettings(QSettings *s) const; + + bool equals(const CdbOptions &rhs) const; + + static bool autoDetectExecutable(QString *outPath, bool *is64bit = 0, + QStringList *checkedDirectories = 0); + + static QString settingsGroup(); + + bool enabled; + bool is64bit; + QString executable; + QStringList symbolPaths; + QStringList sourcePaths; +}; + +inline bool operator==(const CdbOptions &s1, const CdbOptions &s2) +{ return s1.equals(s2); } +inline bool operator!=(const CdbOptions &s1, const CdbOptions &s2) +{ return !s1.equals(s2); } + +} // namespace Internal +} // namespace Debugger + +#endif // CDBSETTINGS_H diff --git a/src/plugins/debugger/cdb2/cdboptionspage2.cpp b/src/plugins/debugger/cdb2/cdboptionspage2.cpp new file mode 100644 index 00000000000..c8a67eb7d6c --- /dev/null +++ b/src/plugins/debugger/cdb2/cdboptionspage2.cpp @@ -0,0 +1,218 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "cdboptionspage2.h" +#include "cdboptions2.h" +#include "debuggerconstants.h" + +#ifdef Q_OS_WIN +# include <utils/winutils.h> +#endif +#include <coreplugin/icore.h> + +#include <QtCore/QCoreApplication> +#include <QtCore/QUrl> +#include <QtCore/QTextStream> +#include <QtGui/QMessageBox> +#include <QtGui/QDesktopServices> + +static const char *dgbToolsDownloadLink32C = "http://www.microsoft.com/whdc/devtools/debugging/installx86.Mspx"; +static const char *dgbToolsDownloadLink64C = "http://www.microsoft.com/whdc/devtools/debugging/install64bit.Mspx"; + +namespace Debugger { +namespace Cdb { + +static inline QString msgPathConfigNote() +{ +#ifdef Q_OS_WIN + const bool is64bit = Utils::winIs64BitSystem(); +#else + const bool is64bit = false; +#endif + const QString link = is64bit ? QLatin1String(dgbToolsDownloadLink64C) : QLatin1String(dgbToolsDownloadLink32C); + //: Label text for path configuration. %2 is "x-bit version". + return CdbOptionsPageWidget::tr( + "<html><body><p>Specify the path to the " + "<a href=\"%1\">Windows Console Debugger executable</a>" + " (%2) here.</p>" + "</body></html>").arg(link, (is64bit ? CdbOptionsPageWidget::tr("64-bit version") + : CdbOptionsPageWidget::tr("32-bit version"))); +} + +CdbOptionsPageWidget::CdbOptionsPageWidget(QWidget *parent) : + QWidget(parent) +{ + m_ui.setupUi(this); + m_ui.noteLabel->setText(msgPathConfigNote()); + m_ui.noteLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); + connect(m_ui.noteLabel, SIGNAL(linkActivated(QString)), this, SLOT(downLoadLinkActivated(QString))); + + m_ui.pathChooser->setExpectedKind(Utils::PathChooser::ExistingCommand); + m_ui.pathChooser->addButton(tr("Autodetect"), this, SLOT(autoDetect())); + m_ui.failureLabel->setVisible(false); +} + + +void CdbOptionsPageWidget::setOptions(CdbOptions &o) +{ + m_ui.pathChooser->setPath(o.executable); + m_ui.is64BitCheckBox->setChecked(o.is64bit); + m_ui.cdbPathGroupBox->setChecked(o.enabled); + m_ui.symbolPathListEditor->setPathList(o.symbolPaths); + m_ui.sourcePathListEditor->setPathList(o.sourcePaths); +} + +CdbOptions CdbOptionsPageWidget::options() const +{ + CdbOptions rc; + rc.executable = m_ui.pathChooser->path(); + rc.enabled = m_ui.cdbPathGroupBox->isChecked(); + rc.is64bit = m_ui.is64BitCheckBox->isChecked(); + rc.symbolPaths = m_ui.symbolPathListEditor->pathList(); + rc.sourcePaths = m_ui.sourcePathListEditor->pathList(); + return rc; +} + +void CdbOptionsPageWidget::autoDetect() +{ + QString executable; + QStringList checkedDirectories; + bool is64bit; + const bool ok = CdbOptions::autoDetectExecutable(&executable, &is64bit, &checkedDirectories); + m_ui.cdbPathGroupBox->setChecked(ok); + if (ok) { + m_ui.is64BitCheckBox->setChecked(is64bit); + m_ui.pathChooser->setPath(executable); + } else { + const QString msg = tr("\"Debugging Tools for Windows\" could not be found."); + const QString details = tr("Checked:\n%1").arg(checkedDirectories.join(QString(QLatin1Char('\n')))); + QMessageBox msbBox(QMessageBox::Information, tr("Autodetection"), msg, QMessageBox::Ok, this); + msbBox.setDetailedText(details); + msbBox.exec(); + } +} + +void CdbOptionsPageWidget::setFailureMessage(const QString &msg) +{ + m_ui.failureLabel->setText(msg); + m_ui.failureLabel->setVisible(!msg.isEmpty()); +} + +void CdbOptionsPageWidget::downLoadLinkActivated(const QString &link) +{ + QDesktopServices::openUrl(QUrl(link)); +} + +QString CdbOptionsPageWidget::searchKeywords() const +{ + QString rc; + QTextStream(&rc) << m_ui.pathLabel->text() << ' ' << m_ui.symbolPathLabel->text() + << ' ' << m_ui.sourcePathLabel->text(); + rc.remove(QLatin1Char('&')); + return rc; +} + +// ---------- CdbOptionsPage + +CdbOptionsPage *CdbOptionsPage::m_instance = 0; + +CdbOptionsPage::CdbOptionsPage() : + m_options(new CdbOptions) +{ + CdbOptionsPage::m_instance = this; + m_options->fromSettings(Core::ICore::instance()->settings()); +} + +CdbOptionsPage::~CdbOptionsPage() +{ + CdbOptionsPage::m_instance = 0; +} + +QString CdbOptionsPage::settingsId() +{ + return QLatin1String("F.Cda"); // before old CDB +} + +QString CdbOptionsPage::displayName() const +{ + return tr("CDB (new, experimental)"); +} + +QString CdbOptionsPage::category() const +{ + return QLatin1String(Debugger::Constants::DEBUGGER_SETTINGS_CATEGORY); +} + +QString CdbOptionsPage::displayCategory() const +{ + return QCoreApplication::translate("Debugger", Debugger::Constants::DEBUGGER_SETTINGS_TR_CATEGORY); +} + +QIcon CdbOptionsPage::categoryIcon() const +{ + return QIcon(QLatin1String(Debugger::Constants::DEBUGGER_COMMON_SETTINGS_CATEGORY_ICON)); +} + +QWidget *CdbOptionsPage::createPage(QWidget *parent) +{ + m_widget = new CdbOptionsPageWidget(parent); + m_widget->setOptions(*m_options); + m_widget->setFailureMessage(m_failureMessage); + if (m_searchKeywords.isEmpty()) + m_searchKeywords = m_widget->searchKeywords(); + return m_widget; +} + +void CdbOptionsPage::apply() +{ + if (!m_widget) + return; + const CdbOptions newOptions = m_widget->options(); + if (*m_options != newOptions) { + *m_options = newOptions; + m_options->toSettings(Core::ICore::instance()->settings()); + } +} + +void CdbOptionsPage::finish() +{ +} + +bool CdbOptionsPage::matches(const QString &s) const +{ + return m_searchKeywords.contains(s, Qt::CaseInsensitive); +} + +CdbOptionsPage *CdbOptionsPage::instance() +{ + return m_instance; +} + +} // namespace Internal +} // namespace Debugger diff --git a/src/plugins/debugger/cdb2/cdboptionspage2.h b/src/plugins/debugger/cdb2/cdboptionspage2.h new file mode 100644 index 00000000000..11e5183a2fa --- /dev/null +++ b/src/plugins/debugger/cdb2/cdboptionspage2.h @@ -0,0 +1,105 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef CDBSETTINGSPAGE_H +#define CDBSETTINGSPAGE_H + +#include "cdboptions2.h" + +#include <coreplugin/dialogs/ioptionspage.h> +#include "ui_cdboptionspagewidget2.h" + +#include <QtGui/QWidget> +#include <QtCore/QPointer> +#include <QtCore/QSharedPointer> + +namespace Debugger { +namespace Cdb { + +class CdbOptionsPageWidget : public QWidget +{ + Q_OBJECT +public: + explicit CdbOptionsPageWidget(QWidget *parent); + + void setOptions(CdbOptions &o); + CdbOptions options() const; + + void setFailureMessage(const QString &); + + QString searchKeywords() const; + +private slots: + void autoDetect(); + void downLoadLinkActivated(const QString &); + +private: + Ui::CdbOptionsPageWidget2 m_ui; +}; + +class CdbOptionsPage : public Core::IOptionsPage +{ + Q_DISABLE_COPY(CdbOptionsPage) + Q_OBJECT +public: + explicit CdbOptionsPage(); + virtual ~CdbOptionsPage(); + + static CdbOptionsPage *instance(); + + // IOptionsPage + virtual QString id() const { return settingsId(); } + virtual QString displayName() const; + virtual QString category() const; + virtual QString displayCategory() const; + QIcon categoryIcon() const; + + virtual QWidget *createPage(QWidget *parent); + virtual void apply(); + virtual void finish(); + virtual bool matches(const QString &) const; + + static QString settingsId(); + + // Load failure messages can be displayed here + void setFailureMessage(const QString &msg) { m_failureMessage = msg; } + QSharedPointer<CdbOptions> options() const { return m_options; } + +private: + static CdbOptionsPage *m_instance; + const QSharedPointer<CdbOptions> m_options; + QPointer<CdbOptionsPageWidget> m_widget; + QString m_failureMessage; + QString m_searchKeywords; +}; + +} // namespace Internal +} // namespace Debugger + +#endif // CDBSETTINGSPAGE_H diff --git a/src/plugins/debugger/cdb2/cdboptionspagewidget2.ui b/src/plugins/debugger/cdb2/cdboptionspagewidget2.ui new file mode 100644 index 00000000000..49d065b569d --- /dev/null +++ b/src/plugins/debugger/cdb2/cdboptionspagewidget2.ui @@ -0,0 +1,128 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Debugger::Cdb::CdbOptionsPageWidget2</class> + <widget class="QWidget" name="Debugger::Cdb::CdbOptionsPageWidget2"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QGroupBox" name="cdbPathGroupBox"> + <property name="toolTip"> + <string>These options take effect at the next start of Qt Creator.</string> + </property> + <property name="title"> + <string extracomment="Placeholder">CDB</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QFormLayout" name="formLayout"> + <item row="1" column="0"> + <widget class="QLabel" name="pathLabel"> + <property name="text"> + <string>Path:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="Utils::PathChooser" name="pathChooser" native="true"/> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="noteLabel"> + <property name="text"> + <string notr="true" extracomment="Placeholder">Note: bla, blah</string> + </property> + </widget> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QCheckBox" name="is64BitCheckBox"> + <property name="text"> + <string>64 bit</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QGroupBox" name="pathGroupBox"> + <property name="title"> + <string>Debugger Paths</string> + </property> + <layout class="QFormLayout" name="formLayout_2"> + <property name="fieldGrowthPolicy"> + <enum>QFormLayout::AllNonFixedFieldsGrow</enum> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="symbolPathLabel"> + <property name="text"> + <string>Symbol paths:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="Debugger::Internal::CdbSymbolPathListEditor" name="symbolPathListEditor" native="true"/> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="sourcePathLabel"> + <property name="text"> + <string>Source paths:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="Utils::PathListEditor" name="sourcePathListEditor" native="true"/> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="failureLabel"> + <property name="styleSheet"> + <string notr="true">background-color: 'red';</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>Utils::PathChooser</class> + <extends>QWidget</extends> + <header location="global">utils/pathchooser.h</header> + <container>1</container> + </customwidget> + <customwidget> + <class>Utils::PathListEditor</class> + <extends>QWidget</extends> + <header location="global">utils/pathlisteditor.h</header> + <container>1</container> + </customwidget> + <customwidget> + <class>Debugger::Internal::CdbSymbolPathListEditor</class> + <extends>QWidget</extends> + <header location="global">cdbsymbolpathlisteditor.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/debugger/cdb2/cdbparsehelpers.cpp b/src/plugins/debugger/cdb2/cdbparsehelpers.cpp new file mode 100644 index 00000000000..8debc517947 --- /dev/null +++ b/src/plugins/debugger/cdb2/cdbparsehelpers.cpp @@ -0,0 +1,355 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "cdbparsehelpers.h" +#include "breakpoint.h" +#include "stackframe.h" +#include "threadshandler.h" +#include "registerhandler.h" +#include "bytearrayinputstream.h" +#include "gdb/gdbmi.h" +#ifdef Q_OS_WIN +# include "shared/dbgwinutils.h" +#endif +#include <QtCore/QByteArray> +#include <QtCore/QVariant> +#include <QtCore/QString> +#include <QtCore/QDir> +#include <QtCore/QDebug> + +#include <utils/qtcassert.h> + +#include <cctype> + +namespace Debugger { +namespace Cdb { + +// Convert breakpoint in CDB syntax. +QByteArray cdbAddBreakpointCommand(const Debugger::Internal::BreakpointParameters &bpIn, bool oneshot, int id) +{ +#ifdef Q_OS_WIN + const Debugger::Internal::BreakpointParameters bp = Debugger::Internal::fixWinMSVCBreakpoint(bpIn); +#else + const Debugger::Internal::BreakpointParameters bp = bpIn; +#endif + + QByteArray rc; + ByteArrayInputStream str(rc); + + if (!bp.threadSpec.isEmpty()) + str << '~' << bp.threadSpec << ' '; + + str << (bp.type == Debugger::Internal::Watchpoint ? "ba" : "bp"); + if (id >= 0) + str << id; + str << ' '; + if (oneshot) + str << "/1 "; + switch (bp.type) { + case Debugger::Internal::UnknownType: + case Debugger::Internal::BreakpointAtCatch: + case Debugger::Internal::BreakpointAtThrow: + case Debugger::Internal::BreakpointAtMain: + QTC_ASSERT(false, return QByteArray(); ) + break; + case Debugger::Internal::BreakpointByAddress: + str << hex << hexPrefixOn << bp.address << hexPrefixOff << dec; + break; + case Debugger::Internal::BreakpointByFunction: + str << bp.functionName; + break; + case Debugger::Internal::BreakpointByFileAndLine: + str << '`' << QDir::toNativeSeparators(bp.fileName) << ':' << bp.lineNumber << '`'; + break; + case Debugger::Internal::Watchpoint: + str << "rw 1 " << hex << hexPrefixOn << bp.address << hexPrefixOff << dec; + break; + } + if (bp.ignoreCount) + str << ' ' << bp.ignoreCount; + // Condition currently unsupported. + return rc; +} + +// Remove the address separator. Format the address exactly as +// the agent does (0xhex, as taken from frame) for the location mark to trigger. +QString formatCdbDisassembler(const QList<QByteArray> &in) +{ + QString disassembly; + const QChar newLine = QLatin1Char('\n'); + foreach(QByteArray line, in) { + // Remove 64bit separator. + if (line.size() >= 9 && line.at(8) == '`') + line.remove(8, 1); + // Ensure address is as wide as agent's address. + disassembly += QString::fromLatin1(line); + disassembly += newLine; + } + return disassembly; +} + +// Fix a CDB integer value: '00000000`0012a290' -> '12a290', '0n10' ->'10' +QByteArray fixCdbIntegerValue(QByteArray t, bool stripLeadingZeros, int *basePtr /* = 0 */) +{ + if (t.isEmpty()) + return t; + int base = 16; + // Prefixes + if (t.startsWith("0x")) { + t.remove(0, 2); + } else if (t.startsWith("0n")) { + base = 10; + t.remove(0, 2); + } + if (base == 16 && t.size() >= 9 && t.at(8) == '`') + t.remove(8, 1); + if (stripLeadingZeros) { // Strip all but last '0' + const int last = t.size() - 1; + int pos = 0; + for ( ; pos < last && t.at(pos) == '0'; pos++) ; + if (pos) + t.remove(0, pos); + } + if (basePtr) + *basePtr = base; + return t; +} + +// Convert a CDB integer value: '00000000`0012a290' -> '12a290', '0n10' ->'10' +QVariant cdbIntegerValue(const QByteArray &t) +{ + int base; + const QByteArray fixed = fixCdbIntegerValue(t, false, &base); + bool ok; + const QVariant converted = base == 16 ? + fixed.toULongLong(&ok, base) : + fixed.toLongLong(&ok, base); + QTC_ASSERT(ok, return QVariant(); ) + return converted; +} + +/* Parse: +\code +Child-SP RetAddr Call Site +00000000`0012a290 00000000`70deb844 QtCored4!QString::QString+0x18 [c:\qt\src\corelib\tools\qstring.h @ 729] +\endcode */ + +static inline bool isHexDigit(char c) +{ + return std::isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +} + +static inline bool parseStackFrame(QByteArray line, Debugger::Internal::StackFrame *frame) +{ + frame->clear(); + if (line.isEmpty() || line.startsWith("Child-SP") || !isHexDigit(line.at(0))) + return false; + if (line.endsWith(']')) { + const int sourceFilePos = line.lastIndexOf('['); + const int sepPos = line.lastIndexOf(" @ "); + if (sourceFilePos != -1 && sepPos != -1) { + const QString fileName = QString::fromLocal8Bit(line.mid(sourceFilePos + 1, sepPos - sourceFilePos - 1)); + frame->file = QDir::cleanPath(fileName); + frame->line = line.mid(sepPos + 3, line.size() - sepPos - 4).toInt(); + line.truncate(sourceFilePos - 1); + } + } + // Split address tokens + const int retAddrPos = line.indexOf(' '); + const int symbolPos = retAddrPos != -1 ? line.indexOf(' ', retAddrPos + 1) : -1; + if (symbolPos == -1) + return false; + + // Remove offset off symbol + const int offsetPos = line.lastIndexOf("+0x"); + if (offsetPos != -1) + line.truncate(offsetPos); + + frame->address = cdbIntegerValue(line.mid(0, retAddrPos)).toULongLong(); + // Module!foo + frame->function = QString::fromAscii(line.mid(symbolPos)); + const int moduleSep = frame->function.indexOf(QLatin1Char('!')); + if (moduleSep != -1) { + frame->from = frame->function.left(moduleSep); + frame->function.remove(0, moduleSep + 1); + } + return true; +} + +int parseCdbStackTrace(const QList<QByteArray> &in, QList<Debugger::Internal::StackFrame> *frames) +{ + frames->clear(); + Debugger::Internal::StackFrame frame; + frames->reserve(in.size()); + int level = 0; + int current = -1; + foreach(const QByteArray &line, in) + if (parseStackFrame(line, &frame)) { + frame.level = level++; + if (current == -1 && frame.isUsable()) + current = frames->size(); + frames->push_back(frame); + } + return current; +} + +/* \code +0:002> ~ [Debugger-Id] Id: <hex pid> <hex tid> Suspends count thread environment block add state name + 0 Id: 133c.1374 Suspend: 1 Teb: 000007ff`fffdd000 Unfrozen +. 2 Id: 133c.1160 Suspend: 1 Teb: 000007ff`fffd9000 Unfrozen "QThread" + 3 Id: 133c.38c Suspend: 1 Teb: 000007ff`fffd7000 Unfrozen "QThread" +\endcode */ + +static inline bool parseThread(QByteArray line, Debugger::Internal::ThreadData *thread, bool *current) +{ + *current = false; + if (line.size() < 5) + return false; + *current = line.at(0) == '.'; + if (current) + line[0] = ' '; + const QList<QByteArray> tokens = simplify(line).split(' '); + if (tokens.size() < 8 || tokens.at(1) != "Id:") + return false; + switch (tokens.size()) { // fallthru intended + case 9: + thread->name = QString::fromLocal8Bit(tokens.at(8)); + case 8: + thread->state = QString::fromLocal8Bit(tokens.at(7)); + case 3: { + const QByteArray &pidTid = tokens.at(2); + const int dotPos = pidTid.indexOf('.'); + if (dotPos != -1) + thread->targetId = QLatin1String("0x") + QString::fromAscii(pidTid.mid(dotPos + 1)); + } + case 1: + thread->id = tokens.at(0).toInt(); + break; + } // switch size + return true; +} + +QString debugByteArray(const QByteArray &a) +{ + QString rc; + const int size = a.size(); + rc.reserve(size * 2); + QTextStream str(&rc); + for (int i = 0; i < size; i++) { + const unsigned char uc = (unsigned char)(a.at(i)); + switch (uc) { + case 0: + str << "\\0"; + break; + case '\n': + str << "\\n"; + break; + case '\t': + str << "\\t"; + break; + case '\r': + str << "\\r"; + break; + default: + if (uc >=32 && uc < 128) { + str << a.at(i); + } else { + str << '<' << unsigned(uc) << '>'; + } + break; + } + } + return rc; +} + +QString StringFromBase64EncodedUtf16(const QByteArray &a) +{ + QByteArray utf16 = QByteArray::fromBase64(a); + utf16.append('\0'); + utf16.append('\0'); + return QString::fromUtf16(reinterpret_cast<const unsigned short *>(utf16.constData())); +} + +WinException::WinException() : + exceptionCode(0), exceptionFlags(0), exceptionAddress(0), + info1(0),info2(0), firstChance(false), lineNumber(0) +{ +} + +void WinException::fromGdbMI(const Debugger::Internal::GdbMi &gdbmi) +{ + exceptionCode = gdbmi.findChild("exceptionCode").data().toUInt(); + exceptionFlags = gdbmi.findChild("exceptionFlags").data().toUInt(); + exceptionAddress = gdbmi.findChild("exceptionAddress").data().toULongLong(); + firstChance = gdbmi.findChild("firstChance").data() != "0"; + const Debugger::Internal::GdbMi ginfo1 = gdbmi.findChild("exceptionInformation0"); + if (ginfo1.isValid()) { + info1 = ginfo1.data().toULongLong(); + const Debugger::Internal::GdbMi ginfo2 = gdbmi.findChild("exceptionInformation1"); + if (ginfo2.isValid()) + info2 = ginfo1.data().toULongLong(); + } + const Debugger::Internal::GdbMi gLineNumber = gdbmi.findChild("exceptionLine"); + if (gLineNumber.isValid()) { + lineNumber = gLineNumber.data().toInt(); + file = gdbmi.findChild("exceptionFile").data(); + } + function = gdbmi.findChild("exceptionFunction").data(); +} + +QString WinException::toString(bool includeLocation) const +{ + QString rc; + QTextStream str(&rc); +#ifdef Q_OS_WIN + Debugger::Internal::formatWindowsException(exceptionCode, exceptionAddress, + exceptionFlags, info1, info2, str); +#endif + if (includeLocation) { + if (lineNumber) { + str << " at " << QLatin1String(file) << ':' << lineNumber; + } else { + if (!function.isEmpty()) + str << " in " << QLatin1String(function); + } + } + return rc; +} + +QDebug operator<<(QDebug s, const WinException &e) +{ + QDebug nsp = s.nospace(); + nsp << "code=" << e.exceptionCode << ",flags=" << e.exceptionFlags + << ",address=0x" << QString::number(e.exceptionAddress, 16) + << ",firstChance=" << e.firstChance; + return s; +} + +} // namespace Cdb +} // namespace Debugger diff --git a/src/plugins/debugger/cdb2/cdbparsehelpers.h b/src/plugins/debugger/cdb2/cdbparsehelpers.h new file mode 100644 index 00000000000..ccaeffe2b18 --- /dev/null +++ b/src/plugins/debugger/cdb2/cdbparsehelpers.h @@ -0,0 +1,95 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef CDBPARSEHELPERS_H +#define CDBPARSEHELPERS_H + +#include <QtCore/QtGlobal> +#include <QtCore/QList> +#include <QtCore/QVector> +#include <QtCore/QByteArray> + +QT_BEGIN_NAMESPACE +class QVariant; +class QDebug; +QT_END_NAMESPACE + +namespace Debugger { +namespace Internal { +class BreakpointData; +class BreakpointParameters; +class StackFrame; +struct ThreadData; +class Register; +class GdbMi; +} // namespace Internal + +namespace Cdb { + +// Convert breakpoint in CDB syntax. +QByteArray cdbAddBreakpointCommand(const Debugger::Internal::BreakpointParameters &d, bool oneshot = false, int id = -1); + +// Format CDB Dissambler output. +QString formatCdbDisassembler(const QList<QByteArray> &in); + +// Convert a CDB integer value: '00000000`0012a290' -> '12a290', '0n10' ->'10' +QByteArray fixCdbIntegerValue(QByteArray t, bool stripLeadingZeros = false, int *basePtr = 0); +// Convert a CDB integer value into quint64 or int64 +QVariant cdbIntegerValue(const QByteArray &t); + +// Parse stack frames and return current +int parseCdbStackTrace(const QList<QByteArray> &in, QList<Debugger::Internal::StackFrame> *frames); + +QString debugByteArray(const QByteArray &a); +QString StringFromBase64EncodedUtf16(const QByteArray &a); + +// Model EXCEPTION_RECORD + firstchance +struct WinException +{ + WinException(); + void fromGdbMI(const Debugger::Internal::GdbMi &); + QString toString(bool includeLocation = false) const; + + unsigned exceptionCode; + unsigned exceptionFlags; + quint64 exceptionAddress; + quint64 info1; + quint64 info2; + bool firstChance; + QByteArray file; + int lineNumber; + QByteArray function; +}; + +QDebug operator<<(QDebug s, const WinException &e); + +} // namespace Cdb +} // namespace Debugger + +#endif // CDBPARSEHELPERS_H diff --git a/src/plugins/debugger/debugger.pro b/src/plugins/debugger/debugger.pro index 213d18abe51..015480eacd1 100644 --- a/src/plugins/debugger/debugger.pro +++ b/src/plugins/debugger/debugger.pro @@ -121,6 +121,7 @@ LIBS *= -lole32 \ -lshell32 } include(cdb/cdb.pri) +include(cdb2/cdb2.pri) include(gdb/gdb.pri) include(script/script.pri) include(pdb/pdb.pri) diff --git a/src/plugins/debugger/debuggerplugin.cpp b/src/plugins/debugger/debuggerplugin.cpp index bd89729453c..6e8458d5209 100644 --- a/src/plugins/debugger/debuggerplugin.cpp +++ b/src/plugins/debugger/debuggerplugin.cpp @@ -431,6 +431,9 @@ static bool positionFromContextActionData(const QObject *sender, } namespace Debugger { +namespace Cdb { +void addCdb2OptionPages(QList<Core::IOptionsPage*> *); +} // namespace Cdb namespace Internal { // FIXME: Outdated? @@ -1863,6 +1866,9 @@ bool DebuggerPluginPrivate::initialize(const QStringList &arguments, #ifdef CDB_ENABLED if (cmdLineEnabledEngines & CdbEngineType) addCdbOptionPages(&engineOptionPages); +#endif +#ifdef Q_OS_WIN + Debugger::Cdb::addCdb2OptionPages(&engineOptionPages); #endif //if (cmdLineEnabledEngines & ScriptEngineType) // addScriptOptionPages(&engineOptionPages); diff --git a/src/plugins/debugger/debuggerrunner.cpp b/src/plugins/debugger/debuggerrunner.cpp index 2c1a6240aea..ae4ed1f7480 100644 --- a/src/plugins/debugger/debuggerrunner.cpp +++ b/src/plugins/debugger/debuggerrunner.cpp @@ -95,6 +95,10 @@ bool checkCdbConfiguration(int, QString *, QString *) { return false; } #endif } // namespace Internal +namespace Cdb { +DebuggerEngine *createCdbEngine(const DebuggerStartParameters &, QString *errorMessage); +bool isCdbEngineEnabled(); // Check the configuration page +} static QString toolChainName(int toolChainType) { @@ -261,7 +265,7 @@ unsigned DebuggerRunnerPrivate::enabledEngines() const { unsigned rc = m_cmdLineEnabledEngines; #ifdef CDB_ENABLED - if (!Internal::isCdbEngineEnabled()) + if (!Internal::isCdbEngineEnabled() && !Cdb::isCdbEngineEnabled()) rc &= ~CdbEngineType; #endif return rc; @@ -464,7 +468,12 @@ void DebuggerRunControl::createEngine(const DebuggerStartParameters &startParams d->m_engine = Internal::createScriptEngine(sp); break; case CdbEngineType: - d->m_engine = Internal::createCdbEngine(sp, &d->m_errorMessage); + // Try new engine, fall back to old. + if (Debugger::Cdb::isCdbEngineEnabled()) { + d->m_engine = Debugger::Cdb::createCdbEngine(sp, &d->m_errorMessage); + } else { + d->m_engine = Debugger::Internal::createCdbEngine(sp, &d->m_errorMessage); + } break; case PdbEngineType: d->m_engine = Internal::createPdbEngine(sp); -- GitLab