Debugger: Add memory views.

Add a separate memory view tool window available
from the context menus of:

Locals view:
   If the debugger provides size information, colors the
   areas of member variables for inspecting class layouts.

Registers view:
   Tracks the area pointed to by a register.

The view has a context menu allowing to open subviews
referenced by the pointer at the location using
the toolchain abi's word with/endianness.

Rubber-stamped-by: hjk
This commit is contained in:
Friedemann Kleint
2011-04-08 16:18:47 +02:00
parent 275e1434da
commit f3cc061dbe
12 changed files with 1211 additions and 31 deletions

View File

@@ -63,7 +63,8 @@ HEADERS += breakhandler.h \
debuggerruncontrolfactory.h \ debuggerruncontrolfactory.h \
debuggertooltipmanager.h \ debuggertooltipmanager.h \
debuggertoolchaincombobox.h \ debuggertoolchaincombobox.h \
debuggersourcepathmappingwidget.h debuggersourcepathmappingwidget.h \
memoryviewwidget.h
SOURCES += breakhandler.cpp \ SOURCES += breakhandler.cpp \
breakpoint.cpp \ breakpoint.cpp \
@@ -106,7 +107,8 @@ SOURCES += breakhandler.cpp \
watchdelegatewidgets.cpp \ watchdelegatewidgets.cpp \
debuggertooltipmanager.cpp \ debuggertooltipmanager.cpp \
debuggertoolchaincombobox.cpp \ debuggertoolchaincombobox.cpp \
debuggersourcepathmappingwidget.cpp debuggersourcepathmappingwidget.cpp \
memoryviewwidget.cpp
FORMS += attachexternaldialog.ui \ FORMS += attachexternaldialog.ui \
attachcoredialog.ui \ attachcoredialog.ui \

View File

@@ -1573,6 +1573,11 @@ void DebuggerEngine::openMemoryView(quint64 address)
d->m_memoryAgent.createBinEditor(address); d->m_memoryAgent.createBinEditor(address);
} }
void DebuggerEngine::addMemoryView(Internal::MemoryViewWidget *w)
{
d->m_memoryAgent.addMemoryView(w);
}
void DebuggerEngine::updateMemoryViews() void DebuggerEngine::updateMemoryViews()
{ {
d->m_memoryAgent.updateContents(); d->m_memoryAgent.updateContents();

View File

@@ -82,6 +82,7 @@ class WatchHandler;
class BreakpointParameters; class BreakpointParameters;
class QmlCppEngine; class QmlCppEngine;
class DebuggerToolTipContext; class DebuggerToolTipContext;
class MemoryViewWidget;
struct WatchUpdateFlags struct WatchUpdateFlags
{ {
@@ -157,6 +158,7 @@ public:
virtual void watchPoint(const QPoint &); virtual void watchPoint(const QPoint &);
virtual void openMemoryView(quint64 addr); virtual void openMemoryView(quint64 addr);
virtual void addMemoryView(Internal::MemoryViewWidget *w);
virtual void fetchMemory(Internal::MemoryAgent *, QObject *, virtual void fetchMemory(Internal::MemoryAgent *, QObject *,
quint64 addr, quint64 length); quint64 addr, quint64 length);
virtual void changeMemory(Internal::MemoryAgent *, QObject *, virtual void changeMemory(Internal::MemoryAgent *, QObject *,

View File

@@ -34,7 +34,9 @@
#include "memoryagent.h" #include "memoryagent.h"
#include "debuggerengine.h" #include "debuggerengine.h"
#include "debuggerstartparameters.h"
#include "debuggercore.h" #include "debuggercore.h"
#include "memoryviewwidget.h"
#include <coreplugin/coreconstants.h> #include <coreplugin/coreconstants.h>
#include <coreplugin/editormanager/editormanager.h> #include <coreplugin/editormanager/editormanager.h>
@@ -44,6 +46,9 @@
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <QtGui/QMessageBox> #include <QtGui/QMessageBox>
#include <QtGui/QMainWindow>
#include <cstring>
using namespace Core; using namespace Core;
@@ -81,6 +86,33 @@ MemoryAgent::~MemoryAgent()
EditorManager::instance()->closeEditors(editors); EditorManager::instance()->closeEditors(editors);
} }
void MemoryAgent::openMemoryView(quint64 address, quint64 length, const QPoint &pos)
{
MemoryViewWidget *w = new MemoryViewWidget(Core::ICore::instance()->mainWindow());
w->setUpdateOnInferiorStop(true);
w->move(pos);
w->requestMemory(address, length);
addMemoryView(w);
}
void MemoryAgent::addMemoryView(MemoryViewWidget *w)
{
w->setAbi(m_engine->startParameters().toolChainAbi);
connect(w, SIGNAL(memoryRequested(quint64,quint64)),
this, SLOT(updateMemoryView(quint64,quint64)));
connect(m_engine, SIGNAL(stateChanged(Debugger::DebuggerState)),
w, SLOT(engineStateChanged(Debugger::DebuggerState)));
connect(w, SIGNAL(openViewRequested(quint64,quint64,QPoint)),
this, SLOT(openMemoryView(quint64,quint64,QPoint)));
w->requestMemory();
w->show();
}
void MemoryAgent::updateMemoryView(quint64 address, quint64 length)
{
m_engine->fetchMemory(this, sender(), address, length);
}
void MemoryAgent::createBinEditor(quint64 addr) void MemoryAgent::createBinEditor(quint64 addr)
{ {
EditorManager *editorManager = EditorManager::instance(); EditorManager *editorManager = EditorManager::instance();
@@ -133,11 +165,16 @@ void MemoryAgent::fetchLazyData(IEditor *editor, quint64 block)
void MemoryAgent::addLazyData(QObject *editorToken, quint64 addr, void MemoryAgent::addLazyData(QObject *editorToken, quint64 addr,
const QByteArray &ba) const QByteArray &ba)
{ {
IEditor *editor = qobject_cast<IEditor *>(editorToken);
if (editor && editor->widget()) { if (IEditor *editor = qobject_cast<IEditor *>(editorToken)) {
QMetaObject::invokeMethod(editor->widget(), "addData", if (QWidget *editorWidget = editor->widget()) {
QMetaObject::invokeMethod(editorWidget , "addData",
Q_ARG(quint64, addr / BinBlockSize), Q_ARG(QByteArray, ba)); Q_ARG(quint64, addr / BinBlockSize), Q_ARG(QByteArray, ba));
} }
return;
}
if (MemoryViewWidget *mvw = qobject_cast<MemoryViewWidget*>(editorToken))
mvw->setData(ba);
} }
void MemoryAgent::provideNewRange(IEditor *editor, quint64 address) void MemoryAgent::provideNewRange(IEditor *editor, quint64 address)
@@ -185,5 +222,42 @@ bool MemoryAgent::hasVisibleEditor() const
return false; return false;
} }
bool MemoryAgent::isBigEndian(const ProjectExplorer::Abi &a)
{
switch (a.architecture()) {
case ProjectExplorer::Abi::UnknownArchitecture:
case ProjectExplorer::Abi::X86Architecture:
case ProjectExplorer::Abi::ItaniumArchitecture: // Configureable
case ProjectExplorer::Abi::ArmArchitecture: // Configureable
break;
case ProjectExplorer::Abi::MipsArcitecture: // Configureable
case ProjectExplorer::Abi::PowerPCArchitecture: // Configureable
return true;
}
return false;
}
// Read a POD variable from a memory location. Swap bytes if endianness differs
template <class POD> POD readPod(const unsigned char *data, bool swapByteOrder)
{
POD pod = 0;
if (swapByteOrder) {
unsigned char *target = reinterpret_cast<unsigned char *>(&pod) + sizeof(POD) - 1;
for (size_t i = 0; i < sizeof(POD); i++)
*target-- = data[i];
} else {
std::memcpy(&pod, data, sizeof(POD));
}
return pod;
}
// Read memory from debuggee
quint64 MemoryAgent::readInferiorPointerValue(const unsigned char *data, const ProjectExplorer::Abi &a)
{
const bool swapByteOrder = isBigEndian(a) != isBigEndian(ProjectExplorer::Abi::hostAbi());
return a.wordWidth() == 32 ? readPod<quint32>(data, swapByteOrder) :
readPod<quint64>(data, swapByteOrder);
}
} // namespace Internal } // namespace Internal
} // namespace Debugger } // namespace Debugger

View File

@@ -37,15 +37,22 @@
#include <QtCore/QObject> #include <QtCore/QObject>
#include <QtCore/QPointer> #include <QtCore/QPointer>
QT_FORWARD_DECLARE_CLASS(QPoint)
namespace Core { namespace Core {
class IEditor; class IEditor;
} }
namespace ProjectExplorer {
class Abi;
}
namespace Debugger { namespace Debugger {
class DebuggerEngine; class DebuggerEngine;
namespace Internal { namespace Internal {
class MemoryViewWidget;
class MemoryAgent : public QObject class MemoryAgent : public QObject
{ {
@@ -58,9 +65,14 @@ public:
enum { BinBlockSize = 1024 }; enum { BinBlockSize = 1024 };
bool hasVisibleEditor() const; bool hasVisibleEditor() const;
static bool isBigEndian(const ProjectExplorer::Abi &a);
static quint64 readInferiorPointerValue(const unsigned char *data, const ProjectExplorer::Abi &a);
public slots: public slots:
// Called by engine to create a new view. // Called by engine to create a new view.
void createBinEditor(quint64 startAddr); void createBinEditor(quint64 startAddr);
// Called by engine to create a tooltip.
void addMemoryView(MemoryViewWidget *w);
// Called by engine to trigger update of contents. // Called by engine to trigger update of contents.
void updateContents(); void updateContents();
// Called by engine to pass updated contents. // Called by engine to pass updated contents.
@@ -73,6 +85,8 @@ private slots:
void handleEndOfFileRequested(Core::IEditor *editor); void handleEndOfFileRequested(Core::IEditor *editor);
void handleDataChanged(Core::IEditor *editor, quint64 address, void handleDataChanged(Core::IEditor *editor, quint64 address,
const QByteArray &data); const QByteArray &data);
void updateMemoryView(quint64 address, quint64 length);
void openMemoryView(quint64 address, quint64 length, const QPoint &pos);
private: private:
QList<QPointer<Core::IEditor> > m_editors; QList<QPointer<Core::IEditor> > m_editors;

View File

@@ -0,0 +1,666 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** No Commercial Usage
**
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** 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.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**************************************************************************/
#include "memoryviewwidget.h"
#include "memoryagent.h"
#include "registerhandler.h"
#include <coreplugin/coreconstants.h>
#include <texteditor/fontsettings.h>
#include <QtGui/QLabel>
#include <QtGui/QVBoxLayout>
#include <QtGui/QPlainTextEdit>
#include <QtGui/QScrollBar>
#include <QtGui/QToolButton>
#include <QtGui/QToolBar>
#include <QtGui/QTextCursor>
#include <QtGui/QTextBlock>
#include <QtGui/QTextDocument>
#include <QtGui/QIcon>
#include <QtGui/QFont>
#include <QtGui/QFontMetrics>
#include <QtGui/QMenu>
#include <QtCore/QTextStream>
#include <QtCore/QDebug>
#include <QtCore/QVariant>
#include <cctype>
enum { debug = 0 };
// Formatting: 'aaaa:aaaa 0f ab... ASC..'
enum
{
bytesPerLine = 16,
lineWidth = 11 + 4 * bytesPerLine
};
namespace Debugger {
namespace Internal {
/*!
\class Debugger::Internal::MemoryViewWidget
\brief Base class for memory view tool windows
Small tool-window that stays on top and displays a chunk of memory.
Provides next/previous browsing.
Constructed by passing an instance to \c DebuggerEngine::addMemoryView()
which will pass it on to \c Debugger::Internal::MemoryAgent::addMemoryView()
to set up the signal connections to the engine.
Provides API for marking text with a special format/color.
The formatting is stored as a list of struct MemoryViewWidget::Markup and applied
by converting into extra selections once data arrives in setData().
Provides a context menu that offers to open a subview from a pointer value
obtained from the memory shown (converted using the Abi).
\sa Debugger::Internal::MemoryAgent, Debugger::DebuggerEngine
\sa ProjectExplorer::Abi
*/
const quint64 MemoryViewWidget::defaultLength = 128;
MemoryViewWidget::MemoryViewWidget(QWidget *parent) :
QWidget(parent, Qt::Tool|Qt::WindowStaysOnTopHint),
m_previousButton(new QToolButton),
m_nextButton(new QToolButton),
m_textEdit(new QPlainTextEdit),
m_content(new QLabel),
m_address(0),
m_length(0),
m_requestedAddress(0),
m_requestedLength(0),
m_updateOnInferiorStop(false)
{
setAttribute(Qt::WA_DeleteOnClose);
QVBoxLayout *layout = new QVBoxLayout(this);
QToolBar *toolBar = new QToolBar;
toolBar->setObjectName(QLatin1String("MemoryViewWidgetToolBar"));
toolBar->setProperty("_q_custom_style_disabled", QVariant(true));
toolBar->setIconSize(QSize(16, 16));
m_previousButton->setIcon(QIcon(QLatin1String(Core::Constants::ICON_PREV)));
connect(m_previousButton, SIGNAL(clicked()), this, SLOT(slotPrevious()));
toolBar->addWidget(m_previousButton);
m_nextButton->setIcon(QIcon(QLatin1String(Core::Constants::ICON_NEXT)));
connect(m_nextButton, SIGNAL(clicked()), this, SLOT(slotNext()));
toolBar->addWidget(m_nextButton);
layout->addWidget(toolBar);
m_textEdit->setObjectName(QLatin1String("MemoryViewWidgetTextEdit"));
m_textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
m_textEdit->setReadOnly(true);
m_textEdit->setWordWrapMode(QTextOption::NoWrap);
m_textEdit->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_textEdit, SIGNAL(customContextMenuRequested(QPoint)),
this, SLOT(slotContextMenuRequested(QPoint)));
// Text: Pick a fixed font and set minimum size to accommodate default length with vertical scrolling
const QFont fixedFont(TextEditor::FontSettings::defaultFixedFontFamily(), TextEditor::FontSettings::defaultFontSize());
const QFontMetrics metrics(fixedFont);
const QSize lineSize = metrics.size(Qt::TextSingleLine , QString(lineWidth, QLatin1Char('0')));
int defaultLineCount = defaultLength / bytesPerLine;
if (defaultLength % bytesPerLine)
defaultLineCount++;
const QSize textSize(lineSize.width() + m_textEdit->verticalScrollBar()->width() + 10,
lineSize.height() * defaultLineCount + 10);
m_textEdit->setFont(fixedFont);
m_textEdit->setMinimumSize(textSize);
m_textEdit->installEventFilter(this);
layout->addWidget(m_textEdit);
}
void MemoryViewWidget::engineStateChanged(Debugger::DebuggerState s)
{
switch (s) {
case Debugger::InferiorUnrunnable:
setBrowsingEnabled(true);
break;
case Debugger::InferiorStopOk:
setBrowsingEnabled(true);
if (m_updateOnInferiorStop)
requestMemory();
break;
case Debugger::DebuggerFinished:
close();
break;
default:
setBrowsingEnabled(false);
break;
}
}
void MemoryViewWidget::setBrowsingEnabled(bool b)
{
m_previousButton->setEnabled(b && m_address >= m_length);
m_nextButton->setEnabled(b);
}
void MemoryViewWidget::clear()
{
m_data.clear();
m_textEdit->setExtraSelections(QList<QTextEdit::ExtraSelection>());
m_textEdit->setPlainText(tr("No data available."));
setBrowsingEnabled(false);
updateTitle();
}
void MemoryViewWidget::requestMemory()
{
requestMemory(m_address, m_length);
}
void MemoryViewWidget::requestMemory(quint64 address, quint64 length)
{
m_requestedAddress = address;
m_requestedLength = length;
// For RegisterMemoryViewWidget, the register values sometimes switch to 0
// while stepping, handle gracefully.
if (!address || !length) {
m_address = address;
m_length = length;
clear();
return;
}
// Is this the first request and no data available yet? -> Set initial state.
if (m_data.isEmpty() && !m_address && !m_length) {
m_address = address;
m_length = length;
updateTitle();
m_textEdit->setPlainText(tr("Fetching %1 bytes...").arg(length));
setBrowsingEnabled(false);
}
if (debug)
qDebug() << this << "requestMemory()" << m_requestedAddress << m_requestedLength
<< " currently at: " << m_address << m_length;
emit memoryRequested(m_requestedAddress, m_requestedLength);
}
void MemoryViewWidget::setTitle(const QString &t)
{
setWindowTitle(t);
}
void MemoryViewWidget::slotNext()
{
requestMemory(m_address + m_length, m_length);
}
void MemoryViewWidget::slotPrevious()
{
if (m_address >= m_length)
requestMemory(m_address - m_length, m_length);
}
// Convert address to line and column in range 0..(n - 1), return false
// if out of range.
bool MemoryViewWidget::addressToLineColumn(quint64 posAddress,
int *lineIn /* = 0 */, int *columnIn /* = 0 */,
quint64 *lineStartIn /* = 0 */) const
{
if (posAddress < m_address)
return false;
const quint64 offset = posAddress - m_address;
if (offset >= quint64(m_data.size()))
return false;
const quint64 line = offset / bytesPerLine;
const quint64 lineStart = m_address + line * bytesPerLine;
if (lineStartIn)
*lineStartIn = lineStart;
if (lineIn)
*lineIn = int(line);
const int column = 3 * int(offset % bytesPerLine) + 10;
if (columnIn)
*columnIn = column;
if (debug)
qDebug() << this << "at" << m_address << " addressToLineColumn "
<< posAddress << "->" << line << ',' << column << " lineAt" << lineStart;
return true;
}
// Return address at position
quint64 MemoryViewWidget::addressAt(const QPoint &textPos) const
{
QTextCursor cursor = m_textEdit->cursorForPosition(textPos);
if (cursor.isNull())
return 0;
const int line = cursor.blockNumber();
const int column = cursor.columnNumber() - 1;
const quint64 lineAddress = m_address + line * bytesPerLine;
const int byte = (qMax(0, column - 9)) / 3;
if (byte >= bytesPerLine) // Within ASC part
return 0;
return lineAddress + byte;
}
void MemoryViewWidget::slotContextMenuRequested(const QPoint &pos)
{
QMenu *menu = m_textEdit->createStandardContextMenu();
menu->addSeparator();
// Add action offering to open a sub-view with a pointer read from the memory
// at the location: Dereference the chunk of memory as pointer address.
QAction *derefPointerAction = 0;
quint64 pointerValue = 0;
if (!m_data.isEmpty()) {
const quint64 pointerSize = m_abi.wordWidth() / 8;
quint64 contextAddress = addressAt(pos);
if (const quint64 remainder = contextAddress % pointerSize) // Pad pointer location.
contextAddress -= remainder;
// Dereference pointer from location
if (contextAddress) {
const quint64 dataOffset = contextAddress - address();
if (pointerSize && (dataOffset + pointerSize) <= quint64(m_data.size())) {
const unsigned char *data = reinterpret_cast<const unsigned char *>(m_data.constData() + dataOffset);
pointerValue = MemoryAgent::readInferiorPointerValue(data, m_abi);
}
}
} // has data
if (pointerValue) {
const QString msg = tr("Open Memory View at Pointer Value 0x%1")
.arg(pointerValue, 0, 16);
derefPointerAction = menu->addAction(msg);
} else {
derefPointerAction = menu->addAction("Open Memory View at Pointer Value");
derefPointerAction->setEnabled(false);
}
const QPoint globalPos = m_textEdit->mapToGlobal(pos);
QAction *action = menu->exec(globalPos);
if (!action)
return;
if (action == derefPointerAction) {
emit openViewRequested(pointerValue, MemoryViewWidget::defaultLength, globalPos);
return;
}
}
// Format address as in binary editor '0000:00AB' onto a stream set up for hex output.
static inline void formatAddressToHexStream(QTextStream &hexStream, quint64 address)
{
hexStream.setFieldWidth(4);
hexStream << (address >> 32);
hexStream.setFieldWidth(1);
hexStream << ':';
hexStream.setFieldWidth(4);
hexStream << (address & 0xFFFF);
}
// Return formatted address for window title: Prefix + binary editor format: '0x0000:00AB'
static inline QString formattedAddress(quint64 a)
{
QString rc = QLatin1String("0x");
QTextStream str(&rc);
str.setIntegerBase(16);
str.setPadChar(QLatin1Char('0'));
formatAddressToHexStream(str, a);
return rc;
}
// Format data as in binary editor '0000:00AB 0A A3 ..ccc'
QString MemoryViewWidget::formatData(quint64 startAddress, const QByteArray &data)
{
QString rc;
rc.reserve(5 * data.size());
const quint64 endAddress = startAddress + data.size();
QTextStream str(&rc);
str.setIntegerBase(16);
str.setPadChar(QLatin1Char('0'));
str.setFieldAlignment(QTextStream::AlignRight);
for (quint64 address = startAddress; address < endAddress; address += 16) {
formatAddressToHexStream(str, address);
// Format hex bytes
const int dataStart = int(address - startAddress);
const int dataEnd = qMin(dataStart + int(bytesPerLine), data.size());
for (int i = dataStart; i < dataEnd; i++) {
str.setFieldWidth(1);
str << ' ';
str.setFieldWidth(2);
const char c = data.at(i);
unsigned char uc = c;
str << unsigned(uc);
}
// Pad for character part
str.setFieldWidth(1);
if (const int remainder = int(bytesPerLine) - (dataEnd - dataStart))
str << QString(3 * remainder, QLatin1Char(' '));
// Format characters
str << ' ';
for (int i = dataStart; i < dataEnd; i++) {
const char c = data.at(i);
str << (c >= 0 && std::isprint(c) ? c : '.'); // MSVC has an assert on c>=0.
}
str << '\n';
}
return rc;
}
void MemoryViewWidget::updateTitle()
{
const QString title = tr("Memory at %1").arg(formattedAddress(address()));
setTitle(title);
}
// Convert an markup range into a list of selections for the bytes,
// resulting in a rectangular selection in the bytes area (provided range
// is within data available).
bool MemoryViewWidget::markUpToSelections(const Markup &r,
QList<QTextEdit::ExtraSelection> *extraSelections) const
{
// Fully covered?
if (r.address < m_address)
return false;
const quint64 rangeEnd = r.address + r.size;
if (rangeEnd > (m_address + quint64(m_data.size())))
return false;
QTextCursor cursor = m_textEdit->textCursor();
cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor);
// Goto first position
int line;
int column;
quint64 lineStartAddress;
if (!addressToLineColumn(r.address, &line, &column, &lineStartAddress))
return false;
if (line)
cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, line);
if (column)
cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, column - 1);
quint64 current = r.address;
// Mark rectangular area in the bytes section
while (true) {
// Mark in current line
quint64 nextLineAddress = lineStartAddress + bytesPerLine;
const int numberOfCharsToMark = 3 * int(qMin(nextLineAddress, rangeEnd) - current);
cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, numberOfCharsToMark);
QTextEdit::ExtraSelection sel;
sel.cursor = cursor;
sel.format = r.format;
extraSelections->push_back(sel);
if (nextLineAddress >= rangeEnd)
break;
// Goto beginning of next line, past address.
cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor);
cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 9);
lineStartAddress += bytesPerLine;
current = lineStartAddress;
}
return true;
}
void MemoryViewWidget::clearMarkup()
{
m_markup.clear();
m_textEdit->setExtraSelections(QList<QTextEdit::ExtraSelection>());
}
void MemoryViewWidget::addMarkup(quint64 begin, quint64 size,
const QTextCharFormat &fmt, const QString &toolTip)
{
m_markup.push_back(Markup(begin, size, fmt, toolTip));
}
void MemoryViewWidget::addMarkup(quint64 begin, quint64 size,
const QColor &background, const QString &toolTip)
{
QTextCharFormat format = textCharFormat();
format.setBackground(QBrush(background));
addMarkup(begin, size, format, toolTip);
}
QTextCharFormat MemoryViewWidget::textCharFormat() const
{
return m_textEdit->currentCharFormat();
}
void MemoryViewWidget::setData(const QByteArray &a)
{
if (debug)
qDebug() << this << m_requestedAddress << m_requestedLength << "received" << a.size();
if (quint64(a.size()) < m_requestedLength) {
const QString msg = QString::fromLatin1("Warning: %1 received only %2 bytes of %3 at 0x%4")
.arg(QString::fromAscii(metaObject()->className()))
.arg(a.size()).arg(m_requestedLength).arg(m_requestedAddress, 0, 16);
qWarning("%s", qPrintable(msg));
}
if (m_address != m_requestedAddress || m_length != m_requestedLength) {
m_address = m_requestedAddress;
m_length = m_requestedLength;
updateTitle();
}
if (a.isEmpty()) {
clear();
return;
}
m_data = a;
QList<QTextEdit::ExtraSelection> extra;
m_textEdit->setExtraSelections(extra);
m_textEdit->setPlainText(MemoryViewWidget::formatData(address(), a));
// Do markup which is in visible range now.
foreach (const Markup &r, m_markup)
markUpToSelections(r, &extra);
if (!extra.isEmpty())
m_textEdit->setExtraSelections(extra);
setBrowsingEnabled(true);
}
// Find markup by address.
int MemoryViewWidget::indexOfMarkup(quint64 address) const
{
const int size = m_markup.size();
for (int m = 0; m < size; m++)
if (m_markup.at(m).covers(address))
return m;
return -1;
}
bool MemoryViewWidget::eventFilter(QObject *o, QEvent *e)
{
if (o != m_textEdit || e->type() != QEvent::ToolTip)
return QWidget::eventFilter(o, e);
// ToolTip handling: is the cursor over an address range that has a tooltip
// defined in the markup list?
const QHelpEvent *he = static_cast<const QHelpEvent *>(e);
if (const quint64 toolTipAddress = addressAt(he->pos())) {
const int mIndex = indexOfMarkup(toolTipAddress);
if (mIndex != -1) {
m_textEdit->setToolTip(m_markup.at(mIndex).toolTip);
} else {
m_textEdit->setToolTip(QString());
}
}
return QWidget::eventFilter(o, e);
}
/*!
\class Debugger::Internal::LocalsMemoryViewWidget
\brief Memory view that shows the memory at the location of a local variable.
Refreshes whenever Debugger::InferiorStopOk is reported.
\sa Debugger::Internal::WatchWindow
\sa Debugger::Internal::MemoryAgent, Debugger::DebuggerEngine
*/
LocalsMemoryViewWidget::LocalsMemoryViewWidget(QWidget *parent) :
MemoryViewWidget(parent), m_variableAddress(0)
{
setUpdateOnInferiorStop(true);
}
void LocalsMemoryViewWidget::init(quint64 variableAddress, quint64 size, const QString &name)
{
m_variableAddress = variableAddress;
m_variableSize = size;
m_variableName = name;
// Size may be 0.
addMarkup(variableAddress, qMax(size, quint64(1)), Qt::lightGray);
requestMemory(m_variableAddress, qMax(size, quint64(defaultLength)));
if (debug)
qDebug() << this << "init" << variableAddress << m_variableName << m_variableSize;
}
void LocalsMemoryViewWidget::updateTitle()
{
const QString variableAddress = formattedAddress(m_variableAddress);
if (address() == m_variableAddress) {
const QString title = tr("Memory at '%1' (%2)")
.arg(m_variableName, variableAddress);
setTitle(title);
} else if (address() > m_variableAddress) {
const QString title = tr("Memory at '%1' (%2 + %3)")
.arg(m_variableName, variableAddress)
.arg(address() - m_variableAddress);
setTitle(title);
} else if (address() < m_variableAddress) {
const QString title = tr("Memory at '%1' (%2 - %3)")
.arg(m_variableName, variableAddress)
.arg(m_variableAddress - address());
setTitle(title);
}
}
/*!
\class Debugger::Internal::RegisterMemoryViewWidget
\brief Memory view that shows the memory around the contents of a register
(such as stack pointer, program counter),
tracking changes of the register value.
Connects to Debugger::Internal::RegisterHandler to listen for changes
of the register value.
\sa Debugger::Internal::RegisterHandler, Debugger::Internal::RegisterWindow
\sa Debugger::Internal::MemoryAgent, Debugger::DebuggerEngine
*/
RegisterMemoryViewWidget::Markup::Markup(quint64 a, quint64 s,
const QTextCharFormat &fmt, const QString &tt) :
address(a), size(s), format(fmt), toolTip(tt)
{
}
RegisterMemoryViewWidget::RegisterMemoryViewWidget(QWidget *parent) :
MemoryViewWidget(parent),
m_registerIndex(-1),
m_registerAddress(0),
m_offset(0)
{
setUpdateOnInferiorStop(false); // We update on register changed.
}
void RegisterMemoryViewWidget::updateTitle()
{
const quint64 shownAddress = address() + m_offset;
const QString registerAddress = formattedAddress(m_registerAddress);
if (shownAddress == m_registerAddress) {
const QString title = tr("Memory at Register '%1' (%2)")
.arg(m_registerName, registerAddress);
setTitle(title);
} else if (shownAddress > m_registerAddress) {
const QString title = tr("Memory at Register '%1' (%2 + %3)")
.arg(m_registerName, registerAddress)
.arg(shownAddress - m_registerAddress);
setTitle(title);
} else if (shownAddress < m_registerAddress) {
const QString title = tr("Memory at Register '%1' (%2 - %3)")
.arg(m_registerName, registerAddress)
.arg(m_registerAddress - shownAddress);
setTitle(title);
}
}
void RegisterMemoryViewWidget::setRegisterAddress(quint64 a)
{
if (!a) { // Registers might switch to 0 (for example, 'rsi' while stepping out).
m_offset = m_registerAddress = a;
requestMemory(0, 0);
return;
}
if (m_registerAddress == a) { // Same value: just re-fetch
requestMemory();
return;
}
// Show an area around that register
m_registerAddress = a;
const quint64 range = MemoryViewWidget::defaultLength / 2;
const quint64 end = a + range;
const quint64 begin = a >= range ? a - range : 0;
m_offset = m_registerAddress - begin;
// Mark one byte showing the register
clearMarkup();
addMarkup(m_registerAddress, 1, Qt::lightGray, tr("Register %1").arg(m_registerName));
requestMemory(begin, end - begin);
}
void RegisterMemoryViewWidget::slotRegisterSet(const QModelIndex &index)
{
if (m_registerIndex != index.row())
return;
const QVariant newAddressV = index.data(Qt::EditRole);
if (newAddressV.type() == QVariant::ULongLong) {
if (debug)
qDebug() << this << m_registerIndex << m_registerName << "slotRegisterSet" << newAddressV;
setRegisterAddress(newAddressV.toULongLong());
}
}
void RegisterMemoryViewWidget::init(int registerIndex, RegisterHandler *h)
{
m_registerIndex = registerIndex;
m_registerName = QString::fromAscii(h->registerAt(registerIndex).name);
if (debug)
qDebug() << this << "init" << registerIndex << m_registerName;
// Known issue: CDB might reset the model by changing the special
// registers it reports.
connect(h, SIGNAL(modelReset()), this, SLOT(close()));
connect(h, SIGNAL(registerSet(QModelIndex)),
this, SLOT(slotRegisterSet(QModelIndex)));
setRegisterAddress(h->registerAt(m_registerIndex).editValue().toULongLong());
}
} // namespace Internal
} // namespace Debugger

View File

@@ -0,0 +1,187 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** No Commercial Usage
**
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** 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.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**************************************************************************/
#ifndef MEMORYTOOLTIP_H
#define MEMORYTOOLTIP_H
#include "debuggerconstants.h"
#include <projectexplorer/abi.h>
#include <QtGui/QTextEdit> // QTextEdit::ExtraSelection
#include <QtCore/QList>
QT_BEGIN_NAMESPACE
class QLabel;
class QModelIndex;
class QPlainTextEdit;
class QToolButton;
class QTextCharFormat;
QT_END_NAMESPACE
namespace Debugger {
class DebuggerEngine;
namespace Internal {
class RegisterHandler;
// Documentation inside.
class MemoryViewWidget : public QWidget
{
Q_OBJECT
public:
// Address range to be marked with special format
struct Markup
{
Markup(quint64 a = 0, quint64 s = 0,
const QTextCharFormat &fmt = QTextCharFormat(),
const QString &toolTip = QString());
bool covers(quint64 a) const { return a >= address && a < (address + size); }
quint64 address;
quint64 size;
QTextCharFormat format;
QString toolTip;
};
explicit MemoryViewWidget(QWidget *parent = 0);
quint64 address() const { return m_address; }
quint64 length() const { return m_length; }
// How read an address used for 'dereference pointer at' context menu action
void setAbi(const ProjectExplorer::Abi &a) { m_abi = a; }
ProjectExplorer::Abi abi() const { return m_abi; }
bool updateOnInferiorStop() const { return m_updateOnInferiorStop; }
void setUpdateOnInferiorStop(bool v) { m_updateOnInferiorStop = v ; }
QTextCharFormat textCharFormat() const;
QList<Markup> markup() const { return m_markup; }
void setMarkup(const QList<Markup> &m) { clearMarkup(); m_markup = m; }
static QString formatData(quint64 address, const QByteArray &d);
static const quint64 defaultLength;
virtual bool eventFilter(QObject *, QEvent *);
signals:
// Fetch memory and use setData().
void memoryRequested(quint64 address, quint64 length);
// Open a (sub) view from context menu
void openViewRequested(quint64 address, quint64 length, const QPoint &pos);
public slots:
void setData(const QByteArray &a); // Set to empty to indicate non-available data
void engineStateChanged(Debugger::DebuggerState s);
void addMarkup(quint64 begin, quint64 size, const QTextCharFormat &,
const QString &toolTip = QString());
void addMarkup(quint64 begin, quint64 size, const QColor &background,
const QString &toolTip = QString());
void clear();
void clearMarkup();
void requestMemory();
void requestMemory(quint64 address, quint64 length);
protected:
virtual void updateTitle();
void setTitle(const QString &);
private slots:
void slotNext();
void slotPrevious();
void slotContextMenuRequested(const QPoint &pos);
private:
void setBrowsingEnabled(bool);
quint64 addressAt(const QPoint &textPos) const;
bool addressToLineColumn(quint64 address, int *line = 0, int *column = 0,
quint64 *lineStart = 0) const;
bool markUpToSelections(const Markup &r,
QList<QTextEdit::ExtraSelection> *extraSelections) const;
int indexOfMarkup(quint64 address) const;
QToolButton *m_previousButton;
QToolButton *m_nextButton;
QPlainTextEdit *m_textEdit;
QLabel *m_content;
quint64 m_address;
quint64 m_length;
quint64 m_requestedAddress;
quint64 m_requestedLength;
ProjectExplorer::Abi m_abi;
QByteArray m_data;
bool m_updateOnInferiorStop;
QList<Markup> m_markup;
};
class LocalsMemoryViewWidget : public MemoryViewWidget
{
Q_OBJECT
public:
explicit LocalsMemoryViewWidget(QWidget *parent = 0);
void init(quint64 variableAddress, quint64 size, const QString &name);
private:
virtual void updateTitle();
quint64 m_variableAddress;
quint64 m_variableSize;
QString m_variableName;
};
class RegisterMemoryViewWidget : public MemoryViewWidget
{
Q_OBJECT
public:
explicit RegisterMemoryViewWidget(QWidget *parent = 0);
void init(int registerIndex, RegisterHandler *h);
private slots:
void slotRegisterSet(const QModelIndex &);
private:
virtual void updateTitle();
void setRegisterAddress(quint64 a);
int m_registerIndex;
quint64 m_registerAddress;
quint64 m_offset;
QString m_registerName;
};
} // namespace Internal
} // namespace Debugger
#endif // MEMORYTOOLTIP_H

View File

@@ -175,13 +175,14 @@ void RegisterHandler::setAndMarkRegisters(const Registers &registers)
} }
const int size = m_registers.size(); const int size = m_registers.size();
for (int r = 0; r < size; r++) { for (int r = 0; r < size; r++) {
const QModelIndex regIndex = index(r, 1);
if (m_registers.at(r).value != registers.at(r).value) { if (m_registers.at(r).value != registers.at(r).value) {
// Indicate red if values change, keep changed. // Indicate red if values change, keep changed.
m_registers[r].changed = m_registers[r].changed || !m_registers.at(r).value.isEmpty(); m_registers[r].changed = m_registers[r].changed || !m_registers.at(r).value.isEmpty();
m_registers[r].value = registers.at(r).value; m_registers[r].value = registers.at(r).value;
const QModelIndex regIndex = index(r, 1);
emit dataChanged(regIndex, regIndex); emit dataChanged(regIndex, regIndex);
} }
emit registerSet(regIndex); // notify attached memory views.
} }
} }

View File

@@ -81,6 +81,9 @@ public:
Q_SLOT void setNumberBase(int base); Q_SLOT void setNumberBase(int base);
int numberBase() const { return m_base; } int numberBase() const { return m_base; }
signals:
void registerSet(const QModelIndex &r); // Register was set, for memory views
private: private:
void calculateWidth(); void calculateWidth();
int rowCount(const QModelIndex &parent = QModelIndex()) const; int rowCount(const QModelIndex &parent = QModelIndex()) const;

View File

@@ -32,7 +32,7 @@
**************************************************************************/ **************************************************************************/
#include "registerwindow.h" #include "registerwindow.h"
#include "memoryviewwidget.h"
#include "debuggeractions.h" #include "debuggeractions.h"
#include "debuggerconstants.h" #include "debuggerconstants.h"
#include "debuggercore.h" #include "debuggercore.h"
@@ -173,11 +173,7 @@ RegisterWindow::RegisterWindow(QWidget *parent)
connect(debuggerCore()->action(AlwaysAdjustRegistersColumnWidths), connect(debuggerCore()->action(AlwaysAdjustRegistersColumnWidths),
SIGNAL(toggled(bool)), SIGNAL(toggled(bool)),
SLOT(setAlwaysResizeColumnsToContents(bool))); SLOT(setAlwaysResizeColumnsToContents(bool)));
} setObjectName(QLatin1String("RegisterWindow"));
void RegisterWindow::resizeEvent(QResizeEvent *ev)
{
QTreeView::resizeEvent(ev);
} }
void RegisterWindow::contextMenuEvent(QContextMenuEvent *ev) void RegisterWindow::contextMenuEvent(QContextMenuEvent *ev)
@@ -197,16 +193,24 @@ void RegisterWindow::contextMenuEvent(QContextMenuEvent *ev)
menu.addSeparator(); menu.addSeparator();
QModelIndex idx = indexAt(ev->pos()); const QModelIndex idx = indexAt(ev->pos());
QString address = handler->registers().at(idx.row()).value; if (!idx.isValid())
QAction *actShowMemory = menu.addAction(QString()); return;
if (address.isEmpty()) { const Register &aRegister = handler->registers().at(idx.row());
actShowMemory->setText(tr("Open Memory Editor")); const QVariant addressV = aRegister.editValue();
actShowMemory->setEnabled(false); const quint64 address = addressV.type() == QVariant::ULongLong ? addressV.toULongLong() : 0;
QAction *actViewMemory = menu.addAction(QString());
QAction *actEditMemory = menu.addAction(QString());
if (address) {
const bool canShow = actionsEnabled && (engineCapabilities & ShowMemoryCapability);
actEditMemory->setText(tr("Open Memory Editor at 0x%1").arg(address, 0, 16));
actEditMemory->setEnabled(canShow);
actViewMemory->setText(tr("Open Memory View at Value of Register %1 0x%2")
.arg(QString::fromAscii(aRegister.name)).arg(address, 0, 16));
} else { } else {
actShowMemory->setText(tr("Open Memory Editor at %1").arg(address)); actEditMemory->setText(tr("Open Memory Editor"));
actShowMemory->setEnabled(actionsEnabled actViewMemory->setText(tr("Open Memory View at Value of Register"));
&& (engineCapabilities & ShowMemoryCapability)); actEditMemory->setEnabled(false);
} }
menu.addSeparator(); menu.addSeparator();
@@ -231,15 +235,21 @@ void RegisterWindow::contextMenuEvent(QContextMenuEvent *ev)
menu.addAction(debuggerCore()->action(SettingsDialog)); menu.addAction(debuggerCore()->action(SettingsDialog));
QAction *act = menu.exec(ev->globalPos()); const QPoint position = ev->globalPos();
QAction *act = menu.exec(position);
if (act == actAdjust) if (act == actAdjust)
resizeColumnsToContents(); resizeColumnsToContents();
else if (act == actReload) else if (act == actReload)
engine->reloadRegisters(); engine->reloadRegisters();
else if (act == actShowMemory) else if (act == actEditMemory)
engine->openMemoryView(address.toULongLong(0, 0)); engine->openMemoryView(address);
else if (act == act16) else if (act == actViewMemory) {
RegisterMemoryViewWidget *w = new RegisterMemoryViewWidget(this);
w->move(position);
w->init(idx.row(), handler);
engine->addMemoryView(w);
} else if (act == act16)
handler->setNumberBase(16); handler->setNumberBase(16);
else if (act == act10) else if (act == act10)
handler->setNumberBase(10); handler->setNumberBase(10);

View File

@@ -54,8 +54,7 @@ public slots:
void reloadRegisters(); void reloadRegisters();
private: private:
void resizeEvent(QResizeEvent *ev); virtual void contextMenuEvent(QContextMenuEvent *ev);
void contextMenuEvent(QContextMenuEvent *ev);
}; };
} // namespace Internal } // namespace Internal

View File

@@ -39,9 +39,11 @@
#include "debuggercore.h" #include "debuggercore.h"
#include "debuggerdialogs.h" #include "debuggerdialogs.h"
#include "debuggerengine.h" #include "debuggerengine.h"
#include "debuggerstartparameters.h"
#include "watchdelegatewidgets.h" #include "watchdelegatewidgets.h"
#include "watchhandler.h" #include "watchhandler.h"
#include "debuggertooltipmanager.h" #include "debuggertooltipmanager.h"
#include "memoryviewwidget.h"
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/savedaction.h> #include <utils/savedaction.h>
@@ -52,6 +54,7 @@
#include <QtCore/QVariant> #include <QtCore/QVariant>
#include <QtGui/QApplication> #include <QtGui/QApplication>
#include <QtGui/QPalette>
#include <QtGui/QClipboard> #include <QtGui/QClipboard>
#include <QtGui/QContextMenuEvent> #include <QtGui/QContextMenuEvent>
#include <QtGui/QHeaderView> #include <QtGui/QHeaderView>
@@ -140,6 +143,199 @@ private:
WatchWindow *m_watchWindow; WatchWindow *m_watchWindow;
}; };
// Watch model query helpers.
static inline quint64 addressOf(const QModelIndex &m)
{ return m.data(LocalsAddressRole).toULongLong(); }
static inline quint64 pointerValueOf(const QModelIndex &m)
{ return m.data(LocalsPointerValueRole).toULongLong(); }
static inline QString nameOf(const QModelIndex &m)
{ return m.data().toString(); }
static inline uint sizeOf(const QModelIndex &m)
{ return m.data(LocalsSizeRole).toUInt(); }
// Helper functionality to obtain a address-sorted list of member variables
// of a watch model index and its size. Restrict this to the size passed
// in since static members can be contained that are in different areas.
struct MemberVariable
{
MemberVariable(quint64 a = 0, uint s = 0, const QString &n = QString()) :
address(a), size(s), name(n) {}
quint64 address;
uint size;
QString name;
};
bool lessThanMV(const MemberVariable &m1, const MemberVariable &m2)
{
return m1.address < m2.address;
}
static QVector<MemberVariable> sortedMemberVariables(const QModelIndex &m,
quint64 start, quint64 end)
{
const int rowCount = m.model()->rowCount(m);
if (!rowCount)
return QVector<MemberVariable>();
QVector<MemberVariable> result;
result.reserve(rowCount);
for (int r = 0; r < rowCount; r++) {
const QModelIndex childIndex = m.child(r, 0);
const quint64 childAddress = addressOf(childIndex);
const uint childSize = sizeOf(childIndex);
if (childAddress && childAddress >= start
&& (childAddress + childSize) <= end) { // Non-static, within area?
result.push_back(MemberVariable(childAddress, childSize, nameOf(childIndex)));
}
}
qStableSort(result.begin(), result.end(), lessThanMV);
return result;
}
/*!
\fn variableMemoryMarkup()
\brief Creates markup for a variable in the memory view.
Marks the 1st order children with alternating colors in the parent, that is, for
\code
struct Foo {
char c1
char c2
int x2;
}
\endcode
create something like:
\code
0 memberColor1
1 memberColor2
2 base color (padding area of parent)
3 base color
4 member color1
...
\endcode
Fixme: When dereferencing a pointer, the size of the pointee is not
known, currently. So, we take an area of 1024 and fill the background
with the default color so that just the members are shown
(sizeIsEstimate=true). This could be fixed by passing the pointee size
as well from the debugger, but would require expensive type manipulation.
\sa Debugger::Internal::MemoryViewWidget
*/
typedef QList<MemoryViewWidget::Markup> MemoryViewWidgetMarkup;
static inline MemoryViewWidgetMarkup
variableMemoryMarkup(const QModelIndex &m, quint64 address, quint64 size,
bool sizeIsEstimate,
const QTextCharFormat &defaultFormat,
const QColor &defaultBackground)
{
enum { debug = 0 };
typedef QPair<QColor, QString> ColorNamePair;
typedef QVector<ColorNamePair> ColorNameVector;
MemoryViewWidgetMarkup result;
const QVector<MemberVariable> members = sortedMemberVariables(m, address, address + size);
// Starting out from base, create an array representing the area filled with base
// color. Fill children with alternating member colors,
// leaving the padding areas of the parent colored with the base color.
if (sizeIsEstimate && members.isEmpty())
return result; // Fixme: Exact size not known, no point in filling if no children.
const QColor baseColor = sizeIsEstimate ? defaultBackground : Qt::lightGray;
const QString name = nameOf(m);
ColorNameVector ranges(size, ColorNamePair(baseColor, name));
if (!members.isEmpty()) {
QColor memberColor1 = QColor(Qt::yellow).lighter();
QColor memberColor2 = QColor(Qt::cyan).lighter();
for (int m = 0; m < members.size(); m++) {
QColor memberColor;
if (m & 1) {
memberColor = memberColor1;
memberColor1 = memberColor1.darker(120);
} else {
memberColor = memberColor2;
memberColor2 = memberColor2.darker(120);
}
const quint64 childOffset = members.at(m).address - address;
const QString toolTip = WatchWindow::tr("%1.%2 at #%3")
.arg(name, members.at(m).name).arg(childOffset);
qFill(ranges.begin() + childOffset,
ranges.begin() + childOffset + members.at(m).size,
ColorNamePair(memberColor, toolTip));
}
}
if (debug) {
QDebug dbg = qDebug().nospace();
dbg << name << ' ' << address << ' ' << size << '\n';
foreach (const MemberVariable &mv, members)
dbg << ' ' << mv.name << ' ' << mv.address << ' ' << mv.size << '\n';
QString name;
for (unsigned i = 0; i < size; i++)
if (name != ranges.at(i).second) {
dbg << ",[" << i << ' ' << ranges.at(i).first << ' ' << ranges.at(i).second << ']';
name = ranges.at(i).second;
}
}
// Condense ranges of identical color into markup ranges.
for (unsigned i = 0; i < size; i++) {
const ColorNamePair &range = ranges.at(i);
if (result.isEmpty() || result.back().format.background().color() != range.first) {
QTextCharFormat format = defaultFormat;
format.setBackground(QBrush(range.first));
result.push_back(MemoryViewWidget::Markup(address + i, 1, format, range.second));
} else {
result.back().size++;
}
}
if (debug) {
QDebug dbg = qDebug().nospace();
dbg << name << ' ' << address << ' ' << size << '\n';
foreach (const MemberVariable &mv, members)
dbg << ' ' << mv.name << ' ' << mv.address << ' ' << mv.size << '\n';
QString name;
for (unsigned i = 0; i < size; i++)
if (name != ranges.at(i).second) {
dbg << ',' << i << ' ' << ranges.at(i).first << ' ' << ranges.at(i).second;
name = ranges.at(i).second;
}
dbg << '\n';
foreach (const MemoryViewWidget::Markup &m, result)
dbg << m.address << ' ' << m.size << ' ' << m.toolTip << '\n';
}
return result;
}
// Convenience to create a memory view of a variable.
static void addVariableMemoryView(DebuggerEngine *engine,
const QModelIndex &m, bool deferencePointer,
const QPoint &p, QWidget *parent)
{
const QColor background = parent->palette().color(QPalette::Normal, QPalette::Base);
LocalsMemoryViewWidget *w = new LocalsMemoryViewWidget(parent);
const quint64 address = deferencePointer ? pointerValueOf(m) : addressOf(m);
// Fixme: Get the size of pointee (see variableMemoryMarkup())?
// Also, gdb does not report the size yet as of 8.4.2011
const quint64 typeSize = sizeOf(m);
const bool sizeIsEstimate = deferencePointer || !typeSize;
const quint64 size = sizeIsEstimate ? 1024 : typeSize;
if (!address)
return;
const MemoryViewWidgetMarkup markup
= variableMemoryMarkup(m, address, size, sizeIsEstimate,
w->textCharFormat(), background);
w->init(address, qMax(size, LocalsMemoryViewWidget::defaultLength), nameOf(m));
w->setMarkup(markup);
w->move(p);
engine->addMemoryView(w);
}
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
// //
// WatchWindow // WatchWindow
@@ -150,6 +346,7 @@ WatchWindow::WatchWindow(Type type, QWidget *parent)
: QTreeView(parent), : QTreeView(parent),
m_type(type) m_type(type)
{ {
setObjectName(QLatin1String("WatchWindow"));
m_grabbing = false; m_grabbing = false;
setFrameStyle(QFrame::NoFrame); setFrameStyle(QFrame::NoFrame);
@@ -278,9 +475,9 @@ void WatchWindow::contextMenuEvent(QContextMenuEvent *ev)
const QModelIndex mi0 = idx.sibling(idx.row(), 0); const QModelIndex mi0 = idx.sibling(idx.row(), 0);
const QModelIndex mi1 = idx.sibling(idx.row(), 1); const QModelIndex mi1 = idx.sibling(idx.row(), 1);
const QModelIndex mi2 = idx.sibling(idx.row(), 2); const QModelIndex mi2 = idx.sibling(idx.row(), 2);
const quint64 address = mi0.data(LocalsAddressRole).toULongLong(); const quint64 address = addressOf(mi0);
const uint size = mi0.data(LocalsSizeRole).toUInt(); const uint size = sizeOf(mi0);
const quint64 pointerValue = mi0.data(LocalsPointerValueRole).toULongLong(); const quint64 pointerValue = pointerValueOf(mi0);
const QString exp = mi0.data(LocalsExpressionRole).toString(); const QString exp = mi0.data(LocalsExpressionRole).toString();
const QString type = mi2.data().toString(); const QString type = mi2.data().toString();
@@ -425,26 +622,42 @@ void WatchWindow::contextMenuEvent(QContextMenuEvent *ev)
QAction *actOpenMemoryEditAtVariableAddress = new QAction(&memoryMenu); QAction *actOpenMemoryEditAtVariableAddress = new QAction(&memoryMenu);
QAction *actOpenMemoryEditAtPointerValue = new QAction(&memoryMenu); QAction *actOpenMemoryEditAtPointerValue = new QAction(&memoryMenu);
QAction *actOpenMemoryEditor = new QAction(&memoryMenu); QAction *actOpenMemoryEditor = new QAction(&memoryMenu);
QAction *actOpenMemoryViewAtVariableAddress = new QAction(&memoryMenu);
QAction *actOpenMemoryViewAtPointerValue = new QAction(&memoryMenu);
if (engineCapabilities & ShowMemoryCapability) { if (engineCapabilities & ShowMemoryCapability) {
actOpenMemoryEditor->setText(tr("Open Memory Editor...")); actOpenMemoryEditor->setText(tr("Open Memory Editor..."));
if (address) { if (address) {
actOpenMemoryEditAtVariableAddress->setText( actOpenMemoryEditAtVariableAddress->setText(
tr("Open Memory Editor at Object's Address (0x%1)") tr("Open Memory Editor at Object's Address (0x%1)")
.arg(address, 0, 16)); .arg(address, 0, 16));
actOpenMemoryViewAtVariableAddress->setText(
tr("Open Memory View at Object's Address (0x%1)")
.arg(address, 0, 16));
} else { } else {
actOpenMemoryEditAtVariableAddress->setText( actOpenMemoryEditAtVariableAddress->setText(
tr("Open Memory Editor at Object's Address")); tr("Open Memory Editor at Object's Address"));
actOpenMemoryEditAtVariableAddress->setEnabled(false); actOpenMemoryEditAtVariableAddress->setEnabled(false);
actOpenMemoryViewAtVariableAddress->setText(
tr("Open Memory View at Object's Address"));
actOpenMemoryViewAtVariableAddress->setEnabled(false);
} }
if (createPointerActions) { if (createPointerActions) {
actOpenMemoryEditAtPointerValue->setText( actOpenMemoryEditAtPointerValue->setText(
tr("Open Memory Editor at Referenced Address (0x%1)") tr("Open Memory Editor at Referenced Address (0x%1)")
.arg(pointerValue, 0, 16)); .arg(pointerValue, 0, 16));
actOpenMemoryViewAtPointerValue->setText(
tr("Open Memory View at Referenced Address (0x%1)")
.arg(pointerValue, 0, 16));
} else { } else {
actOpenMemoryEditAtPointerValue->setText( actOpenMemoryEditAtPointerValue->setText(
tr("Open Memory Editor at Referenced Address")); tr("Open Memory Editor at Referenced Address"));
actOpenMemoryEditAtPointerValue->setEnabled(false); actOpenMemoryEditAtPointerValue->setEnabled(false);
actOpenMemoryViewAtPointerValue->setText(
tr("Open Memory View at Referenced Address"));
actOpenMemoryViewAtPointerValue->setEnabled(false);
} }
memoryMenu.addAction(actOpenMemoryViewAtVariableAddress);
memoryMenu.addAction(actOpenMemoryViewAtPointerValue);
memoryMenu.addAction(actOpenMemoryEditAtVariableAddress); memoryMenu.addAction(actOpenMemoryEditAtVariableAddress);
memoryMenu.addAction(actOpenMemoryEditAtPointerValue); memoryMenu.addAction(actOpenMemoryEditAtPointerValue);
memoryMenu.addAction(actOpenMemoryEditor); memoryMenu.addAction(actOpenMemoryEditor);
@@ -513,6 +726,10 @@ void WatchWindow::contextMenuEvent(QContextMenuEvent *ev)
AddressDialog dialog; AddressDialog dialog;
if (dialog.exec() == QDialog::Accepted) if (dialog.exec() == QDialog::Accepted)
currentEngine()->openMemoryView(dialog.address()); currentEngine()->openMemoryView(dialog.address());
} else if (act == actOpenMemoryViewAtVariableAddress) {
addVariableMemoryView(currentEngine(), mi0, false, ev->globalPos(), this);
} else if (act == actOpenMemoryViewAtPointerValue) {
addVariableMemoryView(currentEngine(), mi0, true, ev->globalPos(), this);
} else if (act == actSetWatchpointAtVariableAddress) { } else if (act == actSetWatchpointAtVariableAddress) {
setWatchpoint(address, size); setWatchpoint(address, size);
} else if (act == actSetWatchpointAtPointerValue) { } else if (act == actSetWatchpointAtPointerValue) {