From b186b049955006925cc3bf16c078ebfa4ab47e51 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Sat, 23 May 2026 17:25:14 +0200 Subject: [PATCH] core: Refactor thread unschedule and add debug frontend unschedule (#2145) --- src/citra_qt/citra_qt.cpp | 6 ++++++ src/citra_qt/configuration/config.cpp | 10 ++++++---- src/citra_qt/configuration/config.h | 2 +- src/citra_qt/main.ui | 19 +++++++++++++++++++ src/core/core.cpp | 13 +++++++++++++ src/core/core.h | 2 ++ src/core/gdbstub/gdbstub.cpp | 10 +++++----- src/core/hle/kernel/process.cpp | 7 ++++--- src/core/hle/kernel/process.h | 16 +++++++++++++++- src/core/hle/kernel/svc.cpp | 6 +++++- src/core/hle/kernel/thread.cpp | 22 +++++++++++++++------- src/core/hle/kernel/thread.h | 16 ++++++++-------- 12 files changed, 99 insertions(+), 30 deletions(-) diff --git a/src/citra_qt/citra_qt.cpp b/src/citra_qt/citra_qt.cpp index b4335797e..832f4d409 100644 --- a/src/citra_qt/citra_qt.cpp +++ b/src/citra_qt/citra_qt.cpp @@ -879,6 +879,8 @@ void GMainWindow::InitializeHotkeys() { link_action_shortcut(ui->action_Debug_Pause, QStringLiteral("Debug Pause")); link_action_shortcut(ui->action_Debug_Resume, QStringLiteral("Debug Resume")); link_action_shortcut(ui->action_Debug_Step, QStringLiteral("Debug Step"), false, true); + link_action_shortcut(ui->action_Debug_Unschedule_All, QStringLiteral("Debug Unschedule All")); + link_action_shortcut(ui->action_Debug_Schedule_All, QStringLiteral("Debug Schedule All")); link_action_shortcut(ui->action_Screen_Layout_Swap_Screens, QStringLiteral("Swap Screens")); link_action_shortcut(ui->action_Screen_Layout_Upright_Screens, QStringLiteral("Rotate Screens Upright")); @@ -1209,6 +1211,10 @@ void GMainWindow::ConnectMenuEvents() { emu_thread->ExecStep(); } }); + connect_menu(ui->action_Debug_Unschedule_All, + [this] { system.DebugUnscheduleAllThreadsFromFrontend(true); }); + connect_menu(ui->action_Debug_Schedule_All, + [this] { system.DebugUnscheduleAllThreadsFromFrontend(false); }); // Tools connect_menu(ui->action_Compress_ROM_File, &GMainWindow::OnCompressFile); diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 81d1956af..9091d734b 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -57,15 +57,17 @@ const std::array, Settings::NativeAnalog::NumAnalogs> QtConfi // This must be in alphabetical order according to action name as it must have the same order as // UISetting::values.shortcuts, which is alphabetically ordered. // clang-format off -const std::array QtConfig::default_hotkeys {{ +const std::array QtConfig::default_hotkeys {{ {QStringLiteral("Advance Frame"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}}, {QStringLiteral("Audio Volume Down"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}}, {QStringLiteral("Audio Volume Up"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}}, {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}}, - {QStringLiteral("Debug Pause"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F4"),Qt::WidgetWithChildrenShortcut}}, - {QStringLiteral("Debug Resume"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F5"),Qt::WidgetWithChildrenShortcut}}, - {QStringLiteral("Debug Step"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F6"),Qt::WidgetWithChildrenShortcut}}, + {QStringLiteral("Debug Pause"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WidgetWithChildrenShortcut}}, + {QStringLiteral("Debug Resume"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WidgetWithChildrenShortcut}}, + {QStringLiteral("Debug Step"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WidgetWithChildrenShortcut}}, + {QStringLiteral("Debug Unschedule All"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WidgetWithChildrenShortcut}}, + {QStringLiteral("Debug Schedule All"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WidgetWithChildrenShortcut}}, {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}}, {QStringLiteral("Decrease 3D Factor"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+-"), Qt::ApplicationShortcut}}, {QStringLiteral("Decrease Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("-"), Qt::ApplicationShortcut}}, diff --git a/src/citra_qt/configuration/config.h b/src/citra_qt/configuration/config.h index 6cc87f7f0..127edce78 100644 --- a/src/citra_qt/configuration/config.h +++ b/src/citra_qt/configuration/config.h @@ -26,7 +26,7 @@ public: static const std::array default_buttons; static const std::array, Settings::NativeAnalog::NumAnalogs> default_analogs; - static const std::array default_hotkeys; + static const std::array default_hotkeys; private: void Initialize(const std::string& config_name); diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui index 1e5dbb4e5..7fc2fec65 100644 --- a/src/citra_qt/main.ui +++ b/src/citra_qt/main.ui @@ -213,6 +213,9 @@ + + + @@ -487,6 +490,22 @@ Debug Step + + + true + + + Debug Unschedule All + + + + + true + + + Debug Schedule All + + true diff --git a/src/core/core.cpp b/src/core/core.cpp index f9dfe1534..9b2144a50 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -833,6 +833,19 @@ bool System::IsInitialSetup() { return app_loader && app_loader->DoingInitialSetup(); } +void System::DebugUnscheduleAllThreadsFromFrontend(bool unschedule) { + if (!is_powered_on) + return; + + for (auto proc : kernel->GetProcessList()) { + if (unschedule) { + proc->SetUnscheduleMode(Kernel::UnscheduleMode::FRONTEND); + } else { + proc->ClearUnscheduleMode(Kernel::UnscheduleMode::FRONTEND); + } + } +} + template void System::serialize(Archive& ar, const unsigned int file_version) { diff --git a/src/core/core.h b/src/core/core.h index c46a196b2..eed41f8bb 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -419,6 +419,8 @@ public: debug_next_process = false; } + void DebugUnscheduleAllThreadsFromFrontend(bool unschedule); + private: /** * Initialize the emulated system. diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp index 017677606..d31574098 100644 --- a/src/core/gdbstub/gdbstub.cpp +++ b/src/core/gdbstub/gdbstub.cpp @@ -877,7 +877,7 @@ static void HandleGetStopReason() { for (const auto& process : process_list) { if (process->codeset->program_id == program_id) { current_process = process.get(); - current_process->SetDebugBreak(true); + current_process->SetUnscheduleMode(Kernel::UnscheduleMode::GDB); is_running = false; if (SetThread(0)) { SendStopReply(current_thread, 0); @@ -900,7 +900,7 @@ static void BreakImpl(int signal) { "and memory exceptions. Disable CPU JIT for more accuracy."); } - current_process->SetDebugBreak(true); + current_process->SetUnscheduleMode(Kernel::UnscheduleMode::GDB); is_running = false; latest_signal = signal; @@ -1248,7 +1248,7 @@ static void Continue() { continue_list.push_back(thread_id); } - current_process->SetDebugBreak(false, continue_list); + current_process->ClearUnscheduleMode(Kernel::UnscheduleMode::GDB, continue_list); is_running = true; ClearAllInstructionCache(); @@ -1414,7 +1414,7 @@ void HandleVCommand() { SendReply("E02"); } else { current_process = process.get(); - current_process->SetDebugBreak(true); + current_process->SetUnscheduleMode(Kernel::UnscheduleMode::GDB); is_running = false; if (SetThread(0)) { SendStopReply(current_thread, 0); @@ -1456,7 +1456,7 @@ void HandleVCommand() { HexToInt(reinterpret_cast(threads[i].c_str()), threads[i].size())); } - current_process->SetDebugBreak(false, thread_ids); + current_process->ClearUnscheduleMode(Kernel::UnscheduleMode::GDB, thread_ids); is_running = true; } } else { diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index b895a1f13..5835ec28d 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -270,7 +270,7 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) { #ifdef ENABLE_GDBSTUB if (GDBStub::IsServerEnabled()) { LOG_INFO(Loader, "Pausing process {} at start", process_id); - SetDebugBreak(true); + SetUnscheduleMode(Kernel::UnscheduleMode::GDB); } #endif Core::System::GetInstance().ClearDebugNextProcessFlag(); @@ -624,7 +624,8 @@ std::vector> Kernel::Process::GetThreadList() { return ret; } -void Kernel::Process::SetDebugBreak(bool debug_break, std::vector thread_ids) { +void Kernel::Process::ChangeUnscheduleMode(UnscheduleMode mode, std::vector thread_ids, + bool set) { auto thread_list = GetThreadList(); bool needs_reschedule = false; for (auto& t : thread_list) { @@ -636,7 +637,7 @@ void Kernel::Process::SetDebugBreak(bool debug_break, std::vector thread_id } } - needs_reschedule |= t->SetDebugBreak(debug_break); + needs_reschedule |= (set ? t->SetUnscheduleMode(mode) : t->ClearUnscheduleMode(mode)); } if (needs_reschedule) { diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index 4067839cd..2f94ac844 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -13,6 +13,7 @@ #include #include #include "common/bit_field.h" +#include "common/common_funcs.h" #include "common/common_types.h" #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/object.h" @@ -51,6 +52,13 @@ union ProcessFlags { BitField<12, 1, u16> loaded_high; ///< Application loaded high (not at 0x00100000). }; +enum class UnscheduleMode : u32 { + SVC = (1 << 0), + GDB = (1 << 1), + FRONTEND = (1 << 2), +}; +DECLARE_ENUM_FLAG_OPERATORS(UnscheduleMode); + enum class ProcessStatus { Created, Running, Exited }; class ResourceLimit; @@ -228,9 +236,15 @@ public: std::vector> GetThreadList(); - void SetDebugBreak(bool debug_break, std::vector thread_ids = {}); + void SetUnscheduleMode(UnscheduleMode mode, std::vector thread_ids = {}) { + ChangeUnscheduleMode(mode, thread_ids, true); + } + void ClearUnscheduleMode(UnscheduleMode mode, std::vector thread_ids = {}) { + ChangeUnscheduleMode(mode, thread_ids, false); + } private: + void ChangeUnscheduleMode(UnscheduleMode mode, std::vector thread_ids, bool set); void FreeAllMemory(); KernelSystem& kernel; diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 715aedd6a..3668982cc 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -2191,7 +2191,11 @@ Result SVC::ControlProcess(Handle process_handle, u32 process_OP, u32 varg2, u32 kernel.GetCurrentThreadManager().GetCurrentThread()->thread_id) { continue; } - thread.get()->can_schedule = !varg2; + if (varg2) { + thread->SetUnscheduleMode(Kernel::UnscheduleMode::SVC); + } else { + thread->ClearUnscheduleMode(Kernel::UnscheduleMode::SVC); + } } } return ResultSuccess; diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 5f982c9e6..d5cd69781 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -80,7 +80,7 @@ void Thread::serialize(Archive& ar, const unsigned int file_version) { } } ar & wakeup_callback; - ar & debug_break; + ar & unschedule_mode; } SERIALIZE_IMPL(Thread) @@ -545,12 +545,20 @@ VAddr Thread::GetCommandBufferAddress() const { return GetTLSAddress() + command_header_offset; } -bool Thread::SetDebugBreak(bool _debug_break) { - if (debug_break == _debug_break) { - return false; - } - debug_break = _debug_break; - return true; +bool Thread::SetUnscheduleMode(UnscheduleMode mode) { + UnscheduleMode old = unschedule_mode; + + unschedule_mode |= mode; + + return unschedule_mode != old; +} + +bool Thread::ClearUnscheduleMode(UnscheduleMode mode) { + UnscheduleMode old = unschedule_mode; + + unschedule_mode &= ~mode; + + return unschedule_mode != old; } CpuLimiter::~CpuLimiter() {} diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 49891b31a..7f3e37ca3 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -21,6 +21,7 @@ #include "core/arm/arm_interface.h" #include "core/core_timing.h" #include "core/hle/kernel/object.h" +#include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" #include "core/hle/kernel/wait_object.h" #include "core/hle/result.h" @@ -28,7 +29,6 @@ namespace Kernel { class Mutex; -class Process; enum ThreadPriority : u32 { ThreadPrioHighest = 0, ///< Highest thread priority @@ -366,19 +366,16 @@ public: } bool CanSchedule() { - // TODO(PabloMK7): This may not be the proper way - // threads are marked as non-schedulable when they - // are in debug break. Figure out and fix. - return can_schedule && !debug_break; + return static_cast(unschedule_mode) == 0; } - bool SetDebugBreak(bool debug_break); + bool SetUnscheduleMode(UnscheduleMode mode); + bool ClearUnscheduleMode(UnscheduleMode mode); Core::ARM_Interface::ThreadContext context{}; u32 thread_id; - bool can_schedule{true}; ThreadStatus status; VAddr entry_point; VAddr stack_top; @@ -419,7 +416,10 @@ public: private: ThreadManager& thread_manager; - bool debug_break{}; + + // Does not represent how real HW works, instead it mimics behaviour + // taking into account how our scheduler works. + UnscheduleMode unschedule_mode{}; friend class boost::serialization::access; template