mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 13:50:03 +00:00 
			
		
		
		
	Pica/DebugUtils: Add breakpoint functionality.
This commit is contained in:
		
							parent
							
								
									706f9c5574
								
							
						
					
					
						commit
						2c71ec7052
					
				
					 5 changed files with 204 additions and 2 deletions
				
			
		|  | @ -14,6 +14,8 @@ | |||
| #include "core/core.h" | ||||
| #include "core/settings.h" | ||||
| 
 | ||||
| #include "video_core/debug_utils/debug_utils.h" | ||||
| 
 | ||||
| #include "video_core/video_core.h" | ||||
| 
 | ||||
| #include "citra_qt/version.h" | ||||
|  | @ -65,14 +67,21 @@ void EmuThread::Stop() | |||
|     } | ||||
|     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;
 | ||||
| 
 | ||||
|     wait(500); | ||||
|     // TODO: Waiting here is just a bad workaround for retarded shutdown logic.
 | ||||
|     wait(1000); | ||||
|     if (isRunning()) | ||||
|     { | ||||
|         WARN_LOG(MASTER_LOG, "EmuThread still running, terminating..."); | ||||
|         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()) | ||||
|         { | ||||
|             WARN_LOG(MASTER_LOG, "EmuThread STILL running, something is wrong here..."); | ||||
|  |  | |||
|  | @ -36,6 +36,8 @@ GMainWindow::GMainWindow() | |||
| { | ||||
|     LogManager::Init(); | ||||
| 
 | ||||
|     Pica::g_debug_context = Pica::DebugContext::Construct(); | ||||
| 
 | ||||
|     Config config; | ||||
| 
 | ||||
|     if (!Settings::values.enable_log) | ||||
|  | @ -133,6 +135,8 @@ GMainWindow::~GMainWindow() | |||
|     // will get automatically deleted otherwise
 | ||||
|     if (render_window->parent() == nullptr) | ||||
|         delete render_window; | ||||
| 
 | ||||
|     Pica::g_debug_context.reset(); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::BootGame(std::string filename) | ||||
|  |  | |||
|  | @ -34,6 +34,9 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
|     u32 old_value = registers[id]; | ||||
|     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]); | ||||
| 
 | ||||
|     switch(id) { | ||||
|  | @ -43,6 +46,9 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
|         { | ||||
|             DebugUtils::DumpTevStageConfig(registers.GetTevStages()); | ||||
| 
 | ||||
|             if (g_debug_context) | ||||
|                 g_debug_context->OnEvent(DebugContext::Event::IncomingPrimitiveBatch, nullptr); | ||||
| 
 | ||||
|             const auto& attribute_config = registers.vertex_attributes; | ||||
|             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); | ||||
|             } | ||||
|             geometry_dumper.Dump(); | ||||
| 
 | ||||
|             if (g_debug_context) | ||||
|                 g_debug_context->OnEvent(DebugContext::Event::FinishedPrimitiveBatch, nullptr); | ||||
| 
 | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|  | @ -229,6 +239,9 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
|         default: | ||||
|             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) { | ||||
|  |  | |||
|  | @ -3,6 +3,8 @@ | |||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <condition_variable> | ||||
| #include <list> | ||||
| #include <map> | ||||
| #include <fstream> | ||||
| #include <mutex> | ||||
|  | @ -12,6 +14,7 @@ | |||
| #include <png.h> | ||||
| #endif | ||||
| 
 | ||||
| #include "common/log.h" | ||||
| #include "common/file_util.h" | ||||
| 
 | ||||
| #include "video_core/pica.h" | ||||
|  | @ -20,6 +23,46 @@ | |||
| 
 | ||||
| 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 { | ||||
| 
 | ||||
| void GeometryDumper::AddTriangle(Vertex& v0, Vertex& v1, Vertex& v2) { | ||||
|  |  | |||
|  | @ -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
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue