diff --git a/src/plugins/debugger/cdb/breakpoint.cpp b/src/plugins/debugger/cdb/breakpoint.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5ce3edf2d66808098f26f2db2274853797f02a17 --- /dev/null +++ b/src/plugins/debugger/cdb/breakpoint.cpp @@ -0,0 +1,493 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 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 "breakpoint.h" +#include "coreengine.h" + +#include <utils/qtcassert.h> + +#include <QtCore/QTextStream> +#include <QtCore/QDir> +#include <QtCore/QDebug> +#include <QtCore/QMap> + +#include <psapi.h> + +enum { debugBP = 0 }; + +namespace CdbCore { + +// The CDB breakpoint expression syntax is: +// `foo.cpp:523`[ "condition"] +// module!function[ "condition"] + +static const char sourceFileQuoteC = '`'; + +BreakPoint::BreakPoint() : + ignoreCount(0), + lineNumber(-1), + oneShot(false), + enabled(true) +{ +} + +int BreakPoint::compare(const BreakPoint& 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 (oneShot && !rhs.oneShot) + return 1; + if (!oneShot && rhs.oneShot) + return -1; + if (enabled && !rhs.enabled) + return 1; + if (!enabled && rhs.enabled) + 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 BreakPoint::clear() +{ + ignoreCount = 0; + oneShot = false; + enabled = true; + clearExpressionData(); +} + +void BreakPoint::clearExpressionData() +{ + fileName.clear(); + condition.clear(); + funcName.clear(); + lineNumber = -1; +} + +QDebug operator<<(QDebug dbg, const BreakPoint &bp) +{ + QDebug nsp = dbg.nospace(); + if (!bp.fileName.isEmpty()) { + nsp << "fileName='" << bp.fileName << ':' << bp.lineNumber << '\''; + } else { + nsp << "funcName='" << bp.funcName << '\''; + } + if (!bp.condition.isEmpty()) + nsp << " condition='" << bp.condition << '\''; + if (bp.ignoreCount) + nsp << " ignoreCount=" << bp.ignoreCount; + if (bp.enabled) + nsp << " enabled"; + if (bp.oneShot) + nsp << " oneShot"; + return dbg; +} + +QString BreakPoint::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 BreakPoint::apply(CIDebugBreakpoint *ibp, QString *errorMessage) const +{ + const QString expr = expression(); + if (debugBP) + qDebug() << Q_FUNC_INFO << *this << expr; + const HRESULT hr = ibp->SetOffsetExpressionWide(reinterpret_cast<PCWSTR>(expr.utf16())); + if (FAILED(hr)) { + *errorMessage = QString::fromLatin1("Unable to set breakpoint '%1' : %2"). + arg(expr, CdbCore::msgComFailed("SetOffsetExpressionWide", hr)); + return false; + } + // Pass Count is ignoreCount + 1 + ibp->SetPassCount(ignoreCount + 1u); + ULONG flags = 0; + if (enabled) + flags |= DEBUG_BREAKPOINT_ENABLED; + if (oneShot) + flags |= DEBUG_BREAKPOINT_ONE_SHOT; + ibp->AddFlags(flags); + return true; +} + +static inline QString msgCannotAddBreakPoint(const QString &why) +{ + return QString::fromLatin1("Unable to add breakpoint: %1").arg(why); +} + +bool BreakPoint::add(CIDebugControl* debugControl, + QString *errorMessage, + unsigned long *id, + quint64 *address) const +{ + IDebugBreakpoint2* ibp = 0; + if (address) + *address = 0; + if (id) + *id = 0; + HRESULT hr = debugControl->AddBreakpoint2(DEBUG_BREAKPOINT_CODE, DEBUG_ANY_ID, &ibp); + if (FAILED(hr)) { + *errorMessage = msgCannotAddBreakPoint(CdbCore::msgComFailed("AddBreakpoint2", hr)); + return false; + } + if (!ibp) { + *errorMessage = msgCannotAddBreakPoint(QLatin1String("<Unknown error>")); + return false; + } + if (!apply(ibp, errorMessage)) + return false; + // GetOffset can fail when attaching to remote processes, ignore return + if (address) { + hr = ibp->GetOffset(address); + if (FAILED(hr)) + *address = 0; + } + if (id) { + hr = ibp->GetId(id); + if (FAILED(hr)) { + *errorMessage = msgCannotAddBreakPoint(CdbCore::msgComFailed("GetId", hr)); + return false; + } + } + return true; +} + +// Helper for normalizing file names: +// Map the device paths in a file name to back to drive letters +// "/Device/HarddiskVolume1/file.cpp" -> "C:/file.cpp" + +static bool mapDeviceToDriveLetter(QString *s) +{ + enum { bufSize = 512 }; + // Retrieve drive letters and get their device names. + // Do not cache as it may change due to removable/network drives. + TCHAR driveLetters[bufSize]; + if (!GetLogicalDriveStrings(bufSize-1, driveLetters)) + return false; + + TCHAR driveName[MAX_PATH]; + TCHAR szDrive[3] = TEXT(" :"); + for (const TCHAR *driveLetter = driveLetters; *driveLetter; driveLetter++) { + szDrive[0] = *driveLetter; // Look up each device name + if (QueryDosDevice(szDrive, driveName, MAX_PATH)) { + const QString deviceName = QString::fromUtf16(driveName); + if (s->startsWith(deviceName)) { + s->replace(0, deviceName.size(), QString::fromUtf16(szDrive)); + return true; + } + } + } + return false; +} + +// Helper for normalizing file names: +// Determine normalized case of a Windows file name (camelcase.cpp -> CamelCase.cpp) +// as the debugger reports lower case file names. +// Restriction: File needs to exists and be non-empty and will be to be opened/mapped. +// This is the MSDN-recommended way of doing that. The result should be cached. + +static inline QString normalizeFileNameCaseHelper(const QString &f) +{ + HANDLE hFile = CreateFile(f.utf16(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if(hFile == INVALID_HANDLE_VALUE) + return f; + // Get the file size. We need a non-empty file to map it. + DWORD dwFileSizeHi = 0; + DWORD dwFileSizeLo = GetFileSize(hFile, &dwFileSizeHi); + if (dwFileSizeLo == 0 && dwFileSizeHi == 0) { + CloseHandle(hFile); + return f; + } + // Create a file mapping object. + HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 1, NULL); + if (!hFileMap) { + CloseHandle(hFile); + return f; + } + + // Create a file mapping to get the file name. + void* pMem = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 1); + if (!pMem) { + CloseHandle(hFileMap); + CloseHandle(hFile); + return f; + } + + QString rc; + WCHAR pszFilename[MAX_PATH]; + pszFilename[0] = 0; + // Get a file name of the form "/Device/HarddiskVolume1/file.cpp" + if (GetMappedFileName (GetCurrentProcess(), pMem, pszFilename, MAX_PATH)) { + rc = QString::fromUtf16(pszFilename); + if (!mapDeviceToDriveLetter(&rc)) + rc.clear(); + } + + UnmapViewOfFile(pMem); + CloseHandle(hFileMap); + CloseHandle(hFile); + return rc.isEmpty() ? f : rc; +} + +// Make sure file can be found in editor manager and text markers +// Use '/', correct case and capitalize drive letter. Use a cache. + +typedef QHash<QString, QString> NormalizedFileCache; +Q_GLOBAL_STATIC(NormalizedFileCache, normalizedFileNameCache) + +QString BreakPoint::normalizeFileName(const QString &f) +{ + QTC_ASSERT(!f.isEmpty(), return f) + const NormalizedFileCache::const_iterator it = normalizedFileNameCache()->constFind(f); + if (it != normalizedFileNameCache()->constEnd()) + return it.value(); + QString normalizedName = QDir::fromNativeSeparators(normalizeFileNameCaseHelper(f)); + // Upcase drive letter for consistency even if case mapping fails. + if (normalizedName.size() > 2 && normalizedName.at(1) == QLatin1Char(':')) + normalizedName[0] = normalizedName.at(0).toUpper(); + normalizedFileNameCache()->insert(f, normalizedName); + return normalizedName; +} + +void BreakPoint::clearNormalizeFileNameCache() +{ + normalizedFileNameCache()->clear(); +} + +bool BreakPoint::retrieve(CIDebugBreakpoint *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(CdbCore::msgComFailed("GetOffsetExpressionWide", hr)); + return false; + } + // Pass Count is ignoreCount + 1 + ibp->GetPassCount(&ignoreCount); + if (ignoreCount) + ignoreCount--; + ULONG flags = 0; + ibp->GetFlags(&flags); + oneShot = (flags & DEBUG_BREAKPOINT_ONE_SHOT); + enabled = (flags & DEBUG_BREAKPOINT_ENABLED); + const QString expr = QString::fromUtf16(reinterpret_cast<const ushort *>(wszBuf)); + if (!parseExpression(expr)) { + *errorMessage = QString::fromLatin1("Parsing of '%1' failed.").arg(expr); + return false; + } + return true; +} + +bool BreakPoint::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 = normalizeFileName(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 BreakPoint::getBreakPointCount(CIDebugControl* debugControl, ULONG *count, QString *errorMessage /* = 0*/) +{ + const HRESULT hr = debugControl->GetNumberBreakpoints(count); + if (FAILED(hr)) { + if (errorMessage) + *errorMessage = QString::fromLatin1("Cannot determine breakpoint count: %1"). + arg(CdbCore::msgComFailed("GetNumberBreakpoints", hr)); + return false; + } + return true; +} + +bool BreakPoint::getBreakPoints(CIDebugControl* debugControl, QList<BreakPoint> *bps, QString *errorMessage) +{ + ULONG count = 0; + bps->clear(); + if (!getBreakPointCount(debugControl, &count, errorMessage)) + return false; + // retrieve one by one and parse + for (ULONG b= 0; b < count; b++) { + IDebugBreakpoint2 *ibp = 0; + const HRESULT hr = debugControl->GetBreakpointByIndex2(b, &ibp); + if (FAILED(hr)) { + *errorMessage = QString::fromLatin1("Cannot retrieve breakpoint %1: %2"). + arg(b).arg(CdbCore::msgComFailed("GetBreakpointByIndex2", hr)); + return false; + } + BreakPoint bp; + if (!bp.retrieve(ibp, errorMessage)) + return false; + bps->push_back(bp); + } + return true; +} + +// Find a breakpoint by id +static inline QString msgNoBreakPointWithId(unsigned long id, const QString &why) +{ + return QString::fromLatin1("Unable to find breakpoint with id %1: %2").arg(id).arg(why); +} + +IDebugBreakpoint2 *BreakPoint::breakPointById(CIDebugControl *ctl, unsigned long id, QString *errorMessage) +{ + CIDebugBreakpoint *ibp = 0; + const HRESULT hr = ctl->GetBreakpointById2(id, &ibp); + if (FAILED(hr)) { + *errorMessage = msgNoBreakPointWithId(id, CdbCore::msgComFailed("GetBreakpointById2", hr)); + return 0; + } + if (!ibp) { + *errorMessage = msgNoBreakPointWithId(id, QLatin1String("<not found>")); + return 0; + } + return ibp; +} + +// Remove breakpoint by id +bool BreakPoint::removeBreakPointById(CIDebugControl *ctl, unsigned long id, QString *errorMessage) +{ + if (debugBP) + qDebug() << Q_FUNC_INFO << id; + CIDebugBreakpoint *ibp = breakPointById(ctl, id, errorMessage); + if (!ibp) + return false; + const HRESULT hr = ctl->RemoveBreakpoint2(ibp); + if (FAILED(hr)) { + *errorMessage = QString::fromLatin1("Cannot remove breakpoint %1: %2").arg(id).arg(CdbCore::msgComFailed("RemoveBreakpointById2", hr)); + return false; + } + return true; +} + +// Set enabled by id + +// Change enabled state of a breakpoint by id +static inline QString msgCannotSetBreakPointEnabled(unsigned long id, bool enabled, const QString &why) +{ + return QString::fromLatin1("Cannot %1 breakpoint %2: %3"). + arg(QLatin1String(enabled ? "enable" : "disable")).arg(id).arg(why); +} + +bool BreakPoint::setBreakPointEnabledById(CIDebugControl *ctl, unsigned long id, bool enabled, QString *errorMessage) +{ + if (debugBP) + qDebug() << Q_FUNC_INFO << id << enabled; + CIDebugBreakpoint *ibp = breakPointById(ctl, id, errorMessage); + if (!ibp) { + *errorMessage = msgCannotSetBreakPointEnabled(id, enabled, *errorMessage); + return false; + } + // Compare flags + ULONG flags; + HRESULT hr = ibp->GetFlags(&flags); + if (FAILED(hr)) { + *errorMessage = msgCannotSetBreakPointEnabled(id, enabled, CdbCore::msgComFailed("GetFlags", hr)); + return false; + } + const bool wasEnabled = (flags & DEBUG_BREAKPOINT_ENABLED); + if (wasEnabled == enabled) + return true; + // Set new value + if (enabled) { + flags |= DEBUG_BREAKPOINT_ENABLED; + } else { + flags &= ~DEBUG_BREAKPOINT_ENABLED; + } + hr = ibp->SetFlags(flags); + if (FAILED(hr)) { + *errorMessage = msgCannotSetBreakPointEnabled(id, enabled, CdbCore::msgComFailed("SetFlags", hr)); + return false; + } + return true; +} + +} diff --git a/src/plugins/debugger/cdb/breakpoint.h b/src/plugins/debugger/cdb/breakpoint.h new file mode 100644 index 0000000000000000000000000000000000000000..61fb02accd51236d45c08b0dc44282d1acf5b030 --- /dev/null +++ b/src/plugins/debugger/cdb/breakpoint.h @@ -0,0 +1,99 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 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 CDBCOREBREAKPOINTS_H +#define CDBCOREBREAKPOINTS_H + +#include "cdbcom.h" + +#include <QtCore/QString> +#include <QtCore/QList> + +QT_BEGIN_NAMESPACE +class QDebug; +QT_END_NAMESPACE + +namespace CdbCore { + +/* CDB Break point data structure with utilities to + * apply to engine and to retrieve them from the engine and comparison. */ + +struct BreakPoint +{ + BreakPoint(); + + int compare(const BreakPoint& 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(CIDebugControl* debugControl, + QString *errorMessage, + unsigned long *id = 0, + quint64 *address = 0) 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 getBreakPointCount(CIDebugControl* debugControl, ULONG *count, QString *errorMessage = 0); + static bool getBreakPoints(CIDebugControl* debugControl, QList<BreakPoint> *bps, QString *errorMessage); + // Control helpers + static IDebugBreakpoint2 *breakPointById(CIDebugControl *ctl, unsigned long id, QString *errorMessage); + static bool removeBreakPointById(CIDebugControl *ctl, unsigned long id, QString *errorMessage); + static bool setBreakPointEnabledById(CIDebugControl *ctl, unsigned long id, bool enabled, QString *errorMessage); + + // Return a 'canonical' file (using '/' and capitalized drive letter) + static QString normalizeFileName(const QString &f); + static void clearNormalizeFileNameCache(); + + 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 + bool oneShot; + bool enabled; +}; + +QDebug operator<<(QDebug, const BreakPoint &bp); + +inline bool operator==(const BreakPoint& b1, const BreakPoint& b2) + { return b1.compare(b2) == 0; } +inline bool operator!=(const BreakPoint& b1, const BreakPoint& b2) + { return b1.compare(b2) != 0; } + +} + +#endif // CDBCOREBREAKPOINTS_H diff --git a/src/plugins/debugger/cdb/cdb.pri b/src/plugins/debugger/cdb/cdb.pri index 050083339741180d4df7e8bf7167810346ab61a7..fa0b173331f0ca58de979e578805abbfe8642ad2 100644 --- a/src/plugins/debugger/cdb/cdb.pri +++ b/src/plugins/debugger/cdb/cdb.pri @@ -10,7 +10,6 @@ HEADERS += \ $$PWD/cdbsymbolgroupcontext.h \ $$PWD/cdbsymbolgroupcontext_tpl.h \ $$PWD/cdbstacktracecontext.h \ - $$PWD/cdbstackframecontext.h \ $$PWD/cdbbreakpoint.h \ $$PWD/cdbmodules.h \ $$PWD/cdbassembler.h \ @@ -25,7 +24,6 @@ SOURCES += \ $$PWD/cdbdebugeventcallback.cpp \ $$PWD/cdbdebugoutput.cpp \ $$PWD/cdbsymbolgroupcontext.cpp \ - $$PWD/cdbstackframecontext.cpp \ $$PWD/cdbstacktracecontext.cpp \ $$PWD/cdbbreakpoint.cpp \ $$PWD/cdbmodules.cpp \ @@ -37,6 +35,4 @@ SOURCES += \ $$PWD/cdbexceptionutils.cpp FORMS += $$PWD/cdboptionspagewidget.ui - -LIBS+=-lpsapi } diff --git a/src/plugins/debugger/cdb/cdbbreakpoint.cpp b/src/plugins/debugger/cdb/cdbbreakpoint.cpp index 5c81b371512d02d3f544d5d00b6e0cb5d12cecea..032222a3cb961c90508673d2759e4620654b6dbc 100644 --- a/src/plugins/debugger/cdb/cdbbreakpoint.cpp +++ b/src/plugins/debugger/cdb/cdbbreakpoint.cpp @@ -29,482 +29,13 @@ #include "cdbbreakpoint.h" #include "cdbmodules.h" -#include "breakhandler.h" -#include "cdbdebugengine_p.h" -#include <QtCore/QTextStream> -#include <QtCore/QDir> -#include <QtCore/QDebug> -#include <QtCore/QMap> - -#include <psapi.h> - -enum { debugBP = 0 }; 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), - oneShot(false), - enabled(true) -{ -} - -CDBBreakPoint::CDBBreakPoint(const BreakpointData &bpd) : - fileName(QDir::toNativeSeparators(bpd.fileName)), - condition(bpd.condition), - ignoreCount(0), - funcName(bpd.funcName), - lineNumber(-1), - oneShot(false), - enabled(bpd.enabled) -{ - 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 (oneShot && !rhs.oneShot) - return 1; - if (!oneShot && rhs.oneShot) - return -1; - if (enabled && !rhs.enabled) - return 1; - if (!enabled && rhs.enabled) - 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; - oneShot = false; - enabled = true; - clearExpressionData(); -} - -void CDBBreakPoint::clearExpressionData() -{ - fileName.clear(); - condition.clear(); - funcName.clear(); - lineNumber = -1; -} - -QDebug operator<<(QDebug dbg, const CDBBreakPoint &bp) -{ - QDebug nsp = dbg.nospace(); - if (!bp.fileName.isEmpty()) { - nsp << "fileName='" << bp.fileName << ':' << bp.lineNumber << '\''; - } else { - nsp << "funcName='" << bp.funcName << '\''; - } - if (!bp.condition.isEmpty()) - nsp << " condition='" << bp.condition << '\''; - if (bp.ignoreCount) - nsp << " ignoreCount=" << bp.ignoreCount; - if (bp.enabled) - nsp << " enabled"; - if (bp.oneShot) - nsp << " oneShot"; - 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(CIDebugBreakpoint *ibp, QString *errorMessage) const -{ - const QString expr = expression(); - if (debugCDB) - qDebug() << Q_FUNC_INFO << *this << expr; - const HRESULT hr = ibp->SetOffsetExpressionWide(reinterpret_cast<PCWSTR>(expr.utf16())); - if (FAILED(hr)) { - *errorMessage = QString::fromLatin1("Unable to set breakpoint '%1' : %2"). - arg(expr, CdbCore::msgComFailed("SetOffsetExpressionWide", hr)); - return false; - } - // Pass Count is ignoreCount + 1 - ibp->SetPassCount(ignoreCount + 1u); - ULONG flags = 0; - if (enabled) - flags |= DEBUG_BREAKPOINT_ENABLED; - if (oneShot) - flags |= DEBUG_BREAKPOINT_ONE_SHOT; - ibp->AddFlags(flags); - return true; -} - -static inline QString msgCannotAddBreakPoint(const QString &why) -{ - return QString::fromLatin1("Unable to add breakpoint: %1").arg(why); -} - -bool CDBBreakPoint::add(CIDebugControl* debugControl, - QString *errorMessage, - unsigned long *id, - quint64 *address) const -{ - IDebugBreakpoint2* ibp = 0; - if (address) - *address = 0; - if (id) - *id = 0; - HRESULT hr = debugControl->AddBreakpoint2(DEBUG_BREAKPOINT_CODE, DEBUG_ANY_ID, &ibp); - if (FAILED(hr)) { - *errorMessage = msgCannotAddBreakPoint(CdbCore::msgComFailed("AddBreakpoint2", hr)); - return false; - } - if (!ibp) { - *errorMessage = msgCannotAddBreakPoint(QLatin1String("<Unknown error>")); - return false; - } - if (!apply(ibp, errorMessage)) - return false; - // GetOffset can fail when attaching to remote processes, ignore return - if (address) { - hr = ibp->GetOffset(address); - if (FAILED(hr)) - *address = 0; - } - if (id) { - hr = ibp->GetId(id); - if (FAILED(hr)) { - *errorMessage = msgCannotAddBreakPoint(CdbCore::msgComFailed("GetId", hr)); - return false; - } - } - return true; -} - -// Helper for normalizing file names: -// Map the device paths in a file name to back to drive letters -// "/Device/HarddiskVolume1/file.cpp" -> "C:/file.cpp" - -static bool mapDeviceToDriveLetter(QString *s) -{ - enum { bufSize = 512 }; - // Retrieve drive letters and get their device names. - // Do not cache as it may change due to removable/network drives. - TCHAR driveLetters[bufSize]; - if (!GetLogicalDriveStrings(bufSize-1, driveLetters)) - return false; - - TCHAR driveName[MAX_PATH]; - TCHAR szDrive[3] = TEXT(" :"); - for (const TCHAR *driveLetter = driveLetters; *driveLetter; driveLetter++) { - szDrive[0] = *driveLetter; // Look up each device name - if (QueryDosDevice(szDrive, driveName, MAX_PATH)) { - const QString deviceName = QString::fromUtf16(driveName); - if (s->startsWith(deviceName)) { - s->replace(0, deviceName.size(), QString::fromUtf16(szDrive)); - return true; - } - } - } - return false; -} - -// Helper for normalizing file names: -// Determine normalized case of a Windows file name (camelcase.cpp -> CamelCase.cpp) -// as the debugger reports lower case file names. -// Restriction: File needs to exists and be non-empty and will be to be opened/mapped. -// This is the MSDN-recommended way of doing that. The result should be cached. - -static inline QString normalizeFileNameCaseHelper(const QString &f) -{ - HANDLE hFile = CreateFile(f.utf16(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); - if(hFile == INVALID_HANDLE_VALUE) - return f; - // Get the file size. We need a non-empty file to map it. - DWORD dwFileSizeHi = 0; - DWORD dwFileSizeLo = GetFileSize(hFile, &dwFileSizeHi); - if (dwFileSizeLo == 0 && dwFileSizeHi == 0) { - CloseHandle(hFile); - return f; - } - // Create a file mapping object. - HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 1, NULL); - if (!hFileMap) { - CloseHandle(hFile); - return f; - } - - // Create a file mapping to get the file name. - void* pMem = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 1); - if (!pMem) { - CloseHandle(hFileMap); - CloseHandle(hFile); - return f; - } - - QString rc; - WCHAR pszFilename[MAX_PATH]; - pszFilename[0] = 0; - // Get a file name of the form "/Device/HarddiskVolume1/file.cpp" - if (GetMappedFileName (GetCurrentProcess(), pMem, pszFilename, MAX_PATH)) { - rc = QString::fromUtf16(pszFilename); - if (!mapDeviceToDriveLetter(&rc)) - rc.clear(); - } - - UnmapViewOfFile(pMem); - CloseHandle(hFileMap); - CloseHandle(hFile); - return rc.isEmpty() ? f : rc; -} - -// Make sure file can be found in editor manager and text markers -// Use '/', correct case and capitalize drive letter. Use a cache. - -typedef QHash<QString, QString> NormalizedFileCache; -Q_GLOBAL_STATIC(NormalizedFileCache, normalizedFileNameCache) - -QString CDBBreakPoint::normalizeFileName(const QString &f) -{ - QTC_ASSERT(!f.isEmpty(), return f) - const NormalizedFileCache::const_iterator it = normalizedFileNameCache()->constFind(f); - if (it != normalizedFileNameCache()->constEnd()) - return it.value(); - QString normalizedName = QDir::fromNativeSeparators(normalizeFileNameCaseHelper(f)); - // Upcase drive letter for consistency even if case mapping fails. - if (normalizedName.size() > 2 && normalizedName.at(1) == QLatin1Char(':')) - normalizedName[0] = normalizedName.at(0).toUpper(); - normalizedFileNameCache()->insert(f, normalizedName); - return normalizedName; -} - -void CDBBreakPoint::clearNormalizeFileNameCache() -{ - normalizedFileNameCache()->clear(); -} - -bool CDBBreakPoint::retrieve(CIDebugBreakpoint *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(CdbCore::msgComFailed("GetOffsetExpressionWide", hr)); - return false; - } - // Pass Count is ignoreCount + 1 - ibp->GetPassCount(&ignoreCount); - if (ignoreCount) - ignoreCount--; - ULONG flags = 0; - ibp->GetFlags(&flags); - oneShot = (flags & DEBUG_BREAKPOINT_ONE_SHOT); - enabled = (flags & DEBUG_BREAKPOINT_ENABLED); - const QString expr = QString::fromUtf16(reinterpret_cast<const ushort *>(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 = normalizeFileName(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::getBreakPointCount(CIDebugControl* debugControl, ULONG *count, QString *errorMessage /* = 0*/) -{ - const HRESULT hr = debugControl->GetNumberBreakpoints(count); - if (FAILED(hr)) { - if (errorMessage) - *errorMessage = QString::fromLatin1("Cannot determine breakpoint count: %1"). - arg(CdbCore::msgComFailed("GetNumberBreakpoints", hr)); - return false; - } - return true; -} - -bool CDBBreakPoint::getBreakPoints(CIDebugControl* debugControl, QList<CDBBreakPoint> *bps, QString *errorMessage) -{ - ULONG count = 0; - bps->clear(); - if (!getBreakPointCount(debugControl, &count, errorMessage)) - return false; - // retrieve one by one and parse - for (ULONG b= 0; b < count; b++) { - IDebugBreakpoint2 *ibp = 0; - const HRESULT hr = debugControl->GetBreakpointByIndex2(b, &ibp); - if (FAILED(hr)) { - *errorMessage = QString::fromLatin1("Cannot retrieve breakpoint %1: %2"). - arg(b).arg(CdbCore::msgComFailed("GetBreakpointByIndex2", hr)); - return false; - } - CDBBreakPoint bp; - if (!bp.retrieve(ibp, errorMessage)) - return false; - bps->push_back(bp); - } - return true; -} - -// Find a breakpoint by id -static inline QString msgNoBreakPointWithId(unsigned long id, const QString &why) -{ - return QString::fromLatin1("Unable to find breakpoint with id %1: %2").arg(id).arg(why); -} - -static IDebugBreakpoint2 *breakPointById(CIDebugControl *ctl, unsigned long id, QString *errorMessage) -{ - CIDebugBreakpoint *ibp = 0; - const HRESULT hr = ctl->GetBreakpointById2(id, &ibp); - if (FAILED(hr)) { - *errorMessage = msgNoBreakPointWithId(id, CdbCore::msgComFailed("GetBreakpointById2", hr)); - return 0; - } - if (!ibp) { - *errorMessage = msgNoBreakPointWithId(id, QLatin1String("<not found>")); - return 0; - } - return ibp; -} - -// Remove breakpoint by id -static bool removeBreakPointById(CIDebugControl *ctl, unsigned long id, QString *errorMessage) -{ - if (debugBP) - qDebug() << Q_FUNC_INFO << id; - CIDebugBreakpoint *ibp = breakPointById(ctl, id, errorMessage); - if (!ibp) - return false; - const HRESULT hr = ctl->RemoveBreakpoint2(ibp); - if (FAILED(hr)) { - *errorMessage = QString::fromLatin1("Cannot remove breakpoint %1: %2").arg(id).arg(CdbCore::msgComFailed("RemoveBreakpointById2", hr)); - return false; - } - return true; -} - -// Set enabled by id - -// Change enabled state of a breakpoint by id -static inline QString msgCannotSetBreakPointEnabled(unsigned long id, bool enabled, const QString &why) -{ - return QString::fromLatin1("Cannot %1 breakpoint %2: %3"). - arg(QLatin1String(enabled ? "enable" : "disable")).arg(id).arg(why); -} +enum { debugBP = 0 }; -static bool setBreakPointEnabledById(CIDebugControl *ctl, unsigned long id, bool enabled, QString *errorMessage) -{ - if (debugBP) - qDebug() << Q_FUNC_INFO << id << enabled; - CIDebugBreakpoint *ibp = breakPointById(ctl, id, errorMessage); - if (!ibp) { - *errorMessage = msgCannotSetBreakPointEnabled(id, enabled, *errorMessage); - return false; - } - // Compare flags - ULONG flags; - HRESULT hr = ibp->GetFlags(&flags); - if (FAILED(hr)) { - *errorMessage = msgCannotSetBreakPointEnabled(id, enabled, CdbCore::msgComFailed("GetFlags", hr)); - return false; - } - const bool wasEnabled = (flags & DEBUG_BREAKPOINT_ENABLED); - if (wasEnabled == enabled) - return true; - // Set new value - if (enabled) { - flags |= DEBUG_BREAKPOINT_ENABLED; - } else { - flags &= ~DEBUG_BREAKPOINT_ENABLED; - } - hr = ibp->SetFlags(flags); - if (FAILED(hr)) { - *errorMessage = msgCannotSetBreakPointEnabled(id, enabled, CdbCore::msgComFailed("SetFlags", hr)); - return false; - } - return true; -} static inline QString msgCannotSetBreakAtFunction(const QString &func, const QString &why) { @@ -512,17 +43,18 @@ static inline QString msgCannotSetBreakAtFunction(const QString &func, const QSt } // Synchronize (halted) engine breakpoints with those of the BreakHandler. -bool CDBBreakPoint::synchronizeBreakPoints(CIDebugControl* debugControl, - CIDebugSymbols *syms, - BreakHandler *handler, - QString *errorMessage, QStringList *warnings) +bool synchronizeBreakPoints(CIDebugControl* debugControl, + CIDebugSymbols *syms, + BreakHandler *handler, + QString *errorMessage, + QStringList *warnings) { errorMessage->clear(); warnings->clear(); // Do an initial check whether we are in a state that allows // for modifying breakPoints ULONG engineCount; - if (!getBreakPointCount(debugControl, &engineCount, errorMessage)) { + if (!CdbCore::BreakPoint::getBreakPointCount(debugControl, &engineCount, errorMessage)) { *errorMessage = QString::fromLatin1("Cannot modify breakpoints: %1").arg(*errorMessage); return false; } @@ -556,7 +88,7 @@ bool CDBBreakPoint::synchronizeBreakPoints(CIDebugControl* debugControl, if (breakPointOk) { quint64 address; unsigned long id; - CDBBreakPoint ncdbbp(*nbd); + const CdbCore::BreakPoint ncdbbp = breakPointFromBreakPointData(*nbd); breakPointOk = ncdbbp.add(debugControl, &warning, &id, &address); if (breakPointOk) { if (debugBP) @@ -578,24 +110,24 @@ bool CDBBreakPoint::synchronizeBreakPoints(CIDebugControl* debugControl, warnings->push_back(warning); } // Delete foreach (BreakpointData *rbd, handler->takeRemovedBreakpoints()) { - if (!removeBreakPointById(debugControl, rbd->bpNumber.toUInt(), &warning)) + if (!CdbCore::BreakPoint::removeBreakPointById(debugControl, rbd->bpNumber.toUInt(), &warning)) warnings->push_back(warning); delete rbd; } // Enable/Disable foreach (BreakpointData *ebd, handler->takeEnabledBreakpoints()) - if (!setBreakPointEnabledById(debugControl, ebd->bpNumber.toUInt(), true, &warning)) + if (!CdbCore::BreakPoint::setBreakPointEnabledById(debugControl, ebd->bpNumber.toUInt(), true, &warning)) warnings->push_back(warning); foreach (BreakpointData *dbd, handler->takeDisabledBreakpoints()) - if (!setBreakPointEnabledById(debugControl, dbd->bpNumber.toUInt(), false, &warning)) + if (!CdbCore::BreakPoint::setBreakPointEnabledById(debugControl, dbd->bpNumber.toUInt(), false, &warning)) warnings->push_back(warning); if (updateMarkers) handler->updateMarkers(); if (debugBP > 1) { - QList<CDBBreakPoint> bps; - CDBBreakPoint::getBreakPoints(debugControl, &bps, errorMessage); + QList<CdbCore::BreakPoint> bps; + CdbCore::BreakPoint::getBreakPoints(debugControl, &bps, errorMessage); qDebug().nospace() << "### Breakpoints in engine: " << bps; } return true; diff --git a/src/plugins/debugger/cdb/cdbbreakpoint.h b/src/plugins/debugger/cdb/cdbbreakpoint.h index 3a1e8e09b5f7b787b8788f444fd204e46336f47d..d5df85fd836819186b47b15db59c09d991031217 100644 --- a/src/plugins/debugger/cdb/cdbbreakpoint.h +++ b/src/plugins/debugger/cdb/cdbbreakpoint.h @@ -31,73 +31,39 @@ #define CDBBREAKPOINTS_H #include "cdbcom.h" +#include "breakpoint.h" +#include "breakhandler.h" #include <QtCore/QString> #include <QtCore/QList> +#include <QtCore/QDir> 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 +// Convert breakpoint structs +inline CdbCore::BreakPoint breakPointFromBreakPointData(const Debugger::Internal::BreakpointData &bpd) { - CDBBreakPoint(); - CDBBreakPoint(const BreakpointData &bpd); - - int compare(const CDBBreakPoint& rhs) const; - - void clear(); - void clearExpressionData(); + CdbCore::BreakPoint rc; + rc.fileName = QDir::toNativeSeparators(bpd.fileName); + rc.condition = bpd.condition; + rc.funcName = bpd.funcName; + rc.ignoreCount = bpd.ignoreCount.isEmpty() ? 0 : bpd.ignoreCount.toInt(); + rc.lineNumber = bpd.lineNumber.isEmpty() ? -1 : bpd.lineNumber.toInt(); + rc.oneShot = false; + rc.enabled = bpd.enabled; + return rc; +} - QString expression() const; - - // Apply parameters - bool apply(IDebugBreakpoint2 *ibp, QString *errorMessage) const; - // Convenience to add to a IDebugControl4 - bool add(CIDebugControl* debugControl, - QString *errorMessage, - unsigned long *id = 0, - quint64 *address = 0) 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 getBreakPointCount(CIDebugControl* debugControl, ULONG *count, QString *errorMessage = 0); - static bool getBreakPoints(CIDebugControl* debugControl, QList<CDBBreakPoint> *bps, QString *errorMessage); - // Synchronize (halted) engine with BreakHandler. - static bool synchronizeBreakPoints(CIDebugControl* ctl, CIDebugSymbols *syms, - BreakHandler *bh, - QString *errorMessage, QStringList *warnings); - - // Return a 'canonical' file (using '/' and capitalized drive letter) - static QString normalizeFileName(const QString &f); - static void clearNormalizeFileNameCache(); - - 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 - bool oneShot; - bool enabled; -}; +namespace Debugger { +namespace Internal { -QDebug operator<<(QDebug, const CDBBreakPoint &bp); +// Synchronize (halted) engine with BreakHandler. +bool synchronizeBreakPoints(CIDebugControl* ctl, CIDebugSymbols *syms, + BreakHandler *bh, + QString *errorMessage, QStringList *warnings); -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; } } // namespace Internal } // namespace Debugger diff --git a/src/plugins/debugger/cdb/cdbcore.pri b/src/plugins/debugger/cdb/cdbcore.pri index 045b0c6382c7819ce11604272ee71c8d444bc164..7c65cada5ecaddb37186711a168b1afb75ac5d46 100644 --- a/src/plugins/debugger/cdb/cdbcore.pri +++ b/src/plugins/debugger/cdb/cdbcore.pri @@ -26,15 +26,24 @@ HEADERS += \ $$PWD/cdbcom.h \ $$PWD/coreengine.h \ $$PWD/debugoutputbase.h \ - $$PWD/debugeventcallbackbase.h + $$PWD/debugeventcallbackbase.h \ + $$PWD/symbolgroupcontext.h \ + $$PWD/stacktracecontext.h \ + $$PWD/breakpoint.h + SOURCES += \ $$PWD/coreengine.cpp \ $$PWD/debugoutputbase.cpp \ - $$PWD/debugeventcallbackbase.cpp + $$PWD/debugeventcallbackbase.cpp \ + $$PWD/symbolgroupcontext.cpp \ + $$PWD/stacktracecontext.cpp \ + $$PWD/breakpoint.cpp INCLUDEPATH*=$$PWD DEPENDPATH*=$$PWD +LIBS+=-lpsapi + } else { message("Debugging Tools for Windows could not be found in $$CDB_PATH") CDB_PATH="" diff --git a/src/plugins/debugger/cdb/cdbdebugengine.cpp b/src/plugins/debugger/cdb/cdbdebugengine.cpp index 14b22a6dc519a536e77bdab5dc62d481aeb472c6..81e872d8fec262642d38394ddc59868585f8bef7 100644 --- a/src/plugins/debugger/cdb/cdbdebugengine.cpp +++ b/src/plugins/debugger/cdb/cdbdebugengine.cpp @@ -248,17 +248,21 @@ QString CdbDebugEngine::editorToolTip(const QString &exp, const QString &functio QString errorMessage; QString rc; // Find the frame of the function if there is any - CdbStackFrameContext *frame = 0; + CdbSymbolGroupContext *frame = 0; if (m_d->m_currentStackTrace && !function.isEmpty()) { const int frameIndex = m_d->m_currentStackTrace->indexOf(function); + if (debugToolTips) + qDebug() << "CdbDebugEngine::editorToolTip" << exp << function << frameIndex; if (frameIndex != -1) - frame = m_d->m_currentStackTrace->frameContextAt(frameIndex, &errorMessage); + frame = m_d->m_currentStackTrace->cdbSymbolGroupContextAt(frameIndex, &errorMessage); } if (frame && frame->editorToolTip(QLatin1String("local.") + exp, &rc, &errorMessage)) return rc; // No function/symbol context found, try to evaluate in current context. // Do not append type as this will mostly be 'long long' for integers, etc. QString type; + if (debugToolTips) + qDebug() << "Defaulting to expression"; if (!m_d->evaluateExpression(exp, &rc, &type, &errorMessage)) return QString(); return rc; @@ -341,7 +345,7 @@ void CdbDebugEngine::startDebugger(const QSharedPointer<DebuggerStartParameters> { if (debugCDBExecution) qDebug() << "startDebugger" << *sp; - CDBBreakPoint::clearNormalizeFileNameCache(); + CdbCore::BreakPoint::clearNormalizeFileNameCache(); setState(AdapterStarting, Q_FUNC_INFO, __LINE__); m_d->checkVersion(); if (m_d->m_hDebuggeeProcess) { @@ -599,13 +603,13 @@ void CdbDebugEngine::detachDebugger() m_d->endDebugging(CdbDebugEnginePrivate::EndDebuggingDetach); } -CdbStackFrameContext *CdbDebugEnginePrivate::getStackFrameContext(int frameIndex, QString *errorMessage) const +CdbSymbolGroupContext *CdbDebugEnginePrivate::getSymbolGroupContext(int frameIndex, QString *errorMessage) const { if (!m_currentStackTrace) { *errorMessage = QLatin1String(msgNoStackTraceC); return 0; } - if (CdbStackFrameContext *sg = m_currentStackTrace->frameContextAt(frameIndex, errorMessage)) + if (CdbSymbolGroupContext *sg = m_currentStackTrace->cdbSymbolGroupContextAt(frameIndex, errorMessage)) return sg; return 0; } @@ -649,7 +653,7 @@ void CdbDebugEngine::updateWatchData(const WatchData &incomplete) bool success = false; QString errorMessage; do { - CdbStackFrameContext *sg = m_d->m_currentStackTrace->frameContextAt(frameIndex, &errorMessage); + CdbSymbolGroupContext *sg = m_d->m_currentStackTrace->cdbSymbolGroupContextAt(frameIndex, &errorMessage); if (!sg) break; if (!sg->completeData(incomplete, watchHandler, &errorMessage)) @@ -902,7 +906,7 @@ void CdbDebugEngine::runToLineExec(const QString &fileName, int lineNumber) { manager()->showDebuggerOutput(LogMisc, tr("Running up to %1:%2...").arg(fileName).arg(lineNumber)); QString errorMessage; - CDBBreakPoint tempBreakPoint; + CdbCore::BreakPoint tempBreakPoint; tempBreakPoint.fileName = fileName; tempBreakPoint.lineNumber = lineNumber; tempBreakPoint.oneShot = true; @@ -916,7 +920,7 @@ void CdbDebugEngine::runToFunctionExec(const QString &functionName) { manager()->showDebuggerOutput(LogMisc, tr("Running up to function '%1()'...").arg(functionName)); QString errorMessage; - CDBBreakPoint tempBreakPoint; + CdbCore::BreakPoint tempBreakPoint; tempBreakPoint.funcName = functionName; tempBreakPoint.oneShot = true; const bool ok = tempBreakPoint.add(m_d->interfaces().debugControl, &errorMessage) @@ -939,7 +943,7 @@ void CdbDebugEngine::assignValueInDebugger(const QString &expr, const QString &v bool success = false; do { QString newValue; - CdbStackFrameContext *sg = m_d->getStackFrameContext(frameIndex, &errorMessage); + CdbSymbolGroupContext *sg = m_d->getSymbolGroupContext(frameIndex, &errorMessage); if (!sg) break; if (!sg->assignValue(expr, value, &newValue, &errorMessage)) @@ -1004,7 +1008,7 @@ void CdbDebugEngine::activateFrame(int frameIndex) if (oldIndex != frameIndex || m_d->m_firstActivatedFrame) { watchHandler->beginCycle(); - if (CdbStackFrameContext *sgc = m_d->getStackFrameContext(frameIndex, &errorMessage)) + if (CdbSymbolGroupContext *sgc = m_d->getSymbolGroupContext(frameIndex, &errorMessage)) success = sgc->populateModelInitially(watchHandler, &errorMessage); watchHandler->endCycle(); } else { @@ -1059,7 +1063,7 @@ bool CdbDebugEnginePrivate::attemptBreakpointSynchronization(QString *errorMessa // called again from the debug event handler. ULONG dummy; - const bool wasRunning = !CDBBreakPoint::getBreakPointCount(interfaces().debugControl, &dummy); + const bool wasRunning = !CdbCore::BreakPoint::getBreakPointCount(interfaces().debugControl, &dummy); if (debugCDB) qDebug() << Q_FUNC_INFO << "\n Running=" << wasRunning; @@ -1074,10 +1078,10 @@ bool CdbDebugEnginePrivate::attemptBreakpointSynchronization(QString *errorMessa } QStringList warnings; - const bool ok = CDBBreakPoint::synchronizeBreakPoints(interfaces().debugControl, - interfaces().debugSymbols, - manager()->breakHandler(), - errorMessage, &warnings); + const bool ok = synchronizeBreakPoints(interfaces().debugControl, + interfaces().debugSymbols, + manager()->breakHandler(), + errorMessage, &warnings); if (const int warningsCount = warnings.size()) for (int w = 0; w < warningsCount; w++) m_engine->warning(warnings.at(w)); @@ -1361,7 +1365,7 @@ ULONG CdbDebugEnginePrivate::updateThreadList() ULONG currentThreadId; QString errorMessage; // When interrupting, an artifical thread with a breakpoint is created. - if (!CdbStackTraceContext::getThreads(interfaces(), true, &threads, ¤tThreadId, &errorMessage)) + if (!CdbStackTraceContext::getThreads(interfaces(), &threads, ¤tThreadId, &errorMessage)) m_engine->warning(errorMessage); manager()->threadsHandler()->setThreads(threads); return currentThreadId; @@ -1378,14 +1382,18 @@ static inline unsigned long dumperThreadId(const QList<StackFrame> &frames, { if (frames.empty()) return CdbDumperHelper::InvalidDumperCallThread; - if (frames.at(0).function == QLatin1String(CdbStackTraceContext::winFuncDebugBreakPoint)) + switch (CdbCore::StackTraceContext::specialFunction(frames.at(0).from, frames.at(0).function)) { + case CdbCore::StackTraceContext::BreakPointFunction: + case CdbCore::StackTraceContext::WaitFunction: return CdbDumperHelper::InvalidDumperCallThread; + default: + break; + } + // Check remaining frames for wait const int waitCheckDepth = qMin(frames.size(), 5); - static const QString waitForPrefix = QLatin1String(CdbStackTraceContext::winFuncWaitForPrefix); - static const QString msgWaitForPrefix = QLatin1String(CdbStackTraceContext::winFuncMsgWaitForPrefix); - for (int f = 0; f < waitCheckDepth; f++) { - const QString &function = frames.at(f).function; - if (function.startsWith(waitForPrefix) || function.startsWith(msgWaitForPrefix)) + for (int f = 1; f < waitCheckDepth; f++) { + if (CdbCore::StackTraceContext::specialFunction(frames.at(f).from, frames.at(f).function) + == CdbCore::StackTraceContext::WaitFunction) return CdbDumperHelper::InvalidDumperCallThread; } return currentThread; @@ -1404,7 +1412,7 @@ void CdbDebugEnginePrivate::updateStackTrace() return; } m_currentStackTrace = - CdbStackTraceContext::create(m_dumper, m_currentThreadId, &errorMessage); + CdbStackTraceContext::create(m_dumper, &errorMessage); if (!m_currentStackTrace) { m_engine->warning(msgFunctionFailed(Q_FUNC_INFO, errorMessage)); return; @@ -1413,7 +1421,7 @@ void CdbDebugEnginePrivate::updateStackTrace() #if 0 m_engine->reloadDisassembler(); // requires stack trace #endif - const QList<StackFrame> stackFrames = m_currentStackTrace->frames(); + const QList<StackFrame> stackFrames = m_currentStackTrace->stackFrames(); // find the first usable frame and select it int current = -1; const int count = stackFrames.count(); diff --git a/src/plugins/debugger/cdb/cdbdebugengine_p.h b/src/plugins/debugger/cdb/cdbdebugengine_p.h index fe6c2a5b61c0ec3c1aaa9ceb73ff9e651d2fa22f..dabbcf8735094f3e20e500a0a53bbf433bdb0c59 100644 --- a/src/plugins/debugger/cdb/cdbdebugengine_p.h +++ b/src/plugins/debugger/cdb/cdbdebugengine_p.h @@ -46,8 +46,8 @@ class DebuggerManager; namespace Internal { class WatchHandler; -class CdbStackFrameContext; class CdbStackTraceContext; +class CdbSymbolGroupContext; class CdbDebugEnginePrivate : public CdbCore::CoreEngine { @@ -82,7 +82,7 @@ public: void cleanStackTrace(); void clearForRun(); void handleModuleLoad(const QString &); - CdbStackFrameContext *getStackFrameContext(int frameIndex, QString *errorMessage) const; + CdbSymbolGroupContext *getSymbolGroupContext(int frameIndex, QString *errorMessage) const; void clearDisplay(); bool interruptInterferiorProcess(QString *errorMessage); @@ -135,6 +135,7 @@ enum { messageTimeOut = 5000 }; enum { debugCDB = 0 }; enum { debugCDBExecution = 0 }; enum { debugCDBWatchHandling = 0 }; +enum { debugToolTips = 0 }; } // namespace Internal } // namespace Debugger diff --git a/src/plugins/debugger/cdb/cdbdebugeventcallback.cpp b/src/plugins/debugger/cdb/cdbdebugeventcallback.cpp index 0ffc51ff20eda399951dc68beec59dcc55e86d32..ccc249df31a521a085e5db832f167926dbf76b73 100644 --- a/src/plugins/debugger/cdb/cdbdebugeventcallback.cpp +++ b/src/plugins/debugger/cdb/cdbdebugeventcallback.cpp @@ -75,7 +75,7 @@ STDMETHODIMP CdbDebugEventCallback::Exception( QString msg; { QTextStream str(&msg); - formatException(Exception, m_pEngine->m_d->m_dumper, str); + formatException(Exception, &m_pEngine->m_d->interfaces(), str); } const bool fatal = isFatalException(Exception->ExceptionCode); if (debugCDB) diff --git a/src/plugins/debugger/cdb/cdbexceptionutils.cpp b/src/plugins/debugger/cdb/cdbexceptionutils.cpp index d0d61663cf8974a3245efa5b05720bcc7a69a779..1679650bb43d4b0349fe724d395cb163b8243480 100644 --- a/src/plugins/debugger/cdb/cdbexceptionutils.cpp +++ b/src/plugins/debugger/cdb/cdbexceptionutils.cpp @@ -29,8 +29,7 @@ #include "cdbexceptionutils.h" #include "cdbdebugengine_p.h" -#include "cdbdumperhelper.h" -#include "cdbstacktracecontext.h" +#include "stacktracecontext.h" #include <QtCore/QString> #include <QtCore/QTextStream> @@ -247,15 +246,13 @@ void formatException(const EXCEPTION_RECORD64 *e, QTextStream &str) // Format exception with stacktrace in case of C++ exception void formatException(const EXCEPTION_RECORD64 *e, - const QSharedPointer<CdbDumperHelper> &dumper, + const CdbCore::ComInterfaces *cif, QTextStream &str) { formatException(e, str); if (e->ExceptionCode == winExceptionCppException) { QString errorMessage; - ULONG currentThreadId = 0; - dumper->comInterfaces()->debugSystemObjects->GetCurrentThreadId(¤tThreadId); - if (CdbStackTraceContext *stc = CdbStackTraceContext::create(dumper, currentThreadId, &errorMessage)) { + if (CdbCore::StackTraceContext *stc = CdbCore::StackTraceContext::create(cif, 9999, &errorMessage)) { str << "at:\n"; stc->format(str); str <<'\n'; diff --git a/src/plugins/debugger/cdb/cdbexceptionutils.h b/src/plugins/debugger/cdb/cdbexceptionutils.h index b54447580f34c9e3b0fc592e39fcd85e3681dec4..2a5cc2e7528ffe215ec302767f5780d9519a7ef6 100644 --- a/src/plugins/debugger/cdb/cdbexceptionutils.h +++ b/src/plugins/debugger/cdb/cdbexceptionutils.h @@ -39,6 +39,10 @@ QT_BEGIN_NAMESPACE class QTextStream; QT_END_NAMESPACE +namespace CdbCore { + struct ComInterfaces; +} + namespace Debugger { namespace Internal { @@ -94,7 +98,7 @@ void formatException(const EXCEPTION_RECORD64 *e, QTextStream &str); // Format exception with stacktrace in case of C++ exception void formatException(const EXCEPTION_RECORD64 *e, - const QSharedPointer<CdbDumperHelper> &dumper, + const CdbCore::ComInterfaces *cif, QTextStream &str); // Is this a crash/recoverable? diff --git a/src/plugins/debugger/cdb/cdbstackframecontext.cpp b/src/plugins/debugger/cdb/cdbstackframecontext.cpp index 7aceaae9100e8fa2e50deca1645910b023b953e1..be11b3e600ab51e9c05e0484a821790b91d890b3 100644 --- a/src/plugins/debugger/cdb/cdbstackframecontext.cpp +++ b/src/plugins/debugger/cdb/cdbstackframecontext.cpp @@ -310,15 +310,14 @@ bool CdbStackFrameContext::populateModelInitially(WatchHandler *wh, QString *err qDebug() << "populateModelInitially dumpers=" << m_useDumpers; // Recurse down items that are initially expanded in the view, stop processing for // dumper items. - const CdbSymbolGroupRecursionContext rctx(m_symbolContext, OwnerSymbolGroupDumper, - m_dumper->comInterfaces()->debugDataSpaces); + const CdbSymbolGroupRecursionContext rctx(m_symbolContext, OwnerSymbolGroupDumper); const bool rc = m_useDumpers ? - CdbSymbolGroupContext::populateModelInitially(rctx, + CdbSymbolGroupContext::populateModelInitiallyHelper(rctx, WatchHandleDumperInserter(wh, m_dumper), WatchHandlerExpandedPredicate(wh), isDumperPredicate, errorMessage) : - CdbSymbolGroupContext::populateModelInitially(rctx, + CdbSymbolGroupContext::populateModelInitiallyHelper(rctx, WatchHandlerModelInserter(wh), WatchHandlerExpandedPredicate(wh), falsePredicate, @@ -333,11 +332,10 @@ bool CdbStackFrameContext::completeData(const WatchData &incompleteLocal, if (debugCDBWatchHandling) qDebug() << ">completeData src=" << incompleteLocal.source << incompleteLocal.toString(); - const CdbSymbolGroupRecursionContext rctx(m_symbolContext, OwnerSymbolGroupDumper, - m_dumper->comInterfaces()->debugDataSpaces); + const CdbSymbolGroupRecursionContext rctx(m_symbolContext, OwnerSymbolGroupDumper); // Expand symbol group items, recurse one level from desired item if (!m_useDumpers) { - return CdbSymbolGroupContext::completeData(rctx, incompleteLocal, + return CdbSymbolGroupContext::completeDataHelper(rctx, incompleteLocal, WatchHandlerModelInserter(wh), MatchINamePredicate(incompleteLocal.iname), falsePredicate, @@ -375,7 +373,7 @@ bool CdbStackFrameContext::completeData(const WatchData &incompleteLocal, } // Expand symbol group items, recurse one level from desired item - return CdbSymbolGroupContext::completeData(rctx, incompleteLocal, + return CdbSymbolGroupContext::completeDataHelper(rctx, incompleteLocal, WatchHandleDumperInserter(wh, m_dumper), MatchINamePredicate(incompleteLocal.iname), isDumperPredicate, @@ -398,8 +396,12 @@ bool CdbStackFrameContext::editorToolTip(const QString &iname, return false; } // Check dumpers. Should actually be just one item. - const WatchData wd = m_symbolContext->watchDataAt(index); - if (m_useDumpers && !wd.error && m_dumper->state() != CdbDumperHelper::Disabled) { + + WatchData wd; + const unsigned rc = m_symbolContext->watchDataAt(index, &wd); + if (m_useDumpers && !wd.error + && (0u == (rc & CdbCore::SymbolGroupContext::InternalDumperMask)) + && m_dumper->state() != CdbDumperHelper::Disabled) { QList<WatchData> result; if (CdbDumperHelper::DumpOk == m_dumper->dumpType(wd, false, &result, errorMessage)) { foreach (const WatchData &dwd, result) { diff --git a/src/plugins/debugger/cdb/cdbstackframecontext.h b/src/plugins/debugger/cdb/cdbstackframecontext.h deleted file mode 100644 index 99f1464f02f50c2db2dbf56f5fcc2a5612ce9630..0000000000000000000000000000000000000000 --- a/src/plugins/debugger/cdb/cdbstackframecontext.h +++ /dev/null @@ -1,78 +0,0 @@ -/************************************************************************** -** -** This file is part of Qt Creator -** -** Copyright (c) 2009 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 CDBSTACKFRAMECONTEXT_H -#define CDBSTACKFRAMECONTEXT_H - -#include <QtCore/QList> -#include <QtCore/QSharedPointer> - -namespace Debugger { -namespace Internal { - -class WatchData; -class WatchHandler; -class CdbSymbolGroupContext; -class CdbDumperHelper; - -/* CdbStackFrameContext manages a symbol group context and - * a dumper context. It dispatches calls between the local items - * that are handled by the symbol group and those that are handled by the dumpers. */ - -class CdbStackFrameContext -{ - Q_DISABLE_COPY(CdbStackFrameContext) -public: - // Mask bits for the source field of watch data. - enum { SourceMask = 0xFF, ChildrenKnownBit = 0x0100 }; - - explicit CdbStackFrameContext(const QSharedPointer<CdbDumperHelper> &dumper, - CdbSymbolGroupContext *symbolContext); - ~CdbStackFrameContext(); - - bool assignValue(const QString &iname, const QString &value, - QString *newValue /* = 0 */, QString *errorMessage); - bool editorToolTip(const QString &iname, QString *value, QString *errorMessage); - - bool populateModelInitially(WatchHandler *wh, QString *errorMessage); - - bool completeData(const WatchData &incompleteLocal, - WatchHandler *wh, - QString *errorMessage); - -private: - const bool m_useDumpers; - const QSharedPointer<CdbDumperHelper> m_dumper; - CdbSymbolGroupContext *m_symbolContext; -}; - -} // namespace Internal -} // namespace Debugger - -#endif // CDBSTACKFRAMECONTEXT_H diff --git a/src/plugins/debugger/cdb/cdbstacktracecontext.cpp b/src/plugins/debugger/cdb/cdbstacktracecontext.cpp index b79b7fe8241a866be32c145cb1d7feda13dd4fff..80a006a53f7314908a6b9f957ea38411ebd56cbe 100644 --- a/src/plugins/debugger/cdb/cdbstacktracecontext.cpp +++ b/src/plugins/debugger/cdb/cdbstacktracecontext.cpp @@ -28,332 +28,110 @@ **************************************************************************/ #include "cdbstacktracecontext.h" -#include "coreengine.h" -#include "cdbstackframecontext.h" -#include "cdbbreakpoint.h" #include "cdbsymbolgroupcontext.h" -#include "cdbdebugengine_p.h" #include "cdbdumperhelper.h" +#include "cdbdebugengine_p.h" #include "debuggeractions.h" -#include "debuggermanager.h" +#include "watchutils.h" -#include <QtCore/QDir> #include <QtCore/QDebug> -#include <QtCore/QTextStream> + +enum { debug = 1 }; namespace Debugger { namespace Internal { -const char *CdbStackTraceContext::winFuncFastSystemCallRet = "ntdll!KiFastSystemCallRet"; -const char *CdbStackTraceContext::winFuncDebugBreakPoint = "ntdll!DbgBreakPoint"; -const char *CdbStackTraceContext::winFuncWaitForPrefix = "kernel32!WaitFor"; -const char *CdbStackTraceContext::winFuncMsgWaitForPrefix = "kernel32!MsgWaitForMultipleObjects"; - CdbStackTraceContext::CdbStackTraceContext(const QSharedPointer<CdbDumperHelper> &dumper) : - m_dumper(dumper), - m_cif(dumper->comInterfaces()), - m_instructionOffset(0) + CdbCore::StackTraceContext(dumper->comInterfaces()), + m_dumper(dumper) { } CdbStackTraceContext *CdbStackTraceContext::create(const QSharedPointer<CdbDumperHelper> &dumper, - unsigned long threadId, QString *errorMessage) { - if (debugCDB) - qDebug() << Q_FUNC_INFO << threadId; - // fill the DEBUG_STACK_FRAME array - ULONG frameCount; CdbStackTraceContext *ctx = new CdbStackTraceContext(dumper); - const HRESULT hr = dumper->comInterfaces()->debugControl->GetStackTrace(0, 0, 0, ctx->m_cdbFrames, CdbStackTraceContext::maxFrames, &frameCount); - if (FAILED(hr)) { - delete ctx; - *errorMessage = CdbCore::msgComFailed("GetStackTrace", hr); - return 0; - } - if (!ctx->init(frameCount, errorMessage)) { + if (!ctx->init(UINT_MAX, errorMessage)) { delete ctx; return 0; - } return ctx; } -CdbStackTraceContext::~CdbStackTraceContext() -{ - qDeleteAll(m_frameContexts); -} - -bool CdbStackTraceContext::init(unsigned long frameCount, QString * /*errorMessage*/) -{ - if (debugCDB) - qDebug() << Q_FUNC_INFO << frameCount; - - const QChar exclamationMark = QLatin1Char('!'); - m_frameContexts.resize(frameCount); - qFill(m_frameContexts, static_cast<CdbStackFrameContext*>(0)); - - // Convert the DEBUG_STACK_FRAMEs to our StackFrame structure and populate the frames - WCHAR wszBuf[MAX_PATH]; - for (ULONG i=0; i < frameCount; ++i) { - StackFrame frame; - frame.level = i; - const ULONG64 instructionOffset = m_cdbFrames[i].InstructionOffset; - if (i == 0) - m_instructionOffset = instructionOffset; - frame.address = QString::fromLatin1("0x%1").arg(instructionOffset, 0, 16); - - m_cif->debugSymbols->GetNameByOffsetWide(instructionOffset, wszBuf, MAX_PATH, 0, 0); - // Determine function and module, if available - frame.function = QString::fromUtf16(reinterpret_cast<const ushort *>(wszBuf)); - const int moduleSepPos = frame.function.indexOf(exclamationMark); - if (moduleSepPos != -1) - frame.from = frame.function.mid(0, moduleSepPos); - - ULONG ulLine; - ULONG64 ul64Displacement; - const HRESULT hr = m_cif->debugSymbols->GetLineByOffsetWide(instructionOffset, &ulLine, wszBuf, MAX_PATH, 0, &ul64Displacement); - if (SUCCEEDED(hr)) { - frame.line = ulLine; - // Vitally important to use canonical file that matches editormanager, - // else the marker will not show. - frame.file = CDBBreakPoint::normalizeFileName(QString::fromUtf16(reinterpret_cast<const ushort *>(wszBuf))); - } - m_frames.push_back(frame); - } - return true; -} - -int CdbStackTraceContext::indexOf(const QString &function) const -{ - - const QChar exclamationMark = QLatin1Char('!'); - const int count = m_frames.size(); - // Module contained ('module!foo'). Exact match - if (function.contains(exclamationMark)) { - - for (int i = 0; i < count; i++) - if (m_frames.at(i).function == function) - return i; - return -1; - } - // No module, fuzzy match - QString pattern = exclamationMark + function; - for (int i = 0; i < count; i++) - if (m_frames.at(i).function.endsWith(pattern)) - return i; - return -1; -} - -static inline QString msgFrameContextFailed(int index, const StackFrame &f, const QString &why) +CdbCore::SymbolGroupContext + *CdbStackTraceContext::createSymbolGroup(const CdbCore::ComInterfaces & /* cif */, + int index, + const QString &prefix, + CIDebugSymbolGroup *comSymbolGroup, + QString *errorMessage) { - return QString::fromLatin1("Unable to create stack frame context #%1, %2:%3 (%4): %5"). - arg(index).arg(f.function).arg(f.line).arg(f.file, why); -} - -CdbStackFrameContext *CdbStackTraceContext::frameContextAt(int index, QString *errorMessage) -{ - // Create a frame on demand - if (debugCDB) - qDebug() << Q_FUNC_INFO << index; - - if (index < 0 || index >= m_frameContexts.size()) { - *errorMessage = QString::fromLatin1("%1: Index %2 out of range %3."). - arg(QLatin1String(Q_FUNC_INFO)).arg(index).arg(m_frameContexts.size()); - return 0; - } - if (m_frameContexts.at(index)) - return m_frameContexts.at(index); - CIDebugSymbolGroup *sg = createSymbolGroup(index, errorMessage); - if (!sg) { - *errorMessage = msgFrameContextFailed(index, m_frames.at(index), *errorMessage); - return 0; - } // Exclude uninitialized variables if desired QStringList uninitializedVariables; - if (theDebuggerAction(UseCodeModel)->isChecked()) { - const StackFrame &frame = m_frames.at(index); - getUninitializedVariables(DebuggerManager::instance()->cppCodeModelSnapshot(), frame.function, frame.file, frame.line, &uninitializedVariables); - } - CdbSymbolGroupContext *sc = CdbSymbolGroupContext::create(QLatin1String("local"), sg, uninitializedVariables, errorMessage); + const CdbCore::StackFrame &frame = stackFrameAt(index); + if (theDebuggerAction(UseCodeModel)->isChecked()) + getUninitializedVariables(DebuggerManager::instance()->cppCodeModelSnapshot(), frame.function, frame.fileName, frame.line, &uninitializedVariables); + if (debug) + qDebug() << frame << uninitializedVariables; + CdbSymbolGroupContext *sc = CdbSymbolGroupContext::create(prefix, + comSymbolGroup, + m_dumper, + uninitializedVariables, + errorMessage); if (!sc) { - *errorMessage = msgFrameContextFailed(index, m_frames.at(index), *errorMessage); + *errorMessage = msgFrameContextFailed(index, frame, *errorMessage); return 0; } - CdbStackFrameContext *fr = new CdbStackFrameContext(m_dumper, sc); - m_frameContexts[index] = fr; - return fr; + return sc; } -CIDebugSymbolGroup *CdbStackTraceContext::createSymbolGroup(int index, QString *errorMessage) +CdbSymbolGroupContext *CdbStackTraceContext::cdbSymbolGroupContextAt(int index, QString *errorMessage) { - CIDebugSymbolGroup *sg = 0; - HRESULT hr = m_cif->debugSymbols->GetScopeSymbolGroup2(DEBUG_SCOPE_GROUP_LOCALS, NULL, &sg); - if (FAILED(hr)) { - *errorMessage = CdbCore::msgComFailed("GetScopeSymbolGroup", hr); - return 0; - } - - hr = m_cif->debugSymbols->SetScope(0, m_cdbFrames + index, NULL, 0); - if (FAILED(hr)) { - *errorMessage = CdbCore::msgComFailed("SetScope", hr); - sg->Release(); - return 0; - } - // refresh with current frame - hr = m_cif->debugSymbols->GetScopeSymbolGroup2(DEBUG_SCOPE_GROUP_LOCALS, sg, &sg); - if (FAILED(hr)) { - *errorMessage = CdbCore::msgComFailed("GetScopeSymbolGroup", hr); - sg->Release(); - return 0; - } - return sg; -} - -QString CdbStackTraceContext::toString() const -{ - QString rc; - QTextStream str(&rc); - format(str); - return rc; + return static_cast<CdbSymbolGroupContext *>(symbolGroupContextAt(index, errorMessage)); } -void CdbStackTraceContext::format(QTextStream &str) const +QList<StackFrame> CdbStackTraceContext::stackFrames() const { - const int count = m_frames.count(); - const int defaultFieldWidth = str.fieldWidth(); - const QTextStream::FieldAlignment defaultAlignment = str.fieldAlignment(); - for (int f = 0; f < count; f++) { - const StackFrame &frame = m_frames.at(f); - const bool hasFile = !frame.file.isEmpty(); - // left-pad level - str << qSetFieldWidth(6) << left << f; - str.setFieldWidth(defaultFieldWidth); - str.setFieldAlignment(defaultAlignment); - if (hasFile) - str << QDir::toNativeSeparators(frame.file) << ':' << frame.line << " ("; - str << frame.function; - if (hasFile) - str << ')'; - str << '\n'; - } -} - -// Thread state helper - -static inline QString msgGetThreadStateFailed(unsigned long threadId, const QString &why) -{ - return QString::fromLatin1("Unable to determine the state of thread %1: %2").arg(threadId).arg(why); -} - -// Determine information about thread. This changes the -// current thread to thread->id. -static inline bool getStoppedThreadState(const CdbCore::ComInterfaces &cif, - ThreadData *t, - QString *errorMessage) -{ - enum { MaxFrames = 2 }; - ULONG currentThread; - HRESULT hr = cif.debugSystemObjects->GetCurrentThreadId(¤tThread); - if (FAILED(hr)) { - *errorMessage = msgGetThreadStateFailed(t->id, CdbCore::msgComFailed("GetCurrentThreadId", hr)); - return false; - } - if (currentThread != t->id) { - hr = cif.debugSystemObjects->SetCurrentThreadId(t->id); - if (FAILED(hr)) { - *errorMessage = msgGetThreadStateFailed(t->id, CdbCore::msgComFailed("SetCurrentThreadId", hr)); - return false; - } - } - ULONG frameCount; - // Ignore the top frame if it is "ntdll!KiFastSystemCallRet", which is - // not interesting for display. - DEBUG_STACK_FRAME frames[MaxFrames]; - hr = cif.debugControl->GetStackTrace(0, 0, 0, frames, MaxFrames, &frameCount); - if (FAILED(hr)) { - *errorMessage = msgGetThreadStateFailed(t->id, CdbCore::msgComFailed("GetStackTrace", hr)); - return false; - } - // Ignore the top frame if it is "ntdll!KiFastSystemCallRet", which is - // not interesting for display. - WCHAR wszBuf[MAX_PATH]; - for (int frame = 0; frame < MaxFrames; frame++) { - cif.debugSymbols->GetNameByOffsetWide(frames[frame].InstructionOffset, wszBuf, MAX_PATH, 0, 0); - t->function = QString::fromUtf16(reinterpret_cast<const ushort *>(wszBuf)); - if (frame != 0 || t->function != QLatin1String(CdbStackTraceContext::winFuncFastSystemCallRet)) { - t->address = frames[frame].InstructionOffset; - cif.debugSymbols->GetNameByOffsetWide(frames[frame].InstructionOffset, wszBuf, MAX_PATH, 0, 0); - t->function = QString::fromUtf16(reinterpret_cast<const ushort *>(wszBuf)); - ULONG ulLine; - hr = cif.debugSymbols->GetLineByOffsetWide(frames[frame].InstructionOffset, &ulLine, wszBuf, MAX_PATH, 0, 0); - if (SUCCEEDED(hr)) { - t->line = ulLine; - // Just display base name - t->file = QString::fromUtf16(reinterpret_cast<const ushort *>(wszBuf)); - if (!t->file.isEmpty()) { - const int slashPos = t->file.lastIndexOf(QLatin1Char('\\')); - if (slashPos != -1) - t->file.remove(0, slashPos + 1); - } - } - break; - } // was not "ntdll!KiFastSystemCallRet" + // Convert from Core data structures + QList<StackFrame> rc; + const int count = frameCount(); + const QString hexPrefix = QLatin1String("0x"); + for(int i = 0; i < count; i++) { + const CdbCore::StackFrame &coreFrame = stackFrameAt(i); + StackFrame frame; + frame.level = i; + frame.file = coreFrame.fileName; + frame.line = coreFrame.line; + frame.function =coreFrame.function; + frame.from = coreFrame.module; + frame.address = hexPrefix + QString::number(coreFrame.address, 16); + rc.push_back(frame); } - return true; -} - -static inline QString msgGetThreadsFailed(const QString &why) -{ - return QString::fromLatin1("Unable to determine the thread information: %1").arg(why); + return rc; } bool CdbStackTraceContext::getThreads(const CdbCore::ComInterfaces &cif, - bool isStopped, QList<ThreadData> *threads, ULONG *currentThreadId, QString *errorMessage) { + // Convert from Core data structures threads->clear(); - ULONG threadCount; - *currentThreadId = 0; - HRESULT hr= cif.debugSystemObjects->GetNumberThreads(&threadCount); - if (FAILED(hr)) { - *errorMessage= msgGetThreadsFailed(CdbCore::msgComFailed("GetNumberThreads", hr)); + ThreadIdFrameMap threadMap; + if (!CdbCore::StackTraceContext::getThreads(cif, &threadMap, + currentThreadId, errorMessage)) return false; - } - // Get ids and index of current - if (!threadCount) - return true; - hr = cif.debugSystemObjects->GetCurrentThreadId(currentThreadId); - if (FAILED(hr)) { - *errorMessage= msgGetThreadsFailed(CdbCore::msgComFailed("GetCurrentThreadId", hr)); - return false; - } - - QVector<ULONG> threadIds(threadCount); - hr = cif.debugSystemObjects->GetThreadIdsByIndex(0, threadCount, &(*threadIds.begin()), 0); - if (FAILED(hr)) { - *errorMessage= msgGetThreadsFailed(CdbCore::msgComFailed("GetThreadIdsByIndex", hr)); - return false; - } - for (ULONG i = 0; i < threadCount; i++) { - ThreadData threadData(threadIds.at(i)); - if (isStopped) { - if (!getStoppedThreadState(cif, &threadData, errorMessage)) { - qWarning("%s\n", qPrintable(*errorMessage)); - errorMessage->clear(); - } - } - threads->push_back(threadData); - } - // Restore thread id - if (isStopped && threads->back().id != *currentThreadId) { - hr = cif.debugSystemObjects->SetCurrentThreadId(*currentThreadId); - if (FAILED(hr)) { - *errorMessage= msgGetThreadsFailed(CdbCore::msgComFailed("SetCurrentThreadId", hr)); - return false; - } + const QChar slash = QLatin1Char('/'); + const ThreadIdFrameMap::const_iterator cend = threadMap.constEnd(); + for (ThreadIdFrameMap::const_iterator it = threadMap.constBegin(); it != cend; ++it) { + ThreadData data(it.key()); + const CdbCore::StackFrame &coreFrame = it.value(); + data.address = coreFrame.address; + data.function = coreFrame.function; + data.line = coreFrame.line; + // Basename only for brevity + const int slashPos = coreFrame.fileName.lastIndexOf(slash); + data.file = slashPos == -1 ? coreFrame.fileName : coreFrame.fileName.mid(slashPos + 1); + threads->push_back(data); } return true; } diff --git a/src/plugins/debugger/cdb/cdbstacktracecontext.h b/src/plugins/debugger/cdb/cdbstacktracecontext.h index 1e3a65a0da0e8f0c358b34d453ca64e870857a01..c2f5199fa4b27e7d96ea5d12a368ce3ef31146d7 100644 --- a/src/plugins/debugger/cdb/cdbstacktracecontext.h +++ b/src/plugins/debugger/cdb/cdbstacktracecontext.h @@ -31,6 +31,7 @@ #define CDBSTACKTRACECONTEXT_H #include "stackhandler.h" +#include "stacktracecontext.h" #include "cdbcom.h" @@ -54,65 +55,38 @@ class CdbStackFrameContext; class CdbDumperHelper; struct ThreadData; -/* Context representing a break point stack consisting of several frames. - * Maintains an on-demand constructed list of CdbStackFrameContext - * containining the local variables of the stack. */ +/* CdbStackTraceContext: Bridges CdbCore data structures and + * Debugger structures and handles CdbSymbolGroupContext's. */ -class CdbStackTraceContext +class CdbStackTraceContext : public CdbCore::StackTraceContext { Q_DISABLE_COPY(CdbStackTraceContext) explicit CdbStackTraceContext(const QSharedPointer<CdbDumperHelper> &dumper); -public: - enum { maxFrames = 100 }; - - // Some well known-functions - static const char *winFuncFastSystemCallRet; - // WaitFor... - static const char *winFuncWaitForPrefix; - static const char *winFuncMsgWaitForPrefix; - - // Dummy function used for interrupting a debuggee - static const char *winFuncDebugBreakPoint; - ~CdbStackTraceContext(); +public: static CdbStackTraceContext *create(const QSharedPointer<CdbDumperHelper> &dumper, - unsigned long threadid, QString *errorMessage); - QList<StackFrame> frames() const { return m_frames; } - inline int frameCount() const { return m_frames.size(); } - // Search for function. Should ideally contain the module as 'module!foo'. - int indexOf(const QString &function) const; - - // Top-Level instruction offset for disassembler - ULONG64 instructionOffset() const { return m_instructionOffset; } + CdbSymbolGroupContext *cdbSymbolGroupContextAt(int index, QString *errorMessage); - CdbStackFrameContext *frameContextAt(int index, QString *errorMessage); + QList<StackFrame> stackFrames() const; - // Format for logging - void format(QTextStream &str) const; - QString toString() const; - - // Retrieve information about threads. When stopped, add - // current stack frame. + // get threads in stopped state static bool getThreads(const CdbCore::ComInterfaces &cif, - bool isStopped, QList<ThreadData> *threads, ULONG *currentThreadId, QString *errorMessage); -private: - bool init(unsigned long frameCount, QString *errorMessage); - CIDebugSymbolGroup *createSymbolGroup(int index, QString *errorMessage); +protected: + virtual CdbCore::SymbolGroupContext *createSymbolGroup(const CdbCore::ComInterfaces &cif, + int index, + const QString &prefix, + CIDebugSymbolGroup *comSymbolGroup, + QString *errorMessage); +private: const QSharedPointer<CdbDumperHelper> m_dumper; - const CdbCore::ComInterfaces *m_cif; - - DEBUG_STACK_FRAME m_cdbFrames[maxFrames]; - QVector <CdbStackFrameContext*> m_frameContexts; - QList<StackFrame> m_frames; - ULONG64 m_instructionOffset; }; } // namespace Internal diff --git a/src/plugins/debugger/cdb/cdbsymbolgroupcontext.cpp b/src/plugins/debugger/cdb/cdbsymbolgroupcontext.cpp index e8c25bd90b9830f54008822a66c550d6fc64f66c..b4bc8492c8db3e3e0764592c0cccbcd0fe2f3afc 100644 --- a/src/plugins/debugger/cdb/cdbsymbolgroupcontext.cpp +++ b/src/plugins/debugger/cdb/cdbsymbolgroupcontext.cpp @@ -29,9 +29,13 @@ #include "cdbsymbolgroupcontext.h" #include "cdbdebugengine_p.h" +#include "cdbdumperhelper.h" #include "watchhandler.h" #include "watchutils.h" +#include "debuggeractions.h" +#include "coreengine.h" +#include <QtCore/QDebug> #include <QtCore/QTextStream> #include <QtCore/QCoreApplication> #include <QtCore/QRegExp> @@ -39,95 +43,262 @@ enum { debug = 0 }; enum { debugInternalDumpers = 0 }; -// name separator for shadowed variables -static const char iNameShadowDelimiter = '#'; - -static inline QString msgSymbolNotFound(const QString &s) -{ - return QString::fromLatin1("The symbol '%1' could not be found.").arg(s); -} - -static inline QString msgOutOfScope() -{ - return QCoreApplication::translate("SymbolGroup", "Out of scope"); -} - -static inline bool isTopLevelSymbol(const DEBUG_SYMBOL_PARAMETERS &p) -{ - return p.ParentSymbol == DEBUG_ANY_ID; -} +namespace Debugger { +namespace Internal { -static inline void debugSymbolFlags(unsigned long f, QTextStream &str) -{ - if (f & DEBUG_SYMBOL_EXPANDED) - str << "DEBUG_SYMBOL_EXPANDED"; - if (f & DEBUG_SYMBOL_READ_ONLY) - str << "|DEBUG_SYMBOL_READ_ONLY"; - if (f & DEBUG_SYMBOL_IS_ARRAY) - str << "|DEBUG_SYMBOL_IS_ARRAY"; - if (f & DEBUG_SYMBOL_IS_FLOAT) - str << "|DEBUG_SYMBOL_IS_FLOAT"; - if (f & DEBUG_SYMBOL_IS_ARGUMENT) - str << "|DEBUG_SYMBOL_IS_ARGUMENT"; - if (f & DEBUG_SYMBOL_IS_LOCAL) - str << "|DEBUG_SYMBOL_IS_LOCAL"; +enum { OwnerNewItem, OwnerSymbolGroup, OwnerSymbolGroupDumper , OwnerDumper }; + +typedef QSharedPointer<CdbDumperHelper> SharedPointerCdbDumperHelper; +typedef QList<WatchData> WatchDataList; + +// Predicates for parametrizing the symbol group +inline bool truePredicate(const WatchData & /* whatever */) { return true; } +inline bool falsePredicate(const WatchData & /* whatever */) { return false; } +inline bool isDumperPredicate(const WatchData &wd) +{ return (wd.source & CdbSymbolGroupContext::SourceMask) == OwnerDumper; } + +static inline void debugWatchDataList(const QList<WatchData> &l, const char *why = 0) +{ + QDebug nospace = qDebug().nospace(); + if (why) + nospace << why << '\n'; + foreach(const WatchData &wd, l) + nospace << wd.toString() << '\n'; + nospace << '\n'; +} + +// Match an item that is expanded in the watchhandler. +class WatchHandlerExpandedPredicate { +public: + explicit inline WatchHandlerExpandedPredicate(const WatchHandler *wh) : m_wh(wh) {} + inline bool operator()(const WatchData &wd) { return m_wh->isExpandedIName(wd.iname); } +private: + const WatchHandler *m_wh; +}; + +// Match an item by iname +class MatchINamePredicate { +public: + explicit inline MatchINamePredicate(const QString &iname) : m_iname(iname) {} + inline bool operator()(const WatchData &wd) { return wd.iname == m_iname; } +private: + const QString &m_iname; +}; + +// Put a sequence of WatchData into the model for the non-dumper case +class WatchHandlerModelInserter { +public: + explicit WatchHandlerModelInserter(WatchHandler *wh) : m_wh(wh) {} + + inline WatchHandlerModelInserter & operator*() { return *this; } + inline WatchHandlerModelInserter &operator=(const WatchData &wd) { + m_wh->insertData(wd); + return *this; + } + inline WatchHandlerModelInserter &operator++() { return *this; } + +private: + WatchHandler *m_wh; +}; + +// Put a sequence of WatchData into the model for the dumper case. +// Sorts apart a sequence of WatchData using the Dumpers. +// Puts the stuff for which there is no dumper in the model +// as is and sets ownership to symbol group. The rest goes +// to the dumpers. Additionally, checks for items pointing to a +// dumpeable type and inserts a fake dereferenced item and value. +class WatchHandleDumperInserter { +public: + explicit WatchHandleDumperInserter(WatchHandler *wh, + const SharedPointerCdbDumperHelper &dumper); + + inline WatchHandleDumperInserter & operator*() { return *this; } + inline WatchHandleDumperInserter &operator=(WatchData &wd); + inline WatchHandleDumperInserter &operator++() { return *this; } + +private: + bool expandPointerToDumpable(const WatchData &wd, QString *errorMessage); + + const QRegExp m_hexNullPattern; + WatchHandler *m_wh; + const SharedPointerCdbDumperHelper m_dumper; + QList<WatchData> m_dumperResult; +}; + +WatchHandleDumperInserter::WatchHandleDumperInserter(WatchHandler *wh, + const SharedPointerCdbDumperHelper &dumper) : + m_hexNullPattern(QLatin1String("0x0+")), + m_wh(wh), + m_dumper(dumper) +{ + Q_ASSERT(m_hexNullPattern.isValid()); +} + +// Prevent recursion of the model by setting value and type +static inline bool fixDumperType(WatchData *wd, const WatchData *source = 0) +{ + const bool missing = wd->isTypeNeeded() || wd->type.isEmpty(); + if (missing) { + static const QString unknownType = QCoreApplication::translate("CdbSymbolGroupContext", "<Unknown Type>"); + wd->setType(source ? source->type : unknownType, false); + } + return missing; } -QTextStream &operator<<(QTextStream &str, const DEBUG_SYMBOL_PARAMETERS &p) +static inline bool fixDumperValue(WatchData *wd, const WatchData *source = 0) { - str << " Type=" << p.TypeId << " parent="; - if (isTopLevelSymbol(p)) { - str << "<ROOT>"; - } else { - str << p.ParentSymbol; + const bool missing = wd->isValueNeeded(); + if (missing) { + if (source && source->isValueKnown()) { + wd->setValue(source->value); + } else { + static const QString unknownValue = QCoreApplication::translate("CdbSymbolGroupContext", "<Unknown Value>"); + wd->setValue(unknownValue); + } } - str << " Subs=" << p.SubElements << " flags=" << p.Flags << '/'; - debugSymbolFlags(p.Flags, str); - return str; + return missing; +} + +// When querying an item, the queried item is sometimes returned in incomplete form. +// Take over values from source. +static inline void fixDumperResult(const WatchData &source, + QList<WatchData> *result, + bool suppressGrandChildren) +{ + + const int size = result->size(); + if (!size) + return; + if (debugCDBWatchHandling) + debugWatchDataList(*result, suppressGrandChildren ? ">fixDumperResult suppressGrandChildren" : ">fixDumperResult"); + WatchData &returned = result->front(); + if (returned.iname != source.iname) + return; + fixDumperType(&returned, &source); + fixDumperValue(&returned, &source); + // Indicate owner and known children + returned.source = OwnerDumper; + if (returned.isChildrenKnown() && returned.isHasChildrenKnown() && returned.hasChildren) + returned.source |= CdbSymbolGroupContext::ChildrenKnownBit; + if (size == 1) + return; + // If the model queries the expanding item by pretending childrenNeeded=1, + // refuse the request as the children are already known + returned.source |= CdbSymbolGroupContext::ChildrenKnownBit; + // Fix the children: If the address is missing, we cannot query any further. + const QList<WatchData>::iterator wend = result->end(); + QList<WatchData>::iterator it = result->begin(); + for (++it; it != wend; ++it) { + WatchData &wd = *it; + // Indicate owner and known children + it->source = OwnerDumper; + if (it->isChildrenKnown() && it->isHasChildrenKnown() && it->hasChildren) + it->source |= CdbSymbolGroupContext::ChildrenKnownBit; + // Cannot dump items with missing addresses or missing types + const bool typeFixed = fixDumperType(&wd); // Order of evaluation! + if ((wd.addr.isEmpty() && wd.isSomethingNeeded()) || typeFixed) { + wd.setHasChildren(false); + wd.setAllUnneeded(); + } else { + // Hack: Suppress endless recursion of the model. To be fixed, + // the model should not query non-visible items. + if (suppressGrandChildren && (wd.isChildrenNeeded() || wd.isHasChildrenNeeded())) + wd.setHasChildren(false); + } + } + if (debugCDBWatchHandling) + debugWatchDataList(*result, "<fixDumperResult"); } -static inline QByteArray hexSymbolOffset(CIDebugSymbolGroup *sg, unsigned long index) +// Is this a non-null pointer to a dumpeable item with a value +// "0x4343 class QString *" ? - Insert a fake '*' dereferenced item +// and run dumpers on it. If that succeeds, insert the fake items owned by dumpers, +// which will trigger the ignore predicate. +// Note that the symbol context does not create '*' dereferenced items for +// classes (see note in its header documentation). +bool WatchHandleDumperInserter::expandPointerToDumpable(const WatchData &wd, QString *errorMessage) { - ULONG64 rc = 0; - if (FAILED(sg->GetSymbolOffset(index, &rc))) - rc = 0; + if (debugCDBWatchHandling) + qDebug() << ">expandPointerToDumpable" << wd.toString(); - return QByteArray("0x") + QByteArray::number(rc, 16); + bool handled = false; + do { + if (wd.error || !isPointerType(wd.type)) + break; + const int classPos = wd.value.indexOf(" class "); + if (classPos == -1) + break; + const QString hexAddrS = wd.value.mid(0, classPos); + if (m_hexNullPattern.exactMatch(hexAddrS)) + break; + const QString type = stripPointerType(wd.value.mid(classPos + 7)); + WatchData derefedWd; + derefedWd.setType(type); + derefedWd.setAddress(hexAddrS); + derefedWd.name = QString(QLatin1Char('*')); + derefedWd.iname = wd.iname + ".*"; + derefedWd.source = OwnerDumper | CdbSymbolGroupContext::ChildrenKnownBit; + const CdbDumperHelper::DumpResult dr = m_dumper->dumpType(derefedWd, true, &m_dumperResult, errorMessage); + if (dr != CdbDumperHelper::DumpOk) + break; + fixDumperResult(derefedWd, &m_dumperResult, false); + // Insert the pointer item with 1 additional child + its dumper results + // Note: formal arguments might already be expanded in the symbol group. + WatchData ptrWd = wd; + ptrWd.source = OwnerDumper | CdbSymbolGroupContext::ChildrenKnownBit; + ptrWd.setHasChildren(true); + ptrWd.setChildrenUnneeded(); + m_wh->insertData(ptrWd); + m_wh->insertBulkData(m_dumperResult); + handled = true; + } while (false); + if (debugCDBWatchHandling) + qDebug() << "<expandPointerToDumpable returns " << handled << *errorMessage; + return handled; } -// A helper function to extract a string value from a member function of -// IDebugSymbolGroup2 taking the symbol index and a character buffer. -// Pass in the the member function as '&IDebugSymbolGroup2::GetSymbolNameWide' - -typedef HRESULT (__stdcall IDebugSymbolGroup2::*WideStringRetrievalFunction)(ULONG, PWSTR, ULONG, PULONG); - -static inline QString getSymbolString(IDebugSymbolGroup2 *sg, - WideStringRetrievalFunction wsf, - unsigned long index) +WatchHandleDumperInserter &WatchHandleDumperInserter::operator=(WatchData &wd) { - // Template type names can get quite long.... - enum { BufSize = 1024 }; - static WCHAR nameBuffer[BufSize + 1]; - // Name - ULONG nameLength; - const HRESULT hr = (sg->*wsf)(index, nameBuffer, BufSize, &nameLength); - if (SUCCEEDED(hr)) { - nameBuffer[nameLength] = 0; - return QString::fromUtf16(reinterpret_cast<const ushort *>(nameBuffer)); + if (debugCDBWatchHandling) + qDebug() << "WatchHandleDumperInserter::operator=" << wd.toString(); + // Check pointer to dumpeable, dumpeable, insert accordingly. + QString errorMessage; + if (expandPointerToDumpable(wd, &errorMessage)) { + // Nasty side effect: Modify owner for the ignore predicate + wd.source = OwnerDumper; + return *this; + } + // Expanded by internal dumper? : ok + if ((wd.source & CdbSymbolGroupContext::SourceMask) == OwnerSymbolGroupDumper) { + m_wh->insertData(wd); + return *this; } - return QString(); + // Try library dumpers. + switch (m_dumper->dumpType(wd, true, &m_dumperResult, &errorMessage)) { + case CdbDumperHelper::DumpOk: + if (debugCDBWatchHandling) + qDebug() << "dumper triggered"; + // Dumpers omit types for complicated templates + fixDumperResult(wd, &m_dumperResult, false); + // Discard the original item and insert the dumper results + m_wh->insertBulkData(m_dumperResult); + // Nasty side effect: Modify owner for the ignore predicate + wd.source = OwnerDumper; + break; + case CdbDumperHelper::DumpNotHandled: + case CdbDumperHelper::DumpError: + wd.source = OwnerSymbolGroup; + m_wh->insertData(wd); + break; + } + return *this; } -namespace Debugger { -namespace Internal { - CdbSymbolGroupRecursionContext::CdbSymbolGroupRecursionContext(CdbSymbolGroupContext *ctx, - int ido, - CIDebugDataSpaces *ds) : + int ido) : context(ctx), - internalDumperOwner(ido), - dataspaces(ds) + internalDumperOwner(ido) { } @@ -142,27 +313,26 @@ static inline CdbSymbolGroupContext::SymbolState getSymbolState(const DEBUG_SYMB CdbSymbolGroupContext::CdbSymbolGroupContext(const QString &prefix, CIDebugSymbolGroup *symbolGroup, + const QSharedPointer<CdbDumperHelper> &dumper, const QStringList &uninitializedVariables) : - m_prefix(prefix), - m_nameDelimiter(QLatin1Char('.')), - m_uninitializedVariables(uninitializedVariables.toSet()), - m_symbolGroup(symbolGroup), - m_unnamedSymbolNumber(1) -{ - -} - -CdbSymbolGroupContext::~CdbSymbolGroupContext() + CdbCore::SymbolGroupContext(prefix, symbolGroup, + dumper->comInterfaces()->debugDataSpaces, + uninitializedVariables), + m_useDumpers(dumper->isEnabled() && theDebuggerBoolSetting(UseDebuggingHelpers)), + m_dumper(dumper) { - m_symbolGroup->Release(); + setShadowedNameFormat(WatchData::shadowedNameFormat()); } CdbSymbolGroupContext *CdbSymbolGroupContext::create(const QString &prefix, CIDebugSymbolGroup *symbolGroup, + const QSharedPointer<CdbDumperHelper> &dumper, const QStringList &uninitializedVariables, QString *errorMessage) { - CdbSymbolGroupContext *rc = new CdbSymbolGroupContext(prefix, symbolGroup, uninitializedVariables); + CdbSymbolGroupContext *rc = new CdbSymbolGroupContext(prefix, symbolGroup, + dumper, + uninitializedVariables); if (!rc->init(errorMessage)) { delete rc; return 0; @@ -170,287 +340,6 @@ CdbSymbolGroupContext *CdbSymbolGroupContext::create(const QString &prefix, return rc; } -bool CdbSymbolGroupContext::init(QString *errorMessage) -{ - // retrieve the root symbols - ULONG count; - HRESULT hr = m_symbolGroup->GetNumberSymbols(&count); - if (FAILED(hr)) { - *errorMessage = CdbCore::msgComFailed("GetNumberSymbols", hr); - return false; - } - - if (count) { - m_symbolParameters.reserve(3u * count); - m_symbolParameters.resize(count); - - hr = m_symbolGroup->GetSymbolParameters(0, count, symbolParameters()); - if (FAILED(hr)) { - *errorMessage = QString::fromLatin1("In %1: %2 (%3 symbols)").arg(QLatin1String(Q_FUNC_INFO), - CdbCore::msgComFailed("GetSymbolParameters", hr)).arg(count); - return false; - } - populateINameIndexMap(m_prefix, DEBUG_ANY_ID, count); - } - if (debug) - qDebug() << Q_FUNC_INFO << '\n'<< toString(true); - return true; -} - -/* Make the entries for iname->index mapping. We might encounter - * already expanded subitems when doing it for top-level ('this'-pointers), - * recurse in that case, (skip over expanded children). - * Loop backwards to detect shadowed variables in the order the -/* debugger expects them: -\code -int x; // Occurrence (1), should be reported as "x <shadowed 1>" -if (true) { - int x = 5; (2) // Occurrence (2), should be reported as "x" -} -\endcode - * The order in the symbol group is (1),(2). Give them an iname of - * <root>#<shadowed-nr>, which will be split apart for display. */ - -void CdbSymbolGroupContext::populateINameIndexMap(const QString &prefix, unsigned long parentId, - unsigned long end) -{ - const QString symbolPrefix = prefix + m_nameDelimiter; - if (debug) - qDebug() << Q_FUNC_INFO << '\n'<< symbolPrefix << parentId << end; - for (unsigned long i = end - 1; ; i--) { - const DEBUG_SYMBOL_PARAMETERS &p = m_symbolParameters.at(i); - if (parentId == p.ParentSymbol) { - // "__formal" occurs when someone writes "void foo(int /* x */)..." - static const QString unnamedFormalParameter = QLatin1String("__formal"); - QString symbolName = getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolNameWide, i); - if (symbolName == unnamedFormalParameter) { - symbolName = QLatin1String("<unnamed"); - symbolName += QString::number(m_unnamedSymbolNumber++); - symbolName += QLatin1Char('>'); - } - // Find a unique name in case the variable is shadowed by - // an existing one - const QString namePrefix = symbolPrefix + symbolName; - QString name = namePrefix; - for (int n = 1; m_inameIndexMap.contains(name); n++) { - name.truncate(namePrefix.size()); - name += QLatin1Char(iNameShadowDelimiter); - name += QString::number(n); - } - m_inameIndexMap.insert(name, i); - if (getSymbolState(p) == ExpandedSymbol) - populateINameIndexMap(name, i, i + 1 + p.SubElements); - } - if (i == 0 || i == parentId) - break; - } -} - -QString CdbSymbolGroupContext::toString(bool verbose) const -{ - QString rc; - QTextStream str(&rc); - const int count = m_symbolParameters.size(); - for (int i = 0; i < count; i++) { - str << i << ' '; - const DEBUG_SYMBOL_PARAMETERS &p = m_symbolParameters.at(i); - if (!isTopLevelSymbol(p)) - str << " "; - str << getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolNameWide, i); - if (p.Flags & DEBUG_SYMBOL_IS_LOCAL) - str << " '" << getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolTypeNameWide, i) << '\''; - str << " Address: " << hexSymbolOffset(m_symbolGroup, i); - if (verbose) - str << " '" << getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolValueTextWide, i) << '\''; - str << p << '\n'; - } - if (verbose) { - str << "NameIndexMap\n"; - NameIndexMap::const_iterator ncend = m_inameIndexMap.constEnd(); - for (NameIndexMap::const_iterator it = m_inameIndexMap.constBegin() ; it != ncend; ++it) - str << it.key() << ' ' << it.value() << '\n'; - } - return rc; -} - -CdbSymbolGroupContext::SymbolState CdbSymbolGroupContext::symbolState(unsigned long index) const -{ - return getSymbolState(m_symbolParameters.at(index)); -} - -CdbSymbolGroupContext::SymbolState CdbSymbolGroupContext::symbolState(const QString &prefix) const -{ - if (prefix == m_prefix) // root - return ExpandedSymbol; - unsigned long index; - if (!lookupPrefix(prefix, &index)) { - qWarning("WARNING %s: %s\n", Q_FUNC_INFO, qPrintable(msgSymbolNotFound(prefix))); - return LeafSymbol; - } - return symbolState(index); -} - -// Find index of a prefix -bool CdbSymbolGroupContext::lookupPrefix(const QString &prefix, unsigned long *index) const -{ - *index = 0; - const NameIndexMap::const_iterator it = m_inameIndexMap.constFind(prefix); - if (it == m_inameIndexMap.constEnd()) - return false; - *index = it.value(); - return true; -} - -/* Retrieve children and get the position. */ -bool CdbSymbolGroupContext::getChildSymbolsPosition(const QString &prefix, - unsigned long *start, - unsigned long *parentId, - QString *errorMessage) -{ - if (debug) - qDebug() << Q_FUNC_INFO << '\n'<< prefix; - - *start = *parentId = 0; - // Root item? - if (prefix == m_prefix) { - *start = 0; - *parentId = DEBUG_ANY_ID; - if (debug) - qDebug() << '<' << prefix << "at" << *start; - return true; - } - // Get parent index, make sure it is expanded - NameIndexMap::const_iterator nit = m_inameIndexMap.constFind(prefix); - if (nit == m_inameIndexMap.constEnd()) { - *errorMessage = QString::fromLatin1("'%1' not found.").arg(prefix); - return false; - } - *parentId = nit.value(); - *start = nit.value() + 1; - if (!expandSymbol(prefix, *parentId, errorMessage)) - return false; - if (debug) - qDebug() << '<' << prefix << "at" << *start; - return true; -} - -static inline QString msgExpandFailed(const QString &prefix, unsigned long index, const QString &why) -{ - return QString::fromLatin1("Unable to expand '%1' %2: %3").arg(prefix).arg(index).arg(why); -} - -// Expand a symbol using the symbol group interface. -bool CdbSymbolGroupContext::expandSymbol(const QString &prefix, unsigned long index, QString *errorMessage) -{ - if (debug) - qDebug() << '>' << Q_FUNC_INFO << '\n' << prefix << index; - - switch (symbolState(index)) { - case LeafSymbol: - *errorMessage = QString::fromLatin1("Attempt to expand leaf node '%1' %2!").arg(prefix).arg(index); - return false; - case ExpandedSymbol: - return true; - case CollapsedSymbol: - break; - } - - HRESULT hr = m_symbolGroup->ExpandSymbol(index, TRUE); - if (FAILED(hr)) { - *errorMessage = msgExpandFailed(prefix, index, CdbCore::msgComFailed("ExpandSymbol", hr)); - return false; - } - // Hopefully, this will never fail, else data structure will be foobar. - const ULONG oldSize = m_symbolParameters.size(); - ULONG newSize; - hr = m_symbolGroup->GetNumberSymbols(&newSize); - if (FAILED(hr)) { - *errorMessage = msgExpandFailed(prefix, index, CdbCore::msgComFailed("GetNumberSymbols", hr)); - return false; - } - - // Retrieve the new parameter structs which will be inserted - // after the parents, offsetting consecutive indexes. - m_symbolParameters.resize(newSize); - - hr = m_symbolGroup->GetSymbolParameters(0, newSize, symbolParameters()); - if (FAILED(hr)) { - *errorMessage = msgExpandFailed(prefix, index, CdbCore::msgComFailed("GetSymbolParameters", hr)); - return false; - } - // The new symbols are inserted after the parent symbol. - // We need to correct the following values in the name->index map - const unsigned long newSymbolCount = newSize - oldSize; - const NameIndexMap::iterator nend = m_inameIndexMap.end(); - for (NameIndexMap::iterator it = m_inameIndexMap.begin(); it != nend; ++it) - if (it.value() > index) - it.value() += newSymbolCount; - // insert the new symbols - populateINameIndexMap(prefix, index, index + 1 + newSymbolCount); - if (debug > 1) - qDebug() << '<' << Q_FUNC_INFO << '\n' << prefix << index << '\n' << toString(); - return true; -} - -void CdbSymbolGroupContext::clear() -{ - m_symbolParameters.clear(); - m_inameIndexMap.clear(); -} - -QString CdbSymbolGroupContext::symbolINameAt(unsigned long index) const -{ - return m_inameIndexMap.key(index); -} - -// Return hexadecimal pointer value from a CDB pointer value -// which look like "0x000032a" or "0x00000000`0250124a" or -// "0x1`0250124a" on 64-bit systems. -static bool inline getUnsignedHexValue(QString stringValue, quint64 *value) -{ - *value = 0; - if (!stringValue.startsWith(QLatin1String("0x"))) - return false; - stringValue.remove(0, 2); - // Remove 64bit separator - if (stringValue.size() > 9) { - const int sepPos = stringValue.size() - 9; - if (stringValue.at(sepPos) == QLatin1Char('`')) - stringValue.remove(sepPos, 1); - } - bool ok; - *value = stringValue.toULongLong(&ok, 16); - return ok; -} - -// check for "0x000", "0x000 class X" or its 64-bit equivalents. -static inline bool isNullPointer(const WatchData &wd) -{ - if (!isPointerType(wd.type)) - return false; - QString stringValue = wd.value; - const int blankPos = stringValue.indexOf(QLatin1Char(' ')); - if (blankPos != -1) - stringValue.truncate(blankPos); - quint64 value; - return getUnsignedHexValue(stringValue, &value) && value == 0u; -} - -// Fix a symbol group value. It is set to the class type for -// expandable classes (type="class std::foo<..>[*]", -// value="std::foo<...>[*]". This is not desired -// as it widens the value column for complex std::template types. -// Remove the inner template type. - -static inline QString removeInnerTemplateType(QString value) -{ - const int firstBracketPos = value.indexOf(QLatin1Char('<')); - const int lastBracketPos = firstBracketPos != -1 ? value.lastIndexOf(QLatin1Char('>')) : -1; - if (lastBracketPos != -1) - value.replace(firstBracketPos + 1, lastBracketPos - firstBracketPos -1, QLatin1String("...")); - return value; -} - // Fix display values: Pass through strings, convert unsigned integers // to decimal ('0x5454`fedf'), remove inner templates from // "0x4343 class list<>". @@ -463,267 +352,155 @@ static inline QString fixValue(const QString &value, const QString &type) // Unsigned hex numbers if (isIntType(type) && (size > 2 && value.at(1) == QLatin1Char('x'))) { quint64 intValue; - if (getUnsignedHexValue(value, &intValue)) + if (CdbCore::SymbolGroupContext::getUnsignedHexValue(value, &intValue)) return QString::number(intValue); } - return size < 20 ? value : removeInnerTemplateType(value); + return size < 20 ? value : CdbCore::SymbolGroupContext::removeInnerTemplateType(value); } -WatchData CdbSymbolGroupContext::watchDataAt(unsigned long index) const +unsigned CdbSymbolGroupContext::watchDataAt(unsigned long index, WatchData *wd) { - WatchData wd; - wd.iname = symbolINameAt(index).toLatin1(); - wd.exp = wd.iname; - // Determine name from iname and format shadowed variables correctly - // as "<shadowed X>, see populateINameIndexMap(). - const int lastDelimiterPos = wd.iname.lastIndexOf(m_nameDelimiter.toLatin1()); - QString name = lastDelimiterPos == -1 ? wd.iname : wd.iname.mid(lastDelimiterPos + 1); - int shadowedNumber = 0; - const int shadowedPos = name.lastIndexOf(QLatin1Char(iNameShadowDelimiter)); - if (shadowedPos != -1) { - shadowedNumber = name.mid(shadowedPos + 1).toInt(); - name.truncate(shadowedPos); + ULONG typeId; + ULONG64 address; + QString iname; + QString value; + QString type; + const unsigned rc = dumpValue(index, &iname, &(wd->name), &address, + &typeId, &type, &value); + wd->exp = wd->iname = iname.toLatin1(); + // Trigger numeric sorting for arrays "local.[22]" -> "local.22" + if (wd->iname.endsWith(']')) { + const int openingBracketPos = wd->iname.lastIndexOf('['); + if (openingBracketPos != -1) { + wd->iname.truncate(wd->iname.size() - 1); + wd->iname.remove(openingBracketPos, 1); + } } - // For class hierarchies, we get sometimes complicated std::template types here. - // (std::map extends std::tree<>... Remove them for display only. - const QString fullShadowedName = WatchData::shadowedName(name, shadowedNumber); - wd.name = WatchData::shadowedName(removeInnerTemplateType(name), shadowedNumber); - wd.addr = hexSymbolOffset(m_symbolGroup, index); - const QString type = getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolTypeNameWide, index); - wd.setType(type); - // Check for uninitialized variables at level 0 only. - const DEBUG_SYMBOL_PARAMETERS &p = m_symbolParameters.at(index); - if (p.ParentSymbol == DEBUG_ANY_ID && m_uninitializedVariables.contains(fullShadowedName)) { - wd.setError(WatchData::msgNotInScope()); - return wd; + wd->setAddress(QString::fromLatin1("0x") + QString::number(address, 16)); + wd->setType(type, false); + wd->setValue(fixValue(value, type)); + if (rc & OutOfScope) { + wd->setError(WatchData::msgNotInScope()); + } else { + const bool hasChildren = rc & HasChildren; + wd->setHasChildren(hasChildren); + if (hasChildren) + wd->setChildrenNeeded(); } - const QString value = getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolValueTextWide, index); - wd.setValue(fixValue(value, type)); - wd.setChildrenNeeded(); // compensate side effects of above setters - // Figure out children. The SubElement is only a guess unless the symbol, - // is expanded, so, we leave this as a guess for later updates. - // If the symbol has children (expanded or not), we leave the 'Children' flag - // in 'needed' state. Suppress 0-pointers right ("0x000 class X") - // here as they only lead to children with memory access errors. - const bool hasChildren = p.SubElements && !isNullPointer(wd); - wd.setHasChildren(hasChildren); if (debug > 1) - qDebug() << Q_FUNC_INFO << index << '\n' << wd.toString(); - return wd; -} - -WatchData CdbSymbolGroupContext::dumpSymbolAt(CIDebugDataSpaces *ds, unsigned long index) -{ - WatchData rc = watchDataAt(index); - dump(ds, &rc); + qDebug() << "watchDataAt" << index << QString::number(rc, 16) << wd->toString(); return rc; } -bool CdbSymbolGroupContext::assignValue(const QString &iname, const QString &value, - QString *newValue, QString *errorMessage) -{ - if (debugCDB) - qDebug() << Q_FUNC_INFO << '\n' << iname << value; - const NameIndexMap::const_iterator it = m_inameIndexMap.constFind(iname); - if (it == m_inameIndexMap.constEnd()) { - *errorMessage = msgSymbolNotFound(iname); - return false; - } - const unsigned long index = it.value(); - const HRESULT hr = m_symbolGroup->WriteSymbolWide(index, reinterpret_cast<PCWSTR>(value.utf16())); - if (FAILED(hr)) { - *errorMessage = QString::fromLatin1("Unable to assign '%1' to '%2': %3"). - arg(value, iname, CdbCore::msgComFailed("WriteSymbolWide", hr)); - return false; - } - if (newValue) - *newValue = getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolValueTextWide, index); - return true; -} - -/* The special type dumpers have an integer return code meaning: - * 0: ok - * 1: Dereferencing or retrieving memory failed, this is out of scope, - * do not try to query further. - * > 1: A structural error was encountered, that is, the implementation - * of the class changed (Qt or say, a different STL implementation). - * Visibly warn about it. - * To add further types, have a look at the toString() output of the - * symbol group. */ - -static QString msgStructuralError(const QString &name, const QString &type, int code) -{ - return QString::fromLatin1("Warning: Internal dumper for '%1' (%2) failed with %3.").arg(name, type).arg(code); -} - -static inline bool isStdStringOrPointer(const QString &type) -{ -#define STD_WSTRING "std::basic_string<unsigned short,std::char_traits<unsigned short>,std::allocator<unsigned short> >" -#define STD_STRING "std::basic_string<char,std::char_traits<char>,std::allocator<char> >" - return type.endsWith(QLatin1String(STD_STRING)) - || type.endsWith(QLatin1String(STD_STRING" *")) - || type.endsWith(QLatin1String(STD_WSTRING)) - || type.endsWith(QLatin1String(STD_WSTRING" *")); -#undef STD_WSTRING -#undef STD_STRING -} - -CdbSymbolGroupContext::DumperResult - CdbSymbolGroupContext::dump(CIDebugDataSpaces *ds, WatchData *wd) -{ - DumperResult rc = DumperNotHandled; - do { - // Is this a previously detected Null-Pointer? - if (wd->isHasChildrenKnown() && !wd->hasChildren) - break; - // QString - if (wd->type.endsWith(QLatin1String("QString")) || wd->type.endsWith(QLatin1String("QString *"))) { - const int drc = dumpQString(ds, wd); - switch (drc) { - case 0: - rc = DumperOk; - break; - case 1: - rc = DumperError; - break; - default: - qWarning("%s\n", qPrintable(msgStructuralError(wd->iname, wd->type, drc))); - rc = DumperNotHandled; - break; - } - } - // StdString - if (isStdStringOrPointer(wd->type)) { - const int drc = dumpStdString(wd); - switch (drc) { - case 0: - rc = DumperOk; - break; - case 1: - rc = DumperError; - break; - default: - qWarning("%s\n", qPrintable(msgStructuralError(wd->iname, wd->type, drc))); - rc = DumperNotHandled; - break; - } - - } - } while (false); - if (debugInternalDumpers) - qDebug() << "CdbSymbolGroupContext::dump" << rc << wd->toString(); +bool CdbSymbolGroupContext::populateModelInitially(WatchHandler *wh, QString *errorMessage) +{ + if (debugCDBWatchHandling) + qDebug() << "populateModelInitially dumpers=" << m_useDumpers; + // Recurse down items that are initially expanded in the view, stop processing for + // dumper items. + const CdbSymbolGroupRecursionContext rctx(this, OwnerSymbolGroupDumper); + const bool rc = m_useDumpers ? + populateModelInitiallyHelper(rctx, + WatchHandleDumperInserter(wh, m_dumper), + WatchHandlerExpandedPredicate(wh), + isDumperPredicate, + errorMessage) : + populateModelInitiallyHelper(rctx, + WatchHandlerModelInserter(wh), + WatchHandlerExpandedPredicate(wh), + falsePredicate, + errorMessage); return rc; } -// Get integer value of symbol group -static inline bool getIntValue(CIDebugSymbolGroup *sg, int index, int *value) +bool CdbSymbolGroupContext::completeData(const WatchData &incompleteLocal, + WatchHandler *wh, + QString *errorMessage) { - const QString valueS = getSymbolString(sg, &IDebugSymbolGroup2::GetSymbolValueTextWide, index); - bool ok; - *value = valueS.toInt(&ok); - return ok; -} + if (debugCDBWatchHandling) + qDebug() << ">completeData src=" << incompleteLocal.source << incompleteLocal.toString(); -// Get pointer value of symbol group ("0xAAB") -// Note that this is on "00000000`0250124a" on 64bit systems. -static inline bool getUnsignedHexValue(CIDebugSymbolGroup *sg, int index, quint64 *value) -{ - const QString stringValue = getSymbolString(sg, &IDebugSymbolGroup2::GetSymbolValueTextWide, index); - return getUnsignedHexValue(stringValue, value); -} - -enum { maxStringLength = 4096 }; + const CdbSymbolGroupRecursionContext rctx(this, OwnerSymbolGroupDumper); + // Expand symbol group items, recurse one level from desired item + if (!m_useDumpers) { + return completeDataHelper(rctx, incompleteLocal, + WatchHandlerModelInserter(wh), + MatchINamePredicate(incompleteLocal.iname), + falsePredicate, + errorMessage); + } -int CdbSymbolGroupContext::dumpQString(CIDebugDataSpaces *ds, WatchData *wd) -{ - QString errorMessage; - unsigned long stringIndex; - if (!lookupPrefix(wd->iname, &stringIndex)) - return 1; - - // Expand string and it's "d" (step over 'static null') - if (!expandSymbol(wd->iname, stringIndex, &errorMessage)) - return 2; - const unsigned long dIndex = stringIndex + 4; - if (!expandSymbol(wd->iname, dIndex, &errorMessage)) - return 3; - const unsigned long sizeIndex = dIndex + 3; - const unsigned long arrayIndex = dIndex + 4; - // Get size and pointer - int size; - if (!getIntValue(m_symbolGroup, sizeIndex, &size)) - return 4; - quint64 array; - if (!getUnsignedHexValue(m_symbolGroup, arrayIndex, &array)) - return 5; - // Fetch - const bool truncated = size > maxStringLength; - if (truncated) - size = maxStringLength; - const QChar doubleQuote = QLatin1Char('"'); - QString value; - if (size > 0) { - value += doubleQuote; - // Should this ever be a remote debugger, need to check byte order. - unsigned short *buf = new unsigned short[size + 1]; - unsigned long bytesRead; - const HRESULT hr = ds->ReadVirtual(array, buf, size * sizeof(unsigned short), &bytesRead); - if (FAILED(hr)) { - delete [] buf; - return 1; + // Expand artifical dumper items + if ((incompleteLocal.source & CdbSymbolGroupContext::SourceMask) == OwnerDumper) { + // If the model queries the expanding item by pretending childrenNeeded=1, + // refuse the request if the children are already known + if (incompleteLocal.state == WatchData::ChildrenNeeded && (incompleteLocal.source & CdbSymbolGroupContext::ChildrenKnownBit)) { + WatchData local = incompleteLocal; + local.setChildrenUnneeded(); + wh->insertData(local); + return true; } - buf[bytesRead / sizeof(unsigned short)] = 0; - value += QString::fromUtf16(buf); - delete [] buf; - if (truncated) - value += QLatin1String("..."); - value += doubleQuote; - } else if (size == 0) { - value = QString(doubleQuote) + doubleQuote; - } else { - value = "Invalid QString"; + QList<WatchData> dumperResult; + const CdbDumperHelper::DumpResult dr = m_dumper->dumpType(incompleteLocal, true, &dumperResult, errorMessage); + if (dr == CdbDumperHelper::DumpOk) { + // Hack to stop endless model recursion + const bool suppressGrandChildren = !wh->isExpandedIName(incompleteLocal.iname); + fixDumperResult(incompleteLocal, &dumperResult, suppressGrandChildren); + wh->insertBulkData(dumperResult); + } else { + const QString msg = QString::fromLatin1("Unable to further expand dumper watch data: '%1' (%2): %3/%4").arg(incompleteLocal.name, incompleteLocal.type).arg(int(dr)).arg(*errorMessage); + qWarning("%s", qPrintable(msg)); + WatchData wd = incompleteLocal; + if (wd.isValueNeeded()) + wd.setValue(QCoreApplication::translate("CdbSymbolGroupContext", "<Unknown>")); + wd.setHasChildren(false); + wd.setAllUnneeded(); + wh->insertData(wd); + } + return true; } - wd->setValue(value); - wd->setHasChildren(false); - return 0; + // Expand symbol group items, recurse one level from desired item + return completeDataHelper(rctx, incompleteLocal, + WatchHandleDumperInserter(wh, m_dumper), + MatchINamePredicate(incompleteLocal.iname), + isDumperPredicate, + errorMessage); } -int CdbSymbolGroupContext::dumpStdString(WatchData *wd) +bool CdbSymbolGroupContext::editorToolTip(const QString &iname, + QString *value, + QString *errorMessage) { - QString errorMessage; - unsigned long stringIndex; - if (!lookupPrefix(wd->iname, &stringIndex)) - return 1; - - // Expand string ->string_val->_bx. - if (!expandSymbol(wd->iname, stringIndex, &errorMessage)) - return 1; - const unsigned long bxIndex = stringIndex + 3; - if (!expandSymbol(wd->iname, bxIndex, &errorMessage)) - return 2; - // Check if size is something sane - const int sizeIndex = stringIndex + 6; - int size; - if (!getIntValue(m_symbolGroup, sizeIndex, &size)) - return 3; - if (size < 0) - return 1; - // Just copy over the value of the buf[]-array, which should be the string - const QChar doubleQuote = QLatin1Char('"'); - const int bufIndex = stringIndex + 4; - QString bufValue = getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolValueTextWide, bufIndex); - const int quotePos = bufValue.indexOf(doubleQuote); - if (quotePos == -1) - return 1; - bufValue.remove(0, quotePos); - if (bufValue.size() > maxStringLength) { - bufValue.truncate(maxStringLength); - bufValue += QLatin1String("...\""); + if (debugToolTips) + qDebug() << iname; + value->clear(); + unsigned long index; + if (!lookupPrefix(iname, &index)) { + *errorMessage = QString::fromLatin1("%1 not found.").arg(iname); + return false; } - wd->setValue(bufValue); - wd->setHasChildren(false); - return 0; + // Check dumpers. Should actually be just one item. + + WatchData wd; + const unsigned rc = watchDataAt(index, &wd); + if (m_useDumpers && !wd.error + && (0u == (rc & CdbCore::SymbolGroupContext::InternalDumperMask)) + && m_dumper->state() != CdbDumperHelper::Disabled) { + QList<WatchData> result; + if (CdbDumperHelper::DumpOk == m_dumper->dumpType(wd, false, &result, errorMessage)) { + foreach (const WatchData &dwd, result) { + if (!value->isEmpty()) + value->append(QLatin1Char('\n')); + value->append(dwd.toToolTip()); + } + return true; + } // Dumped ok + } // has Dumpers + if (debugToolTips) + qDebug() << iname << wd.toString(); + *value = wd.toToolTip(); + return true; } } // namespace Internal diff --git a/src/plugins/debugger/cdb/cdbsymbolgroupcontext.h b/src/plugins/debugger/cdb/cdbsymbolgroupcontext.h index d3f63b282bdd0f7d8d2e6d412a96b49546597754..c23379df1b7173bc0df61ee9382a18cf0ce270cf 100644 --- a/src/plugins/debugger/cdb/cdbsymbolgroupcontext.h +++ b/src/plugins/debugger/cdb/cdbsymbolgroupcontext.h @@ -31,12 +31,13 @@ #define CDBSYMBOLGROUPCONTEXT_H #include "cdbcom.h" -#include "watchhandler.h" +#include "symbolgroupcontext.h" #include <QtCore/QString> #include <QtCore/QVector> #include <QtCore/QList> #include <QtCore/QStringList> +#include <QtCore/QSharedPointer> #include <QtCore/QPair> #include <QtCore/QMap> #include <QtCore/QSet> @@ -47,50 +48,47 @@ namespace Internal { class WatchData; class WatchHandler; struct CdbSymbolGroupRecursionContext; +class CdbDumperHelper; -/* A thin wrapper around the IDebugSymbolGroup2 interface which represents - * a flat list of symbols using an index (for example, belonging to a stack - * frame). It uses the hierarchical naming convention of WatchHandler as in: - * "local" (invisible root) - * "local.string" (local class variable) - * "local.string.data" (class member) - * and maintains a mapping iname -> index. - * IDebugSymbolGroup2 can "expand" expandable symbols, inserting them into the - * flat list after their parent. - * - * Note the pecularity of IDebugSymbolGroup2 with regard to pointed to items: - * 1) A pointer to a POD (say int *) will expand to a pointed-to integer named '*'. - * 2) A pointer to a class (QString *), will expand to the class members right away, - * omitting the '*' derefenced item. That is a problem since the dumpers trigger - * only on the derefenced item, so, additional handling is required. - */ - -class CdbSymbolGroupContext + +/* CdbSymbolGroupContext manages a symbol group context and + * a dumper context. It dispatches calls between the local items + * that are handled by the symbol group and those that are handled by the dumpers. */ + +class CdbSymbolGroupContext : public CdbCore::SymbolGroupContext { Q_DISABLE_COPY(CdbSymbolGroupContext); explicit CdbSymbolGroupContext(const QString &prefix, CIDebugSymbolGroup *symbolGroup, - const QStringList &uninitializedVariables = QStringList()); + const QSharedPointer<CdbDumperHelper> &dumper, + const QStringList &uninitializedVariables); public: - ~CdbSymbolGroupContext(); - static CdbSymbolGroupContext *create(const QString &prefix, - CIDebugSymbolGroup *symbolGroup, - const QStringList &uninitializedVariables, - QString *errorMessage); + // Mask bits for the source field of watch data. + enum { SourceMask = 0xFF, ChildrenKnownBit = 0x0100 }; + + static CdbSymbolGroupContext *create(const QString &prefix, + CIDebugSymbolGroup *symbolGroup, + const QSharedPointer<CdbDumperHelper> &dumper, + const QStringList &uninitializedVariables, + QString *errorMessage); - QString prefix() const { return m_prefix; } + bool editorToolTip(const QString &iname, QString *value, QString *errorMessage); - bool assignValue(const QString &iname, const QString &value, - QString *newValue /* = 0 */, QString *errorMessage); + bool populateModelInitially(WatchHandler *wh, QString *errorMessage); + bool completeData(const WatchData &incompleteLocal, + WatchHandler *wh, + QString *errorMessage); + +private: // Initially populate the locals model for a new stackframe. // Write a sequence of WatchData to it, recurse down if the // recursionPredicate agrees. The ignorePredicate can be used // to terminate processing after insertion of an item (if the calling // routine wants to insert another subtree). template <class OutputIterator, class RecursionPredicate, class IgnorePredicate> - static bool populateModelInitially(const CdbSymbolGroupRecursionContext &ctx, + static bool populateModelInitiallyHelper(const CdbSymbolGroupRecursionContext &ctx, OutputIterator it, RecursionPredicate recursionPredicate, IgnorePredicate ignorePredicate, @@ -102,7 +100,7 @@ public: // to terminate processing after insertion of an item (if the calling // routine wants to insert another subtree). template <class OutputIterator, class RecursionPredicate, class IgnorePredicate> - static bool completeData (const CdbSymbolGroupRecursionContext &ctx, + static bool completeDataHelper (const CdbSymbolGroupRecursionContext &ctx, WatchData incompleteLocal, OutputIterator it, RecursionPredicate recursionPredicate, @@ -116,67 +114,30 @@ public: // Retrieve child symbols of prefix as a sequence of WatchData. // Is CIDebugDataSpaces is != 0, try internal dumper and set owner template <class OutputIterator> - bool getDumpChildSymbols(CIDebugDataSpaces *ds, const QString &prefix, - int dumpedOwner, - OutputIterator it, QString *errorMessage); - - WatchData watchDataAt(unsigned long index) const; - // Run the internal dumpers on the symbol - WatchData dumpSymbolAt(CIDebugDataSpaces *ds, unsigned long index); + bool getDumpChildSymbols(const QString &prefix, + int dumpedOwner, + OutputIterator it, QString *errorMessage); - bool lookupPrefix(const QString &prefix, unsigned long *index) const; - - enum SymbolState { LeafSymbol, ExpandedSymbol, CollapsedSymbol }; - SymbolState symbolState(unsigned long index) const; - SymbolState symbolState(const QString &prefix) const; - - inline bool isExpanded(unsigned long index) const { return symbolState(index) == ExpandedSymbol; } - inline bool isExpanded(const QString &prefix) const { return symbolState(prefix) == ExpandedSymbol; } + template <class OutputIterator, class RecursionPredicate, class IgnorePredicate> + static bool insertSymbolRecursion(WatchData wd, + const CdbSymbolGroupRecursionContext &ctx, + OutputIterator it, + RecursionPredicate recursionPredicate, + IgnorePredicate ignorePredicate, + QString *errorMessage); - // Dump - enum DumperResult { DumperOk, DumperError, DumperNotHandled }; - DumperResult dump(CIDebugDataSpaces *ds, WatchData *wd); + unsigned watchDataAt(unsigned long index, WatchData *); -private: - typedef QMap<QString, unsigned long> NameIndexMap; - - static inline bool isSymbolDisplayable(const DEBUG_SYMBOL_PARAMETERS &p); - - bool init(QString *errorMessage); - void clear(); - QString toString(bool verbose = false) const; - bool getChildSymbolsPosition(const QString &prefix, - unsigned long *startPos, - unsigned long *parentId, - QString *errorMessage); - bool expandSymbol(const QString &prefix, unsigned long index, QString *errorMessage); - void populateINameIndexMap(const QString &prefix, unsigned long parentId, unsigned long end); - QString symbolINameAt(unsigned long index) const; - - int dumpQString(CIDebugDataSpaces *ds, WatchData *wd); - int dumpStdString(WatchData *wd); - - inline DEBUG_SYMBOL_PARAMETERS *symbolParameters() { return &(*m_symbolParameters.begin()); } - inline const DEBUG_SYMBOL_PARAMETERS *symbolParameters() const { return &(*m_symbolParameters.constBegin()); } - - const QString m_prefix; - const QChar m_nameDelimiter; - const QSet<QString> m_uninitializedVariables; - - CIDebugSymbolGroup *m_symbolGroup; - NameIndexMap m_inameIndexMap; - QVector<DEBUG_SYMBOL_PARAMETERS> m_symbolParameters; - int m_unnamedSymbolNumber; + const bool m_useDumpers; + const QSharedPointer<CdbDumperHelper> m_dumper; }; - // A convenience struct to save parameters for the model recursion. struct CdbSymbolGroupRecursionContext { - explicit CdbSymbolGroupRecursionContext(CdbSymbolGroupContext *ctx, int internalDumperOwner, CIDebugDataSpaces *ds); + explicit CdbSymbolGroupRecursionContext(CdbSymbolGroupContext *ctx, int internalDumperOwner); CdbSymbolGroupContext *context; int internalDumperOwner; - CIDebugDataSpaces *dataspaces; }; // Helper to a sequence of WatchData into a list. diff --git a/src/plugins/debugger/cdb/cdbsymbolgroupcontext_tpl.h b/src/plugins/debugger/cdb/cdbsymbolgroupcontext_tpl.h index 0ae6be3353e64decee8e9101c70772f5b82f8f0a..673a1abccc8979c6eff485318ef00cf8acefece2 100644 --- a/src/plugins/debugger/cdb/cdbsymbolgroupcontext_tpl.h +++ b/src/plugins/debugger/cdb/cdbsymbolgroupcontext_tpl.h @@ -30,6 +30,8 @@ #ifndef CDBSYMBOLGROUPCONTEXT_TPL_H #define CDBSYMBOLGROUPCONTEXT_TPL_H +#include "watchhandler.h" + #include <QtCore/QDebug> namespace Debugger { @@ -37,20 +39,10 @@ namespace Internal { enum { debugSgRecursion = 0 }; -/* inline static */ bool CdbSymbolGroupContext::isSymbolDisplayable(const DEBUG_SYMBOL_PARAMETERS &p) -{ - if (p.Flags & (DEBUG_SYMBOL_IS_LOCAL|DEBUG_SYMBOL_IS_ARGUMENT)) - return true; - // Do not display static members. - if (p.Flags & DEBUG_SYMBOL_READ_ONLY) - return false; - return true; -} - template <class OutputIterator> -bool CdbSymbolGroupContext::getDumpChildSymbols(CIDebugDataSpaces *ds, const QString &prefix, - int dumpedOwner, - OutputIterator it, QString *errorMessage) +bool CdbSymbolGroupContext::getDumpChildSymbols(const QString &prefix, + int dumpedOwner, + OutputIterator it, QString *errorMessage) { unsigned long start; unsigned long parentId; @@ -58,21 +50,14 @@ bool CdbSymbolGroupContext::getDumpChildSymbols(CIDebugDataSpaces *ds, const QSt return false; // Skip over expanded children. Internal dumping might expand // children, so, re-evaluate size in end condition. - for (int s = start; s < m_symbolParameters.size(); ++s) { - const DEBUG_SYMBOL_PARAMETERS &p = m_symbolParameters.at(s); + const int count = size(); + for (int s = start; s < count; ++s) { + const DEBUG_SYMBOL_PARAMETERS &p = symbolParameterAt(s); if (p.ParentSymbol == parentId && isSymbolDisplayable(p)) { - WatchData wd = watchDataAt(s); - // Run internal dumper, mark ownership - if (ds) { - switch (dump(ds, &wd)) { - case DumperOk: - case DumperError: // Not initialized yet, do not run other dumpers - wd.source = dumpedOwner; - break; - case DumperNotHandled: - break; - } - } + WatchData wd; + const unsigned rc = watchDataAt(s, &wd); + if (rc & InternalDumperMask) + wd.source = dumpedOwner; *it = wd; ++it; } @@ -86,7 +71,7 @@ bool CdbSymbolGroupContext::getDumpChildSymbols(CIDebugDataSpaces *ds, const QSt // children but can expand none, which would lead to invalid parent information // (expand icon), though (ignore for simplicity). template <class OutputIterator, class RecursionPredicate, class IgnorePredicate> -bool insertSymbolRecursion(WatchData wd, +bool CdbSymbolGroupContext::insertSymbolRecursion(WatchData wd, const CdbSymbolGroupRecursionContext &ctx, OutputIterator it, RecursionPredicate recursionPredicate, @@ -124,8 +109,7 @@ bool insertSymbolRecursion(WatchData wd, return true; QList<WatchData> watchList; // This implicitly enforces expansion - if (!ctx.context->getDumpChildSymbols(ctx.dataspaces, - wd.iname, + if (!ctx.context->getDumpChildSymbols(wd.iname, ctx.internalDumperOwner, WatchDataBackInserter(watchList), errorMessage)) return false; @@ -145,11 +129,11 @@ bool insertSymbolRecursion(WatchData wd, } template <class OutputIterator, class RecursionPredicate, class IgnorePredicate> -bool CdbSymbolGroupContext::populateModelInitially(const CdbSymbolGroupRecursionContext &ctx, - OutputIterator it, - RecursionPredicate recursionPredicate, - IgnorePredicate ignorePredicate, - QString *errorMessage) +bool CdbSymbolGroupContext::populateModelInitiallyHelper(const CdbSymbolGroupRecursionContext &ctx, + OutputIterator it, + RecursionPredicate recursionPredicate, + IgnorePredicate ignorePredicate, + QString *errorMessage) { if (debugSgRecursion) qDebug() << "### CdbSymbolGroupContext::populateModelInitially"; @@ -157,7 +141,7 @@ bool CdbSymbolGroupContext::populateModelInitially(const CdbSymbolGroupRecursion // Insert root items QList<WatchData> watchList; CdbSymbolGroupContext *sg = ctx.context; - if (!sg->getDumpChildSymbols(ctx.dataspaces, sg->prefix(), + if (!sg->getDumpChildSymbols(sg->prefix(), ctx.internalDumperOwner, WatchDataBackInserter(watchList), errorMessage)) return false; @@ -169,7 +153,7 @@ bool CdbSymbolGroupContext::populateModelInitially(const CdbSymbolGroupRecursion } template <class OutputIterator, class RecursionPredicate, class IgnorePredicate> -bool CdbSymbolGroupContext::completeData(const CdbSymbolGroupRecursionContext &ctx, +bool CdbSymbolGroupContext::completeDataHelper(const CdbSymbolGroupRecursionContext &ctx, WatchData incompleteLocal, OutputIterator it, RecursionPredicate recursionPredicate, diff --git a/src/plugins/debugger/cdb/stacktracecontext.cpp b/src/plugins/debugger/cdb/stacktracecontext.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f5639010f28626334ff0782f6070608acbc68a5e --- /dev/null +++ b/src/plugins/debugger/cdb/stacktracecontext.cpp @@ -0,0 +1,412 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 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 "stacktracecontext.h" +#include "symbolgroupcontext.h" +#include "breakpoint.h" +#include "coreengine.h" + +#include <QtCore/QDir> +#include <QtCore/QDebug> +#include <QtCore/QTextStream> + +enum { debug = 0 }; + +namespace CdbCore { + +StackFrame::StackFrame() : + line(0),address(0) +{ +} + +QString StackFrame::toString() const +{ + QString rc; + QTextStream str(&rc); + format(str); + return rc; +} + +QDebug operator<<(QDebug d, const StackFrame &f) +{ + d.nospace() << f.toString(); + return d; +} + +void StackFrame::format(QTextStream &str) const +{ + // left-pad level + if (hasFile()) + str << QDir::toNativeSeparators(fileName) << ':' << line << " ("; + if (!module.isEmpty()) + str << module << '!'; + str << function; + if (hasFile()) + str << ')'; + str.setIntegerBase(16); + str << " 0x" << address; + str.setIntegerBase(10); +} + +// Check for special functions +StackTraceContext::SpecialFunction StackTraceContext::specialFunction(const QString &module, + const QString &function) +{ + if (module == QLatin1String("ntdll")) { + if (function == QLatin1String("DbgBreakPoint")) + return BreakPointFunction; + if (function == QLatin1String("KiFastSystemCallRet")) + return KiFastSystemCallRet; + if (function.startsWith("ZwWaitFor")) + return WaitFunction; + } + if (module == QLatin1String("kernel32")) { + if (function == QLatin1String("MsgWaitForMultipleObjects")) + return WaitFunction; + if (function.startsWith(QLatin1String("WaitFor"))) + return WaitFunction; + } + return None; +} + +StackTraceContext::StackTraceContext(const ComInterfaces *cif) : + m_cif(cif), + m_instructionOffset(0) +{ +} + +StackTraceContext *StackTraceContext::create(const ComInterfaces *cif, + unsigned long maxFramesIn, + QString *errorMessage) +{ + StackTraceContext *ctx = new StackTraceContext(cif); + if (!ctx->init(maxFramesIn, errorMessage)) { + delete ctx; + return 0; + + } + return ctx; +} + +StackTraceContext::~StackTraceContext() +{ + qDeleteAll(m_frameContexts); +} + +// Convert the DEBUG_STACK_FRAMEs to our StackFrame structure +StackFrame StackTraceContext::frameFromFRAME(const CdbCore::ComInterfaces &cif, + const DEBUG_STACK_FRAME &s) +{ + static WCHAR wszBuf[MAX_PATH]; + StackFrame frame; + frame.address = s.InstructionOffset; + cif.debugSymbols->GetNameByOffsetWide(frame.address, wszBuf, MAX_PATH, 0, 0); + // Determine function and module, if available ("Qt4Core!foo"). + const QString moduleFunction = QString::fromUtf16(reinterpret_cast<const ushort *>(wszBuf)); + const int moduleSepPos = moduleFunction.indexOf(QLatin1Char('!')); + if (moduleSepPos == -1) { + frame.function = moduleFunction; + } else { + frame.module = moduleFunction.left(moduleSepPos); + frame.function = moduleFunction.mid(moduleSepPos + 1); + } + ULONG64 ul64Displacement; + const HRESULT hr = cif.debugSymbols->GetLineByOffsetWide(frame.address, &frame.line, wszBuf, MAX_PATH, 0, &ul64Displacement); + if (SUCCEEDED(hr)) { + const QString rawName = QString::fromUtf16(reinterpret_cast<const ushort *>(wszBuf)); + if (!rawName.isEmpty()) + frame.fileName = BreakPoint::normalizeFileName(rawName); + } + return frame; +} + +bool StackTraceContext::init(unsigned long maxFramesIn, QString *errorMessage) +{ + if (debug) + qDebug() << Q_FUNC_INFO << maxFramesIn; + + // fill the DEBUG_STACK_FRAME array + ULONG frameCount; + const unsigned long effectiveMaxFrames = qMin(maxFramesIn, unsigned long(StackTraceContext::maxFrames)); + const HRESULT hr = m_cif->debugControl->GetStackTrace(0, 0, 0, m_cdbFrames, + effectiveMaxFrames, + &frameCount); + if (FAILED(hr)) { + *errorMessage = CdbCore::msgComFailed("GetStackTrace", hr); + return false; + } + + // Adapt group cache. + m_frameContexts.resize(frameCount); + qFill(m_frameContexts, static_cast<SymbolGroupContext*>(0)); + // Convert the DEBUG_STACK_FRAMEs to our StackFrame structure and populate the frames + for (ULONG i=0; i < frameCount; ++i) + m_frames.push_back(frameFromFRAME(*m_cif, m_cdbFrames[i])); + m_instructionOffset = m_frames.empty() ? ULONG64(0) : m_frames.front().address; + return true; +} + +int StackTraceContext::indexOf(const QString &function, + const QString &module /* = QString() */) const +{ + const bool noModuleMatch = module.isEmpty(); + const int count = m_frames.size(); + for (int i = 0; i < count; i++) { + if (m_frames.at(i).function == function + && (noModuleMatch || module == m_frames.at(i).module)) + return i; + } + return -1; +} + +QString StackTraceContext::msgFrameContextFailed(int index, const StackFrame &f, const QString &why) +{ + return QString::fromLatin1("Unable to create stack frame context #%1, %2!%3:%4 (%5): %6"). + arg(index).arg(f.module).arg(f.function).arg(f.line).arg(f.fileName, why); +} + +SymbolGroupContext *StackTraceContext::createSymbolGroup(const ComInterfaces &cif, + int /* index */, + const QString &prefix, + CIDebugSymbolGroup *comSymbolGroup, + QString *errorMessage) +{ + return SymbolGroupContext::create(prefix, comSymbolGroup, cif.debugDataSpaces, + QStringList(), errorMessage); +} + +SymbolGroupContext *StackTraceContext::symbolGroupContextAt(int index, QString *errorMessage) +{ + // Create a frame on demand + if (debug) + qDebug() << Q_FUNC_INFO << index; + + if (index < 0 || index >= m_frameContexts.size()) { + *errorMessage = QString::fromLatin1("%1: Index %2 out of range %3."). + arg(QLatin1String(Q_FUNC_INFO)).arg(index).arg(m_frameContexts.size()); + return 0; + } + if (m_frameContexts.at(index)) + return m_frameContexts.at(index); + CIDebugSymbolGroup *comSymbolGroup = createCOM_SymbolGroup(index, errorMessage); + if (!comSymbolGroup) { + *errorMessage = msgFrameContextFailed(index, m_frames.at(index), *errorMessage); + return 0; + } + SymbolGroupContext *sc = createSymbolGroup(*m_cif, index, QLatin1String("local"), + comSymbolGroup, errorMessage); + if (!sc) { + *errorMessage = msgFrameContextFailed(index, m_frames.at(index), *errorMessage); + return 0; + } + m_frameContexts[index] = sc; + return sc; +} + +CIDebugSymbolGroup *StackTraceContext::createCOM_SymbolGroup(int index, QString *errorMessage) +{ + CIDebugSymbolGroup *sg = 0; + HRESULT hr = m_cif->debugSymbols->GetScopeSymbolGroup2(DEBUG_SCOPE_GROUP_LOCALS, NULL, &sg); + if (FAILED(hr)) { + *errorMessage = CdbCore::msgComFailed("GetScopeSymbolGroup", hr); + return 0; + } + + hr = m_cif->debugSymbols->SetScope(0, m_cdbFrames + index, NULL, 0); + if (FAILED(hr)) { + *errorMessage = CdbCore::msgComFailed("SetScope", hr); + sg->Release(); + return 0; + } + // refresh with current frame + hr = m_cif->debugSymbols->GetScopeSymbolGroup2(DEBUG_SCOPE_GROUP_LOCALS, sg, &sg); + if (FAILED(hr)) { + *errorMessage = CdbCore::msgComFailed("GetScopeSymbolGroup", hr); + sg->Release(); + return 0; + } + return sg; +} + +QString StackTraceContext::toString() const +{ + QString rc; + QTextStream str(&rc); + format(str); + return rc; +} + +void StackTraceContext::format(QTextStream &str) const +{ + const int count = m_frames.count(); + const int defaultFieldWidth = str.fieldWidth(); + const QTextStream::FieldAlignment defaultAlignment = str.fieldAlignment(); + for (int f = 0; f < count; f++) { + // left-pad level + str << qSetFieldWidth(6) << left << f; + str.setFieldWidth(defaultFieldWidth); + str.setFieldAlignment(defaultAlignment); + m_frames.at(f).format(str); + str << '\n'; + } +} + +// Thread state helper +static inline QString msgGetThreadStateFailed(unsigned long threadId, const QString &why) +{ + return QString::fromLatin1("Unable to determine the state of thread %1: %2").arg(threadId).arg(why); +} + +// Determine information about thread. This changes the +// current thread to thread->id. +bool StackTraceContext::getStoppedThreadState(const CdbCore::ComInterfaces &cif, + unsigned long id, + StackFrame *topFrame, + QString *errorMessage) +{ + enum { MaxFrames = 2 }; + ULONG currentThread; + HRESULT hr = cif.debugSystemObjects->GetCurrentThreadId(¤tThread); + if (FAILED(hr)) { + *errorMessage = msgGetThreadStateFailed(id, CdbCore::msgComFailed("GetCurrentThreadId", hr)); + return false; + } + if (currentThread != id) { + hr = cif.debugSystemObjects->SetCurrentThreadId(id); + if (FAILED(hr)) { + *errorMessage = msgGetThreadStateFailed(id, CdbCore::msgComFailed("SetCurrentThreadId", hr)); + return false; + } + } + ULONG frameCount; + // Ignore the top frame if it is "ntdll!KiFastSystemCallRet", which is + // not interesting for display. + DEBUG_STACK_FRAME frames[MaxFrames]; + hr = cif.debugControl->GetStackTrace(0, 0, 0, frames, MaxFrames, &frameCount); + if (FAILED(hr)) { + *errorMessage = msgGetThreadStateFailed(id, CdbCore::msgComFailed("GetStackTrace", hr)); + return false; + } + // Ignore the top frame if it is "ntdll!KiFastSystemCallRet", which is + // not interesting for display. + *topFrame = frameFromFRAME(cif, frames[0]); + if (frameCount > 1 + && StackTraceContext::specialFunction(topFrame->module, topFrame->function) == KiFastSystemCallRet) + *topFrame = frameFromFRAME(cif, frames[1]); + return true; +} + +static inline QString msgGetThreadsFailed(const QString &why) +{ + return QString::fromLatin1("Unable to determine the thread information: %1").arg(why); +} + +bool StackTraceContext::getThreadIds(const CdbCore::ComInterfaces &cif, + QVector<ULONG> *threadIds, + ULONG *currentThreadId, + QString *errorMessage) +{ + threadIds->clear(); + ULONG threadCount; + *currentThreadId = 0; + HRESULT hr= cif.debugSystemObjects->GetNumberThreads(&threadCount); + if (FAILED(hr)) { + *errorMessage= msgGetThreadsFailed(CdbCore::msgComFailed("GetNumberThreads", hr)); + return false; + } + // Get ids and index of current + if (!threadCount) + return true; + hr = cif.debugSystemObjects->GetCurrentThreadId(currentThreadId); + if (FAILED(hr)) { + *errorMessage= msgGetThreadsFailed(CdbCore::msgComFailed("GetCurrentThreadId", hr)); + return false; + } + threadIds->resize(threadCount); + hr = cif.debugSystemObjects->GetThreadIdsByIndex(0, threadCount, &(*threadIds->begin()), 0); + if (FAILED(hr)) { + *errorMessage= msgGetThreadsFailed(CdbCore::msgComFailed("GetThreadIdsByIndex", hr)); + return false; + } + return true; +} + +bool StackTraceContext::getThreads(const CdbCore::ComInterfaces &cif, + ThreadIdFrameMap *threads, + ULONG *currentThreadId, + QString *errorMessage) +{ + threads->clear(); + QVector<ULONG> threadIds; + if (!getThreadIds(cif, &threadIds, currentThreadId, errorMessage)) + return false; + if (threadIds.isEmpty()) + return true; + + const int threadCount = threadIds.size(); + for (int i = 0; i < threadCount; i++) { + const ULONG id = threadIds.at(i); + StackFrame frame; + if (!getStoppedThreadState(cif, id, &frame, errorMessage)) { + qWarning("%s\n", qPrintable(*errorMessage)); + errorMessage->clear(); + } + threads->insert(id, frame); + } + // Restore thread id + if (threadIds.back() != *currentThreadId) { + const HRESULT hr = cif.debugSystemObjects->SetCurrentThreadId(*currentThreadId); + if (FAILED(hr)) { + *errorMessage= msgGetThreadsFailed(CdbCore::msgComFailed("SetCurrentThreadId", hr)); + return false; + } + } + return true; +} + +QString StackTraceContext::formatThreads(const ThreadIdFrameMap &threads) +{ + QString rc; + QTextStream str(&rc); + const ThreadIdFrameMap::const_iterator cend = threads.constEnd(); + for (ThreadIdFrameMap::const_iterator it = threads.constBegin(); it != cend; ++it) { + str << '#' << it.key() << ' '; + it.value().format(str); + str << '\n'; + } + return rc; +} + +QDebug operator<<(QDebug d, const StackTraceContext &t) +{ + d.nospace() << t.toString(); + return d; +} + +} diff --git a/src/plugins/debugger/cdb/stacktracecontext.h b/src/plugins/debugger/cdb/stacktracecontext.h new file mode 100644 index 0000000000000000000000000000000000000000..02277b52270327ac5b9d5eee5b107f487bed2ea6 --- /dev/null +++ b/src/plugins/debugger/cdb/stacktracecontext.h @@ -0,0 +1,162 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 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 CORESTACKTRACECONTEXT_H +#define CORESTACKTRACECONTEXT_H + +#include "cdbcom.h" + +#include <QtCore/QString> +#include <QtCore/QVector> +#include <QtCore/QSharedPointer> +#include <QtCore/QMap> + +QT_BEGIN_NAMESPACE +class QTextStream; +class QDebug; +QT_END_NAMESPACE + +namespace CdbCore { + +struct ComInterfaces; +class SymbolGroupContext; + + +struct StackFrame { + StackFrame(); + + bool hasFile() const { return !fileName.isEmpty(); } + void format(QTextStream &) const; + + QString toString() const; + + QString module; + QString function; + QString fileName; + ULONG line; + ULONG64 address; +}; + +QDebug operator<<(QDebug d, const StackFrame &); + +/* Context representing a break point stack consisting of several frames. + * Maintains an on-demand constructed list of SymbolGroupContext + * containining the local variables of the stack. */ + +class StackTraceContext +{ + Q_DISABLE_COPY(StackTraceContext) + +protected: + explicit StackTraceContext(const ComInterfaces *cif); + bool init(unsigned long maxFramesIn, QString *errorMessage); + +public: + // Utilities to check for special functions + enum SpecialFunction { + BreakPointFunction, + WaitFunction, + KiFastSystemCallRet, + None + }; + static SpecialFunction specialFunction(const QString &module, const QString &function); + + // A map of thread id, stack frame + typedef QMap<unsigned long, StackFrame> ThreadIdFrameMap; + + enum { maxFrames = 100 }; + + ~StackTraceContext(); + static StackTraceContext *create(const ComInterfaces *cif, + unsigned long maxFramesIn, + QString *errorMessage); + + // Search for function. Empty module means "don't match on module" + int indexOf(const QString &function, const QString &module = QString()) const; + + // Top-Level instruction offset for disassembler + ULONG64 instructionOffset() const { return m_instructionOffset; } + int frameCount() const { return m_frames.size(); } + + SymbolGroupContext *symbolGroupContextAt(int index, QString *errorMessage); + const StackFrame stackFrameAt(int index) const { return m_frames.at(index); } + + // Format for logging + void format(QTextStream &str) const; + QString toString() const; + + // Thread helpers: Retrieve a list of thread ids. Also works when running. + static inline bool getThreadIds(const CdbCore::ComInterfaces &cif, + QVector<ULONG> *threadIds, + ULONG *currentThreadId, + QString *errorMessage); + + // Retrieve detailed information about a threads in stopped state. + // Potentially changes current thread id. + static inline bool getStoppedThreadState(const CdbCore::ComInterfaces &cif, + unsigned long id, + StackFrame *topFrame, + QString *errorMessage); + + // Retrieve detailed information about all threads, works in stopped state. + static bool getThreads(const CdbCore::ComInterfaces &cif, + ThreadIdFrameMap *threads, + ULONG *currentThreadId, + QString *errorMessage); + + static QString formatThreads(const ThreadIdFrameMap &threads); + +protected: + virtual SymbolGroupContext *createSymbolGroup(const ComInterfaces &cif, + int index, + const QString &prefix, + CIDebugSymbolGroup *comSymbolGroup, + QString *errorMessage); + + static QString msgFrameContextFailed(int index, const StackFrame &f, const QString &why); + +private: + CIDebugSymbolGroup *createCOM_SymbolGroup(int index, QString *errorMessage); + inline static StackFrame frameFromFRAME(const CdbCore::ComInterfaces &cif, + const DEBUG_STACK_FRAME &s); + + // const QSharedPointer<CdbDumperHelper> m_dumper; + const ComInterfaces *m_cif; + + DEBUG_STACK_FRAME m_cdbFrames[maxFrames]; + QVector <SymbolGroupContext*> m_frameContexts; + QVector<StackFrame> m_frames; + ULONG64 m_instructionOffset; +}; + +QDebug operator<<(QDebug d, const StackTraceContext &); + +} // namespace Internal + +#endif // CORESTACKTRACECONTEXT_H diff --git a/src/plugins/debugger/cdb/symbolgroupcontext.cpp b/src/plugins/debugger/cdb/symbolgroupcontext.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a36d142b379ee48df62718bf309136ab37b80b24 --- /dev/null +++ b/src/plugins/debugger/cdb/symbolgroupcontext.cpp @@ -0,0 +1,740 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 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 "symbolgroupcontext.h" +#include "coreengine.h" + +#include <QtCore/QTextStream> +#include <QtCore/QCoreApplication> +#include <QtCore/QRegExp> +#include <QtCore/QString> +#include <QtCore/QDebug> + +enum { debug = 0 }; +enum { debugInternalDumpers = 0 }; + +// name separator for shadowed variables +static const char iNameShadowDelimiter = '#'; + +static inline QString msgSymbolNotFound(const QString &s) +{ + return QString::fromLatin1("The symbol '%1' could not be found.").arg(s); +} + +static inline QString msgOutOfScope() +{ + return QCoreApplication::translate("SymbolGroup", "Out of scope"); +} + +static inline bool isTopLevelSymbol(const DEBUG_SYMBOL_PARAMETERS &p) +{ + return p.ParentSymbol == DEBUG_ANY_ID; +} + +static inline void debugSymbolFlags(unsigned long f, QTextStream &str) +{ + if (f & DEBUG_SYMBOL_EXPANDED) + str << "DEBUG_SYMBOL_EXPANDED"; + if (f & DEBUG_SYMBOL_READ_ONLY) + str << "|DEBUG_SYMBOL_READ_ONLY"; + if (f & DEBUG_SYMBOL_IS_ARRAY) + str << "|DEBUG_SYMBOL_IS_ARRAY"; + if (f & DEBUG_SYMBOL_IS_FLOAT) + str << "|DEBUG_SYMBOL_IS_FLOAT"; + if (f & DEBUG_SYMBOL_IS_ARGUMENT) + str << "|DEBUG_SYMBOL_IS_ARGUMENT"; + if (f & DEBUG_SYMBOL_IS_LOCAL) + str << "|DEBUG_SYMBOL_IS_LOCAL"; +} + +QTextStream &operator<<(QTextStream &str, const DEBUG_SYMBOL_PARAMETERS &p) +{ + str << " Type=" << p.TypeId << " parent="; + if (isTopLevelSymbol(p)) { + str << "<ROOT>"; + } else { + str << p.ParentSymbol; + } + str << " Subs=" << p.SubElements << " flags=" << p.Flags << '/'; + debugSymbolFlags(p.Flags, str); + return str; +} + +static inline ULONG64 symbolOffset(CIDebugSymbolGroup *sg, unsigned long index) +{ + ULONG64 rc = 0; + if (FAILED(sg->GetSymbolOffset(index, &rc))) + rc = 0; + return rc; +} + +// A helper function to extract a string value from a member function of +// IDebugSymbolGroup2 taking the symbol index and a character buffer. +// Pass in the the member function as '&IDebugSymbolGroup2::GetSymbolNameWide' + +typedef HRESULT (__stdcall IDebugSymbolGroup2::*WideStringRetrievalFunction)(ULONG, PWSTR, ULONG, PULONG); + +static inline QString getSymbolString(IDebugSymbolGroup2 *sg, + WideStringRetrievalFunction wsf, + unsigned long index) +{ + // Template type names can get quite long.... + enum { BufSize = 1024 }; + static WCHAR nameBuffer[BufSize + 1]; + // Name + ULONG nameLength; + const HRESULT hr = (sg->*wsf)(index, nameBuffer, BufSize, &nameLength); + if (SUCCEEDED(hr)) { + nameBuffer[nameLength] = 0; + return QString::fromUtf16(reinterpret_cast<const ushort *>(nameBuffer)); + } + return QString(); +} + +namespace CdbCore { + +static inline SymbolGroupContext::SymbolState getSymbolState(const DEBUG_SYMBOL_PARAMETERS &p) +{ + if (p.SubElements == 0u) + return SymbolGroupContext::LeafSymbol; + return (p.Flags & DEBUG_SYMBOL_EXPANDED) ? + SymbolGroupContext::ExpandedSymbol : + SymbolGroupContext::CollapsedSymbol; +} + +SymbolGroupContext::SymbolGroupContext(const QString &prefix, + CIDebugSymbolGroup *symbolGroup, + CIDebugDataSpaces *dataSpaces, + const QStringList &uninitializedVariables) : + m_prefix(prefix), + m_nameDelimiter(QLatin1Char('.')), + m_uninitializedVariables(uninitializedVariables.toSet()), + m_symbolGroup(symbolGroup), + m_dataSpaces(dataSpaces), + m_unnamedSymbolNumber(1), + m_shadowedNameFormat(QLatin1String("%1#%2")) +{ +} + +SymbolGroupContext::~SymbolGroupContext() +{ + m_symbolGroup->Release(); +} + +SymbolGroupContext *SymbolGroupContext::create(const QString &prefix, + CIDebugSymbolGroup *symbolGroup, + CIDebugDataSpaces *dataSpaces, + const QStringList &uninitializedVariables, + QString *errorMessage) +{ + SymbolGroupContext *rc = new SymbolGroupContext(prefix, symbolGroup, dataSpaces, uninitializedVariables); + if (!rc->init(errorMessage)) { + delete rc; + return 0; + } + return rc; +} + +bool SymbolGroupContext::init(QString *errorMessage) +{ + // retrieve the root symbols + ULONG count; + HRESULT hr = m_symbolGroup->GetNumberSymbols(&count); + if (FAILED(hr)) { + *errorMessage = CdbCore::msgComFailed("GetNumberSymbols", hr); + return false; + } + + if (count) { + m_symbolParameters.reserve(3u * count); + m_symbolParameters.resize(count); + + hr = m_symbolGroup->GetSymbolParameters(0, count, symbolParameters()); + if (FAILED(hr)) { + *errorMessage = QString::fromLatin1("In %1: %2 (%3 symbols)").arg(QLatin1String(Q_FUNC_INFO), + CdbCore::msgComFailed("GetSymbolParameters", hr)).arg(count); + return false; + } + populateINameIndexMap(m_prefix, DEBUG_ANY_ID, count); + } + if (debug) + qDebug() << Q_FUNC_INFO << '\n'<< debugToString(); + return true; +} + +QString SymbolGroupContext::shadowedNameFormat() const +{ + return m_shadowedNameFormat; +} + +void SymbolGroupContext::setShadowedNameFormat(const QString &f) +{ + m_shadowedNameFormat = f; +} + +/* Make the entries for iname->index mapping. We might encounter + * already expanded subitems when doing it for top-level ('this'-pointers), + * recurse in that case, (skip over expanded children). + * Loop backwards to detect shadowed variables in the order the +/* debugger expects them: +\code +int x; // Occurrence (1), should be reported as "x <shadowed 1>" +if (true) { + int x = 5; (2) // Occurrence (2), should be reported as "x" +} +\endcode + * The order in the symbol group is (1),(2). Give them an iname of + * <root>#<shadowed-nr>, which will be split apart for display. */ + +void SymbolGroupContext::populateINameIndexMap(const QString &prefix, unsigned long parentId, + unsigned long end) +{ + const QString symbolPrefix = prefix + m_nameDelimiter; + if (debug) + qDebug() << Q_FUNC_INFO << '\n'<< symbolPrefix << parentId << end; + for (unsigned long i = end - 1; ; i--) { + const DEBUG_SYMBOL_PARAMETERS &p = m_symbolParameters.at(i); + if (parentId == p.ParentSymbol) { + // "__formal" occurs when someone writes "void foo(int /* x */)..." + static const QString unnamedFormalParameter = QLatin1String("__formal"); + QString symbolName = getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolNameWide, i); + if (symbolName == unnamedFormalParameter) { + symbolName = QLatin1String("<unnamed"); + symbolName += QString::number(m_unnamedSymbolNumber++); + symbolName += QLatin1Char('>'); + } + // Find a unique name in case the variable is shadowed by + // an existing one + const QString namePrefix = symbolPrefix + symbolName; + QString name = namePrefix; + for (int n = 1; m_inameIndexMap.contains(name); n++) { + name.truncate(namePrefix.size()); + name += QLatin1Char(iNameShadowDelimiter); + name += QString::number(n); + } + m_inameIndexMap.insert(name, i); + if (getSymbolState(p) == ExpandedSymbol) + populateINameIndexMap(name, i, i + 1 + p.SubElements); + } + if (i == 0 || i == parentId) + break; + } +} + +QString SymbolGroupContext::toString() +{ + QString rc; + QTextStream str(&rc); + const unsigned long count = m_symbolParameters.size(); + QString iname; + QString name; + ULONG64 addr; + ULONG typeId; + QString typeName; + QString value; + + + for (unsigned long i = 0; i < count; i++) { + const unsigned rc = dumpValue(i, &iname, &name, &addr, + &typeId, &typeName, &value); + str << iname << ' ' << name << ' ' << typeName << " (" << typeId + << ") '" << value; + str.setIntegerBase(16); + str << "' 0x" << addr << " flags: 0x" <<rc << '\n'; + str.setIntegerBase(10); + } // for + + return rc; +} + +QString SymbolGroupContext::debugToString(bool verbose) const +{ + QString rc; + QTextStream str(&rc); + const int count = m_symbolParameters.size(); + for (int i = 0; i < count; i++) { + str << i << ' '; + const DEBUG_SYMBOL_PARAMETERS &p = m_symbolParameters.at(i); + if (!isTopLevelSymbol(p)) + str << " "; + str << getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolNameWide, i); + if (p.Flags & DEBUG_SYMBOL_IS_LOCAL) + str << " '" << getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolTypeNameWide, i) << '\''; + str << " Address: " << symbolOffset(m_symbolGroup, i); + if (verbose) + str << " '" << getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolValueTextWide, i) << '\''; + str << p << '\n'; + } + if (verbose) { + str << "NameIndexMap\n"; + NameIndexMap::const_iterator ncend = m_inameIndexMap.constEnd(); + for (NameIndexMap::const_iterator it = m_inameIndexMap.constBegin() ; it != ncend; ++it) + str << it.key() << ' ' << it.value() << '\n'; + } + return rc; +} + +SymbolGroupContext::SymbolState SymbolGroupContext::symbolState(unsigned long index) const +{ + return getSymbolState(m_symbolParameters.at(index)); +} + +SymbolGroupContext::SymbolState SymbolGroupContext::symbolState(const QString &prefix) const +{ + if (prefix == m_prefix) // root + return ExpandedSymbol; + unsigned long index; + if (!lookupPrefix(prefix, &index)) { + qWarning("WARNING %s: %s\n", Q_FUNC_INFO, qPrintable(msgSymbolNotFound(prefix))); + return LeafSymbol; + } + return symbolState(index); +} + +// Find index of a prefix +bool SymbolGroupContext::lookupPrefix(const QString &prefix, unsigned long *index) const +{ + *index = 0; + const NameIndexMap::const_iterator it = m_inameIndexMap.constFind(prefix); + if (it == m_inameIndexMap.constEnd()) + return false; + *index = it.value(); + return true; +} + +/* Retrieve children and get the position. */ +bool SymbolGroupContext::getChildSymbolsPosition(const QString &prefix, + unsigned long *start, + unsigned long *parentId, + QString *errorMessage) +{ + if (debug) + qDebug() << Q_FUNC_INFO << '\n'<< prefix; + + *start = *parentId = 0; + // Root item? + if (prefix == m_prefix) { + *start = 0; + *parentId = DEBUG_ANY_ID; + if (debug) + qDebug() << '<' << prefix << "at" << *start; + return true; + } + // Get parent index, make sure it is expanded + NameIndexMap::const_iterator nit = m_inameIndexMap.constFind(prefix); + if (nit == m_inameIndexMap.constEnd()) { + *errorMessage = QString::fromLatin1("'%1' not found.").arg(prefix); + return false; + } + *parentId = nit.value(); + *start = nit.value() + 1; + if (!expandSymbol(prefix, *parentId, errorMessage)) + return false; + if (debug) + qDebug() << '<' << prefix << "at" << *start; + return true; +} + +static inline QString msgExpandFailed(const QString &prefix, unsigned long index, const QString &why) +{ + return QString::fromLatin1("Unable to expand '%1' %2: %3").arg(prefix).arg(index).arg(why); +} + +// Expand a symbol using the symbol group interface. +bool SymbolGroupContext::expandSymbol(const QString &prefix, unsigned long index, QString *errorMessage) +{ + if (debug) + qDebug() << '>' << Q_FUNC_INFO << '\n' << prefix << index; + + switch (symbolState(index)) { + case LeafSymbol: + *errorMessage = QString::fromLatin1("Attempt to expand leaf node '%1' %2!").arg(prefix).arg(index); + return false; + case ExpandedSymbol: + return true; + case CollapsedSymbol: + break; + } + + HRESULT hr = m_symbolGroup->ExpandSymbol(index, TRUE); + if (FAILED(hr)) { + *errorMessage = msgExpandFailed(prefix, index, CdbCore::msgComFailed("ExpandSymbol", hr)); + return false; + } + // Hopefully, this will never fail, else data structure will be foobar. + const ULONG oldSize = m_symbolParameters.size(); + ULONG newSize; + hr = m_symbolGroup->GetNumberSymbols(&newSize); + if (FAILED(hr)) { + *errorMessage = msgExpandFailed(prefix, index, CdbCore::msgComFailed("GetNumberSymbols", hr)); + return false; + } + + // Retrieve the new parameter structs which will be inserted + // after the parents, offsetting consecutive indexes. + m_symbolParameters.resize(newSize); + + hr = m_symbolGroup->GetSymbolParameters(0, newSize, symbolParameters()); + if (FAILED(hr)) { + *errorMessage = msgExpandFailed(prefix, index, CdbCore::msgComFailed("GetSymbolParameters", hr)); + return false; + } + // The new symbols are inserted after the parent symbol. + // We need to correct the following values in the name->index map + const unsigned long newSymbolCount = newSize - oldSize; + const NameIndexMap::iterator nend = m_inameIndexMap.end(); + for (NameIndexMap::iterator it = m_inameIndexMap.begin(); it != nend; ++it) + if (it.value() > index) + it.value() += newSymbolCount; + // insert the new symbols + populateINameIndexMap(prefix, index, index + 1 + newSymbolCount); + if (debug > 1) + qDebug() << '<' << Q_FUNC_INFO << '\n' << prefix << index << '\n' << toString(); + return true; +} + +void SymbolGroupContext::clear() +{ + m_symbolParameters.clear(); + m_inameIndexMap.clear(); +} + +QString SymbolGroupContext::symbolINameAt(unsigned long index) const +{ + return m_inameIndexMap.key(index); +} + +// Return hexadecimal pointer value from a CDB pointer value +// which look like "0x000032a" or "0x00000000`0250124a" or +// "0x1`0250124a" on 64-bit systems. +bool SymbolGroupContext::getUnsignedHexValue(QString stringValue, quint64 *value) +{ + *value = 0; + if (!stringValue.startsWith(QLatin1String("0x"))) + return false; + stringValue.remove(0, 2); + // Remove 64bit separator + if (stringValue.size() > 9) { + const int sepPos = stringValue.size() - 9; + if (stringValue.at(sepPos) == QLatin1Char('`')) + stringValue.remove(sepPos, 1); + } + bool ok; + *value = stringValue.toULongLong(&ok, 16); + return ok; +} + +// check for "0x000", "0x000 class X" or its 64-bit equivalents. +bool SymbolGroupContext::isNullPointer(const QString &type , QString valueS) +{ + if (!type.endsWith(QLatin1String(" *"))) + return false; + const int blankPos = valueS.indexOf(QLatin1Char(' ')); + if (blankPos != -1) + valueS.truncate(blankPos); + quint64 value; + return SymbolGroupContext::getUnsignedHexValue(valueS, &value) && value == 0u; +} + +// Fix a symbol group value. It is set to the class type for +// expandable classes (type="class std::foo<..>[*]", +// value="std::foo<...>[*]". This is not desired +// as it widens the value column for complex std::template types. +// Remove the inner template type. + +QString SymbolGroupContext::removeInnerTemplateType(QString value) +{ + const int firstBracketPos = value.indexOf(QLatin1Char('<')); + const int lastBracketPos = firstBracketPos != -1 ? value.lastIndexOf(QLatin1Char('>')) : -1; + if (lastBracketPos != -1) + value.replace(firstBracketPos + 1, lastBracketPos - firstBracketPos -1, QLatin1String("...")); + return value; +} + +QString SymbolGroupContext::formatShadowedName(const QString &name, int n) const +{ + return n > 0 ? m_shadowedNameFormat.arg(name).arg(n) : name; +} + +unsigned SymbolGroupContext::dumpValueRaw(unsigned long index, + QString *inameIn, + QString *nameIn, + ULONG64 *addrIn, + ULONG *typeIdIn, + QString *typeNameIn, + QString *valueIn) const +{ + unsigned rc = 0; + const QString iname = symbolINameAt(index); + *inameIn = iname; + *addrIn = symbolOffset(m_symbolGroup, index); + // Determine name from iname and format shadowed variables correctly + // as "<shadowed X>, see populateINameIndexMap() (from "name#1"). + const int lastDelimiterPos = iname.lastIndexOf(m_nameDelimiter); + QString name = lastDelimiterPos == -1 ? iname : iname.mid(lastDelimiterPos + 1); + int shadowedNumber = 0; + const int shadowedPos = name.lastIndexOf(QLatin1Char(iNameShadowDelimiter)); + if (shadowedPos != -1) { + shadowedNumber = name.mid(shadowedPos + 1).toInt(); + name.truncate(shadowedPos); + } + // For class hierarchies, we get sometimes complicated std::template types here. + // (std::map extends std::tree<>... Remove them for display only. + *nameIn = formatShadowedName(removeInnerTemplateType(name), shadowedNumber); + *typeNameIn = getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolTypeNameWide, index); + // Check for uninitialized variables at level 0 only. + const DEBUG_SYMBOL_PARAMETERS &p = m_symbolParameters.at(index); + *typeIdIn = p.TypeId; + if (p.ParentSymbol == DEBUG_ANY_ID) { + const QString fullShadowedName = formatShadowedName(name, shadowedNumber); + if (m_uninitializedVariables.contains(fullShadowedName)) { + rc |= OutOfScope; + valueIn->clear(); + return rc; + } + } + // In scope: Figure out value + *valueIn = getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolValueTextWide, index); + // Figure out children. The SubElement is only a guess unless the symbol, + // is expanded, so, we leave this as a guess for later updates. + // If the symbol has children (expanded or not), we leave the 'Children' flag + // in 'needed' state. Suppress 0-pointers right ("0x000 class X") + // here as they only lead to children with memory access errors. + if (p.SubElements && !isNullPointer(*typeNameIn, *valueIn)) + rc |= HasChildren; + return rc; +} + +/* The special type dumpers have an integer return code meaning: + * 0: ok + * 1: Dereferencing or retrieving memory failed, this is out of scope, + * do not try to query further. + * > 1: A structural error was encountered, that is, the implementation + * of the class changed (Qt or say, a different STL implementation). + * Visibly warn about it. + * To add further types, have a look at the toString() output of the + * symbol group. */ + +static QString msgStructuralError(const QString &name, const QString &type, int code) +{ + return QString::fromLatin1("Warning: Internal dumper for '%1' (%2) failed with %3.").arg(name, type).arg(code); +} + +static inline bool isStdStringOrPointer(const QString &type) +{ +#define STD_WSTRING "std::basic_string<unsigned short,std::char_traits<unsigned short>,std::allocator<unsigned short> >" +#define STD_STRING "std::basic_string<char,std::char_traits<char>,std::allocator<char> >" + return type.endsWith(QLatin1String(STD_STRING)) + || type.endsWith(QLatin1String(STD_STRING" *")) + || type.endsWith(QLatin1String(STD_WSTRING)) + || type.endsWith(QLatin1String(STD_WSTRING" *")); +#undef STD_WSTRING +#undef STD_STRING +} + +unsigned SymbolGroupContext::dumpValue(unsigned long index, + QString *inameIn, + QString *nameIn, + ULONG64 *addrIn, + ULONG *typeIdIn, + QString *typeNameIn, + QString *valueIn) +{ + unsigned rc = dumpValueRaw(index, inameIn, nameIn, addrIn, typeIdIn, + typeNameIn, valueIn); + do { + // Is this a previously detected Null-Pointer or out of scope + if ( (rc & (HasChildren|OutOfScope)) ) + break; + // QString + if (typeNameIn->endsWith(QLatin1String("QString")) || typeNameIn->endsWith(QLatin1String("QString *"))) { + const int drc = dumpQString(index, *inameIn, valueIn); + switch (drc) { + case 0: + rc |= InternalDumperSucceeded; + break; + case 1: + rc |= InternalDumperError; + break; + default: + rc |= InternalDumperFailed; + qWarning("%s\n", qPrintable(msgStructuralError(*inameIn, *typeNameIn, drc))); + break; + } + } + // StdString + if (isStdStringOrPointer(*typeNameIn)) { + const int drc = dumpStdString(index, *inameIn, valueIn); + switch (drc) { + case 0: + rc |= InternalDumperSucceeded; + break; + case 1: + rc |= InternalDumperError; + break; + default: + rc |= InternalDumperFailed; + qWarning("%s\n", qPrintable(msgStructuralError(*inameIn, *typeNameIn, drc))); + break; + } + + } + } while (false); + if (debugInternalDumpers) + qDebug() << "SymbolGroupContext::dump" << rc << nameIn << valueIn; + return rc; +} + +// Get integer value of symbol group +bool SymbolGroupContext::getDecimalIntValue(CIDebugSymbolGroup *sg, int index, int *value) +{ + const QString valueS = getSymbolString(sg, &IDebugSymbolGroup2::GetSymbolValueTextWide, index); + bool ok; + *value = valueS.toInt(&ok); + return ok; +} + +// Get pointer value of symbol group ("0xAAB") +// Note that this is on "00000000`0250124a" on 64bit systems. +static inline bool getSG_UnsignedHexValue(CIDebugSymbolGroup *sg, int index, quint64 *value) +{ + const QString stringValue = getSymbolString(sg, &IDebugSymbolGroup2::GetSymbolValueTextWide, index); + return SymbolGroupContext::getUnsignedHexValue(stringValue, value); +} + +enum { maxStringLength = 4096 }; + +int SymbolGroupContext::dumpQString(unsigned long index, + const QString &iname, + QString *valueIn) +{ + valueIn->clear(); + QString errorMessage; + // Expand string and it's "d" (step over 'static null') + if (!expandSymbol(iname, index, &errorMessage)) + return 2; + const unsigned long dIndex = index + 4; + if (!expandSymbol(iname, dIndex, &errorMessage)) + return 3; + const unsigned long sizeIndex = dIndex + 3; + const unsigned long arrayIndex = dIndex + 4; + // Get size and pointer + int size; + if (!getDecimalIntValue(m_symbolGroup, sizeIndex, &size)) + return 4; + quint64 array; + if (!getSG_UnsignedHexValue(m_symbolGroup, arrayIndex, &array)) + return 5; + // Fetch + const bool truncated = size > maxStringLength; + if (truncated) + size = maxStringLength; + const QChar doubleQuote = QLatin1Char('"'); + if (size > 0) { + valueIn->append(doubleQuote); + // Should this ever be a remote debugger, need to check byte order. + unsigned short *buf = new unsigned short[size + 1]; + unsigned long bytesRead; + const HRESULT hr = m_dataSpaces->ReadVirtual(array, buf, size * sizeof(unsigned short), &bytesRead); + if (FAILED(hr)) { + delete [] buf; + return 1; + } + buf[bytesRead / sizeof(unsigned short)] = 0; + valueIn->append(QString::fromUtf16(buf)); + delete [] buf; + if (truncated) + valueIn->append(QLatin1String("...")); + valueIn->append(doubleQuote); + } else if (size == 0) { + *valueIn = QString(doubleQuote) + doubleQuote; + } else { + *valueIn = QLatin1String("Invalid QString"); + } + return 0; +} + +int SymbolGroupContext::dumpStdString(unsigned long index, + const QString &inameIn, + QString *valueIn) + +{ + QString errorMessage; + + // Expand string ->string_val->_bx. + if (!expandSymbol(inameIn, index, &errorMessage)) + return 1; + const unsigned long bxIndex = index + 3; + if (!expandSymbol(inameIn, bxIndex, &errorMessage)) + return 2; + // Check if size is something sane + const int sizeIndex = index + 6; + int size; + if (!getDecimalIntValue(m_symbolGroup, sizeIndex, &size)) + return 3; + if (size < 0) + return 1; + // Just copy over the value of the buf[]-array, which should be the string + const QChar doubleQuote = QLatin1Char('"'); + const int bufIndex = index + 4; + *valueIn = getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolValueTextWide, bufIndex); + const int quotePos = valueIn->indexOf(doubleQuote); + if (quotePos == -1) + return 1; + valueIn->remove(0, quotePos); + if (valueIn->size() > maxStringLength) { + valueIn->truncate(maxStringLength); + valueIn->append(QLatin1String("...\"")); + } + return 0; +} + +bool SymbolGroupContext::assignValue(const QString &iname, const QString &value, + QString *newValue, QString *errorMessage) +{ + if (debug) + qDebug() << Q_FUNC_INFO << '\n' << iname << value; + const NameIndexMap::const_iterator it = m_inameIndexMap.constFind(iname); + if (it == m_inameIndexMap.constEnd()) { + *errorMessage = msgSymbolNotFound(iname); + return false; + } + const unsigned long index = it.value(); + const HRESULT hr = m_symbolGroup->WriteSymbolWide(index, reinterpret_cast<PCWSTR>(value.utf16())); + if (FAILED(hr)) { + *errorMessage = QString::fromLatin1("Unable to assign '%1' to '%2': %3"). + arg(value, iname, CdbCore::msgComFailed("WriteSymbolWide", hr)); + return false; + } + if (newValue) + *newValue = getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolValueTextWide, index); + return true; +} + +} // namespace CdbCore diff --git a/src/plugins/debugger/cdb/symbolgroupcontext.h b/src/plugins/debugger/cdb/symbolgroupcontext.h new file mode 100644 index 0000000000000000000000000000000000000000..0288943f114f956bdabe9a7fc460bdb1cbf43be3 --- /dev/null +++ b/src/plugins/debugger/cdb/symbolgroupcontext.h @@ -0,0 +1,183 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 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 SYMBOLGROUPCONTEXT_H +#define SYMBOLGROUPCONTEXT_H + +#include "cdbcom.h" + +#include <QtCore/QString> +#include <QtCore/QVector> +#include <QtCore/QList> +#include <QtCore/QStringList> +#include <QtCore/QPair> +#include <QtCore/QMap> +#include <QtCore/QSet> + +namespace CdbCore { + +/* A thin wrapper around the IDebugSymbolGroup2 interface which represents + * a flat list of symbols using an index (for example, belonging to a stack + * frame). It uses the hierarchical naming convention of WatchHandler as in: + * "local" (invisible root) + * "local.string" (local class variable) + * "local.string.data" (class member) + * and maintains a mapping iname -> index. + * IDebugSymbolGroup2 can "expand" expandable symbols, inserting them into the + * flat list after their parent. + * + * Note the pecularity of IDebugSymbolGroup2 with regard to pointed to items: + * 1) A pointer to a POD (say int *) will expand to a pointed-to integer named '*'. + * 2) A pointer to a class (QString *), will expand to the class members right away, + * omitting the '*' derefenced item. That is a problem since the dumpers trigger + * only on the derefenced item, so, additional handling is required. + */ + +class SymbolGroupContext +{ + Q_DISABLE_COPY(SymbolGroupContext); +protected: + explicit SymbolGroupContext(const QString &prefix, + CIDebugSymbolGroup *symbolGroup, + CIDebugDataSpaces *dataSpaces, + const QStringList &uninitializedVariables = QStringList()); + bool init(QString *errorMessage); + +public: + virtual ~SymbolGroupContext(); + static SymbolGroupContext *create(const QString &prefix, + CIDebugSymbolGroup *symbolGroup, + CIDebugDataSpaces *dataSpaces, + const QStringList &uninitializedVariables, + QString *errorMessage); + + QString prefix() const { return m_prefix; } + int size() const { return m_symbolParameters.size(); } + + // Format a shadowed variable name/iname using a format taking two arguments: + // "x <shadowed n" + QString shadowedNameFormat() const; + void setShadowedNameFormat(const QString &); + + bool assignValue(const QString &iname, const QString &value, + QString *newValue /* = 0 */, QString *errorMessage); + + bool lookupPrefix(const QString &prefix, unsigned long *index) const; + + enum SymbolState { LeafSymbol, ExpandedSymbol, CollapsedSymbol }; + SymbolState symbolState(unsigned long index) const; + SymbolState symbolState(const QString &prefix) const; + + inline bool isExpanded(unsigned long index) const { return symbolState(index) == ExpandedSymbol; } + inline bool isExpanded(const QString &prefix) const { return symbolState(prefix) == ExpandedSymbol; } + + // Dump name/type of an entry running the internal dumpers for known types + // May expand symbols. + + enum ValueFlags { + HasChildren = 0x1, + OutOfScope = 0x2, + InternalDumperSucceeded = 0x4, + InternalDumperError = 0x8, // Hard error + InternalDumperFailed = 0x10, + InternalDumperMask = InternalDumperSucceeded|InternalDumperError|InternalDumperFailed + }; + + unsigned dumpValue(unsigned long index, QString *inameIn, QString *nameIn, ULONG64 *addrIn, + ULONG *typeIdIn, QString *typeNameIn, QString *valueIn); + + // For 32bit values (returned as dec) + static bool getDecimalIntValue(CIDebugSymbolGroup *sg, int index, int *value); + // For pointers and 64bit values (returned as hex) + static bool getUnsignedHexValue(QString stringValue, quint64 *value); + // Null-check for pointers + static bool isNullPointer(const QString &type , QString valueS); + // Symbol group values may contain template types which is not desired. + static QString removeInnerTemplateType(QString value); + + QString debugToString(bool verbose = false) const; + QString toString(); // calls dump/potentially expands + + // Filter out locale variables and arguments + inline static bool isSymbolDisplayable(const DEBUG_SYMBOL_PARAMETERS &p); + +protected: + bool getChildSymbolsPosition(const QString &prefix, + unsigned long *startPos, + unsigned long *parentId, + QString *errorMessage); + + const DEBUG_SYMBOL_PARAMETERS &symbolParameterAt(int i) const { return m_symbolParameters.at(i); } + +private: + typedef QMap<QString, unsigned long> NameIndexMap; + + void clear(); + bool expandSymbol(const QString &prefix, unsigned long index, QString *errorMessage); + void populateINameIndexMap(const QString &prefix, unsigned long parentId, unsigned long end); + QString symbolINameAt(unsigned long index) const; + inline QString formatShadowedName(const QString &name, int n) const; + + // Raw dump of an entry (without dumpers) + unsigned dumpValueRaw(unsigned long index, QString *inameIn, QString *nameIn, + ULONG64 *addrIn, ULONG *typeIdIn, QString *typeNameIn, + QString *valueIn) const; + + int dumpQString(unsigned long index, const QString &inameIn, QString *valueIn); + int dumpStdString(unsigned long index, const QString &inameIn, QString *valueIn); + + inline DEBUG_SYMBOL_PARAMETERS *symbolParameters() { return &(*m_symbolParameters.begin()); } + inline const DEBUG_SYMBOL_PARAMETERS *symbolParameters() const { return &(*m_symbolParameters.constBegin()); } + + const QString m_prefix; + const QChar m_nameDelimiter; + const QSet<QString> m_uninitializedVariables; + + CIDebugSymbolGroup *m_symbolGroup; + CIDebugDataSpaces *m_dataSpaces; + NameIndexMap m_inameIndexMap; + QVector<DEBUG_SYMBOL_PARAMETERS> m_symbolParameters; + int m_unnamedSymbolNumber; + QString m_shadowedNameFormat; +}; + +// Filter out locale variables and arguments +bool SymbolGroupContext::isSymbolDisplayable(const DEBUG_SYMBOL_PARAMETERS &p) +{ + if (p.Flags & (DEBUG_SYMBOL_IS_LOCAL|DEBUG_SYMBOL_IS_ARGUMENT)) + return true; + // Do not display static members. + if (p.Flags & DEBUG_SYMBOL_READ_ONLY) + return false; + return true; +} + +} // namespace CdbCore + +#endif // SYMBOLGROUPCONTEXT_H diff --git a/src/plugins/debugger/watchhandler.cpp b/src/plugins/debugger/watchhandler.cpp index f908e7fbd07500b05b05c3fdb63956348730f53a..f0b4458144024ee628ccf598037e3ab82f95d2a5 100644 --- a/src/plugins/debugger/watchhandler.cpp +++ b/src/plugins/debugger/watchhandler.cpp @@ -324,14 +324,19 @@ QString WatchData::msgNotInScope() return rc; } +const QString &WatchData::shadowedNameFormat() +{ + static const QString format = QCoreApplication::translate("Debugger::Internal::WatchData", "%1 <shadowed %2>"); + return format; +} + QString WatchData::shadowedName(const QString &name, int seen) { if (seen <= 0) return name; - return QCoreApplication::translate("Debugger::Internal::WatchData", "%1 <shadowed %2>").arg(name).arg(seen); + return shadowedNameFormat().arg(name, seen); } - /////////////////////////////////////////////////////////////////////// // // WatchModel diff --git a/src/plugins/debugger/watchhandler.h b/src/plugins/debugger/watchhandler.h index c033965f019f07c116da9051cb9a59b73136254f..15f4a56569bdde40085b6b52a416d45070ff0cc6 100644 --- a/src/plugins/debugger/watchhandler.h +++ b/src/plugins/debugger/watchhandler.h @@ -117,6 +117,7 @@ public: static QString msgNotInScope(); static QString shadowedName(const QString &name, int seen); + static const QString &shadowedNameFormat(); public: QByteArray iname; // internal name sth like 'local.baz.public.a' diff --git a/tests/manual/ccdb/cdbapplication.cpp b/tests/manual/ccdb/cdbapplication.cpp index 8ae44c2c17b8203680b1fc1086e11dd16d3707f3..919e65a8c2e6e0948376a9f684495f52ecec961c 100644 --- a/tests/manual/ccdb/cdbapplication.cpp +++ b/tests/manual/ccdb/cdbapplication.cpp @@ -32,6 +32,8 @@ #include "cdbdebugoutput.h" #include "cdbpromptthread.h" #include "debugeventcallback.h" +#include "symbolgroupcontext.h" +#include "stacktracecontext.h" #include <QtCore/QStringList> #include <QtCore/QTimer> @@ -149,6 +151,45 @@ void CdbApplication::asyncCommand(int command, const QString &arg) } } +void CdbApplication::printFrame(const QString &arg) +{ + QString errorMessage; + do { + if (m_stackTrace.isNull()) { + errorMessage = QLatin1String("No trace."); + break; + } + bool ok; + const int frame = arg.toInt(&ok); + if (!ok || frame < 0 || frame >= m_stackTrace->frameCount()) { + errorMessage = QLatin1String("Invalid or out of range."); + break; + } + CdbCore::SymbolGroupContext *ctx = m_stackTrace->symbolGroupContextAt(frame, &errorMessage); + if (!ctx) + break; + printf("%s\n", qPrintable(ctx->toString())); + } while (false); + if (!errorMessage.isEmpty()) + printf("%s\n", qPrintable(errorMessage)); +} + +bool CdbApplication::queueBreakPoint(const QString &arg) +{ + // Queue file:line + const int cpos = arg.lastIndexOf(QLatin1Char(':')); + if (cpos == -1) + return false; + CdbCore::BreakPoint bp; + bp.fileName = arg.left(cpos); + bool ok; + bp.lineNumber = arg.mid(cpos + 1).toInt(&ok); + if (!ok || bp.lineNumber < 1) + return false; + m_queuedBreakPoints.push_back(bp); + return true; +} + void CdbApplication::syncCommand(int command, const QString &arg) { QString errorMessage; @@ -163,6 +204,34 @@ void CdbApplication::syncCommand(int command, const QString &arg) } } break; + case Sync_Queue: { + const QString targs = arg.trimmed(); + if (targs.isEmpty()) { + std::printf("Queue cleared\n"); + m_queuedCommands.clear(); + } else { + std::printf("Queueing %s\n", qPrintable(targs)); + m_queuedCommands.push_back(targs); + } + } + break; + case Sync_QueueBreakPoint: { + const QString targs = arg.trimmed(); + if (targs.isEmpty()) { + std::printf("Breakpoint queue cleared\n"); + m_queuedBreakPoints.clear(); + } else { + if (queueBreakPoint(targs)) { + std::printf("Queueing breakpoint %s\n", qPrintable(targs)); + } else { + std::printf("BREAKPOINT SYNTAX ERROR: %s\n", qPrintable(targs)); + } + } + } + break; + case Sync_PrintFrame: + printFrame(arg); + break; case Unknown: if (!m_engine->executeDebuggerCommand(arg, &errorMessage)) std::printf("%s\n", qPrintable(errorMessage)); @@ -196,6 +265,7 @@ void CdbApplication::executionCommand(int command, const QString &arg) } if (ok) { m_engine->startWatchTimer(); + m_stackTrace.reset(); } else { std::fprintf(stderr, "%s\n", qPrintable(errorMessage)); } @@ -203,11 +273,50 @@ void CdbApplication::executionCommand(int command, const QString &arg) void CdbApplication::debugEvent() { + QString errorMessage; std::printf("Debug event\n"); + + CdbCore::StackTraceContext::ThreadIdFrameMap threads; + ULONG currentThreadId; + if (CdbCore::StackTraceContext::getThreads(m_engine->interfaces(), &threads, + ¤tThreadId, &errorMessage)) { + printf("%s\n", qPrintable(CdbCore::StackTraceContext::formatThreads(threads))); + } else { + std::fprintf(stderr, "%s\n", qPrintable(errorMessage)); + } + + CdbCore::StackTraceContext *trace = + CdbCore::StackTraceContext::create(&m_engine->interfaces(), + 0xFFFF, &errorMessage); + if (trace) { + m_stackTrace.reset(trace); + printf("%s\n", qPrintable(m_stackTrace->toString())); + } else { + std::fprintf(stderr, "%s\n", qPrintable(errorMessage)); + } } void CdbApplication::processAttached(void *handle) { - std::printf("pe\n"); + std::printf("### Process attached\n"); m_processHandle = handle; + QString errorMessage; + // Commands + foreach(const QString &qc, m_queuedCommands) { + if (m_engine->executeDebuggerCommand(qc, &errorMessage)) { + std::printf("'%s' [ok]\n", qPrintable(qc)); + } else { + std::printf("%s\n", qPrintable(errorMessage)); + } + } + // Breakpoints + foreach(const CdbCore::BreakPoint &bp, m_queuedBreakPoints) { + if (bp.add(m_engine->interfaces().debugControl, &errorMessage)) { + std::printf("'%s' [ok]\n", qPrintable(bp.expression())); + } else { + std::fprintf(stderr, "%s: %s\n", + qPrintable(bp.expression()), + qPrintable(errorMessage)); + } + } } diff --git a/tests/manual/ccdb/cdbapplication.h b/tests/manual/ccdb/cdbapplication.h index 5ee35b24c253bb9c8808a8ac4091ed447a8c879d..9a360d43488111d3bcbcb8d7f0a18646020dee05 100644 --- a/tests/manual/ccdb/cdbapplication.h +++ b/tests/manual/ccdb/cdbapplication.h @@ -30,11 +30,16 @@ #ifndef CDBAPPLICATION_H #define CDBAPPLICATION_H +#include "breakpoint.h" + #include <QtCore/QCoreApplication> #include <QtCore/QSharedPointer> +#include <QtCore/QScopedPointer> +#include <QtCore/QStringList> namespace CdbCore { class CoreEngine; + class StackTraceContext; } class CdbPromptThread; @@ -61,10 +66,16 @@ private slots: private: bool parseOptions(); + void printFrame(const QString &arg); + bool queueBreakPoint(const QString &arg); QString m_engineDll; QSharedPointer<CdbCore::CoreEngine> m_engine; + QScopedPointer<CdbCore::StackTraceContext> m_stackTrace; CdbPromptThread *m_promptThread; + QStringList m_queuedCommands; + QList<CdbCore::BreakPoint> m_queuedBreakPoints; + void *m_processHandle; }; diff --git a/tests/manual/ccdb/cdbpromptthread.cpp b/tests/manual/ccdb/cdbpromptthread.cpp index 07e7bc31a48092d7a82ccebc67203913723756cd..c9359aa5b69b6e4b26d3a56618460f7c077dd978 100644 --- a/tests/manual/ccdb/cdbpromptthread.cpp +++ b/tests/manual/ccdb/cdbpromptthread.cpp @@ -40,6 +40,11 @@ static const char help[] = "S binary args Start binary\n" "I Interrupt\n" "G Go\n" +"Q cmd Queue command for execution in AttachProcess\n" +"Q Clear command queue\n" +"B file:line Queue a breakpoint for adding in AttachProcess\n" +"B Clear breakpoint queue\n" +"F <n> Print stack frame <n>, 0 being top\n" "\nThe remaining commands are passed to CDB.\n"; CdbPromptThread::CdbPromptThread(QObject *parent) : @@ -75,12 +80,18 @@ static Command evaluateCommand(const QString &cmdToken) switch(cmdToken.at(0).toAscii()) { case 'I': return Async_Interrupt; + case 'Q': + return Sync_Queue; + case 'B': + return Sync_QueueBreakPoint; case 'E': return Sync_EvalExpression; case 'G': return Execution_Go; case 'S': return Execution_StartBinary; + case 'F': + return Sync_PrintFrame; default: break; } diff --git a/tests/manual/ccdb/cdbpromptthread.h b/tests/manual/ccdb/cdbpromptthread.h index 09df8452a597f334bfaed1b20bb7995858ce7854..48a7679a34249a1156523413f1356cd34a56eefb 100644 --- a/tests/manual/ccdb/cdbpromptthread.h +++ b/tests/manual/ccdb/cdbpromptthread.h @@ -47,6 +47,9 @@ enum Command { UnknownCommand = 0, Async_Interrupt = AsyncCommand|1, Sync_EvalExpression = SyncCommand|1, + Sync_Queue = SyncCommand|2, + Sync_QueueBreakPoint = SyncCommand|3, + Sync_PrintFrame = SyncCommand|4, Execution_Go = ExecutionCommand|1, Execution_StartBinary = ExecutionCommand|2 };