debuggerwindow.cpp (22231B)
1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) 3 4 #include "debuggerwindow.h" 5 #include "debuggermodels.h" 6 #include "qthost.h" 7 #include "qtutils.h" 8 9 #include "common/assert.h" 10 #include "core/cpu_core_private.h" 11 12 #include <QtCore/QSignalBlocker> 13 #include <QtGui/QCursor> 14 #include <QtGui/QFontDatabase> 15 #include <QtWidgets/QFileDialog> 16 #include <QtWidgets/QMessageBox> 17 18 DebuggerWindow::DebuggerWindow(QWidget* parent /* = nullptr */) 19 : QMainWindow(parent), m_active_memory_region(Bus::MemoryRegion::Count) 20 { 21 m_ui.setupUi(this); 22 setupAdditionalUi(); 23 connectSignals(); 24 createModels(); 25 setMemoryViewRegion(Bus::MemoryRegion::RAM); 26 setUIEnabled(QtHost::IsSystemPaused(), QtHost::IsSystemValid()); 27 } 28 29 DebuggerWindow::~DebuggerWindow() = default; 30 31 void DebuggerWindow::onSystemStarted() 32 { 33 setUIEnabled(false, true); 34 } 35 36 void DebuggerWindow::onSystemDestroyed() 37 { 38 setUIEnabled(false, false); 39 } 40 41 void DebuggerWindow::onSystemPaused() 42 { 43 setUIEnabled(true, true); 44 refreshAll(); 45 46 { 47 QSignalBlocker sb(m_ui.actionPause); 48 m_ui.actionPause->setChecked(true); 49 } 50 } 51 52 void DebuggerWindow::onSystemResumed() 53 { 54 setUIEnabled(false, true); 55 56 { 57 QSignalBlocker sb(m_ui.actionPause); 58 m_ui.actionPause->setChecked(false); 59 } 60 } 61 62 void DebuggerWindow::onDebuggerMessageReported(const QString& message) 63 { 64 m_ui.statusbar->showMessage(message, 0); 65 } 66 67 void DebuggerWindow::refreshAll() 68 { 69 m_registers_model->updateValues(); 70 m_stack_model->invalidateView(); 71 m_ui.memoryView->repaint(); 72 73 m_code_model->setPC(CPU::g_state.pc); 74 scrollToPC(); 75 } 76 77 void DebuggerWindow::scrollToPC() 78 { 79 return scrollToCodeAddress(CPU::g_state.pc); 80 } 81 82 void DebuggerWindow::scrollToCodeAddress(VirtualMemoryAddress address) 83 { 84 m_code_model->ensureAddressVisible(address); 85 86 int row = m_code_model->getRowForAddress(address); 87 if (row >= 0) 88 { 89 qApp->processEvents(QEventLoop::ExcludeUserInputEvents); 90 m_ui.codeView->scrollTo(m_code_model->index(row, 0)); 91 } 92 } 93 94 void DebuggerWindow::onPauseActionToggled(bool paused) 95 { 96 if (!paused) 97 { 98 m_registers_model->saveCurrentValues(); 99 setUIEnabled(false, true); 100 } 101 102 g_emu_thread->setSystemPaused(paused); 103 } 104 105 void DebuggerWindow::onRunToCursorTriggered() 106 { 107 std::optional<VirtualMemoryAddress> addr = getSelectedCodeAddress(); 108 if (!addr.has_value()) 109 { 110 QMessageBox::critical(this, windowTitle(), tr("No address selected.")); 111 return; 112 } 113 114 CPU::AddBreakpoint(CPU::BreakpointType::Execute, addr.value(), true, true); 115 g_emu_thread->setSystemPaused(false); 116 } 117 118 void DebuggerWindow::onGoToPCTriggered() 119 { 120 scrollToPC(); 121 } 122 123 void DebuggerWindow::onGoToAddressTriggered() 124 { 125 std::optional<VirtualMemoryAddress> address = 126 QtUtils::PromptForAddress(this, windowTitle(), tr("Enter code address:"), true); 127 if (!address.has_value()) 128 return; 129 130 scrollToCodeAddress(address.value()); 131 } 132 133 void DebuggerWindow::onDumpAddressTriggered() 134 { 135 std::optional<VirtualMemoryAddress> address = 136 QtUtils::PromptForAddress(this, windowTitle(), tr("Enter memory address:"), false); 137 if (!address.has_value()) 138 return; 139 140 scrollToMemoryAddress(address.value()); 141 } 142 143 void DebuggerWindow::onTraceTriggered() 144 { 145 if (!CPU::IsTraceEnabled()) 146 { 147 QMessageBox::critical( 148 this, windowTitle(), 149 tr("Trace logging started to cpu_log.txt.\nThis file can be several gigabytes, so be aware of SSD wear.")); 150 CPU::StartTrace(); 151 } 152 else 153 { 154 CPU::StopTrace(); 155 QMessageBox::critical(this, windowTitle(), tr("Trace logging to cpu_log.txt stopped.")); 156 } 157 } 158 159 void DebuggerWindow::onFollowAddressTriggered() 160 { 161 // 162 } 163 164 void DebuggerWindow::onAddBreakpointTriggered() 165 { 166 DebuggerAddBreakpointDialog dlg(this); 167 if (!dlg.exec()) 168 return; 169 170 addBreakpoint(dlg.getType(), dlg.getAddress()); 171 } 172 173 void DebuggerWindow::onToggleBreakpointTriggered() 174 { 175 std::optional<VirtualMemoryAddress> address = getSelectedCodeAddress(); 176 if (!address.has_value()) 177 return; 178 179 toggleBreakpoint(address.value()); 180 } 181 182 void DebuggerWindow::onClearBreakpointsTriggered() 183 { 184 clearBreakpoints(); 185 } 186 187 void DebuggerWindow::onBreakpointListContextMenuRequested() 188 { 189 const QList<QTreeWidgetItem*> selected = m_ui.breakpointsWidget->selectedItems(); 190 if (selected.size() != 1) 191 return; 192 193 const QTreeWidgetItem* item = selected[0]; 194 const u32 address = item->data(1, Qt::UserRole).toUInt(); 195 const CPU::BreakpointType type = static_cast<CPU::BreakpointType>(item->data(2, Qt::UserRole).toUInt()); 196 197 QMenu menu(this); 198 connect(menu.addAction(tr("&Remove")), &QAction::triggered, this, 199 [this, address, type]() { removeBreakpoint(type, address); }); 200 menu.exec(QCursor::pos()); 201 } 202 203 void DebuggerWindow::onStepIntoActionTriggered() 204 { 205 Assert(System::IsPaused()); 206 m_registers_model->saveCurrentValues(); 207 g_emu_thread->singleStepCPU(); 208 } 209 210 void DebuggerWindow::onStepOverActionTriggered() 211 { 212 Assert(System::IsPaused()); 213 if (!CPU::AddStepOverBreakpoint()) 214 { 215 onStepIntoActionTriggered(); 216 return; 217 } 218 219 // unpause to let it run to the breakpoint 220 m_registers_model->saveCurrentValues(); 221 g_emu_thread->setSystemPaused(false); 222 } 223 224 void DebuggerWindow::onStepOutActionTriggered() 225 { 226 Assert(System::IsPaused()); 227 if (!CPU::AddStepOutBreakpoint()) 228 { 229 QMessageBox::critical(this, tr("Debugger"), tr("Failed to add step-out breakpoint, are you in a valid function?")); 230 return; 231 } 232 233 // unpause to let it run to the breakpoint 234 m_registers_model->saveCurrentValues(); 235 g_emu_thread->setSystemPaused(false); 236 } 237 238 void DebuggerWindow::onCodeViewItemActivated(QModelIndex index) 239 { 240 if (!index.isValid()) 241 return; 242 243 const VirtualMemoryAddress address = m_code_model->getAddressForIndex(index); 244 switch (index.column()) 245 { 246 case 0: // breakpoint 247 case 3: // disassembly 248 toggleBreakpoint(address); 249 break; 250 251 case 1: // address 252 case 2: // bytes 253 scrollToMemoryAddress(address); 254 break; 255 256 case 4: // comment 257 tryFollowLoadStore(address); 258 break; 259 } 260 } 261 262 void DebuggerWindow::onCodeViewContextMenuRequested(const QPoint& pt) 263 { 264 const QModelIndex index = m_ui.codeView->indexAt(pt); 265 if (!index.isValid()) 266 return; 267 268 const VirtualMemoryAddress address = m_code_model->getAddressForIndex(index); 269 270 QMenu menu; 271 menu.addAction(QStringLiteral("0x%1").arg(static_cast<uint>(address), 8, 16, QChar('0')))->setEnabled(false); 272 menu.addSeparator(); 273 274 QAction* action = menu.addAction(QIcon::fromTheme("debug-toggle-breakpoint"), tr("Toggle &Breakpoint")); 275 connect(action, &QAction::triggered, this, [this, address]() { toggleBreakpoint(address); }); 276 277 action = menu.addAction(QIcon::fromTheme("debugger-go-to-cursor"), tr("&Run To Cursor")); 278 connect(action, &QAction::triggered, this, [address]() { 279 Host::RunOnCPUThread([address]() { 280 CPU::AddBreakpoint(CPU::BreakpointType::Execute, address, true, true); 281 g_emu_thread->setSystemPaused(false); 282 }); 283 }); 284 285 menu.addSeparator(); 286 action = menu.addAction(QIcon::fromTheme("debugger-go-to-address"), tr("View in &Dump")); 287 connect(action, &QAction::triggered, this, [this, address]() { scrollToMemoryAddress(address); }); 288 289 action = menu.addAction(QIcon::fromTheme("debug-trace-line"), tr("&Follow Load/Store")); 290 connect(action, &QAction::triggered, this, [this, address]() { tryFollowLoadStore(address); }); 291 292 menu.exec(m_ui.codeView->mapToGlobal(pt)); 293 } 294 295 void DebuggerWindow::onMemorySearchTriggered() 296 { 297 m_ui.memoryView->clearHighlightRange(); 298 299 const QString pattern_str = m_ui.memorySearchString->text(); 300 if (pattern_str.isEmpty()) 301 return; 302 303 std::vector<u8> pattern; 304 std::vector<u8> mask; 305 u8 spattern = 0; 306 u8 smask = 0; 307 bool msb = false; 308 309 pattern.reserve(static_cast<size_t>(pattern_str.length()) / 2); 310 mask.reserve(static_cast<size_t>(pattern_str.length()) / 2); 311 312 for (int i = 0; i < pattern_str.length(); i++) 313 { 314 const QChar ch = pattern_str[i]; 315 if (ch == ' ') 316 continue; 317 318 if (ch == '?') 319 { 320 spattern = (spattern << 4); 321 smask = (smask << 4); 322 } 323 else if (ch.isDigit()) 324 { 325 spattern = (spattern << 4) | static_cast<u8>(ch.digitValue()); 326 smask = (smask << 4) | 0xF; 327 } 328 else if (ch.unicode() >= 'a' && ch.unicode() <= 'f') 329 { 330 spattern = (spattern << 4) | (0xA + static_cast<u8>(ch.unicode() - 'a')); 331 smask = (smask << 4) | 0xF; 332 } 333 else if (ch.unicode() >= 'A' && ch.unicode() <= 'F') 334 { 335 spattern = (spattern << 4) | (0xA + static_cast<u8>(ch.unicode() - 'A')); 336 smask = (smask << 4) | 0xF; 337 } 338 else 339 { 340 QMessageBox::critical(this, windowTitle(), 341 tr("Invalid search pattern. It should contain hex digits or question marks.")); 342 return; 343 } 344 345 if (msb) 346 { 347 pattern.push_back(spattern); 348 mask.push_back(smask); 349 spattern = 0; 350 smask = 0; 351 } 352 353 msb = !msb; 354 } 355 356 if (msb) 357 { 358 // partial byte on the end 359 spattern = (spattern << 4); 360 smask = (smask << 4); 361 pattern.push_back(spattern); 362 mask.push_back(smask); 363 } 364 365 if (pattern.empty()) 366 { 367 QMessageBox::critical(this, windowTitle(), 368 tr("Invalid search pattern. It should contain hex digits or question marks.")); 369 return; 370 } 371 372 std::optional<PhysicalMemoryAddress> found_address = 373 Bus::SearchMemory(m_next_memory_search_address, pattern.data(), mask.data(), static_cast<u32>(pattern.size())); 374 bool wrapped_around = false; 375 if (!found_address.has_value()) 376 { 377 found_address = Bus::SearchMemory(0, pattern.data(), mask.data(), static_cast<u32>(pattern.size())); 378 if (!found_address.has_value()) 379 { 380 m_ui.statusbar->showMessage(tr("Pattern not found.")); 381 return; 382 } 383 384 wrapped_around = true; 385 } 386 387 m_next_memory_search_address = found_address.value() + 1; 388 if (scrollToMemoryAddress(found_address.value())) 389 { 390 const size_t highlight_offset = found_address.value() - m_ui.memoryView->addressOffset(); 391 m_ui.memoryView->setHighlightRange(highlight_offset, highlight_offset + pattern.size()); 392 } 393 394 if (wrapped_around) 395 { 396 m_ui.statusbar->showMessage(tr("Pattern found at 0x%1 (passed the end of memory).") 397 .arg(static_cast<uint>(found_address.value()), 8, 16, static_cast<QChar>('0'))); 398 } 399 else 400 { 401 m_ui.statusbar->showMessage( 402 tr("Pattern found at 0x%1.").arg(static_cast<uint>(found_address.value()), 8, 16, static_cast<QChar>('0'))); 403 } 404 } 405 406 void DebuggerWindow::onMemorySearchStringChanged(const QString&) 407 { 408 m_next_memory_search_address = 0; 409 } 410 411 void DebuggerWindow::closeEvent(QCloseEvent* event) 412 { 413 g_emu_thread->disconnect(this); 414 Host::RunOnCPUThread(&CPU::ClearBreakpoints); 415 QMainWindow::closeEvent(event); 416 emit closed(); 417 } 418 419 void DebuggerWindow::setupAdditionalUi() 420 { 421 setWindowIcon(QtHost::GetAppIcon()); 422 423 #ifdef _WIN32 424 QFont fixedFont; 425 fixedFont.setFamily(QStringLiteral("Consolas")); 426 fixedFont.setFixedPitch(true); 427 fixedFont.setStyleHint(QFont::TypeWriter); 428 fixedFont.setPointSize(10); 429 #else 430 const QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); 431 #endif 432 m_ui.codeView->setFont(fixedFont); 433 m_ui.registerView->setFont(fixedFont); 434 m_ui.memoryView->setFont(fixedFont); 435 m_ui.stackView->setFont(fixedFont); 436 437 m_ui.codeView->setContextMenuPolicy(Qt::CustomContextMenu); 438 m_ui.breakpointsWidget->setContextMenuPolicy(Qt::CustomContextMenu); 439 440 setCentralWidget(nullptr); 441 delete m_ui.centralwidget; 442 } 443 444 void DebuggerWindow::connectSignals() 445 { 446 connect(g_emu_thread, &EmuThread::systemPaused, this, &DebuggerWindow::onSystemPaused); 447 connect(g_emu_thread, &EmuThread::systemResumed, this, &DebuggerWindow::onSystemResumed); 448 connect(g_emu_thread, &EmuThread::systemStarted, this, &DebuggerWindow::onSystemStarted); 449 connect(g_emu_thread, &EmuThread::systemDestroyed, this, &DebuggerWindow::onSystemDestroyed); 450 connect(g_emu_thread, &EmuThread::debuggerMessageReported, this, &DebuggerWindow::onDebuggerMessageReported); 451 452 connect(m_ui.actionPause, &QAction::toggled, this, &DebuggerWindow::onPauseActionToggled); 453 connect(m_ui.actionRunToCursor, &QAction::triggered, this, &DebuggerWindow::onRunToCursorTriggered); 454 connect(m_ui.actionGoToPC, &QAction::triggered, this, &DebuggerWindow::onGoToPCTriggered); 455 connect(m_ui.actionGoToAddress, &QAction::triggered, this, &DebuggerWindow::onGoToAddressTriggered); 456 connect(m_ui.actionDumpAddress, &QAction::triggered, this, &DebuggerWindow::onDumpAddressTriggered); 457 connect(m_ui.actionTrace, &QAction::triggered, this, &DebuggerWindow::onTraceTriggered); 458 connect(m_ui.actionStepInto, &QAction::triggered, this, &DebuggerWindow::onStepIntoActionTriggered); 459 connect(m_ui.actionStepOver, &QAction::triggered, this, &DebuggerWindow::onStepOverActionTriggered); 460 connect(m_ui.actionStepOut, &QAction::triggered, this, &DebuggerWindow::onStepOutActionTriggered); 461 connect(m_ui.actionAddBreakpoint, &QAction::triggered, this, &DebuggerWindow::onAddBreakpointTriggered); 462 connect(m_ui.actionToggleBreakpoint, &QAction::triggered, this, &DebuggerWindow::onToggleBreakpointTriggered); 463 connect(m_ui.actionClearBreakpoints, &QAction::triggered, this, &DebuggerWindow::onClearBreakpointsTriggered); 464 connect(m_ui.actionClose, &QAction::triggered, this, &DebuggerWindow::close); 465 connect(m_ui.codeView, &QTreeView::activated, this, &DebuggerWindow::onCodeViewItemActivated); 466 connect(m_ui.codeView, &QTreeView::customContextMenuRequested, this, &DebuggerWindow::onCodeViewContextMenuRequested); 467 connect(m_ui.breakpointsWidget, &QTreeWidget::customContextMenuRequested, this, 468 &DebuggerWindow::onBreakpointListContextMenuRequested); 469 470 connect(m_ui.memoryRegionRAM, &QRadioButton::clicked, [this]() { setMemoryViewRegion(Bus::MemoryRegion::RAM); }); 471 connect(m_ui.memoryRegionEXP1, &QRadioButton::clicked, [this]() { setMemoryViewRegion(Bus::MemoryRegion::EXP1); }); 472 connect(m_ui.memoryRegionScratchpad, &QRadioButton::clicked, 473 [this]() { setMemoryViewRegion(Bus::MemoryRegion::Scratchpad); }); 474 connect(m_ui.memoryRegionBIOS, &QRadioButton::clicked, [this]() { setMemoryViewRegion(Bus::MemoryRegion::BIOS); }); 475 476 connect(m_ui.memorySearch, &QPushButton::clicked, this, &DebuggerWindow::onMemorySearchTriggered); 477 connect(m_ui.memorySearchString, &QLineEdit::textChanged, this, &DebuggerWindow::onMemorySearchStringChanged); 478 } 479 480 void DebuggerWindow::disconnectSignals() 481 { 482 EmuThread* hi = g_emu_thread; 483 hi->disconnect(this); 484 } 485 486 void DebuggerWindow::createModels() 487 { 488 m_code_model = std::make_unique<DebuggerCodeModel>(); 489 m_ui.codeView->setModel(m_code_model.get()); 490 491 // set default column width in code view 492 m_ui.codeView->setColumnWidth(0, 40); 493 m_ui.codeView->setColumnWidth(1, 80); 494 m_ui.codeView->setColumnWidth(2, 80); 495 m_ui.codeView->setColumnWidth(3, 250); 496 m_ui.codeView->setColumnWidth(4, m_ui.codeView->width() - (40 + 80 + 80 + 250)); 497 498 m_registers_model = std::make_unique<DebuggerRegistersModel>(); 499 m_ui.registerView->setModel(m_registers_model.get()); 500 // m_ui->registerView->resizeRowsToContents(); 501 502 m_stack_model = std::make_unique<DebuggerStackModel>(); 503 m_ui.stackView->setModel(m_stack_model.get()); 504 505 m_ui.breakpointsWidget->setColumnWidth(0, 50); 506 m_ui.breakpointsWidget->setColumnWidth(1, 80); 507 m_ui.breakpointsWidget->setColumnWidth(2, 50); 508 m_ui.breakpointsWidget->setColumnWidth(3, 40); 509 m_ui.breakpointsWidget->setRootIsDecorated(false); 510 } 511 512 void DebuggerWindow::setUIEnabled(bool enabled, bool allow_pause) 513 { 514 m_ui.actionPause->setEnabled(allow_pause); 515 516 // Disable all UI elements that depend on execution state 517 m_ui.codeView->setEnabled(enabled); 518 m_ui.registerView->setEnabled(enabled); 519 m_ui.stackView->setEnabled(enabled); 520 m_ui.memoryView->setEnabled(enabled); 521 m_ui.actionRunToCursor->setEnabled(enabled); 522 m_ui.actionAddBreakpoint->setEnabled(enabled); 523 m_ui.actionToggleBreakpoint->setEnabled(enabled); 524 m_ui.actionClearBreakpoints->setEnabled(enabled); 525 m_ui.actionDumpAddress->setEnabled(enabled); 526 m_ui.actionStepInto->setEnabled(enabled); 527 m_ui.actionStepOver->setEnabled(enabled); 528 m_ui.actionStepOut->setEnabled(enabled); 529 m_ui.actionGoToAddress->setEnabled(enabled); 530 m_ui.actionGoToPC->setEnabled(enabled); 531 m_ui.actionTrace->setEnabled(enabled); 532 m_ui.memoryRegionRAM->setEnabled(enabled); 533 m_ui.memoryRegionEXP1->setEnabled(enabled); 534 m_ui.memoryRegionScratchpad->setEnabled(enabled); 535 m_ui.memoryRegionBIOS->setEnabled(enabled); 536 } 537 538 void DebuggerWindow::setMemoryViewRegion(Bus::MemoryRegion region) 539 { 540 if (m_active_memory_region == region) 541 return; 542 543 m_active_memory_region = region; 544 545 const PhysicalMemoryAddress start = Bus::GetMemoryRegionStart(region); 546 const PhysicalMemoryAddress end = Bus::GetMemoryRegionEnd(region); 547 m_ui.memoryView->setData(start, Bus::GetMemoryRegionPointer(region), end - start); 548 549 #define SET_REGION_RADIO_BUTTON(name, rb_region) \ 550 do \ 551 { \ 552 QSignalBlocker sb(name); \ 553 name->setChecked(region == rb_region); \ 554 } while (0) 555 556 SET_REGION_RADIO_BUTTON(m_ui.memoryRegionRAM, Bus::MemoryRegion::RAM); 557 SET_REGION_RADIO_BUTTON(m_ui.memoryRegionEXP1, Bus::MemoryRegion::EXP1); 558 SET_REGION_RADIO_BUTTON(m_ui.memoryRegionScratchpad, Bus::MemoryRegion::Scratchpad); 559 SET_REGION_RADIO_BUTTON(m_ui.memoryRegionBIOS, Bus::MemoryRegion::BIOS); 560 561 #undef SET_REGION_REGION_BUTTON 562 563 m_ui.memoryView->repaint(); 564 } 565 566 void DebuggerWindow::toggleBreakpoint(VirtualMemoryAddress address) 567 { 568 Host::RunOnCPUThread([this, address]() { 569 const bool new_bp_state = !CPU::HasBreakpointAtAddress(CPU::BreakpointType::Execute, address); 570 if (new_bp_state) 571 { 572 if (!CPU::AddBreakpoint(CPU::BreakpointType::Execute, address, false)) 573 return; 574 } 575 else 576 { 577 if (!CPU::RemoveBreakpoint(CPU::BreakpointType::Execute, address)) 578 return; 579 } 580 581 QtHost::RunOnUIThread([this, address, new_bp_state, bps = CPU::CopyBreakpointList()]() { 582 m_code_model->setBreakpointState(address, new_bp_state); 583 refreshBreakpointList(bps); 584 }); 585 }); 586 } 587 588 void DebuggerWindow::clearBreakpoints() 589 { 590 m_code_model->clearBreakpoints(); 591 Host::RunOnCPUThread(&CPU::ClearBreakpoints); 592 } 593 594 std::optional<VirtualMemoryAddress> DebuggerWindow::getSelectedCodeAddress() 595 { 596 QItemSelectionModel* sel_model = m_ui.codeView->selectionModel(); 597 const QModelIndexList indices(sel_model->selectedIndexes()); 598 if (indices.empty()) 599 return std::nullopt; 600 601 return m_code_model->getAddressForIndex(indices[0]); 602 } 603 604 bool DebuggerWindow::tryFollowLoadStore(VirtualMemoryAddress address) 605 { 606 CPU::Instruction inst; 607 if (!CPU::SafeReadInstruction(address, &inst.bits)) 608 return false; 609 610 const std::optional<VirtualMemoryAddress> ea = GetLoadStoreEffectiveAddress(inst, &CPU::g_state.regs); 611 if (!ea.has_value()) 612 return false; 613 614 scrollToMemoryAddress(ea.value()); 615 return true; 616 } 617 618 bool DebuggerWindow::scrollToMemoryAddress(VirtualMemoryAddress address) 619 { 620 const PhysicalMemoryAddress phys_address = CPU::VirtualAddressToPhysical(address); 621 std::optional<Bus::MemoryRegion> region = Bus::GetMemoryRegionForAddress(phys_address); 622 if (!region.has_value()) 623 return false; 624 625 setMemoryViewRegion(region.value()); 626 627 const PhysicalMemoryAddress offset = phys_address - Bus::GetMemoryRegionStart(region.value()); 628 m_ui.memoryView->scrolltoOffset(offset); 629 return true; 630 } 631 632 void DebuggerWindow::refreshBreakpointList() 633 { 634 Host::RunOnCPUThread( 635 [this]() { QtHost::RunOnUIThread([this, bps = CPU::CopyBreakpointList()]() { refreshBreakpointList(bps); }); }); 636 } 637 638 void DebuggerWindow::refreshBreakpointList(const CPU::BreakpointList& bps) 639 { 640 while (m_ui.breakpointsWidget->topLevelItemCount() > 0) 641 delete m_ui.breakpointsWidget->takeTopLevelItem(0); 642 643 for (const CPU::Breakpoint& bp : bps) 644 { 645 QTreeWidgetItem* item = new QTreeWidgetItem(); 646 item->setFlags(item->flags() | Qt::ItemIsUserCheckable); 647 item->setCheckState(0, bp.enabled ? Qt::Checked : Qt::Unchecked); 648 item->setText(0, QString::asprintf("%u", bp.number)); 649 item->setText(1, QString::asprintf("0x%08X", bp.address)); 650 item->setText(2, QString::fromUtf8(CPU::GetBreakpointTypeName(bp.type))); 651 item->setText(3, QString::asprintf("%u", bp.hit_count)); 652 item->setData(0, Qt::UserRole, bp.number); 653 item->setData(1, Qt::UserRole, bp.address); 654 item->setData(2, Qt::UserRole, static_cast<u32>(bp.type)); 655 m_ui.breakpointsWidget->addTopLevelItem(item); 656 } 657 } 658 659 void DebuggerWindow::addBreakpoint(CPU::BreakpointType type, u32 address) 660 { 661 Host::RunOnCPUThread([this, address, type]() { 662 const bool result = CPU::AddBreakpoint(type, address); 663 QtHost::RunOnUIThread([this, address, type, result, bps = CPU::CopyBreakpointList()]() { 664 if (!result) 665 { 666 QMessageBox::critical(this, windowTitle(), 667 tr("Failed to add breakpoint. A breakpoint may already exist at this address.")); 668 return; 669 } 670 671 if (type == CPU::BreakpointType::Execute) 672 m_code_model->setBreakpointState(address, true); 673 674 refreshBreakpointList(bps); 675 }); 676 }); 677 } 678 679 void DebuggerWindow::removeBreakpoint(CPU::BreakpointType type, u32 address) 680 { 681 Host::RunOnCPUThread([this, address, type]() { 682 const bool result = CPU::RemoveBreakpoint(type, address); 683 QtHost::RunOnUIThread([this, address, type, result, bps = CPU::CopyBreakpointList()]() { 684 if (!result) 685 { 686 QMessageBox::critical(this, windowTitle(), tr("Failed to remove breakpoint. This breakpoint may not exist.")); 687 return; 688 } 689 690 if (type == CPU::BreakpointType::Execute) 691 m_code_model->setBreakpointState(address, false); 692 693 refreshBreakpointList(bps); 694 }); 695 }); 696 }