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