File indexing completed on 2024-05-05 04:39:53

0001 /*
0002     SPDX-FileCopyrightText: 1999 John Birch <jbb@kdevelop.org>
0003     SPDX-FileCopyrightText: 2006 Vladimir Prus <ghost@cs.msu.su>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "memviewdlg.h"
0009 
0010 #include "dbgglobal.h"
0011 #include "debugsession.h"
0012 #include "mi/micommand.h"
0013 
0014 #include <interfaces/icore.h>
0015 #include <interfaces/idebugcontroller.h>
0016 
0017 #include <KLocalizedString>
0018 
0019 #include <Okteta/ByteArrayColumnView>
0020 #include <Okteta/ByteArrayModel>
0021 
0022 #include <QAction>
0023 #include <QContextMenuEvent>
0024 #include <QFormLayout>
0025 #include <QLineEdit>
0026 #include <QDialogButtonBox>
0027 #include <QMenu>
0028 #include <QPushButton>
0029 #include <QToolBox>
0030 #include <QVBoxLayout>
0031 
0032 #include <cctype>
0033 
0034 using KDevMI::MI::CommandType;
0035 
0036 namespace KDevMI
0037 {
0038 namespace GDB
0039 {
0040 
0041 /** Container for controls that select memory range.
0042      *
0043     The memory range selection is embedded into memory view widget,
0044     it's not a standalone dialog. However, we want to have easy way
0045     to hide/show all controls, so we group them in this class.
0046 */
0047 class MemoryRangeSelector : public QWidget
0048 {
0049         Q_OBJECT
0050     public:
0051         QLineEdit* startAddressLineEdit;
0052         QLineEdit* amountLineEdit;
0053         QPushButton* okButton;
0054         QPushButton* cancelButton;
0055 
0056     explicit MemoryRangeSelector(QWidget* parent)
0057     : QWidget(parent)
0058     {
0059         auto* l = new QVBoxLayout(this);
0060 
0061         // Form layout: labels + address field
0062         auto formLayout = new QFormLayout();
0063         l->addLayout(formLayout);
0064 
0065         startAddressLineEdit = new QLineEdit(this);
0066         formLayout->addRow(i18nc("@label:textbox", "Start:"), startAddressLineEdit);
0067 
0068         amountLineEdit = new QLineEdit(this);
0069         formLayout->addRow(i18nc("@label:textbox", "Amount:"), amountLineEdit);
0070 
0071         auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel, this);
0072         l->addWidget(buttonBox);
0073 
0074         okButton = buttonBox->button(QDialogButtonBox::Ok);
0075         cancelButton = buttonBox->button(QDialogButtonBox::Cancel);
0076 
0077         setLayout(l);
0078 
0079         connect(startAddressLineEdit, &QLineEdit::returnPressed, okButton, [this]() {
0080                 okButton->animateClick();
0081         });
0082 
0083         connect(amountLineEdit, &QLineEdit::returnPressed, okButton, [this]() {
0084                 okButton->animateClick();
0085         });
0086     }
0087 };
0088 
0089 MemoryView::MemoryView(QWidget* parent)
0090 : QWidget(parent),
0091     // New memory view can be created only when debugger is active,
0092     // so don't set s_appNotStarted here.
0093     m_memViewView(nullptr),
0094     m_debuggerState(0)
0095 {
0096     setWindowTitle(i18nc("@title:window", "Memory View"));
0097 
0098     initWidget();
0099 
0100     if (isOk())
0101         slotEnableOrDisable();
0102 
0103     auto debugController = KDevelop::ICore::self()->debugController();
0104     Q_ASSERT(debugController);
0105 
0106     connect(debugController, &KDevelop::IDebugController::currentSessionChanged,
0107             this, &MemoryView::currentSessionChanged);
0108 }
0109 
0110 void MemoryView::currentSessionChanged(KDevelop::IDebugSession* s)
0111 {
0112     auto *session = qobject_cast<DebugSession*>(s);
0113     if (!session) return;
0114 
0115     connect(session, &DebugSession::debuggerStateChanged,
0116              this, &MemoryView::slotStateChanged);
0117 }
0118 
0119 void MemoryView::slotStateChanged(DBGStateFlags oldState, DBGStateFlags newState)
0120 {
0121     Q_UNUSED(oldState);
0122     debuggerStateChanged(newState);
0123 }
0124 
0125 void MemoryView::initWidget()
0126 {
0127     auto *l = new QVBoxLayout(this);
0128     l->setContentsMargins(0, 0, 0, 0);
0129 
0130     m_memViewModel = new Okteta::ByteArrayModel(0, -1, this);
0131     m_memViewView = new Okteta::ByteArrayColumnView(this);
0132     m_memViewView->setByteArrayModel(m_memViewModel);
0133 
0134     m_memViewModel->setReadOnly(false);
0135     m_memViewView->setReadOnly(false);
0136     m_memViewView->setOverwriteMode(true);
0137     m_memViewView->setOverwriteOnly(true);
0138     m_memViewModel->setAutoDelete(false);
0139 
0140     m_memViewView->setValueCoding( Okteta::ByteArrayColumnView::HexadecimalCoding );
0141     m_memViewView->setNoOfGroupedBytes(4);
0142     m_memViewView->setByteSpacingWidth(2);
0143     m_memViewView->setGroupSpacingWidth(12);
0144     m_memViewView->setLayoutStyle(Okteta::AbstractByteArrayView::FullSizeLayoutStyle);
0145 
0146 
0147     m_memViewView->setShowsNonprinting(false);
0148     m_memViewView->setSubstituteChar(QLatin1Char('*'));
0149 
0150     m_rangeSelector = new MemoryRangeSelector(this);
0151     l->addWidget(m_rangeSelector);
0152 
0153     connect(m_rangeSelector->okButton, &QPushButton::clicked,
0154             this, &MemoryView::slotChangeMemoryRange);
0155 
0156     connect(m_rangeSelector->cancelButton, &QPushButton::clicked,
0157             this, &MemoryView::slotHideRangeDialog);
0158 
0159     connect(m_rangeSelector->startAddressLineEdit,
0160             &QLineEdit::textChanged,
0161             this,
0162             &MemoryView::slotEnableOrDisable);
0163 
0164     connect(m_rangeSelector->amountLineEdit,
0165             &QLineEdit::textChanged,
0166             this,
0167             &MemoryView::slotEnableOrDisable);
0168 
0169     l->addWidget(m_memViewView);
0170 }
0171 
0172 void MemoryView::debuggerStateChanged(DBGStateFlags state)
0173 {
0174     if (isOk())
0175     {
0176         m_debuggerState = state;
0177         slotEnableOrDisable();
0178     }
0179 }
0180 
0181 
0182 void MemoryView::slotHideRangeDialog()
0183 {
0184     m_rangeSelector->hide();
0185 }
0186 
0187 void MemoryView::slotChangeMemoryRange()
0188 {
0189     auto *session = qobject_cast<DebugSession*>(
0190         KDevelop::ICore::self()->debugController()->currentSession());
0191     if (!session) return;
0192 
0193     QString amount = m_rangeSelector->amountLineEdit->text();
0194     if(amount.isEmpty())
0195         amount = QStringLiteral("sizeof(%1)").arg(m_rangeSelector->startAddressLineEdit->text());
0196 
0197     session->addCommand(std::make_unique<MI::ExpressionValueCommand>(amount, this, &MemoryView::sizeComputed));
0198 }
0199 
0200 void MemoryView::sizeComputed(const QString& size)
0201 {
0202     auto *session = qobject_cast<DebugSession*>(
0203         KDevelop::ICore::self()->debugController()->currentSession());
0204     if (!session) return;
0205 
0206     session->addCommand(MI::DataReadMemory,
0207             QStringLiteral("%1 x 1 1 %2")
0208                 .arg(m_rangeSelector->startAddressLineEdit->text(), size),
0209             this,
0210             &MemoryView::memoryRead);
0211 }
0212 
0213 void MemoryView::memoryRead(const MI::ResultRecord& r)
0214 {
0215     const MI::Value& content = r[QStringLiteral("memory")][0][QStringLiteral("data")];
0216     bool startStringConverted;
0217     m_memStart = r[QStringLiteral("addr")].literal().toULongLong(&startStringConverted, 16);
0218     m_memData.resize(content.size());
0219 
0220     m_memStartStr = m_rangeSelector->startAddressLineEdit->text();
0221     m_memAmountStr = m_rangeSelector->amountLineEdit->text();
0222 
0223     setWindowTitle(i18np("%2 (1 byte)","%2 (%1 bytes)",m_memData.size(),m_memStartStr));
0224     emit captionChanged(windowTitle());
0225 
0226     for(int i = 0; i < content.size(); ++i)
0227     {
0228         m_memData[i] = content[i].literal().toInt(nullptr, 16);
0229     }
0230 
0231     m_memViewModel->setData(reinterpret_cast<Okteta::Byte*>(m_memData.data()), m_memData.size());
0232 
0233     slotHideRangeDialog();
0234 }
0235 
0236 
0237 void MemoryView::memoryEdited(int start, int end)
0238 {
0239     auto *session = qobject_cast<DebugSession*>(
0240         KDevelop::ICore::self()->debugController()->currentSession());
0241     if (!session) return;
0242 
0243     for(int i = start; i <= end; ++i)
0244     {
0245         session->addCommand(MI::GdbSet,
0246                 QStringLiteral("*(char*)(%1 + %2) = %3")
0247                     .arg(m_memStart)
0248                     .arg(i)
0249                     .arg(QString::number(m_memData[i])));
0250     }
0251 }
0252 
0253 void MemoryView::contextMenuEvent(QContextMenuEvent *e)
0254 {
0255     if (!isOk())
0256         return;
0257 
0258     QMenu menu(this);
0259 
0260     bool app_running = !(m_debuggerState & s_appNotStarted);
0261 
0262     QAction* reload = menu.addAction(i18nc("@action::inmenu", "&Reload"));
0263     reload->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
0264     reload->setEnabled(app_running && !m_memData.isEmpty() );
0265 
0266     QActionGroup* formatGroup = nullptr;
0267     QActionGroup* groupingGroup = nullptr;
0268     if (m_memViewModel && m_memViewView)
0269     {
0270         // make Format menu with action group
0271         QMenu* formatMenu = menu.addMenu(i18nc("@title:menu", "&Format"));
0272         formatGroup = new QActionGroup(formatMenu);
0273 
0274         QAction *binary = formatGroup->addAction(i18nc("@item:inmenu display format", "&Binary"));
0275         binary->setData(Okteta::ByteArrayColumnView::BinaryCoding);
0276         binary->setShortcut(Qt::Key_B);
0277         formatMenu->addAction(binary);
0278 
0279         QAction *octal = formatGroup->addAction(i18nc("@item:inmenu display format", "&Octal"));
0280         octal->setData(Okteta::ByteArrayColumnView::OctalCoding);
0281         octal->setShortcut(Qt::Key_O);
0282         formatMenu->addAction(octal);
0283 
0284         QAction *decimal = formatGroup->addAction(i18nc("@item:inmenu display format", "&Decimal"));
0285         decimal->setData(Okteta::ByteArrayColumnView::DecimalCoding);
0286         decimal->setShortcut(Qt::Key_D);
0287         formatMenu->addAction(decimal);
0288 
0289         QAction *hex = formatGroup->addAction(i18nc("@item:inmenu display format", "&Hexadecimal"));
0290         hex->setData(Okteta::ByteArrayColumnView::HexadecimalCoding);
0291         hex->setShortcut(Qt::Key_H);
0292         formatMenu->addAction(hex);
0293 
0294         const auto formatActions = formatGroup->actions();
0295         for (QAction* act : formatActions) {
0296             act->setCheckable(true);
0297             act->setChecked(act->data().toInt() ==  m_memViewView->valueCoding());
0298             act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
0299         }
0300 
0301 
0302         // make Grouping menu with action group
0303         QMenu* groupingMenu = menu.addMenu(i18nc("@title:menu", "&Grouping"));
0304         groupingGroup = new QActionGroup(groupingMenu);
0305 
0306         QAction *group0 = groupingGroup->addAction(i18nc("@item:inmenu no byte grouping", "&0"));
0307         group0->setData(0);
0308         group0->setShortcut(Qt::Key_0);
0309         groupingMenu->addAction(group0);
0310 
0311         QAction *group1 = groupingGroup->addAction(i18nc("@item:inmenu byte group size", "&1"));
0312         group1->setData(1);
0313         group1->setShortcut(Qt::Key_1);
0314         groupingMenu->addAction(group1);
0315 
0316         QAction *group2 = groupingGroup->addAction(i18nc("@item:inmenu byte group size", "&2"));
0317         group2->setData(2);
0318         group2->setShortcut(Qt::Key_2);
0319         groupingMenu->addAction(group2);
0320 
0321         QAction *group4 = groupingGroup->addAction(i18nc("@item:inmenu byte group size", "&4"));
0322         group4->setData(4);
0323         group4->setShortcut(Qt::Key_4);
0324         groupingMenu->addAction(group4);
0325 
0326         QAction *group8 = groupingGroup->addAction(i18nc("@item:inmenu byte group size", "&8"));
0327         group8->setData(8);
0328         group8->setShortcut(Qt::Key_8);
0329         groupingMenu->addAction(group8);
0330 
0331         QAction *group16 = groupingGroup->addAction(i18nc("@item:inmenu byte group size", "1&6"));
0332         group16->setData(16);
0333         group16->setShortcut(Qt::Key_6);
0334         groupingMenu->addAction(group16);
0335 
0336         const auto groupingActions = groupingGroup->actions();
0337         for (QAction* act : groupingActions) {
0338             act->setCheckable(true);
0339             act->setChecked(act->data().toInt() == m_memViewView->noOfGroupedBytes());
0340             act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
0341         }
0342     }
0343 
0344     QAction* write = menu.addAction(i18nc("@action:inmenu", "Write Changes"));
0345     write->setIcon(QIcon::fromTheme(QStringLiteral("document-save")));
0346     write->setEnabled(app_running && m_memViewView && m_memViewView->isModified());
0347 
0348     QAction* range = menu.addAction(i18nc("@action:inmenu", "Change Memory Range"));
0349     range->setEnabled(app_running && !m_rangeSelector->isVisible());
0350     range->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
0351 
0352     QAction* close = menu.addAction(i18nc("@action:inmenu", "Close View"));
0353     close->setIcon(QIcon::fromTheme(QStringLiteral("window-close")));
0354 
0355 
0356     QAction* result = menu.exec(e->globalPos());
0357 
0358 
0359     if (result == reload)
0360     {
0361         // We use m_memStart and m_memAmount stored in this,
0362         // not textual m_memStartStr and m_memAmountStr,
0363         // because program position might have changes and expressions
0364         // are no longer valid.
0365         auto *session = qobject_cast<DebugSession*>(
0366             KDevelop::ICore::self()->debugController()->currentSession());
0367         if (session) {
0368             session->addCommand(MI::DataReadMemory,
0369                     QStringLiteral("%1 x 1 1 %2").arg(m_memStart).arg(m_memData.size()),
0370                     this,
0371                     &MemoryView::memoryRead);
0372         }
0373     }
0374 
0375     if (result && formatGroup && formatGroup == result->actionGroup())
0376         m_memViewView->setValueCoding( (Okteta::ByteArrayColumnView::ValueCoding)result->data().toInt());
0377 
0378     if (result && groupingGroup && groupingGroup == result->actionGroup())
0379         m_memViewView->setNoOfGroupedBytes(result->data().toInt());
0380 
0381     if (result == write)
0382     {
0383         memoryEdited(0, m_memData.size());
0384         m_memViewView->setModified(false);
0385     }
0386 
0387     if (result == range)
0388     {
0389         m_rangeSelector->startAddressLineEdit->setText(m_memStartStr);
0390         m_rangeSelector->amountLineEdit->setText(m_memAmountStr);
0391 
0392         m_rangeSelector->show();
0393         m_rangeSelector->startAddressLineEdit->setFocus();
0394     }
0395 
0396     if (result == close)
0397         deleteLater();
0398 }
0399 
0400 bool MemoryView::isOk() const
0401 {
0402     return m_memViewView;
0403 }
0404 
0405 void MemoryView::slotEnableOrDisable()
0406 {
0407     bool app_started = !(m_debuggerState & s_appNotStarted);
0408 
0409     bool enabled_ = app_started && !m_rangeSelector->startAddressLineEdit->text().isEmpty();
0410 
0411     m_rangeSelector->okButton->setEnabled(enabled_);
0412 }
0413 
0414 
0415 MemoryViewerWidget::MemoryViewerWidget(CppDebuggerPlugin* /*plugin*/, QWidget* parent)
0416 : QWidget(parent)
0417 {
0418     setWindowIcon(QIcon::fromTheme(QStringLiteral("server-database"), windowIcon()));
0419     setWindowTitle(i18nc("@title:window", "Memory Viewer"));
0420 
0421     auto * newMemoryViewerAction = new QAction(this);
0422     newMemoryViewerAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
0423     newMemoryViewerAction->setText(i18nc("@action", "New Memory Viewer"));
0424     newMemoryViewerAction->setToolTip(i18nc("@info:tooltip", "Open a new memory viewer"));
0425     newMemoryViewerAction->setIcon(QIcon::fromTheme(QStringLiteral("window-new")));
0426     connect(newMemoryViewerAction, &QAction::triggered, this , &MemoryViewerWidget::slotAddMemoryView);
0427     addAction(newMemoryViewerAction);
0428 
0429     auto *l = new QVBoxLayout(this);
0430     l->setContentsMargins(0, 0, 0, 0);
0431 
0432     m_toolBox = new QToolBox(this);
0433     m_toolBox->setContentsMargins(0, 0, 0, 0);
0434     l->addWidget(m_toolBox);
0435 
0436     setLayout(l);
0437 
0438     // Start with one empty memory view.
0439     slotAddMemoryView();
0440 }
0441 
0442 void MemoryViewerWidget::slotAddMemoryView()
0443 {
0444     auto* widget = new MemoryView(this);
0445     m_toolBox->addItem(widget, widget->windowTitle());
0446     m_toolBox->setCurrentIndex(m_toolBox->indexOf(widget));
0447 
0448     connect(widget, &MemoryView::captionChanged,
0449             this, &MemoryViewerWidget::slotChildCaptionChanged);
0450 }
0451 
0452 void MemoryViewerWidget::slotChildCaptionChanged(const QString& caption)
0453 {
0454     const auto* s = static_cast<const QWidget*>(sender());
0455     auto* ncs = const_cast<QWidget*>(s);
0456     QString cap = caption;
0457     // Prevent interpreting '&' as accelerator specifier.
0458     cap.replace(QLatin1Char('&'), QLatin1String("&&"));
0459     m_toolBox->setItemText(m_toolBox->indexOf(ncs), cap);
0460 }
0461 
0462 } // end of namespace GDB
0463 } // end of namespace KDevMI
0464 
0465 #include "memviewdlg.moc"
0466 #include "moc_memviewdlg.cpp"