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
2020-12-16 15:40:20 -08:00
committed by TryTwo
parent 9ca1c0f533
commit 7842f9a715
17 changed files with 1240 additions and 36 deletions

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

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);
}

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);

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;
}

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;
}

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} "