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:
smurf3tte
2022-10-06 21:34:44 -07:00
committed by TryTwo
co-authored by TryTwo
parent 9ca1c0f533
commit 7842f9a715
17 changed files with 1240 additions and 36 deletions
+2
View File
@@ -967,6 +967,8 @@ endif()
include_directories(Externals/picojson)
add_subdirectory(Externals/expr)
add_subdirectory(Externals/rangeset)
add_subdirectory(Externals/FatFs)
+2
View File
@@ -0,0 +1,2 @@
add_library(expr INTERFACE)
target_include_directories(expr INTERFACE include/)
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Serge Zaitsev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+921
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -14,6 +14,8 @@ Dolphin includes or links code of the following third-party software projects:
[MIT](https://github.com/discordapp/discord-rpc/blob/master/LICENSE)
- [ENet](http://enet.bespin.org/):
[MIT](http://enet.bespin.org/License.html)
- [expr](https://github.com/zserge/expr):
[MIT](https://github.com/zserge/expr/blob/master/LICENSE)
- [FatFs](http://elm-chan.org/fsw/ff/00index_e.html):
[BSD 1-Clause](http://elm-chan.org/fsw/ff/doc/appnote.html#license)
- [GCEmu](http://sourceforge.net/projects/gcemu-project/):
+3
View File
@@ -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
+37 -19
View File
@@ -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);
}
+7 -4
View File
@@ -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
View 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
View 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;
}
+5 -3
View File
@@ -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} "
+2
View File
@@ -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;
+1
View File
@@ -12,6 +12,7 @@
<!--For now, header-only libs don't have their own vcxproj/exports, so just supply their include paths to all Dolphin code-->
<AdditionalIncludeDirectories>$(ExternalsDir)FFmpeg-bin\$(Platform)\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ExternalsDir)OpenAL\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ExternalsDir)expr\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ExternalsDir)rangeset\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ExternalsDir)Vulkan\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ExternalsDir)WIL\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>