mirror of
https://github.com/RPCSX/rpcsx.git
synced 2025-12-06 07:12:14 +01:00
A few more items: Standardising on 'OK' for all base language uses (rather than 'Ok', or 'Okay'). It's perceived as the 'most correct' variant, and importantly having a single variant is best from a translation perspective. Added plurality handling for multiple PPU caches created. Added plurality handling for multiple items deleted in save manager. Capitalised trophy grade to align with Sony terminology. Brought trophy name position into translatable string (for languages that might really want to deviate from SVO)
632 lines
17 KiB
C++
632 lines
17 KiB
C++
#include "debugger_frame.h"
|
|
#include "register_editor_dialog.h"
|
|
#include "instruction_editor_dialog.h"
|
|
#include "gui_settings.h"
|
|
#include "debugger_list.h"
|
|
#include "breakpoint_list.h"
|
|
#include "breakpoint_handler.h"
|
|
#include "call_stack_list.h"
|
|
#include "qt_utils.h"
|
|
|
|
#include "Emu/System.h"
|
|
#include "Emu/IdManager.h"
|
|
#include "Emu/Cell/PPUDisAsm.h"
|
|
#include "Emu/Cell/PPUThread.h"
|
|
#include "Emu/Cell/SPUDisAsm.h"
|
|
#include "Emu/Cell/SPUThread.h"
|
|
#include "Emu/CPU/CPUThread.h"
|
|
#include "Emu/CPU/CPUDisAsm.h"
|
|
|
|
#include <QKeyEvent>
|
|
#include <QScrollBar>
|
|
#include <QApplication>
|
|
#include <QFontDatabase>
|
|
#include <QCompleter>
|
|
#include <QMenu>
|
|
#include <QVBoxLayout>
|
|
#include <QTimer>
|
|
#include <atomic>
|
|
|
|
constexpr auto qstr = QString::fromStdString;
|
|
|
|
debugger_frame::debugger_frame(std::shared_ptr<gui_settings> settings, QWidget *parent)
|
|
: custom_dock_widget(tr("Debugger"), parent), xgui_settings(settings)
|
|
{
|
|
setContentsMargins(0, 0, 0, 0);
|
|
|
|
m_update = new QTimer(this);
|
|
connect(m_update, &QTimer::timeout, this, &debugger_frame::UpdateUI);
|
|
EnableUpdateTimer(true);
|
|
|
|
m_mono = QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
|
m_mono.setPointSize(10);
|
|
|
|
QVBoxLayout* vbox_p_main = new QVBoxLayout();
|
|
vbox_p_main->setContentsMargins(5, 5, 5, 5);
|
|
|
|
QHBoxLayout* hbox_b_main = new QHBoxLayout();
|
|
hbox_b_main->setContentsMargins(0, 0, 0, 0);
|
|
|
|
m_breakpoint_handler = new breakpoint_handler();
|
|
m_breakpoint_list = new breakpoint_list(this, m_breakpoint_handler);
|
|
|
|
m_debugger_list = new debugger_list(this, settings, m_breakpoint_handler);
|
|
m_debugger_list->installEventFilter(this);
|
|
|
|
m_call_stack_list = new call_stack_list(this);
|
|
|
|
m_choice_units = new QComboBox(this);
|
|
m_choice_units->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
|
m_choice_units->setMaxVisibleItems(30);
|
|
m_choice_units->setMaximumWidth(500);
|
|
m_choice_units->setEditable(true);
|
|
m_choice_units->setInsertPolicy(QComboBox::NoInsert);
|
|
m_choice_units->lineEdit()->setPlaceholderText(tr("Choose a thread"));
|
|
m_choice_units->completer()->setCompletionMode(QCompleter::PopupCompletion);
|
|
m_choice_units->completer()->setMaxVisibleItems(30);
|
|
m_choice_units->completer()->setFilterMode(Qt::MatchContains);
|
|
|
|
m_go_to_addr = new QPushButton(tr("Go To Address"), this);
|
|
m_go_to_pc = new QPushButton(tr("Go To PC"), this);
|
|
m_btn_step = new QPushButton(tr("Step"), this);
|
|
m_btn_step_over = new QPushButton(tr("Step Over"), this);
|
|
m_btn_run = new QPushButton(RunString, this);
|
|
|
|
EnableButtons(false);
|
|
|
|
ChangeColors();
|
|
|
|
hbox_b_main->addWidget(m_go_to_addr);
|
|
hbox_b_main->addWidget(m_go_to_pc);
|
|
hbox_b_main->addWidget(m_btn_step);
|
|
hbox_b_main->addWidget(m_btn_step_over);
|
|
hbox_b_main->addWidget(m_btn_run);
|
|
hbox_b_main->addWidget(m_choice_units);
|
|
hbox_b_main->addStretch();
|
|
|
|
// Misc state
|
|
m_misc_state = new QTextEdit(this);
|
|
m_misc_state->setLineWrapMode(QTextEdit::NoWrap);
|
|
m_misc_state->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
|
|
|
|
// Registers
|
|
m_regs = new QTextEdit(this);
|
|
m_regs->setLineWrapMode(QTextEdit::NoWrap);
|
|
m_regs->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
|
|
|
|
m_debugger_list->setFont(m_mono);
|
|
m_misc_state->setFont(m_mono);
|
|
m_regs->setFont(m_mono);
|
|
m_call_stack_list->setFont(m_mono);
|
|
|
|
m_right_splitter = new QSplitter(this);
|
|
m_right_splitter->setOrientation(Qt::Vertical);
|
|
m_right_splitter->addWidget(m_misc_state);
|
|
m_right_splitter->addWidget(m_regs);
|
|
m_right_splitter->addWidget(m_call_stack_list);
|
|
m_right_splitter->addWidget(m_breakpoint_list);
|
|
|
|
// Set relative sizes for widgets
|
|
m_right_splitter->setStretchFactor(0, 2); // misc state
|
|
m_right_splitter->setStretchFactor(1, 8); // registers
|
|
m_right_splitter->setStretchFactor(2, 3); // call stack
|
|
m_right_splitter->setStretchFactor(3, 1); // breakpoint list
|
|
|
|
m_splitter = new QSplitter(this);
|
|
m_splitter->addWidget(m_debugger_list);
|
|
m_splitter->addWidget(m_right_splitter);
|
|
|
|
QHBoxLayout* hbox_w_list = new QHBoxLayout();
|
|
hbox_w_list->addWidget(m_splitter);
|
|
|
|
vbox_p_main->addLayout(hbox_b_main);
|
|
vbox_p_main->addLayout(hbox_w_list);
|
|
|
|
QWidget* body = new QWidget(this);
|
|
body->setLayout(vbox_p_main);
|
|
setWidget(body);
|
|
|
|
connect(m_go_to_addr, &QAbstractButton::clicked, this, &debugger_frame::ShowGotoAddressDialog);
|
|
connect(m_go_to_pc, &QAbstractButton::clicked, this, &debugger_frame::ShowPC);
|
|
|
|
connect(m_btn_step, &QAbstractButton::clicked, this, &debugger_frame::DoStep);
|
|
connect(m_btn_step_over, &QAbstractButton::clicked, [this]() { DoStep(true); });
|
|
|
|
connect(m_btn_run, &QAbstractButton::clicked, [this]()
|
|
{
|
|
if (const auto cpu = this->cpu.lock())
|
|
{
|
|
if (m_btn_run->text() == RunString && cpu->state.test_and_reset(cpu_flag::dbg_pause))
|
|
{
|
|
if (!(cpu->state & (cpu_flag::dbg_pause + cpu_flag::dbg_global_pause)))
|
|
{
|
|
cpu->notify();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cpu->state += cpu_flag::dbg_pause;
|
|
}
|
|
}
|
|
UpdateUI();
|
|
});
|
|
|
|
connect(m_choice_units->lineEdit(), &QLineEdit::editingFinished, [&]
|
|
{
|
|
m_choice_units->clearFocus();
|
|
});
|
|
|
|
connect(m_choice_units, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &debugger_frame::UpdateUI);
|
|
connect(m_choice_units, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &debugger_frame::OnSelectUnit);
|
|
connect(this, &QDockWidget::visibilityChanged, this, &debugger_frame::EnableUpdateTimer);
|
|
|
|
connect(m_debugger_list, &debugger_list::BreakpointRequested, m_breakpoint_list, &breakpoint_list::HandleBreakpointRequest);
|
|
connect(m_breakpoint_list, &breakpoint_list::RequestShowAddress, m_debugger_list, &debugger_list::ShowAddress);
|
|
|
|
connect(this, &debugger_frame::CallStackUpdateRequested, m_call_stack_list, &call_stack_list::HandleUpdate);
|
|
connect(m_call_stack_list, &call_stack_list::RequestShowAddress, m_debugger_list, &debugger_list::ShowAddress);
|
|
|
|
m_debugger_list->ShowAddress(m_debugger_list->m_pc);
|
|
UpdateUnitList();
|
|
}
|
|
|
|
void debugger_frame::SaveSettings()
|
|
{
|
|
xgui_settings->SetValue(gui::d_splitterState, m_splitter->saveState());
|
|
}
|
|
|
|
void debugger_frame::ChangeColors()
|
|
{
|
|
if (m_debugger_list)
|
|
{
|
|
m_debugger_list->m_color_bp = m_breakpoint_list->m_color_bp = gui::utils::get_label_color("debugger_frame_breakpoint", QPalette::Window);
|
|
m_debugger_list->m_color_pc = gui::utils::get_label_color("debugger_frame_pc", QPalette::Window);
|
|
m_debugger_list->m_text_color_bp = m_breakpoint_list->m_text_color_bp = gui::utils::get_label_color("debugger_frame_breakpoint");;
|
|
m_debugger_list->m_text_color_pc = gui::utils::get_label_color("debugger_frame_pc");;
|
|
}
|
|
}
|
|
|
|
bool debugger_frame::eventFilter(QObject* object, QEvent* ev)
|
|
{
|
|
// There's no overlap between keys so returning true wouldn't matter.
|
|
if (object == m_debugger_list && ev->type() == QEvent::KeyPress)
|
|
{
|
|
keyPressEvent(static_cast<QKeyEvent*>(ev));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void debugger_frame::closeEvent(QCloseEvent *event)
|
|
{
|
|
QDockWidget::closeEvent(event);
|
|
Q_EMIT DebugFrameClosed();
|
|
}
|
|
|
|
void debugger_frame::showEvent(QShowEvent * event)
|
|
{
|
|
// resize splitter widgets
|
|
if (!m_splitter->restoreState(xgui_settings->GetValue(gui::d_splitterState).toByteArray()))
|
|
{
|
|
const int width_right = width() / 3;
|
|
const int width_left = width() - width_right;
|
|
m_splitter->setSizes({width_left, width_right});
|
|
}
|
|
|
|
QDockWidget::showEvent(event);
|
|
}
|
|
|
|
void debugger_frame::hideEvent(QHideEvent * event)
|
|
{
|
|
// save splitter state or it will resume its initial state on next show
|
|
xgui_settings->SetValue(gui::d_splitterState, m_splitter->saveState());
|
|
QDockWidget::hideEvent(event);
|
|
}
|
|
|
|
void debugger_frame::keyPressEvent(QKeyEvent* event)
|
|
{
|
|
const auto cpu = this->cpu.lock();
|
|
int i = m_debugger_list->currentRow();
|
|
|
|
if (!isActiveWindow() || i < 0 || !cpu || m_no_thread_selected)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const u32 pc = m_debugger_list->m_pc + i * 4;
|
|
|
|
if (QApplication::keyboardModifiers() & Qt::ControlModifier)
|
|
{
|
|
switch (event->key())
|
|
{
|
|
case Qt::Key_G:
|
|
{
|
|
ShowGotoAddressDialog();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (event->key())
|
|
{
|
|
case Qt::Key_E:
|
|
{
|
|
instruction_editor_dialog* dlg = new instruction_editor_dialog(this, pc, cpu, m_disasm.get());
|
|
dlg->show();
|
|
return;
|
|
}
|
|
case Qt::Key_R:
|
|
{
|
|
register_editor_dialog* dlg = new register_editor_dialog(this, pc, cpu, m_disasm.get());
|
|
dlg->show();
|
|
return;
|
|
}
|
|
|
|
case Qt::Key_F10:
|
|
{
|
|
DoStep(true);
|
|
return;
|
|
}
|
|
case Qt::Key_F11:
|
|
{
|
|
DoStep(false);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
u32 debugger_frame::GetPc() const
|
|
{
|
|
const auto cpu = this->cpu.lock();
|
|
|
|
if (!cpu)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return cpu->id_type() == 1 ? static_cast<ppu_thread*>(cpu.get())->cia : static_cast<spu_thread*>(cpu.get())->pc;
|
|
}
|
|
|
|
void debugger_frame::UpdateUI()
|
|
{
|
|
UpdateUnitList();
|
|
|
|
if (m_no_thread_selected) return;
|
|
|
|
const auto cpu = this->cpu.lock();
|
|
|
|
if (!cpu)
|
|
{
|
|
if (m_last_pc != umax || m_last_stat)
|
|
{
|
|
m_last_pc = -1;
|
|
m_last_stat = 0;
|
|
DoUpdate();
|
|
}
|
|
|
|
m_btn_run->setEnabled(false);
|
|
m_btn_step->setEnabled(false);
|
|
m_btn_step_over->setEnabled(false);
|
|
}
|
|
else
|
|
{
|
|
const auto cia = GetPc();
|
|
const auto state = cpu->state.load();
|
|
|
|
if (m_last_pc != cia || m_last_stat != static_cast<u32>(state))
|
|
{
|
|
m_last_pc = cia;
|
|
m_last_stat = static_cast<u32>(state);
|
|
DoUpdate();
|
|
|
|
if (state & cpu_flag::dbg_pause)
|
|
{
|
|
m_btn_run->setText(RunString);
|
|
m_btn_step->setEnabled(true);
|
|
m_btn_step_over->setEnabled(true);
|
|
}
|
|
else
|
|
{
|
|
m_btn_run->setText(PauseString);
|
|
m_btn_step->setEnabled(false);
|
|
m_btn_step_over->setEnabled(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void debugger_frame::UpdateUnitList()
|
|
{
|
|
const u64 threads_created = cpu_thread::g_threads_created;
|
|
const u64 threads_deleted = cpu_thread::g_threads_deleted;
|
|
|
|
if (threads_created != m_threads_created || threads_deleted != m_threads_deleted)
|
|
{
|
|
m_threads_created = threads_created;
|
|
m_threads_deleted = threads_deleted;
|
|
}
|
|
else
|
|
{
|
|
// Nothing to do
|
|
return;
|
|
}
|
|
|
|
QVariant old_cpu = m_choice_units->currentData();
|
|
|
|
m_choice_units->clear();
|
|
m_choice_units->addItem(NoThreadString);
|
|
|
|
const auto on_select = [&](u32, cpu_thread& cpu)
|
|
{
|
|
QVariant var_cpu = QVariant::fromValue<void*>(&cpu);
|
|
m_choice_units->addItem(qstr(cpu.get_name()), var_cpu);
|
|
if (old_cpu == var_cpu) m_choice_units->setCurrentIndex(m_choice_units->count() - 1);
|
|
};
|
|
|
|
{
|
|
const QSignalBlocker blocker(m_choice_units);
|
|
|
|
idm::select<named_thread<ppu_thread>>(on_select);
|
|
idm::select<named_thread<spu_thread>>(on_select);
|
|
}
|
|
|
|
OnSelectUnit();
|
|
|
|
m_choice_units->update();
|
|
}
|
|
|
|
void debugger_frame::OnSelectUnit()
|
|
{
|
|
if (m_choice_units->count() < 1 || m_current_choice == m_choice_units->currentText()) return;
|
|
|
|
m_current_choice = m_choice_units->currentText();
|
|
m_no_thread_selected = m_current_choice == NoThreadString;
|
|
m_debugger_list->m_no_thread_selected = m_no_thread_selected;
|
|
|
|
m_disasm.reset();
|
|
cpu.reset();
|
|
|
|
if (!m_no_thread_selected)
|
|
{
|
|
const auto on_select = [&](u32, cpu_thread& cpu)
|
|
{
|
|
cpu_thread* data = static_cast<cpu_thread*>(m_choice_units->currentData().value<void*>());
|
|
return data == &cpu;
|
|
};
|
|
|
|
if (auto ppu = idm::select<named_thread<ppu_thread>>(on_select))
|
|
{
|
|
m_disasm = std::make_unique<PPUDisAsm>(CPUDisAsm_InterpreterMode);
|
|
cpu = ppu.ptr;
|
|
}
|
|
else if (auto spu1 = idm::select<named_thread<spu_thread>>(on_select))
|
|
{
|
|
m_disasm = std::make_unique<SPUDisAsm>(CPUDisAsm_InterpreterMode);
|
|
cpu = spu1.ptr;
|
|
}
|
|
}
|
|
|
|
EnableButtons(!m_no_thread_selected);
|
|
|
|
m_debugger_list->UpdateCPUData(this->cpu, m_disasm);
|
|
m_breakpoint_list->UpdateCPUData(this->cpu, m_disasm);
|
|
m_call_stack_list->UpdateCPUData(this->cpu, m_disasm);
|
|
DoUpdate();
|
|
UpdateUI();
|
|
}
|
|
|
|
void debugger_frame::DoUpdate()
|
|
{
|
|
// Check if we need to disable a step over bp
|
|
if (m_last_step_over_breakpoint != umax && GetPc() == m_last_step_over_breakpoint)
|
|
{
|
|
m_breakpoint_handler->RemoveBreakpoint(m_last_step_over_breakpoint);
|
|
m_last_step_over_breakpoint = -1;
|
|
}
|
|
|
|
ShowPC();
|
|
WritePanels();
|
|
}
|
|
|
|
void debugger_frame::WritePanels()
|
|
{
|
|
const auto cpu = this->cpu.lock();
|
|
|
|
if (!cpu)
|
|
{
|
|
m_misc_state->clear();
|
|
m_regs->clear();
|
|
return;
|
|
}
|
|
|
|
int loc;
|
|
|
|
loc = m_regs->verticalScrollBar()->value();
|
|
m_misc_state->clear();
|
|
m_misc_state->setText(qstr(cpu->dump_misc()));
|
|
m_misc_state->verticalScrollBar()->setValue(loc);
|
|
|
|
loc = m_regs->verticalScrollBar()->value();
|
|
m_regs->clear();
|
|
m_regs->setText(qstr(cpu->dump_regs()));
|
|
m_regs->verticalScrollBar()->setValue(loc);
|
|
|
|
Q_EMIT CallStackUpdateRequested(cpu->dump_callstack_list());
|
|
}
|
|
|
|
void debugger_frame::ShowGotoAddressDialog()
|
|
{
|
|
QDialog* diag = new QDialog(this);
|
|
diag->setWindowTitle(tr("Enter expression"));
|
|
diag->setModal(true);
|
|
|
|
// Panels
|
|
QVBoxLayout* vbox_panel(new QVBoxLayout());
|
|
QHBoxLayout* hbox_address_preview_panel(new QHBoxLayout());
|
|
QHBoxLayout* hbox_expression_input_panel = new QHBoxLayout();
|
|
QHBoxLayout* hbox_button_panel(new QHBoxLayout());
|
|
|
|
// Address preview
|
|
QLabel* address_preview_label(new QLabel(diag));
|
|
address_preview_label->setFont(m_mono);
|
|
|
|
// Address expression input
|
|
QLineEdit* expression_input(new QLineEdit(diag));
|
|
expression_input->setFont(m_mono);
|
|
expression_input->setMaxLength(18);
|
|
expression_input->setFixedWidth(190);
|
|
|
|
// Ok/Cancel
|
|
QPushButton* button_ok = new QPushButton(tr("OK"));
|
|
QPushButton* button_cancel = new QPushButton(tr("Cancel"));
|
|
|
|
hbox_address_preview_panel->addWidget(address_preview_label);
|
|
|
|
hbox_expression_input_panel->addWidget(expression_input);
|
|
|
|
hbox_button_panel->addWidget(button_ok);
|
|
hbox_button_panel->addWidget(button_cancel);
|
|
|
|
vbox_panel->addLayout(hbox_address_preview_panel);
|
|
vbox_panel->addSpacing(8);
|
|
vbox_panel->addLayout(hbox_expression_input_panel);
|
|
vbox_panel->addSpacing(8);
|
|
vbox_panel->addLayout(hbox_button_panel);
|
|
|
|
diag->setLayout(vbox_panel);
|
|
|
|
const auto cpu = this->cpu.lock();
|
|
|
|
if (cpu)
|
|
{
|
|
unsigned long pc = cpu ? GetPc() : 0x0;
|
|
address_preview_label->setText(QString("Address: 0x%1").arg(pc, 8, 16, QChar('0')));
|
|
expression_input->setPlaceholderText(QString("0x%1").arg(pc, 8, 16, QChar('0')));
|
|
}
|
|
else
|
|
{
|
|
expression_input->setPlaceholderText("0x00000000");
|
|
address_preview_label->setText("Address: 0x00000000");
|
|
}
|
|
|
|
auto l_changeLabel = [&](const QString& text)
|
|
{
|
|
if (text.isEmpty())
|
|
{
|
|
address_preview_label->setText("Address: " + expression_input->placeholderText());
|
|
}
|
|
else
|
|
{
|
|
ulong ul_addr = EvaluateExpression(text);
|
|
address_preview_label->setText(QString("Address: 0x%1").arg(ul_addr, 8, 16, QChar('0')));
|
|
}
|
|
};
|
|
|
|
connect(expression_input, &QLineEdit::textChanged, l_changeLabel);
|
|
connect(button_ok, &QAbstractButton::clicked, diag, &QDialog::accept);
|
|
connect(button_cancel, &QAbstractButton::clicked, diag, &QDialog::reject);
|
|
|
|
diag->move(QCursor::pos());
|
|
|
|
if (diag->exec() == QDialog::Accepted)
|
|
{
|
|
u32 address = cpu ? GetPc() : 0x0;
|
|
|
|
if (expression_input->text().isEmpty())
|
|
{
|
|
address_preview_label->setText(expression_input->placeholderText());
|
|
}
|
|
else
|
|
{
|
|
address = EvaluateExpression(expression_input->text());
|
|
address_preview_label->setText(expression_input->text());
|
|
}
|
|
|
|
m_debugger_list->ShowAddress(address);
|
|
}
|
|
|
|
diag->deleteLater();
|
|
}
|
|
|
|
u64 debugger_frame::EvaluateExpression(const QString& expression)
|
|
{
|
|
auto thread = cpu.lock();
|
|
|
|
if (!thread) return 0;
|
|
|
|
// Parse expression(or at least used to, was nuked to remove the need for QtJsEngine)
|
|
const QString fixed_expression = QRegExp("^[A-Fa-f0-9]+$").exactMatch(expression) ? "0x" + expression : expression;
|
|
return static_cast<ulong>(fixed_expression.toULong(nullptr, 0));
|
|
}
|
|
|
|
void debugger_frame::ClearBreakpoints()
|
|
{
|
|
m_breakpoint_list->ClearBreakpoints();
|
|
}
|
|
|
|
void debugger_frame::ClearCallStack()
|
|
{
|
|
Q_EMIT CallStackUpdateRequested({});
|
|
}
|
|
|
|
void debugger_frame::ShowPC()
|
|
{
|
|
m_debugger_list->ShowAddress(GetPc());
|
|
}
|
|
|
|
void debugger_frame::DoStep(bool stepOver)
|
|
{
|
|
if (const auto cpu = this->cpu.lock())
|
|
{
|
|
bool should_step_over = stepOver && cpu->id_type() == 1;
|
|
|
|
if (+cpu_flag::dbg_pause & +cpu->state.fetch_op([&](bs_t<cpu_flag>& state)
|
|
{
|
|
if (!should_step_over)
|
|
state += cpu_flag::dbg_step;
|
|
|
|
state -= cpu_flag::dbg_pause;
|
|
}))
|
|
{
|
|
if (should_step_over)
|
|
{
|
|
u32 current_instruction_pc = GetPc();
|
|
|
|
// Set breakpoint on next instruction
|
|
u32 next_instruction_pc = current_instruction_pc + 4;
|
|
m_breakpoint_handler->AddBreakpoint(next_instruction_pc);
|
|
|
|
// Undefine previous step over breakpoint if it hasnt been already
|
|
// This can happen when the user steps over a branch that doesn't return to itself
|
|
if (m_last_step_over_breakpoint != umax)
|
|
{
|
|
m_breakpoint_handler->RemoveBreakpoint(next_instruction_pc);
|
|
}
|
|
|
|
m_last_step_over_breakpoint = next_instruction_pc;
|
|
}
|
|
|
|
cpu->notify();
|
|
}
|
|
}
|
|
|
|
UpdateUI();
|
|
}
|
|
|
|
void debugger_frame::EnableUpdateTimer(bool enable)
|
|
{
|
|
enable ? m_update->start(50) : m_update->stop();
|
|
}
|
|
|
|
void debugger_frame::EnableButtons(bool enable)
|
|
{
|
|
if (m_no_thread_selected) enable = false;
|
|
|
|
m_go_to_addr->setEnabled(enable);
|
|
m_go_to_pc->setEnabled(enable);
|
|
m_btn_step->setEnabled(enable);
|
|
m_btn_step_over->setEnabled(enable);
|
|
m_btn_run->setEnabled(enable);
|
|
}
|