forked from qt-creator/qt-creator
CDB: Extract SymbolGroupContext class into Core library.
Split for testing/scripting purposes.
This commit is contained in:
493
src/plugins/debugger/cdb/breakpoint.cpp
Normal file
493
src/plugins/debugger/cdb/breakpoint.cpp
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
99
src/plugins/debugger/cdb/breakpoint.h
Normal file
99
src/plugins/debugger/cdb/breakpoint.h
Normal file
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
enum { debugBP = 0 };
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -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
|
||||
|
||||
// Convert breakpoint structs
|
||||
inline CdbCore::BreakPoint breakPointFromBreakPointData(const Debugger::Internal::BreakpointData &bpd)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
namespace Debugger {
|
||||
namespace Internal {
|
||||
|
||||
class BreakHandler;
|
||||
class BreakpointData;
|
||||
// Synchronize (halted) engine with BreakHandler.
|
||||
bool synchronizeBreakPoints(CIDebugControl* ctl, CIDebugSymbols *syms,
|
||||
BreakHandler *bh,
|
||||
QString *errorMessage, QStringList *warnings);
|
||||
|
||||
/* CDB Break point data structure with utilities to
|
||||
* apply to engine and to retrieve them from the engine and comparison. */
|
||||
|
||||
struct CDBBreakPoint
|
||||
{
|
||||
CDBBreakPoint();
|
||||
CDBBreakPoint(const BreakpointData &bpd);
|
||||
|
||||
int compare(const CDBBreakPoint& rhs) const;
|
||||
|
||||
void clear();
|
||||
void clearExpressionData();
|
||||
|
||||
QString expression() const;
|
||||
|
||||
// Apply parameters
|
||||
bool apply(IDebugBreakpoint2 *ibp, QString *errorMessage) const;
|
||||
// Convenience to add to a IDebugControl4
|
||||
bool add(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;
|
||||
};
|
||||
|
||||
QDebug operator<<(QDebug, const CDBBreakPoint &bp);
|
||||
|
||||
inline bool operator==(const CDBBreakPoint& b1, const CDBBreakPoint& b2)
|
||||
{ return b1.compare(b2) == 0; }
|
||||
inline bool operator!=(const CDBBreakPoint& b1, const CDBBreakPoint& b2)
|
||||
{ return b1.compare(b2) != 0; }
|
||||
} // namespace Internal
|
||||
} // namespace Debugger
|
||||
|
||||
|
||||
@@ -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=""
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
CdbCore::SymbolGroupContext
|
||||
*CdbStackTraceContext::createSymbolGroup(const CdbCore::ComInterfaces & /* cif */,
|
||||
int index,
|
||||
const QString &prefix,
|
||||
CIDebugSymbolGroup *comSymbolGroup,
|
||||
QString *errorMessage)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
return static_cast<CdbSymbolGroupContext *>(symbolGroupContextAt(index, errorMessage));
|
||||
}
|
||||
|
||||
QString CdbStackTraceContext::toString() const
|
||||
QList<StackFrame> CdbStackTraceContext::stackFrames() const
|
||||
{
|
||||
QString rc;
|
||||
QTextStream str(&rc);
|
||||
format(str);
|
||||
// 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 rc;
|
||||
}
|
||||
|
||||
void CdbStackTraceContext::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++) {
|
||||
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"
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline QString msgGetThreadsFailed(const QString &why)
|
||||
{
|
||||
return QString::fromLatin1("Unable to determine the thread information: %1").arg(why);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
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;
|
||||
CdbSymbolGroupContext *cdbSymbolGroupContextAt(int index, QString *errorMessage);
|
||||
|
||||
// Top-Level instruction offset for disassembler
|
||||
ULONG64 instructionOffset() const { return m_instructionOffset; }
|
||||
QList<StackFrame> stackFrames() const;
|
||||
|
||||
CdbStackFrameContext *frameContextAt(int index, QString *errorMessage);
|
||||
|
||||
// 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);
|
||||
|
||||
protected:
|
||||
virtual CdbCore::SymbolGroupContext *createSymbolGroup(const CdbCore::ComInterfaces &cif,
|
||||
int index,
|
||||
const QString &prefix,
|
||||
CIDebugSymbolGroup *comSymbolGroup,
|
||||
QString *errorMessage);
|
||||
|
||||
private:
|
||||
bool init(unsigned long frameCount, QString *errorMessage);
|
||||
CIDebugSymbolGroup *createSymbolGroup(int index, QString *errorMessage);
|
||||
|
||||
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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 };
|
||||
|
||||
QString prefix() const { return m_prefix; }
|
||||
static CdbSymbolGroupContext *create(const QString &prefix,
|
||||
CIDebugSymbolGroup *symbolGroup,
|
||||
const QSharedPointer<CdbDumperHelper> &dumper,
|
||||
const QStringList &uninitializedVariables,
|
||||
QString *errorMessage);
|
||||
|
||||
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:
|
||||
// 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);
|
||||
bool getDumpChildSymbols(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);
|
||||
template <class OutputIterator, class RecursionPredicate, class IgnorePredicate>
|
||||
static bool insertSymbolRecursion(WatchData wd,
|
||||
const CdbSymbolGroupRecursionContext &ctx,
|
||||
OutputIterator it,
|
||||
RecursionPredicate recursionPredicate,
|
||||
IgnorePredicate ignorePredicate,
|
||||
QString *errorMessage);
|
||||
|
||||
bool lookupPrefix(const QString &prefix, unsigned long *index) const;
|
||||
unsigned watchDataAt(unsigned long index, WatchData *);
|
||||
|
||||
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
|
||||
enum DumperResult { DumperOk, DumperError, DumperNotHandled };
|
||||
DumperResult dump(CIDebugDataSpaces *ds, WatchData *wd);
|
||||
|
||||
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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
412
src/plugins/debugger/cdb/stacktracecontext.cpp
Normal file
412
src/plugins/debugger/cdb/stacktracecontext.cpp
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
162
src/plugins/debugger/cdb/stacktracecontext.h
Normal file
162
src/plugins/debugger/cdb/stacktracecontext.h
Normal file
@@ -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
|
||||
740
src/plugins/debugger/cdb/symbolgroupcontext.cpp
Normal file
740
src/plugins/debugger/cdb/symbolgroupcontext.cpp
Normal file
@@ -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
|
||||
183
src/plugins/debugger/cdb/symbolgroupcontext.h
Normal file
183
src/plugins/debugger/cdb/symbolgroupcontext.h
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user