diff options
| author | 2014-10-25 18:02:26 +0200 | |
|---|---|---|
| committer | 2014-12-09 16:37:34 +0100 | |
| commit | 2c71ec70527abd091d69f1fdd30aaf95d815214a (patch) | |
| tree | d0e1f48b048f6d3ef801d0562bfcfe0fa11de6f1 | |
| parent | citra-qt: Polish the pica tracing widget. (diff) | |
| download | yuzu-2c71ec70527abd091d69f1fdd30aaf95d815214a.tar.gz yuzu-2c71ec70527abd091d69f1fdd30aaf95d815214a.tar.xz yuzu-2c71ec70527abd091d69f1fdd30aaf95d815214a.zip | |
Pica/DebugUtils: Add breakpoint functionality.
| -rw-r--r-- | src/citra_qt/bootmanager.cpp | 13 | ||||
| -rw-r--r-- | src/citra_qt/main.cpp | 4 | ||||
| -rw-r--r-- | src/video_core/command_processor.cpp | 13 | ||||
| -rw-r--r-- | src/video_core/debug_utils/debug_utils.cpp | 43 | ||||
| -rw-r--r-- | src/video_core/debug_utils/debug_utils.h | 133 |
5 files changed, 204 insertions, 2 deletions
diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 9a29f974b..b53206be6 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp | |||
| @@ -14,6 +14,8 @@ | |||
| 14 | #include "core/core.h" | 14 | #include "core/core.h" |
| 15 | #include "core/settings.h" | 15 | #include "core/settings.h" |
| 16 | 16 | ||
| 17 | #include "video_core/debug_utils/debug_utils.h" | ||
| 18 | |||
| 17 | #include "video_core/video_core.h" | 19 | #include "video_core/video_core.h" |
| 18 | 20 | ||
| 19 | #include "citra_qt/version.h" | 21 | #include "citra_qt/version.h" |
| @@ -65,14 +67,21 @@ void EmuThread::Stop() | |||
| 65 | } | 67 | } |
| 66 | stop_run = true; | 68 | stop_run = true; |
| 67 | 69 | ||
| 70 | // Release emu threads from any breakpoints, so that this doesn't hang forever. | ||
| 71 | Pica::g_debug_context->ClearBreakpoints(); | ||
| 72 | |||
| 68 | //core::g_state = core::SYS_DIE; | 73 | //core::g_state = core::SYS_DIE; |
| 69 | 74 | ||
| 70 | wait(500); | 75 | // TODO: Waiting here is just a bad workaround for retarded shutdown logic. |
| 76 | wait(1000); | ||
| 71 | if (isRunning()) | 77 | if (isRunning()) |
| 72 | { | 78 | { |
| 73 | WARN_LOG(MASTER_LOG, "EmuThread still running, terminating..."); | 79 | WARN_LOG(MASTER_LOG, "EmuThread still running, terminating..."); |
| 74 | quit(); | 80 | quit(); |
| 75 | wait(1000); | 81 | |
| 82 | // TODO: Waiting 50 seconds can be necessary if the logging subsystem has a lot of spam | ||
| 83 | // queued... This should be fixed. | ||
| 84 | wait(50000); | ||
| 76 | if (isRunning()) | 85 | if (isRunning()) |
| 77 | { | 86 | { |
| 78 | WARN_LOG(MASTER_LOG, "EmuThread STILL running, something is wrong here..."); | 87 | WARN_LOG(MASTER_LOG, "EmuThread STILL running, something is wrong here..."); |
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 0701decef..869826e61 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp | |||
| @@ -36,6 +36,8 @@ GMainWindow::GMainWindow() | |||
| 36 | { | 36 | { |
| 37 | LogManager::Init(); | 37 | LogManager::Init(); |
| 38 | 38 | ||
| 39 | Pica::g_debug_context = Pica::DebugContext::Construct(); | ||
| 40 | |||
| 39 | Config config; | 41 | Config config; |
| 40 | 42 | ||
| 41 | if (!Settings::values.enable_log) | 43 | if (!Settings::values.enable_log) |
| @@ -133,6 +135,8 @@ GMainWindow::~GMainWindow() | |||
| 133 | // will get automatically deleted otherwise | 135 | // will get automatically deleted otherwise |
| 134 | if (render_window->parent() == nullptr) | 136 | if (render_window->parent() == nullptr) |
| 135 | delete render_window; | 137 | delete render_window; |
| 138 | |||
| 139 | Pica::g_debug_context.reset(); | ||
| 136 | } | 140 | } |
| 137 | 141 | ||
| 138 | void GMainWindow::BootGame(std::string filename) | 142 | void GMainWindow::BootGame(std::string filename) |
diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index 8a6ba2560..298b04c51 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp | |||
| @@ -34,6 +34,9 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
| 34 | u32 old_value = registers[id]; | 34 | u32 old_value = registers[id]; |
| 35 | registers[id] = (old_value & ~mask) | (value & mask); | 35 | registers[id] = (old_value & ~mask) | (value & mask); |
| 36 | 36 | ||
| 37 | if (g_debug_context) | ||
| 38 | g_debug_context->OnEvent(DebugContext::Event::CommandLoaded, reinterpret_cast<void*>(&id)); | ||
| 39 | |||
| 37 | DebugUtils::OnPicaRegWrite(id, registers[id]); | 40 | DebugUtils::OnPicaRegWrite(id, registers[id]); |
| 38 | 41 | ||
| 39 | switch(id) { | 42 | switch(id) { |
| @@ -43,6 +46,9 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
| 43 | { | 46 | { |
| 44 | DebugUtils::DumpTevStageConfig(registers.GetTevStages()); | 47 | DebugUtils::DumpTevStageConfig(registers.GetTevStages()); |
| 45 | 48 | ||
| 49 | if (g_debug_context) | ||
| 50 | g_debug_context->OnEvent(DebugContext::Event::IncomingPrimitiveBatch, nullptr); | ||
| 51 | |||
| 46 | const auto& attribute_config = registers.vertex_attributes; | 52 | const auto& attribute_config = registers.vertex_attributes; |
| 47 | const u8* const base_address = Memory::GetPointer(attribute_config.GetBaseAddress()); | 53 | const u8* const base_address = Memory::GetPointer(attribute_config.GetBaseAddress()); |
| 48 | 54 | ||
| @@ -132,6 +138,10 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
| 132 | clipper_primitive_assembler.SubmitVertex(output, Clipper::ProcessTriangle); | 138 | clipper_primitive_assembler.SubmitVertex(output, Clipper::ProcessTriangle); |
| 133 | } | 139 | } |
| 134 | geometry_dumper.Dump(); | 140 | geometry_dumper.Dump(); |
| 141 | |||
| 142 | if (g_debug_context) | ||
| 143 | g_debug_context->OnEvent(DebugContext::Event::FinishedPrimitiveBatch, nullptr); | ||
| 144 | |||
| 135 | break; | 145 | break; |
| 136 | } | 146 | } |
| 137 | 147 | ||
| @@ -229,6 +239,9 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
| 229 | default: | 239 | default: |
| 230 | break; | 240 | break; |
| 231 | } | 241 | } |
| 242 | |||
| 243 | if (g_debug_context) | ||
| 244 | g_debug_context->OnEvent(DebugContext::Event::CommandProcessed, reinterpret_cast<void*>(&id)); | ||
| 232 | } | 245 | } |
| 233 | 246 | ||
| 234 | static std::ptrdiff_t ExecuteCommandBlock(const u32* first_command_word) { | 247 | static std::ptrdiff_t ExecuteCommandBlock(const u32* first_command_word) { |
diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index 8a5f11424..11f87d988 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp | |||
| @@ -3,6 +3,8 @@ | |||
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <algorithm> | 5 | #include <algorithm> |
| 6 | #include <condition_variable> | ||
| 7 | #include <list> | ||
| 6 | #include <map> | 8 | #include <map> |
| 7 | #include <fstream> | 9 | #include <fstream> |
| 8 | #include <mutex> | 10 | #include <mutex> |
| @@ -12,6 +14,7 @@ | |||
| 12 | #include <png.h> | 14 | #include <png.h> |
| 13 | #endif | 15 | #endif |
| 14 | 16 | ||
| 17 | #include "common/log.h" | ||
| 15 | #include "common/file_util.h" | 18 | #include "common/file_util.h" |
| 16 | 19 | ||
| 17 | #include "video_core/pica.h" | 20 | #include "video_core/pica.h" |
| @@ -20,6 +23,46 @@ | |||
| 20 | 23 | ||
| 21 | namespace Pica { | 24 | namespace Pica { |
| 22 | 25 | ||
| 26 | void DebugContext::OnEvent(Event event, void* data) { | ||
| 27 | if (!breakpoints[event].enabled) | ||
| 28 | return; | ||
| 29 | |||
| 30 | { | ||
| 31 | std::unique_lock<std::mutex> lock(breakpoint_mutex); | ||
| 32 | |||
| 33 | // TODO: Should stop the CPU thread here once we multithread emulation. | ||
| 34 | |||
| 35 | active_breakpoint = event; | ||
| 36 | at_breakpoint = true; | ||
| 37 | |||
| 38 | // Tell all observers that we hit a breakpoint | ||
| 39 | for (auto& breakpoint_observer : breakpoint_observers) { | ||
| 40 | breakpoint_observer->OnPicaBreakPointHit(event, data); | ||
| 41 | } | ||
| 42 | |||
| 43 | // Wait until another thread tells us to Resume() | ||
| 44 | resume_from_breakpoint.wait(lock, [&]{ return !at_breakpoint; }); | ||
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | void DebugContext::Resume() { | ||
| 49 | { | ||
| 50 | std::unique_lock<std::mutex> lock(breakpoint_mutex); | ||
| 51 | |||
| 52 | // Tell all observers that we are about to resume | ||
| 53 | for (auto& breakpoint_observer : breakpoint_observers) { | ||
| 54 | breakpoint_observer->OnPicaResume(); | ||
| 55 | } | ||
| 56 | |||
| 57 | // Resume the waiting thread (i.e. OnEvent()) | ||
| 58 | at_breakpoint = false; | ||
| 59 | } | ||
| 60 | |||
| 61 | resume_from_breakpoint.notify_one(); | ||
| 62 | } | ||
| 63 | |||
| 64 | std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this global | ||
| 65 | |||
| 23 | namespace DebugUtils { | 66 | namespace DebugUtils { |
| 24 | 67 | ||
| 25 | void GeometryDumper::AddTriangle(Vertex& v0, Vertex& v1, Vertex& v2) { | 68 | void GeometryDumper::AddTriangle(Vertex& v0, Vertex& v1, Vertex& v2) { |
diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h index b1558cfae..26b26e22f 100644 --- a/src/video_core/debug_utils/debug_utils.h +++ b/src/video_core/debug_utils/debug_utils.h | |||
| @@ -5,13 +5,146 @@ | |||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <array> | 7 | #include <array> |
| 8 | #include <condition_variable> | ||
| 9 | #include <list> | ||
| 10 | #include <map> | ||
| 8 | #include <memory> | 11 | #include <memory> |
| 12 | #include <mutex> | ||
| 9 | #include <vector> | 13 | #include <vector> |
| 10 | 14 | ||
| 11 | #include "video_core/pica.h" | 15 | #include "video_core/pica.h" |
| 12 | 16 | ||
| 13 | namespace Pica { | 17 | namespace Pica { |
| 14 | 18 | ||
| 19 | class DebugContext { | ||
| 20 | public: | ||
| 21 | enum class Event { | ||
| 22 | FirstEvent = 0, | ||
| 23 | |||
| 24 | CommandLoaded = FirstEvent, | ||
| 25 | CommandProcessed, | ||
| 26 | IncomingPrimitiveBatch, | ||
| 27 | FinishedPrimitiveBatch, | ||
| 28 | |||
| 29 | NumEvents | ||
| 30 | }; | ||
| 31 | |||
| 32 | /** | ||
| 33 | * Inherit from this class to be notified of events registered to some debug context. | ||
| 34 | * Most importantly this is used for our debugger GUI. | ||
| 35 | * | ||
| 36 | * To implement event handling, override the OnPicaBreakPointHit and OnPicaResume methods. | ||
| 37 | * @warning All BreakPointObservers need to be on the same thread to guarantee thread-safe state access | ||
| 38 | * @todo Evaluate an alternative interface, in which there is only one managing observer and multiple child observers running (by design) on the same thread. | ||
| 39 | */ | ||
| 40 | class BreakPointObserver { | ||
| 41 | public: | ||
| 42 | /// Constructs the object such that it observes events of the given DebugContext. | ||
| 43 | BreakPointObserver(std::shared_ptr<DebugContext> debug_context) : context_weak(debug_context) { | ||
| 44 | std::unique_lock<std::mutex> lock(debug_context->breakpoint_mutex); | ||
| 45 | debug_context->breakpoint_observers.push_back(this); | ||
| 46 | } | ||
| 47 | |||
| 48 | virtual ~BreakPointObserver() { | ||
| 49 | auto context = context_weak.lock(); | ||
| 50 | if (context) { | ||
| 51 | std::unique_lock<std::mutex> lock(context->breakpoint_mutex); | ||
| 52 | context->breakpoint_observers.remove(this); | ||
| 53 | |||
| 54 | // If we are the last observer to be destroyed, tell the debugger context that | ||
| 55 | // it is free to continue. In particular, this is required for a proper Citra | ||
| 56 | // shutdown, when the emulation thread is waiting at a breakpoint. | ||
| 57 | if (context->breakpoint_observers.empty()) | ||
| 58 | context->Resume(); | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | /** | ||
| 63 | * Action to perform when a breakpoint was reached. | ||
| 64 | * @param event Type of event which triggered the breakpoint | ||
| 65 | * @param data Optional data pointer (if unused, this is a nullptr) | ||
| 66 | * @note This function will perform nothing unless it is overridden in the child class. | ||
| 67 | */ | ||
| 68 | virtual void OnPicaBreakPointHit(Event, void*) { | ||
| 69 | } | ||
| 70 | |||
| 71 | /** | ||
| 72 | * Action to perform when emulation is resumed from a breakpoint. | ||
| 73 | * @note This function will perform nothing unless it is overridden in the child class. | ||
| 74 | */ | ||
| 75 | virtual void OnPicaResume() { | ||
| 76 | } | ||
| 77 | |||
| 78 | protected: | ||
| 79 | /** | ||
| 80 | * Weak context pointer. This need not be valid, so when requesting a shared_ptr via | ||
| 81 | * context_weak.lock(), always compare the result against nullptr. | ||
| 82 | */ | ||
| 83 | std::weak_ptr<DebugContext> context_weak; | ||
| 84 | }; | ||
| 85 | |||
| 86 | /** | ||
| 87 | * Simple structure defining a breakpoint state | ||
| 88 | */ | ||
| 89 | struct BreakPoint { | ||
| 90 | bool enabled = false; | ||
| 91 | }; | ||
| 92 | |||
| 93 | /** | ||
| 94 | * Static constructor used to create a shared_ptr of a DebugContext. | ||
| 95 | */ | ||
| 96 | static std::shared_ptr<DebugContext> Construct() { | ||
| 97 | return std::shared_ptr<DebugContext>(new DebugContext); | ||
| 98 | } | ||
| 99 | |||
| 100 | /** | ||
| 101 | * Used by the emulation core when a given event has happened. If a breakpoint has been set | ||
| 102 | * for this event, OnEvent calls the event handlers of the registered breakpoint observers. | ||
| 103 | * The current thread then is halted until Resume() is called from another thread (or until | ||
| 104 | * emulation is stopped). | ||
| 105 | * @param event Event which has happened | ||
| 106 | * @param data Optional data pointer (pass nullptr if unused). Needs to remain valid until Resume() is called. | ||
| 107 | */ | ||
| 108 | void OnEvent(Event event, void* data); | ||
| 109 | |||
| 110 | /** | ||
| 111 | * Resume from the current breakpoint. | ||
| 112 | * @warning Calling this from the same thread that OnEvent was called in will cause a deadlock. Calling from any other thread is safe. | ||
| 113 | */ | ||
| 114 | void Resume(); | ||
| 115 | |||
| 116 | /** | ||
| 117 | * Delete all set breakpoints and resume emulation. | ||
| 118 | */ | ||
| 119 | void ClearBreakpoints() { | ||
| 120 | breakpoints.clear(); | ||
| 121 | Resume(); | ||
| 122 | } | ||
| 123 | |||
| 124 | // TODO: Evaluate if access to these members should be hidden behind a public interface. | ||
| 125 | std::map<Event, BreakPoint> breakpoints; | ||
| 126 | Event active_breakpoint; | ||
| 127 | bool at_breakpoint = false; | ||
| 128 | |||
| 129 | private: | ||
| 130 | /** | ||
| 131 | * Private default constructor to make sure people always construct this through Construct() | ||
| 132 | * instead. | ||
| 133 | */ | ||
| 134 | DebugContext() = default; | ||
| 135 | |||
| 136 | /// Mutex protecting current breakpoint state and the observer list. | ||
| 137 | std::mutex breakpoint_mutex; | ||
| 138 | |||
| 139 | /// Used by OnEvent to wait for resumption. | ||
| 140 | std::condition_variable resume_from_breakpoint; | ||
| 141 | |||
| 142 | /// List of registered observers | ||
| 143 | std::list<BreakPointObserver*> breakpoint_observers; | ||
| 144 | }; | ||
| 145 | |||
| 146 | extern std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this global | ||
| 147 | |||
| 15 | namespace DebugUtils { | 148 | namespace DebugUtils { |
| 16 | 149 | ||
| 17 | // Simple utility class for dumping geometry data to an OBJ file | 150 | // Simple utility class for dumping geometry data to an OBJ file |