mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-21 13:20:27 -06:00
Debugger: Initial implementation of conditional breakpoints
Expression class to store compiled expressions and associated variable list. Co-authored-by: TryTwo <taolas@gmail.com>
This commit is contained in:
@ -438,6 +438,8 @@ add_library(core
|
||||
PowerPC/CachedInterpreter/InterpreterBlockCache.h
|
||||
PowerPC/ConditionRegister.cpp
|
||||
PowerPC/ConditionRegister.h
|
||||
PowerPC/Expression.cpp
|
||||
PowerPC/Expression.h
|
||||
PowerPC/Interpreter/ExceptionUtils.h
|
||||
PowerPC/Interpreter/Interpreter_Branch.cpp
|
||||
PowerPC/Interpreter/Interpreter_FloatingPoint.cpp
|
||||
@ -582,6 +584,7 @@ PUBLIC
|
||||
cubeb
|
||||
discio
|
||||
enet
|
||||
expr
|
||||
inputcommon
|
||||
${MBEDTLS_LIBRARIES}
|
||||
pugixml
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "Common/DebugInterface.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/PowerPC/Expression.h"
|
||||
#include "Core/PowerPC/JitInterface.h"
|
||||
#include "Core/PowerPC/MMU.h"
|
||||
|
||||
@ -35,17 +36,16 @@ bool BreakPoints::IsTempBreakPoint(u32 address) const
|
||||
});
|
||||
}
|
||||
|
||||
bool BreakPoints::IsBreakPointBreakOnHit(u32 address) const
|
||||
const TBreakPoint* BreakPoints::GetBreakpoint(u32 address) const
|
||||
{
|
||||
return std::any_of(m_breakpoints.begin(), m_breakpoints.end(), [address](const auto& bp) {
|
||||
return bp.address == address && bp.break_on_hit;
|
||||
auto bp = std::find_if(m_breakpoints.begin(), m_breakpoints.end(), [address](const auto& bp) {
|
||||
return bp.is_enabled && bp.address == address;
|
||||
});
|
||||
}
|
||||
|
||||
bool BreakPoints::IsBreakPointLogOnHit(u32 address) const
|
||||
{
|
||||
return std::any_of(m_breakpoints.begin(), m_breakpoints.end(),
|
||||
[address](const auto& bp) { return bp.address == address && bp.log_on_hit; });
|
||||
if (bp == m_breakpoints.end() || !EvaluateCondition(bp->condition))
|
||||
return nullptr;
|
||||
|
||||
return &*bp;
|
||||
}
|
||||
|
||||
BreakPoints::TBreakPointsStr BreakPoints::GetStrings() const
|
||||
@ -57,10 +57,16 @@ BreakPoints::TBreakPointsStr BreakPoints::GetStrings() const
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss.imbue(std::locale::classic());
|
||||
|
||||
ss << std::hex << bp.address << " " << (bp.is_enabled ? "n" : "")
|
||||
<< (bp.log_on_hit ? "l" : "") << (bp.break_on_hit ? "b" : "");
|
||||
bp_strings.push_back(ss.str());
|
||||
ss << fmt::format("${:08x} ", bp.address);
|
||||
if (bp.is_enabled)
|
||||
ss << "n";
|
||||
if (bp.log_on_hit)
|
||||
ss << "l";
|
||||
if (bp.break_on_hit)
|
||||
ss << "b";
|
||||
if (bp.condition)
|
||||
ss << "c " << bp.condition->GetText();
|
||||
bp_strings.emplace_back(ss.str());
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,32 +82,43 @@ void BreakPoints::AddFromStrings(const TBreakPointsStr& bp_strings)
|
||||
std::istringstream iss(bp_string);
|
||||
iss.imbue(std::locale::classic());
|
||||
|
||||
if (iss.peek() == '$')
|
||||
iss.ignore();
|
||||
|
||||
iss >> std::hex >> bp.address;
|
||||
iss >> flags;
|
||||
bp.is_enabled = flags.find('n') != flags.npos;
|
||||
bp.log_on_hit = flags.find('l') != flags.npos;
|
||||
bp.break_on_hit = flags.find('b') != flags.npos;
|
||||
if (flags.find('c') != std::string::npos)
|
||||
{
|
||||
iss >> std::ws;
|
||||
std::string condition;
|
||||
std::getline(iss, condition);
|
||||
bp.condition = Expression::TryParse(condition);
|
||||
}
|
||||
bp.is_temporary = false;
|
||||
Add(bp);
|
||||
Add(std::move(bp));
|
||||
}
|
||||
}
|
||||
|
||||
void BreakPoints::Add(const TBreakPoint& bp)
|
||||
void BreakPoints::Add(TBreakPoint bp)
|
||||
{
|
||||
if (IsAddressBreakPoint(bp.address))
|
||||
return;
|
||||
|
||||
m_breakpoints.push_back(bp);
|
||||
|
||||
JitInterface::InvalidateICache(bp.address, 4, true);
|
||||
|
||||
m_breakpoints.emplace_back(std::move(bp));
|
||||
}
|
||||
|
||||
void BreakPoints::Add(u32 address, bool temp)
|
||||
{
|
||||
BreakPoints::Add(address, temp, true, false);
|
||||
BreakPoints::Add(address, temp, true, false, std::nullopt);
|
||||
}
|
||||
|
||||
void BreakPoints::Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit)
|
||||
void BreakPoints::Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit,
|
||||
std::optional<Expression> condition)
|
||||
{
|
||||
// Only add new addresses
|
||||
if (IsAddressBreakPoint(address))
|
||||
@ -113,8 +130,9 @@ void BreakPoints::Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit
|
||||
bp.break_on_hit = break_on_hit;
|
||||
bp.log_on_hit = log_on_hit;
|
||||
bp.address = address;
|
||||
bp.condition = std::move(condition);
|
||||
|
||||
m_breakpoints.push_back(bp);
|
||||
m_breakpoints.emplace_back(std::move(bp));
|
||||
|
||||
JitInterface::InvalidateICache(address, 4, true);
|
||||
}
|
||||
|
@ -4,10 +4,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/PowerPC/Expression.h"
|
||||
|
||||
namespace Common
|
||||
{
|
||||
@ -21,6 +23,7 @@ struct TBreakPoint
|
||||
bool is_temporary = false;
|
||||
bool log_on_hit = false;
|
||||
bool break_on_hit = false;
|
||||
std::optional<Expression> condition;
|
||||
};
|
||||
|
||||
struct TMemCheck
|
||||
@ -59,13 +62,13 @@ public:
|
||||
bool IsAddressBreakPoint(u32 address) const;
|
||||
bool IsBreakPointEnable(u32 adresss) const;
|
||||
bool IsTempBreakPoint(u32 address) const;
|
||||
bool IsBreakPointBreakOnHit(u32 address) const;
|
||||
bool IsBreakPointLogOnHit(u32 address) const;
|
||||
const TBreakPoint* GetBreakpoint(u32 address) const;
|
||||
|
||||
// Add BreakPoint
|
||||
void Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit);
|
||||
void Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit,
|
||||
std::optional<Expression> condition);
|
||||
void Add(u32 address, bool temp = false);
|
||||
void Add(const TBreakPoint& bp);
|
||||
void Add(TBreakPoint bp);
|
||||
|
||||
// Modify Breakpoint
|
||||
bool ToggleBreakPoint(u32 address);
|
||||
|
130
Source/Core/Core/PowerPC/Expression.cpp
Normal file
130
Source/Core/Core/PowerPC/Expression.cpp
Normal file
@ -0,0 +1,130 @@
|
||||
// Copyright 2020 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/PowerPC/Expression.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include <expr.h>
|
||||
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
|
||||
void ExprDeleter::operator()(expr* expression) const
|
||||
{
|
||||
expr_destroy(expression, nullptr);
|
||||
}
|
||||
|
||||
void ExprVarListDeleter::operator()(expr_var_list* vars) const
|
||||
{
|
||||
// Free list elements
|
||||
expr_destroy(nullptr, vars);
|
||||
// Free list object
|
||||
delete vars;
|
||||
}
|
||||
|
||||
Expression::Expression(std::string_view text, ExprPointer ex, ExprVarListPointer vars)
|
||||
: m_text(text), m_expr(std::move(ex)), m_vars(std::move(vars))
|
||||
{
|
||||
for (auto* v = m_vars->head; v != nullptr; v = v->next)
|
||||
{
|
||||
const std::string_view name = v->name;
|
||||
VarBinding bind;
|
||||
|
||||
if (name.length() >= 2 && name.length() <= 3)
|
||||
{
|
||||
if (name[0] == 'r' || name[0] == 'f')
|
||||
{
|
||||
char* end = nullptr;
|
||||
const int index = std::strtol(name.data() + 1, &end, 10);
|
||||
if (index >= 0 && index <= 31 && end == name.data() + name.length())
|
||||
{
|
||||
bind.type = name[0] == 'r' ? VarBindingType::GPR : VarBindingType::FPR;
|
||||
bind.index = index;
|
||||
}
|
||||
}
|
||||
else if (name == "lr")
|
||||
{
|
||||
bind.type = VarBindingType::SPR;
|
||||
bind.index = SPR_LR;
|
||||
}
|
||||
else if (name == "ctr")
|
||||
{
|
||||
bind.type = VarBindingType::SPR;
|
||||
bind.index = SPR_CTR;
|
||||
}
|
||||
else if (name == "pc")
|
||||
{
|
||||
bind.type = VarBindingType::PCtr;
|
||||
}
|
||||
}
|
||||
|
||||
m_binds.emplace_back(bind);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<Expression> Expression::TryParse(std::string_view text)
|
||||
{
|
||||
ExprVarListPointer vars{new expr_var_list{}};
|
||||
ExprPointer ex{expr_create(text.data(), text.length(), vars.get(), nullptr)};
|
||||
if (!ex)
|
||||
return std::nullopt;
|
||||
|
||||
return Expression{text, std::move(ex), std::move(vars)};
|
||||
}
|
||||
|
||||
double Expression::Evaluate() const
|
||||
{
|
||||
SynchronizeBindings(SynchronizeDirection::From);
|
||||
|
||||
double result = expr_eval(m_expr.get());
|
||||
|
||||
SynchronizeBindings(SynchronizeDirection::To);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void Expression::SynchronizeBindings(SynchronizeDirection dir) const
|
||||
{
|
||||
auto bind = m_binds.begin();
|
||||
for (auto* v = m_vars->head; v != nullptr; v = v->next, ++bind)
|
||||
{
|
||||
switch (bind->type)
|
||||
{
|
||||
case VarBindingType::Zero:
|
||||
if (dir == SynchronizeDirection::From)
|
||||
v->value = 0;
|
||||
break;
|
||||
case VarBindingType::GPR:
|
||||
if (dir == SynchronizeDirection::From)
|
||||
v->value = static_cast<double>(GPR(bind->index));
|
||||
else
|
||||
GPR(bind->index) = static_cast<u32>(static_cast<s64>(v->value));
|
||||
break;
|
||||
case VarBindingType::FPR:
|
||||
if (dir == SynchronizeDirection::From)
|
||||
v->value = rPS(bind->index).PS0AsDouble();
|
||||
else
|
||||
rPS(bind->index).SetPS0(v->value);
|
||||
break;
|
||||
case VarBindingType::SPR:
|
||||
if (dir == SynchronizeDirection::From)
|
||||
v->value = static_cast<double>(rSPR(bind->index));
|
||||
else
|
||||
rSPR(bind->index) = static_cast<u32>(static_cast<s64>(v->value));
|
||||
break;
|
||||
case VarBindingType::PCtr:
|
||||
if (dir == SynchronizeDirection::From)
|
||||
v->value = static_cast<double>(PC);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string Expression::GetText() const
|
||||
{
|
||||
return m_text;
|
||||
}
|
73
Source/Core/Core/PowerPC/Expression.h
Normal file
73
Source/Core/Core/PowerPC/Expression.h
Normal file
@ -0,0 +1,73 @@
|
||||
// Copyright 2020 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
struct expr;
|
||||
struct expr_var_list;
|
||||
|
||||
struct ExprDeleter
|
||||
{
|
||||
void operator()(expr* expression) const;
|
||||
};
|
||||
|
||||
using ExprPointer = std::unique_ptr<expr, ExprDeleter>;
|
||||
|
||||
struct ExprVarListDeleter
|
||||
{
|
||||
void operator()(expr_var_list* vars) const;
|
||||
};
|
||||
|
||||
using ExprVarListPointer = std::unique_ptr<expr_var_list, ExprVarListDeleter>;
|
||||
|
||||
class Expression
|
||||
{
|
||||
public:
|
||||
static std::optional<Expression> TryParse(std::string_view text);
|
||||
|
||||
double Evaluate() const;
|
||||
|
||||
std::string GetText() const;
|
||||
|
||||
private:
|
||||
enum class SynchronizeDirection
|
||||
{
|
||||
From,
|
||||
To,
|
||||
};
|
||||
|
||||
enum class VarBindingType
|
||||
{
|
||||
Zero,
|
||||
GPR,
|
||||
FPR,
|
||||
SPR,
|
||||
PCtr,
|
||||
};
|
||||
|
||||
struct VarBinding
|
||||
{
|
||||
VarBindingType type = VarBindingType::Zero;
|
||||
int index = -1;
|
||||
};
|
||||
|
||||
Expression(std::string_view text, ExprPointer ex, ExprVarListPointer vars);
|
||||
|
||||
void SynchronizeBindings(SynchronizeDirection dir) const;
|
||||
|
||||
std::string m_text;
|
||||
ExprPointer m_expr;
|
||||
ExprVarListPointer m_vars;
|
||||
std::vector<VarBinding> m_binds;
|
||||
};
|
||||
|
||||
inline bool EvaluateCondition(const std::optional<Expression>& condition)
|
||||
{
|
||||
return !condition || condition->Evaluate() != 0.0;
|
||||
}
|
@ -610,16 +610,18 @@ void CheckExternalExceptions()
|
||||
|
||||
void CheckBreakPoints()
|
||||
{
|
||||
if (!PowerPC::breakpoints.IsBreakPointEnable(PC))
|
||||
const TBreakPoint* bp = PowerPC::breakpoints.GetBreakpoint(PC);
|
||||
|
||||
if (bp == nullptr)
|
||||
return;
|
||||
|
||||
if (PowerPC::breakpoints.IsBreakPointBreakOnHit(PC))
|
||||
if (bp->break_on_hit)
|
||||
{
|
||||
CPU::Break();
|
||||
if (GDBStub::IsActive())
|
||||
GDBStub::TakeControl();
|
||||
}
|
||||
if (PowerPC::breakpoints.IsBreakPointLogOnHit(PC))
|
||||
if (bp->log_on_hit)
|
||||
{
|
||||
NOTICE_LOG_FMT(MEMMAP,
|
||||
"BP {:08x} {}({:08x} {:08x} {:08x} {:08x} {:08x} {:08x} {:08x} {:08x} {:08x} "
|
||||
|
@ -398,6 +398,7 @@
|
||||
<ClInclude Include="Core\PowerPC\CachedInterpreter\InterpreterBlockCache.h" />
|
||||
<ClInclude Include="Core\PowerPC\ConditionRegister.h" />
|
||||
<ClInclude Include="Core\PowerPC\CPUCoreBase.h" />
|
||||
<ClInclude Include="Core\PowerPC\Expression.h" />
|
||||
<ClInclude Include="Core\PowerPC\GDBStub.h" />
|
||||
<ClInclude Include="Core\PowerPC\Gekko.h" />
|
||||
<ClInclude Include="Core\PowerPC\Interpreter\ExceptionUtils.h" />
|
||||
@ -1016,6 +1017,7 @@
|
||||
<ClCompile Include="Core\PowerPC\CachedInterpreter\CachedInterpreter.cpp" />
|
||||
<ClCompile Include="Core\PowerPC\CachedInterpreter\InterpreterBlockCache.cpp" />
|
||||
<ClCompile Include="Core\PowerPC\ConditionRegister.cpp" />
|
||||
<ClCompile Include="Core\PowerPC\Expression.cpp" />
|
||||
<ClCompile Include="Core\PowerPC\GDBStub.cpp" />
|
||||
<ClCompile Include="Core\PowerPC\Interpreter\Interpreter_Branch.cpp" />
|
||||
<ClCompile Include="Core\PowerPC\Interpreter\Interpreter_FloatingPoint.cpp" />
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/PowerPC/BreakPoints.h"
|
||||
#include "Core/PowerPC/Expression.h"
|
||||
#include "Core/PowerPC/PPCSymbolDB.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
|
||||
@ -86,7 +87,7 @@ void BreakpointWidget::CreateWidgets()
|
||||
m_table = new QTableWidget;
|
||||
m_table->setTabKeyNavigation(false);
|
||||
m_table->setContentsMargins(0, 0, 0, 0);
|
||||
m_table->setColumnCount(5);
|
||||
m_table->setColumnCount(6);
|
||||
m_table->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
m_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
@ -160,7 +161,7 @@ void BreakpointWidget::Update()
|
||||
m_table->clear();
|
||||
|
||||
m_table->setHorizontalHeaderLabels(
|
||||
{tr("Active"), tr("Type"), tr("Function"), tr("Address"), tr("Flags")});
|
||||
{tr("Active"), tr("Type"), tr("Function"), tr("Address"), tr("Flags"), tr("Condition")});
|
||||
|
||||
int i = 0;
|
||||
m_table->setRowCount(i);
|
||||
@ -203,6 +204,13 @@ void BreakpointWidget::Update()
|
||||
|
||||
m_table->setItem(i, 4, create_item(flags));
|
||||
|
||||
QString condition;
|
||||
|
||||
if (bp.condition)
|
||||
condition = QString::fromStdString(bp.condition->GetText());
|
||||
|
||||
m_table->setItem(i, 5, create_item(condition));
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
@ -387,12 +395,15 @@ void BreakpointWidget::OnContextMenu()
|
||||
|
||||
void BreakpointWidget::AddBP(u32 addr)
|
||||
{
|
||||
AddBP(addr, false, true, true);
|
||||
AddBP(addr, false, true, true, {});
|
||||
}
|
||||
|
||||
void BreakpointWidget::AddBP(u32 addr, bool temp, bool break_on_hit, bool log_on_hit)
|
||||
void BreakpointWidget::AddBP(u32 addr, bool temp, bool break_on_hit, bool log_on_hit,
|
||||
const QString& condition)
|
||||
{
|
||||
PowerPC::breakpoints.Add(addr, temp, break_on_hit, log_on_hit);
|
||||
PowerPC::breakpoints.Add(
|
||||
addr, temp, break_on_hit, log_on_hit,
|
||||
!condition.isEmpty() ? Expression::TryParse(condition.toUtf8().constData()) : std::nullopt);
|
||||
|
||||
emit BreakpointsChanged();
|
||||
Update();
|
||||
|
@ -21,7 +21,7 @@ public:
|
||||
~BreakpointWidget();
|
||||
|
||||
void AddBP(u32 addr);
|
||||
void AddBP(u32 addr, bool temp, bool break_on_hit, bool log_on_hit);
|
||||
void AddBP(u32 addr, bool temp, bool break_on_hit, bool log_on_hit, const QString& condition);
|
||||
void AddAddressMBP(u32 addr, bool on_read = true, bool on_write = true, bool do_log = true,
|
||||
bool do_break = true);
|
||||
void AddRangedMBP(u32 from, u32 to, bool do_read = true, bool do_write = true, bool do_log = true,
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <QRadioButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Core/PowerPC/Expression.h"
|
||||
#include "DolphinQt/Debugger/BreakpointWidget.h"
|
||||
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
||||
|
||||
@ -40,11 +41,14 @@ void NewBreakpointDialog::CreateWidgets()
|
||||
type_group->addButton(m_instruction_bp);
|
||||
m_instruction_box = new QGroupBox;
|
||||
m_instruction_address = new QLineEdit;
|
||||
m_instruction_condition = new QLineEdit;
|
||||
|
||||
auto* instruction_layout = new QHBoxLayout;
|
||||
auto* instruction_layout = new QGridLayout;
|
||||
m_instruction_box->setLayout(instruction_layout);
|
||||
instruction_layout->addWidget(new QLabel(tr("Address:")));
|
||||
instruction_layout->addWidget(m_instruction_address);
|
||||
instruction_layout->addWidget(new QLabel(tr("Address:")), 0, 0);
|
||||
instruction_layout->addWidget(m_instruction_address, 0, 1);
|
||||
instruction_layout->addWidget(new QLabel(tr("Condition:")), 1, 0);
|
||||
instruction_layout->addWidget(m_instruction_condition, 1, 1);
|
||||
|
||||
// Memory BP
|
||||
m_memory_bp = new QRadioButton(tr("Memory Breakpoint"));
|
||||
@ -174,7 +178,15 @@ void NewBreakpointDialog::accept()
|
||||
return;
|
||||
}
|
||||
|
||||
m_parent->AddBP(address, false, do_break, do_log);
|
||||
const QString condition = m_instruction_condition->text().trimmed();
|
||||
|
||||
if (!condition.isEmpty() && !Expression::TryParse(condition.toUtf8().constData()))
|
||||
{
|
||||
invalid_input(tr("Condition"));
|
||||
return;
|
||||
}
|
||||
|
||||
m_parent->AddBP(address, false, do_break, do_log, condition);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -34,6 +34,7 @@ private:
|
||||
QRadioButton* m_instruction_bp;
|
||||
QGroupBox* m_instruction_box;
|
||||
QLineEdit* m_instruction_address;
|
||||
QLineEdit* m_instruction_condition;
|
||||
|
||||
// Memory BPs
|
||||
QRadioButton* m_memory_bp;
|
||||
|
Reference in New Issue
Block a user