mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2024-12-22 20:31:14 +00:00
kern/strat: update for new DebugFlags capability semantics
This commit is contained in:
parent
52bc54205b
commit
dfff4508fa
17 changed files with 142 additions and 110 deletions
|
@ -1,6 +1,6 @@
|
||||||
# Key: debugmode, default: 1.
|
# Key: debugmode, default: 1.
|
||||||
# Desc: Controls whether kernel is debug mode.
|
# Desc: Controls whether kernel is debug mode.
|
||||||
# Disabling this may break Atmosphere's debugger in a future release.
|
# Disabling this will break Atmosphere.
|
||||||
|
|
||||||
# Key: debugmode_user, default: 0.
|
# Key: debugmode_user, default: 0.
|
||||||
# Desc: Controls whether userland is debug mode.
|
# Desc: Controls whether userland is debug mode.
|
||||||
|
|
|
@ -154,8 +154,8 @@ namespace ams::kern::arch::arm64 {
|
||||||
R_RETURN(m_page_table.InvalidateCurrentProcessDataCache(address, size));
|
R_RETURN(m_page_table.InvalidateCurrentProcessDataCache(address, size));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ReadDebugMemory(void *buffer, KProcessAddress address, size_t size) {
|
Result ReadDebugMemory(void *buffer, KProcessAddress address, size_t size, bool force_debug_prod) {
|
||||||
R_RETURN(m_page_table.ReadDebugMemory(buffer, address, size));
|
R_RETURN(m_page_table.ReadDebugMemory(buffer, address, size, force_debug_prod));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ReadDebugIoMemory(void *buffer, KProcessAddress address, size_t size, KMemoryState state) {
|
Result ReadDebugIoMemory(void *buffer, KProcessAddress address, size_t size, KMemoryState state) {
|
||||||
|
|
|
@ -168,9 +168,10 @@ namespace ams::kern {
|
||||||
struct DebugFlags {
|
struct DebugFlags {
|
||||||
using IdBits = Field<0, CapabilityId<CapabilityType::DebugFlags> + 1>;
|
using IdBits = Field<0, CapabilityId<CapabilityType::DebugFlags> + 1>;
|
||||||
|
|
||||||
DEFINE_FIELD(AllowDebug, IdBits, 1, bool);
|
DEFINE_FIELD(AllowDebug, IdBits, 1, bool);
|
||||||
DEFINE_FIELD(ForceDebug, AllowDebug, 1, bool);
|
DEFINE_FIELD(ForceDebugProd, AllowDebug, 1, bool);
|
||||||
DEFINE_FIELD(Reserved, ForceDebug, 13);
|
DEFINE_FIELD(ForceDebug, ForceDebugProd, 1, bool);
|
||||||
|
DEFINE_FIELD(Reserved, ForceDebug, 12);
|
||||||
};
|
};
|
||||||
|
|
||||||
#undef DEFINE_FIELD
|
#undef DEFINE_FIELD
|
||||||
|
@ -255,6 +256,10 @@ namespace ams::kern {
|
||||||
return m_debug_capabilities.Get<DebugFlags::AllowDebug>();
|
return m_debug_capabilities.Get<DebugFlags::AllowDebug>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr bool CanForceDebugProd() const {
|
||||||
|
return m_debug_capabilities.Get<DebugFlags::ForceDebugProd>();
|
||||||
|
}
|
||||||
|
|
||||||
constexpr bool CanForceDebug() const {
|
constexpr bool CanForceDebug() const {
|
||||||
return m_debug_capabilities.Get<DebugFlags::ForceDebug>();
|
return m_debug_capabilities.Get<DebugFlags::ForceDebug>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ namespace ams::kern {
|
||||||
KLightLock m_lock;
|
KLightLock m_lock;
|
||||||
KProcess::State m_old_process_state;
|
KProcess::State m_old_process_state;
|
||||||
bool m_is_attached;
|
bool m_is_attached;
|
||||||
|
bool m_is_force_debug_prod;
|
||||||
public:
|
public:
|
||||||
explicit KDebugBase() { /* ... */ }
|
explicit KDebugBase() { /* ... */ }
|
||||||
protected:
|
protected:
|
||||||
|
@ -62,6 +63,10 @@ namespace ams::kern {
|
||||||
return m_is_attached;
|
return m_is_attached;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE bool IsForceDebugProd() const {
|
||||||
|
return m_is_force_debug_prod;
|
||||||
|
}
|
||||||
|
|
||||||
ALWAYS_INLINE bool OpenProcess() {
|
ALWAYS_INLINE bool OpenProcess() {
|
||||||
return m_process_holder.Open();
|
return m_process_holder.Open();
|
||||||
}
|
}
|
||||||
|
|
|
@ -328,6 +328,8 @@ namespace ams::kern {
|
||||||
R_RETURN(this->CheckMemoryState(nullptr, addr, size, state_mask, state, perm_mask, perm, attr_mask, attr, ignore_attr));
|
R_RETURN(this->CheckMemoryState(nullptr, addr, size, state_mask, state, perm_mask, perm, attr_mask, attr, ignore_attr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CanReadWriteDebugMemory(KProcessAddress addr, size_t size, bool force_debug_prod);
|
||||||
|
|
||||||
Result LockMemoryAndOpen(KPageGroup *out_pg, KPhysicalAddress *out_paddr, KProcessAddress addr, size_t size, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr, KMemoryPermission new_perm, u32 lock_attr);
|
Result LockMemoryAndOpen(KPageGroup *out_pg, KPhysicalAddress *out_paddr, KProcessAddress addr, size_t size, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr, KMemoryPermission new_perm, u32 lock_attr);
|
||||||
Result UnlockMemory(KProcessAddress addr, size_t size, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr, KMemoryPermission new_perm, u32 lock_attr, const KPageGroup *pg);
|
Result UnlockMemory(KProcessAddress addr, size_t size, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr, KMemoryPermission new_perm, u32 lock_attr, const KPageGroup *pg);
|
||||||
|
|
||||||
|
@ -421,7 +423,7 @@ namespace ams::kern {
|
||||||
Result InvalidateProcessDataCache(KProcessAddress address, size_t size);
|
Result InvalidateProcessDataCache(KProcessAddress address, size_t size);
|
||||||
Result InvalidateCurrentProcessDataCache(KProcessAddress address, size_t size);
|
Result InvalidateCurrentProcessDataCache(KProcessAddress address, size_t size);
|
||||||
|
|
||||||
Result ReadDebugMemory(void *buffer, KProcessAddress address, size_t size);
|
Result ReadDebugMemory(void *buffer, KProcessAddress address, size_t size, bool force_debug_prod);
|
||||||
Result ReadDebugIoMemory(void *buffer, KProcessAddress address, size_t size, KMemoryState state);
|
Result ReadDebugIoMemory(void *buffer, KProcessAddress address, size_t size, KMemoryState state);
|
||||||
|
|
||||||
Result WriteDebugMemory(KProcessAddress address, const void *buffer, size_t size);
|
Result WriteDebugMemory(KProcessAddress address, const void *buffer, size_t size);
|
||||||
|
|
|
@ -206,6 +206,10 @@ namespace ams::kern {
|
||||||
return m_capabilities.IsPermittedDebug();
|
return m_capabilities.IsPermittedDebug();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr bool CanForceDebugProd() const {
|
||||||
|
return m_capabilities.CanForceDebugProd();
|
||||||
|
}
|
||||||
|
|
||||||
constexpr bool CanForceDebug() const {
|
constexpr bool CanForceDebug() const {
|
||||||
return m_capabilities.CanForceDebug();
|
return m_capabilities.CanForceDebug();
|
||||||
}
|
}
|
||||||
|
|
|
@ -262,7 +262,14 @@ namespace ams::kern {
|
||||||
/* Validate. */
|
/* Validate. */
|
||||||
R_UNLESS(cap.Get<DebugFlags::Reserved>() == 0, svc::ResultReservedUsed());
|
R_UNLESS(cap.Get<DebugFlags::Reserved>() == 0, svc::ResultReservedUsed());
|
||||||
|
|
||||||
|
u32 total = 0;
|
||||||
|
if (cap.Get<DebugFlags::AllowDebug>()) { ++total; }
|
||||||
|
if (cap.Get<DebugFlags::ForceDebugProd>()) { ++total; }
|
||||||
|
if (cap.Get<DebugFlags::ForceDebug>()) { ++total; }
|
||||||
|
R_UNLESS(total <= 1, svc::ResultInvalidCombination());
|
||||||
|
|
||||||
m_debug_capabilities.Set<DebugFlags::AllowDebug>(cap.Get<DebugFlags::AllowDebug>());
|
m_debug_capabilities.Set<DebugFlags::AllowDebug>(cap.Get<DebugFlags::AllowDebug>());
|
||||||
|
m_debug_capabilities.Set<DebugFlags::ForceDebugProd>(cap.Get<DebugFlags::ForceDebugProd>());
|
||||||
m_debug_capabilities.Set<DebugFlags::ForceDebug>(cap.Get<DebugFlags::ForceDebug>());
|
m_debug_capabilities.Set<DebugFlags::ForceDebug>(cap.Get<DebugFlags::ForceDebug>());
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,8 @@ namespace ams::kern {
|
||||||
|
|
||||||
void KDebugBase::Initialize() {
|
void KDebugBase::Initialize() {
|
||||||
/* Clear the continue flags. */
|
/* Clear the continue flags. */
|
||||||
m_continue_flags = 0;
|
m_continue_flags = 0;
|
||||||
|
m_is_force_debug_prod = GetCurrentProcess().CanForceDebugProd();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool KDebugBase::Is64Bit() const {
|
bool KDebugBase::Is64Bit() const {
|
||||||
|
@ -120,8 +121,11 @@ namespace ams::kern {
|
||||||
/* Read the memory. */
|
/* Read the memory. */
|
||||||
if (info.GetSvcState() != ams::svc::MemoryState_Io) {
|
if (info.GetSvcState() != ams::svc::MemoryState_Io) {
|
||||||
/* The memory is normal memory. */
|
/* The memory is normal memory. */
|
||||||
R_TRY(target_pt.ReadDebugMemory(GetVoidPointer(buffer), cur_address, cur_size));
|
R_TRY(target_pt.ReadDebugMemory(GetVoidPointer(buffer), cur_address, cur_size, this->IsForceDebugProd()));
|
||||||
} else {
|
} else {
|
||||||
|
/* Only allow IO memory to be read if not force debug prod. */
|
||||||
|
R_UNLESS(!this->IsForceDebugProd(), svc::ResultInvalidCurrentMemory());
|
||||||
|
|
||||||
/* The memory is IO memory. */
|
/* The memory is IO memory. */
|
||||||
R_TRY(target_pt.ReadDebugIoMemory(GetVoidPointer(buffer), cur_address, cur_size, info.GetState()));
|
R_TRY(target_pt.ReadDebugIoMemory(GetVoidPointer(buffer), cur_address, cur_size, info.GetState()));
|
||||||
}
|
}
|
||||||
|
@ -269,6 +273,9 @@ namespace ams::kern {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case KProcess::State_Created:
|
case KProcess::State_Created:
|
||||||
case KProcess::State_Running:
|
case KProcess::State_Running:
|
||||||
|
/* Created and running processes can only be debugged if the debugger is not ForceDebugProd. */
|
||||||
|
R_UNLESS(!this->IsForceDebugProd(), svc::ResultInvalidState());
|
||||||
|
break;
|
||||||
case KProcess::State_Crashed:
|
case KProcess::State_Crashed:
|
||||||
break;
|
break;
|
||||||
case KProcess::State_CreatedAttached:
|
case KProcess::State_CreatedAttached:
|
||||||
|
@ -408,69 +415,6 @@ namespace ams::kern {
|
||||||
/* Get the process pointer. */
|
/* Get the process pointer. */
|
||||||
KProcess * const target = this->GetProcessUnsafe();
|
KProcess * const target = this->GetProcessUnsafe();
|
||||||
|
|
||||||
/* Detach from the process. */
|
|
||||||
{
|
|
||||||
/* Lock both ourselves and the target process. */
|
|
||||||
KScopedLightLock state_lk(target->GetStateLock());
|
|
||||||
KScopedLightLock list_lk(target->GetListLock());
|
|
||||||
KScopedLightLock this_lk(m_lock);
|
|
||||||
|
|
||||||
/* Check that we're still attached. */
|
|
||||||
if (this->IsAttached()) {
|
|
||||||
/* Lock the scheduler. */
|
|
||||||
KScopedSchedulerLock sl;
|
|
||||||
|
|
||||||
/* Get the process's state. */
|
|
||||||
const KProcess::State state = target->GetState();
|
|
||||||
|
|
||||||
/* Check that the process is in a state where we can terminate it. */
|
|
||||||
R_UNLESS(state != KProcess::State_Created, svc::ResultInvalidState());
|
|
||||||
R_UNLESS(state != KProcess::State_CreatedAttached, svc::ResultInvalidState());
|
|
||||||
|
|
||||||
/* Decide on a new state for the process. */
|
|
||||||
KProcess::State new_state;
|
|
||||||
if (state == KProcess::State_RunningAttached) {
|
|
||||||
/* If the process is running, transition it accordingly. */
|
|
||||||
new_state = KProcess::State_Running;
|
|
||||||
} else if (state == KProcess::State_DebugBreak) {
|
|
||||||
/* If the process is debug breaked, transition it accordingly. */
|
|
||||||
new_state = KProcess::State_Crashed;
|
|
||||||
|
|
||||||
/* Suspend all the threads in the process. */
|
|
||||||
{
|
|
||||||
auto end = target->GetThreadList().end();
|
|
||||||
for (auto it = target->GetThreadList().begin(); it != end; ++it) {
|
|
||||||
/* Request that we suspend the thread. */
|
|
||||||
it->RequestSuspend(KThread::SuspendType_Debug);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* Otherwise, don't transition. */
|
|
||||||
new_state = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
|
|
||||||
/* Clear single step on all threads. */
|
|
||||||
{
|
|
||||||
auto end = target->GetThreadList().end();
|
|
||||||
for (auto it = target->GetThreadList().begin(); it != end; ++it) {
|
|
||||||
it->ClearHardwareSingleStep();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Detach from the process. */
|
|
||||||
target->ClearDebugObject(new_state);
|
|
||||||
m_is_attached = false;
|
|
||||||
|
|
||||||
/* Close the initial reference opened to our process. */
|
|
||||||
this->CloseProcess();
|
|
||||||
|
|
||||||
/* Clear our continue flags. */
|
|
||||||
m_continue_flags = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Terminate the process. */
|
/* Terminate the process. */
|
||||||
target->Terminate();
|
target->Terminate();
|
||||||
|
|
||||||
|
@ -962,7 +906,12 @@ namespace ams::kern {
|
||||||
case ams::svc::DebugException_UndefinedInstruction:
|
case ams::svc::DebugException_UndefinedInstruction:
|
||||||
{
|
{
|
||||||
MESOSPHERE_ASSERT(info->info.exception.exception_data_count == 1);
|
MESOSPHERE_ASSERT(info->info.exception.exception_data_count == 1);
|
||||||
out->info.exception.specific.undefined_instruction.insn = info->info.exception.exception_data[0];
|
/* Only save the instruction if the caller is not force debug prod. */
|
||||||
|
if (this->IsForceDebugProd()) {
|
||||||
|
out->info.exception.specific.undefined_instruction.insn = 0;
|
||||||
|
} else {
|
||||||
|
out->info.exception.specific.undefined_instruction.insn = info->info.exception.exception_data[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ams::svc::DebugException_BreakPoint:
|
case ams::svc::DebugException_BreakPoint:
|
||||||
|
|
|
@ -2695,18 +2695,37 @@ namespace ams::kern {
|
||||||
R_RETURN(cpu::InvalidateDataCache(GetVoidPointer(address), size));
|
R_RETURN(cpu::InvalidateDataCache(GetVoidPointer(address), size));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result KPageTableBase::ReadDebugMemory(void *buffer, KProcessAddress address, size_t size) {
|
bool KPageTableBase::CanReadWriteDebugMemory(KProcessAddress address, size_t size, bool force_debug_prod) {
|
||||||
|
/* Check pre-conditions. */
|
||||||
|
MESOSPHERE_ASSERT(this->IsLockedByCurrentThread());
|
||||||
|
|
||||||
|
/* If the memory is debuggable and user-readable, we can perform the access. */
|
||||||
|
if (R_SUCCEEDED(this->CheckMemoryStateContiguous(address, size, KMemoryState_FlagCanDebug, KMemoryState_FlagCanDebug, KMemoryPermission_NotMapped | KMemoryPermission_UserRead, KMemoryPermission_UserRead, KMemoryAttribute_None, KMemoryAttribute_None))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we're in debug mode, and the process isn't force debug prod, check if the memory is debuggable and kernel-readable and user-executable. */
|
||||||
|
if (KTargetSystem::IsDebugMode() && !force_debug_prod) {
|
||||||
|
if (R_SUCCEEDED(this->CheckMemoryStateContiguous(address, size, KMemoryState_FlagCanDebug, KMemoryState_FlagCanDebug, KMemoryPermission_KernelRead | KMemoryPermission_UserExecute, KMemoryPermission_KernelRead | KMemoryPermission_UserExecute, KMemoryAttribute_None, KMemoryAttribute_None))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If neither of the above checks passed, we can't access the memory. */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result KPageTableBase::ReadDebugMemory(void *buffer, KProcessAddress address, size_t size, bool force_debug_prod) {
|
||||||
/* Lightly validate the region is in range. */
|
/* Lightly validate the region is in range. */
|
||||||
R_UNLESS(this->Contains(address, size), svc::ResultInvalidCurrentMemory());
|
R_UNLESS(this->Contains(address, size), svc::ResultInvalidCurrentMemory());
|
||||||
|
|
||||||
/* Lock the table. */
|
/* Lock the table. */
|
||||||
KScopedLightLock lk(m_general_lock);
|
KScopedLightLock lk(m_general_lock);
|
||||||
|
|
||||||
/* Require that the memory either be user readable or debuggable. */
|
/* Require that the memory either be user-readable-and-mapped or debug-accessible. */
|
||||||
const bool can_read = R_SUCCEEDED(this->CheckMemoryStateContiguous(address, size, KMemoryState_None, KMemoryState_None, KMemoryPermission_UserRead, KMemoryPermission_UserRead, KMemoryAttribute_None, KMemoryAttribute_None));
|
const bool can_read = R_SUCCEEDED(this->CheckMemoryStateContiguous(address, size, KMemoryState_None, KMemoryState_None, KMemoryPermission_NotMapped | KMemoryPermission_UserRead, KMemoryPermission_UserRead, KMemoryAttribute_None, KMemoryAttribute_None));
|
||||||
if (!can_read) {
|
if (!can_read) {
|
||||||
const bool can_debug = R_SUCCEEDED(this->CheckMemoryStateContiguous(address, size, KMemoryState_FlagCanDebug, KMemoryState_FlagCanDebug, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_None, KMemoryAttribute_None));
|
R_UNLESS(this->CanReadWriteDebugMemory(address, size, force_debug_prod), svc::ResultInvalidCurrentMemory());
|
||||||
R_UNLESS(can_debug, svc::ResultInvalidCurrentMemory());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get the impl. */
|
/* Get the impl. */
|
||||||
|
@ -2788,11 +2807,10 @@ namespace ams::kern {
|
||||||
/* Lock the table. */
|
/* Lock the table. */
|
||||||
KScopedLightLock lk(m_general_lock);
|
KScopedLightLock lk(m_general_lock);
|
||||||
|
|
||||||
/* Require that the memory either be user writable or debuggable. */
|
/* Require that the memory either be user-writable-and-mapped or debug-accessible. */
|
||||||
const bool can_read = R_SUCCEEDED(this->CheckMemoryStateContiguous(address, size, KMemoryState_None, KMemoryState_None, KMemoryPermission_UserReadWrite, KMemoryPermission_UserReadWrite, KMemoryAttribute_None, KMemoryAttribute_None));
|
const bool can_write = R_SUCCEEDED(this->CheckMemoryStateContiguous(address, size, KMemoryState_None, KMemoryState_None, KMemoryPermission_NotMapped | KMemoryPermission_UserReadWrite, KMemoryPermission_UserReadWrite, KMemoryAttribute_None, KMemoryAttribute_None));
|
||||||
if (!can_read) {
|
if (!can_write) {
|
||||||
const bool can_debug = R_SUCCEEDED(this->CheckMemoryStateContiguous(address, size, KMemoryState_FlagCanDebug, KMemoryState_FlagCanDebug, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_None, KMemoryAttribute_None));
|
R_UNLESS(this->CanReadWriteDebugMemory(address, size, false), svc::ResultInvalidCurrentMemory());
|
||||||
R_UNLESS(can_debug, svc::ResultInvalidCurrentMemory());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get the impl. */
|
/* Get the impl. */
|
||||||
|
|
|
@ -24,6 +24,9 @@ namespace ams::kern::svc {
|
||||||
constexpr inline int32_t MaximumDebuggableThreadCount = 0x60;
|
constexpr inline int32_t MaximumDebuggableThreadCount = 0x60;
|
||||||
|
|
||||||
Result DebugActiveProcess(ams::svc::Handle *out_handle, uint64_t process_id) {
|
Result DebugActiveProcess(ams::svc::Handle *out_handle, uint64_t process_id) {
|
||||||
|
/* Check that the SVC can be used. */
|
||||||
|
R_UNLESS(KTargetSystem::IsDebugMode() || GetCurrentProcess().CanForceDebugProd(), svc::ResultNotImplemented());
|
||||||
|
|
||||||
/* Get the process from its id. */
|
/* Get the process from its id. */
|
||||||
KProcess *process = KProcess::GetProcessFromId(process_id);
|
KProcess *process = KProcess::GetProcessFromId(process_id);
|
||||||
R_UNLESS(process != nullptr, svc::ResultInvalidProcessId());
|
R_UNLESS(process != nullptr, svc::ResultInvalidProcessId());
|
||||||
|
@ -32,9 +35,8 @@ namespace ams::kern::svc {
|
||||||
ON_SCOPE_EXIT { process->Close(); };
|
ON_SCOPE_EXIT { process->Close(); };
|
||||||
|
|
||||||
/* Check that the debugging is allowed. */
|
/* Check that the debugging is allowed. */
|
||||||
if (!process->IsPermittedDebug()) {
|
const bool allowable = process->IsPermittedDebug() || GetCurrentProcess().CanForceDebug() || GetCurrentProcess().CanForceDebugProd();
|
||||||
R_UNLESS(GetCurrentProcess().CanForceDebug(), svc::ResultInvalidState());
|
R_UNLESS(allowable, svc::ResultInvalidState());
|
||||||
}
|
|
||||||
|
|
||||||
/* Disallow debugging one's own processs, to prevent softlocks. */
|
/* Disallow debugging one's own processs, to prevent softlocks. */
|
||||||
R_UNLESS(process != GetCurrentProcessPointer(), svc::ResultInvalidState());
|
R_UNLESS(process != GetCurrentProcessPointer(), svc::ResultInvalidState());
|
||||||
|
@ -92,6 +94,9 @@ namespace ams::kern::svc {
|
||||||
|
|
||||||
template<typename EventInfoType>
|
template<typename EventInfoType>
|
||||||
Result GetDebugEvent(KUserPointer<EventInfoType *> out_info, ams::svc::Handle debug_handle) {
|
Result GetDebugEvent(KUserPointer<EventInfoType *> out_info, ams::svc::Handle debug_handle) {
|
||||||
|
/* Only allow invoking the svc on development hardware or if force debug prod. */
|
||||||
|
R_UNLESS(KTargetSystem::IsDebugMode() || GetCurrentProcess().CanForceDebugProd(), svc::ResultNotImplemented());
|
||||||
|
|
||||||
/* Get the debug object. */
|
/* Get the debug object. */
|
||||||
KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject<KDebug>(debug_handle);
|
KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject<KDebug>(debug_handle);
|
||||||
R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle());
|
R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle());
|
||||||
|
@ -164,6 +169,9 @@ namespace ams::kern::svc {
|
||||||
}
|
}
|
||||||
|
|
||||||
Result GetDebugThreadContext(KUserPointer<ams::svc::ThreadContext *> out_context, ams::svc::Handle debug_handle, uint64_t thread_id, uint32_t context_flags) {
|
Result GetDebugThreadContext(KUserPointer<ams::svc::ThreadContext *> out_context, ams::svc::Handle debug_handle, uint64_t thread_id, uint32_t context_flags) {
|
||||||
|
/* Only allow invoking the svc on development hardware or if force debug prod. */
|
||||||
|
R_UNLESS(KTargetSystem::IsDebugMode() || GetCurrentProcess().CanForceDebugProd(), svc::ResultNotImplemented());
|
||||||
|
|
||||||
/* Validate the context flags. */
|
/* Validate the context flags. */
|
||||||
R_UNLESS((context_flags | ams::svc::ThreadContextFlag_All) == ams::svc::ThreadContextFlag_All, svc::ResultInvalidEnumValue());
|
R_UNLESS((context_flags | ams::svc::ThreadContextFlag_All) == ams::svc::ThreadContextFlag_All, svc::ResultInvalidEnumValue());
|
||||||
|
|
||||||
|
@ -220,6 +228,9 @@ namespace ams::kern::svc {
|
||||||
}
|
}
|
||||||
|
|
||||||
Result QueryDebugProcessMemory(ams::svc::MemoryInfo *out_memory_info, ams::svc::PageInfo *out_page_info, ams::svc::Handle debug_handle, uintptr_t address) {
|
Result QueryDebugProcessMemory(ams::svc::MemoryInfo *out_memory_info, ams::svc::PageInfo *out_page_info, ams::svc::Handle debug_handle, uintptr_t address) {
|
||||||
|
/* Only allow invoking the svc on development hardware or if force debug prod. */
|
||||||
|
R_UNLESS(KTargetSystem::IsDebugMode() || GetCurrentProcess().CanForceDebugProd(), svc::ResultNotImplemented());
|
||||||
|
|
||||||
/* Get the debug object. */
|
/* Get the debug object. */
|
||||||
KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject<KDebug>(debug_handle);
|
KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject<KDebug>(debug_handle);
|
||||||
R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle());
|
R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle());
|
||||||
|
@ -261,6 +272,9 @@ namespace ams::kern::svc {
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ReadDebugProcessMemory(uintptr_t buffer, ams::svc::Handle debug_handle, uintptr_t address, size_t size) {
|
Result ReadDebugProcessMemory(uintptr_t buffer, ams::svc::Handle debug_handle, uintptr_t address, size_t size) {
|
||||||
|
/* Only allow invoking the svc on development hardware or if force debug prod. */
|
||||||
|
R_UNLESS(KTargetSystem::IsDebugMode() || GetCurrentProcess().CanForceDebugProd(), svc::ResultNotImplemented());
|
||||||
|
|
||||||
/* Validate address / size. */
|
/* Validate address / size. */
|
||||||
R_UNLESS(size > 0, svc::ResultInvalidSize());
|
R_UNLESS(size > 0, svc::ResultInvalidSize());
|
||||||
R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory());
|
R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory());
|
||||||
|
@ -306,6 +320,9 @@ namespace ams::kern::svc {
|
||||||
}
|
}
|
||||||
|
|
||||||
Result GetDebugThreadParam(uint64_t *out_64, uint32_t *out_32, ams::svc::Handle debug_handle, uint64_t thread_id, ams::svc::DebugThreadParam param) {
|
Result GetDebugThreadParam(uint64_t *out_64, uint32_t *out_32, ams::svc::Handle debug_handle, uint64_t thread_id, ams::svc::DebugThreadParam param) {
|
||||||
|
/* Only allow invoking the svc on development hardware or if force debug prod. */
|
||||||
|
R_UNLESS(KTargetSystem::IsDebugMode() || GetCurrentProcess().CanForceDebugProd(), svc::ResultNotImplemented());
|
||||||
|
|
||||||
/* Get the debug object. */
|
/* Get the debug object. */
|
||||||
KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject<KDebug>(debug_handle);
|
KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject<KDebug>(debug_handle);
|
||||||
R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle());
|
R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle());
|
||||||
|
|
|
@ -71,6 +71,9 @@ namespace ams::kern::svc {
|
||||||
}
|
}
|
||||||
|
|
||||||
Result GetProcessList(int32_t *out_num_processes, KUserPointer<uint64_t *> out_process_ids, int32_t max_out_count) {
|
Result GetProcessList(int32_t *out_num_processes, KUserPointer<uint64_t *> out_process_ids, int32_t max_out_count) {
|
||||||
|
/* Only allow invoking the svc on development hardware. */
|
||||||
|
R_UNLESS(KTargetSystem::IsDebugMode(), svc::ResultNotImplemented());
|
||||||
|
|
||||||
/* Validate that the out count is valid. */
|
/* Validate that the out count is valid. */
|
||||||
R_UNLESS((0 <= max_out_count && max_out_count <= static_cast<int32_t>(std::numeric_limits<int32_t>::max() / sizeof(u64))), svc::ResultOutOfRange());
|
R_UNLESS((0 <= max_out_count && max_out_count <= static_cast<int32_t>(std::numeric_limits<int32_t>::max() / sizeof(u64))), svc::ResultOutOfRange());
|
||||||
|
|
||||||
|
|
|
@ -217,6 +217,9 @@ namespace ams::kern::svc {
|
||||||
}
|
}
|
||||||
|
|
||||||
Result GetThreadList(int32_t *out_num_threads, KUserPointer<uint64_t *> out_thread_ids, int32_t max_out_count, ams::svc::Handle debug_handle) {
|
Result GetThreadList(int32_t *out_num_threads, KUserPointer<uint64_t *> out_thread_ids, int32_t max_out_count, ams::svc::Handle debug_handle) {
|
||||||
|
/* Only allow invoking the svc on development hardware. */
|
||||||
|
R_UNLESS(KTargetSystem::IsDebugMode(), svc::ResultNotImplemented());
|
||||||
|
|
||||||
/* Validate that the out count is valid. */
|
/* Validate that the out count is valid. */
|
||||||
R_UNLESS((0 <= max_out_count && max_out_count <= static_cast<int32_t>(std::numeric_limits<int32_t>::max() / sizeof(u64))), svc::ResultOutOfRange());
|
R_UNLESS((0 <= max_out_count && max_out_count <= static_cast<int32_t>(std::numeric_limits<int32_t>::max() / sizeof(u64))), svc::ResultOutOfRange());
|
||||||
|
|
||||||
|
@ -225,30 +228,34 @@ namespace ams::kern::svc {
|
||||||
R_UNLESS(GetCurrentProcess().GetPageTable().Contains(KProcessAddress(out_thread_ids.GetUnsafePointer()), max_out_count * sizeof(u64)), svc::ResultInvalidCurrentMemory());
|
R_UNLESS(GetCurrentProcess().GetPageTable().Contains(KProcessAddress(out_thread_ids.GetUnsafePointer()), max_out_count * sizeof(u64)), svc::ResultInvalidCurrentMemory());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (debug_handle == ams::svc::InvalidHandle) {
|
/* Get the handle table. */
|
||||||
/* If passed invalid handle, we should return the global thread list. */
|
auto &handle_table = GetCurrentProcess().GetHandleTable();
|
||||||
R_TRY(KThread::GetThreadList(out_num_threads, out_thread_ids, max_out_count));
|
|
||||||
|
/* Try to get as a debug object. */
|
||||||
|
KScopedAutoObject debug = handle_table.GetObject<KDebug>(debug_handle);
|
||||||
|
if (debug.IsNotNull()) {
|
||||||
|
/* Check that the debug object has a process. */
|
||||||
|
R_UNLESS(debug->IsAttached(), svc::ResultProcessTerminated());
|
||||||
|
R_UNLESS(debug->OpenProcess(), svc::ResultProcessTerminated());
|
||||||
|
ON_SCOPE_EXIT { debug->CloseProcess(); };
|
||||||
|
|
||||||
|
/* Get the thread list. */
|
||||||
|
R_TRY(debug->GetProcessUnsafe()->GetThreadList(out_num_threads, out_thread_ids, max_out_count));
|
||||||
} else {
|
} else {
|
||||||
/* Get the handle table. */
|
/* Only allow getting as a process (or global) if the caller does not have ForceDebugProd. */
|
||||||
auto &handle_table = GetCurrentProcess().GetHandleTable();
|
R_UNLESS(!GetCurrentProcess().CanForceDebugProd(), svc::ResultInvalidHandle());
|
||||||
|
|
||||||
/* Try to get as a debug object. */
|
|
||||||
KScopedAutoObject debug = handle_table.GetObject<KDebug>(debug_handle);
|
|
||||||
if (debug.IsNotNull()) {
|
|
||||||
/* Check that the debug object has a process. */
|
|
||||||
R_UNLESS(debug->IsAttached(), svc::ResultProcessTerminated());
|
|
||||||
R_UNLESS(debug->OpenProcess(), svc::ResultProcessTerminated());
|
|
||||||
ON_SCOPE_EXIT { debug->CloseProcess(); };
|
|
||||||
|
|
||||||
/* Get the thread list. */
|
|
||||||
R_TRY(debug->GetProcessUnsafe()->GetThreadList(out_num_threads, out_thread_ids, max_out_count));
|
|
||||||
} else {
|
|
||||||
/* Try to get as a process. */
|
|
||||||
KScopedAutoObject process = handle_table.GetObjectWithoutPseudoHandle<KProcess>(debug_handle);
|
|
||||||
R_UNLESS(process.IsNotNull(), svc::ResultInvalidHandle());
|
|
||||||
|
|
||||||
|
/* Try to get as a process. */
|
||||||
|
KScopedAutoObject process = handle_table.GetObjectWithoutPseudoHandle<KProcess>(debug_handle);
|
||||||
|
if (process.IsNotNull()) {
|
||||||
/* Get the thread list. */
|
/* Get the thread list. */
|
||||||
R_TRY(process->GetThreadList(out_num_threads, out_thread_ids, max_out_count));
|
R_TRY(process->GetThreadList(out_num_threads, out_thread_ids, max_out_count));
|
||||||
|
} else {
|
||||||
|
/* If the object is not a process, the caller may want the global thread list. */
|
||||||
|
R_UNLESS(debug_handle == ams::svc::InvalidHandle, svc::ResultInvalidHandle());
|
||||||
|
|
||||||
|
/* If passed invalid handle, we should return the global thread list. */
|
||||||
|
R_TRY(KThread::GetThreadList(out_num_threads, out_thread_ids, max_out_count));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -110,6 +110,7 @@
|
||||||
"type": "debug_flags",
|
"type": "debug_flags",
|
||||||
"value": {
|
"value": {
|
||||||
"allow_debug": false,
|
"allow_debug": false,
|
||||||
|
"force_debug_prod": false,
|
||||||
"force_debug": true
|
"force_debug": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,6 +105,7 @@
|
||||||
"type": "debug_flags",
|
"type": "debug_flags",
|
||||||
"value": {
|
"value": {
|
||||||
"allow_debug": false,
|
"allow_debug": false,
|
||||||
|
"force_debug_prod": false,
|
||||||
"force_debug": true
|
"force_debug": true
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
|
|
@ -104,6 +104,7 @@
|
||||||
"type": "debug_flags",
|
"type": "debug_flags",
|
||||||
"value": {
|
"value": {
|
||||||
"allow_debug": false,
|
"allow_debug": false,
|
||||||
|
"force_debug_prod": false,
|
||||||
"force_debug": true
|
"force_debug": true
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
|
|
@ -308,10 +308,19 @@ namespace ams::ldr {
|
||||||
);
|
);
|
||||||
|
|
||||||
DEFINE_CAPABILITY_CLASS(DebugFlags,
|
DEFINE_CAPABILITY_CLASS(DebugFlags,
|
||||||
DEFINE_CAPABILITY_FIELD(AllowDebug, IdBits, 1, bool);
|
DEFINE_CAPABILITY_FIELD(AllowDebug, IdBits, 1, bool);
|
||||||
DEFINE_CAPABILITY_FIELD(ForceDebug, AllowDebug, 1, bool);
|
DEFINE_CAPABILITY_FIELD(ForceDebugProd, AllowDebug, 1, bool);
|
||||||
|
DEFINE_CAPABILITY_FIELD(ForceDebug, ForceDebugProd, 1, bool);
|
||||||
|
|
||||||
bool IsValid(const util::BitPack32 *kac, size_t kac_count) const {
|
bool IsValid(const util::BitPack32 *kac, size_t kac_count) const {
|
||||||
|
u32 total = 0;
|
||||||
|
if (this->GetAllowDebug()) { ++total; }
|
||||||
|
if (this->GetForceDebugProd()) { ++total; }
|
||||||
|
if (this->GetForceDebug()) { ++total; }
|
||||||
|
if (total > 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < kac_count; i++) {
|
for (size_t i = 0; i < kac_count; i++) {
|
||||||
if (GetCapabilityId(kac[i]) == Id) {
|
if (GetCapabilityId(kac[i]) == Id) {
|
||||||
const auto restriction = Decode(kac[i]);
|
const auto restriction = Decode(kac[i]);
|
||||||
|
@ -319,12 +328,14 @@ namespace ams::ldr {
|
||||||
return (restriction.GetValue() & this->GetValue()) == this->GetValue();
|
return (restriction.GetValue() & this->GetValue()) == this->GetValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr util::BitPack32 Encode(bool allow_debug, bool force_debug) {
|
static constexpr util::BitPack32 Encode(bool allow_debug, bool force_debug_prod, bool force_debug) {
|
||||||
util::BitPack32 encoded{IdBitsValue};
|
util::BitPack32 encoded{IdBitsValue};
|
||||||
encoded.Set<AllowDebug>(allow_debug);
|
encoded.Set<AllowDebug>(allow_debug);
|
||||||
|
encoded.Set<ForceDebugProd>(force_debug_prod);
|
||||||
encoded.Set<ForceDebug>(force_debug);
|
encoded.Set<ForceDebug>(force_debug);
|
||||||
return encoded;
|
return encoded;
|
||||||
}
|
}
|
||||||
|
@ -406,7 +417,7 @@ namespace ams::ldr {
|
||||||
kac[i] = CapabilityApplicationType::Encode(flags & ProgramInfoFlag_ApplicationTypeMask);
|
kac[i] = CapabilityApplicationType::Encode(flags & ProgramInfoFlag_ApplicationTypeMask);
|
||||||
break;
|
break;
|
||||||
case CapabilityId::DebugFlags:
|
case CapabilityId::DebugFlags:
|
||||||
kac[i] = CapabilityDebugFlags::Encode((flags & ProgramInfoFlag_AllowDebug) != 0, CapabilityDebugFlags::Decode(cap).GetForceDebug());
|
kac[i] = CapabilityDebugFlags::Encode((flags & ProgramInfoFlag_AllowDebug) != 0, CapabilityDebugFlags::Decode(cap).GetForceDebugProd(), CapabilityDebugFlags::Decode(cap).GetForceDebug());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -85,6 +85,7 @@
|
||||||
"type": "debug_flags",
|
"type": "debug_flags",
|
||||||
"value": {
|
"value": {
|
||||||
"allow_debug": false,
|
"allow_debug": false,
|
||||||
|
"force_debug_prod": false,
|
||||||
"force_debug": true
|
"force_debug": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue