mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-23 14:19:46 -06:00
Merge pull request #11554 from JosJuice/host-lock-cpu
DolphinQt: Properly lock CPU before accessing emulated memory
This commit is contained in:
@ -36,6 +36,7 @@
|
||||
#include "Core/CheatGeneration.h"
|
||||
#include "Core/CheatSearch.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
|
||||
#include "DolphinQt/Config/CheatCodeEditor.h"
|
||||
@ -286,7 +287,11 @@ void CheatSearchWidget::OnNextScanClicked()
|
||||
return;
|
||||
}
|
||||
}
|
||||
Cheats::SearchErrorCode error_code = m_session->RunSearch();
|
||||
|
||||
const Cheats::SearchErrorCode error_code = [this] {
|
||||
Core::CPUThreadGuard guard;
|
||||
return m_session->RunSearch(guard);
|
||||
}();
|
||||
|
||||
if (error_code == Cheats::SearchErrorCode::Success)
|
||||
{
|
||||
@ -391,7 +396,13 @@ bool CheatSearchWidget::RefreshValues()
|
||||
}
|
||||
|
||||
tmp->SetFilterType(Cheats::FilterType::DoNotFilter);
|
||||
if (tmp->RunSearch() != Cheats::SearchErrorCode::Success)
|
||||
|
||||
const Cheats::SearchErrorCode error_code = [&tmp] {
|
||||
Core::CPUThreadGuard guard;
|
||||
return tmp->RunSearch(guard);
|
||||
}();
|
||||
|
||||
if (error_code != Cheats::SearchErrorCode::Success)
|
||||
{
|
||||
m_info_label_1->setText(tr("Refresh failed. Please run the game for a bit and try again."));
|
||||
return false;
|
||||
|
@ -483,7 +483,11 @@ void CodeDiffDialog::OnSetBLR()
|
||||
Common::Symbol* symbol = g_symbolDB.GetSymbolFromAddr(item->data(Qt::UserRole).toUInt());
|
||||
if (!symbol)
|
||||
return;
|
||||
PowerPC::debug_interface.SetPatch(symbol->address, 0x4E800020);
|
||||
|
||||
{
|
||||
Core::CPUThreadGuard guard;
|
||||
PowerPC::debug_interface.SetPatch(guard, symbol->address, 0x4E800020);
|
||||
}
|
||||
|
||||
int row = item->row();
|
||||
m_matching_results_table->item(row, 0)->setForeground(QBrush(Qt::red));
|
||||
|
@ -175,14 +175,15 @@ CodeViewWidget::CodeViewWidget()
|
||||
Update();
|
||||
});
|
||||
|
||||
connect(&Settings::Instance(), &Settings::ThemeChanged, this, &CodeViewWidget::Update);
|
||||
connect(&Settings::Instance(), &Settings::ThemeChanged, this,
|
||||
qOverload<>(&CodeViewWidget::Update));
|
||||
}
|
||||
|
||||
CodeViewWidget::~CodeViewWidget() = default;
|
||||
|
||||
static u32 GetBranchFromAddress(u32 addr)
|
||||
static u32 GetBranchFromAddress(const Core::CPUThreadGuard& guard, u32 addr)
|
||||
{
|
||||
std::string disasm = PowerPC::debug_interface.Disassemble(addr);
|
||||
std::string disasm = PowerPC::debug_interface.Disassemble(&guard, addr);
|
||||
size_t pos = disasm.find("->0x");
|
||||
|
||||
if (pos == std::string::npos)
|
||||
@ -248,6 +249,26 @@ static bool IsInstructionLoadStore(std::string_view ins)
|
||||
}
|
||||
|
||||
void CodeViewWidget::Update()
|
||||
{
|
||||
if (!isVisible())
|
||||
return;
|
||||
|
||||
if (m_updating)
|
||||
return;
|
||||
|
||||
if (Core::GetState() == Core::State::Paused)
|
||||
{
|
||||
Core::CPUThreadGuard guard;
|
||||
Update(&guard);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the core is running, blank out the view of memory instead of reading anything.
|
||||
Update(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void CodeViewWidget::Update(const Core::CPUThreadGuard* guard)
|
||||
{
|
||||
if (!isVisible())
|
||||
return;
|
||||
@ -284,11 +305,11 @@ void CodeViewWidget::Update()
|
||||
for (int i = 0; i < rowCount(); i++)
|
||||
{
|
||||
const u32 addr = AddressForRow(i);
|
||||
const u32 color = PowerPC::debug_interface.GetColor(addr);
|
||||
const u32 color = PowerPC::debug_interface.GetColor(guard, addr);
|
||||
auto* bp_item = new QTableWidgetItem;
|
||||
auto* addr_item = new QTableWidgetItem(QStringLiteral("%1").arg(addr, 8, 16, QLatin1Char('0')));
|
||||
|
||||
std::string disas = PowerPC::debug_interface.Disassemble(addr);
|
||||
std::string disas = PowerPC::debug_interface.Disassemble(guard, addr);
|
||||
auto split = disas.find('\t');
|
||||
|
||||
std::string ins = (split == std::string::npos ? disas : disas.substr(0, split));
|
||||
@ -332,9 +353,9 @@ void CodeViewWidget::Update()
|
||||
hex_str = param.substr(pos);
|
||||
}
|
||||
|
||||
if (hex_str.length() == VALID_BRANCH_LENGTH && desc != "---")
|
||||
if (guard && hex_str.length() == VALID_BRANCH_LENGTH && desc != "---")
|
||||
{
|
||||
u32 branch_addr = GetBranchFromAddress(addr);
|
||||
u32 branch_addr = GetBranchFromAddress(*guard, addr);
|
||||
CodeViewBranch& branch = m_branches.emplace_back();
|
||||
branch.src_addr = addr;
|
||||
branch.dst_addr = branch_addr;
|
||||
@ -514,15 +535,20 @@ void CodeViewWidget::SetAddress(u32 address, SetAddressUpdate update)
|
||||
|
||||
void CodeViewWidget::ReplaceAddress(u32 address, ReplaceWith replace)
|
||||
{
|
||||
PowerPC::debug_interface.SetPatch(address, replace == ReplaceWith::BLR ? 0x4e800020 : 0x60000000);
|
||||
Update();
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
PowerPC::debug_interface.SetPatch(guard, address,
|
||||
replace == ReplaceWith::BLR ? 0x4e800020 : 0x60000000);
|
||||
|
||||
Update(&guard);
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnContextMenu()
|
||||
{
|
||||
QMenu* menu = new QMenu(this);
|
||||
|
||||
bool running = Core::GetState() != Core::State::Uninitialized;
|
||||
const bool running = Core::GetState() != Core::State::Uninitialized;
|
||||
const bool paused = Core::GetState() == Core::State::Paused;
|
||||
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
@ -567,14 +593,25 @@ void CodeViewWidget::OnContextMenu()
|
||||
menu->addAction(tr("Restore instruction"), this, &CodeViewWidget::OnRestoreInstruction);
|
||||
|
||||
QString target;
|
||||
if (addr == PowerPC::ppcState.pc && running && Core::GetState() == Core::State::Paused)
|
||||
bool valid_load_store = false;
|
||||
bool follow_branch_enabled = false;
|
||||
if (paused)
|
||||
{
|
||||
const std::string line = PowerPC::debug_interface.Disassemble(PowerPC::ppcState.pc);
|
||||
const auto target_it = std::find(line.begin(), line.end(), '\t');
|
||||
const auto target_end = std::find(target_it, line.end(), ',');
|
||||
Core::CPUThreadGuard guard;
|
||||
const std::string disasm = PowerPC::debug_interface.Disassemble(&guard, PowerPC::ppcState.pc);
|
||||
|
||||
if (target_it != line.end() && target_end != line.end())
|
||||
target = QString::fromStdString(std::string{target_it + 1, target_end});
|
||||
if (addr == PowerPC::ppcState.pc)
|
||||
{
|
||||
const auto target_it = std::find(disasm.begin(), disasm.end(), '\t');
|
||||
const auto target_end = std::find(target_it, disasm.end(), ',');
|
||||
|
||||
if (target_it != disasm.end() && target_end != disasm.end())
|
||||
target = QString::fromStdString(std::string{target_it + 1, target_end});
|
||||
}
|
||||
|
||||
valid_load_store = IsInstructionLoadStore(disasm);
|
||||
|
||||
follow_branch_enabled = GetBranchFromAddress(guard, addr);
|
||||
}
|
||||
|
||||
auto* run_until_menu = menu->addMenu(tr("Run until (ignoring breakpoints)"));
|
||||
@ -589,18 +626,17 @@ void CodeViewWidget::OnContextMenu()
|
||||
[this] { AutoStep(CodeTrace::AutoStop::Changed); });
|
||||
|
||||
run_until_menu->setEnabled(!target.isEmpty());
|
||||
follow_branch_action->setEnabled(running && GetBranchFromAddress(addr));
|
||||
follow_branch_action->setEnabled(follow_branch_enabled);
|
||||
|
||||
for (auto* action : {copy_address_action, copy_line_action, copy_hex_action, function_action,
|
||||
ppc_action, insert_blr_action, insert_nop_action, replace_action})
|
||||
{
|
||||
action->setEnabled(running);
|
||||
}
|
||||
|
||||
for (auto* action : {symbol_rename_action, symbol_size_action, symbol_end_action})
|
||||
action->setEnabled(has_symbol);
|
||||
|
||||
const bool valid_load_store = Core::GetState() == Core::State::Paused &&
|
||||
IsInstructionLoadStore(PowerPC::debug_interface.Disassemble(addr));
|
||||
|
||||
for (auto* action : {copy_target_memory, show_target_memory})
|
||||
{
|
||||
action->setEnabled(valid_load_store);
|
||||
@ -617,6 +653,8 @@ 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.
|
||||
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
CodeTrace code_trace;
|
||||
bool repeat = false;
|
||||
|
||||
@ -628,7 +666,7 @@ void CodeViewWidget::AutoStep(CodeTrace::AutoStop option)
|
||||
do
|
||||
{
|
||||
// Run autostep then update codeview
|
||||
const AutoStepResults results = code_trace.AutoStepping(repeat, option);
|
||||
const AutoStepResults results = code_trace.AutoStepping(guard, repeat, option);
|
||||
emit Host::GetInstance()->UpdateDisasmDialog();
|
||||
repeat = true;
|
||||
|
||||
@ -703,16 +741,24 @@ void CodeViewWidget::OnCopyTargetAddress()
|
||||
if (Core::GetState() != Core::State::Paused)
|
||||
return;
|
||||
|
||||
const std::string code_line = PowerPC::debug_interface.Disassemble(GetContextAddress());
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
const std::string code_line = [addr] {
|
||||
Core::CPUThreadGuard guard;
|
||||
return PowerPC::debug_interface.Disassemble(&guard, addr);
|
||||
}();
|
||||
|
||||
if (!IsInstructionLoadStore(code_line))
|
||||
return;
|
||||
|
||||
const std::optional<u32> addr =
|
||||
const std::optional<u32> target_addr =
|
||||
PowerPC::debug_interface.GetMemoryAddressFromInstruction(code_line);
|
||||
|
||||
if (addr)
|
||||
QApplication::clipboard()->setText(QStringLiteral("%1").arg(*addr, 8, 16, QLatin1Char('0')));
|
||||
{
|
||||
QApplication::clipboard()->setText(
|
||||
QStringLiteral("%1").arg(*target_addr, 8, 16, QLatin1Char('0')));
|
||||
}
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnShowInMemory()
|
||||
@ -725,24 +771,33 @@ void CodeViewWidget::OnShowTargetInMemory()
|
||||
if (Core::GetState() != Core::State::Paused)
|
||||
return;
|
||||
|
||||
const std::string code_line = PowerPC::debug_interface.Disassemble(GetContextAddress());
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
const std::string code_line = [addr] {
|
||||
Core::CPUThreadGuard guard;
|
||||
return PowerPC::debug_interface.Disassemble(&guard, addr);
|
||||
}();
|
||||
|
||||
if (!IsInstructionLoadStore(code_line))
|
||||
return;
|
||||
|
||||
const std::optional<u32> addr =
|
||||
const std::optional<u32> target_addr =
|
||||
PowerPC::debug_interface.GetMemoryAddressFromInstruction(code_line);
|
||||
|
||||
if (addr)
|
||||
emit ShowMemory(*addr);
|
||||
emit ShowMemory(*target_addr);
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnCopyCode()
|
||||
{
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
QApplication::clipboard()->setText(
|
||||
QString::fromStdString(PowerPC::debug_interface.Disassemble(addr)));
|
||||
const std::string text = [addr] {
|
||||
Core::CPUThreadGuard guard;
|
||||
return PowerPC::debug_interface.Disassemble(&guard, addr);
|
||||
}();
|
||||
|
||||
QApplication::clipboard()->setText(QString::fromStdString(text));
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnCopyFunction()
|
||||
@ -754,13 +809,18 @@ void CodeViewWidget::OnCopyFunction()
|
||||
return;
|
||||
|
||||
std::string text = symbol->name + "\r\n";
|
||||
// we got a function
|
||||
const u32 start = symbol->address;
|
||||
const u32 end = start + symbol->size;
|
||||
for (u32 addr = start; addr != end; addr += 4)
|
||||
|
||||
{
|
||||
const std::string disasm = PowerPC::debug_interface.Disassemble(addr);
|
||||
fmt::format_to(std::back_inserter(text), "{:08x}: {}\r\n", addr, disasm);
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
// we got a function
|
||||
const u32 start = symbol->address;
|
||||
const u32 end = start + symbol->size;
|
||||
for (u32 addr = start; addr != end; addr += 4)
|
||||
{
|
||||
const std::string disasm = PowerPC::debug_interface.Disassemble(&guard, addr);
|
||||
fmt::format_to(std::back_inserter(text), "{:08x}: {}\r\n", addr, disasm);
|
||||
}
|
||||
}
|
||||
|
||||
QApplication::clipboard()->setText(QString::fromStdString(text));
|
||||
@ -769,7 +829,11 @@ void CodeViewWidget::OnCopyFunction()
|
||||
void CodeViewWidget::OnCopyHex()
|
||||
{
|
||||
const u32 addr = GetContextAddress();
|
||||
const u32 instruction = PowerPC::debug_interface.ReadInstruction(addr);
|
||||
|
||||
const u32 instruction = [addr] {
|
||||
Core::CPUThreadGuard guard;
|
||||
return PowerPC::debug_interface.ReadInstruction(guard, addr);
|
||||
}();
|
||||
|
||||
QApplication::clipboard()->setText(
|
||||
QStringLiteral("%1").arg(instruction, 8, 16, QLatin1Char('0')));
|
||||
@ -795,9 +859,11 @@ void CodeViewWidget::OnAddFunction()
|
||||
{
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
g_symbolDB.AddFunction(addr);
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
g_symbolDB.AddFunction(guard, addr);
|
||||
emit SymbolsChanged();
|
||||
Update();
|
||||
Update(&guard);
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnInsertBLR()
|
||||
@ -818,7 +884,10 @@ void CodeViewWidget::OnFollowBranch()
|
||||
{
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
u32 branch_addr = GetBranchFromAddress(addr);
|
||||
const u32 branch_addr = [addr] {
|
||||
Core::CPUThreadGuard guard;
|
||||
return GetBranchFromAddress(guard, addr);
|
||||
}();
|
||||
|
||||
if (!branch_addr)
|
||||
return;
|
||||
@ -879,9 +948,11 @@ void CodeViewWidget::OnSetSymbolSize()
|
||||
if (!good)
|
||||
return;
|
||||
|
||||
PPCAnalyst::ReanalyzeFunction(symbol->address, *symbol, size);
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
PPCAnalyst::ReanalyzeFunction(guard, symbol->address, *symbol, size);
|
||||
emit SymbolsChanged();
|
||||
Update();
|
||||
Update(&guard);
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnSetSymbolEndAddress()
|
||||
@ -905,37 +976,43 @@ void CodeViewWidget::OnSetSymbolEndAddress()
|
||||
if (!good)
|
||||
return;
|
||||
|
||||
PPCAnalyst::ReanalyzeFunction(symbol->address, *symbol, address - symbol->address);
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
PPCAnalyst::ReanalyzeFunction(guard, symbol->address, *symbol, address - symbol->address);
|
||||
emit SymbolsChanged();
|
||||
Update();
|
||||
Update(&guard);
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnReplaceInstruction()
|
||||
{
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
if (!PowerPC::HostIsInstructionRAMAddress(addr))
|
||||
if (!PowerPC::HostIsInstructionRAMAddress(guard, addr))
|
||||
return;
|
||||
|
||||
const PowerPC::TryReadInstResult read_result = PowerPC::TryReadInstruction(addr);
|
||||
if (!read_result.valid)
|
||||
return;
|
||||
|
||||
PatchInstructionDialog dialog(this, addr, PowerPC::debug_interface.ReadInstruction(addr));
|
||||
PatchInstructionDialog dialog(this, addr, PowerPC::debug_interface.ReadInstruction(guard, addr));
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted)
|
||||
{
|
||||
PowerPC::debug_interface.SetPatch(addr, dialog.GetCode());
|
||||
Update();
|
||||
PowerPC::debug_interface.SetPatch(guard, addr, dialog.GetCode());
|
||||
Update(&guard);
|
||||
}
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnRestoreInstruction()
|
||||
{
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
PowerPC::debug_interface.UnsetPatch(addr);
|
||||
Update();
|
||||
PowerPC::debug_interface.UnsetPatch(guard, addr);
|
||||
Update(&guard);
|
||||
}
|
||||
|
||||
void CodeViewWidget::resizeEvent(QResizeEvent*)
|
||||
|
@ -15,6 +15,11 @@ class QMouseEvent;
|
||||
class QResizeEvent;
|
||||
class QShowEvent;
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class CPUThreadGuard;
|
||||
};
|
||||
|
||||
struct CodeViewBranch;
|
||||
class BranchDisplayDelegate;
|
||||
|
||||
@ -39,6 +44,7 @@ public:
|
||||
// Set tighter row height. Set BP column sizing. This needs to run when font type changes.
|
||||
void FontBasedSizing();
|
||||
void Update();
|
||||
void Update(const Core::CPUThreadGuard* guard);
|
||||
|
||||
void ToggleBreakpoint();
|
||||
void AddBreakpoint();
|
||||
|
@ -322,14 +322,17 @@ void CodeWidget::Update()
|
||||
|
||||
void CodeWidget::UpdateCallstack()
|
||||
{
|
||||
if (Core::GetState() == Core::State::Starting)
|
||||
return;
|
||||
|
||||
m_callstack_list->clear();
|
||||
|
||||
if (Core::GetState() != Core::State::Paused)
|
||||
return;
|
||||
|
||||
std::vector<Dolphin_Debugger::CallstackEntry> stack;
|
||||
|
||||
bool success = Dolphin_Debugger::GetCallstack(Core::System::GetInstance(), stack);
|
||||
const bool success = [&stack] {
|
||||
Core::CPUThreadGuard guard;
|
||||
return Dolphin_Debugger::GetCallstack(Core::System::GetInstance(), guard, stack);
|
||||
}();
|
||||
|
||||
if (!success)
|
||||
{
|
||||
@ -452,7 +455,11 @@ void CodeWidget::StepOver()
|
||||
if (!CPU::IsStepping())
|
||||
return;
|
||||
|
||||
UGeckoInstruction inst = PowerPC::HostRead_Instruction(PowerPC::ppcState.pc);
|
||||
const UGeckoInstruction inst = [] {
|
||||
Core::CPUThreadGuard guard;
|
||||
return PowerPC::HostRead_Instruction(guard, PowerPC::ppcState.pc);
|
||||
}();
|
||||
|
||||
if (inst.LK)
|
||||
{
|
||||
PowerPC::breakpoints.ClearAllTemporary();
|
||||
@ -485,48 +492,51 @@ void CodeWidget::StepOut()
|
||||
if (!CPU::IsStepping())
|
||||
return;
|
||||
|
||||
CPU::PauseAndLock(true, false);
|
||||
PowerPC::breakpoints.ClearAllTemporary();
|
||||
|
||||
// Keep stepping until the next return instruction or timeout after five seconds
|
||||
using clock = std::chrono::steady_clock;
|
||||
clock::time_point timeout = clock::now() + std::chrono::seconds(5);
|
||||
PowerPC::CoreMode old_mode = PowerPC::GetMode();
|
||||
PowerPC::SetMode(PowerPC::CoreMode::Interpreter);
|
||||
|
||||
// Loop until either the current instruction is a return instruction with no Link flag
|
||||
// or a breakpoint is detected so it can step at the breakpoint. If the PC is currently
|
||||
// on a breakpoint, skip it.
|
||||
UGeckoInstruction inst = PowerPC::HostRead_Instruction(PowerPC::ppcState.pc);
|
||||
do
|
||||
{
|
||||
if (WillInstructionReturn(inst))
|
||||
{
|
||||
PowerPC::SingleStep();
|
||||
break;
|
||||
}
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
if (inst.LK)
|
||||
PowerPC::breakpoints.ClearAllTemporary();
|
||||
|
||||
PowerPC::CoreMode old_mode = PowerPC::GetMode();
|
||||
PowerPC::SetMode(PowerPC::CoreMode::Interpreter);
|
||||
|
||||
// Loop until either the current instruction is a return instruction with no Link flag
|
||||
// or a breakpoint is detected so it can step at the breakpoint. If the PC is currently
|
||||
// on a breakpoint, skip it.
|
||||
UGeckoInstruction inst = PowerPC::HostRead_Instruction(guard, PowerPC::ppcState.pc);
|
||||
do
|
||||
{
|
||||
// Step over branches
|
||||
u32 next_pc = PowerPC::ppcState.pc + 4;
|
||||
do
|
||||
if (WillInstructionReturn(inst))
|
||||
{
|
||||
PowerPC::SingleStep();
|
||||
} while (PowerPC::ppcState.pc != next_pc && clock::now() < timeout &&
|
||||
!PowerPC::breakpoints.IsAddressBreakPoint(PowerPC::ppcState.pc));
|
||||
}
|
||||
else
|
||||
{
|
||||
PowerPC::SingleStep();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
inst = PowerPC::HostRead_Instruction(PowerPC::ppcState.pc);
|
||||
} while (clock::now() < timeout &&
|
||||
!PowerPC::breakpoints.IsAddressBreakPoint(PowerPC::ppcState.pc));
|
||||
if (inst.LK)
|
||||
{
|
||||
// Step over branches
|
||||
u32 next_pc = PowerPC::ppcState.pc + 4;
|
||||
do
|
||||
{
|
||||
PowerPC::SingleStep();
|
||||
} while (PowerPC::ppcState.pc != next_pc && clock::now() < timeout &&
|
||||
!PowerPC::breakpoints.IsAddressBreakPoint(PowerPC::ppcState.pc));
|
||||
}
|
||||
else
|
||||
{
|
||||
PowerPC::SingleStep();
|
||||
}
|
||||
|
||||
PowerPC::SetMode(old_mode);
|
||||
CPU::PauseAndLock(false, false);
|
||||
inst = PowerPC::HostRead_Instruction(guard, PowerPC::ppcState.pc);
|
||||
} while (clock::now() < timeout &&
|
||||
!PowerPC::breakpoints.IsAddressBreakPoint(PowerPC::ppcState.pc));
|
||||
|
||||
PowerPC::SetMode(old_mode);
|
||||
}
|
||||
|
||||
emit Host::GetInstance()->UpdateDisasmDialog();
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include <fmt/printf.h>
|
||||
|
||||
#include "Common/Align.h"
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/FloatUtils.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Swap.h"
|
||||
@ -46,6 +47,8 @@ constexpr int SCROLLBAR_PAGESTEP = 250;
|
||||
constexpr int SCROLLBAR_MAXIMUM = 20000;
|
||||
constexpr int SCROLLBAR_CENTER = SCROLLBAR_MAXIMUM / 2;
|
||||
|
||||
const QString INVALID_MEMORY = QStringLiteral("-");
|
||||
|
||||
class MemoryViewTable final : public QTableWidget
|
||||
{
|
||||
public:
|
||||
@ -151,11 +154,13 @@ public:
|
||||
u32 end_address = address + static_cast<u32>(bytes.size()) - 1;
|
||||
AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_view->GetAddressSpace());
|
||||
|
||||
if (!bytes.empty() && accessors->IsValidAddress(address) &&
|
||||
accessors->IsValidAddress(end_address))
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
if (!bytes.empty() && accessors->IsValidAddress(guard, address) &&
|
||||
accessors->IsValidAddress(guard, end_address))
|
||||
{
|
||||
for (const u8 c : bytes)
|
||||
accessors->WriteU8(address++, c);
|
||||
accessors->WriteU8(guard, address++, c);
|
||||
}
|
||||
|
||||
m_view->Update();
|
||||
@ -190,8 +195,9 @@ MemoryViewWidget::MemoryViewWidget(QWidget* parent) : QWidget(parent)
|
||||
|
||||
connect(&Settings::Instance(), &Settings::DebugFontChanged, this, &MemoryViewWidget::UpdateFont);
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
|
||||
&MemoryViewWidget::UpdateColumns);
|
||||
connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this, &MemoryViewWidget::UpdateColumns);
|
||||
qOverload<>(&MemoryViewWidget::UpdateColumns));
|
||||
connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this,
|
||||
qOverload<>(&MemoryViewWidget::UpdateColumns));
|
||||
connect(&Settings::Instance(), &Settings::ThemeChanged, this, &MemoryViewWidget::Update);
|
||||
|
||||
// Also calls create table.
|
||||
@ -322,13 +328,13 @@ void MemoryViewWidget::CreateTable()
|
||||
bp_item->setData(USER_ROLE_VALUE_TYPE, static_cast<int>(Type::Null));
|
||||
|
||||
// Row Addresses
|
||||
auto* row_item = new QTableWidgetItem(QStringLiteral("-"));
|
||||
auto* row_item = new QTableWidgetItem(INVALID_MEMORY);
|
||||
row_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
|
||||
row_item->setData(USER_ROLE_IS_ROW_BREAKPOINT_CELL, false);
|
||||
row_item->setData(USER_ROLE_VALUE_TYPE, static_cast<int>(Type::Null));
|
||||
|
||||
// Data item
|
||||
auto* item = new QTableWidgetItem(QStringLiteral("-"));
|
||||
auto* item = new QTableWidgetItem(INVALID_MEMORY);
|
||||
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
|
||||
item->setData(USER_ROLE_IS_ROW_BREAKPOINT_CELL, false);
|
||||
|
||||
@ -430,6 +436,24 @@ void MemoryViewWidget::Update()
|
||||
}
|
||||
|
||||
void MemoryViewWidget::UpdateColumns()
|
||||
{
|
||||
// Check if table is created
|
||||
if (m_table->item(1, 1) == nullptr)
|
||||
return;
|
||||
|
||||
if (Core::GetState() == Core::State::Paused)
|
||||
{
|
||||
Core::CPUThreadGuard guard;
|
||||
UpdateColumns(&guard);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the core is running, blank out the view of memory instead of reading anything.
|
||||
UpdateColumns(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryViewWidget::UpdateColumns(const Core::CPUThreadGuard* guard)
|
||||
{
|
||||
// Check if table is created
|
||||
if (m_table->item(1, 1) == nullptr)
|
||||
@ -445,7 +469,7 @@ void MemoryViewWidget::UpdateColumns()
|
||||
const u32 cell_address = cell_item->data(USER_ROLE_CELL_ADDRESS).toUInt();
|
||||
const Type type = static_cast<Type>(cell_item->data(USER_ROLE_VALUE_TYPE).toInt());
|
||||
|
||||
cell_item->setText(ValueToString(cell_address, type));
|
||||
cell_item->setText(guard ? ValueToString(*guard, cell_address, type) : INVALID_MEMORY);
|
||||
|
||||
// Set search address to selected / colored
|
||||
if (cell_address == m_address_highlight)
|
||||
@ -454,55 +478,56 @@ void MemoryViewWidget::UpdateColumns()
|
||||
}
|
||||
}
|
||||
|
||||
QString MemoryViewWidget::ValueToString(u32 address, Type type)
|
||||
// May only be called if we have taken on the role of the CPU thread
|
||||
QString MemoryViewWidget::ValueToString(const Core::CPUThreadGuard& guard, u32 address, Type type)
|
||||
{
|
||||
const AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_address_space);
|
||||
if (!accessors->IsValidAddress(address) || Core::GetState() != Core::State::Paused)
|
||||
return QStringLiteral("-");
|
||||
if (!accessors->IsValidAddress(guard, address))
|
||||
return INVALID_MEMORY;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case Type::Hex8:
|
||||
{
|
||||
const u8 value = accessors->ReadU8(address);
|
||||
const u8 value = accessors->ReadU8(guard, address);
|
||||
return QStringLiteral("%1").arg(value, 2, 16, QLatin1Char('0'));
|
||||
}
|
||||
case Type::ASCII:
|
||||
{
|
||||
const char value = accessors->ReadU8(address);
|
||||
const char value = accessors->ReadU8(guard, address);
|
||||
return IsPrintableCharacter(value) ? QString{QChar::fromLatin1(value)} :
|
||||
QString{QChar::fromLatin1('.')};
|
||||
}
|
||||
case Type::Hex16:
|
||||
{
|
||||
const u16 value = accessors->ReadU16(address);
|
||||
const u16 value = accessors->ReadU16(guard, address);
|
||||
return QStringLiteral("%1").arg(value, 4, 16, QLatin1Char('0'));
|
||||
}
|
||||
case Type::Hex32:
|
||||
{
|
||||
const u32 value = accessors->ReadU32(address);
|
||||
const u32 value = accessors->ReadU32(guard, address);
|
||||
return QStringLiteral("%1").arg(value, 8, 16, QLatin1Char('0'));
|
||||
}
|
||||
case Type::Hex64:
|
||||
{
|
||||
const u64 value = accessors->ReadU64(address);
|
||||
const u64 value = accessors->ReadU64(guard, address);
|
||||
return QStringLiteral("%1").arg(value, 16, 16, QLatin1Char('0'));
|
||||
}
|
||||
case Type::Unsigned8:
|
||||
return QString::number(accessors->ReadU8(address));
|
||||
return QString::number(accessors->ReadU8(guard, address));
|
||||
case Type::Unsigned16:
|
||||
return QString::number(accessors->ReadU16(address));
|
||||
return QString::number(accessors->ReadU16(guard, address));
|
||||
case Type::Unsigned32:
|
||||
return QString::number(accessors->ReadU32(address));
|
||||
return QString::number(accessors->ReadU32(guard, address));
|
||||
case Type::Signed8:
|
||||
return QString::number(Common::BitCast<s8>(accessors->ReadU8(address)));
|
||||
return QString::number(Common::BitCast<s8>(accessors->ReadU8(guard, address)));
|
||||
case Type::Signed16:
|
||||
return QString::number(Common::BitCast<s16>(accessors->ReadU16(address)));
|
||||
return QString::number(Common::BitCast<s16>(accessors->ReadU16(guard, address)));
|
||||
case Type::Signed32:
|
||||
return QString::number(Common::BitCast<s32>(accessors->ReadU32(address)));
|
||||
return QString::number(Common::BitCast<s32>(accessors->ReadU32(guard, address)));
|
||||
case Type::Float32:
|
||||
{
|
||||
QString string = QString::number(accessors->ReadF32(address), 'g', 4);
|
||||
QString string = QString::number(accessors->ReadF32(guard, address), 'g', 4);
|
||||
// Align to first digit.
|
||||
if (!string.startsWith(QLatin1Char('-')))
|
||||
string.prepend(QLatin1Char(' '));
|
||||
@ -511,7 +536,8 @@ QString MemoryViewWidget::ValueToString(u32 address, Type type)
|
||||
}
|
||||
case Type::Double:
|
||||
{
|
||||
QString string = QString::number(Common::BitCast<double>(accessors->ReadU64(address)), 'g', 4);
|
||||
QString string =
|
||||
QString::number(Common::BitCast<double>(accessors->ReadU64(guard, address)), 'g', 4);
|
||||
// Align to first digit.
|
||||
if (!string.startsWith(QLatin1Char('-')))
|
||||
string.prepend(QLatin1Char(' '));
|
||||
@ -519,7 +545,7 @@ QString MemoryViewWidget::ValueToString(u32 address, Type type)
|
||||
return string;
|
||||
}
|
||||
default:
|
||||
return QStringLiteral("-");
|
||||
return INVALID_MEMORY;
|
||||
}
|
||||
}
|
||||
|
||||
@ -823,7 +849,11 @@ void MemoryViewWidget::OnCopyHex(u32 addr)
|
||||
const auto length = GetTypeSize(m_type);
|
||||
|
||||
const AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_address_space);
|
||||
u64 value = accessors->ReadU64(addr);
|
||||
|
||||
const u64 value = [addr, accessors] {
|
||||
Core::CPUThreadGuard guard;
|
||||
return accessors->ReadU64(guard, addr);
|
||||
}();
|
||||
|
||||
QApplication::clipboard()->setText(
|
||||
QStringLiteral("%1").arg(value, sizeof(u64) * 2, 16, QLatin1Char('0')).left(length * 2));
|
||||
@ -839,10 +869,14 @@ void MemoryViewWidget::OnContextMenu(const QPoint& pos)
|
||||
return;
|
||||
|
||||
const u32 addr = item_selected->data(USER_ROLE_CELL_ADDRESS).toUInt();
|
||||
const AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_address_space);
|
||||
const bool item_has_value =
|
||||
item_selected->data(USER_ROLE_VALUE_TYPE).toInt() != static_cast<int>(Type::Null) &&
|
||||
accessors->IsValidAddress(addr);
|
||||
[this, addr] {
|
||||
const AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_address_space);
|
||||
|
||||
Core::CPUThreadGuard guard;
|
||||
return accessors->IsValidAddress(guard, addr);
|
||||
}();
|
||||
|
||||
auto* menu = new QMenu(this);
|
||||
|
||||
|
@ -15,6 +15,11 @@ namespace AddressSpace
|
||||
enum class Type;
|
||||
}
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class CPUThreadGuard;
|
||||
}
|
||||
|
||||
class MemoryViewTable;
|
||||
|
||||
class MemoryViewWidget final : public QWidget
|
||||
@ -75,9 +80,10 @@ private:
|
||||
void OnCopyHex(u32 addr);
|
||||
void UpdateBreakpointTags();
|
||||
void UpdateColumns();
|
||||
void UpdateColumns(const Core::CPUThreadGuard* guard);
|
||||
void ScrollbarActionTriggered(int action);
|
||||
void ScrollbarSliderReleased();
|
||||
QString ValueToString(u32 address, Type type);
|
||||
QString ValueToString(const Core::CPUThreadGuard& guard, u32 address, Type type);
|
||||
|
||||
MemoryViewTable* m_table;
|
||||
QScrollBar* m_scrollbar;
|
||||
|
@ -495,7 +495,9 @@ void MemoryWidget::SetAddress(u32 address)
|
||||
{
|
||||
AddressSpace::Accessors* accessors =
|
||||
AddressSpace::GetAccessors(m_memory_view->GetAddressSpace());
|
||||
good = accessors->IsValidAddress(current_addr);
|
||||
|
||||
Core::CPUThreadGuard guard;
|
||||
good = accessors->IsValidAddress(guard, current_addr);
|
||||
}
|
||||
|
||||
if (m_search_address->findText(current_text) == -1 && good)
|
||||
@ -651,17 +653,20 @@ void MemoryWidget::OnSetValue()
|
||||
return;
|
||||
}
|
||||
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_memory_view->GetAddressSpace());
|
||||
u32 end_address = target_addr.address + static_cast<u32>(bytes.size()) - 1;
|
||||
|
||||
if (!accessors->IsValidAddress(target_addr.address) || !accessors->IsValidAddress(end_address))
|
||||
if (!accessors->IsValidAddress(guard, target_addr.address) ||
|
||||
!accessors->IsValidAddress(guard, end_address))
|
||||
{
|
||||
ModalMessageBox::critical(this, tr("Error"), tr("Target address range is invalid."));
|
||||
return;
|
||||
}
|
||||
|
||||
for (const char c : bytes)
|
||||
accessors->WriteU8(target_addr.address++, static_cast<u8>(c));
|
||||
accessors->WriteU8(guard, target_addr.address++, static_cast<u8>(c));
|
||||
|
||||
Update();
|
||||
}
|
||||
@ -710,8 +715,10 @@ void MemoryWidget::OnSetValueFromFile()
|
||||
|
||||
AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_memory_view->GetAddressSpace());
|
||||
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
for (u8 b : file_contents)
|
||||
accessors->WriteU8(target_addr.address++, b);
|
||||
accessors->WriteU8(guard, target_addr.address++, b);
|
||||
|
||||
Update();
|
||||
}
|
||||
@ -822,11 +829,15 @@ void MemoryWidget::FindValue(bool next)
|
||||
target_addr.address += next ? 1 : -1;
|
||||
}
|
||||
|
||||
AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_memory_view->GetAddressSpace());
|
||||
const std::optional<u32> found_addr = [&] {
|
||||
AddressSpace::Accessors* accessors =
|
||||
AddressSpace::GetAccessors(m_memory_view->GetAddressSpace());
|
||||
|
||||
const auto found_addr =
|
||||
accessors->Search(target_addr.address, reinterpret_cast<const u8*>(search_for.data()),
|
||||
static_cast<u32>(search_for.size()), next);
|
||||
Core::CPUThreadGuard guard;
|
||||
return accessors->Search(guard, target_addr.address,
|
||||
reinterpret_cast<const u8*>(search_for.data()),
|
||||
static_cast<u32>(search_for.size()), next);
|
||||
}();
|
||||
|
||||
if (found_addr.has_value())
|
||||
{
|
||||
|
@ -20,6 +20,11 @@ class QRadioButton;
|
||||
class QShowEvent;
|
||||
class QSplitter;
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class CPUThreadGuard;
|
||||
}
|
||||
|
||||
class MemoryWidget : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -295,7 +295,11 @@ void RegisterWidget::AutoStep(const std::string& reg) const
|
||||
|
||||
while (true)
|
||||
{
|
||||
const AutoStepResults results = trace.AutoStepping(true);
|
||||
const AutoStepResults results = [&trace] {
|
||||
Core::CPUThreadGuard guard;
|
||||
return trace.AutoStepping(guard, true);
|
||||
}();
|
||||
|
||||
emit Host::GetInstance()->UpdateDisasmDialog();
|
||||
|
||||
if (!results.timed_out)
|
||||
|
@ -256,7 +256,9 @@ void ThreadWidget::Update()
|
||||
{
|
||||
m_thread_table->setRowCount(0);
|
||||
UpdateThreadContext({});
|
||||
UpdateThreadCallstack({});
|
||||
|
||||
Core::CPUThreadGuard guard;
|
||||
UpdateThreadCallstack(guard, {});
|
||||
}
|
||||
if (emu_state != Core::State::Paused)
|
||||
return;
|
||||
@ -264,8 +266,8 @@ void ThreadWidget::Update()
|
||||
const auto format_hex = [](u32 value) {
|
||||
return QStringLiteral("%1").arg(value, 8, 16, QLatin1Char('0'));
|
||||
};
|
||||
const auto format_hex_from = [&format_hex](u32 addr) {
|
||||
addr = PowerPC::HostIsRAMAddress(addr) ? PowerPC::HostRead_U32(addr) : 0;
|
||||
const auto format_hex_from = [&format_hex](const Core::CPUThreadGuard& guard, u32 addr) {
|
||||
addr = PowerPC::HostIsRAMAddress(guard, addr) ? PowerPC::HostRead_U32(guard, addr) : 0;
|
||||
return format_hex(addr);
|
||||
};
|
||||
const auto get_state = [](u16 thread_state) {
|
||||
@ -298,35 +300,41 @@ void ThreadWidget::Update()
|
||||
.arg(start, 8, 16, QLatin1Char('0'));
|
||||
};
|
||||
|
||||
// YAGCD - Section 4.2.1.4 Dolphin OS Globals
|
||||
m_current_context->setText(format_hex_from(0x800000D4));
|
||||
m_current_thread->setText(format_hex_from(0x800000E4));
|
||||
m_default_thread->setText(format_hex_from(0x800000D8));
|
||||
|
||||
m_queue_head->setText(format_hex_from(0x800000DC));
|
||||
m_queue_tail->setText(format_hex_from(0x800000E0));
|
||||
|
||||
// Thread group
|
||||
m_threads = PowerPC::debug_interface.GetThreads();
|
||||
int i = 0;
|
||||
m_thread_table->setRowCount(i);
|
||||
for (const auto& thread : m_threads)
|
||||
{
|
||||
m_thread_table->insertRow(i);
|
||||
m_thread_table->setItem(i, 0, new QTableWidgetItem(format_hex(thread->GetAddress())));
|
||||
m_thread_table->setItem(i, 1, new QTableWidgetItem(get_state(thread->GetState())));
|
||||
m_thread_table->setItem(i, 2, new QTableWidgetItem(QString::number(thread->IsDetached())));
|
||||
m_thread_table->setItem(i, 3, new QTableWidgetItem(QString::number(thread->IsSuspended())));
|
||||
m_thread_table->setItem(i, 4,
|
||||
new QTableWidgetItem(get_priority(thread->GetBasePriority(),
|
||||
thread->GetEffectivePriority())));
|
||||
m_thread_table->setItem(
|
||||
i, 5, new QTableWidgetItem(get_stack(thread->GetStackEnd(), thread->GetStackStart())));
|
||||
m_thread_table->setItem(i, 6, new QTableWidgetItem(QString::number(thread->GetErrno())));
|
||||
m_thread_table->setItem(i, 7,
|
||||
new QTableWidgetItem(QString::fromStdString(thread->GetSpecific())));
|
||||
i += 1;
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
// YAGCD - Section 4.2.1.4 Dolphin OS Globals
|
||||
m_current_context->setText(format_hex_from(guard, 0x800000D4));
|
||||
m_current_thread->setText(format_hex_from(guard, 0x800000E4));
|
||||
m_default_thread->setText(format_hex_from(guard, 0x800000D8));
|
||||
|
||||
m_queue_head->setText(format_hex_from(guard, 0x800000DC));
|
||||
m_queue_tail->setText(format_hex_from(guard, 0x800000E0));
|
||||
|
||||
// Thread group
|
||||
m_threads = PowerPC::debug_interface.GetThreads(guard);
|
||||
|
||||
int i = 0;
|
||||
m_thread_table->setRowCount(i);
|
||||
for (const auto& thread : m_threads)
|
||||
{
|
||||
m_thread_table->insertRow(i);
|
||||
m_thread_table->setItem(i, 0, new QTableWidgetItem(format_hex(thread->GetAddress())));
|
||||
m_thread_table->setItem(i, 1, new QTableWidgetItem(get_state(thread->GetState())));
|
||||
m_thread_table->setItem(i, 2, new QTableWidgetItem(QString::number(thread->IsDetached())));
|
||||
m_thread_table->setItem(i, 3, new QTableWidgetItem(QString::number(thread->IsSuspended())));
|
||||
m_thread_table->setItem(i, 4,
|
||||
new QTableWidgetItem(get_priority(thread->GetBasePriority(),
|
||||
thread->GetEffectivePriority())));
|
||||
m_thread_table->setItem(
|
||||
i, 5, new QTableWidgetItem(get_stack(thread->GetStackEnd(), thread->GetStackStart())));
|
||||
m_thread_table->setItem(i, 6, new QTableWidgetItem(QString::number(thread->GetErrno())));
|
||||
m_thread_table->setItem(
|
||||
i, 7, new QTableWidgetItem(QString::fromStdString(thread->GetSpecific(guard))));
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
m_thread_table->resizeColumnsToContents();
|
||||
m_thread_table->resizeRowsToContents();
|
||||
|
||||
@ -425,7 +433,8 @@ void ThreadWidget::UpdateThreadContext(const Common::Debug::PartialContext& cont
|
||||
m_context_table->resizeColumnsToContents();
|
||||
}
|
||||
|
||||
void ThreadWidget::UpdateThreadCallstack(const Common::Debug::PartialContext& context)
|
||||
void ThreadWidget::UpdateThreadCallstack(const Core::CPUThreadGuard& guard,
|
||||
const Common::Debug::PartialContext& context)
|
||||
{
|
||||
m_callstack_table->setRowCount(0);
|
||||
|
||||
@ -439,13 +448,13 @@ void ThreadWidget::UpdateThreadCallstack(const Common::Debug::PartialContext& co
|
||||
u32 sp = context.gpr->at(1);
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
if (sp == 0 || sp == 0xffffffff || !PowerPC::HostIsRAMAddress(sp))
|
||||
if (sp == 0 || sp == 0xffffffff || !PowerPC::HostIsRAMAddress(guard, sp))
|
||||
break;
|
||||
m_callstack_table->insertRow(i);
|
||||
m_callstack_table->setItem(i, 0, new QTableWidgetItem(format_hex(sp)));
|
||||
if (PowerPC::HostIsRAMAddress(sp + 4))
|
||||
if (PowerPC::HostIsRAMAddress(guard, sp + 4))
|
||||
{
|
||||
const u32 lr_save = PowerPC::HostRead_U32(sp + 4);
|
||||
const u32 lr_save = PowerPC::HostRead_U32(guard, sp + 4);
|
||||
m_callstack_table->setItem(i, 2, new QTableWidgetItem(format_hex(lr_save)));
|
||||
m_callstack_table->setItem(i, 3,
|
||||
new QTableWidgetItem(QString::fromStdString(
|
||||
@ -455,18 +464,19 @@ void ThreadWidget::UpdateThreadCallstack(const Common::Debug::PartialContext& co
|
||||
{
|
||||
m_callstack_table->setItem(i, 2, new QTableWidgetItem(QStringLiteral("--------")));
|
||||
}
|
||||
sp = PowerPC::HostRead_U32(sp);
|
||||
sp = PowerPC::HostRead_U32(guard, sp);
|
||||
m_callstack_table->setItem(i, 1, new QTableWidgetItem(format_hex(sp)));
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadWidget::OnSelectionChanged(int row)
|
||||
{
|
||||
Core::CPUThreadGuard guard;
|
||||
Common::Debug::PartialContext context;
|
||||
|
||||
if (row >= 0 && size_t(row) < m_threads.size())
|
||||
context = m_threads[row]->GetContext();
|
||||
context = m_threads[row]->GetContext(guard);
|
||||
|
||||
UpdateThreadContext(context);
|
||||
UpdateThreadCallstack(context);
|
||||
UpdateThreadCallstack(guard, context);
|
||||
}
|
||||
|
@ -47,7 +47,8 @@ private:
|
||||
|
||||
void Update();
|
||||
void UpdateThreadContext(const Common::Debug::PartialContext& context);
|
||||
void UpdateThreadCallstack(const Common::Debug::PartialContext& context);
|
||||
void UpdateThreadCallstack(const Core::CPUThreadGuard& guard,
|
||||
const Common::Debug::PartialContext& context);
|
||||
void OnSelectionChanged(int row);
|
||||
|
||||
QGroupBox* m_state;
|
||||
|
@ -161,6 +161,8 @@ void WatchWidget::Update()
|
||||
// i18n: Floating-point (non-integer) number
|
||||
tr("Float"), tr("Locked")});
|
||||
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
const auto& entry = PowerPC::debug_interface.GetWatch(i);
|
||||
@ -181,18 +183,18 @@ void WatchWidget::Update()
|
||||
|
||||
QBrush brush = QPalette().brush(QPalette::Text);
|
||||
|
||||
if (!Core::IsRunning() || !PowerPC::HostIsRAMAddress(entry.address))
|
||||
if (!Core::IsRunning() || !PowerPC::HostIsRAMAddress(guard, entry.address))
|
||||
brush.setColor(Qt::red);
|
||||
|
||||
if (Core::IsRunning())
|
||||
{
|
||||
if (PowerPC::HostIsRAMAddress(entry.address))
|
||||
if (PowerPC::HostIsRAMAddress(guard, entry.address))
|
||||
{
|
||||
hex->setText(QStringLiteral("%1").arg(PowerPC::HostRead_U32(entry.address), 8, 16,
|
||||
hex->setText(QStringLiteral("%1").arg(PowerPC::HostRead_U32(guard, entry.address), 8, 16,
|
||||
QLatin1Char('0')));
|
||||
decimal->setText(QString::number(PowerPC::HostRead_U32(entry.address)));
|
||||
string->setText(QString::fromStdString(PowerPC::HostGetString(entry.address, 32)));
|
||||
floatValue->setText(QString::number(PowerPC::HostRead_F32(entry.address)));
|
||||
decimal->setText(QString::number(PowerPC::HostRead_U32(guard, entry.address)));
|
||||
string->setText(QString::fromStdString(PowerPC::HostGetString(guard, entry.address, 32)));
|
||||
floatValue->setText(QString::number(PowerPC::HostRead_F32(guard, entry.address)));
|
||||
lockValue->setCheckState(entry.locked ? Qt::Checked : Qt::Unchecked);
|
||||
}
|
||||
}
|
||||
@ -279,11 +281,13 @@ void WatchWidget::OnLoad()
|
||||
return;
|
||||
}
|
||||
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
if (ini.GetLines("Watches", &watches, false))
|
||||
{
|
||||
for (const auto& watch : PowerPC::debug_interface.GetWatches())
|
||||
{
|
||||
PowerPC::debug_interface.UnsetPatch(watch.address);
|
||||
PowerPC::debug_interface.UnsetPatch(guard, watch.address);
|
||||
}
|
||||
PowerPC::debug_interface.ClearWatches();
|
||||
PowerPC::debug_interface.LoadWatchesFromStrings(watches);
|
||||
@ -387,17 +391,19 @@ void WatchWidget::OnItemChanged(QTableWidgetItem* item)
|
||||
|
||||
if (good)
|
||||
{
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
if (column == COLUMN_INDEX_ADDRESS)
|
||||
{
|
||||
const auto& watch = PowerPC::debug_interface.GetWatch(row);
|
||||
PowerPC::debug_interface.UnsetPatch(watch.address);
|
||||
PowerPC::debug_interface.UnsetPatch(guard, watch.address);
|
||||
PowerPC::debug_interface.UpdateWatchAddress(row, value);
|
||||
if (watch.locked)
|
||||
LockWatchAddress(value);
|
||||
LockWatchAddress(guard, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
PowerPC::HostWrite_U32(value, PowerPC::debug_interface.GetWatch(row).address);
|
||||
PowerPC::HostWrite_U32(guard, value, PowerPC::debug_interface.GetWatch(row).address);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -410,10 +416,11 @@ void WatchWidget::OnItemChanged(QTableWidgetItem* item)
|
||||
{
|
||||
PowerPC::debug_interface.UpdateWatchLockedState(row, item->checkState() == Qt::Checked);
|
||||
const auto& watch = PowerPC::debug_interface.GetWatch(row);
|
||||
Core::CPUThreadGuard guard;
|
||||
if (watch.locked)
|
||||
LockWatchAddress(watch.address);
|
||||
LockWatchAddress(guard, watch.address);
|
||||
else
|
||||
PowerPC::debug_interface.UnsetPatch(watch.address);
|
||||
PowerPC::debug_interface.UnsetPatch(guard, watch.address);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -422,9 +429,9 @@ void WatchWidget::OnItemChanged(QTableWidgetItem* item)
|
||||
}
|
||||
}
|
||||
|
||||
void WatchWidget::LockWatchAddress(u32 address)
|
||||
void WatchWidget::LockWatchAddress(const Core::CPUThreadGuard& guard, u32 address)
|
||||
{
|
||||
const std::string memory_data_as_string = PowerPC::HostGetString(address, 4);
|
||||
const std::string memory_data_as_string = PowerPC::HostGetString(guard, address, 4);
|
||||
|
||||
std::vector<u8> bytes;
|
||||
for (const char c : memory_data_as_string)
|
||||
@ -432,42 +439,48 @@ void WatchWidget::LockWatchAddress(u32 address)
|
||||
bytes.push_back(static_cast<u8>(c));
|
||||
}
|
||||
|
||||
PowerPC::debug_interface.SetFramePatch(address, bytes);
|
||||
PowerPC::debug_interface.SetFramePatch(guard, address, bytes);
|
||||
}
|
||||
|
||||
void WatchWidget::DeleteSelectedWatches()
|
||||
{
|
||||
std::vector<int> row_indices;
|
||||
for (const auto& index : m_table->selectionModel()->selectedRows())
|
||||
{
|
||||
const auto* item = m_table->item(index.row(), index.column());
|
||||
const auto row_variant = item->data(Qt::UserRole);
|
||||
if (row_variant.isNull())
|
||||
continue;
|
||||
Core::CPUThreadGuard guard;
|
||||
std::vector<int> row_indices;
|
||||
for (const auto& index : m_table->selectionModel()->selectedRows())
|
||||
{
|
||||
const auto* item = m_table->item(index.row(), index.column());
|
||||
const auto row_variant = item->data(Qt::UserRole);
|
||||
if (row_variant.isNull())
|
||||
continue;
|
||||
|
||||
row_indices.push_back(row_variant.toInt());
|
||||
}
|
||||
row_indices.push_back(row_variant.toInt());
|
||||
}
|
||||
|
||||
// Sort greatest to smallest, so we
|
||||
// don't stomp on existing indices
|
||||
std::sort(row_indices.begin(), row_indices.end(), std::greater{});
|
||||
for (const int row : row_indices)
|
||||
{
|
||||
DeleteWatch(row);
|
||||
// Sort greatest to smallest, so we don't stomp on existing indices
|
||||
std::sort(row_indices.begin(), row_indices.end(), std::greater{});
|
||||
for (const int row : row_indices)
|
||||
{
|
||||
DeleteWatch(guard, row);
|
||||
}
|
||||
}
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
void WatchWidget::DeleteWatch(int row)
|
||||
void WatchWidget::DeleteWatch(const Core::CPUThreadGuard& guard, int row)
|
||||
{
|
||||
PowerPC::debug_interface.UnsetPatch(PowerPC::debug_interface.GetWatch(row).address);
|
||||
PowerPC::debug_interface.UnsetPatch(guard, PowerPC::debug_interface.GetWatch(row).address);
|
||||
PowerPC::debug_interface.RemoveWatch(row);
|
||||
}
|
||||
|
||||
void WatchWidget::DeleteWatchAndUpdate(int row)
|
||||
{
|
||||
DeleteWatch(row);
|
||||
{
|
||||
Core::CPUThreadGuard guard;
|
||||
DeleteWatch(guard, row);
|
||||
}
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
@ -489,18 +502,21 @@ void WatchWidget::AddWatch(QString name, u32 addr)
|
||||
|
||||
void WatchWidget::LockSelectedWatches()
|
||||
{
|
||||
for (const auto& index : m_table->selectionModel()->selectedRows())
|
||||
{
|
||||
const auto* item = m_table->item(index.row(), index.column());
|
||||
const auto row_variant = item->data(Qt::UserRole);
|
||||
if (row_variant.isNull())
|
||||
continue;
|
||||
const int row = row_variant.toInt();
|
||||
const auto& watch = PowerPC::debug_interface.GetWatch(row);
|
||||
if (watch.locked)
|
||||
continue;
|
||||
PowerPC::debug_interface.UpdateWatchLockedState(row, true);
|
||||
LockWatchAddress(watch.address);
|
||||
Core::CPUThreadGuard guard;
|
||||
for (const auto& index : m_table->selectionModel()->selectedRows())
|
||||
{
|
||||
const auto* item = m_table->item(index.row(), index.column());
|
||||
const auto row_variant = item->data(Qt::UserRole);
|
||||
if (row_variant.isNull())
|
||||
continue;
|
||||
const int row = row_variant.toInt();
|
||||
const auto& watch = PowerPC::debug_interface.GetWatch(row);
|
||||
if (watch.locked)
|
||||
continue;
|
||||
PowerPC::debug_interface.UpdateWatchLockedState(row, true);
|
||||
LockWatchAddress(guard, watch.address);
|
||||
}
|
||||
}
|
||||
|
||||
Update();
|
||||
@ -508,18 +524,21 @@ void WatchWidget::LockSelectedWatches()
|
||||
|
||||
void WatchWidget::UnlockSelectedWatches()
|
||||
{
|
||||
for (const auto& index : m_table->selectionModel()->selectedRows())
|
||||
{
|
||||
const auto* item = m_table->item(index.row(), index.column());
|
||||
const auto row_variant = item->data(Qt::UserRole);
|
||||
if (row_variant.isNull())
|
||||
continue;
|
||||
const int row = row_variant.toInt();
|
||||
const auto& watch = PowerPC::debug_interface.GetWatch(row);
|
||||
if (!watch.locked)
|
||||
continue;
|
||||
PowerPC::debug_interface.UpdateWatchLockedState(row, false);
|
||||
PowerPC::debug_interface.UnsetPatch(watch.address);
|
||||
Core::CPUThreadGuard guard;
|
||||
for (const auto& index : m_table->selectionModel()->selectedRows())
|
||||
{
|
||||
const auto* item = m_table->item(index.row(), index.column());
|
||||
const auto row_variant = item->data(Qt::UserRole);
|
||||
if (row_variant.isNull())
|
||||
continue;
|
||||
const int row = row_variant.toInt();
|
||||
const auto& watch = PowerPC::debug_interface.GetWatch(row);
|
||||
if (!watch.locked)
|
||||
continue;
|
||||
PowerPC::debug_interface.UpdateWatchLockedState(row, false);
|
||||
PowerPC::debug_interface.UnsetPatch(guard, watch.address);
|
||||
}
|
||||
}
|
||||
|
||||
Update();
|
||||
|
@ -14,6 +14,11 @@ class QTableWidget;
|
||||
class QTableWidgetItem;
|
||||
class QToolBar;
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class CPUThreadGuard;
|
||||
};
|
||||
|
||||
class WatchWidget : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -46,9 +51,9 @@ private:
|
||||
|
||||
void ShowContextMenu();
|
||||
void OnItemChanged(QTableWidgetItem* item);
|
||||
void LockWatchAddress(u32 address);
|
||||
void LockWatchAddress(const Core::CPUThreadGuard& guard, u32 address);
|
||||
void DeleteSelectedWatches();
|
||||
void DeleteWatch(int row);
|
||||
void DeleteWatch(const Core::CPUThreadGuard& guard, int row);
|
||||
void DeleteWatchAndUpdate(int row);
|
||||
void AddWatchBreakpoint(int row);
|
||||
void ShowInMemory(int row);
|
||||
|
@ -1186,25 +1186,29 @@ void MenuBar::ClearSymbols()
|
||||
|
||||
void MenuBar::GenerateSymbolsFromAddress()
|
||||
{
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& memory = system.GetMemory();
|
||||
|
||||
PPCAnalyst::FindFunctions(Memory::MEM1_BASE_ADDR,
|
||||
PPCAnalyst::FindFunctions(guard, Memory::MEM1_BASE_ADDR,
|
||||
Memory::MEM1_BASE_ADDR + memory.GetRamSizeReal(), &g_symbolDB);
|
||||
emit NotifySymbolsUpdated();
|
||||
}
|
||||
|
||||
void MenuBar::GenerateSymbolsFromSignatureDB()
|
||||
{
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& memory = system.GetMemory();
|
||||
|
||||
PPCAnalyst::FindFunctions(Memory::MEM1_BASE_ADDR,
|
||||
PPCAnalyst::FindFunctions(guard, Memory::MEM1_BASE_ADDR,
|
||||
Memory::MEM1_BASE_ADDR + memory.GetRamSizeReal(), &g_symbolDB);
|
||||
SignatureDB db(SignatureDB::HandlerType::DSY);
|
||||
if (db.Load(File::GetSysDirectory() + TOTALDB))
|
||||
{
|
||||
db.Apply(&g_symbolDB);
|
||||
db.Apply(guard, &g_symbolDB);
|
||||
ModalMessageBox::information(
|
||||
this, tr("Information"),
|
||||
tr("Generated symbol names from '%1'").arg(QString::fromStdString(TOTALDB)));
|
||||
@ -1240,10 +1244,12 @@ void MenuBar::GenerateSymbolsFromRSO()
|
||||
return;
|
||||
}
|
||||
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
RSOChainView rso_chain;
|
||||
if (rso_chain.Load(static_cast<u32>(address)))
|
||||
if (rso_chain.Load(guard, static_cast<u32>(address)))
|
||||
{
|
||||
rso_chain.Apply(&g_symbolDB);
|
||||
rso_chain.Apply(guard, &g_symbolDB);
|
||||
emit NotifySymbolsUpdated();
|
||||
}
|
||||
else
|
||||
@ -1293,9 +1299,12 @@ void MenuBar::GenerateSymbolsFromRSOAuto()
|
||||
|
||||
RSOChainView rso_chain;
|
||||
const u32 address = item.mid(0, item.indexOf(QLatin1Char(' '))).toUInt(nullptr, 16);
|
||||
if (rso_chain.Load(address))
|
||||
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
if (rso_chain.Load(guard, address))
|
||||
{
|
||||
rso_chain.Apply(&g_symbolDB);
|
||||
rso_chain.Apply(guard, &g_symbolDB);
|
||||
emit NotifySymbolsUpdated();
|
||||
}
|
||||
else
|
||||
@ -1306,6 +1315,8 @@ void MenuBar::GenerateSymbolsFromRSOAuto()
|
||||
|
||||
RSOVector MenuBar::DetectRSOModules(ParallelProgressDialog& progress)
|
||||
{
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
constexpr std::array<std::string_view, 2> search_for = {".elf", ".plf"};
|
||||
|
||||
const AddressSpace::Accessors* accessors =
|
||||
@ -1324,8 +1335,8 @@ RSOVector MenuBar::DetectRSOModules(ParallelProgressDialog& progress)
|
||||
return matches;
|
||||
}
|
||||
|
||||
auto found_addr =
|
||||
accessors->Search(next, reinterpret_cast<const u8*>(str.data()), str.size() + 1, true);
|
||||
auto found_addr = accessors->Search(guard, next, reinterpret_cast<const u8*>(str.data()),
|
||||
str.size() + 1, true);
|
||||
|
||||
if (!found_addr.has_value())
|
||||
break;
|
||||
@ -1334,13 +1345,13 @@ RSOVector MenuBar::DetectRSOModules(ParallelProgressDialog& progress)
|
||||
|
||||
// Non-null data can precede the module name.
|
||||
// Get the maximum name length that a module could have.
|
||||
auto get_max_module_name_len = [found_addr] {
|
||||
auto get_max_module_name_len = [&guard, found_addr] {
|
||||
constexpr u32 MODULE_NAME_MAX_LENGTH = 260;
|
||||
u32 len = 0;
|
||||
|
||||
for (; len < MODULE_NAME_MAX_LENGTH; ++len)
|
||||
{
|
||||
const auto res = PowerPC::HostRead_U8(*found_addr - (len + 1));
|
||||
const auto res = PowerPC::HostRead_U8(guard, *found_addr - (len + 1));
|
||||
if (!std::isprint(res))
|
||||
{
|
||||
break;
|
||||
@ -1375,12 +1386,12 @@ RSOVector MenuBar::DetectRSOModules(ParallelProgressDialog& progress)
|
||||
|
||||
// Get the field (Module Name Offset) that point to the string
|
||||
const auto module_name_offset_addr =
|
||||
accessors->Search(lookup_addr, ref.data(), ref.size(), false);
|
||||
accessors->Search(guard, lookup_addr, ref.data(), ref.size(), false);
|
||||
if (!module_name_offset_addr.has_value())
|
||||
continue;
|
||||
|
||||
// The next 4 bytes should be the module name length
|
||||
module_name_length = accessors->ReadU32(*module_name_offset_addr + 4);
|
||||
module_name_length = accessors->ReadU32(guard, *module_name_offset_addr + 4);
|
||||
if (module_name_length == max_name_length - i + str.length())
|
||||
{
|
||||
found_addr = module_name_offset_addr;
|
||||
@ -1392,11 +1403,11 @@ RSOVector MenuBar::DetectRSOModules(ParallelProgressDialog& progress)
|
||||
if (!found)
|
||||
continue;
|
||||
|
||||
const auto module_name_offset = accessors->ReadU32(*found_addr);
|
||||
const auto module_name_offset = accessors->ReadU32(guard, *found_addr);
|
||||
|
||||
// Go to the beginning of the RSO header
|
||||
matches.emplace_back(*found_addr - 16,
|
||||
PowerPC::HostGetString(module_name_offset, module_name_length));
|
||||
PowerPC::HostGetString(guard, module_name_offset, module_name_length));
|
||||
|
||||
progress.SetLabelText(tr("Modules found: %1").arg(matches.size()));
|
||||
}
|
||||
@ -1416,11 +1427,16 @@ void MenuBar::LoadSymbolMap()
|
||||
if (!map_exists)
|
||||
{
|
||||
g_symbolDB.Clear();
|
||||
PPCAnalyst::FindFunctions(Memory::MEM1_BASE_ADDR + 0x1300000,
|
||||
Memory::MEM1_BASE_ADDR + memory.GetRamSizeReal(), &g_symbolDB);
|
||||
SignatureDB db(SignatureDB::HandlerType::DSY);
|
||||
if (db.Load(File::GetSysDirectory() + TOTALDB))
|
||||
db.Apply(&g_symbolDB);
|
||||
|
||||
{
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
PPCAnalyst::FindFunctions(guard, Memory::MEM1_BASE_ADDR + 0x1300000,
|
||||
Memory::MEM1_BASE_ADDR + memory.GetRamSizeReal(), &g_symbolDB);
|
||||
SignatureDB db(SignatureDB::HandlerType::DSY);
|
||||
if (db.Load(File::GetSysDirectory() + TOTALDB))
|
||||
db.Apply(guard, &g_symbolDB);
|
||||
}
|
||||
|
||||
ModalMessageBox::warning(this, tr("Warning"),
|
||||
tr("'%1' not found, scanning for common functions instead")
|
||||
@ -1505,7 +1521,13 @@ void MenuBar::SaveCode()
|
||||
const std::string path =
|
||||
writable_map_file.substr(0, writable_map_file.find_last_of('.')) + "_code.map";
|
||||
|
||||
if (!g_symbolDB.SaveCodeMap(path))
|
||||
bool success;
|
||||
{
|
||||
Core::CPUThreadGuard guard;
|
||||
success = g_symbolDB.SaveCodeMap(guard, path);
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
ModalMessageBox::warning(
|
||||
this, tr("Error"),
|
||||
@ -1515,7 +1537,9 @@ void MenuBar::SaveCode()
|
||||
|
||||
bool MenuBar::TryLoadMapFile(const QString& path, const bool bad)
|
||||
{
|
||||
if (!g_symbolDB.LoadMap(path.toStdString(), bad))
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
if (!g_symbolDB.LoadMap(guard, path.toStdString(), bad))
|
||||
{
|
||||
ModalMessageBox::warning(this, tr("Error"), tr("Failed to load map file '%1'").arg(path));
|
||||
return false;
|
||||
@ -1596,7 +1620,10 @@ void MenuBar::ApplySignatureFile()
|
||||
const std::string load_path = file.toStdString();
|
||||
SignatureDB db(load_path);
|
||||
db.Load(load_path);
|
||||
db.Apply(&g_symbolDB);
|
||||
{
|
||||
Core::CPUThreadGuard guard;
|
||||
db.Apply(guard, &g_symbolDB);
|
||||
}
|
||||
db.List();
|
||||
auto& system = Core::System::GetInstance();
|
||||
HLE::PatchFunctions(system);
|
||||
@ -1665,12 +1692,14 @@ void MenuBar::SearchInstruction()
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& memory = system.GetMemory();
|
||||
|
||||
Core::CPUThreadGuard guard;
|
||||
|
||||
bool found = false;
|
||||
for (u32 addr = Memory::MEM1_BASE_ADDR; addr < Memory::MEM1_BASE_ADDR + memory.GetRamSizeReal();
|
||||
addr += 4)
|
||||
{
|
||||
const auto ins_name =
|
||||
QString::fromStdString(PPCTables::GetInstructionName(PowerPC::HostRead_U32(addr)));
|
||||
QString::fromStdString(PPCTables::GetInstructionName(PowerPC::HostRead_U32(guard, addr)));
|
||||
if (op == ins_name)
|
||||
{
|
||||
NOTICE_LOG_FMT(POWERPC, "Found {} at {:08x}", op.toStdString(), addr);
|
||||
|
Reference in New Issue
Block a user