From fb79c04cf1aa95ed4c0d55dc840dafb16319f49b Mon Sep 17 00:00:00 2001 From: TryTwo Date: Sat, 26 Mar 2022 14:48:25 -0700 Subject: [PATCH] MemoryView auto updateDebugger. Implement base codetrace logic. Add register breakpoints. Add CodeViewWidget autostepping to track a value.Debugger --- Source/Core/Common/CMakeLists.txt | 2 + Source/Core/Common/Debug/CodeTrace.cpp | 389 ++++++++++++++++++ Source/Core/Common/Debug/CodeTrace.h | 76 ++++ Source/Core/DolphinLib.props | 2 + .../DolphinQt/Debugger/CodeViewWidget.cpp | 101 +++++ .../Core/DolphinQt/Debugger/CodeViewWidget.h | 2 + .../DolphinQt/Debugger/RegisterWidget.cpp | 38 ++ .../Core/DolphinQt/Debugger/RegisterWidget.h | 1 + 8 files changed, 611 insertions(+) create mode 100644 Source/Core/Common/Debug/CodeTrace.cpp create mode 100644 Source/Core/Common/Debug/CodeTrace.h diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index 134d38062c..471f083f68 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -33,6 +33,8 @@ add_library(common Crypto/ec.h Crypto/SHA1.cpp Crypto/SHA1.h + Debug/CodeTrace.cpp + Debug/CodeTrace.h Debug/MemoryPatches.cpp Debug/MemoryPatches.h Debug/Threads.h diff --git a/Source/Core/Common/Debug/CodeTrace.cpp b/Source/Core/Common/Debug/CodeTrace.cpp new file mode 100644 index 0000000000..3eea7a72fc --- /dev/null +++ b/Source/Core/Common/Debug/CodeTrace.cpp @@ -0,0 +1,389 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Common/Debug/CodeTrace.h" + +#include +#include + +#include "Common/Event.h" +#include "Common/StringUtil.h" +#include "Core/Debugger/PPCDebugInterface.h" +#include "Core/HW/CPU.h" +#include "Core/PowerPC/PowerPC.h" + +namespace +{ +bool IsInstructionLoadStore(std::string_view ins) +{ + return (StringBeginsWith(ins, "l") && !StringBeginsWith(ins, "li")) || + StringBeginsWith(ins, "st") || StringBeginsWith(ins, "psq_l") || + StringBeginsWith(ins, "psq_s"); +} + +u32 GetMemoryTargetSize(std::string_view instr) +{ + // Word-size operations are taken as the default, check the others. + auto op = instr.substr(0, 4); + + constexpr char BYTE_TAG = 'b'; + constexpr char HALF_TAG = 'h'; + constexpr char DOUBLE_WORD_TAG = 'd'; + constexpr char PAIRED_TAG = 'p'; + + // Actual range is 0 to size - 1; + if (op.find(BYTE_TAG) != std::string::npos) + { + return 1; + } + else if (op.find(HALF_TAG) != std::string::npos) + { + return 2; + } + else if (op.find(DOUBLE_WORD_TAG) != std::string::npos || + op.find(PAIRED_TAG) != std::string::npos) + { + return 8; + } + + return 4; +} +} // namespace + +void CodeTrace::SetRegTracked(const std::string& reg) +{ + m_reg_autotrack.push_back(reg); +} + +InstructionAttributes CodeTrace::GetInstructionAttributes(const TraceOutput& instruction) const +{ + // Slower process of breaking down saved instruction. Only used when stepping through code if a + // decision has to be made, otherwise used afterwards on a log file. + InstructionAttributes tmp_attributes; + tmp_attributes.instruction = instruction.instruction; + tmp_attributes.address = PC; + std::string instr = instruction.instruction; + std::smatch match; + + // Convert sp, rtoc, and ps to r1, r2, and F#. ps is handled like a float operation. + static const std::regex replace_sp("(\\W)sp"); + instr = std::regex_replace(instr, replace_sp, "$1r1"); + static const std::regex replace_rtoc("rtoc"); + instr = std::regex_replace(instr, replace_rtoc, "r2"); + static const std::regex replace_ps("(\\W)p(\\d+)"); + instr = std::regex_replace(instr, replace_ps, "$1f$2"); + + // Pull all register numbers out and store them. Limited to Reg0 if ps operation, as ps get + // too complicated to track easily. + // ex: add r4, r5, r6 -> r4 = Reg0, r5 = Reg1, r6 = Reg2. Reg0 is always the target register. + static const std::regex regis( + "\\W([rfp]\\d+)[^r^f]*(?:([rf]\\d+))?[^r^f\\d]*(?:([rf]\\d+))?[^r^f\\d]*(?:([rf]\\d+))?", + std::regex::optimize); + + if (std::regex_search(instr, match, regis)) + { + tmp_attributes.reg0 = match.str(1); + if (match[2].matched) + tmp_attributes.reg1 = match.str(2); + if (match[3].matched) + tmp_attributes.reg2 = match.str(3); + if (match[4].matched) + tmp_attributes.reg3 = match.str(4); + + if (instruction.memory_target) + { + tmp_attributes.memory_target = instruction.memory_target; + tmp_attributes.memory_target_size = GetMemoryTargetSize(instr); + + if (StringBeginsWith(instr, "st") || StringBeginsWith(instr, "psq_s")) + tmp_attributes.is_store = true; + else + tmp_attributes.is_load = true; + } + } + + return tmp_attributes; +} + +TraceOutput CodeTrace::SaveCurrentInstruction() const +{ + // Quickly save instruction and memory target for fast logging. + TraceOutput output; + const std::string instr = PowerPC::debug_interface.Disassemble(PC); + output.instruction = instr; + output.address = PC; + + if (IsInstructionLoadStore(output.instruction)) + output.memory_target = PowerPC::debug_interface.GetMemoryAddressFromInstruction(instr); + + return output; +} + +bool CompareMemoryTargetToTracked(const std::string& instr, const u32 mem_target, + const std::set& mem_tracked) +{ + // This function is hit often and should be optimized. + auto it_lower = std::lower_bound(mem_tracked.begin(), mem_tracked.end(), mem_target); + + if (it_lower == mem_tracked.end()) + return false; + else if (*it_lower == mem_target) + return true; + + // If the base value doesn't hit, still need to check if longer values overlap. + return *it_lower < mem_target + GetMemoryTargetSize(instr); +} + +AutoStepResults CodeTrace::AutoStepping(bool continue_previous, AutoStop stop_on) +{ + AutoStepResults results; + + if (!CPU::IsStepping() || m_recording) + return results; + + TraceOutput pc_instr = SaveCurrentInstruction(); + const InstructionAttributes instr = GetInstructionAttributes(pc_instr); + + // Not an instruction we should start autostepping from (ie branches). + if (instr.reg0.empty() && !continue_previous) + return results; + + m_recording = true; + + // Once autostep stops, it can be told to continue running without resetting the tracked + // registers and memory. + if (!continue_previous) + { + m_reg_autotrack.clear(); + m_mem_autotrack.clear(); + m_reg_autotrack.push_back(instr.reg0); + + // It wouldn't necessarily be wrong to also record the memory of a load operation, as the + // value exists there too. May or may not be desirable depending on task. Leaving it out. + if (instr.is_store) + { + const u32 size = GetMemoryTargetSize(instr.instruction); + for (u32 i = 0; i < size; i++) + m_mem_autotrack.insert(instr.memory_target.value() + i); + } + } + + // Count is important for feedback on how much work was done. + + HitType hit = HitType::SKIP; + HitType stop_condition = HitType::SAVELOAD; + + // Could use bit flags, but I organized it to have decreasing levels of verbosity, so the + // less-than comparison ignores what is needed for the current usage. + if (stop_on == AutoStop::Always) + stop_condition = HitType::SAVELOAD; + else if (stop_on == AutoStop::Used) + stop_condition = HitType::PASSIVE; + else if (stop_on == AutoStop::Changed) + stop_condition = HitType::ACTIVE; + + CPU::PauseAndLock(true, false); + PowerPC::breakpoints.ClearAllTemporary(); + using clock = std::chrono::steady_clock; + clock::time_point timeout = clock::now() + std::chrono::seconds(4); + + PowerPC::CoreMode old_mode = PowerPC::GetMode(); + PowerPC::SetMode(PowerPC::CoreMode::Interpreter); + + do + { + PowerPC::SingleStep(); + + pc_instr = SaveCurrentInstruction(); + hit = TraceLogic(pc_instr); + results.count += 1; + } while (clock::now() < timeout && hit < stop_condition && + !(m_reg_autotrack.empty() && m_mem_autotrack.empty())); + + // Report the timeout to the caller. + if (clock::now() >= timeout) + results.timed_out = true; + + PowerPC::SetMode(old_mode); + CPU::PauseAndLock(false, false); + m_recording = false; + + results.reg_tracked = m_reg_autotrack; + results.mem_tracked = m_mem_autotrack; + + // Doesn't currently need to report the hit type to the caller. Denoting when the reg and mem + // trackers are both empty is important, as it means our target was overwritten and can no longer + // be tracked. Different actions can be taken on a timeout vs empty trackers, so they are reported + // individually. + return results; +} + +HitType CodeTrace::TraceLogic(const TraceOutput& current_instr, bool first_hit) +{ + // Tracks the original value that is in the targeted register or memory through loads, stores, + // register moves, and value changes. Also finds when it is used. ps operations are not fully + // supported. -ux memory instructions may need special cases. + // Should not be called if reg and mem tracked are empty. + + // Using a std::set because it can easily insert the memory range being accessed without + // causing duplicates, and quickly erases all members of the memory range without caring if the + // element actually exists. + + bool mem_hit = false; + if (current_instr.memory_target && !m_mem_autotrack.empty()) + { + mem_hit = CompareMemoryTargetToTracked(current_instr.instruction, *current_instr.memory_target, + m_mem_autotrack); + } + + // Optimization for tracking a memory target when no registers are being tracked. + if (m_reg_autotrack.empty() && !mem_hit) + return HitType::SKIP; + + // Break instruction down into parts to be analyzed. + const InstructionAttributes instr = GetInstructionAttributes(current_instr); + + // Not an instruction we care about (branches). + if (instr.reg0.empty()) + return HitType::SKIP; + + // The reg_itr will be used later for erasing. + auto reg_itr = std::find(m_reg_autotrack.begin(), m_reg_autotrack.end(), instr.reg0); + const bool match_reg123 = + (!instr.reg1.empty() && std::find(m_reg_autotrack.begin(), m_reg_autotrack.end(), + instr.reg1) != m_reg_autotrack.end()) || + (!instr.reg2.empty() && std::find(m_reg_autotrack.begin(), m_reg_autotrack.end(), + instr.reg2) != m_reg_autotrack.end()) || + (!instr.reg3.empty() && std::find(m_reg_autotrack.begin(), m_reg_autotrack.end(), + instr.reg3) != m_reg_autotrack.end()); + const bool match_reg0 = reg_itr != m_reg_autotrack.end(); + + if (!match_reg0 && !match_reg123 && !mem_hit) + return HitType::SKIP; + + // Checks if the intstruction is a type that needs special handling. + const auto CompareInstruction = [](std::string_view instruction, const auto& type_compare) { + return std::any_of( + type_compare.begin(), type_compare.end(), + [&instruction](std::string_view s) { return StringBeginsWith(instruction, s); }); + }; + + // Exclusions from updating tracking logic. mt operations are too complex and specialized. + // Combiner used later. + static const std::array exclude{"dc", "ic", "mt"}; + static const std::array compare{"c", "fc"}; + + // rlwimi, at least, can preserve parts of the target register. Not sure if rldimi can too or if + // there are any others like this. + static const std::array combiner{"rlwimi"}; + + static const std::array mover{"mr", "fmr"}; + + // Link register for when r0 gets overwritten + if (StringBeginsWith(instr.instruction, "mflr") && match_reg0) + { + m_reg_autotrack.erase(reg_itr); + return HitType::OVERWRITE; + } + else if (StringBeginsWith(instr.instruction, "mtlr") && match_reg0) + { + // LR is not something tracked + return HitType::MOVED; + } + + if (CompareInstruction(instr.instruction, exclude)) + return HitType::SKIP; + else if (CompareInstruction(instr.instruction, compare)) + return HitType::PASSIVE; + else if (match_reg123 && !match_reg0 && (instr.is_store || instr.is_load)) + return HitType::POINTER; + + // Update tracking logic. At this point a memory or register hit happened. + // Save/Load + if (instr.memory_target) + { + if (mem_hit) + { + // If hit a tracked memory. Load -> Add register to tracked. Store -> Remove tracked memory + // if overwritten. + + if (instr.is_load && !match_reg0) + { + m_reg_autotrack.push_back(instr.reg0); + return HitType::SAVELOAD; + } + else if (instr.is_store && !match_reg0) + { + // On First Hit it wouldn't necessarily be wrong to track the register, which contains the + // same value. A matter of preference. + if (first_hit) + return HitType::SAVELOAD; + + for (u32 i = 0; i < instr.memory_target_size; i++) + m_mem_autotrack.erase(*instr.memory_target + i); + + return HitType::OVERWRITE; + } + else + { + // If reg0 and store/load are both already tracked, do nothing. + return HitType::SAVELOAD; + } + } + else if (instr.is_store && match_reg0) + { + // If store to untracked memory, then track memory. + for (u32 i = 0; i < instr.memory_target_size; i++) + m_mem_autotrack.insert(*instr.memory_target + i); + + return HitType::SAVELOAD; + } + else if (instr.is_load && match_reg0) + { + // Not wrong to track load memory_target here. Preference. + if (first_hit) + return HitType::SAVELOAD; + + // If untracked load is overwriting tracked register, then remove register + m_reg_autotrack.erase(reg_itr); + return HitType::OVERWRITE; + } + } + else if (!match_reg0 && !match_reg123) + { + // Skip if no matches. Happens most often. + return HitType::SKIP; + } + else + { + // If tracked register data is being stored in a new register, save new register. + if (match_reg123 && !match_reg0) + { + m_reg_autotrack.push_back(instr.reg0); + + // This should include any instruction that can reach this point and is not ACTIVE. Can only + // think of mr at this time. + if (CompareInstruction(instr.instruction, mover)) + return HitType::MOVED; + + return HitType::ACTIVE; + } + // If tracked register is overwritten, stop tracking. + else if (match_reg0 && !match_reg123) + { + if (CompareInstruction(instr.instruction, combiner) || first_hit) + return HitType::UPDATED; + + m_reg_autotrack.erase(reg_itr); + return HitType::OVERWRITE; + } + else if (match_reg0 && match_reg123) + { + // Or moved + return HitType::UPDATED; + } + } + + // Should not reach this + return HitType::SKIP; +} diff --git a/Source/Core/Common/Debug/CodeTrace.h b/Source/Core/Common/Debug/CodeTrace.h new file mode 100644 index 0000000000..4f2cb701f2 --- /dev/null +++ b/Source/Core/Common/Debug/CodeTrace.h @@ -0,0 +1,76 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "Common/CommonTypes.h" + +struct InstructionAttributes +{ + u32 address = 0; + std::string instruction = ""; + std::string reg0 = ""; + std::string reg1 = ""; + std::string reg2 = ""; + std::string reg3 = ""; + std::optional memory_target = std::nullopt; + u32 memory_target_size = 4; + bool is_store = false; + bool is_load = false; +}; + +struct TraceOutput +{ + u32 address; + std::optional memory_target = std::nullopt; + std::string instruction; +}; + +struct AutoStepResults +{ + std::vector reg_tracked; + std::set mem_tracked; + u32 count = 0; + bool timed_out = false; + bool trackers_empty = false; +}; + +enum class HitType : u32 +{ + SKIP = (1 << 0), // Not a hit + OVERWRITE = (1 << 1), // Tracked value gets overwritten by untracked. Typically skipped. + MOVED = (1 << 2), // Target duplicated to another register, unchanged. + SAVELOAD = (1 << 3), // Target saved or loaded. Priority over Pointer. + POINTER = (1 << 4), // Target used as pointer/offset for save or load + PASSIVE = (1 << 5), // Conditional, etc, but not pointer. Unchanged + ACTIVE = (1 << 6), // Math, etc. Changed. + UPDATED = (1 << 7), // Masked or math without changing register. +}; + +class CodeTrace +{ +public: + enum class AutoStop + { + Always, + Used, + Changed + }; + + void SetRegTracked(const std::string& reg); + AutoStepResults AutoStepping(bool continue_previous = false, AutoStop stop_on = AutoStop::Always); + +private: + InstructionAttributes GetInstructionAttributes(const TraceOutput& line) const; + TraceOutput SaveCurrentInstruction() const; + HitType TraceLogic(const TraceOutput& current_instr, bool first_hit = false); + + bool m_recording = false; + std::vector m_reg_autotrack; + std::set m_mem_autotrack; +}; diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 4c08f0a6d8..87672afacb 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -37,6 +37,7 @@ + @@ -730,6 +731,7 @@ --> /d2ssa-peeps-post-color- %(AdditionalOptions) + diff --git a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp index 76e43d50ad..886b04da64 100644 --- a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,7 @@ #include #include "Common/Assert.h" +#include "Common/Debug/CodeTrace.h" #include "Common/GekkoDisassembler.h" #include "Common/StringUtil.h" #include "Core/Core.h" @@ -564,6 +566,26 @@ void CodeViewWidget::OnContextMenu() auto* restore_action = menu->addAction(tr("Restore instruction"), this, &CodeViewWidget::OnRestoreInstruction); + QString target; + if (addr == PC && running && Core::GetState() == Core::State::Paused) + { + const std::string line = PowerPC::debug_interface.Disassemble(PC); + const auto target_it = std::find(line.begin(), line.end(), '\t'); + const auto target_end = std::find(target_it, line.end(), ','); + + if (target_it != line.end() && target_end != line.end()) + target = QString::fromStdString(std::string{target_it + 1, target_end}); + } + + auto* run_until_menu = menu->addMenu(tr("Run until (ignoring breakpoints)")); + run_until_menu->addAction(tr("%1's value is hit").arg(target), this, + [this] { AutoStep(CodeTrace::AutoStop::Always); }); + run_until_menu->addAction(tr("%1's value is used").arg(target), this, + [this] { AutoStep(CodeTrace::AutoStop::Used); }); + run_until_menu->addAction(tr("%1's value is changed").arg(target), + [this] { AutoStep(CodeTrace::AutoStop::Changed); }); + + run_until_menu->setEnabled(!target.isEmpty()); follow_branch_action->setEnabled(running && GetBranchFromAddress(addr)); for (auto* action : {copy_address_action, copy_line_action, copy_hex_action, function_action, @@ -587,6 +609,85 @@ void CodeViewWidget::OnContextMenu() Update(); } +void CodeViewWidget::AutoStep(CodeTrace::AutoStop option) +{ + // Autosteps and follows value in the target (left-most) register. The Used and Changed options + // silently follows target through reshuffles in memory and registers and stops on use or update. + + CodeTrace code_trace; + bool repeat = false; + + QMessageBox msgbox(QMessageBox::NoIcon, tr("Run until"), {}, QMessageBox::Cancel); + QPushButton* run_button = msgbox.addButton(tr("Keep Running"), QMessageBox::AcceptRole); + // Not sure if we want default to be cancel. Spacebar can let you quickly continue autostepping if + // Yes. + + do + { + // Run autostep then update codeview + const AutoStepResults results = code_trace.AutoStepping(repeat, option); + emit Host::GetInstance()->UpdateDisasmDialog(); + repeat = true; + + // Invalid instruction, 0 means no step executed. + if (results.count == 0) + return; + + // Status report + if (results.reg_tracked.empty() && results.mem_tracked.empty()) + { + QMessageBox::warning( + this, tr("Overwritten"), + tr("Target value was overwritten by current instruction.\nInstructions executed: %1") + .arg(QString::number(results.count)), + QMessageBox::Cancel); + return; + } + else if (results.timed_out) + { + // Can keep running and try again after a time out. + msgbox.setText( + tr("AutoStepping timed out. Current instruction is irrelevant.")); + } + else + { + msgbox.setText(tr("Value tracked to current instruction.")); + } + + // Mem_tracked needs to track each byte individually, so a tracked word-sized value would have + // four entries. The displayed memory list needs to be shortened so it's not a huge list of + // bytes. Assumes adjacent bytes represent a word or half-word and removes the redundant bytes. + std::set mem_out; + auto iter = results.mem_tracked.begin(); + + while (iter != results.mem_tracked.end()) + { + const u32 address = *iter; + mem_out.insert(address); + + for (u32 i = 1; i <= 3; i++) + { + if (results.mem_tracked.count(address + i)) + iter++; + else + break; + } + + iter++; + } + + const QString msgtext = + tr("Instructions executed: %1\nValue contained in:\nRegisters: %2\nMemory: %3") + .arg(QString::number(results.count)) + .arg(QString::fromStdString(fmt::format("{}", fmt::join(results.reg_tracked, ", ")))) + .arg(QString::fromStdString(fmt::format("{:#x}", fmt::join(mem_out, ", ")))); + + msgbox.setInformativeText(msgtext); + msgbox.exec(); + + } while (msgbox.clickedButton() == (QAbstractButton*)run_button); +} + void CodeViewWidget::OnCopyAddress() { const u32 addr = GetContextAddress(); diff --git a/Source/Core/DolphinQt/Debugger/CodeViewWidget.h b/Source/Core/DolphinQt/Debugger/CodeViewWidget.h index c962971cb8..33f34f3fc8 100644 --- a/Source/Core/DolphinQt/Debugger/CodeViewWidget.h +++ b/Source/Core/DolphinQt/Debugger/CodeViewWidget.h @@ -8,6 +8,7 @@ #include #include "Common/CommonTypes.h" +#include "Common/Debug/CodeTrace.h" class QKeyEvent; class QMouseEvent; @@ -68,6 +69,7 @@ private: void OnContextMenu(); + void AutoStep(CodeTrace::AutoStop option = CodeTrace::AutoStop::Always); void OnFollowBranch(); void OnCopyAddress(); void OnCopyTargetAddress(); diff --git a/Source/Core/DolphinQt/Debugger/RegisterWidget.cpp b/Source/Core/DolphinQt/Debugger/RegisterWidget.cpp index 8ce8d19ca4..c671499119 100644 --- a/Source/Core/DolphinQt/Debugger/RegisterWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/RegisterWidget.cpp @@ -8,9 +8,11 @@ #include #include #include +#include #include #include +#include "Common/Debug/CodeTrace.h" #include "Core/Core.h" #include "Core/HW/ProcessorInterface.h" #include "Core/PowerPC/PowerPC.h" @@ -164,6 +166,16 @@ void RegisterWidget::ShowContextMenu() auto* view_double_column = menu->addAction(tr("All Double")); view_double_column->setData(static_cast(RegisterDisplay::Double)); + if (type == RegisterType::gpr || type == RegisterType::fpr) + { + menu->addSeparator(); + + const std::string type_string = + fmt::format("{}{}", type == RegisterType::gpr ? "r" : "f", m_table->currentItem()->row()); + menu->addAction(tr("Run until hit (ignoring breakpoints)"), + [this, type_string]() { AutoStep(type_string); }); + } + for (auto* action : {view_hex, view_int, view_uint, view_float, view_double}) { action->setCheckable(true); @@ -269,6 +281,32 @@ void RegisterWidget::ShowContextMenu() menu->exec(QCursor::pos()); } +void RegisterWidget::AutoStep(const std::string& reg) const +{ + CodeTrace trace; + trace.SetRegTracked(reg); + + QMessageBox msgbox( + QMessageBox::NoIcon, tr("Timed Out"), + tr("AutoStepping timed out. Current instruction is irrelevant."), + QMessageBox::Cancel); + QPushButton* run_button = msgbox.addButton(tr("Keep Running"), QMessageBox::AcceptRole); + + while (true) + { + const AutoStepResults results = trace.AutoStepping(true); + emit Host::GetInstance()->UpdateDisasmDialog(); + + if (!results.timed_out) + break; + + // Can keep running and try again after a time out. + msgbox.exec(); + if (msgbox.clickedButton() != (QAbstractButton*)run_button) + break; + } +} + void RegisterWidget::PopulateTable() { for (int i = 0; i < 32; i++) diff --git a/Source/Core/DolphinQt/Debugger/RegisterWidget.h b/Source/Core/DolphinQt/Debugger/RegisterWidget.h index e9435c08a7..f57a3b04d9 100644 --- a/Source/Core/DolphinQt/Debugger/RegisterWidget.h +++ b/Source/Core/DolphinQt/Debugger/RegisterWidget.h @@ -46,6 +46,7 @@ private: void AddRegister(int row, int column, RegisterType type, std::string register_name, std::function get_reg, std::function set_reg); + void AutoStep(const std::string& reg) const; void Update(); QTableWidget* m_table;