From 4405116324fda4895ccdc1ec6bc32ee253b16261 Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Sun, 24 Dec 2023 00:25:01 +0100 Subject: [PATCH] GDBStub: Support watchpoints on linux (#1030) * GDBStub: Support watchpoints on linux * GDBStub: Use `TCP_NODELAY` --- src/Cafe/CMakeLists.txt | 1 + .../HW/Espresso/Debugger/GDBBreakpoints.cpp | 304 ++++++++++++++++++ .../HW/Espresso/Debugger/GDBBreakpoints.h | 211 +----------- src/Cafe/HW/Espresso/Debugger/GDBStub.cpp | 8 + src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp | 31 +- .../ExceptionHandler_posix.cpp | 13 + 6 files changed, 371 insertions(+), 197 deletions(-) create mode 100644 src/Cafe/HW/Espresso/Debugger/GDBBreakpoints.cpp diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 29c5a0b3..9e20bb33 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -40,6 +40,7 @@ add_library(CemuCafe HW/Espresso/Debugger/DebugSymbolStorage.h HW/Espresso/Debugger/GDBStub.h HW/Espresso/Debugger/GDBStub.cpp + HW/Espresso/Debugger/GDBBreakpoints.cpp HW/Espresso/Debugger/GDBBreakpoints.h HW/Espresso/EspressoISA.h HW/Espresso/Interpreter/PPCInterpreterALU.hpp diff --git a/src/Cafe/HW/Espresso/Debugger/GDBBreakpoints.cpp b/src/Cafe/HW/Espresso/Debugger/GDBBreakpoints.cpp new file mode 100644 index 00000000..675050d3 --- /dev/null +++ b/src/Cafe/HW/Espresso/Debugger/GDBBreakpoints.cpp @@ -0,0 +1,304 @@ +#include "GDBBreakpoints.h" +#include "Debugger.h" +#include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" + +#if defined(ARCH_X86_64) && BOOST_OS_LINUX +#include +#include +#include + +DRType _GetDR(pid_t tid, int drIndex) +{ + size_t drOffset = offsetof(struct user, u_debugreg) + drIndex * sizeof(user::u_debugreg[0]); + + long v; + v = ptrace(PTRACE_PEEKUSER, tid, drOffset, nullptr); + if (v == -1) + perror("ptrace(PTRACE_PEEKUSER)"); + + return (DRType)v; +} + +void _SetDR(pid_t tid, int drIndex, DRType newValue) +{ + size_t drOffset = offsetof(struct user, u_debugreg) + drIndex * sizeof(user::u_debugreg[0]); + + long rc = ptrace(PTRACE_POKEUSER, tid, drOffset, newValue); + if (rc == -1) + perror("ptrace(PTRACE_POKEUSER)"); +} + +DRType _ReadDR6() +{ + pid_t tid = gettid(); + + // linux doesn't let us attach to the current thread / threads in the current thread group + // we have to create a child process which then modifies the debug registers and quits + pid_t child = fork(); + if (child == -1) + { + perror("fork"); + return 0; + } + + if (child == 0) + { + if (ptrace(PTRACE_ATTACH, tid, nullptr, nullptr)) + { + perror("attach"); + _exit(0); + } + + waitpid(tid, NULL, 0); + + uint64_t dr6 = _GetDR(tid, 6); + + if (ptrace(PTRACE_DETACH, tid, nullptr, nullptr)) + perror("detach"); + + // since the status code only uses the lower 8 bits, we have to discard the rest of DR6 + // this should be fine though, since the lower 4 bits of DR6 contain all the bp conditions + _exit(dr6 & 0xff); + } + + // wait for child process + int wstatus; + waitpid(child, &wstatus, 0); + + return (DRType)WEXITSTATUS(wstatus); +} +#endif + +GDBServer::ExecutionBreakpoint::ExecutionBreakpoint(MPTR address, BreakpointType type, bool visible, std::string reason) + : m_address(address), m_removedAfterInterrupt(false), m_reason(std::move(reason)) +{ + if (type == BreakpointType::BP_SINGLE) + { + this->m_pauseThreads = true; + this->m_restoreAfterInterrupt = false; + this->m_deleteAfterAnyInterrupt = false; + this->m_pauseOnNextInterrupt = false; + this->m_visible = visible; + } + else if (type == BreakpointType::BP_PERSISTENT) + { + this->m_pauseThreads = true; + this->m_restoreAfterInterrupt = true; + this->m_deleteAfterAnyInterrupt = false; + this->m_pauseOnNextInterrupt = false; + this->m_visible = visible; + } + else if (type == BreakpointType::BP_RESTORE_POINT) + { + this->m_pauseThreads = false; + this->m_restoreAfterInterrupt = false; + this->m_deleteAfterAnyInterrupt = false; + this->m_pauseOnNextInterrupt = false; + this->m_visible = false; + } + else if (type == BreakpointType::BP_STEP_POINT) + { + this->m_pauseThreads = false; + this->m_restoreAfterInterrupt = false; + this->m_deleteAfterAnyInterrupt = true; + this->m_pauseOnNextInterrupt = true; + this->m_visible = false; + } + + this->m_origOpCode = memory_readU32(address); + memory_writeU32(address, DEBUGGER_BP_T_GDBSTUB_TW); + PPCRecompiler_invalidateRange(address, address + 4); +} + +GDBServer::ExecutionBreakpoint::~ExecutionBreakpoint() +{ + memory_writeU32(this->m_address, this->m_origOpCode); + PPCRecompiler_invalidateRange(this->m_address, this->m_address + 4); +} + +uint32 GDBServer::ExecutionBreakpoint::GetVisibleOpCode() const +{ + if (this->m_visible) + return memory_readU32(this->m_address); + else + return this->m_origOpCode; +} + +void GDBServer::ExecutionBreakpoint::RemoveTemporarily() +{ + memory_writeU32(this->m_address, this->m_origOpCode); + PPCRecompiler_invalidateRange(this->m_address, this->m_address + 4); + this->m_restoreAfterInterrupt = true; +} + +void GDBServer::ExecutionBreakpoint::Restore() +{ + memory_writeU32(this->m_address, DEBUGGER_BP_T_GDBSTUB_TW); + PPCRecompiler_invalidateRange(this->m_address, this->m_address + 4); + this->m_restoreAfterInterrupt = false; +} + +namespace coreinit +{ +#if BOOST_OS_LINUX + std::vector& OSGetSchedulerThreadIds(); +#endif + + std::vector& OSGetSchedulerThreads(); +} + +GDBServer::AccessBreakpoint::AccessBreakpoint(MPTR address, AccessPointType type) + : m_address(address), m_type(type) +{ +#if defined(ARCH_X86_64) && BOOST_OS_WINDOWS + for (auto& hThreadNH : coreinit::OSGetSchedulerThreads()) + { + HANDLE hThread = (HANDLE)hThreadNH; + CONTEXT ctx{}; + ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; + SuspendThread(hThread); + GetThreadContext(hThread, &ctx); + + // use BP 2/3 for gdb stub since cemu's internal debugger uses BP 0/1 already + ctx.Dr2 = (DWORD64)memory_getPointerFromVirtualOffset(address); + ctx.Dr3 = (DWORD64)memory_getPointerFromVirtualOffset(address); + // breakpoint 2 + SetBits(ctx.Dr7, 4, 1, 1); // breakpoint #3 enabled: true + SetBits(ctx.Dr7, 24, 2, 1); // breakpoint #3 condition: 1 (write) + SetBits(ctx.Dr7, 26, 2, 3); // breakpoint #3 length: 3 (4 bytes) + // breakpoint 3 + SetBits(ctx.Dr7, 6, 1, 1); // breakpoint #4 enabled: true + SetBits(ctx.Dr7, 28, 2, 3); // breakpoint #4 condition: 3 (read & write) + SetBits(ctx.Dr7, 30, 2, 3); // breakpoint #4 length: 3 (4 bytes) + + SetThreadContext(hThread, &ctx); + ResumeThread(hThread); + } +#elif defined(ARCH_X86_64) && BOOST_OS_LINUX + // linux doesn't let us attach to threads which are in the same thread group as our current thread + // we have to create a child process which then modifies the debug registers and quits + pid_t child = fork(); + if (child == -1) + { + perror("fork"); + return; + } + + if (child == 0) + { + for (pid_t tid : coreinit::OSGetSchedulerThreadIds()) + { + long rc = ptrace(PTRACE_ATTACH, tid, nullptr, nullptr); + if (rc == -1) + perror("ptrace(PTRACE_ATTACH)"); + + waitpid(tid, nullptr, 0); + + DRType dr7 = _GetDR(tid, 7); + // use BP 2/3 for gdb stub since cemu's internal debugger uses BP 0/1 already + DRType dr2 = (uint64)memory_getPointerFromVirtualOffset(address); + DRType dr3 = (uint64)memory_getPointerFromVirtualOffset(address); + // breakpoint 2 + SetBits(dr7, 4, 1, 1); // breakpoint #3 enabled: true + SetBits(dr7, 24, 2, 1); // breakpoint #3 condition: 1 (write) + SetBits(dr7, 26, 2, 3); // breakpoint #3 length: 3 (4 bytes) + // breakpoint 3 + SetBits(dr7, 6, 1, 1); // breakpoint #4 enabled: true + SetBits(dr7, 28, 2, 3); // breakpoint #4 condition: 3 (read & write) + SetBits(dr7, 30, 2, 3); // breakpoint #4 length: 3 (4 bytes) + + _SetDR(tid, 2, dr2); + _SetDR(tid, 3, dr3); + _SetDR(tid, 7, dr7); + + rc = ptrace(PTRACE_DETACH, tid, nullptr, nullptr); + if (rc == -1) + perror("ptrace(PTRACE_DETACH)"); + } + + // exit child process + _exit(0); + } + + // wait for child process + waitpid(child, nullptr, 0); +#else + cemuLog_log(LogType::Force, "Debugger read/write breakpoints are not supported on non-x86 CPUs yet."); +#endif +} + +GDBServer::AccessBreakpoint::~AccessBreakpoint() +{ +#if defined(ARCH_X86_64) && BOOST_OS_WINDOWS + for (auto& hThreadNH : coreinit::OSGetSchedulerThreads()) + { + HANDLE hThread = (HANDLE)hThreadNH; + CONTEXT ctx{}; + ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; + SuspendThread(hThread); + GetThreadContext(hThread, &ctx); + + // reset BP 2/3 to zero + ctx.Dr2 = (DWORD64)0; + ctx.Dr3 = (DWORD64)0; + // breakpoint 2 + SetBits(ctx.Dr7, 4, 1, 0); + SetBits(ctx.Dr7, 24, 2, 0); + SetBits(ctx.Dr7, 26, 2, 0); + // breakpoint 3 + SetBits(ctx.Dr7, 6, 1, 0); + SetBits(ctx.Dr7, 28, 2, 0); + SetBits(ctx.Dr7, 30, 2, 0); + SetThreadContext(hThread, &ctx); + ResumeThread(hThread); + } +#elif defined(ARCH_X86_64) && BOOST_OS_LINUX + // linux doesn't let us attach to threads which are in the same thread group as our current thread + // we have to create a child process which then modifies the debug registers and quits + pid_t child = fork(); + if (child == -1) + { + perror("fork"); + return; + } + + if (child == 0) + { + for (pid_t tid : coreinit::OSGetSchedulerThreadIds()) + { + long rc = ptrace(PTRACE_ATTACH, tid, nullptr, nullptr); + if (rc == -1) + perror("ptrace(PTRACE_ATTACH)"); + + waitpid(tid, nullptr, 0); + + DRType dr7 = _GetDR(tid, 7); + // reset BP 2/3 to zero + DRType dr2 = 0; + DRType dr3 = 0; + // breakpoint 2 + SetBits(dr7, 4, 1, 0); + SetBits(dr7, 24, 2, 0); + SetBits(dr7, 26, 2, 0); + // breakpoint 3 + SetBits(dr7, 6, 1, 0); + SetBits(dr7, 28, 2, 0); + SetBits(dr7, 30, 2, 0); + + _SetDR(tid, 2, dr2); + _SetDR(tid, 3, dr3); + _SetDR(tid, 7, dr7); + + rc = ptrace(PTRACE_DETACH, tid, nullptr, nullptr); + if (rc == -1) + perror("ptrace(PTRACE_DETACH)"); + } + + // exit child process + _exit(0); + } + + // wait for child process + waitpid(child, nullptr, 0); +#endif +} diff --git a/src/Cafe/HW/Espresso/Debugger/GDBBreakpoints.h b/src/Cafe/HW/Espresso/Debugger/GDBBreakpoints.h index b86bd9a6..f94365c2 100644 --- a/src/Cafe/HW/Espresso/Debugger/GDBBreakpoints.h +++ b/src/Cafe/HW/Espresso/Debugger/GDBBreakpoints.h @@ -1,33 +1,18 @@ +#pragma once +#include "GDBStub.h" #include -#if defined(ARCH_X86_64) && BOOST_OS_LINUX && FALSE -#include -#include -#include +#if defined(ARCH_X86_64) && BOOST_OS_LINUX +#include // helpers for accessing debug register typedef unsigned long DRType; -DRType _GetDR(pid_t tid, int drIndex) -{ - unsigned long v; - v = ptrace (PTRACE_PEEKUSER, tid, offsetof (struct user, u_debugreg[drIndex]), 0); - return (DRType)v; -} - -void _SetDR(pid_t tid, int drIndex, DRType newValue) -{ - unsigned long v = newValue; - ptrace (PTRACE_POKEUSER, tid, offsetof (struct user, u_debugreg[drIndex]), v); -} - +DRType _GetDR(pid_t tid, int drIndex); +void _SetDR(pid_t tid, int drIndex, DRType newValue); +DRType _ReadDR6(); #endif -namespace coreinit -{ - std::vector& OSGetSchedulerThreads(); -} - enum class BreakpointType { BP_SINGLE, @@ -38,59 +23,10 @@ enum class BreakpointType class GDBServer::ExecutionBreakpoint { public: - ExecutionBreakpoint(MPTR address, BreakpointType type, bool visible, std::string reason) - : m_address(address), m_removedAfterInterrupt(false), m_reason(std::move(reason)) - { - if (type == BreakpointType::BP_SINGLE) - { - this->m_pauseThreads = true; - this->m_restoreAfterInterrupt = false; - this->m_deleteAfterAnyInterrupt = false; - this->m_pauseOnNextInterrupt = false; - this->m_visible = visible; - } - else if (type == BreakpointType::BP_PERSISTENT) - { - this->m_pauseThreads = true; - this->m_restoreAfterInterrupt = true; - this->m_deleteAfterAnyInterrupt = false; - this->m_pauseOnNextInterrupt = false; - this->m_visible = visible; - } - else if (type == BreakpointType::BP_RESTORE_POINT) - { - this->m_pauseThreads = false; - this->m_restoreAfterInterrupt = false; - this->m_deleteAfterAnyInterrupt = false; - this->m_pauseOnNextInterrupt = false; - this->m_visible = false; - } - else if (type == BreakpointType::BP_STEP_POINT) - { - this->m_pauseThreads = false; - this->m_restoreAfterInterrupt = false; - this->m_deleteAfterAnyInterrupt = true; - this->m_pauseOnNextInterrupt = true; - this->m_visible = false; - } + ExecutionBreakpoint(MPTR address, BreakpointType type, bool visible, std::string reason); + ~ExecutionBreakpoint(); - this->m_origOpCode = memory_readU32(address); - memory_writeU32(address, DEBUGGER_BP_T_GDBSTUB_TW); - PPCRecompiler_invalidateRange(address, address + 4); - }; - ~ExecutionBreakpoint() - { - memory_writeU32(this->m_address, this->m_origOpCode); - PPCRecompiler_invalidateRange(this->m_address, this->m_address + 4); - }; - - [[nodiscard]] uint32 GetVisibleOpCode() const - { - if (this->m_visible) - return memory_readU32(this->m_address); - else - return this->m_origOpCode; - }; + [[nodiscard]] uint32 GetVisibleOpCode() const; [[nodiscard]] bool ShouldBreakThreads() const { return this->m_pauseThreads; @@ -118,18 +54,8 @@ public: return m_reason; }; - void RemoveTemporarily() - { - memory_writeU32(this->m_address, this->m_origOpCode); - PPCRecompiler_invalidateRange(this->m_address, this->m_address + 4); - this->m_restoreAfterInterrupt = true; - }; - void Restore() - { - memory_writeU32(this->m_address, DEBUGGER_BP_T_GDBSTUB_TW); - PPCRecompiler_invalidateRange(this->m_address, this->m_address + 4); - this->m_restoreAfterInterrupt = false; - }; + void RemoveTemporarily(); + void Restore(); void PauseOnNextInterrupt() { this->m_pauseOnNextInterrupt = true; @@ -162,115 +88,8 @@ enum class AccessPointType class GDBServer::AccessBreakpoint { public: - AccessBreakpoint(MPTR address, AccessPointType type) - : m_address(address), m_type(type) - { -#if defined(ARCH_X86_64) && BOOST_OS_WINDOWS - for (auto& hThreadNH : coreinit::OSGetSchedulerThreads()) - { - HANDLE hThread = (HANDLE)hThreadNH; - CONTEXT ctx{}; - ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; - SuspendThread(hThread); - GetThreadContext(hThread, &ctx); - - // use BP 2/3 for gdb stub since cemu's internal debugger uses BP 0/1 already - ctx.Dr2 = (DWORD64)memory_getPointerFromVirtualOffset(address); - ctx.Dr3 = (DWORD64)memory_getPointerFromVirtualOffset(address); - // breakpoint 2 - SetBits(ctx.Dr7, 4, 1, 1); // breakpoint #3 enabled: true - SetBits(ctx.Dr7, 24, 2, 1); // breakpoint #3 condition: 1 (write) - SetBits(ctx.Dr7, 26, 2, 3); // breakpoint #3 length: 3 (4 bytes) - // breakpoint 3 - SetBits(ctx.Dr7, 6, 1, 1); // breakpoint #4 enabled: true - SetBits(ctx.Dr7, 28, 2, 3); // breakpoint #4 condition: 3 (read & write) - SetBits(ctx.Dr7, 30, 2, 3); // breakpoint #4 length: 3 (4 bytes) - - SetThreadContext(hThread, &ctx); - ResumeThread(hThread); - } - // todo: port the following code to all unix platforms, they seem to differ quite a bit -#elif defined(ARCH_X86_64) && BOOST_OS_LINUX && FALSE - for (auto& hThreadNH : coreinit::OSGetSchedulerThreads()) - { - pid_t pid = (pid_t)(uintptr_t)hThreadNH; - ptrace(PTRACE_ATTACH, pid, nullptr, nullptr); - waitpid(pid, nullptr, 0); - - DRType dr7 = _GetDR(pid, 7); - // use BP 2/3 for gdb stub since cemu's internal debugger uses BP 0/1 already - DRType dr2 = (uint64)memory_getPointerFromVirtualOffset(address); - DRType dr3 = (uint64)memory_getPointerFromVirtualOffset(address); - // breakpoint 2 - SetBits(dr7, 4, 1, 1); // breakpoint #3 enabled: true - SetBits(dr7, 24, 2, 1); // breakpoint #3 condition: 1 (write) - SetBits(dr7, 26, 2, 3); // breakpoint #3 length: 3 (4 bytes) - // breakpoint 3 - SetBits(dr7, 6, 1, 1); // breakpoint #4 enabled: true - SetBits(dr7, 28, 2, 3); // breakpoint #4 condition: 3 (read & write) - SetBits(dr7, 30, 2, 3); // breakpoint #4 length: 3 (4 bytes) - - _SetDR(pid, 2, dr2); - _SetDR(pid, 3, dr3); - _SetDR(pid, 7, dr7); - ptrace(PTRACE_DETACH, pid, nullptr, nullptr); - } -#else - cemuLog_log(LogType::Force, "Debugger read/write breakpoints are not supported on non-x86 CPUs yet."); -#endif - }; - ~AccessBreakpoint() - { -#if defined(ARCH_X86_64) && BOOST_OS_WINDOWS - for (auto& hThreadNH : coreinit::OSGetSchedulerThreads()) - { - HANDLE hThread = (HANDLE)hThreadNH; - CONTEXT ctx{}; - ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; - SuspendThread(hThread); - GetThreadContext(hThread, &ctx); - - // reset BP 2/3 to zero - ctx.Dr2 = (DWORD64)0; - ctx.Dr3 = (DWORD64)0; - // breakpoint 2 - SetBits(ctx.Dr7, 4, 1, 0); - SetBits(ctx.Dr7, 24, 2, 0); - SetBits(ctx.Dr7, 26, 2, 0); - // breakpoint 3 - SetBits(ctx.Dr7, 6, 1, 0); - SetBits(ctx.Dr7, 28, 2, 0); - SetBits(ctx.Dr7, 30, 2, 0); - SetThreadContext(hThread, &ctx); - ResumeThread(hThread); - } -#elif defined(ARCH_X86_64) && BOOST_OS_LINUX && FALSE - for (auto& hThreadNH : coreinit::OSGetSchedulerThreads()) - { - pid_t pid = (pid_t)(uintptr_t)hThreadNH; - ptrace(PTRACE_ATTACH, pid, nullptr, nullptr); - waitpid(pid, nullptr, 0); - - DRType dr7 = _GetDR(pid, 7); - // reset BP 2/3 to zero - DRType dr2 = 0; - DRType dr3 = 0; - // breakpoint 2 - SetBits(dr7, 4, 1, 0); - SetBits(dr7, 24, 2, 0); - SetBits(dr7, 26, 2, 0); - // breakpoint 3 - SetBits(dr7, 6, 1, 0); - SetBits(dr7, 28, 2, 0); - SetBits(dr7, 30, 2, 0); - - _SetDR(pid, 2, dr2); - _SetDR(pid, 3, dr3); - _SetDR(pid, 7, dr7); - ptrace(PTRACE_DETACH, pid, nullptr, nullptr); - } -#endif - }; + AccessBreakpoint(MPTR address, AccessPointType type); + ~AccessBreakpoint(); MPTR GetAddress() const { @@ -284,4 +103,4 @@ public: private: const MPTR m_address; const AccessPointType m_type; -}; \ No newline at end of file +}; diff --git a/src/Cafe/HW/Espresso/Debugger/GDBStub.cpp b/src/Cafe/HW/Espresso/Debugger/GDBStub.cpp index c8308594..6cddae01 100644 --- a/src/Cafe/HW/Espresso/Debugger/GDBStub.cpp +++ b/src/Cafe/HW/Espresso/Debugger/GDBStub.cpp @@ -263,6 +263,14 @@ bool GDBServer::Initialize() return false; } + int nodelayEnabled = TRUE; + if (setsockopt(m_server_socket, IPPROTO_TCP, TCP_NODELAY, (char*)&nodelayEnabled, sizeof(nodelayEnabled)) == SOCKET_ERROR) + { + closesocket(m_server_socket); + m_server_socket = INVALID_SOCKET; + return false; + } + memset(&m_server_addr, 0, sizeof(m_server_addr)); m_server_addr.sin_family = AF_INET; m_server_addr.sin_addr.s_addr = htonl(INADDR_ANY); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp index d9b33dca..3701a4d7 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -5,6 +5,7 @@ #include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" #include "Cafe/OS/libs/snd_core/ax.h" +#include "Cafe/HW/Espresso/Debugger/GDBStub.h" #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" #include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" @@ -1153,6 +1154,18 @@ namespace coreinit } } +#if BOOST_OS_LINUX + #include + #include + + std::vector g_schedulerThreadIds; + + std::vector& OSGetSchedulerThreadIds() + { + return g_schedulerThreadIds; + } +#endif + void OSSchedulerCoreEmulationThread(void* _assignedCoreIndex) { SetThreadName(fmt::format("OSSchedulerThread[core={}]", (uintptr_t)_assignedCoreIndex).c_str()); @@ -1160,8 +1173,21 @@ namespace coreinit #if defined(ARCH_X86_64) _mm_setcsr(_mm_getcsr() | 0x8000); // flush denormals to zero #endif + +#if BOOST_OS_LINUX + if (g_gdbstub) + { + // need to allow the GDBStub to attach to our thread + prctl(PR_SET_DUMPABLE, (unsigned long)1); + prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY); + } + + pid_t tid = gettid(); + g_schedulerThreadIds.emplace_back(tid); +#endif + t_schedulerFiber = Fiber::PrepareCurrentThread(); - + // create scheduler idle fiber and switch to it g_idleLoopFiber[t_assignedCoreIndex] = new Fiber(__OSThreadCoreIdle, nullptr, nullptr); cemu_assert_debug(PPCInterpreter_getCurrentInstance() == nullptr); @@ -1211,6 +1237,9 @@ namespace coreinit threadItr.join(); sSchedulerThreads.clear(); g_schedulerThreadHandles.clear(); +#if BOOST_OS_LINUX + g_schedulerThreadIds.clear(); +#endif // clean up all fibers for (auto& it : g_idleLoopFiber) { diff --git a/src/Common/ExceptionHandler/ExceptionHandler_posix.cpp b/src/Common/ExceptionHandler/ExceptionHandler_posix.cpp index 34430e37..cf547110 100644 --- a/src/Common/ExceptionHandler/ExceptionHandler_posix.cpp +++ b/src/Common/ExceptionHandler/ExceptionHandler_posix.cpp @@ -6,6 +6,9 @@ #include "util/helpers/StringHelpers.h" #include "ExceptionHandler.h" +#include "Cafe/HW/Espresso/Debugger/GDBStub.h" +#include "Cafe/HW/Espresso/Debugger/GDBBreakpoints.h" + #if BOOST_OS_LINUX #include "ELFSymbolTable.h" #endif @@ -61,6 +64,16 @@ void DemangleAndPrintBacktrace(char** backtrace, size_t size) // handle signals that would dump core, print stacktrace and then dump depending on config void handlerDumpingSignal(int sig, siginfo_t *info, void *context) { +#if defined(ARCH_X86_64) && BOOST_OS_LINUX + // Check for hardware breakpoints + if (info->si_signo == SIGTRAP && info->si_code == TRAP_HWBKPT) + { + uint64 dr6 = _ReadDR6(); + g_gdbstub->HandleAccessException(dr6); + return; + } +#endif + if(!CrashLog_Create()) return; // give up if crashlog was already created