dolphin/Source/Core/Core/PowerPC/BreakPoints.h
Martino Fontana bd3cf67cbc Debugger: Rework temporary breakpoints
Before:
1. In theory there could be multiple, but in practice they were (manually) cleared before creating one
2. (Some of) the conditions to clear one were either to reach it, to create a new one (due to the point above), or to step. This created weird behavior: let's say you Step Over a `bl` (thus creating a temporary breakpoint on `pc+4`), and you reached a regular breakpoint inside the `bl`. The temporary one would still be there: if you resumed, the emulation would still stop there, as a sort of Step Out. But, if before resuming, you made a Step, then it wouldn't do that.
3. The breakpoint widget had no idea concept of them, and will treat them as regular breakpoints. Also, they'll be shown only when the widget is updated in some other way, leading to more confusion.
4. Because only one breakpoint could exist per address, the creation of a temporary breakpoint on a top of a regular one would delete it and inherit its properties (e.g. being log-only). This could happen, for instance, if you Stepped Over a `bl` specifically, and pc+4 had a regular breakpoint.

Now there can only be one temporary breakpoint, which is automatically cleared whenever emulation is paused. So, removing some manual clearing from 1., and removing the weird behavior of 2. As it is stored in a separate variable, it won't be seen at all depending on the function used (fixing 3., and removing some checks in other places), and it won't replace a regular breakpoint, instead simply having priority (fixing 4.).
2024-07-05 21:33:22 +02:00

133 lines
3.6 KiB
C++

// Copyright 2008 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstddef>
#include <optional>
#include <string>
#include <vector>
#include "Common/CommonTypes.h"
#include "Core/PowerPC/Expression.h"
namespace Core
{
class System;
}
struct TBreakPoint
{
u32 address = 0;
bool is_enabled = false;
bool log_on_hit = false;
bool break_on_hit = false;
std::optional<Expression> condition;
};
struct TMemCheck
{
u32 start_address = 0;
u32 end_address = 0;
bool is_enabled = true;
bool is_ranged = false;
bool is_break_on_read = false;
bool is_break_on_write = false;
bool log_on_hit = false;
bool break_on_hit = false;
u32 num_hits = 0;
std::optional<Expression> condition;
// returns whether to break
bool Action(Core::System& system, u64 value, u32 addr, bool write, size_t size, u32 pc);
};
// Code breakpoints.
class BreakPoints
{
public:
explicit BreakPoints(Core::System& system);
BreakPoints(const BreakPoints& other) = delete;
BreakPoints(BreakPoints&& other) = delete;
BreakPoints& operator=(const BreakPoints& other) = delete;
BreakPoints& operator=(BreakPoints&& other) = delete;
~BreakPoints();
using TBreakPoints = std::vector<TBreakPoint>;
using TBreakPointsStr = std::vector<std::string>;
const TBreakPoints& GetBreakPoints() const { return m_breakpoints; }
TBreakPointsStr GetStrings() const;
void AddFromStrings(const TBreakPointsStr& bp_strings);
bool IsAddressBreakPoint(u32 address) const;
bool IsBreakPointEnable(u32 adresss) const;
// Get the breakpoint in this address (for most purposes)
const TBreakPoint* GetBreakpoint(u32 address) const;
// Get the breakpoint in this address (ignore temporary breakpoint, e.g. for editing purposes)
const TBreakPoint* GetRegularBreakpoint(u32 address) const;
// Add BreakPoint. If one already exists on the same address, replace it.
void Add(u32 address, bool break_on_hit, bool log_on_hit, std::optional<Expression> condition);
void Add(u32 address);
void Add(TBreakPoint bp);
// Add temporary breakpoint (e.g., Step Over, Run to Here)
// It can be on the same address of a regular breakpoint (it will have priority in this case)
// It's cleared whenever the emulation is paused for any reason
// (CPUManager::SetStateLocked(State::Paused))
// TODO: Should it somehow force to resume emulation when called?
void SetTemporary(u32 address);
bool ToggleBreakPoint(u32 address);
bool ToggleEnable(u32 address);
// Remove Breakpoint. Returns whether it was removed.
bool Remove(u32 address);
void Clear();
void ClearTemporary();
private:
TBreakPoints m_breakpoints;
std::optional<TBreakPoint> m_temp_breakpoint;
Core::System& m_system;
};
// Memory breakpoints
class MemChecks
{
public:
explicit MemChecks(Core::System& system);
MemChecks(const MemChecks& other) = delete;
MemChecks(MemChecks&& other) = delete;
MemChecks& operator=(const MemChecks& other) = delete;
MemChecks& operator=(MemChecks&& other) = delete;
~MemChecks();
using TMemChecks = std::vector<TMemCheck>;
using TMemChecksStr = std::vector<std::string>;
const TMemChecks& GetMemChecks() const { return m_mem_checks; }
TMemChecksStr GetStrings() const;
void AddFromStrings(const TMemChecksStr& mc_strings);
void Add(TMemCheck memory_check);
bool ToggleEnable(u32 address);
TMemCheck* GetMemCheck(u32 address, size_t size = 1);
bool OverlapsMemcheck(u32 address, u32 length) const;
// Remove Breakpoint. Returns whether it was removed.
bool Remove(u32 address);
void Clear();
bool HasAny() const { return !m_mem_checks.empty(); }
private:
TMemChecks m_mem_checks;
Core::System& m_system;
};