From f3cc061dbe36df106f9d6e2db881de69cb16a5c2 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Fri, 8 Apr 2011 16:18:47 +0200 Subject: [PATCH] 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 --- src/plugins/debugger/debugger.pro | 6 +- src/plugins/debugger/debuggerengine.cpp | 5 + src/plugins/debugger/debuggerengine.h | 2 + src/plugins/debugger/memoryagent.cpp | 82 +++- src/plugins/debugger/memoryagent.h | 14 + src/plugins/debugger/memoryviewwidget.cpp | 666 ++++++++++++++++++++++++++++++ src/plugins/debugger/memoryviewwidget.h | 187 +++++++++ src/plugins/debugger/registerhandler.cpp | 3 +- src/plugins/debugger/registerhandler.h | 3 + src/plugins/debugger/registerwindow.cpp | 48 ++- src/plugins/debugger/registerwindow.h | 3 +- src/plugins/debugger/watchwindow.cpp | 223 +++++++++- 12 files changed, 1211 insertions(+), 31 deletions(-) create mode 100644 src/plugins/debugger/memoryviewwidget.cpp create mode 100644 src/plugins/debugger/memoryviewwidget.h diff --git a/src/plugins/debugger/debugger.pro b/src/plugins/debugger/debugger.pro index 7667804352..e77b89d19a 100644 --- a/src/plugins/debugger/debugger.pro +++ b/src/plugins/debugger/debugger.pro @@ -63,7 +63,8 @@ HEADERS += breakhandler.h \ debuggerruncontrolfactory.h \ debuggertooltipmanager.h \ debuggertoolchaincombobox.h \ - debuggersourcepathmappingwidget.h + debuggersourcepathmappingwidget.h \ + memoryviewwidget.h SOURCES += breakhandler.cpp \ breakpoint.cpp \ @@ -106,7 +107,8 @@ SOURCES += breakhandler.cpp \ watchdelegatewidgets.cpp \ debuggertooltipmanager.cpp \ debuggertoolchaincombobox.cpp \ - debuggersourcepathmappingwidget.cpp + debuggersourcepathmappingwidget.cpp \ + memoryviewwidget.cpp FORMS += attachexternaldialog.ui \ attachcoredialog.ui \ diff --git a/src/plugins/debugger/debuggerengine.cpp b/src/plugins/debugger/debuggerengine.cpp index 88d38d45a6..96be6e470b 100644 --- a/src/plugins/debugger/debuggerengine.cpp +++ b/src/plugins/debugger/debuggerengine.cpp @@ -1573,6 +1573,11 @@ void DebuggerEngine::openMemoryView(quint64 address) d->m_memoryAgent.createBinEditor(address); } +void DebuggerEngine::addMemoryView(Internal::MemoryViewWidget *w) +{ + d->m_memoryAgent.addMemoryView(w); +} + void DebuggerEngine::updateMemoryViews() { d->m_memoryAgent.updateContents(); diff --git a/src/plugins/debugger/debuggerengine.h b/src/plugins/debugger/debuggerengine.h index eff8ce1f26..014fa89f31 100644 --- a/src/plugins/debugger/debuggerengine.h +++ b/src/plugins/debugger/debuggerengine.h @@ -82,6 +82,7 @@ class WatchHandler; class BreakpointParameters; class QmlCppEngine; class DebuggerToolTipContext; +class MemoryViewWidget; struct WatchUpdateFlags { @@ -157,6 +158,7 @@ public: virtual void watchPoint(const QPoint &); virtual void openMemoryView(quint64 addr); + virtual void addMemoryView(Internal::MemoryViewWidget *w); virtual void fetchMemory(Internal::MemoryAgent *, QObject *, quint64 addr, quint64 length); virtual void changeMemory(Internal::MemoryAgent *, QObject *, diff --git a/src/plugins/debugger/memoryagent.cpp b/src/plugins/debugger/memoryagent.cpp index 0b56157375..f35a24b576 100644 --- a/src/plugins/debugger/memoryagent.cpp +++ b/src/plugins/debugger/memoryagent.cpp @@ -34,7 +34,9 @@ #include "memoryagent.h" #include "debuggerengine.h" +#include "debuggerstartparameters.h" #include "debuggercore.h" +#include "memoryviewwidget.h" #include #include @@ -44,6 +46,9 @@ #include #include +#include + +#include using namespace Core; @@ -81,6 +86,33 @@ MemoryAgent::~MemoryAgent() 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) { EditorManager *editorManager = EditorManager::instance(); @@ -133,11 +165,16 @@ void MemoryAgent::fetchLazyData(IEditor *editor, quint64 block) void MemoryAgent::addLazyData(QObject *editorToken, quint64 addr, const QByteArray &ba) { - IEditor *editor = qobject_cast(editorToken); - if (editor && editor->widget()) { - QMetaObject::invokeMethod(editor->widget(), "addData", - Q_ARG(quint64, addr / BinBlockSize), Q_ARG(QByteArray, ba)); + + if (IEditor *editor = qobject_cast(editorToken)) { + if (QWidget *editorWidget = editor->widget()) { + QMetaObject::invokeMethod(editorWidget , "addData", + Q_ARG(quint64, addr / BinBlockSize), Q_ARG(QByteArray, ba)); + } + return; } + if (MemoryViewWidget *mvw = qobject_cast(editorToken)) + mvw->setData(ba); } void MemoryAgent::provideNewRange(IEditor *editor, quint64 address) @@ -185,5 +222,42 @@ bool MemoryAgent::hasVisibleEditor() const 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 POD readPod(const unsigned char *data, bool swapByteOrder) +{ + POD pod = 0; + if (swapByteOrder) { + unsigned char *target = reinterpret_cast(&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(data, swapByteOrder) : + readPod(data, swapByteOrder); +} + } // namespace Internal } // namespace Debugger diff --git a/src/plugins/debugger/memoryagent.h b/src/plugins/debugger/memoryagent.h index 20c0632c44..3e092653fd 100644 --- a/src/plugins/debugger/memoryagent.h +++ b/src/plugins/debugger/memoryagent.h @@ -37,15 +37,22 @@ #include #include +QT_FORWARD_DECLARE_CLASS(QPoint) + namespace Core { class IEditor; } +namespace ProjectExplorer { +class Abi; +} + namespace Debugger { class DebuggerEngine; namespace Internal { +class MemoryViewWidget; class MemoryAgent : public QObject { @@ -58,9 +65,14 @@ public: enum { BinBlockSize = 1024 }; bool hasVisibleEditor() const; + static bool isBigEndian(const ProjectExplorer::Abi &a); + static quint64 readInferiorPointerValue(const unsigned char *data, const ProjectExplorer::Abi &a); + public slots: // Called by engine to create a new view. void createBinEditor(quint64 startAddr); + // Called by engine to create a tooltip. + void addMemoryView(MemoryViewWidget *w); // Called by engine to trigger update of contents. void updateContents(); // Called by engine to pass updated contents. @@ -73,6 +85,8 @@ private slots: void handleEndOfFileRequested(Core::IEditor *editor); void handleDataChanged(Core::IEditor *editor, quint64 address, const QByteArray &data); + void updateMemoryView(quint64 address, quint64 length); + void openMemoryView(quint64 address, quint64 length, const QPoint &pos); private: QList > m_editors; diff --git a/src/plugins/debugger/memoryviewwidget.cpp b/src/plugins/debugger/memoryviewwidget.cpp new file mode 100644 index 0000000000..b797e0e6ed --- /dev/null +++ b/src/plugins/debugger/memoryviewwidget.cpp @@ -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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +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()); + 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(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 *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()); +} + +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 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(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 diff --git a/src/plugins/debugger/memoryviewwidget.h b/src/plugins/debugger/memoryviewwidget.h new file mode 100644 index 0000000000..505bad2bdd --- /dev/null +++ b/src/plugins/debugger/memoryviewwidget.h @@ -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 + +#include // QTextEdit::ExtraSelection +#include + +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() const { return m_markup; } + void setMarkup(const QList &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 *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 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 diff --git a/src/plugins/debugger/registerhandler.cpp b/src/plugins/debugger/registerhandler.cpp index f935290efb..b7ddd6e9a2 100644 --- a/src/plugins/debugger/registerhandler.cpp +++ b/src/plugins/debugger/registerhandler.cpp @@ -175,13 +175,14 @@ void RegisterHandler::setAndMarkRegisters(const Registers ®isters) } const int size = m_registers.size(); for (int r = 0; r < size; r++) { + const QModelIndex regIndex = index(r, 1); if (m_registers.at(r).value != registers.at(r).value) { // Indicate red if values change, keep changed. m_registers[r].changed = m_registers[r].changed || !m_registers.at(r).value.isEmpty(); m_registers[r].value = registers.at(r).value; - const QModelIndex regIndex = index(r, 1); emit dataChanged(regIndex, regIndex); } + emit registerSet(regIndex); // notify attached memory views. } } diff --git a/src/plugins/debugger/registerhandler.h b/src/plugins/debugger/registerhandler.h index f8f70597d5..c15de49881 100644 --- a/src/plugins/debugger/registerhandler.h +++ b/src/plugins/debugger/registerhandler.h @@ -81,6 +81,9 @@ public: Q_SLOT void setNumberBase(int base); int numberBase() const { return m_base; } +signals: + void registerSet(const QModelIndex &r); // Register was set, for memory views + private: void calculateWidth(); int rowCount(const QModelIndex &parent = QModelIndex()) const; diff --git a/src/plugins/debugger/registerwindow.cpp b/src/plugins/debugger/registerwindow.cpp index 68cdddd017..bee5a83904 100644 --- a/src/plugins/debugger/registerwindow.cpp +++ b/src/plugins/debugger/registerwindow.cpp @@ -32,7 +32,7 @@ **************************************************************************/ #include "registerwindow.h" - +#include "memoryviewwidget.h" #include "debuggeractions.h" #include "debuggerconstants.h" #include "debuggercore.h" @@ -173,11 +173,7 @@ RegisterWindow::RegisterWindow(QWidget *parent) connect(debuggerCore()->action(AlwaysAdjustRegistersColumnWidths), SIGNAL(toggled(bool)), SLOT(setAlwaysResizeColumnsToContents(bool))); -} - -void RegisterWindow::resizeEvent(QResizeEvent *ev) -{ - QTreeView::resizeEvent(ev); + setObjectName(QLatin1String("RegisterWindow")); } void RegisterWindow::contextMenuEvent(QContextMenuEvent *ev) @@ -197,16 +193,24 @@ void RegisterWindow::contextMenuEvent(QContextMenuEvent *ev) menu.addSeparator(); - QModelIndex idx = indexAt(ev->pos()); - QString address = handler->registers().at(idx.row()).value; - QAction *actShowMemory = menu.addAction(QString()); - if (address.isEmpty()) { - actShowMemory->setText(tr("Open Memory Editor")); - actShowMemory->setEnabled(false); + const QModelIndex idx = indexAt(ev->pos()); + if (!idx.isValid()) + return; + const Register &aRegister = handler->registers().at(idx.row()); + const QVariant addressV = aRegister.editValue(); + 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 { - actShowMemory->setText(tr("Open Memory Editor at %1").arg(address)); - actShowMemory->setEnabled(actionsEnabled - && (engineCapabilities & ShowMemoryCapability)); + actEditMemory->setText(tr("Open Memory Editor")); + actViewMemory->setText(tr("Open Memory View at Value of Register")); + actEditMemory->setEnabled(false); } menu.addSeparator(); @@ -231,15 +235,21 @@ void RegisterWindow::contextMenuEvent(QContextMenuEvent *ev) menu.addAction(debuggerCore()->action(SettingsDialog)); - QAction *act = menu.exec(ev->globalPos()); + const QPoint position = ev->globalPos(); + QAction *act = menu.exec(position); if (act == actAdjust) resizeColumnsToContents(); else if (act == actReload) engine->reloadRegisters(); - else if (act == actShowMemory) - engine->openMemoryView(address.toULongLong(0, 0)); - else if (act == act16) + else if (act == actEditMemory) + engine->openMemoryView(address); + 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); else if (act == act10) handler->setNumberBase(10); diff --git a/src/plugins/debugger/registerwindow.h b/src/plugins/debugger/registerwindow.h index c9723302d9..df1adc4401 100644 --- a/src/plugins/debugger/registerwindow.h +++ b/src/plugins/debugger/registerwindow.h @@ -54,8 +54,7 @@ public slots: void reloadRegisters(); private: - void resizeEvent(QResizeEvent *ev); - void contextMenuEvent(QContextMenuEvent *ev); + virtual void contextMenuEvent(QContextMenuEvent *ev); }; } // namespace Internal diff --git a/src/plugins/debugger/watchwindow.cpp b/src/plugins/debugger/watchwindow.cpp index b4afe52391..7dbf7e1ac8 100644 --- a/src/plugins/debugger/watchwindow.cpp +++ b/src/plugins/debugger/watchwindow.cpp @@ -39,9 +39,11 @@ #include "debuggercore.h" #include "debuggerdialogs.h" #include "debuggerengine.h" +#include "debuggerstartparameters.h" #include "watchdelegatewidgets.h" #include "watchhandler.h" #include "debuggertooltipmanager.h" +#include "memoryviewwidget.h" #include #include @@ -52,6 +54,7 @@ #include #include +#include #include #include #include @@ -140,6 +143,199 @@ private: 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 sortedMemberVariables(const QModelIndex &m, + quint64 start, quint64 end) +{ + const int rowCount = m.model()->rowCount(m); + if (!rowCount) + return QVector(); + QVector 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 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 ColorNamePair; + typedef QVector ColorNameVector; + + MemoryViewWidgetMarkup result; + const QVector 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 @@ -150,6 +346,7 @@ WatchWindow::WatchWindow(Type type, QWidget *parent) : QTreeView(parent), m_type(type) { + setObjectName(QLatin1String("WatchWindow")); m_grabbing = false; setFrameStyle(QFrame::NoFrame); @@ -278,9 +475,9 @@ void WatchWindow::contextMenuEvent(QContextMenuEvent *ev) const QModelIndex mi0 = idx.sibling(idx.row(), 0); const QModelIndex mi1 = idx.sibling(idx.row(), 1); const QModelIndex mi2 = idx.sibling(idx.row(), 2); - const quint64 address = mi0.data(LocalsAddressRole).toULongLong(); - const uint size = mi0.data(LocalsSizeRole).toUInt(); - const quint64 pointerValue = mi0.data(LocalsPointerValueRole).toULongLong(); + const quint64 address = addressOf(mi0); + const uint size = sizeOf(mi0); + const quint64 pointerValue = pointerValueOf(mi0); const QString exp = mi0.data(LocalsExpressionRole).toString(); const QString type = mi2.data().toString(); @@ -425,26 +622,42 @@ void WatchWindow::contextMenuEvent(QContextMenuEvent *ev) QAction *actOpenMemoryEditAtVariableAddress = new QAction(&memoryMenu); QAction *actOpenMemoryEditAtPointerValue = new QAction(&memoryMenu); QAction *actOpenMemoryEditor = new QAction(&memoryMenu); + QAction *actOpenMemoryViewAtVariableAddress = new QAction(&memoryMenu); + QAction *actOpenMemoryViewAtPointerValue = new QAction(&memoryMenu); if (engineCapabilities & ShowMemoryCapability) { actOpenMemoryEditor->setText(tr("Open Memory Editor...")); if (address) { actOpenMemoryEditAtVariableAddress->setText( tr("Open Memory Editor at Object's Address (0x%1)") .arg(address, 0, 16)); + actOpenMemoryViewAtVariableAddress->setText( + tr("Open Memory View at Object's Address (0x%1)") + .arg(address, 0, 16)); } else { actOpenMemoryEditAtVariableAddress->setText( tr("Open Memory Editor at Object's Address")); actOpenMemoryEditAtVariableAddress->setEnabled(false); + actOpenMemoryViewAtVariableAddress->setText( + tr("Open Memory View at Object's Address")); + actOpenMemoryViewAtVariableAddress->setEnabled(false); } if (createPointerActions) { actOpenMemoryEditAtPointerValue->setText( tr("Open Memory Editor at Referenced Address (0x%1)") .arg(pointerValue, 0, 16)); + actOpenMemoryViewAtPointerValue->setText( + tr("Open Memory View at Referenced Address (0x%1)") + .arg(pointerValue, 0, 16)); } else { actOpenMemoryEditAtPointerValue->setText( tr("Open Memory Editor at Referenced Address")); 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(actOpenMemoryEditAtPointerValue); memoryMenu.addAction(actOpenMemoryEditor); @@ -513,6 +726,10 @@ void WatchWindow::contextMenuEvent(QContextMenuEvent *ev) AddressDialog dialog; if (dialog.exec() == QDialog::Accepted) 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) { setWatchpoint(address, size); } else if (act == actSetWatchpointAtPointerValue) { -- 2.11.0