1 /*
2 SPDX-FileCopyrightText: 1999 John Birch <jbb@kdevelop.org>
3 SPDX-FileCopyrightText: 2006 Vladimir Prus <ghost@cs.msu.su>
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8 #include "memviewdlg.h"
9
10 #include "dbgglobal.h"
11 #include "debugsession.h"
12 #include "mi/micommand.h"
13
14 #include <interfaces/icore.h>
15 #include <interfaces/idebugcontroller.h>
16
17 #include <KLocalizedString>
18
19 #include <Okteta/ByteArrayColumnView>
20 #include <Okteta/ByteArrayModel>
21
22 #include <QAction>
23 #include <QContextMenuEvent>
24 #include <QFormLayout>
25 #include <QLineEdit>
26 #include <QDialogButtonBox>
27 #include <QMenu>
28 #include <QPushButton>
29 #include <QToolBox>
30 #include <QVBoxLayout>
31
32 #include <cctype>
33
34 using KDevMI::MI::CommandType;
35
36 namespace KDevMI
37 {
38 namespace GDB
39 {
40
41 /** Container for controls that select memory range.
42 *
43 The memory range selection is embedded into memory view widget,
44 it's not a standalone dialog. However, we want to have easy way
45 to hide/show all controls, so we group them in this class.
46 */
47 class MemoryRangeSelector : public QWidget
48 {
49 Q_OBJECT
50 public:
51 QLineEdit* startAddressLineEdit;
52 QLineEdit* amountLineEdit;
53 QPushButton* okButton;
54 QPushButton* cancelButton;
55
MemoryRangeSelector(QWidget * parent)56 explicit MemoryRangeSelector(QWidget* parent)
57 : QWidget(parent)
58 {
59 auto* l = new QVBoxLayout(this);
60
61 // Form layout: labels + address field
62 auto formLayout = new QFormLayout();
63 l->addLayout(formLayout);
64
65 startAddressLineEdit = new QLineEdit(this);
66 formLayout->addRow(i18nc("@label:textbox", "Start:"), startAddressLineEdit);
67
68 amountLineEdit = new QLineEdit(this);
69 formLayout->addRow(i18nc("@label:textbox", "Amount:"), amountLineEdit);
70
71 auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel, this);
72 l->addWidget(buttonBox);
73
74 okButton = buttonBox->button(QDialogButtonBox::Ok);
75 cancelButton = buttonBox->button(QDialogButtonBox::Cancel);
76
77 setLayout(l);
78
79 connect(startAddressLineEdit, &QLineEdit::returnPressed, okButton, [this]() {
80 okButton->animateClick();
81 });
82
83 connect(amountLineEdit, &QLineEdit::returnPressed, okButton, [this]() {
84 okButton->animateClick();
85 });
86 }
87 };
88
MemoryView(QWidget * parent)89 MemoryView::MemoryView(QWidget* parent)
90 : QWidget(parent),
91 // New memory view can be created only when debugger is active,
92 // so don't set s_appNotStarted here.
93 m_memViewView(nullptr),
94 m_debuggerState(0)
95 {
96 setWindowTitle(i18nc("@title:window", "Memory View"));
97
98 initWidget();
99
100 if (isOk())
101 slotEnableOrDisable();
102
103 auto debugController = KDevelop::ICore::self()->debugController();
104 Q_ASSERT(debugController);
105
106 connect(debugController, &KDevelop::IDebugController::currentSessionChanged,
107 this, &MemoryView::currentSessionChanged);
108 }
109
currentSessionChanged(KDevelop::IDebugSession * s)110 void MemoryView::currentSessionChanged(KDevelop::IDebugSession* s)
111 {
112 auto *session = qobject_cast<DebugSession*>(s);
113 if (!session) return;
114
115 connect(session, &DebugSession::debuggerStateChanged,
116 this, &MemoryView::slotStateChanged);
117 }
118
slotStateChanged(DBGStateFlags oldState,DBGStateFlags newState)119 void MemoryView::slotStateChanged(DBGStateFlags oldState, DBGStateFlags newState)
120 {
121 Q_UNUSED(oldState);
122 debuggerStateChanged(newState);
123 }
124
initWidget()125 void MemoryView::initWidget()
126 {
127 auto *l = new QVBoxLayout(this);
128 l->setContentsMargins(0, 0, 0, 0);
129
130 m_memViewModel = new Okteta::ByteArrayModel(0, -1, this);
131 m_memViewView = new Okteta::ByteArrayColumnView(this);
132 m_memViewView->setByteArrayModel(m_memViewModel);
133
134 m_memViewModel->setReadOnly(false);
135 m_memViewView->setReadOnly(false);
136 m_memViewView->setOverwriteMode(true);
137 m_memViewView->setOverwriteOnly(true);
138 m_memViewModel->setAutoDelete(false);
139
140 m_memViewView->setValueCoding( Okteta::ByteArrayColumnView::HexadecimalCoding );
141 m_memViewView->setNoOfGroupedBytes(4);
142 m_memViewView->setByteSpacingWidth(2);
143 m_memViewView->setGroupSpacingWidth(12);
144 m_memViewView->setLayoutStyle(Okteta::AbstractByteArrayView::FullSizeLayoutStyle);
145
146
147 m_memViewView->setShowsNonprinting(false);
148 m_memViewView->setSubstituteChar(QLatin1Char('*'));
149
150 m_rangeSelector = new MemoryRangeSelector(this);
151 l->addWidget(m_rangeSelector);
152
153 connect(m_rangeSelector->okButton, &QPushButton::clicked,
154 this, &MemoryView::slotChangeMemoryRange);
155
156 connect(m_rangeSelector->cancelButton, &QPushButton::clicked,
157 this, &MemoryView::slotHideRangeDialog);
158
159 connect(m_rangeSelector->startAddressLineEdit,
160 &QLineEdit::textChanged,
161 this,
162 &MemoryView::slotEnableOrDisable);
163
164 connect(m_rangeSelector->amountLineEdit,
165 &QLineEdit::textChanged,
166 this,
167 &MemoryView::slotEnableOrDisable);
168
169 l->addWidget(m_memViewView);
170 }
171
debuggerStateChanged(DBGStateFlags state)172 void MemoryView::debuggerStateChanged(DBGStateFlags state)
173 {
174 if (isOk())
175 {
176 m_debuggerState = state;
177 slotEnableOrDisable();
178 }
179 }
180
181
slotHideRangeDialog()182 void MemoryView::slotHideRangeDialog()
183 {
184 m_rangeSelector->hide();
185 }
186
slotChangeMemoryRange()187 void MemoryView::slotChangeMemoryRange()
188 {
189 auto *session = qobject_cast<DebugSession*>(
190 KDevelop::ICore::self()->debugController()->currentSession());
191 if (!session) return;
192
193 QString amount = m_rangeSelector->amountLineEdit->text();
194 if(amount.isEmpty())
195 amount = QStringLiteral("sizeof(%1)").arg(m_rangeSelector->startAddressLineEdit->text());
196
197 session->addCommand(new MI::ExpressionValueCommand(amount, this, &MemoryView::sizeComputed));
198 }
199
sizeComputed(const QString & size)200 void MemoryView::sizeComputed(const QString& size)
201 {
202 auto *session = qobject_cast<DebugSession*>(
203 KDevelop::ICore::self()->debugController()->currentSession());
204 if (!session) return;
205
206 session->addCommand(MI::DataReadMemory,
207 QStringLiteral("%1 x 1 1 %2")
208 .arg(m_rangeSelector->startAddressLineEdit->text(), size),
209 this,
210 &MemoryView::memoryRead);
211 }
212
memoryRead(const MI::ResultRecord & r)213 void MemoryView::memoryRead(const MI::ResultRecord& r)
214 {
215 const MI::Value& content = r[QStringLiteral("memory")][0][QStringLiteral("data")];
216 bool startStringConverted;
217 m_memStart = r[QStringLiteral("addr")].literal().toULongLong(&startStringConverted, 16);
218 m_memData.resize(content.size());
219
220 m_memStartStr = m_rangeSelector->startAddressLineEdit->text();
221 m_memAmountStr = m_rangeSelector->amountLineEdit->text();
222
223 setWindowTitle(i18np("%2 (1 byte)","%2 (%1 bytes)",m_memData.size(),m_memStartStr));
224 emit captionChanged(windowTitle());
225
226 for(int i = 0; i < content.size(); ++i)
227 {
228 m_memData[i] = content[i].literal().toInt(nullptr, 16);
229 }
230
231 m_memViewModel->setData(reinterpret_cast<Okteta::Byte*>(m_memData.data()), m_memData.size());
232
233 slotHideRangeDialog();
234 }
235
236
memoryEdited(int start,int end)237 void MemoryView::memoryEdited(int start, int end)
238 {
239 auto *session = qobject_cast<DebugSession*>(
240 KDevelop::ICore::self()->debugController()->currentSession());
241 if (!session) return;
242
243 for(int i = start; i <= end; ++i)
244 {
245 session->addCommand(MI::GdbSet,
246 QStringLiteral("*(char*)(%1 + %2) = %3")
247 .arg(m_memStart)
248 .arg(i)
249 .arg(QString::number(m_memData[i])));
250 }
251 }
252
contextMenuEvent(QContextMenuEvent * e)253 void MemoryView::contextMenuEvent(QContextMenuEvent *e)
254 {
255 if (!isOk())
256 return;
257
258 QMenu menu(this);
259
260 bool app_running = !(m_debuggerState & s_appNotStarted);
261
262 QAction* reload = menu.addAction(i18nc("@action::inmenu", "&Reload"));
263 reload->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
264 reload->setEnabled(app_running && !m_memData.isEmpty() );
265
266 QActionGroup* formatGroup = nullptr;
267 QActionGroup* groupingGroup = nullptr;
268 if (m_memViewModel && m_memViewView)
269 {
270 // make Format menu with action group
271 QMenu* formatMenu = menu.addMenu(i18nc("@title:menu", "&Format"));
272 formatGroup = new QActionGroup(formatMenu);
273
274 QAction *binary = formatGroup->addAction(i18nc("@item:inmenu display format", "&Binary"));
275 binary->setData(Okteta::ByteArrayColumnView::BinaryCoding);
276 binary->setShortcut(Qt::Key_B);
277 formatMenu->addAction(binary);
278
279 QAction *octal = formatGroup->addAction(i18nc("@item:inmenu display format", "&Octal"));
280 octal->setData(Okteta::ByteArrayColumnView::OctalCoding);
281 octal->setShortcut(Qt::Key_O);
282 formatMenu->addAction(octal);
283
284 QAction *decimal = formatGroup->addAction(i18nc("@item:inmenu display format", "&Decimal"));
285 decimal->setData(Okteta::ByteArrayColumnView::DecimalCoding);
286 decimal->setShortcut(Qt::Key_D);
287 formatMenu->addAction(decimal);
288
289 QAction *hex = formatGroup->addAction(i18nc("@item:inmenu display format", "&Hexadecimal"));
290 hex->setData(Okteta::ByteArrayColumnView::HexadecimalCoding);
291 hex->setShortcut(Qt::Key_H);
292 formatMenu->addAction(hex);
293
294 const auto formatActions = formatGroup->actions();
295 for (QAction* act : formatActions) {
296 act->setCheckable(true);
297 act->setChecked(act->data().toInt() == m_memViewView->valueCoding());
298 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
299 }
300
301
302 // make Grouping menu with action group
303 QMenu* groupingMenu = menu.addMenu(i18nc("@title:menu", "&Grouping"));
304 groupingGroup = new QActionGroup(groupingMenu);
305
306 QAction *group0 = groupingGroup->addAction(i18nc("@item:inmenu no byte grouping", "&0"));
307 group0->setData(0);
308 group0->setShortcut(Qt::Key_0);
309 groupingMenu->addAction(group0);
310
311 QAction *group1 = groupingGroup->addAction(i18nc("@item:inmenu byte group size", "&1"));
312 group1->setData(1);
313 group1->setShortcut(Qt::Key_1);
314 groupingMenu->addAction(group1);
315
316 QAction *group2 = groupingGroup->addAction(i18nc("@item:inmenu byte group size", "&2"));
317 group2->setData(2);
318 group2->setShortcut(Qt::Key_2);
319 groupingMenu->addAction(group2);
320
321 QAction *group4 = groupingGroup->addAction(i18nc("@item:inmenu byte group size", "&4"));
322 group4->setData(4);
323 group4->setShortcut(Qt::Key_4);
324 groupingMenu->addAction(group4);
325
326 QAction *group8 = groupingGroup->addAction(i18nc("@item:inmenu byte group size", "&8"));
327 group8->setData(8);
328 group8->setShortcut(Qt::Key_8);
329 groupingMenu->addAction(group8);
330
331 QAction *group16 = groupingGroup->addAction(i18nc("@item:inmenu byte group size", "1&6"));
332 group16->setData(16);
333 group16->setShortcut(Qt::Key_6);
334 groupingMenu->addAction(group16);
335
336 const auto groupingActions = groupingGroup->actions();
337 for (QAction* act : groupingActions) {
338 act->setCheckable(true);
339 act->setChecked(act->data().toInt() == m_memViewView->noOfGroupedBytes());
340 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
341 }
342 }
343
344 QAction* write = menu.addAction(i18nc("@action:inmenu", "Write Changes"));
345 write->setIcon(QIcon::fromTheme(QStringLiteral("document-save")));
346 write->setEnabled(app_running && m_memViewView && m_memViewView->isModified());
347
348 QAction* range = menu.addAction(i18nc("@action:inmenu", "Change Memory Range"));
349 range->setEnabled(app_running && !m_rangeSelector->isVisible());
350 range->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
351
352 QAction* close = menu.addAction(i18nc("@action:inmenu", "Close View"));
353 close->setIcon(QIcon::fromTheme(QStringLiteral("window-close")));
354
355
356 QAction* result = menu.exec(e->globalPos());
357
358
359 if (result == reload)
360 {
361 // We use m_memStart and m_memAmount stored in this,
362 // not textual m_memStartStr and m_memAmountStr,
363 // because program position might have changes and expressions
364 // are no longer valid.
365 auto *session = qobject_cast<DebugSession*>(
366 KDevelop::ICore::self()->debugController()->currentSession());
367 if (session) {
368 session->addCommand(MI::DataReadMemory,
369 QStringLiteral("%1 x 1 1 %2").arg(m_memStart).arg(m_memData.size()),
370 this,
371 &MemoryView::memoryRead);
372 }
373 }
374
375 if (result && formatGroup && formatGroup == result->actionGroup())
376 m_memViewView->setValueCoding( (Okteta::ByteArrayColumnView::ValueCoding)result->data().toInt());
377
378 if (result && groupingGroup && groupingGroup == result->actionGroup())
379 m_memViewView->setNoOfGroupedBytes(result->data().toInt());
380
381 if (result == write)
382 {
383 memoryEdited(0, m_memData.size());
384 m_memViewView->setModified(false);
385 }
386
387 if (result == range)
388 {
389 m_rangeSelector->startAddressLineEdit->setText(m_memStartStr);
390 m_rangeSelector->amountLineEdit->setText(m_memAmountStr);
391
392 m_rangeSelector->show();
393 m_rangeSelector->startAddressLineEdit->setFocus();
394 }
395
396 if (result == close)
397 deleteLater();
398 }
399
isOk() const400 bool MemoryView::isOk() const
401 {
402 return m_memViewView;
403 }
404
slotEnableOrDisable()405 void MemoryView::slotEnableOrDisable()
406 {
407 bool app_started = !(m_debuggerState & s_appNotStarted);
408
409 bool enabled_ = app_started && !m_rangeSelector->startAddressLineEdit->text().isEmpty();
410
411 m_rangeSelector->okButton->setEnabled(enabled_);
412 }
413
414
MemoryViewerWidget(CppDebuggerPlugin *,QWidget * parent)415 MemoryViewerWidget::MemoryViewerWidget(CppDebuggerPlugin* /*plugin*/, QWidget* parent)
416 : QWidget(parent)
417 {
418 setWindowIcon(QIcon::fromTheme(QStringLiteral("server-database"), windowIcon()));
419 setWindowTitle(i18nc("@title:window", "Memory Viewer"));
420
421 auto * newMemoryViewerAction = new QAction(this);
422 newMemoryViewerAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
423 newMemoryViewerAction->setText(i18nc("@action", "New Memory Viewer"));
424 newMemoryViewerAction->setToolTip(i18nc("@info:tooltip", "Open a new memory viewer"));
425 newMemoryViewerAction->setIcon(QIcon::fromTheme(QStringLiteral("window-new")));
426 connect(newMemoryViewerAction, &QAction::triggered, this , &MemoryViewerWidget::slotAddMemoryView);
427 addAction(newMemoryViewerAction);
428
429 auto *l = new QVBoxLayout(this);
430 l->setContentsMargins(0, 0, 0, 0);
431
432 m_toolBox = new QToolBox(this);
433 m_toolBox->setContentsMargins(0, 0, 0, 0);
434 l->addWidget(m_toolBox);
435
436 setLayout(l);
437
438 // Start with one empty memory view.
439 slotAddMemoryView();
440 }
441
slotAddMemoryView()442 void MemoryViewerWidget::slotAddMemoryView()
443 {
444 auto* widget = new MemoryView(this);
445 m_toolBox->addItem(widget, widget->windowTitle());
446 m_toolBox->setCurrentIndex(m_toolBox->indexOf(widget));
447
448 connect(widget, &MemoryView::captionChanged,
449 this, &MemoryViewerWidget::slotChildCaptionChanged);
450 }
451
slotChildCaptionChanged(const QString & caption)452 void MemoryViewerWidget::slotChildCaptionChanged(const QString& caption)
453 {
454 const auto* s = static_cast<const QWidget*>(sender());
455 auto* ncs = const_cast<QWidget*>(s);
456 QString cap = caption;
457 // Prevent interpreting '&' as accelerator specifier.
458 cap.replace(QLatin1Char('&'), QLatin1String("&&"));
459 m_toolBox->setItemText(m_toolBox->indexOf(ncs), cap);
460 }
461
462 } // end of namespace GDB
463 } // end of namespace KDevMI
464
465 #include "memviewdlg.moc"
466