duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

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 }