|
|
|
@ -5,13 +5,146 @@ |
|
|
|
#pragma once |
|
|
|
|
|
|
|
#include <array> |
|
|
|
#include <condition_variable> |
|
|
|
#include <list> |
|
|
|
#include <map> |
|
|
|
#include <memory> |
|
|
|
#include <mutex> |
|
|
|
#include <vector> |
|
|
|
|
|
|
|
#include "video_core/pica.h" |
|
|
|
|
|
|
|
namespace Pica { |
|
|
|
|
|
|
|
class DebugContext { |
|
|
|
public: |
|
|
|
enum class Event { |
|
|
|
FirstEvent = 0, |
|
|
|
|
|
|
|
CommandLoaded = FirstEvent, |
|
|
|
CommandProcessed, |
|
|
|
IncomingPrimitiveBatch, |
|
|
|
FinishedPrimitiveBatch, |
|
|
|
|
|
|
|
NumEvents |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Inherit from this class to be notified of events registered to some debug context. |
|
|
|
* Most importantly this is used for our debugger GUI. |
|
|
|
* |
|
|
|
* To implement event handling, override the OnPicaBreakPointHit and OnPicaResume methods. |
|
|
|
* @warning All BreakPointObservers need to be on the same thread to guarantee thread-safe state access |
|
|
|
* @todo Evaluate an alternative interface, in which there is only one managing observer and multiple child observers running (by design) on the same thread. |
|
|
|
*/ |
|
|
|
class BreakPointObserver { |
|
|
|
public: |
|
|
|
/// Constructs the object such that it observes events of the given DebugContext. |
|
|
|
BreakPointObserver(std::shared_ptr<DebugContext> debug_context) : context_weak(debug_context) { |
|
|
|
std::unique_lock<std::mutex> lock(debug_context->breakpoint_mutex); |
|
|
|
debug_context->breakpoint_observers.push_back(this); |
|
|
|
} |
|
|
|
|
|
|
|
virtual ~BreakPointObserver() { |
|
|
|
auto context = context_weak.lock(); |
|
|
|
if (context) { |
|
|
|
std::unique_lock<std::mutex> lock(context->breakpoint_mutex); |
|
|
|
context->breakpoint_observers.remove(this); |
|
|
|
|
|
|
|
// If we are the last observer to be destroyed, tell the debugger context that |
|
|
|
// it is free to continue. In particular, this is required for a proper Citra |
|
|
|
// shutdown, when the emulation thread is waiting at a breakpoint. |
|
|
|
if (context->breakpoint_observers.empty()) |
|
|
|
context->Resume(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Action to perform when a breakpoint was reached. |
|
|
|
* @param event Type of event which triggered the breakpoint |
|
|
|
* @param data Optional data pointer (if unused, this is a nullptr) |
|
|
|
* @note This function will perform nothing unless it is overridden in the child class. |
|
|
|
*/ |
|
|
|
virtual void OnPicaBreakPointHit(Event, void*) { |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Action to perform when emulation is resumed from a breakpoint. |
|
|
|
* @note This function will perform nothing unless it is overridden in the child class. |
|
|
|
*/ |
|
|
|
virtual void OnPicaResume() { |
|
|
|
} |
|
|
|
|
|
|
|
protected: |
|
|
|
/** |
|
|
|
* Weak context pointer. This need not be valid, so when requesting a shared_ptr via |
|
|
|
* context_weak.lock(), always compare the result against nullptr. |
|
|
|
*/ |
|
|
|
std::weak_ptr<DebugContext> context_weak; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Simple structure defining a breakpoint state |
|
|
|
*/ |
|
|
|
struct BreakPoint { |
|
|
|
bool enabled = false; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Static constructor used to create a shared_ptr of a DebugContext. |
|
|
|
*/ |
|
|
|
static std::shared_ptr<DebugContext> Construct() { |
|
|
|
return std::shared_ptr<DebugContext>(new DebugContext); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Used by the emulation core when a given event has happened. If a breakpoint has been set |
|
|
|
* for this event, OnEvent calls the event handlers of the registered breakpoint observers. |
|
|
|
* The current thread then is halted until Resume() is called from another thread (or until |
|
|
|
* emulation is stopped). |
|
|
|
* @param event Event which has happened |
|
|
|
* @param data Optional data pointer (pass nullptr if unused). Needs to remain valid until Resume() is called. |
|
|
|
*/ |
|
|
|
void OnEvent(Event event, void* data); |
|
|
|
|
|
|
|
/** |
|
|
|
* Resume from the current breakpoint. |
|
|
|
* @warning Calling this from the same thread that OnEvent was called in will cause a deadlock. Calling from any other thread is safe. |
|
|
|
*/ |
|
|
|
void Resume(); |
|
|
|
|
|
|
|
/** |
|
|
|
* Delete all set breakpoints and resume emulation. |
|
|
|
*/ |
|
|
|
void ClearBreakpoints() { |
|
|
|
breakpoints.clear(); |
|
|
|
Resume(); |
|
|
|
} |
|
|
|
|
|
|
|
// TODO: Evaluate if access to these members should be hidden behind a public interface. |
|
|
|
std::map<Event, BreakPoint> breakpoints; |
|
|
|
Event active_breakpoint; |
|
|
|
bool at_breakpoint = false; |
|
|
|
|
|
|
|
private: |
|
|
|
/** |
|
|
|
* Private default constructor to make sure people always construct this through Construct() |
|
|
|
* instead. |
|
|
|
*/ |
|
|
|
DebugContext() = default; |
|
|
|
|
|
|
|
/// Mutex protecting current breakpoint state and the observer list. |
|
|
|
std::mutex breakpoint_mutex; |
|
|
|
|
|
|
|
/// Used by OnEvent to wait for resumption. |
|
|
|
std::condition_variable resume_from_breakpoint; |
|
|
|
|
|
|
|
/// List of registered observers |
|
|
|
std::list<BreakPointObserver*> breakpoint_observers; |
|
|
|
}; |
|
|
|
|
|
|
|
extern std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this global |
|
|
|
|
|
|
|
namespace DebugUtils { |
|
|
|
|
|
|
|
// Simple utility class for dumping geometry data to an OBJ file |
|
|
|
|