1
0
Fork 0

Pica/DebugUtils: Add breakpoint functionality.

This commit is contained in:
Tony Wasserka 2014-10-25 18:02:26 +02:00
parent 706f9c5574
commit 2c71ec7052
5 changed files with 204 additions and 2 deletions

View File

@ -14,6 +14,8 @@
#include "core/core.h" #include "core/core.h"
#include "core/settings.h" #include "core/settings.h"
#include "video_core/debug_utils/debug_utils.h"
#include "video_core/video_core.h" #include "video_core/video_core.h"
#include "citra_qt/version.h" #include "citra_qt/version.h"
@ -65,14 +67,21 @@ void EmuThread::Stop()
} }
stop_run = true; stop_run = true;
// Release emu threads from any breakpoints, so that this doesn't hang forever.
Pica::g_debug_context->ClearBreakpoints();
//core::g_state = core::SYS_DIE; //core::g_state = core::SYS_DIE;
wait(500); // TODO: Waiting here is just a bad workaround for retarded shutdown logic.
wait(1000);
if (isRunning()) if (isRunning())
{ {
WARN_LOG(MASTER_LOG, "EmuThread still running, terminating..."); WARN_LOG(MASTER_LOG, "EmuThread still running, terminating...");
quit(); quit();
wait(1000);
// TODO: Waiting 50 seconds can be necessary if the logging subsystem has a lot of spam
// queued... This should be fixed.
wait(50000);
if (isRunning()) if (isRunning())
{ {
WARN_LOG(MASTER_LOG, "EmuThread STILL running, something is wrong here..."); WARN_LOG(MASTER_LOG, "EmuThread STILL running, something is wrong here...");

View File

@ -36,6 +36,8 @@ GMainWindow::GMainWindow()
{ {
LogManager::Init(); LogManager::Init();
Pica::g_debug_context = Pica::DebugContext::Construct();
Config config; Config config;
if (!Settings::values.enable_log) if (!Settings::values.enable_log)
@ -133,6 +135,8 @@ GMainWindow::~GMainWindow()
// will get automatically deleted otherwise // will get automatically deleted otherwise
if (render_window->parent() == nullptr) if (render_window->parent() == nullptr)
delete render_window; delete render_window;
Pica::g_debug_context.reset();
} }
void GMainWindow::BootGame(std::string filename) void GMainWindow::BootGame(std::string filename)

View File

@ -34,6 +34,9 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) {
u32 old_value = registers[id]; u32 old_value = registers[id];
registers[id] = (old_value & ~mask) | (value & mask); registers[id] = (old_value & ~mask) | (value & mask);
if (g_debug_context)
g_debug_context->OnEvent(DebugContext::Event::CommandLoaded, reinterpret_cast<void*>(&id));
DebugUtils::OnPicaRegWrite(id, registers[id]); DebugUtils::OnPicaRegWrite(id, registers[id]);
switch(id) { switch(id) {
@ -43,6 +46,9 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) {
{ {
DebugUtils::DumpTevStageConfig(registers.GetTevStages()); DebugUtils::DumpTevStageConfig(registers.GetTevStages());
if (g_debug_context)
g_debug_context->OnEvent(DebugContext::Event::IncomingPrimitiveBatch, nullptr);
const auto& attribute_config = registers.vertex_attributes; const auto& attribute_config = registers.vertex_attributes;
const u8* const base_address = Memory::GetPointer(attribute_config.GetBaseAddress()); const u8* const base_address = Memory::GetPointer(attribute_config.GetBaseAddress());
@ -132,6 +138,10 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) {
clipper_primitive_assembler.SubmitVertex(output, Clipper::ProcessTriangle); clipper_primitive_assembler.SubmitVertex(output, Clipper::ProcessTriangle);
} }
geometry_dumper.Dump(); geometry_dumper.Dump();
if (g_debug_context)
g_debug_context->OnEvent(DebugContext::Event::FinishedPrimitiveBatch, nullptr);
break; break;
} }
@ -229,6 +239,9 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) {
default: default:
break; break;
} }
if (g_debug_context)
g_debug_context->OnEvent(DebugContext::Event::CommandProcessed, reinterpret_cast<void*>(&id));
} }
static std::ptrdiff_t ExecuteCommandBlock(const u32* first_command_word) { static std::ptrdiff_t ExecuteCommandBlock(const u32* first_command_word) {

View File

@ -3,6 +3,8 @@
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <algorithm> #include <algorithm>
#include <condition_variable>
#include <list>
#include <map> #include <map>
#include <fstream> #include <fstream>
#include <mutex> #include <mutex>
@ -12,6 +14,7 @@
#include <png.h> #include <png.h>
#endif #endif
#include "common/log.h"
#include "common/file_util.h" #include "common/file_util.h"
#include "video_core/pica.h" #include "video_core/pica.h"
@ -20,6 +23,46 @@
namespace Pica { namespace Pica {
void DebugContext::OnEvent(Event event, void* data) {
if (!breakpoints[event].enabled)
return;
{
std::unique_lock<std::mutex> lock(breakpoint_mutex);
// TODO: Should stop the CPU thread here once we multithread emulation.
active_breakpoint = event;
at_breakpoint = true;
// Tell all observers that we hit a breakpoint
for (auto& breakpoint_observer : breakpoint_observers) {
breakpoint_observer->OnPicaBreakPointHit(event, data);
}
// Wait until another thread tells us to Resume()
resume_from_breakpoint.wait(lock, [&]{ return !at_breakpoint; });
}
}
void DebugContext::Resume() {
{
std::unique_lock<std::mutex> lock(breakpoint_mutex);
// Tell all observers that we are about to resume
for (auto& breakpoint_observer : breakpoint_observers) {
breakpoint_observer->OnPicaResume();
}
// Resume the waiting thread (i.e. OnEvent())
at_breakpoint = false;
}
resume_from_breakpoint.notify_one();
}
std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this global
namespace DebugUtils { namespace DebugUtils {
void GeometryDumper::AddTriangle(Vertex& v0, Vertex& v1, Vertex& v2) { void GeometryDumper::AddTriangle(Vertex& v0, Vertex& v1, Vertex& v2) {

View File

@ -5,13 +5,146 @@
#pragma once #pragma once
#include <array> #include <array>
#include <condition_variable>
#include <list>
#include <map>
#include <memory> #include <memory>
#include <mutex>
#include <vector> #include <vector>
#include "video_core/pica.h" #include "video_core/pica.h"
namespace Pica { 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 { namespace DebugUtils {
// Simple utility class for dumping geometry data to an OBJ file // Simple utility class for dumping geometry data to an OBJ file