debug_utils.h 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. // Copyright 2014 Citra Emulator Project
  2. // Licensed under GPLv2
  3. // Refer to the license.txt file included.
  4. #pragma once
  5. #include <array>
  6. #include <condition_variable>
  7. #include <list>
  8. #include <memory>
  9. #include <mutex>
  10. namespace Tegra {
  11. class DebugContext {
  12. public:
  13. enum class Event {
  14. FirstEvent = 0,
  15. MaxwellCommandLoaded = FirstEvent,
  16. MaxwellCommandProcessed,
  17. IncomingPrimitiveBatch,
  18. FinishedPrimitiveBatch,
  19. NumEvents
  20. };
  21. /**
  22. * Inherit from this class to be notified of events registered to some debug context.
  23. * Most importantly this is used for our debugger GUI.
  24. *
  25. * To implement event handling, override the OnMaxwellBreakPointHit and OnMaxwellResume methods.
  26. * @warning All BreakPointObservers need to be on the same thread to guarantee thread-safe state
  27. * access
  28. * @todo Evaluate an alternative interface, in which there is only one managing observer and
  29. * multiple child observers running (by design) on the same thread.
  30. */
  31. class BreakPointObserver {
  32. public:
  33. /// Constructs the object such that it observes events of the given DebugContext.
  34. explicit BreakPointObserver(std::shared_ptr<DebugContext> debug_context)
  35. : context_weak(debug_context) {
  36. std::unique_lock<std::mutex> lock(debug_context->breakpoint_mutex);
  37. debug_context->breakpoint_observers.push_back(this);
  38. }
  39. virtual ~BreakPointObserver() {
  40. auto context = context_weak.lock();
  41. if (context) {
  42. {
  43. std::unique_lock<std::mutex> lock(context->breakpoint_mutex);
  44. context->breakpoint_observers.remove(this);
  45. }
  46. // If we are the last observer to be destroyed, tell the debugger context that
  47. // it is free to continue. In particular, this is required for a proper yuzu
  48. // shutdown, when the emulation thread is waiting at a breakpoint.
  49. if (context->breakpoint_observers.empty())
  50. context->Resume();
  51. }
  52. }
  53. /**
  54. * Action to perform when a breakpoint was reached.
  55. * @param event Type of event which triggered the breakpoint
  56. * @param data Optional data pointer (if unused, this is a nullptr)
  57. * @note This function will perform nothing unless it is overridden in the child class.
  58. */
  59. virtual void OnMaxwellBreakPointHit(Event event, void* data) {}
  60. /**
  61. * Action to perform when emulation is resumed from a breakpoint.
  62. * @note This function will perform nothing unless it is overridden in the child class.
  63. */
  64. virtual void OnMaxwellResume() {}
  65. protected:
  66. /**
  67. * Weak context pointer. This need not be valid, so when requesting a shared_ptr via
  68. * context_weak.lock(), always compare the result against nullptr.
  69. */
  70. std::weak_ptr<DebugContext> context_weak;
  71. };
  72. /**
  73. * Simple structure defining a breakpoint state
  74. */
  75. struct BreakPoint {
  76. bool enabled = false;
  77. };
  78. /**
  79. * Static constructor used to create a shared_ptr of a DebugContext.
  80. */
  81. static std::shared_ptr<DebugContext> Construct() {
  82. return std::shared_ptr<DebugContext>(new DebugContext);
  83. }
  84. /**
  85. * Used by the emulation core when a given event has happened. If a breakpoint has been set
  86. * for this event, OnEvent calls the event handlers of the registered breakpoint observers.
  87. * The current thread then is halted until Resume() is called from another thread (or until
  88. * emulation is stopped).
  89. * @param event Event which has happened
  90. * @param data Optional data pointer (pass nullptr if unused). Needs to remain valid until
  91. * Resume() is called.
  92. */
  93. void OnEvent(Event event, void* data) {
  94. // This check is left in the header to allow the compiler to inline it.
  95. if (!breakpoints[(int)event].enabled)
  96. return;
  97. // For the rest of event handling, call a separate function.
  98. DoOnEvent(event, data);
  99. }
  100. void DoOnEvent(Event event, void* data);
  101. /**
  102. * Resume from the current breakpoint.
  103. * @warning Calling this from the same thread that OnEvent was called in will cause a deadlock.
  104. * Calling from any other thread is safe.
  105. */
  106. void Resume();
  107. /**
  108. * Delete all set breakpoints and resume emulation.
  109. */
  110. void ClearBreakpoints() {
  111. for (auto& bp : breakpoints) {
  112. bp.enabled = false;
  113. }
  114. Resume();
  115. }
  116. // TODO: Evaluate if access to these members should be hidden behind a public interface.
  117. std::array<BreakPoint, static_cast<int>(Event::NumEvents)> breakpoints;
  118. Event active_breakpoint{};
  119. bool at_breakpoint = false;
  120. private:
  121. /**
  122. * Private default constructor to make sure people always construct this through Construct()
  123. * instead.
  124. */
  125. DebugContext() = default;
  126. /// Mutex protecting current breakpoint state and the observer list.
  127. std::mutex breakpoint_mutex;
  128. /// Used by OnEvent to wait for resumption.
  129. std::condition_variable resume_from_breakpoint;
  130. /// List of registered observers
  131. std::list<BreakPointObserver*> breakpoint_observers;
  132. };
  133. } // namespace Tegra