Commit d199d660 authored by Friedemann Kleint's avatar Friedemann Kleint

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);
}
void CdbDebugEngine::filterEvaluateWatchers(QList<WatchData> *wd, WatchHandler *wh)