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"