kern: improve kdebug attach semantics

This commit is contained in:
Michael Scire 2021-09-17 18:10:05 -07:00 committed by SciresM
parent 4c73c461f1
commit 2b91956051
8 changed files with 369 additions and 135 deletions

View file

@ -142,7 +142,7 @@ $(OFILES_SRC) : $(HFILES_BIN)
kern_libc_generic.o: CFLAGS += -fno-builtin kern_libc_generic.o: CFLAGS += -fno-builtin
kern_k_auto_object.o: CXXFLAGS += -fno-lto kern_k_auto_object.o kern_k_debug_base_process_holder.o: CXXFLAGS += -fno-lto
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
%_bin.h %.bin.o : %.bin %_bin.h %.bin.o : %.bin

View file

@ -25,14 +25,31 @@ namespace ams::kern {
class KDebugBase : public KSynchronizationObject { class KDebugBase : public KSynchronizationObject {
protected: protected:
using DebugEventList = util::IntrusiveListBaseTraits<KEventInfo>::ListType; using DebugEventList = util::IntrusiveListBaseTraits<KEventInfo>::ListType;
private:
class ProcessHolder {
private:
friend class KDebugBase;
private:
KProcess *m_process;
std::atomic<u32> m_ref_count;
private:
explicit ProcessHolder() : m_process(nullptr) { /* ... */ }
void Attach(KProcess *process);
void Detach();
bool Open();
void Close();
};
private: private:
DebugEventList m_event_info_list; DebugEventList m_event_info_list;
u32 m_continue_flags; u32 m_continue_flags;
KProcess *m_process; ProcessHolder m_process_holder;
KLightLock m_lock; KLightLock m_lock;
KProcess::State m_old_process_state; KProcess::State m_old_process_state;
bool m_is_attached;
public: public:
explicit KDebugBase() { /* ... */ } explicit KDebugBase() : m_event_info_list(), m_process_holder(), m_lock() { /* ... */ }
protected: protected:
bool Is64Bit() const; bool Is64Bit() const;
public: public:
@ -59,7 +76,21 @@ namespace ams::kern {
Result GetDebugEventInfo(ams::svc::lp64::DebugEventInfo *out); Result GetDebugEventInfo(ams::svc::lp64::DebugEventInfo *out);
Result GetDebugEventInfo(ams::svc::ilp32::DebugEventInfo *out); Result GetDebugEventInfo(ams::svc::ilp32::DebugEventInfo *out);
KScopedAutoObject<KProcess> GetProcess(); ALWAYS_INLINE bool IsAttached() const {
return m_is_attached;
}
ALWAYS_INLINE bool OpenProcess() {
return m_process_holder.Open();
}
ALWAYS_INLINE void CloseProcess() {
return m_process_holder.Close();
}
ALWAYS_INLINE KProcess *GetProcessUnsafe() const {
return m_process_holder.m_process;
}
private: private:
void PushDebugEvent(ams::svc::DebugEvent event, uintptr_t param0 = 0, uintptr_t param1 = 0, uintptr_t param2 = 0, uintptr_t param3 = 0, uintptr_t param4 = 0); void PushDebugEvent(ams::svc::DebugEvent event, uintptr_t param0 = 0, uintptr_t param1 = 0, uintptr_t param2 = 0, uintptr_t param3 = 0, uintptr_t param4 = 0);
void EnqueueDebugEventInfo(KEventInfo *info); void EnqueueDebugEventInfo(KEventInfo *info);

View file

@ -307,8 +307,14 @@ namespace ams::kern::arch::arm64 {
R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle()); R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle());
/* Get the process from the debug object. */ /* Get the process from the debug object. */
process = debug->GetProcess(); R_UNLESS(debug->IsAttached(), svc::ResultProcessTerminated());
R_UNLESS(process.IsNotNull(), svc::ResultProcessTerminated()); R_UNLESS(debug->OpenProcess(), svc::ResultProcessTerminated());
/* Close the process when we're done. */
ON_SCOPE_EXIT { debug->CloseProcess(); };
/* Get the proces. */
KProcess * const process = debug->GetProcessUnsafe();
/* Set the value to be the context id. */ /* Set the value to be the context id. */
value = process->GetId() & 0xFFFFFFFF; value = process->GetId() & 0xFFFFFFFF;

View file

@ -25,47 +25,103 @@ namespace ams::kern {
} }
void KDebugBase::ProcessHolder::Attach(KProcess *process) {
MESOSPHERE_ASSERT(m_process == nullptr);
/* Set our process. */
m_process = process;
/* Open reference to our process. */
m_process->Open();
/* Set our reference count. */
m_ref_count = 1;
}
void KDebugBase::ProcessHolder::Detach() {
/* Close our process, if we have one. */
KProcess * const process = m_process;
if (AMS_LIKELY(process != nullptr)) {
/* Set our process to a debug sentinel value, which will cause crash if accessed. */
m_process = reinterpret_cast<KProcess *>(1);
/* Close reference to our process. */
process->Close();
}
}
void KDebugBase::Initialize() { void KDebugBase::Initialize() {
/* Clear the process and continue flags. */ /* Clear the continue flags. */
m_process = nullptr;
m_continue_flags = 0; m_continue_flags = 0;
} }
bool KDebugBase::Is64Bit() const { bool KDebugBase::Is64Bit() const {
MESOSPHERE_ASSERT(m_lock.IsLockedByCurrentThread()); MESOSPHERE_ASSERT(m_lock.IsLockedByCurrentThread());
MESOSPHERE_ASSERT(m_process != nullptr); MESOSPHERE_ASSERT(m_is_attached);
return m_process->Is64Bit();
KProcess * const process = this->GetProcessUnsafe();
MESOSPHERE_ASSERT(process != nullptr);
return process->Is64Bit();
} }
Result KDebugBase::QueryMemoryInfo(ams::svc::MemoryInfo *out_memory_info, ams::svc::PageInfo *out_page_info, KProcessAddress address) { Result KDebugBase::QueryMemoryInfo(ams::svc::MemoryInfo *out_memory_info, ams::svc::PageInfo *out_page_info, KProcessAddress address) {
/* Check that we're attached. */
R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated());
/* Open a reference to our process. */
R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated());
/* Close our reference to our process when we're done. */
ON_SCOPE_EXIT { this->CloseProcess(); };
/* Lock ourselves. */ /* Lock ourselves. */
KScopedLightLock lk(m_lock); KScopedLightLock lk(m_lock);
/* Check that we have a valid process. */ /* Check that we're still attached now that we're locked. */
R_UNLESS(m_process != nullptr, svc::ResultProcessTerminated()); R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated());
R_UNLESS(!m_process->IsTerminated(), svc::ResultProcessTerminated());
/* Get the process pointer. */
KProcess * const process = this->GetProcessUnsafe();
/* Check that the process isn't terminated. */
R_UNLESS(!process->IsTerminated(), svc::ResultProcessTerminated());
/* Query the mapping's info. */ /* Query the mapping's info. */
KMemoryInfo info; KMemoryInfo info;
R_TRY(m_process->GetPageTable().QueryInfo(std::addressof(info), out_page_info, address)); R_TRY(process->GetPageTable().QueryInfo(std::addressof(info), out_page_info, address));
/* Write output. */ /* Write output. */
*out_memory_info = info.GetSvcMemoryInfo(); *out_memory_info = info.GetSvcMemoryInfo();
return ResultSuccess(); return ResultSuccess();
} }
Result KDebugBase::ReadMemory(KProcessAddress buffer, KProcessAddress address, size_t size) { Result KDebugBase::ReadMemory(KProcessAddress buffer, KProcessAddress address, size_t size) {
/* Check that we're attached. */
R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated());
/* Open a reference to our process. */
R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated());
/* Close our reference to our process when we're done. */
ON_SCOPE_EXIT { this->CloseProcess(); };
/* Lock ourselves. */ /* Lock ourselves. */
KScopedLightLock lk(m_lock); KScopedLightLock lk(m_lock);
/* Check that we have a valid process. */ /* Check that we're still attached now that we're locked. */
R_UNLESS(m_process != nullptr, svc::ResultProcessTerminated()); R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated());
R_UNLESS(!m_process->IsTerminated(), svc::ResultProcessTerminated());
/* Get the process pointer. */
KProcess * const process = this->GetProcessUnsafe();
/* Check that the process isn't terminated. */
R_UNLESS(!process->IsTerminated(), svc::ResultProcessTerminated());
/* Get the page tables. */ /* Get the page tables. */
KProcessPageTable &debugger_pt = GetCurrentProcess().GetPageTable(); KProcessPageTable &debugger_pt = GetCurrentProcess().GetPageTable();
KProcessPageTable &target_pt = m_process->GetPageTable(); KProcessPageTable &target_pt = process->GetPageTable();
/* Verify that the regions are in range. */ /* Verify that the regions are in range. */
R_UNLESS(target_pt.Contains(address, size), svc::ResultInvalidCurrentMemory()); R_UNLESS(target_pt.Contains(address, size), svc::ResultInvalidCurrentMemory());
@ -105,16 +161,30 @@ namespace ams::kern {
} }
Result KDebugBase::WriteMemory(KProcessAddress buffer, KProcessAddress address, size_t size) { Result KDebugBase::WriteMemory(KProcessAddress buffer, KProcessAddress address, size_t size) {
/* Check that we're attached. */
R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated());
/* Open a reference to our process. */
R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated());
/* Close our reference to our process when we're done. */
ON_SCOPE_EXIT { this->CloseProcess(); };
/* Lock ourselves. */ /* Lock ourselves. */
KScopedLightLock lk(m_lock); KScopedLightLock lk(m_lock);
/* Check that we have a valid process. */ /* Check that we're still attached now that we're locked. */
R_UNLESS(m_process != nullptr, svc::ResultProcessTerminated()); R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated());
R_UNLESS(!m_process->IsTerminated(), svc::ResultProcessTerminated());
/* Get the process pointer. */
KProcess * const process = this->GetProcessUnsafe();
/* Check that the process isn't terminated. */
R_UNLESS(!process->IsTerminated(), svc::ResultProcessTerminated());
/* Get the page tables. */ /* Get the page tables. */
KProcessPageTable &debugger_pt = GetCurrentProcess().GetPageTable(); KProcessPageTable &debugger_pt = GetCurrentProcess().GetPageTable();
KProcessPageTable &target_pt = m_process->GetPageTable(); KProcessPageTable &target_pt = process->GetPageTable();
/* Verify that the regions are in range. */ /* Verify that the regions are in range. */
R_UNLESS(target_pt.Contains(address, size), svc::ResultInvalidCurrentMemory()); R_UNLESS(target_pt.Contains(address, size), svc::ResultInvalidCurrentMemory());
@ -154,9 +224,17 @@ namespace ams::kern {
} }
Result KDebugBase::GetRunningThreadInfo(ams::svc::LastThreadContext *out_context, u64 *out_thread_id) { Result KDebugBase::GetRunningThreadInfo(ams::svc::LastThreadContext *out_context, u64 *out_thread_id) {
/* Get the attached process. */ /* Check that we're attached. */
KScopedAutoObject process = this->GetProcess(); R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated());
R_UNLESS(process.IsNotNull(), svc::ResultProcessTerminated());
/* Open a reference to our process. */
R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated());
/* Close our reference to our process when we're done. */
ON_SCOPE_EXIT { this->CloseProcess(); };
/* Get the process pointer. */
KProcess * const process = this->GetProcessUnsafe();
/* Get the thread info. */ /* Get the thread info. */
{ {
@ -187,6 +265,9 @@ namespace ams::kern {
/* Check that the process isn't null. */ /* Check that the process isn't null. */
MESOSPHERE_ASSERT(target != nullptr); MESOSPHERE_ASSERT(target != nullptr);
/* Clear ourselves as unattached. */
m_is_attached = false;
/* Attach to the process. */ /* Attach to the process. */
{ {
/* Lock both ourselves, the target process, and the scheduler. */ /* Lock both ourselves, the target process, and the scheduler. */
@ -216,20 +297,20 @@ namespace ams::kern {
MESOSPHERE_UNREACHABLE_DEFAULT_CASE(); MESOSPHERE_UNREACHABLE_DEFAULT_CASE();
} }
/* Set our process member, and open a reference to the target. */ /* Attach to the target. */
m_process = target; m_process_holder.Attach(target);
m_process->Open(); m_is_attached = true;
/* Set ourselves as the process's attached object. */ /* Set ourselves as the process's attached object. */
m_old_process_state = m_process->SetDebugObject(this); m_old_process_state = target->SetDebugObject(this);
/* Send an event for our attaching to the process. */ /* Send an event for our attaching to the process. */
this->PushDebugEvent(ams::svc::DebugEvent_CreateProcess); this->PushDebugEvent(ams::svc::DebugEvent_CreateProcess);
/* Send events for attaching to each thread in the process. */ /* Send events for attaching to each thread in the process. */
{ {
auto end = m_process->GetThreadList().end(); auto end = target->GetThreadList().end();
for (auto it = m_process->GetThreadList().begin(); it != end; ++it) { for (auto it = target->GetThreadList().begin(); it != end; ++it) {
/* Request that we suspend the thread. */ /* Request that we suspend the thread. */
it->RequestSuspend(KThread::SuspendType_Debug); it->RequestSuspend(KThread::SuspendType_Debug);
@ -245,7 +326,7 @@ namespace ams::kern {
} }
/* Send the process's jit debug info, if relevant. */ /* Send the process's jit debug info, if relevant. */
if (KEventInfo *jit_info = m_process->GetJitDebugInfo(); jit_info != nullptr) { if (KEventInfo *jit_info = target->GetJitDebugInfo(); jit_info != nullptr) {
this->EnqueueDebugEventInfo(jit_info); this->EnqueueDebugEventInfo(jit_info);
} }
@ -261,9 +342,17 @@ namespace ams::kern {
} }
Result KDebugBase::BreakProcess() { Result KDebugBase::BreakProcess() {
/* Get the attached process. */ /* Check that we're attached. */
KScopedAutoObject target = this->GetProcess(); R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated());
R_UNLESS(target.IsNotNull(), svc::ResultProcessTerminated());
/* Open a reference to our process. */
R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated());
/* Close our reference to our process when we're done. */
ON_SCOPE_EXIT { this->CloseProcess(); };
/* Get the process pointer. */
KProcess * const target = this->GetProcessUnsafe();
/* Lock both ourselves, the target process, and the scheduler. */ /* Lock both ourselves, the target process, and the scheduler. */
KScopedLightLock state_lk(target->GetStateLock()); KScopedLightLock state_lk(target->GetStateLock());
@ -271,10 +360,11 @@ namespace ams::kern {
KScopedLightLock this_lk(m_lock); KScopedLightLock this_lk(m_lock);
KScopedSchedulerLock sl; KScopedSchedulerLock sl;
/* Check that we're still attached to the process, and that it's not terminated. */ /* Check that we're still attached now that we're locked. */
/* NOTE: Here Nintendo only checks that this->process is not nullptr. */ R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated());
R_UNLESS(m_process == target.GetPointerUnsafe(), svc::ResultProcessTerminated());
R_UNLESS(!target->IsTerminated(), svc::ResultProcessTerminated()); /* Check that the process isn't terminated. */
R_UNLESS(!target->IsTerminated(), svc::ResultProcessTerminated());
/* Get the currently active threads. */ /* Get the currently active threads. */
constexpr u64 ThreadIdNoThread = -1ll; constexpr u64 ThreadIdNoThread = -1ll;
@ -324,9 +414,17 @@ namespace ams::kern {
} }
Result KDebugBase::TerminateProcess() { Result KDebugBase::TerminateProcess() {
/* Get the attached process. If we don't have one, we have nothing to do. */ /* Check that we're attached. */
KScopedAutoObject target = this->GetProcess(); R_UNLESS(this->IsAttached(), ResultSuccess());
R_SUCCEED_IF(target.IsNull());
/* Open a reference to our process. */
R_UNLESS(this->OpenProcess(), ResultSuccess());
/* Close our reference to our process when we're done. */
ON_SCOPE_EXIT { this->CloseProcess(); };
/* Get the process pointer. */
KProcess * const target = this->GetProcessUnsafe();
/* Detach from the process. */ /* Detach from the process. */
{ {
@ -335,11 +433,8 @@ namespace ams::kern {
KScopedLightLock list_lk(target->GetListLock()); KScopedLightLock list_lk(target->GetListLock());
KScopedLightLock this_lk(m_lock); KScopedLightLock this_lk(m_lock);
/* Check that we still have our process. */ /* Check that we're still attached. */
if (m_process != nullptr) { if (this->IsAttached()) {
/* Check that our process is the one we got earlier. */
MESOSPHERE_ASSERT(m_process == target.GetPointerUnsafe());
/* Lock the scheduler. */ /* Lock the scheduler. */
KScopedSchedulerLock sl; KScopedSchedulerLock sl;
@ -384,16 +479,16 @@ namespace ams::kern {
/* Detach from the process. */ /* Detach from the process. */
target->ClearDebugObject(new_state); target->ClearDebugObject(new_state);
m_process = nullptr; m_is_attached = false;
/* Close the initial reference opened to our process. */
this->CloseProcess();
/* Clear our continue flags. */ /* Clear our continue flags. */
m_continue_flags = 0; m_continue_flags = 0;
} }
} }
/* Close the reference we held to the process while we were attached to it. */
target->Close();
/* Terminate the process. */ /* Terminate the process. */
target->Terminate(); target->Terminate();
@ -401,16 +496,31 @@ namespace ams::kern {
} }
Result KDebugBase::GetThreadContext(ams::svc::ThreadContext *out, u64 thread_id, u32 context_flags) { Result KDebugBase::GetThreadContext(ams::svc::ThreadContext *out, u64 thread_id, u32 context_flags) {
/* Check that we're attached. */
R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated());
/* Open a reference to our process. */
R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated());
/* Close our reference to our process when we're done. */
ON_SCOPE_EXIT { this->CloseProcess(); };
/* Lock ourselves. */ /* Lock ourselves. */
KScopedLightLock lk(m_lock); KScopedLightLock lk(m_lock);
/* Check that we're still attached now that we're locked. */
R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated());
/* Get the process pointer. */
KProcess * const process = this->GetProcessUnsafe();
/* Get the thread from its id. */ /* Get the thread from its id. */
KThread *thread = KThread::GetThreadFromId(thread_id); KThread *thread = KThread::GetThreadFromId(thread_id);
R_UNLESS(thread != nullptr, svc::ResultInvalidId()); R_UNLESS(thread != nullptr, svc::ResultInvalidId());
ON_SCOPE_EXIT { thread->Close(); }; ON_SCOPE_EXIT { thread->Close(); };
/* Verify that the thread is owned by our process. */ /* Verify that the thread is owned by our process. */
R_UNLESS(m_process == thread->GetOwnerProcess(), svc::ResultInvalidId()); R_UNLESS(process == thread->GetOwnerProcess(), svc::ResultInvalidId());
/* Verify that the thread isn't terminated. */ /* Verify that the thread isn't terminated. */
R_UNLESS(thread->GetState() != KThread::ThreadState_Terminated, svc::ResultTerminationRequested()); R_UNLESS(thread->GetState() != KThread::ThreadState_Terminated, svc::ResultTerminationRequested());
@ -448,16 +558,31 @@ namespace ams::kern {
} }
Result KDebugBase::SetThreadContext(const ams::svc::ThreadContext &ctx, u64 thread_id, u32 context_flags) { Result KDebugBase::SetThreadContext(const ams::svc::ThreadContext &ctx, u64 thread_id, u32 context_flags) {
/* Check that we're attached. */
R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated());
/* Open a reference to our process. */
R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated());
/* Close our reference to our process when we're done. */
ON_SCOPE_EXIT { this->CloseProcess(); };
/* Lock ourselves. */ /* Lock ourselves. */
KScopedLightLock lk(m_lock); KScopedLightLock lk(m_lock);
/* Check that we're still attached now that we're locked. */
R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated());
/* Get the process pointer. */
KProcess * const process = this->GetProcessUnsafe();
/* Get the thread from its id. */ /* Get the thread from its id. */
KThread *thread = KThread::GetThreadFromId(thread_id); KThread *thread = KThread::GetThreadFromId(thread_id);
R_UNLESS(thread != nullptr, svc::ResultInvalidId()); R_UNLESS(thread != nullptr, svc::ResultInvalidId());
ON_SCOPE_EXIT { thread->Close(); }; ON_SCOPE_EXIT { thread->Close(); };
/* Verify that the thread is owned by our process. */ /* Verify that the thread is owned by our process. */
R_UNLESS(m_process == thread->GetOwnerProcess(), svc::ResultInvalidId()); R_UNLESS(process == thread->GetOwnerProcess(), svc::ResultInvalidId());
/* Verify that the thread isn't terminated. */ /* Verify that the thread isn't terminated. */
R_UNLESS(thread->GetState() != KThread::ThreadState_Terminated, svc::ResultTerminationRequested()); R_UNLESS(thread->GetState() != KThread::ThreadState_Terminated, svc::ResultTerminationRequested());
@ -525,9 +650,17 @@ namespace ams::kern {
Result KDebugBase::ContinueDebug(const u32 flags, const u64 *thread_ids, size_t num_thread_ids) { Result KDebugBase::ContinueDebug(const u32 flags, const u64 *thread_ids, size_t num_thread_ids) {
/* Get the attached process. */ /* Check that we're attached. */
KScopedAutoObject target = this->GetProcess(); R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated());
R_UNLESS(target.IsNotNull(), svc::ResultProcessTerminated());
/* Open a reference to our process. */
R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated());
/* Close our reference to our process when we're done. */
ON_SCOPE_EXIT { this->CloseProcess(); };
/* Get the process pointer. */
KProcess * const target = this->GetProcessUnsafe();
/* Lock both ourselves, the target process, and the scheduler. */ /* Lock both ourselves, the target process, and the scheduler. */
KScopedLightLock state_lk(target->GetStateLock()); KScopedLightLock state_lk(target->GetStateLock());
@ -535,9 +668,11 @@ namespace ams::kern {
KScopedLightLock this_lk(m_lock); KScopedLightLock this_lk(m_lock);
KScopedSchedulerLock sl; KScopedSchedulerLock sl;
/* Check that we're still attached to the process, and that it's not terminated. */ /* Check that we're still attached now that we're locked. */
R_UNLESS(m_process == target.GetPointerUnsafe(), svc::ResultProcessTerminated()); R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated());
R_UNLESS(!target->IsTerminated(), svc::ResultProcessTerminated());
/* Check that the process isn't terminated. */
R_UNLESS(!target->IsTerminated(), svc::ResultProcessTerminated());
/* Check that we have no pending events. */ /* Check that we have no pending events. */
R_UNLESS(m_event_info_list.empty(), svc::ResultBusy()); R_UNLESS(m_event_info_list.empty(), svc::ResultBusy());
@ -747,18 +882,19 @@ namespace ams::kern {
} }
KScopedAutoObject<KProcess> KDebugBase::GetProcess() {
/* Lock ourselves. */
KScopedLightLock lk(m_lock);
return m_process;
}
template<typename T> requires (std::same_as<T, ams::svc::lp64::DebugEventInfo> || std::same_as<T, ams::svc::ilp32::DebugEventInfo>) template<typename T> requires (std::same_as<T, ams::svc::lp64::DebugEventInfo> || std::same_as<T, ams::svc::ilp32::DebugEventInfo>)
Result KDebugBase::GetDebugEventInfoImpl(T *out) { Result KDebugBase::GetDebugEventInfoImpl(T *out) {
/* Get the attached process. */ /* Check that we're attached. */
KScopedAutoObject process = this->GetProcess(); R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated());
R_UNLESS(process.IsNotNull(), svc::ResultProcessTerminated());
/* Open a reference to our process. */
R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated());
/* Close our reference to our process when we're done. */
ON_SCOPE_EXIT { this->CloseProcess(); };
/* Get the process pointer. */
KProcess * const process = this->GetProcessUnsafe();
/* Pop an event info from our queue. */ /* Pop an event info from our queue. */
KEventInfo *info = nullptr; KEventInfo *info = nullptr;
@ -875,56 +1011,50 @@ namespace ams::kern {
void KDebugBase::OnFinalizeSynchronizationObject() { void KDebugBase::OnFinalizeSynchronizationObject() {
/* Detach from our process, if we have one. */ /* Detach from our process, if we have one. */
{ if (this->IsAttached() && this->OpenProcess()) {
/* Get the attached process. */ /* Close the process when we're done with it. */
KScopedAutoObject process = this->GetProcess(); ON_SCOPE_EXIT { this->CloseProcess(); };
/* If the process isn't null, detach. */ /* Get the process pointer. */
if (process.IsNotNull()) { KProcess * const process = this->GetProcessUnsafe();
/* Detach. */
/* Lock both ourselves and the target process. */
KScopedLightLock state_lk(process->GetStateLock());
KScopedLightLock list_lk(process->GetListLock());
KScopedLightLock this_lk(m_lock);
/* Check that we're still attached. */
if (m_is_attached) {
KScopedSchedulerLock sl;
/* Detach ourselves from the process. */
process->ClearDebugObject(m_old_process_state);
/* Release all threads. */
const bool resume = (process->GetState() != KProcess::State_Crashed);
{ {
/* Lock both ourselves and the target process. */ auto end = process->GetThreadList().end();
KScopedLightLock state_lk(process->GetStateLock()); for (auto it = process->GetThreadList().begin(); it != end; ++it) {
KScopedLightLock list_lk(process->GetListLock()); #if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
KScopedLightLock this_lk(m_lock); /* Clear the thread's single-step state. */
it->ClearSingleStep();
#endif
/* Ensure we finalize exactly once. */ if (resume) {
if (m_process != nullptr) { /* If the process isn't crashed, resume threads. */
MESOSPHERE_ASSERT(m_process == process.GetPointerUnsafe()); it->Resume(KThread::SuspendType_Debug);
{ } else {
KScopedSchedulerLock sl; /* Otherwise, suspend them. */
it->RequestSuspend(KThread::SuspendType_Debug);
/* Detach ourselves from the process. */
process->ClearDebugObject(m_old_process_state);
/* Release all threads. */
const bool resume = (process->GetState() != KProcess::State_Crashed);
{
auto end = process->GetThreadList().end();
for (auto it = process->GetThreadList().begin(); it != end; ++it) {
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
/* Clear the thread's single-step state. */
it->ClearSingleStep();
#endif
if (resume) {
/* If the process isn't crashed, resume threads. */
it->Resume(KThread::SuspendType_Debug);
} else {
/* Otherwise, suspend them. */
it->RequestSuspend(KThread::SuspendType_Debug);
}
}
}
/* Clear our process. */
m_process = nullptr;
} }
/* We're done detaching, so clear the reference we opened when we attached. */
process->Close();
} }
} }
/* Note we're now unattached. */
m_is_attached = false;
/* Close the initial reference opened to our process. */
this->CloseProcess();
} }
} }
@ -941,9 +1071,14 @@ namespace ams::kern {
} }
bool KDebugBase::IsSignaled() const { bool KDebugBase::IsSignaled() const {
KScopedSchedulerLock sl; bool empty;
{
KScopedSchedulerLock sl;
return (!m_event_info_list.empty()) || m_process == nullptr || m_process->IsTerminated(); empty = m_event_info_list.empty();
}
return !empty || !m_is_attached || this->GetProcessUnsafe()->IsTerminated();
} }
Result KDebugBase::ProcessDebugEvent(ams::svc::DebugEvent event, uintptr_t param0, uintptr_t param1, uintptr_t param2, uintptr_t param3, uintptr_t param4) { Result KDebugBase::ProcessDebugEvent(ams::svc::DebugEvent event, uintptr_t param0, uintptr_t param1, uintptr_t param2, uintptr_t param3, uintptr_t param4) {

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2018-2020 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 <http://www.gnu.org/licenses/>.
*/
#include <mesosphere.hpp>
namespace ams::kern {
NOINLINE bool KDebugBase::ProcessHolder::Open() {
/* Atomically increment the reference count, only if it's positive. */
u32 cur_ref_count = m_ref_count.load(std::memory_order_relaxed);
do {
if (AMS_UNLIKELY(cur_ref_count == 0)) {
MESOSPHERE_AUDIT(cur_ref_count != 0);
return false;
}
MESOSPHERE_ABORT_UNLESS(cur_ref_count < cur_ref_count + 1);
} while (!m_ref_count.compare_exchange_weak(cur_ref_count, cur_ref_count + 1, std::memory_order_relaxed));
return true;
}
NOINLINE void KDebugBase::ProcessHolder::Close() {
/* Atomically decrement the reference count, not allowing it to become negative. */
u32 cur_ref_count = m_ref_count.load(std::memory_order_relaxed);
do {
MESOSPHERE_ABORT_UNLESS(cur_ref_count > 0);
} while (!m_ref_count.compare_exchange_weak(cur_ref_count, cur_ref_count - 1, std::memory_order_relaxed));
/* If ref count hits zero, schedule the object for destruction. */
if (cur_ref_count - 1 == 0) {
this->Detach();
}
}
}

View file

@ -313,11 +313,17 @@ namespace ams::kern::svc {
ON_SCOPE_EXIT { thread->Close(); }; ON_SCOPE_EXIT { thread->Close(); };
/* Get the process from the debug object. */ /* Get the process from the debug object. */
KScopedAutoObject process = debug->GetProcess(); R_UNLESS(debug->IsAttached(), svc::ResultProcessTerminated());
R_UNLESS(process.IsNotNull(), svc::ResultProcessTerminated()); R_UNLESS(debug->OpenProcess(), svc::ResultProcessTerminated());
/* Close the process when we're done. */
ON_SCOPE_EXIT { debug->CloseProcess(); };
/* Get the proces. */
KProcess * const process = debug->GetProcessUnsafe();
/* Verify that the process is the thread's parent. */ /* Verify that the process is the thread's parent. */
R_UNLESS(process.GetPointerUnsafe() == thread->GetOwnerProcess(), svc::ResultInvalidThreadId()); R_UNLESS(process == thread->GetOwnerProcess(), svc::ResultInvalidThreadId());
/* Get the parameter. */ /* Get the parameter. */
switch (param) { switch (param) {

View file

@ -36,27 +36,35 @@ namespace ams::kern::svc {
R_UNLESS(obj.IsNotNull(), svc::ResultInvalidHandle()); R_UNLESS(obj.IsNotNull(), svc::ResultInvalidHandle());
/* Get the process from the object. */ /* Get the process from the object. */
KProcess *process = nullptr; if (KProcess *process = obj->DynamicCast<KProcess *>(); process != nullptr) {
if (KProcess *p = obj->DynamicCast<KProcess *>(); p != nullptr) {
/* The object is a process, so we can use it directly. */ /* The object is a process, so we can use it directly. */
process = p;
/* Make sure the target process exists. */
R_UNLESS(process != nullptr, svc::ResultInvalidHandle());
/* Get the process id. */
*out_process_id = process->GetId();
} else if (KThread *t = obj->DynamicCast<KThread *>(); t != nullptr) { } else if (KThread *t = obj->DynamicCast<KThread *>(); t != nullptr) {
/* The object is a thread, so we want to use its parent. */ /* The object is a thread, so we want to use its parent. */
process = reinterpret_cast<KThread *>(obj.GetPointerUnsafe())->GetOwnerProcess(); KProcess *process = t->GetOwnerProcess();
/* Make sure the target process exists. */
R_UNLESS(process != nullptr, svc::ResultInvalidHandle());
/* Get the process id. */
*out_process_id = process->GetId();
} else if (KDebug *d = obj->DynamicCast<KDebug *>(); d != nullptr) { } else if (KDebug *d = obj->DynamicCast<KDebug *>(); d != nullptr) {
/* The object is a debug, so we want to use the process it's attached to. */ /* The object is a debug, so we want to use the process it's attached to. */
obj = d->GetProcess();
if (obj.IsNotNull()) { /* Make sure the target process exists. */
process = static_cast<KProcess *>(obj.GetPointerUnsafe()); R_UNLESS(d->IsAttached(), svc::ResultInvalidHandle());
} R_UNLESS(d->OpenProcess(), svc::ResultInvalidHandle());
ON_SCOPE_EXIT { d->CloseProcess(); };
/* Get the process id. */
*out_process_id = d->GetProcessUnsafe()->GetProcessId();
} }
/* Make sure the target process exists. */
R_UNLESS(process != nullptr, svc::ResultInvalidHandle());
/* Get the process id. */
*out_process_id = process->GetId();
return ResultSuccess(); return ResultSuccess();
} }

View file

@ -235,12 +235,13 @@ namespace ams::kern::svc {
/* Try to get as a debug object. */ /* Try to get as a debug object. */
KScopedAutoObject debug = handle_table.GetObject<KDebug>(debug_handle); KScopedAutoObject debug = handle_table.GetObject<KDebug>(debug_handle);
if (debug.IsNotNull()) { if (debug.IsNotNull()) {
/* Get the debug object's process. */ /* Check that the debug object has a process. */
KScopedAutoObject process = debug->GetProcess(); R_UNLESS(debug->IsAttached(), svc::ResultProcessTerminated());
R_UNLESS(process.IsNotNull(), svc::ResultProcessTerminated()); R_UNLESS(debug->OpenProcess(), svc::ResultProcessTerminated());
ON_SCOPE_EXIT { debug->CloseProcess(); };
/* Get the thread list. */ /* Get the thread list. */
R_TRY(process->GetThreadList(out_num_threads, out_thread_ids, max_out_count)); R_TRY(debug->GetProcessUnsafe()->GetThreadList(out_num_threads, out_thread_ids, max_out_count));
} else { } else {
/* Try to get as a process. */ /* Try to get as a process. */
KScopedAutoObject process = handle_table.GetObjectWithoutPseudoHandle<KProcess>(debug_handle); KScopedAutoObject process = handle_table.GetObjectWithoutPseudoHandle<KProcess>(debug_handle);