diff --git a/stratosphere/dmnt/source/dmnt_cheat_debug_events_manager.cpp b/stratosphere/dmnt/source/dmnt_cheat_debug_events_manager.cpp new file mode 100644 index 000000000..d01f23de9 --- /dev/null +++ b/stratosphere/dmnt/source/dmnt_cheat_debug_events_manager.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include "dmnt_config.hpp" +#include "dmnt_cheat_debug_events_manager.hpp" + + +/* WORKAROUND: This design prevents a kernel deadlock from occurring on 6.0.0+ */ + +static HosThread g_per_core_threads[DmntCheatDebugEventsManager::NumCores]; +static HosMessageQueue *g_per_core_queues[DmntCheatDebugEventsManager::NumCores]; +static HosSignal g_continued_signal; + +void DmntCheatDebugEventsManager::PerCoreThreadFunc(void *arg) { + /* This thread will simply wait on the appropriate message queue. */ + size_t current_core = reinterpret_cast(arg); + while (true) { + Handle debug_handle = 0; + /* Get the debug handle. */ + { + uintptr_t x = 0; + g_per_core_queues[current_core]->Receive(&x); + debug_handle = static_cast(x); + } + + /* Continue the process, if needed. */ + if (kernelAbove300()) { + svcContinueDebugEvent(debug_handle, 5, nullptr, 0); + } else { + svcLegacyContinueDebugEvent(debug_handle, 5, 0); + } + + g_continued_signal.Signal(); + } +} + +void DmntCheatDebugEventsManager::ContinueCheatProcess(Handle cheat_dbg_hnd) { + /* Loop getting debug events. */ + DebugEventInfo dbg_event; + while (R_SUCCEEDED(svcGetDebugEvent((u8 *)&dbg_event, cheat_dbg_hnd))) { + /* ... */ + } + + size_t target_core = DmntCheatDebugEventsManager::NumCores - 1; + /* Retrieve correct core for new thread event. */ + if (dbg_event.type == DebugEventType::AttachThread) { + u64 out64; + u32 out32; + Result rc = svcGetDebugThreadParam(&out64, &out32, cheat_dbg_hnd, dbg_event.info.attach_thread.thread_id, DebugThreadParam_CurrentCore); + if (R_FAILED(rc)) { + fatalSimple(rc); + } + + + target_core = out32; + } + + /* Make appropriate thread continue. */ + g_per_core_queues[target_core]->Send(static_cast(cheat_dbg_hnd)); + + /* Wait. */ + g_continued_signal.Wait(); + g_continued_signal.Reset(); +} + +void DmntCheatDebugEventsManager::Initialize() { + /* Spawn per core resources. */ + for (size_t i = 0; i < DmntCheatDebugEventsManager::NumCores; i++) { + /* Create queue. */ + g_per_core_queues[i] = new HosMessageQueue(1); + + /* Create thread. */ + if (R_FAILED(g_per_core_threads[i].Initialize(&DmntCheatDebugEventsManager::PerCoreThreadFunc, reinterpret_cast(i), 0x1000, 24, i))) { + std::abort(); + } + + /* Set core mask. */ + if (R_FAILED(svcSetThreadCoreMask(g_per_core_threads[i].GetHandle(), i, (1u << i)))) { + std::abort(); + } + + /* Start thread. */ + if (R_FAILED(g_per_core_threads[i].Start())) { + std::abort(); + } + } +} \ No newline at end of file diff --git a/stratosphere/dmnt/source/dmnt_cheat_debug_events_manager.hpp b/stratosphere/dmnt/source/dmnt_cheat_debug_events_manager.hpp new file mode 100644 index 000000000..599ac2197 --- /dev/null +++ b/stratosphere/dmnt/source/dmnt_cheat_debug_events_manager.hpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +#include "dmnt_cheat_types.hpp" + +struct StackFrame { + u64 fp; + u64 lr; +}; + +struct AttachProcessInfo { + u64 title_id; + u64 process_id; + char name[0xC]; + u32 flags; + u64 user_exception_context_address; /* 5.0.0+ */ +}; + +struct AttachThreadInfo { + u64 thread_id; + u64 tls_address; + u64 entrypoint; +}; + +/* TODO: ExitProcessInfo */ +/* TODO: ExitThreadInfo */ + +enum class DebugExceptionType : u32 { + UndefinedInstruction = 0, + InstructionAbort = 1, + DataAbort = 2, + AlignmentFault = 3, + DebuggerAttached = 4, + BreakPoint = 5, + UserBreak = 6, + DebuggerBreak = 7, + BadSvc = 8, + UnknownNine = 9, +}; + +struct UndefinedInstructionInfo { + u32 insn; +}; + +struct DataAbortInfo { + u64 address; +}; + +struct AlignmentFaultInfo { + u64 address; +}; + +struct UserBreakInfo { + u64 break_reason; + u64 address; + u64 size; +}; + +struct BadSvcInfo { + u32 id; +}; + +union SpecificExceptionInfo { + UndefinedInstructionInfo undefined_instruction; + DataAbortInfo data_abort; + AlignmentFaultInfo alignment_fault; + UserBreakInfo user_break; + BadSvcInfo bad_svc; + u64 raw; +}; + +struct ExceptionInfo { + DebugExceptionType type; + u64 address; + SpecificExceptionInfo specific; +}; + + +enum class DebugEventType : u32 { + AttachProcess = 0, + AttachThread = 1, + ExitProcess = 2, + ExitThread = 3, + Exception = 4 +}; + +union DebugInfo { + AttachProcessInfo attach_process; + AttachThreadInfo attach_thread; + ExceptionInfo exception; +}; + +struct DebugEventInfo { + DebugEventType type; + u32 flags; + u64 thread_id; + union { + DebugInfo info; + u64 _[0x40/sizeof(u64)]; + }; +}; + +static_assert(sizeof(DebugEventInfo) >= 0x50, "Incorrect DebugEventInfo definition!"); + +class DmntCheatDebugEventsManager { + public: + static constexpr size_t NumCores = 4; + private: + static void PerCoreThreadFunc(void *arg); + public: + static void ContinueCheatProcess(Handle cheat_dbg_hnd); + + static void Initialize(); +}; diff --git a/stratosphere/dmnt/source/dmnt_cheat_manager.cpp b/stratosphere/dmnt/source/dmnt_cheat_manager.cpp index e84a72afd..ba44fdfa0 100644 --- a/stratosphere/dmnt/source/dmnt_cheat_manager.cpp +++ b/stratosphere/dmnt/source/dmnt_cheat_manager.cpp @@ -19,6 +19,7 @@ #include "dmnt_cheat_manager.hpp" #include "dmnt_cheat_vm.hpp" #include "dmnt_config.hpp" +#include "dmnt_cheat_debug_events_manager.hpp" #include "pm_shim.h" static HosMutex g_cheat_lock; @@ -46,10 +47,6 @@ static CheatEntry g_cheat_entries[DmntCheatManager::MaxCheatCount]; /* Global frozen address storage. */ static std::map g_frozen_addresses_map; -/* WORKAROUND: These prevent a kernel deadlock from occurring on 6.0.0+ */ -static HosThread g_workaround_threads[4]; -static void KernelWorkaroundThreadFunc(void *arg) { svcSleepThread(INT64_MAX); } - void DmntCheatManager::StartDebugEventsThread() { std::scoped_lock lk(g_debug_event_thread_lock); @@ -122,21 +119,6 @@ bool DmntCheatManager::HasActiveCheatProcess() { return has_cheat_process; } -void DmntCheatManager::ContinueCheatProcess() { - /* Loop getting debug events. */ - u8 debug_event_buf[0x50]; - while (R_SUCCEEDED(svcGetDebugEvent((u8 *)debug_event_buf, g_cheat_process_debug_hnd))) { - /* ... */ - } - - /* Continue the process, if needed. */ - if (kernelAbove300()) { - svcContinueDebugEvent(g_cheat_process_debug_hnd, 5, nullptr, 0); - } else { - svcLegacyContinueDebugEvent(g_cheat_process_debug_hnd, 5, 0); - } -} - Result DmntCheatManager::ReadCheatProcessMemoryForVm(u64 proc_addr, void *out_data, size_t size) { if (HasActiveCheatProcess()) { return svcReadDebugProcessMemory(out_data, g_cheat_process_debug_hnd, proc_addr, size); @@ -933,7 +915,7 @@ void DmntCheatManager::DebugEventsThread(void *arg) { /* Handle any pending debug events. */ if (HasActiveCheatProcess()) { - ContinueCheatProcess(); + DmntCheatDebugEventsManager::ContinueCheatProcess(g_cheat_process_debug_hnd); } } @@ -975,19 +957,8 @@ void DmntCheatManager::InitializeCheatManager() { } } - /* WORKAROUND: On 6.0.0+, we must ensure that every core has at least one non-game thread. */ - /* This prevents a kernel deadlock when continuing debug events. */ - if (kernelAbove600()) { - for (size_t i = 0; i < sizeof(g_workaround_threads) / sizeof(g_workaround_threads[0]); i++) { - if (R_FAILED(g_workaround_threads[i].Initialize(&KernelWorkaroundThreadFunc, nullptr, 0x1000, 0, i))) { - std::abort(); - } - - if (R_FAILED(g_workaround_threads[i].Start())) { - std::abort(); - } - } - } + /* Initialize debug events manager. */ + DmntCheatDebugEventsManager::Initialize(); /* Spawn application detection thread, spawn cheat vm thread. */ if (R_FAILED(g_detect_thread.Initialize(&DmntCheatManager::DetectThread, nullptr, 0x4000, 39))) {