Commit f3cc061d authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

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
parent 275e1434
......@@ -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 \
......
......@@ -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();
......
......@@ -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 *,
......
......@@ -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
......@@ -37,15 +37,22 @@
#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;
......
This diff is collapsed.
/**************************************************************************
**
** 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
......@@ -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.
}
}
......
......@@ -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;
......
......@@ -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);
......
......@@ -54,8 +54,7 @@ public slots:
void reloadRegisters();
private:
void resizeEvent(QResizeEvent *ev);
void contextMenuEvent(QContextMenuEvent *ev);
virtual void contextMenuEvent(QContextMenuEvent *ev);
};
} // namespace Internal
......
......@@ -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 <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