mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2024-12-22 20:31:14 +00:00
kern: improve kdebug attach semantics
This commit is contained in:
parent
4c73c461f1
commit
2b91956051
8 changed files with 369 additions and 135 deletions
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue