Commit d199d660 authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

Continue CDB.

Improve locals handling, add watcher handling via expressions. Make
debug marker appear, start with breakpoint synchronization.
parent c33ca52b
......@@ -206,6 +206,12 @@ QWidget *OutputPaneManager::buttonsWidget()
return m_buttonsWidget;
}
// Return shortcut as Ctrl+<number>
static inline int paneShortCut(int modifier, int number)
{
return modifier | (Qt::Key_0 + number);
}
void OutputPaneManager::init()
{
ActionManager *am = Core::ICore::instance()->actionManager();
......@@ -254,9 +260,9 @@ void OutputPaneManager::init()
Command *cmd = am->registerAction(action, actionId, QList<int>() << Constants::C_GLOBAL_ID);
if (outPane->priorityInStatusBar() != -1) {
#ifdef Q_OS_MAC
cmd->setDefaultKeySequence(QKeySequence("Ctrl+" + QString::number(shortcutNumber)));
cmd->setDefaultKeySequence(QKeySequence(paneShortCut(Qt::CTRL, shortcutNumber)));
#else
cmd->setDefaultKeySequence(QKeySequence("Alt+" + QString::number(shortcutNumber)));
cmd->setDefaultKeySequence(QKeySequence(paneShortCut(Qt::ALT, shortcutNumber)));
#endif
}
mpanes->addAction(cmd);
......
......@@ -24,14 +24,17 @@ HEADERS += \
$$PWD/cdbdebugeventcallback.h \
$$PWD/cdbdebugoutput.h \
$$PWD/cdbsymbolgroupcontext.h \
$$PWD/cdbstacktracecontext.h
$$PWD/cdbstacktracecontext.h \
$$PWD/cdbbreakpoint.h
SOURCES += \
$$PWD/cdbdebugengine.cpp \
$$PWD/cdbdebugeventcallback.cpp \
$$PWD/cdbdebugoutput.cpp \
$$PWD/cdbsymbolgroupcontext.cpp \
$$PWD/cdbstacktracecontext.cpp
$$PWD/cdbstacktracecontext.cpp \
$$PWD/cdbbreakpoint.cpp
} else {
message("Debugging Tools for Windows could not be found in $$CDB_PATH")
}
......
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Qt Software Information (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 qt-sales@nokia.com.
**
**************************************************************************/
#include "cdbbreakpoint.h"
#include "breakhandler.h"
#include "cdbdebugengine_p.h"
#include <QtCore/QTextStream>
#include <QtCore/QDir>
#include <QtCore/QDebug>
#include <QtCore/QMap>
namespace Debugger {
namespace Internal {
// The CDB breakpoint expression syntax is:
// `foo.cpp:523`[ "condition"]
// module!function[ "condition"]
static const char sourceFileQuoteC = '`';
CDBBreakPoint::CDBBreakPoint() :
ignoreCount(0),
lineNumber(-1)
{
}
CDBBreakPoint::CDBBreakPoint(const BreakpointData &bpd) :
fileName(bpd.fileName),
condition(bpd.condition),
ignoreCount(0),
funcName(bpd.funcName),
lineNumber(-1)
{
if (!bpd.ignoreCount.isEmpty())
ignoreCount = bpd.ignoreCount.toInt();
if (!bpd.lineNumber.isEmpty())
lineNumber = bpd.lineNumber.toInt();
}
int CDBBreakPoint::compare(const CDBBreakPoint& rhs) const
{
if (ignoreCount > rhs.ignoreCount)
return 1;
if (ignoreCount < rhs.ignoreCount)
return -1;
if (lineNumber > rhs.lineNumber)
return 1;
if (lineNumber < rhs.lineNumber)
return -1;
if (const int fileCmp = fileName.compare(rhs.fileName))
return fileCmp;
if (const int funcCmp = funcName.compare(rhs.funcName))
return funcCmp;
if (const int condCmp = condition.compare(rhs.condition))
return condCmp;
return 0;
}
void CDBBreakPoint::clear()
{
ignoreCount = 0;
clearExpressionData();
}
void CDBBreakPoint::clearExpressionData()
{
fileName.clear();
condition.clear();
funcName.clear();
lineNumber = -1;
}
QDebug operator<<(QDebug dbg, const CDBBreakPoint &bp)
{
dbg.nospace() << "fileName='" << bp.fileName << "' condition='"
<< bp.condition << "' ignoreCount=" << bp.ignoreCount
<< " lineNumber=" << bp.lineNumber
<< " funcName='" << bp.funcName << '\'';
return dbg;
}
QString CDBBreakPoint::expression() const
{
// format the breakpoint expression (file/function and condition)
QString rc;
QTextStream str(&rc);
if (funcName.isEmpty()) {
const QChar sourceFileQuote = QLatin1Char(sourceFileQuoteC);
str << sourceFileQuote << QDir::toNativeSeparators(fileName) << QLatin1Char(':') << lineNumber << sourceFileQuote;
} else {
str << funcName;
}
if (!condition.isEmpty()) {
const QChar doubleQuote = QLatin1Char('"');
str << QLatin1Char(' ') << doubleQuote << condition << doubleQuote;
}
return rc;
}
bool CDBBreakPoint::apply(IDebugBreakpoint2 *ibp, QString *errorMessage) const
{
const QString expr = expression();
if (debugCDB)
qDebug() << Q_FUNC_INFO << *this << expr;
const HRESULT hr = ibp->SetOffsetExpressionWide(expr.utf16());
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Unable to set breakpoint '%1' : %2").
arg(expr, msgComFailed("SetOffsetExpressionWide", hr));
return false;
}
// Pass Count is ignoreCount + 1
ibp->SetPassCount(ignoreCount + 1u);
ibp->AddFlags(DEBUG_BREAKPOINT_ENABLED);
return true;
}
bool CDBBreakPoint::add(IDebugControl4* debugControl, QString *errorMessage) const
{
IDebugBreakpoint2* ibp = 0;
const HRESULT hr = debugControl->AddBreakpoint2(DEBUG_BREAKPOINT_CODE, DEBUG_ANY_ID, &ibp);
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Unable to add breakpoint: %1").
arg(msgComFailed("AddBreakpoint2", hr));
return false;
}
if (!ibp) {
*errorMessage = QString::fromLatin1("Unable to add breakpoint: <Unknown error>");
return false;
}
return apply(ibp, errorMessage);
}
// Make sure file can be found in editor manager and text markers
// Use '/' and capitalize drive letter
QString CDBBreakPoint::canonicalSourceFile(const QString &f)
{
if (f.isEmpty())
return f;
QString rc = QDir::fromNativeSeparators(f);
if (rc.size() > 2 && rc.at(1) == QLatin1Char(':'))
rc[0] = rc.at(0).toUpper();
return rc;
}
bool CDBBreakPoint::retrieve(IDebugBreakpoint2 *ibp, QString *errorMessage)
{
clear();
WCHAR wszBuf[MAX_PATH];
const HRESULT hr =ibp->GetOffsetExpressionWide(wszBuf, MAX_PATH, 0);
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Cannot retrieve breakpoint: %1").
arg(msgComFailed("GetOffsetExpressionWide", hr));
return false;
}
// Pass Count is ignoreCount + 1
ibp->GetPassCount(&ignoreCount);
if (ignoreCount)
ignoreCount--;
const QString expr = QString::fromUtf16(wszBuf);
if (!parseExpression(expr)) {
*errorMessage = QString::fromLatin1("Parsing of '%1' failed.").arg(expr);
return false;
}
return true;
}
bool CDBBreakPoint::parseExpression(const QString &expr)
{
clearExpressionData();
const QChar sourceFileQuote = QLatin1Char(sourceFileQuoteC);
// Check for file or function
int conditionPos = 0;
if (expr.startsWith(sourceFileQuote)) { // `c:\foo.cpp:523`[ "condition"]
// Do not fall for the drive letter colon here
const int colonPos = expr.indexOf(QLatin1Char(':'), 3);
if (colonPos == -1)
return false;
conditionPos = expr.indexOf(sourceFileQuote, colonPos + 1);
if (conditionPos == -1)
return false;
fileName = canonicalSourceFile(expr.mid(1, colonPos - 1));
const QString lineNumberS = expr.mid(colonPos + 1, conditionPos - colonPos - 1);
bool lineNumberOk = false;
lineNumber = lineNumberS.toInt(&lineNumberOk);
if (!lineNumberOk)
return false;
conditionPos++;
} else {
// Check function token
conditionPos = expr.indexOf(QLatin1Char(' '));
if (conditionPos != -1) {
funcName = expr.mid(0, conditionPos);
conditionPos++;
} else {
funcName = expr;
conditionPos = expr.size();
}
}
// Condition? ".if bla"
if (conditionPos >= expr.size())
return true;
const QChar doubleQuote = QLatin1Char('"');
conditionPos = expr.indexOf(doubleQuote, conditionPos);
if (conditionPos == -1)
return true;
conditionPos++;
const int condEndPos = expr.lastIndexOf(doubleQuote);
if (condEndPos == -1)
return false;
condition = expr.mid(conditionPos, condEndPos - conditionPos);
return true;
}
bool CDBBreakPoint::getBreakPoints(IDebugControl4* debugControl, QList<CDBBreakPoint> *bps, QString *errorMessage)
{
ULONG count = 0;
bps->clear();
// get number
HRESULT hr = debugControl->GetNumberBreakpoints(&count);
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Cannot retrieve breakpoints: %1").
arg(msgComFailed("GetNumberBreakpoints", hr));
return false;
}
// retrieve one by one and parse
for (ULONG b= 0; b < count; b++) {
IDebugBreakpoint2 *ibp = 0;
hr = debugControl->GetBreakpointByIndex2(b, &ibp);
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Cannot retrieve breakpoint %1: %2").
arg(b).arg(msgComFailed("GetBreakpointByIndex2", hr));
return false;
}
CDBBreakPoint bp;
if (!bp.retrieve(ibp, errorMessage))
return false;
bps->push_back(bp);
}
return true;
}
// Synchronize (halted) engine breakpoints with those of the BreakHandler.
bool CDBBreakPoint::synchronizeBreakPoints(IDebugControl4* debugControl,
BreakHandler *handler,
QString *errorMessage)
{
typedef QMap<CDBBreakPoint, bool> BreakPointPendingMap;
BreakPointPendingMap breakPointPendingMap;
// convert BreakHandler's bps into a map of BreakPoint->Pending
if (debugCDB)
qDebug() << Q_FUNC_INFO;
const int handlerCount = handler->size();
for (int i=0; i < handlerCount; ++i) {
BreakpointData* breakpoint = handler->at(i);
const bool pending = breakpoint->pending;
breakPointPendingMap.insert(CDBBreakPoint(*breakpoint), pending);
if (pending)
breakpoint->pending = false;
}
ULONG engineCount;
// get number of engine breakpoints
HRESULT hr = debugControl->GetNumberBreakpoints(&engineCount);
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Cannot retrieve number of breakpoints: %1").
arg(msgComFailed("GetNumberBreakpoints", hr));
return false;
}
// Starting from end, check if engine breakpoints are still in handler.
// If not->remove
if (engineCount) {
for (ULONG eb = engineCount - 1u; ; eb--) {
// get engine breakpoint
IDebugBreakpoint2 *ibp = 0;
hr = debugControl->GetBreakpointByIndex2(eb, &ibp);
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Cannot retrieve breakpoint %1: %2").
arg(eb).arg(msgComFailed("GetBreakpointByIndex2", hr));
return false;
}
CDBBreakPoint engineBreakPoint;
if (!engineBreakPoint.retrieve(ibp, errorMessage))
return false;
// Still in handler?
if (!breakPointPendingMap.contains(engineBreakPoint)) {
if (debugCDB)
qDebug() << " Removing" << engineBreakPoint;
hr = debugControl->RemoveBreakpoint2(ibp);
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Cannot remove breakpoint %1: %2").
arg(engineBreakPoint.expression(), msgComFailed("RemoveBreakpoint2", hr));
return false;
}
} // not in handler
if (!eb)
break;
}
}
// Add pending breakpoints
const BreakPointPendingMap::const_iterator pcend = breakPointPendingMap.constEnd();
for (BreakPointPendingMap::const_iterator it = breakPointPendingMap.constBegin(); it != pcend; ++it) {
if (it.value()) {
if (debugCDB)
qDebug() << " Adding " << it.key();
if (!it.key().add(debugControl, errorMessage))
return false;
}
}
if (debugCDB > 1) {
QList<CDBBreakPoint> bps;
CDBBreakPoint::getBreakPoints(debugControl, &bps, errorMessage);
qDebug().nospace() << "### Breakpoints in engine: " << bps;
}
return true;
}
}
}
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Qt Software Information (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 qt-sales@nokia.com.
**
**************************************************************************/
#ifndef CDBBREAKPOINTS_H
#define CDBBREAKPOINTS_H
#include <windows.h>
#include <inc/dbgeng.h>
#include <QtCore/QString>
#include <QtCore/QList>
QT_BEGIN_NAMESPACE
class QDebug;
QT_END_NAMESPACE
namespace Debugger {
namespace Internal {
class BreakHandler;
class BreakpointData;
/* CDB Break point data structure with utilities to
* apply to engine and to retrieve them from the engine and comparison. */
struct CDBBreakPoint {
CDBBreakPoint();
CDBBreakPoint(const BreakpointData &bpd);
int compare(const CDBBreakPoint& rhs) const;
void clear();
void clearExpressionData();
QString expression() const;
// Apply parameters
bool apply(IDebugBreakpoint2 *ibp, QString *errorMessage) const;
// Convenience to add to a IDebugControl4
bool add(IDebugControl4* debugControl, QString *errorMessage) const;
// Retrieve/parse breakpoints from the interfaces
bool retrieve(IDebugBreakpoint2 *ibp, QString *errorMessage);
bool parseExpression(const QString &expr);
// Retrieve all breakpoints from the engine
static bool getBreakPoints(IDebugControl4* debugControl, QList<CDBBreakPoint> *bps, QString *errorMessage);
// Synchronize (halted) engine with BreakHandler.
static bool synchronizeBreakPoints(IDebugControl4* ctl, BreakHandler *bh, QString *errorMessage);
// Return a 'canonical' file (using '/' and capitalized drive letter)
static QString canonicalSourceFile(const QString &f);
QString fileName; // short name of source file
QString condition; // condition associated with breakpoint
unsigned long ignoreCount; // ignore count associated with breakpoint
int lineNumber; // line in source file
QString funcName; // name of containing function
};
QDebug operator<<(QDebug, const CDBBreakPoint &bp);
inline bool operator==(const CDBBreakPoint& b1, const CDBBreakPoint& b2)
{ return b1.compare(b2) == 0; }
inline bool operator!=(const CDBBreakPoint& b1, const CDBBreakPoint& b2)
{ return b1.compare(b2) != 0; }
inline bool operator<(const CDBBreakPoint& b1, const CDBBreakPoint& b2)
{ return b1.compare(b2) < 0; }
}
}
#endif // CDBBREAKPOINTS_H
......@@ -31,6 +31,7 @@
#include "cdbdebugengine_p.h"
#include "cdbsymbolgroupcontext.h"
#include "cdbstacktracecontext.h"
#include "cdbbreakpoint.h"
#include "debuggermanager.h"
#include "breakhandler.h"
......@@ -62,6 +63,8 @@ static const char *localSymbolRootC = "local";
namespace Debugger {
namespace Internal {
typedef QList<WatchData> WatchList;
QString msgDebugEngineComResult(HRESULT hr)
{
switch (hr) {
......@@ -127,6 +130,34 @@ bool DebuggerEngineLibrary::init(QString *errorMessage)
return true;
}
// A class that sets an expression syntax on the debug control while in scope.
// Can be nested as it checks for the old value.
class SyntaxSetter {
Q_DISABLE_COPY(SyntaxSetter)
public:
explicit inline SyntaxSetter(IDebugControl4 *ctl, ULONG desiredSyntax);
inline ~SyntaxSetter();
private:
const ULONG m_desiredSyntax;
IDebugControl4 *m_ctl;
ULONG m_oldSyntax;
};
SyntaxSetter::SyntaxSetter(IDebugControl4 *ctl, ULONG desiredSyntax) :
m_desiredSyntax(desiredSyntax),
m_ctl(ctl)
{
m_ctl->GetExpressionSyntax(&m_oldSyntax);
if (m_oldSyntax != m_desiredSyntax)
m_ctl->SetExpressionSyntax(m_desiredSyntax);
}
SyntaxSetter::~SyntaxSetter()
{
if (m_oldSyntax != m_desiredSyntax)
m_ctl->SetExpressionSyntax(m_oldSyntax);
}
// --- CdbDebugEnginePrivate
CdbDebugEnginePrivate::CdbDebugEnginePrivate(DebuggerManager *parent, CdbDebugEngine* engine) :
......@@ -440,13 +471,36 @@ CdbSymbolGroupContext *CdbDebugEnginePrivate::getStackFrameSymbolGroupContext(in
return 0;
}
static inline QString formatWatchList(const WatchList &wl)
{
const int count = wl.size();
QString rc;
for (int i = 0; i < count; i++) {
if (i)
rc += QLatin1String(", ");
rc += wl.at(i).iname;
rc += QLatin1String(" (");
rc += wl.at(i).exp;
rc += QLatin1Char(')');
}
return rc;
}
bool CdbDebugEnginePrivate::updateLocals(int frameIndex,
WatchHandler *wh,
QString *errorMessage)
{
wh->reinitializeWatchers();
QList<WatchData> incompletes = wh->takeCurrentIncompletes();
if (debugCDB)
qDebug() << Q_FUNC_INFO << frameIndex;
wh->cleanup();
qDebug() << Q_FUNC_INFO << "\n " << frameIndex << formatWatchList(incompletes);
m_engine->filterEvaluateWatchers(&incompletes, wh);
if (!incompletes.empty()) {
const QString msg = QLatin1String("Warning: Locals left in incomplete list: ") + formatWatchList(incompletes);
qWarning("%s\n", qPrintable(msg));
}
bool success = false;
if (CdbSymbolGroupContext *sgc = getStackFrameSymbolGroupContext(frameIndex, errorMessage))
......@@ -456,17 +510,88 @@ bool CdbDebugEnginePrivate::updateLocals(int frameIndex,
return success;
}
void CdbDebugEngine::evaluateWatcher(WatchData *wd)
{
if (debugCDB > 1)
qDebug() << Q_FUNC_INFO << wd->exp;
QString errorMessage;
QString value;
QString type;
if (evaluateExpression(wd->exp, &value, &type, &errorMessage)) {
wd->setValue(value);
wd->setType(type);
} else {
wd->setValue(errorMessage);
wd->setTypeUnneeded();
}
wd->setChildCount(0);
}