mirror of
				https://git.suyu.dev/suyu/suyu.git
				synced 2025-11-04 12:34:39 +08:00 
			
		
		
		
	Pica/DebugUtils: Add breakpoint functionality.
This commit is contained in:
		
							parent
							
								
									706f9c5574
								
							
						
					
					
						commit
						2c71ec7052
					
				@ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user