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"