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