OSDN Git Service

Debugger: Add memory views.
authorFriedemann Kleint <Friedemann.Kleint@nokia.com>
Fri, 8 Apr 2011 14:18:47 +0000 (16:18 +0200)
committerFriedemann Kleint <Friedemann.Kleint@nokia.com>
Fri, 8 Apr 2011 14:22:56 +0000 (16:22 +0200)
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
12 files changed:
src/plugins/debugger/debugger.pro
src/plugins/debugger/debuggerengine.cpp
src/plugins/debugger/debuggerengine.h
src/plugins/debugger/memoryagent.cpp
src/plugins/debugger/memoryagent.h
src/plugins/debugger/memoryviewwidget.cpp [new file with mode: 0644]
src/plugins/debugger/memoryviewwidget.h [new file with mode: 0644]
src/plugins/debugger/registerhandler.cpp
src/plugins/debugger/registerhandler.h
src/plugins/debugger/registerwindow.cpp
src/plugins/debugger/registerwindow.h
src/plugins/debugger/watchwindow.cpp

index 7667804..e77b89d 100644 (file)
@@ -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 \
index 88d38d4..96be6e4 100644 (file)
@@ -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();
index eff8ce1..014fa89 100644 (file)
@@ -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 *,
index 0b56157..f35a24b 100644 (file)
@@ -34,7 +34,9 @@
 #include "memoryagent.h"
 
 #include "debuggerengine.h"
+#include "debuggerstartparameters.h"
 #include "debuggercore.h"
+#include "memoryviewwidget.h"
 
 #include <coreplugin/coreconstants.h>
 #include <coreplugin/editormanager/editormanager.h>
@@ -44,6 +46,9 @@
 #include <utils/qtcassert.h>
 
 #include <QtGui/QMessageBox>
+#include <QtGui/QMainWindow>
+
+#include <cstring>
 
 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<IEditor *>(editorToken);
-    if (editor && editor->widget()) {
-        QMetaObject::invokeMethod(editor->widget(), "addData",
-            Q_ARG(quint64, addr / BinBlockSize), Q_ARG(QByteArray, ba));
+
+    if (IEditor *editor = qobject_cast<IEditor *>(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<MemoryViewWidget*>(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 <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 Debugger
index 20c0632..3e09265 100644 (file)
 #include <QtCore/QObject>
 #include <QtCore/QPointer>
 
+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<QPointer<Core::IEditor> > m_editors;
diff --git a/src/plugins/debugger/memoryviewwidget.cpp b/src/plugins/debugger/memoryviewwidget.cpp
new file mode 100644 (file)
index 0000000..b797e0e
--- /dev/null
@@ -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
diff --git a/src/plugins/debugger/memoryviewwidget.h b/src/plugins/debugger/memoryviewwidget.h
new file mode 100644 (file)
index 0000000..505bad2
--- /dev/null
@@ -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
index f935290..b7ddd6e 100644 (file)
@@ -175,13 +175,14 @@ void RegisterHandler::setAndMarkRegisters(const Registers &registers)
     }
     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.
     }
 }
 
index f8f7059..c15de49 100644 (file)
@@ -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;
index 68cdddd..bee5a83 100644 (file)
@@ -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);
index c972330..df1adc4 100644 (file)
@@ -54,8 +54,7 @@ public slots:
     void reloadRegisters();
 
 private:
-    void resizeEvent(QResizeEvent *ev);
-    void contextMenuEvent(QContextMenuEvent *ev);
+    virtual void contextMenuEvent(QContextMenuEvent *ev);
 };
 
 } // namespace Internal
index b4afe52..7dbf7e1 100644 (file)
 #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 <utils/qtcassert.h>
 #include <utils/savedaction.h>
@@ -52,6 +54,7 @@
 #include <QtCore/QVariant>
 
 #include <QtGui/QApplication>
+#include <QtGui/QPalette>
 #include <QtGui/QClipboard>
 #include <QtGui/QContextMenuEvent>
 #include <QtGui/QHeaderView>
@@ -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<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
@@ -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) {