File indexing completed on 2024-05-12 04:39:46

0001 /*
0002     SPDX-FileCopyrightText: 2000 John Birch <jbb@kdevelop.org>
0003     SPDX-FileCopyrightText: 2006 Vladimir Prus <ghost@cs.msu.su>
0004     SPDX-FileCopyrightText: 2007 Hamish Rodda <rodda@kde.org>
0005     SPDX-FileCopyrightText: 2013 Vlas Puhov <vlas.puhov@mail.ru>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "disassemblewidget.h"
0011 
0012 #include "midebuggerplugin.h"
0013 #include "debuglog.h"
0014 #include "midebugsession.h"
0015 #include "mi/micommand.h"
0016 #include "registers/registersmanager.h"
0017 
0018 #include <debugger/interfaces/idebugsession.h>
0019 #include <interfaces/icore.h>
0020 #include <interfaces/idebugcontroller.h>
0021 #include <util/autoorientedsplitter.h>
0022 
0023 #include <KLocalizedString>
0024 #include <KSharedConfig>
0025 
0026 #include <QShowEvent>
0027 #include <QHideEvent>
0028 #include <QAction>
0029 #include <QMenu>
0030 #include <QVBoxLayout>
0031 #include <QHBoxLayout>
0032 #include <QPushButton>
0033 #include <QSplitter>
0034 #include <QFontDatabase>
0035 
0036 using namespace KDevMI;
0037 using namespace KDevMI::MI;
0038 
0039 
0040 SelectAddressDialog::SelectAddressDialog(QWidget* parent)
0041     : QDialog(parent)
0042 {
0043     m_ui.setupUi(this);
0044     setWindowTitle(i18nc("@title:window", "Address Selector"));
0045 
0046     connect(m_ui.comboBox, &KHistoryComboBox::editTextChanged,
0047             this, &SelectAddressDialog::validateInput);
0048     connect(m_ui.comboBox, QOverload<const QString&>::of(&KHistoryComboBox::returnPressed),
0049             this, &SelectAddressDialog::itemSelected);
0050 }
0051 
0052 QString SelectAddressDialog::address() const
0053 {
0054     return hasValidAddress() ? m_ui.comboBox->currentText() : QString();
0055 }
0056 
0057 bool SelectAddressDialog::hasValidAddress() const
0058 {
0059     bool ok;
0060     m_ui.comboBox->currentText().toLongLong(&ok, 16);
0061 
0062     return ok;
0063 }
0064 
0065 void SelectAddressDialog::updateOkState()
0066 {
0067     m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(hasValidAddress());
0068 }
0069 
0070 void SelectAddressDialog::validateInput()
0071 {
0072     updateOkState();
0073 }
0074 
0075 void SelectAddressDialog::itemSelected()
0076 {
0077     QString text = m_ui.comboBox->currentText();
0078     if( hasValidAddress() && m_ui.comboBox->findText(text) < 0 )
0079         m_ui.comboBox->addItem(text);
0080 }
0081 
0082 
0083 
0084 DisassembleWindow::DisassembleWindow(QWidget *parent, DisassembleWidget* widget)
0085     : QTreeWidget(parent)
0086 {
0087     /*context menu commands */{
0088     m_selectAddrAction = new QAction(i18nc("@action", "Change &Address"), this);
0089     m_selectAddrAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
0090     connect(m_selectAddrAction, &QAction::triggered, widget, &DisassembleWidget::slotChangeAddress);
0091 
0092     m_jumpToLocation = new QAction(QIcon::fromTheme(QStringLiteral("debug-execute-to-cursor")), i18nc("@action", "&Jump to Cursor"), this);
0093     m_jumpToLocation->setWhatsThis(i18nc("@info:whatsthis", "Sets the execution pointer to the current cursor position."));
0094     connect(m_jumpToLocation,&QAction::triggered, widget, &DisassembleWidget::jumpToCursor);
0095 
0096     m_runUntilCursor = new QAction(QIcon::fromTheme(QStringLiteral("debug-run-cursor")), i18nc("@action", "&Run to Cursor"), this);
0097     m_runUntilCursor->setWhatsThis(i18nc("@info:whatsthis", "Continues execution until the cursor position is reached."));
0098     connect(m_runUntilCursor,&QAction::triggered, widget, &DisassembleWidget::runToCursor);
0099 
0100     m_disassemblyFlavorAtt = new QAction(i18nc("@option:check", "&AT&&T"), this);
0101     m_disassemblyFlavorAtt->setToolTip(i18nc("@info:tooltip", "GDB will use the AT&T disassembly flavor (e.g. mov 0xc(%ebp),%eax)."));
0102     m_disassemblyFlavorAtt->setData(DisassemblyFlavorATT);
0103     m_disassemblyFlavorAtt->setCheckable(true);
0104 
0105     m_disassemblyFlavorIntel = new QAction(i18nc("@option:check", "&Intel"), this);
0106     m_disassemblyFlavorIntel->setToolTip(i18nc("@info:tooltip", "GDB will use the Intel disassembly flavor (e.g. mov eax, DWORD PTR [ebp+0xc])."));
0107     m_disassemblyFlavorIntel->setData(DisassemblyFlavorIntel);
0108     m_disassemblyFlavorIntel->setCheckable(true);
0109 
0110     m_disassemblyFlavorActionGroup = new QActionGroup(this);
0111     m_disassemblyFlavorActionGroup->setExclusive(true);
0112     m_disassemblyFlavorActionGroup->addAction(m_disassemblyFlavorAtt);
0113     m_disassemblyFlavorActionGroup->addAction(m_disassemblyFlavorIntel);
0114     connect(m_disassemblyFlavorActionGroup, &QActionGroup::triggered, widget, &DisassembleWidget::setDisassemblyFlavor);
0115     }
0116 }
0117 
0118 void DisassembleWindow::setDisassemblyFlavor(DisassemblyFlavor flavor)
0119 {
0120     switch(flavor)
0121     {
0122     case DisassemblyFlavorUnknown:
0123         m_disassemblyFlavorAtt->setChecked(false);
0124         m_disassemblyFlavorIntel->setChecked(false);
0125         break;
0126     case DisassemblyFlavorATT:
0127         m_disassemblyFlavorAtt->setChecked(true);
0128         m_disassemblyFlavorIntel->setChecked(false);
0129         break;
0130     case DisassemblyFlavorIntel:
0131         m_disassemblyFlavorAtt->setChecked(false);
0132         m_disassemblyFlavorIntel->setChecked(true);
0133         break;
0134     }
0135 }
0136 
0137 void DisassembleWindow::contextMenuEvent(QContextMenuEvent *e)
0138 {
0139         QMenu popup(this);
0140         popup.addAction(m_selectAddrAction);
0141         popup.addAction(m_jumpToLocation);
0142         popup.addAction(m_runUntilCursor);
0143         QMenu* disassemblyFlavorMenu = popup.addMenu(i18nc("@title:menu", "Disassembly Flavor"));
0144         disassemblyFlavorMenu->addAction(m_disassemblyFlavorAtt);
0145         disassemblyFlavorMenu->addAction(m_disassemblyFlavorIntel);
0146         popup.exec(e->globalPos());
0147 }
0148 /***************************************************************************/
0149 /***************************************************************************/
0150 /***************************************************************************/
0151 DisassembleWidget::DisassembleWidget(MIDebuggerPlugin* plugin, QWidget *parent)
0152         : QWidget(parent),
0153         active_(false),
0154         lower_(0),
0155         upper_(0),
0156         address_(0),
0157         m_splitter(new KDevelop::AutoOrientedSplitter(this))
0158 {
0159         auto* topLayout = new QVBoxLayout(this);
0160         topLayout->setContentsMargins(0, 0, 0, 0);
0161 
0162         auto* controlsLayout = new QHBoxLayout;
0163 
0164         topLayout->addLayout(controlsLayout);
0165 
0166 
0167     {   // initialize disasm/registers views
0168         topLayout->addWidget(m_splitter);
0169 
0170         //topLayout->setContentsMargins(0, 0, 0, 0);
0171 
0172         m_disassembleWindow = new DisassembleWindow(m_splitter, this);
0173 
0174         m_disassembleWindow->setWhatsThis(i18nc("@info:whatsthis", "<b>Machine code display</b><p>"
0175                         "A machine code view into your running "
0176                         "executable with the current instruction "
0177                         "highlighted. You can step instruction by "
0178                         "instruction using the debuggers toolbar "
0179                         "buttons of \"step over\" instruction and "
0180                         "\"step into\" instruction."));
0181 
0182         m_disassembleWindow->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
0183         m_disassembleWindow->setSelectionMode(QTreeWidget::SingleSelection);
0184         m_disassembleWindow->setColumnCount(ColumnCount);
0185         m_disassembleWindow->setUniformRowHeights(true);
0186         m_disassembleWindow->setRootIsDecorated(false);
0187 
0188         m_disassembleWindow->setHeaderLabels(QStringList{
0189             QString(),
0190             i18nc("@title:column", "Address"),
0191             i18nc("@title:column", "Function"),
0192             i18nc("@title:column", "Instruction")
0193         });
0194 
0195         m_splitter->setStretchFactor(0, 1);
0196         m_splitter->setContentsMargins(0, 0, 0, 0);
0197 
0198         m_registersManager = new RegistersManager(m_splitter);
0199 
0200         m_config = KSharedConfig::openConfig()->group("Disassemble/Registers View");
0201 
0202         QByteArray state = m_config.readEntry<QByteArray>("splitterState", QByteArray());
0203         if (!state.isEmpty()) {
0204             m_splitter->restoreState(state);
0205         }
0206 
0207     }
0208 
0209     setLayout(topLayout);
0210 
0211     setWindowIcon( QIcon::fromTheme(QStringLiteral("system-run"), windowIcon()) );
0212     setWindowTitle(i18nc("@title:window", "Disassemble/Registers View"));
0213 
0214     KDevelop::IDebugController* pDC=KDevelop::ICore::self()->debugController();
0215     Q_ASSERT(pDC);
0216 
0217     connect(pDC,
0218             &KDevelop::IDebugController::currentSessionChanged,
0219             this, &DisassembleWidget::currentSessionChanged);
0220 
0221     connect(plugin, &MIDebuggerPlugin::reset, this, &DisassembleWidget::slotDeactivate);
0222 
0223     m_dlg = new SelectAddressDialog(this);
0224 
0225     // show the data if debug session is active
0226     KDevelop::IDebugSession* pS = pDC->currentSession();
0227 
0228     currentSessionChanged(pS);
0229 
0230     if(pS && !pS->currentAddr().isEmpty())
0231         slotShowStepInSource(pS->currentUrl(), pS->currentLine(), pS->currentAddr());
0232 }
0233 
0234 void DisassembleWidget::jumpToCursor() {
0235     auto *s = qobject_cast<MIDebugSession*>(KDevelop::ICore::
0236             self()->debugController()->currentSession());
0237     if (s && s->isRunning()) {
0238         QString address = m_disassembleWindow->selectedItems().at(0)->text(Address);
0239         s->jumpToMemoryAddress(address);
0240     }
0241 }
0242 
0243 void DisassembleWidget::runToCursor(){
0244     auto *s = qobject_cast<MIDebugSession*>(KDevelop::ICore::
0245             self()->debugController()->currentSession());
0246     if (s && s->isRunning()) {
0247         QString address = m_disassembleWindow->selectedItems().at(0)->text(Address);
0248         s->runUntil(address);
0249     }
0250 }
0251 
0252 void DisassembleWidget::currentSessionChanged(KDevelop::IDebugSession* s)
0253 {
0254     auto *session = qobject_cast<MIDebugSession*>(s);
0255 
0256     enableControls( session != nullptr ); // disable if session closed
0257 
0258     m_registersManager->setSession(session);
0259 
0260     if (session) {
0261         connect(session, &MIDebugSession::showStepInSource,
0262                 this, &DisassembleWidget::slotShowStepInSource);
0263         connect(session,&MIDebugSession::showStepInDisassemble,this, &DisassembleWidget::update);
0264     }
0265 }
0266 
0267 
0268 /***************************************************************************/
0269 
0270 DisassembleWidget::~DisassembleWidget()
0271 {
0272    m_config.writeEntry("splitterState", m_splitter->saveState());
0273 }
0274 
0275 /***************************************************************************/
0276 
0277 bool DisassembleWidget::displayCurrent()
0278 {
0279     if(address_ < lower_ || address_ > upper_) return false;
0280 
0281     bool bFound=false;
0282     for (int line=0; line < m_disassembleWindow->topLevelItemCount(); line++)
0283     {
0284         QTreeWidgetItem* item = m_disassembleWindow->topLevelItem(line);
0285         unsigned long address = item->text(Address).toULong(&ok,16);
0286 
0287         if (address == address_)
0288         {
0289             // put cursor at start of line and highlight the line
0290             m_disassembleWindow->setCurrentItem(item);
0291             item->setIcon(Icon, QIcon::fromTheme(QStringLiteral("go-next")));
0292             bFound = true;  // need to process all items to clear icons
0293         }
0294         else if(!item->icon(Icon).isNull()) item->setIcon(Icon, QIcon());
0295     }
0296 
0297     return bFound;
0298 }
0299 
0300 /***************************************************************************/
0301 
0302 void DisassembleWidget::slotActivate(bool activate)
0303 {
0304     qCDebug(DEBUGGERCOMMON) << "Disassemble widget active: " << activate;
0305 
0306     if (active_ != activate)
0307     {
0308         active_ = activate;
0309         if (active_)
0310         {
0311             updateDisassemblyFlavor();
0312             m_registersManager->updateRegisters();
0313             if (!displayCurrent())
0314                 disassembleMemoryRegion();
0315         }
0316     }
0317 }
0318 
0319 /***************************************************************************/
0320 
0321 void DisassembleWidget::slotShowStepInSource(const QUrl&, int,
0322         const QString& currentAddress)
0323 {
0324     update(currentAddress);
0325 }
0326 
0327 void DisassembleWidget::updateExecutionAddressHandler(const ResultRecord& r)
0328 {
0329     const Value& content = r[QStringLiteral("asm_insns")];
0330     const Value& pc = content[0];
0331     if( pc.hasField(QStringLiteral("address")) ){
0332         QString addr = pc[QStringLiteral("address")].literal();
0333         address_ = addr.toULong(&ok,16);
0334 
0335         disassembleMemoryRegion(addr);
0336     }
0337 }
0338 
0339 /***************************************************************************/
0340 
0341 void DisassembleWidget::disassembleMemoryRegion(const QString& from, const QString& to)
0342 {
0343     auto *s = qobject_cast<MIDebugSession*>(KDevelop::ICore::
0344             self()->debugController()->currentSession());
0345     if(!s || !s->isRunning()) return;
0346 
0347     //only get $pc
0348     if (from.isEmpty()){
0349         s->addCommand(DataDisassemble, QStringLiteral("-s \"$pc\" -e \"$pc+1\" -- 0"),
0350                       this, &DisassembleWidget::updateExecutionAddressHandler);
0351     }else{
0352 
0353         QString cmd = (to.isEmpty())?
0354         QStringLiteral("-s %1 -e \"%1 + 256\" -- 0").arg(from ):
0355         QStringLiteral("-s %1 -e %2+1 -- 0").arg(from, to); // if both addr set
0356 
0357         s->addCommand(DataDisassemble, cmd,
0358                       this, &DisassembleWidget::disassembleMemoryHandler);
0359    }
0360 }
0361 
0362 /***************************************************************************/
0363 
0364 void DisassembleWidget::disassembleMemoryHandler(const ResultRecord& r)
0365 {
0366     const Value& content = r[QStringLiteral("asm_insns")];
0367     QString currentFunction;
0368 
0369     m_disassembleWindow->clear();
0370 
0371     for(int i = 0; i < content.size(); ++i)
0372     {
0373         const Value& line = content[i];
0374 
0375         QString addr, fct, offs, inst;
0376 
0377         if( line.hasField(QStringLiteral("address")) )   addr = line[QStringLiteral("address")].literal();
0378         if( line.hasField(QStringLiteral("func-name")) ) fct  = line[QStringLiteral("func-name")].literal();
0379         if( line.hasField(QStringLiteral("offset")) )    offs = line[QStringLiteral("offset")].literal();
0380         if( line.hasField(QStringLiteral("inst")) )      inst = line[QStringLiteral("inst")].literal();
0381 
0382         //We use offset at the same column where function is.
0383         if(currentFunction == fct){
0384             if(!fct.isEmpty()){
0385                 fct = QLatin1Char('+') + offs;
0386             }
0387         }else { currentFunction = fct; }
0388 
0389         m_disassembleWindow->addTopLevelItem(new QTreeWidgetItem(m_disassembleWindow,
0390                                                                  QStringList{QString(), addr, fct, inst}));
0391 
0392         if (i == 0) {
0393             lower_ = addr.toULong(&ok,16);
0394         } else  if (i == content.size()-1) {
0395             upper_ = addr.toULong(&ok,16);
0396         }
0397     }
0398 
0399   displayCurrent();
0400 
0401   m_disassembleWindow->resizeColumnToContents(Icon);       // make Icon always visible
0402   m_disassembleWindow->resizeColumnToContents(Address);    // make entire address always visible
0403 }
0404 
0405 
0406 void DisassembleWidget::showEvent(QShowEvent*)
0407 {
0408     slotActivate(true);
0409 
0410     //it doesn't work for large names of functions
0411 //    for (int i = 0; i < m_disassembleWindow->model()->columnCount(); ++i)
0412 //        m_disassembleWindow->resizeColumnToContents(i);
0413 }
0414 
0415 void DisassembleWidget::hideEvent(QHideEvent*)
0416 {
0417     slotActivate(false);
0418 }
0419 
0420 void DisassembleWidget::slotDeactivate()
0421 {
0422     slotActivate(false);
0423 }
0424 
0425 void DisassembleWidget::enableControls(bool enabled)
0426 {
0427     m_disassembleWindow->setEnabled(enabled);
0428 }
0429 
0430 void DisassembleWidget::slotChangeAddress()
0431 {
0432     if(!m_dlg) return;
0433     m_dlg->updateOkState();
0434 
0435     if (!m_disassembleWindow->selectedItems().isEmpty()) {
0436         m_dlg->setAddress(m_disassembleWindow->selectedItems().first()->text(Address));
0437     }
0438 
0439     if (m_dlg->exec() == QDialog::Rejected)
0440         return;
0441 
0442     unsigned long addr = m_dlg->address().toULong(&ok,16);
0443 
0444     if (addr < lower_ || addr > upper_ || !displayCurrent())
0445         disassembleMemoryRegion(m_dlg->address());
0446 }
0447 
0448 void SelectAddressDialog::setAddress ( const QString& address )
0449 {
0450      m_ui.comboBox->setCurrentItem ( address, true );
0451 }
0452 
0453 void DisassembleWidget::update(const QString &address)
0454 {
0455     if (!active_) {
0456         return;
0457     }
0458 
0459     address_ = address.toULong(&ok, 16);
0460     if (!displayCurrent()) {
0461         disassembleMemoryRegion();
0462     }
0463     m_registersManager->updateRegisters();
0464 }
0465 
0466 void DisassembleWidget::setDisassemblyFlavor(QAction* action)
0467 {
0468     auto* s = qobject_cast<MIDebugSession*>(KDevelop::ICore::
0469             self()->debugController()->currentSession());
0470     if(!s || !s->isRunning()) {
0471         return;
0472     }
0473 
0474     DisassemblyFlavor disassemblyFlavor = static_cast<DisassemblyFlavor>(action->data().toInt());
0475     QString cmd;
0476     switch(disassemblyFlavor)
0477     {
0478     default:
0479         // unknown flavor, do not build a GDB command
0480         break;
0481     case DisassemblyFlavorATT:
0482         cmd = QStringLiteral("disassembly-flavor att");
0483         break;
0484     case DisassemblyFlavorIntel:
0485         cmd = QStringLiteral("disassembly-flavor intel");
0486         break;
0487     }
0488     qCDebug(DEBUGGERCOMMON) << "Disassemble widget set " << cmd;
0489 
0490     if (!cmd.isEmpty()) {
0491         s->addCommand(GdbSet, cmd, this, &DisassembleWidget::setDisassemblyFlavorHandler);
0492     }
0493 }
0494 
0495 void DisassembleWidget::setDisassemblyFlavorHandler(const ResultRecord& r)
0496 {
0497     if (r.reason == QLatin1String("done") && active_) {
0498         disassembleMemoryRegion();
0499     }
0500 }
0501 
0502 void DisassembleWidget::updateDisassemblyFlavor()
0503 {
0504     auto* s = qobject_cast<MIDebugSession*>(KDevelop::ICore::
0505             self()->debugController()->currentSession());
0506     if(!s || !s->isRunning()) {
0507         return;
0508     }
0509 
0510     s->addCommand(GdbShow, QStringLiteral("disassembly-flavor"), this, &DisassembleWidget::showDisassemblyFlavorHandler);
0511 }
0512 
0513 void DisassembleWidget::showDisassemblyFlavorHandler(const ResultRecord& r)
0514 {
0515     const Value& value = r[QStringLiteral("value")];
0516     qCDebug(DEBUGGERCOMMON) << "Disassemble widget disassembly flavor" << value.literal();
0517 
0518     DisassemblyFlavor disassemblyFlavor = DisassemblyFlavorUnknown;
0519     if (value.literal() == QLatin1String("att")) {
0520         disassemblyFlavor = DisassemblyFlavorATT;
0521     } else if (value.literal() == QLatin1String("intel")) {
0522         disassemblyFlavor = DisassemblyFlavorIntel;
0523     } else if (value.literal() == QLatin1String("default")) {
0524         disassemblyFlavor = DisassemblyFlavorATT;
0525     }
0526     m_disassembleWindow->setDisassemblyFlavor(disassemblyFlavor);
0527 }
0528 
0529 #include "moc_disassemblewidget.cpp"