Compare commits

...

25 Commits
1.2.6 ... tma2

Author SHA1 Message Date
Michael Scire
fed1518fb5 cs: fix launching of tio server 2021-09-11 19:40:11 -07:00
Michael Scire
a5e761ceca cs: fix screenshot packet semantics 2021-09-11 19:40:11 -07:00
Michael Scire
a4da7cc8bf cs: implement TakeScreenShot command 2021-09-11 19:40:11 -07:00
Michael Scire
fe8d031708 cs: fix allocator aborts 2021-09-11 19:40:10 -07:00
Michael Scire
999318838f kern: improve single-step around user-exception entry 2021-09-11 19:40:10 -07:00
Michael Scire
325ae30067 kern: fix spsr register in RestoreContext 2021-09-11 19:40:10 -07:00
Michael Scire
d7c93c44f4 kern: optimize hw-single-step management 2021-09-11 19:40:07 -07:00
Michael Scire
6139b7d5ac dmnt: use hardware single step extension if available 2021-09-11 19:36:08 -07:00
Michael Scire
ae91a32059 kern: add hardware single step extension 2021-09-11 19:36:07 -07:00
Michael Scire
e34a9ba521 dmnt: implement remaining basic gdbstub packets 2021-09-11 19:36:07 -07:00
Michael Scire
252b890a12 dmnt: reload modules on NRO load/unload 2021-09-11 19:36:07 -07:00
Michael Scire
32818a7a99 dmnt: first pass at breakpoints/watchpoints 2021-09-11 19:36:07 -07:00
Michael Scire
eb6d18329e dmnt: refactor to use process accessor 2021-09-11 19:36:06 -07:00
Michael Scire
cf27c8a7a2 osdbg: implement thread info api 2021-09-11 19:36:06 -07:00
Michael Scire
74ca199c94 dmnt: add attach support to gdbstub 2021-09-11 19:36:06 -07:00
Michael Scire
22bce9f680 dmnt: refactor/add support for getting process list in gdb 2021-09-11 19:36:06 -07:00
Michael Scire
e5a0f0d3b0 dmnt: begin working on packet parser 2021-09-11 19:36:05 -07:00
Michael Scire
6da88a436f dmnt: add basic gdb packet receive logic 2021-09-11 19:36:05 -07:00
Michael Scire
e96d9950d2 dmnt2: add logging logic, for use with gdbstub development 2021-09-11 19:35:59 -07:00
Michael Scire
49afbd8a63 fix dmnt.gen2 title id (not sure how I typo'd this) 2021-09-11 19:35:14 -07:00
Michael Scire
2e0d197cbe boot2: launch dmnt.gen2 over dmnt, when using htc 2021-09-11 19:35:08 -07:00
Michael Scire
e7187ace55 tma2: include sysmodules in stratosphere.romfs 2021-09-11 19:34:04 -07:00
Michael Scire
6794c147dd cs: implement GetFirmwareVersion command 2021-09-11 19:32:33 -07:00
Michael Scire
6ea5cf5f91 scs: implement EventHandlerThread for shell 2021-09-11 19:32:33 -07:00
Michael Scire
6c2f005a62 scs: implement DoShellServer 2021-09-11 19:32:33 -07:00
87 changed files with 7291 additions and 74 deletions

View File

@@ -101,8 +101,8 @@ dist-no-debug: all
cp -r config_templates/kip_patches atmosphere-$(AMSVER)/atmosphere/kip_patches
cp -r config_templates/hbl_html atmosphere-$(AMSVER)/atmosphere/hbl_html
mkdir -p atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000008
mkdir -p atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000008
mkdir -p atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/010000000000000D
mkdir -p atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000017
mkdir -p atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/010000000000002B
mkdir -p atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000032
mkdir -p atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000034
@@ -111,8 +111,11 @@ dist-no-debug: all
mkdir -p atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/010000000000003C
mkdir -p atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000042
mkdir -p atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000420
mkdir -p atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/010000000000B240
mkdir -p atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/010000000000D623
cp stratosphere/boot2/boot2.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000008/exefs.nsp
cp stratosphere/dmnt/dmnt.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/010000000000000D/exefs.nsp
cp stratosphere/cs/cs.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000017/exefs.nsp
cp stratosphere/erpt/erpt.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/010000000000002B/exefs.nsp
cp stratosphere/eclct.stub/eclct.stub.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000032/exefs.nsp
cp stratosphere/fatal/fatal.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000034/exefs.nsp
@@ -121,6 +124,8 @@ dist-no-debug: all
cp stratosphere/jpegdec/jpegdec.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/010000000000003C/exefs.nsp
cp stratosphere/pgl/pgl.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000042/exefs.nsp
cp stratosphere/LogManager/LogManager.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/0100000000000420/exefs.nsp
cp stratosphere/htc/htc.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/010000000000B240/exefs.nsp
cp stratosphere/TioServer/TioServer.nsp atmosphere-$(AMSVER)/stratosphere_romfs/atmosphere/contents/010000000000D623/exefs.nsp
@build_romfs atmosphere-$(AMSVER)/stratosphere_romfs atmosphere-$(AMSVER)/atmosphere/stratosphere.romfs
rm -r atmosphere-$(AMSVER)/stratosphere_romfs
cp troposphere/reboot_to_payload/reboot_to_payload.nro atmosphere-$(AMSVER)/switch/reboot_to_payload.nro

View File

@@ -14,6 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <mesosphere/kern_build_config.hpp>
/* TODO: Different header for this? */
#define AMS_KERN_NUM_SUPERVISOR_CALLS 0xC0
@@ -30,6 +31,10 @@
#define THREAD_STACK_PARAMETERS_IS_IN_EXCEPTION_HANDLER 0x2D
#define THREAD_STACK_PARAMETERS_IS_PINNED 0x2E
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
#define THREAD_STACK_PARAMETERS_IS_SINGLE_STEP 0x2F
#endif
/* ams::kern::arch::arm64::KThreadContext, https://github.com/Atmosphere-NX/Atmosphere/blob/master/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_thread_context.hpp */
#define THREAD_CONTEXT_SIZE 0x290
#define THREAD_CONTEXT_CPU_REGISTERS 0x000

View File

@@ -265,6 +265,10 @@ namespace ams::kern::arch::arm64::cpu {
return this->GetBits(12, 1) != 0;
}
constexpr ALWAYS_INLINE bool GetSoftwareStep() const {
return this->GetBits(0, 1) != 0;
}
constexpr ALWAYS_INLINE decltype(auto) SetMde(bool set) {
this->SetBit(15, set);
return *this;
@@ -274,6 +278,11 @@ namespace ams::kern::arch::arm64::cpu {
this->SetBit(12, set);
return *this;
}
constexpr ALWAYS_INLINE decltype(auto) SetSoftwareStep(bool set) {
this->SetBit(0, set);
return *this;
}
};
MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS(MultiprocessorAffinity) {

View File

@@ -31,3 +31,4 @@
//#define MESOSPHERE_BUILD_FOR_TRACING
#define MESOSPHERE_ENABLE_PANIC_REGISTER_DUMP
#define MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP

View File

@@ -92,6 +92,9 @@ namespace ams::kern {
bool is_calling_svc;
bool is_in_exception_handler;
bool is_pinned;
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
bool is_single_step;
#endif
};
static_assert(alignof(StackParameters) == 0x10);
static_assert(sizeof(StackParameters) == THREAD_STACK_PARAMETERS_SIZE);
@@ -106,6 +109,10 @@ namespace ams::kern {
static_assert(__builtin_offsetof(StackParameters, is_in_exception_handler) == THREAD_STACK_PARAMETERS_IS_IN_EXCEPTION_HANDLER);
static_assert(__builtin_offsetof(StackParameters, is_pinned) == THREAD_STACK_PARAMETERS_IS_PINNED);
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
static_assert(__builtin_offsetof(StackParameters, is_single_step) == THREAD_STACK_PARAMETERS_IS_SINGLE_STEP);
#endif
struct QueueEntry {
private:
KThread *m_prev;
@@ -325,6 +332,25 @@ namespace ams::kern {
return this->GetStackParameters().current_svc_id;
}
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
ALWAYS_INLINE void SetSingleStep() {
MESOSPHERE_ASSERT_THIS();
this->GetStackParameters().is_single_step = true;
}
ALWAYS_INLINE void ClearSingleStep() {
MESOSPHERE_ASSERT_THIS();
this->GetStackParameters().is_single_step = false;
}
ALWAYS_INLINE bool IsSingleStep() const {
MESOSPHERE_ASSERT_THIS();
return this->GetStackParameters().is_single_step;
}
#endif
ALWAYS_INLINE void RegisterDpc(DpcFlag flag) {
this->GetStackParameters().dpc_flags.fetch_or(flag);
}

View File

@@ -109,6 +109,11 @@ namespace ams::kern::arch::arm64 {
break;
}
/* In the event that we return from this exception, we want SPSR.SS set so that we advance an instruction if single-stepping. */
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
context->psr |= (1ul << 21);
#endif
/* If we should process the user exception (and it's not a breakpoint), try to enter. */
const bool is_software_break = (ec == EsrEc_Unknown || ec == EsrEc_IllegalExecution || ec == EsrEc_BkptInstruction || ec == EsrEc_BrkInstruction);
const bool is_breakpoint = (ec == EsrEc_BreakPointEl0 || ec == EsrEc_SoftwareStepEl0 || ec == EsrEc_WatchPointEl0);
@@ -215,6 +220,15 @@ namespace ams::kern::arch::arm64 {
}
}
/* If we should, clear the thread's state as single-step. */
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
if (AMS_UNLIKELY(GetCurrentThread().IsSingleStep())) {
GetCurrentThread().ClearSingleStep();
cpu::MonitorDebugSystemControlRegisterAccessor().SetSoftwareStep(false).Store();
cpu::EnsureInstructionConsistency();
}
#endif
{
/* Collect additional information based on the ec. */
ams::svc::DebugException exception;
@@ -290,16 +304,40 @@ namespace ams::kern::arch::arm64 {
return;
}
/* Print that an exception occurred. */
MESOSPHERE_RELEASE_LOG("Exception occurred. %016lx\n", GetCurrentProcess().GetProgramId());
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
{
/* Print the current thread's registers. */
KDebug::PrintRegister();
if (ec != EsrEc_SoftwareStepEl0) {
/* If the exception wasn't single-step, print details. */
MESOSPHERE_RELEASE_LOG("Exception occurred. %016lx\n", GetCurrentProcess().GetProgramId());
/* Print a backtrace. */
KDebug::PrintBacktrace();
{
/* Print the current thread's registers. */
KDebug::PrintRegister();
/* Print a backtrace. */
KDebug::PrintBacktrace();
}
} else {
/* If the exception was single-step and we have no debug object, we should just return. */
if (AMS_UNLIKELY(!cur_process.IsAttachedToDebugger())) {
return;
}
}
}
#else
{
/* Print that an exception occurred. */
MESOSPHERE_RELEASE_LOG("Exception occurred. %016lx\n", GetCurrentProcess().GetProgramId());
{
/* Print the current thread's registers. */
KDebug::PrintRegister();
/* Print a backtrace. */
KDebug::PrintBacktrace();
}
}
#endif
/* If the SVC is handled, handle it. */
if (!svc::ResultNotHandled::Includes(result)) {
@@ -559,6 +597,7 @@ namespace ams::kern::arch::arm64 {
KDpcManager::HandleDpc();
}
}
/* Note that we're no longer in an exception handler. */
GetCurrentThread().ClearInExceptionHandler();
}

View File

@@ -89,6 +89,12 @@ _ZN3ams4kern3svc14RestoreContextEm:
ldp x30, x8, [sp, #(EXCEPTION_CONTEXT_X30_SP)]
ldp x9, x10, [sp, #(EXCEPTION_CONTEXT_PC_PSR)]
ldr x11, [sp, #(EXCEPTION_CONTEXT_TPIDR)]
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
/* Since we're returning from an exception, set SPSR.SS so that we advance an instruction if single-stepping. */
orr x10, x10, #(1 << 21)
#endif
msr sp_el0, x8
msr elr_el1, x9
msr spsr_el1, x10

View File

@@ -154,6 +154,12 @@ _ZN3ams4kern4arch5arm6412SvcHandler64Ev:
ldp x30, x8, [sp, #(EXCEPTION_CONTEXT_X30_SP)]
ldp x9, x10, [sp, #(EXCEPTION_CONTEXT_PC_PSR)]
ldr x11, [sp, #(EXCEPTION_CONTEXT_TPIDR)]
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
/* Since we're returning from an SVC, make sure SPSR.SS is cleared so that if we're single-stepping we break instantly on the instruction after the SVC. */
bic x10, x10, #(1 << 21)
#endif
msr sp_el0, x8
msr elr_el1, x9
msr spsr_el1, x10
@@ -203,6 +209,12 @@ _ZN3ams4kern4arch5arm6412SvcHandler64Ev:
ldp x9, x10, [sp, #(EXCEPTION_CONTEXT_PC_PSR)]
ldr x11, [sp, #(EXCEPTION_CONTEXT_TPIDR)]
ldr x18, [sp, #(EXCEPTION_CONTEXT_X18)]
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
/* Since we're returning from an SVC, make sure SPSR.SS is cleared so that if we're single-stepping we break instantly on the instruction after the SVC. */
bic x10, x10, #(1 << 21)
#endif
msr sp_el0, x8
msr elr_el1, x9
msr spsr_el1, x10
@@ -359,6 +371,12 @@ _ZN3ams4kern4arch5arm6412SvcHandler32Ev:
/* Restore registers. */
ldp x17, x20, [sp, #(EXCEPTION_CONTEXT_PC_PSR)]
ldr x19, [sp, #(EXCEPTION_CONTEXT_TPIDR)]
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
/* Since we're returning from an SVC, make sure SPSR.SS is cleared so that if we're single-stepping we break instantly on the instruction after the SVC. */
bic x20, x20, #(1 << 21)
#endif
msr elr_el1, x17
msr spsr_el1, x20
msr tpidr_el0, x19
@@ -402,6 +420,12 @@ _ZN3ams4kern4arch5arm6412SvcHandler32Ev:
ldp x14, xzr, [sp, #(EXCEPTION_CONTEXT_X14_X15)]
ldp x17, x20, [sp, #(EXCEPTION_CONTEXT_PC_PSR)]
ldr x19, [sp, #(EXCEPTION_CONTEXT_TPIDR)]
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
/* Since we're returning from an SVC, make sure SPSR.SS is cleared so that if we're single-stepping we break instantly on the instruction after the SVC. */
bic x20, x20, #(1 << 21)
#endif
msr elr_el1, x17
msr spsr_el1, x20
msr tpidr_el0, x19

View File

@@ -372,6 +372,16 @@ namespace ams::kern {
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->ClearSingleStep();
}
}
#endif
/* Detach from the process. */
target->ClearDebugObject(new_state);
m_process = nullptr;
@@ -479,6 +489,25 @@ namespace ams::kern {
}
}
/* Update thread single-step state. */
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
{
if ((context_flags & ams::svc::ThreadContextFlag_SetSingleStep) != 0) {
/* Set single step. */
thread->SetSingleStep();
/* If no other thread flags are present, we're done. */
R_SUCCEED_IF((context_flags & ~ams::svc::ThreadContextFlag_SetSingleStep) == 0);
} else if ((context_flags & ams::svc::ThreadContextFlag_ClearSingleStep) != 0) {
/* Clear single step. */
thread->ClearSingleStep();
/* If no other thread flags are present, we're done. */
R_SUCCEED_IF((context_flags & ~ams::svc::ThreadContextFlag_ClearSingleStep) == 0);
}
}
#endif
/* Verify that the thread's svc state is valid. */
if (thread->IsCallingSvc()) {
const u8 svc_id = thread->GetSvcId();
@@ -873,6 +902,11 @@ namespace ams::kern {
{
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);
@@ -960,7 +994,7 @@ namespace ams::kern {
/* Set the process as breaked. */
process->SetDebugBreak();
/* If the event is an exception, set the result. */
/* If the event is an exception, set the result and clear single step. */
if (event == ams::svc::DebugEvent_Exception) {
GetCurrentThread().SetDebugExceptionResult(ResultSuccess());
}

View File

@@ -186,7 +186,24 @@ namespace ams::kern::svc {
R_UNLESS(KTargetSystem::IsDebugMode(), svc::ResultNotImplemented());
/* Validate the context flags. */
R_UNLESS((context_flags | ams::svc::ThreadContextFlag_All) == ams::svc::ThreadContextFlag_All, svc::ResultInvalidEnumValue());
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
{
/* Check that the flags are a subset of the allowable. */
constexpr u32 AllFlagsMask = ams::svc::ThreadContextFlag_All | ams::svc::ThreadContextFlag_SetSingleStep | ams::svc::ThreadContextFlag_ClearSingleStep;
R_UNLESS((context_flags | AllFlagsMask) == AllFlagsMask, svc::ResultInvalidEnumValue());
/* Check that thread isn't both setting and clearing single step. */
const bool set_ss = (context_flags & ams::svc::ThreadContextFlag_SetSingleStep) != 0;
const bool clear_ss = (context_flags & ams::svc::ThreadContextFlag_ClearSingleStep) != 0;
R_UNLESS(!(set_ss && clear_ss), svc::ResultInvalidEnumValue());
}
#else
{
/* Check that the flags are a subset of the allowable. */
R_UNLESS((context_flags | ams::svc::ThreadContextFlag_All) == ams::svc::ThreadContextFlag_All, svc::ResultInvalidEnumValue());
}
#endif
/* Copy the thread context from userspace. */
ams::svc::ThreadContext context;

View File

@@ -271,6 +271,16 @@ namespace ams::kern::svc {
*out = KTraceValue;
}
break;
case ams::svc::MesosphereMetaInfo_IsSingleStepEnabled:
{
/* Return whether the kernel supports hardware single step. */
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
*out = 1;
#else
*out = 0;
#endif
}
break;
default:
return svc::ResultInvalidCombination();
}

View File

@@ -71,6 +71,7 @@
#include <stratosphere/nim.hpp>
#include <stratosphere/ns.hpp>
#include <stratosphere/nsd.hpp>
#include <stratosphere/osdbg.hpp>
#include <stratosphere/patcher.hpp>
#include <stratosphere/pcv.hpp>
#include <stratosphere/pgl.hpp>

View File

@@ -25,6 +25,10 @@ namespace ams::capsrv {
Result InitializeScreenShotControl();
void FinalizeScreenShotControl();
Result OpenRawScreenShotReadStreamForDevelop(size_t *out_data_size, s32 *out_width, s32 *out_height, vi::LayerStack layer_stack, TimeSpan timeout);
Result ReadRawScreenShotReadStreamForDevelop(size_t *out_read_size, void *dst, size_t dst_size, std::ptrdiff_t offset);
void CloseRawScreenShotReadStreamForDevelop();
Result CaptureJpegScreenshot(u64 *out_size, void *dst, size_t dst_size, vi::LayerStack layer_stack, TimeSpan timeout);
}

View File

@@ -16,6 +16,7 @@
#pragma once
#include <vapours.hpp>
#include <stratosphere/scs/scs_command_processor.hpp>
#include <stratosphere/vi/vi_layer_stack.hpp>
namespace ams::cs {
@@ -25,6 +26,10 @@ namespace ams::cs {
class CommandProcessor : public scs::CommandProcessor {
public:
virtual bool ProcessCommand(const CommandHeader &header, const u8 *body, s32 socket) override;
private:
void TakeScreenShot(const CommandHeader &header, s32 socket, vi::LayerStack layer_stack);
private:
static void SendFirmwareVersion(s32 socket, const CommandHeader &header);
};
}

View File

@@ -22,4 +22,10 @@ namespace ams::htc::tenv {
char str[0x40];
};
constexpr inline auto PathLengthMax = 0x300;
struct alignas(4) Path {
char str[PathLengthMax];
};
}

View File

@@ -101,6 +101,7 @@ namespace ams::ncm {
static const SystemProgramId Manu;
static const SystemProgramId Htc;
static const SystemProgramId DmntGen2;
static const SystemProgramId DevServer;
};
@@ -205,6 +206,7 @@ namespace ams::ncm {
inline constexpr const SystemProgramId SystemProgramId::Manu = { 0x010000000000B14Aul };
inline constexpr const SystemProgramId SystemProgramId::Htc = { 0x010000000000B240ul };
inline constexpr const SystemProgramId SystemProgramId::DmntGen2 = { 0x010000000000D609ul };
inline constexpr const SystemProgramId SystemProgramId::DevServer = { 0x010000000000D623ul };
inline constexpr bool IsSystemProgramId(const ProgramId &program_id) {

View File

@@ -36,6 +36,8 @@ namespace ams::os {
using ThreadImpl = ::Thread;
struct ThreadType {
static constexpr u16 Magic = 0xF5A5;
enum State {
State_NotInitialized = 0,
State_Initialized = 1,
@@ -49,7 +51,9 @@ namespace ams::os {
uintptr_t reserved[4];
u8 state;
u8 suspend_count;
s32 base_priority;
u16 magic;
s16 base_priority;
u16 version;
char name_buffer[ThreadNameLengthMax];
const char *name_pointer;
ThreadId thread_id;

View File

@@ -0,0 +1,19 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere/osdbg/osdbg_thread.hpp>

View File

@@ -0,0 +1,19 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere/osdbg/osdbg_thread_types.hpp>
#include <stratosphere/osdbg/osdbg_thread_api.hpp>

View File

@@ -0,0 +1,29 @@
/*
* 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/>.
*/
#pragma once
#include <vapours.hpp>
#include <stratosphere/osdbg/osdbg_thread_api_impl.hpp>
namespace ams::osdbg {
struct ThreadInfo;
Result InitializeThreadInfo(ThreadInfo *thread_info, svc::Handle debug_handle, const svc::DebugInfoCreateProcess *create_process, const svc::DebugInfoCreateThread *create_thread);
Result UpdateThreadInfo(ThreadInfo *thread_info);
Result GetThreadName(char *dst, const ThreadInfo *thread_info);
}

View File

@@ -0,0 +1,50 @@
/*
* 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/>.
*/
#pragma once
#include <vapours.hpp>
#include <stratosphere/osdbg/osdbg_thread_types.hpp>
namespace ams::osdbg {
constexpr inline s32 GetThreadPriority(const ThreadInfo *thread_info) {
return thread_info->_base_priority;
}
constexpr inline s32 GetThreadCurrentPriority(const ThreadInfo *thread_info) {
return thread_info->_current_priority;
}
constexpr inline size_t GetThreadStackSize(const ThreadInfo *thread_info) {
return thread_info->_stack_size;
}
constexpr inline uintptr_t GetThreadStackAddress(const ThreadInfo *thread_info) {
return thread_info->_stack;
}
constexpr inline uintptr_t GetThreadFunction(const ThreadInfo *thread_info) {
return thread_info->_function;
}
constexpr inline uintptr_t GetThreadFunctionArgument(const ThreadInfo *thread_info) {
return thread_info->_argument;
}
constexpr inline uintptr_t GetThreadNamePointer(const ThreadInfo *thread_info) {
return thread_info->_name_pointer;
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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/>.
*/
#pragma once
#include <vapours.hpp>
namespace ams::osdbg {
namespace impl {
union ThreadTypeCommon;
}
enum ThreadTypeType : u8 {
ThreadTypeType_Unknown = 0,
ThreadTypeType_Nintendo,
ThreadTypeType_Stratosphere,
ThreadTypeType_Libnx,
};
struct ThreadInfo {
s32 _base_priority;
s32 _current_priority;
size_t _stack_size;
uintptr_t _stack;
uintptr_t _argument;
uintptr_t _function;
uintptr_t _name_pointer;
impl::ThreadTypeCommon *_thread_type;
svc::Handle _debug_handle;
ThreadTypeType _thread_type_type;
svc::DebugInfoCreateProcess _debug_info_create_process;
svc::DebugInfoCreateThread _debug_info_create_thread;
};
}

View File

@@ -84,11 +84,17 @@ namespace ams::pgl {
class EventObserver {
NON_COPYABLE(EventObserver);
private:
std::unique_ptr<impl::EventObserverInterface> m_impl;
struct Deleter {
void operator()(impl::EventObserverInterface *);
};
public:
using UniquePtr = std::unique_ptr<impl::EventObserverInterface, Deleter>;
private:
UniquePtr m_impl;
public:
EventObserver() { /* ... */ }
explicit EventObserver(std::unique_ptr<impl::EventObserverInterface> impl) : m_impl(std::move(impl)) { /* ... */ }
explicit EventObserver(UniquePtr impl) : m_impl(std::move(impl)) { /* ... */ }
EventObserver(EventObserver &&rhs) {
m_impl = std::move(rhs.m_impl);

View File

@@ -96,6 +96,27 @@ namespace ams::pm {
struct ProcessEventInfo {
u32 event;
os::ProcessId process_id;
inline ProcessEvent GetProcessEvent() const {
if (hos::GetVersion() >= hos::Version_5_0_0) {
return static_cast<ProcessEvent>(this->event);
}
switch (static_cast<ProcessEventDeprecated>(event)) {
case ProcessEventDeprecated::None:
return ProcessEvent::None;
case ProcessEventDeprecated::Exited:
return ProcessEvent::Exited;
case ProcessEventDeprecated::Started:
return ProcessEvent::Started;
case ProcessEventDeprecated::Exception:
return ProcessEvent::Exception;
case ProcessEventDeprecated::DebugRunning:
return ProcessEvent::DebugRunning;
case ProcessEventDeprecated::DebugBreak:
return ProcessEvent::DebugBreak;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
};
static_assert(sizeof(ProcessEventInfo) == 0x10 && util::is_pod<ProcessEventInfo>::value, "ProcessEventInfo definition!");

View File

@@ -18,17 +18,21 @@
namespace ams::scs {
struct CommandHeader {
u64 id;
struct alignas(alignof(u32)) CommandHeader {
u64 id __attribute__((packed));
u32 command;
u32 body_size;
};
static_assert(sizeof(CommandHeader) == 0x10);
static_assert(alignof(CommandHeader) == alignof(u32));
struct ResponseHeader {
u64 id;
struct alignas(alignof(u32)) ResponseHeader {
u64 id __attribute__((packed));
u32 response;
u32 body_size;
};
static_assert(sizeof(ResponseHeader) == 0x10);
static_assert(alignof(ResponseHeader) == alignof(u32));
class CommandProcessor {
protected:
@@ -56,10 +60,15 @@ namespace ams::scs {
};
enum Response {
Response_None = 0,
Response_Success = 1,
Response_Error = 2,
/* ... */
Response_None = 0,
Response_Success = 1,
Response_Error = 2,
Response_ProgramExited = 3,
Response_FirmwareVersion = 4,
Response_JitDebug = 5,
Response_ProgramLaunched = 6,
Response_TitleName = 7,
Response_ScreenShot = 8,
};
public:
constexpr CommandProcessor() = default;
@@ -68,16 +77,20 @@ namespace ams::scs {
public:
virtual bool ProcessCommand(const CommandHeader &header, const u8 *body, s32 socket);
protected:
static std::scoped_lock<os::SdkMutex> MakeSendGuardBlock();
static void Send(s32 socket, const void *data, size_t size);
static void SendSuccess(s32 socket, const CommandHeader &header);
static void SendErrorResult(s32 socket, const CommandHeader &header, Result result);
private:
static void SendErrorResult(s32 socket, u64 id, Result result);
static void SendExited(s32 socket, u64 id, u64 process_id);
static void SendJitDebug(s32 socket, u64 id);
static void SendLaunched(s32 socket, u64 id, u64 process_id);
static void OnProcessStart(u64 id, s32 socket, os::ProcessId process_id);
static void OnProcessExit(u64 id, s32 socket, os::ProcessId process_id);
static void OnProcessJitDebug(u64 id, s32 socket, os::ProcessId process_id);
};
os::SdkMutex &GetHtcsSendMutex();
}

View File

@@ -26,10 +26,14 @@ namespace ams::scs {
void RegisterCommonProcessEventHandler(ProcessEventHandler on_start, ProcessEventHandler on_exit, ProcessEventHandler on_jit_debug);
bool RegisterSocket(s32 socket);
Result RegisterSocket(s32 socket, u64 id);
void UnregisterSocket(s32 socket);
Result LaunchProgram(os::ProcessId *out, ncm::ProgramId program_id, const void *args, size_t args_size, u32 process_flags);
Result LaunchProgram(os::ProcessId *out, const ncm::ProgramLocation &loc, const void *args, size_t args_size, u32 process_flags);
inline Result LaunchProgram(os::ProcessId *out, ncm::ProgramId program_id, const void *args, size_t args_size, u32 process_flags) {
return LaunchProgram(out, ncm::ProgramLocation::Make(program_id, ncm::StorageId::BuiltInSystem), args, args_size, process_flags);
}
Result SubscribeProcessEvent(s32 socket, bool is_register, u64 id);

View File

@@ -395,18 +395,17 @@ namespace ams::boot2 {
pm::bm::SetMaintenanceBoot();
}
/* Launch Atmosphere dmnt, using NcmStorageId_None to force SD card boot. */
LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Dmnt, ncm::StorageId::None), 0);
/* Check for and forward declare non-atmosphere mitm modules. */
DetectAndDeclareFutureMitms();
/* Decide whether to launch tma or htc. */
if (IsHtcEnabled()) {
LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Htc, ncm::StorageId::None), 0);
LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Cs, ncm::StorageId::None), 0);
LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Htc, ncm::StorageId::None), 0);
LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Cs, ncm::StorageId::None), 0);
LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::DmntGen2, ncm::StorageId::None), 0);
} else {
LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Tma, ncm::StorageId::BuiltInSystem), 0);
LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Dmnt, ncm::StorageId::None), 0);
LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Tma, ncm::StorageId::BuiltInSystem), 0);
}
/* Decide whether to launch atmosphere or nintendo's log manager. */

View File

@@ -29,4 +29,27 @@ namespace ams::capsrv {
return ::capsscCaptureJpegScreenShot(out_size, dst, dst_size, static_cast<::ViLayerStack>(layer_stack), timeout.GetNanoSeconds());
}
Result OpenRawScreenShotReadStreamForDevelop(size_t *out_data_size, s32 *out_width, s32 *out_height, vi::LayerStack layer_stack, TimeSpan timeout) {
u64 data_size, width, height;
R_TRY(::capsscOpenRawScreenShotReadStream(std::addressof(data_size), std::addressof(width), std::addressof(height), static_cast<::ViLayerStack>(layer_stack), timeout.GetNanoSeconds()));
*out_data_size = static_cast<size_t>(data_size);
*out_width = static_cast<s32>(width);
*out_height = static_cast<s32>(height);
return ResultSuccess();
}
Result ReadRawScreenShotReadStreamForDevelop(size_t *out_read_size, void *dst, size_t dst_size, std::ptrdiff_t offset) {
u64 read_size;
R_TRY(::capsscReadRawScreenShotReadStream(std::addressof(read_size), dst, dst_size, static_cast<u64>(offset)));
*out_read_size = static_cast<size_t>(read_size);
return ResultSuccess();
}
void CloseRawScreenShotReadStreamForDevelop() {
::capsscCloseRawScreenShotReadStream();
}
}

View File

@@ -0,0 +1,82 @@
/*
* 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 <stratosphere.hpp>
#include "cs_command_impl.hpp"
namespace ams::cs {
namespace {
void SendEmptyData(const CommandDataTakeScreenShot &params, size_t remaining_size) {
/* Clear the data buffer. */
std::memset(params.buffer, 0, params.buffer_size);
/* Send data until the end. */
while (remaining_size > 0) {
/* Send as much as we can. */
const auto cur_size = std::min(remaining_size, params.buffer_size);
params.send_data(params.buffer, cur_size);
/* Advance. */
remaining_size -= cur_size;
}
}
}
Result DoGetFirmwareVersionCommand(settings::system::FirmwareVersion *out) {
settings::system::GetFirmwareVersion(out);
return ResultSuccess();
}
Result DoTakeScreenShotCommand(const CommandDataTakeScreenShot &params) {
/* Initialize screenshot control. */
R_TRY(capsrv::InitializeScreenShotControl());
/* Finalize screenshot control when we're done. */
ON_SCOPE_EXIT { capsrv::FinalizeScreenShotControl(); };
/* Open screenshot read stream. */
size_t data_size;
s32 width, height;
R_TRY(capsrv::OpenRawScreenShotReadStreamForDevelop(std::addressof(data_size), std::addressof(width), std::addressof(height), params.layer_stack, TimeSpan::FromSeconds(10)));
/* Close the screenshot stream when we're done. */
ON_SCOPE_EXIT { capsrv::CloseRawScreenShotReadStreamForDevelop(); };
/* Send the header. */
params.send_header(static_cast<s32>(data_size), width, height);
/* Read and send data. */
size_t total_read_size = 0;
auto data_guard = SCOPE_GUARD { SendEmptyData(params, data_size - total_read_size); };
while (total_read_size < data_size) {
/* Read data from the stream. */
size_t read_size;
R_TRY(capsrv::ReadRawScreenShotReadStreamForDevelop(std::addressof(read_size), params.buffer, params.buffer_size, total_read_size));
/* Send the data that was read. */
params.send_data(params.buffer, read_size);
/* Advance. */
total_read_size += read_size;
}
data_guard.Cancel();
return ResultSuccess();
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::cs {
struct CommandDataTakeScreenShot {
vi::LayerStack layer_stack;
std::function<void (s32, s32, s32)> send_header;
std::function<void (u8 *, size_t)> send_data;
u8 *buffer;
size_t buffer_size;
};
Result DoGetFirmwareVersionCommand(settings::system::FirmwareVersion *out);
Result DoTakeScreenShotCommand(const CommandDataTakeScreenShot &params);
}

View File

@@ -14,11 +14,39 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "cs_command_impl.hpp"
namespace ams::cs {
namespace {
struct ResponseFirmwareVersion {
ResponseHeader header;
settings::system::FirmwareVersion firmware_version;
};
struct ResponseTakeScreenShot {
ResponseHeader header;
s32 data_size;
s32 width;
s32 height;
};
constinit u8 g_data[0x1000];
}
bool CommandProcessor::ProcessCommand(const CommandHeader &header, const u8 *body, s32 socket) {
switch (header.command) {
case Command_GetFirmwareVersion:
SendFirmwareVersion(socket, header);
break;
case Command_TakeScreenShot:
this->TakeScreenShot(header, socket, vi::LayerStack_ApplicationForDebug);
break;
case Command_TakeForegroundScreenShot:
this->TakeScreenShot(header, socket, vi::LayerStack_LastFrame);
break;
/* TODO: Command support. */
default:
scs::CommandProcessor::ProcessCommand(header, body, socket);
@@ -28,4 +56,73 @@ namespace ams::cs {
return true;
}
void CommandProcessor::SendFirmwareVersion(s32 socket, const CommandHeader &header) {
/* Build the response. */
ResponseFirmwareVersion response = {
.header = {
.id = header.id,
.response = Response_FirmwareVersion,
.body_size = sizeof(response) - sizeof(response.header),
},
.firmware_version = {},
};
/* Get the firmware version. */
const Result result = DoGetFirmwareVersionCommand(std::addressof(response.firmware_version));
if (R_SUCCEEDED(result)) {
/* Send the response. */
auto lk = MakeSendGuardBlock();
Send(socket, std::addressof(response), sizeof(response));
} else {
SendErrorResult(socket, header, result);
}
}
void CommandProcessor::TakeScreenShot(const CommandHeader &header, s32 socket, vi::LayerStack layer_stack) {
/* Create the command data. */
const CommandDataTakeScreenShot params = {
.layer_stack = layer_stack,
.send_header = [&](s32 data_size, s32 width, s32 height) {
/* Use global buffer for response. */
ResponseTakeScreenShot *response = reinterpret_cast<ResponseTakeScreenShot *>(g_data);
/* Set response header. */
*response = {
.header = {
.id = header.id,
.response = Response_ScreenShot,
.body_size = static_cast<u32>(sizeof(data_size) + sizeof(width) + sizeof(height) + data_size),
},
.data_size = data_size,
.width = width,
.height = height,
};
/* Send data. */
Send(socket, response, sizeof(*response));
},
.send_data = [&](u8 *data, size_t data_size) {
/* Send data. */
Send(socket, data, data_size);
},
.buffer = g_data,
.buffer_size = sizeof(g_data),
};
/* Take the screenshot. */
Result result;
{
/* Acquire the send lock. */
auto lk = MakeSendGuardBlock();
/* Perform the command. */
result = DoTakeScreenShotCommand(params);
}
/* Handle the error case. */
if (R_FAILED(result)) {
SendErrorResult(socket, header, result);
}
}
}

View File

@@ -20,7 +20,7 @@ namespace ams::cs {
void InitializeTargetIoServer() {
/* Launch target io server. */
os::ProcessId process_id;
scs::LaunchProgram(std::addressof(process_id), ncm::SystemProgramId::DevServer, nullptr, 0, 0);
scs::LaunchProgram(std::addressof(process_id), ncm::ProgramLocation::Make(ncm::SystemProgramId::DevServer, ncm::StorageId::None), nullptr, 0, 0);
}
}

View File

@@ -15,6 +15,7 @@
*/
#include <stratosphere.hpp>
#include "impl/htc_tenv_allocator.hpp"
#include "impl/htc_tenv_impl.hpp"
namespace ams::htc::tenv {
@@ -23,4 +24,8 @@ namespace ams::htc::tenv {
impl::InitializeAllocator(allocate, deallocate);
}
void UnregisterDefinitionFilePath(os::ProcessId process_id) {
return impl::UnregisterDefinitionFilePath(process_id.value);
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "htc_tenv_allocator.hpp"
namespace ams::htc::tenv::impl {
struct DefinitionFileInfo : public util::IntrusiveListBaseNode<DefinitionFileInfo> {
u64 process_id;
Path path;
DefinitionFileInfo(u64 pid, Path *p) : process_id(pid) {
AMS_ASSERT(p != nullptr);
util::Strlcpy(this->path.str, p->str, PathLengthMax);
}
static void *operator new(size_t size) {
return Allocate(size);
}
static void operator delete(void *p, size_t size) {
Deallocate(p, size);
}
};
}

View File

@@ -0,0 +1,81 @@
/*
* 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 <stratosphere.hpp>
#include "htc_tenv_impl.hpp"
#include "htc_tenv_definition_file_info.hpp"
namespace ams::htc::tenv::impl {
namespace {
class DefinitionFileInfoManager {
private:
using DefinitionFileInfoList = util::IntrusiveListBaseTraits<DefinitionFileInfo>::ListType;
private:
DefinitionFileInfoList m_list;
os::SdkMutex m_mutex;
public:
constexpr DefinitionFileInfoManager() = default;
~DefinitionFileInfoManager() {
while (!m_list.empty()) {
auto *p = std::addressof(*m_list.rbegin());
m_list.erase(m_list.iterator_to(*p));
delete p;
}
}
void Remove(DefinitionFileInfo *info) {
std::scoped_lock lk(m_mutex);
m_list.erase(m_list.iterator_to(*info));
delete info;
}
DefinitionFileInfo *GetInfo(u64 process_id) {
std::scoped_lock lk(m_mutex);
for (auto &info : m_list) {
if (info.process_id == process_id) {
return std::addressof(info);
}
}
return nullptr;
}
};
constinit DefinitionFileInfoManager g_definition_file_info_manager;
ALWAYS_INLINE DefinitionFileInfoManager &GetDefinitionFileInfoManager() {
return g_definition_file_info_manager;
}
}
void UnregisterDefinitionFilePath(u64 process_id) {
/* Require the process id to be valid. */
if (process_id == 0) {
return;
}
/* Remove the definition file info, if we have one. */
if (auto *info = GetDefinitionFileInfoManager().GetInfo(process_id); info != nullptr) {
GetDefinitionFileInfoManager().Remove(info);
}
}
}

View File

@@ -0,0 +1,23 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::htc::tenv::impl {
void UnregisterDefinitionFilePath(u64 process_id);
}

View File

@@ -31,6 +31,8 @@ namespace ams::os::impl {
util::ConstructAt(thread->waitlist);
/* Set member variables. */
thread->magic = os::ThreadType::Magic;
thread->version = 0;
thread->thread_impl = (thread_impl != nullptr) ? thread_impl : std::addressof(thread->thread_impl_storage);
thread->function = function;
thread->argument = arg;
@@ -134,6 +136,7 @@ namespace ams::os::impl {
util::DestroyAt(thread->waitlist);
thread->name_buffer[0] = '\x00';
thread->magic = 0xCCCC;
{
std::scoped_lock tlk(this->cs);

View File

@@ -0,0 +1,176 @@
/*
* 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 <stratosphere.hpp>
#include "osdbg_thread_info.os.horizon.hpp"
#include "osdbg_thread_type.os.horizon.hpp"
#include "osdbg_thread_local_region.os.horizon.hpp"
#include "../../os/impl/os_thread_manager_impl.os.horizon.hpp"
namespace ams::osdbg::impl {
namespace {
s32 ConvertToUserPriority(s32 horizon_priority) {
return horizon_priority - os::impl::UserThreadPriorityOffset;
}
s32 GetCurrentThreadPriorityImpl(const ThreadInfo *info) {
u64 dummy;
u32 horizon_priority;
if (R_FAILED(svc::GetDebugThreadParam(std::addressof(dummy), std::addressof(horizon_priority), info->_debug_handle, info->_debug_info_create_thread.thread_id, svc::DebugThreadParam_Priority))) {
return info->_base_priority;
}
return ConvertToUserPriority(static_cast<s32>(horizon_priority));
}
void FillWithCurrentInfoImpl(ThreadInfo *info, const auto &thread_type_impl) {
/* Set fields. */
info->_base_priority = thread_type_impl._base_priority;
info->_current_priority = GetCurrentThreadPriorityImpl(info);
info->_stack_size = thread_type_impl._stack_size;
info->_stack = thread_type_impl._stack;
info->_argument = thread_type_impl._argument;
info->_function = thread_type_impl._thread_function;
info->_name_pointer = thread_type_impl._name_pointer;
}
}
Result ThreadInfoHorizonImpl::FillWithCurrentInfo(ThreadInfo *info) {
/* Detect lp64. */
const bool is_lp64 = IsLp64(info);
/* Ensure that we have a thread type. */
if (info->_thread_type == nullptr) {
/* Ensure we exit with correct thread type. */
auto thread_guard = SCOPE_GUARD { info->_thread_type = nullptr; };
/* Set the target thread type. */
GetTargetThreadType(info);
/* If it's still nullptr, we failed to get the thread type. */
R_UNLESS(info->_thread_type != nullptr, osdbg::ResultCannotGetThreadInfo());
/* Check that the thread type is valid. */
R_UNLESS(info->_thread_type_type != ThreadTypeType_Unknown, osdbg::ResultUnsupportedThreadVersion());
/* We successfully got the thread type. */
thread_guard.Cancel();
}
/* Read and process the thread type. */
ThreadTypeCommon thread_type;
switch (info->_thread_type_type) {
case ThreadTypeType_Nintendo:
if (is_lp64) {
/* Read in the thread type. */
R_TRY(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(thread_type.lp64)), info->_debug_handle, reinterpret_cast<uintptr_t>(info->_thread_type), sizeof(thread_type.lp64)));
/* Process different versions. */
switch (thread_type.lp64._version) {
case 0x0000:
case 0xFFFF:
FillWithCurrentInfoImpl(info, thread_type.lp64_v0);
break;
case 0x0001:
FillWithCurrentInfoImpl(info, thread_type.lp64);
break;
default:
return osdbg::ResultUnsupportedThreadVersion();
}
} else {
/* Read in the thread type. */
R_TRY(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(thread_type.ilp32)), info->_debug_handle, reinterpret_cast<uintptr_t>(info->_thread_type), sizeof(thread_type.ilp32)));
/* Process different versions. */
switch (thread_type.ilp32._version) {
case 0x0000:
case 0xFFFF:
FillWithCurrentInfoImpl(info, thread_type.ilp32_v0);
break;
case 0x0001:
FillWithCurrentInfoImpl(info, thread_type.ilp32);
break;
default:
return osdbg::ResultUnsupportedThreadVersion();
}
}
break;
case ThreadTypeType_Stratosphere:
{
/* Read in the thread type. */
R_TRY(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(thread_type.stratosphere)), info->_debug_handle, reinterpret_cast<uintptr_t>(info->_thread_type), sizeof(thread_type.stratosphere)));
/* Set fields. */
const auto &thread_type_impl = thread_type.stratosphere;
/* Check that our thread version is valid. */
R_UNLESS(thread_type_impl.version == 0x0000 || thread_type_impl.version == 0xFFFF, osdbg::ResultUnsupportedThreadVersion());
info->_base_priority = thread_type_impl.base_priority;
info->_current_priority = GetCurrentThreadPriorityImpl(info);
info->_stack_size = thread_type_impl.stack_size;
info->_stack = reinterpret_cast<uintptr_t>(thread_type_impl.stack);
info->_argument = reinterpret_cast<uintptr_t>(thread_type_impl.argument);
info->_function = reinterpret_cast<uintptr_t>(thread_type_impl.function);
info->_name_pointer = reinterpret_cast<uintptr_t>(thread_type_impl.name_pointer);
}
break;
case ThreadTypeType_Libnx:
{
/* Read in the thread type. */
R_TRY(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(thread_type.libnx)), info->_debug_handle, reinterpret_cast<uintptr_t>(info->_thread_type), sizeof(thread_type.libnx)));
/* Set fields. */
const auto &thread_type_impl = thread_type.libnx;
/* NOTE: libnx does not store/track base priority anywhere. */
info->_base_priority = -1;
info->_current_priority = GetCurrentThreadPriorityImpl(info);
if (info->_current_priority != info->_base_priority) {
info->_base_priority = info->_current_priority;
}
info->_stack_size = thread_type_impl.stack_sz;
info->_stack = reinterpret_cast<uintptr_t>(thread_type_impl.stack_mirror);
/* Parse thread entry args. */
{
LibnxThreadEntryArgs thread_entry_args;
if (R_SUCCEEDED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(thread_entry_args)), info->_debug_handle, info->_stack + info->_stack_size, sizeof(LibnxThreadEntryArgs)))) {
info->_argument = thread_entry_args.arg;
info->_function = thread_entry_args.entry;
} else {
/* Failed to read the argument/function. */
info->_argument = 0;
info->_function = 0;
}
}
/* Libnx threads don't have names. */
info->_name_pointer = 0;
}
break;
default:
return osdbg::ResultUnsupportedThreadVersion();
}
return ResultSuccess();
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "osdbg_types.hpp"
namespace ams::osdbg::impl {
class ThreadInfoHorizonImpl {
public:
static Result FillWithCurrentInfo(ThreadInfo *info);
};
using ThreadInfoImpl = ThreadInfoHorizonImpl;
constexpr inline bool IsLp64(const ThreadInfo *info) {
const auto as = info->_debug_info_create_process.flags & svc::CreateProcessFlag_AddressSpaceMask;
return as == svc::CreateProcessFlag_AddressSpace64Bit || as == svc::CreateProcessFlag_AddressSpace64BitDeprecated;
}
constexpr inline bool Is64BitArch(const ThreadInfo *info) {
return (info->_debug_info_create_process.flags & svc::CreateProcessFlag_Is64Bit);
}
}

View File

@@ -0,0 +1,142 @@
/*
* 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 <stratosphere.hpp>
#include "osdbg_thread_info.os.horizon.hpp"
#include "osdbg_thread_type.os.horizon.hpp"
#include "osdbg_thread_local_region.os.horizon.hpp"
namespace ams::osdbg::impl {
namespace {
Result GetThreadTypePointerFromThreadLocalRegion(uintptr_t *out, ThreadInfo *info) {
/* Detect lp64. */
const bool is_lp64 = IsLp64(info);
/* Get the thread local region. */
const auto *tlr_address = GetTargetThreadLocalRegion(info);
/* Read the thread local region. */
ThreadLocalRegionCommon tlr;
R_TRY(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(tlr)), info->_debug_handle, reinterpret_cast<uintptr_t>(tlr_address), sizeof(tlr)));
/* Detect libnx vs nintendo via magic number. */
if (tlr.libnx.thread_vars.magic == LibnxThreadVars::Magic) {
info->_thread_type_type = ThreadTypeType_Libnx;
*out = reinterpret_cast<uintptr_t>(tlr.libnx.thread_vars.thread_ptr);
} else {
info->_thread_type_type = ThreadTypeType_Nintendo;
*out = is_lp64 ? tlr.lp64.p_thread_type : tlr.ilp32.p_thread_type;
}
return ResultSuccess();
}
Result GetThreadArgumentAndStackPointer(u64 *out_arg, u64 *out_sp, ThreadInfo *info) {
/* Read the thread context. */
svc::ThreadContext thread_context;
R_TRY(svc::GetDebugThreadContext(std::addressof(thread_context), info->_debug_handle, info->_debug_info_create_thread.thread_id, svc::ThreadContextFlag_General | svc::ThreadContextFlag_Control));
/* Argument is in r0. */
*out_arg = thread_context.r[0];
/* Stack pointer varies by architecture. */
if (Is64BitArch(info)) {
*out_sp = thread_context.sp;
} else {
*out_sp = thread_context.r[13];
}
return ResultSuccess();
}
void DetectStratosphereThread(ThreadInfo *info) {
/* Stratosphere threads are initially misdetected as libnx threads. */
if (info->_thread_type_type != ThreadTypeType_Libnx || info->_thread_type == nullptr) {
return;
}
/* Convert to a parent pointer. */
os::ThreadType *stratosphere_ptr = util::GetParentPointer<&os::ThreadType::thread_impl_storage>(std::addressof(info->_thread_type->libnx));
/* Read the magic. */
u16 magic;
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(magic)), info->_debug_handle, reinterpret_cast<uintptr_t>(std::addressof(stratosphere_ptr->magic)), sizeof(magic)))) {
return;
}
/* Check the magic. */
if (magic == os::ThreadType::Magic) {
info->_thread_type_type = ThreadTypeType_Stratosphere;
info->_thread_type = reinterpret_cast<ThreadTypeCommon *>(stratosphere_ptr);
}
}
}
void GetTargetThreadType(ThreadInfo *info) {
/* Ensure we exit with correct state. */
auto type_guard = SCOPE_GUARD {
info->_thread_type = nullptr;
info->_thread_type_type = ThreadTypeType_Unknown;
};
/* Read the thread type pointer. */
uintptr_t tlr_thread_type;
if (R_FAILED(GetThreadTypePointerFromThreadLocalRegion(std::addressof(tlr_thread_type), info))) {
return;
}
/* Handle the case where we have a thread type. */
if (tlr_thread_type != 0) {
info->_thread_type = reinterpret_cast<ThreadTypeCommon *>(tlr_thread_type);
DetectStratosphereThread(info);
type_guard.Cancel();
return;
}
/* Otherwise, the thread is just created, and we should read its context. */
u64 arg, sp;
if (R_FAILED(GetThreadArgumentAndStackPointer(std::addressof(arg), std::addressof(sp), info))) {
return;
}
/* We may have been bamboozled into thinking a nintendo thread was a libnx thread, so check that. */
/* Nintendo threads have argument=ThreadType, libnx threads have argument=ThreadEntryArgs. */
if (info->_thread_type_type == ThreadTypeType_Nintendo && sp == arg) {
/* It's a libnx thread, so we should parse the entry args. */
info->_thread_type_type = ThreadTypeType_Libnx;
/* Read the entry args. */
LibnxThreadEntryArgs entry_args;
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(entry_args)), info->_debug_handle, arg, sizeof(entry_args)))) {
return;
}
info->_thread_type = reinterpret_cast<ThreadTypeCommon *>(entry_args.t);
} else {
info->_thread_type_type = ThreadTypeType_Nintendo;
info->_thread_type = reinterpret_cast<ThreadTypeCommon *>(arg);
}
/* If we got the thread type, we don't need to reset our state. */
if (info->_thread_type != nullptr) {
type_guard.Cancel();
DetectStratosphereThread(info);
}
}
}

View File

@@ -0,0 +1,100 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "osdbg_types.hpp"
namespace ams::osdbg::impl {
struct ThreadLocalRegionLp64 {
u32 message_buffer[0x100 / sizeof(u32)];
volatile u16 disable_counter;
volatile u16 interrupt_flag;
u32 reserved0;
u64 reserved[15];
u64 tls[10];
u64 locale_ptr;
s64 _errno_val;
u64 thread_data;
u64 eh_globals;
u64 thread_pointer;
u64 p_thread_type;
};
static_assert(sizeof(ThreadLocalRegionLp64) == sizeof(svc::ThreadLocalRegion));
static_assert(__builtin_offsetof(ThreadLocalRegionLp64, tls) == 0x180);
struct ThreadLocalRegionIlp32 {
u32 message_buffer[0x100 / sizeof(u32)];
volatile u16 disable_counter;
volatile u16 interrupt_flag;
u32 reserved[(0xC0 - 0x4) / sizeof(u32)];
u32 tls[10];
u32 locale_ptr;
s32 _errno_val;
u32 thread_data;
u32 eh_globals;
u32 thread_pointer;
u32 p_thread_type;
};
static_assert(sizeof(ThreadLocalRegionIlp32) == sizeof(svc::ThreadLocalRegion));
static_assert(__builtin_offsetof(ThreadLocalRegionIlp32, tls) == 0x1C0);
struct LibnxThreadVars {
static constexpr u32 Magic = util::FourCC<'!','T','V','$'>::Code;
u32 magic;
::Handle handle;
::Thread *thread_ptr;
void *reent;
void *tls_tp;
};
static_assert(sizeof(LibnxThreadVars) == 0x20);
struct ThreadLocalRegionLibnx {
u32 message_buffer[0x100 / sizeof(u32)];
volatile u16 disable_counter;
volatile u16 interrupt_flag;
u32 reserved0;
u64 tls[(0x1E0 - 0x108) / sizeof(u64)];
LibnxThreadVars thread_vars;
};
static_assert(sizeof(ThreadLocalRegionLibnx) == sizeof(svc::ThreadLocalRegion));
static_assert(__builtin_offsetof(ThreadLocalRegionLibnx, thread_vars) == 0x1E0);
struct LibnxThreadEntryArgs {
u64 t;
u64 entry;
u64 arg;
u64 reent;
u64 tls;
u64 padding;
};
union ThreadLocalRegionCommon {
ThreadLocalRegionIlp32 ilp32;
ThreadLocalRegionLp64 lp64;
ThreadLocalRegionLibnx libnx;
};
static_assert(sizeof(ThreadLocalRegionCommon) == sizeof(svc::ThreadLocalRegion));
inline ThreadLocalRegionCommon *GetTargetThreadLocalRegion(ThreadInfo *info) {
return reinterpret_cast<ThreadLocalRegionCommon *>(info->_debug_info_create_thread.tls_address);
}
void GetTargetThreadType(ThreadInfo *info);
}

View File

@@ -0,0 +1,149 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "osdbg_types.hpp"
namespace ams::osdbg::impl {
/* Check that our values are the same as Nintendo's. */
static_assert(os::TlsSlotCountMax == 16);
static_assert(os::SdkTlsSlotCountMax == 16);
static_assert(os::ThreadNameLengthMax == 32);
struct ThreadTypeIlp32 {
AlignedStorageIlp32<0, 2, alignof(u32)> _all_threads_node;
AlignedStorageIlp32<0, 2, alignof(u32)> _waitable_object_list;
u32 _padding[4];
u8 _state;
bool _stack_is_aliased;
bool _auto_registered;
u8 _suspend_count;
s16 _base_priority;
u16 _version;
u32 _original_stack;
u32 _stack;
u32 _stack_size;
u32 _argument;
u32 _thread_function;
u32 _current_fiber;
u32 _initial_fiber;
u32 _tls_value_array[os::TlsSlotCountMax + os::SdkTlsSlotCountMax];
char _name_buffer[os::ThreadNameLengthMax];
u32 _name_pointer;
AlignedStorageIlp32<4, 0, alignof(u32)> _cs_thread;
AlignedStorageIlp32<4, 0, alignof(u32)> _cv_thread;
AlignedStorageIlp32<4, 0, alignof(u32)> _handle;
u32 _lock_history;
u32 _thread_id_low;
u32 _thread_id_high;
};
static_assert(sizeof(ThreadTypeIlp32) == 0x100);
struct ThreadTypeIlp32Version0 {
AlignedStorageIlp32<0, 2, alignof(u32)> _all_threads_node;
AlignedStorageIlp32<0, 2, alignof(u32)> _waitable_object_list;
u32 _padding[4];
u8 _state;
bool _stack_is_aliased;
bool _auto_registered;
u8 _padding1;
s32 _base_priority;
u32 _original_stack;
u32 _stack;
u32 _stack_size;
u32 _argument;
u32 _thread_function;
u32 _current_fiber;
u32 _initial_fiber;
u32 _lock_history;
u32 _tls_value_array[os::TlsSlotCountMax + os::SdkTlsSlotCountMax];
char _name_buffer[os::ThreadNameLengthMax];
u32 _name_pointer;
AlignedStorageIlp32<4, 0, alignof(u32)> _cs_thread;
AlignedStorageIlp32<4, 0, alignof(u32)> _cv_thread;
AlignedStorageIlp32<4, 0, alignof(u32)> _handle;
char _padding2[8];
};
static_assert(sizeof(ThreadTypeIlp32Version0) == 0x100);
struct ThreadTypeLp64 {
AlignedStorageLp64<0, 2, alignof(u64)> _all_threads_node;
AlignedStorageLp64<0, 2, alignof(u64)> _waitable_object_list;
u64 _padding[4];
u8 _state;
bool _stack_is_aliased;
bool _auto_registered;
u8 _suspend_count;
s16 _base_priority;
u16 _version;
u64 _original_stack;
u64 _stack;
u64 _stack_size;
u64 _argument;
u64 _thread_function;
u64 _current_fiber;
u64 _initial_fiber;
u64 _tls_value_array[os::TlsSlotCountMax + os::SdkTlsSlotCountMax];
char _name_buffer[os::ThreadNameLengthMax];
u64 _name_pointer;
AlignedStorageLp64<4, 0, alignof(u32)> _cs_thread;
AlignedStorageLp64<4, 0, alignof(u32)> _cv_thread;
AlignedStorageLp64<4, 0, alignof(u32)> _handle;
u32 _lock_history;
u64 thread_id;
};
static_assert(sizeof(ThreadTypeLp64) == 0x1C0);
struct ThreadTypeLp64Version0 {
AlignedStorageLp64<0, 2, alignof(u64)> _all_threads_node;
AlignedStorageLp64<0, 2, alignof(u64)> _waitable_object_list;
u64 _padding[4];
u8 _state;
bool _stack_is_aliased;
bool _auto_registered;
u8 _suspend_count;
s16 _base_priority;
u16 _version;
u64 _original_stack;
u64 _stack;
u64 _stack_size;
u64 _argument;
u64 _thread_function;
u64 _current_fiber;
u64 _initial_fiber;
u32 _lock_history;
u32 _padding2;
u64 _tls_value_array[os::TlsSlotCountMax + os::SdkTlsSlotCountMax];
char _name_buffer[os::ThreadNameLengthMax];
u64 _name_pointer;
AlignedStorageLp64<4, 0, alignof(u32)> _cs_thread;
AlignedStorageLp64<4, 0, alignof(u32)> _cv_thread;
AlignedStorageLp64<4, 0, alignof(u32)> _handle;
u32 _padding3;
};
static_assert(sizeof(ThreadTypeLp64Version0) == 0x1C0);
union ThreadTypeCommon {
ThreadTypeIlp32 ilp32;
ThreadTypeLp64 lp64;
ThreadTypeIlp32Version0 ilp32_v0;
ThreadTypeLp64Version0 lp64_v0;
os::ThreadType stratosphere;
::Thread libnx;
};
}

View File

@@ -0,0 +1,27 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::osdbg::impl {
template<size_t Size, int NumPointers, size_t Alignment>
using AlignedStorageIlp32 = typename std::aligned_storage<Size + NumPointers * sizeof(u32), Alignment>::type;
template<size_t Size, int NumPointers, size_t Alignment>
using AlignedStorageLp64 = typename std::aligned_storage<Size + NumPointers * sizeof(u64), Alignment>::type;
}

View File

@@ -0,0 +1,62 @@
/*
* 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 <stratosphere.hpp>
#if defined(ATMOSPHERE_OS_HORIZON)
#include "impl/osdbg_thread_info.os.horizon.hpp"
#else
#error "Unknown OS for ams::osdbg::ThreadInfo"
#endif
namespace ams::osdbg {
Result InitializeThreadInfo(ThreadInfo *thread_info, svc::Handle debug_handle, const svc::DebugInfoCreateProcess *create_process, const svc::DebugInfoCreateThread *create_thread) {
/* Set basic fields. */
thread_info->_thread_type = nullptr;
thread_info->_thread_type_type = ThreadTypeType_Unknown;
thread_info->_debug_handle = debug_handle;
thread_info->_debug_info_create_process = *create_process;
thread_info->_debug_info_create_thread = *create_thread;
/* Update the current info. */
return impl::ThreadInfoImpl::FillWithCurrentInfo(thread_info);
}
Result UpdateThreadInfo(ThreadInfo *thread_info) {
/* Update the current info. */
return impl::ThreadInfoImpl::FillWithCurrentInfo(thread_info);
}
Result GetThreadName(char *dst, const ThreadInfo *thread_info) {
/* Get the name pointer. */
const auto name_pointer = GetThreadNamePointer(thread_info);
/* Read the name. */
if (name_pointer != 0) {
return svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(dst), thread_info->_debug_handle, name_pointer, os::ThreadNameLengthMax);
} else {
/* Special-case libnx threads. */
if (thread_info->_thread_type_type == ThreadTypeType_Libnx) {
util::TSNPrintf(dst, os::ThreadNameLengthMax, "libnx Thread_0x%p", reinterpret_cast<void *>(thread_info->_thread_type));
} else {
util::TSNPrintf(dst, os::ThreadNameLengthMax, "Thread_0x%p", reinterpret_cast<void *>(thread_info->_thread_type));
}
return ResultSuccess();
}
}
}

View File

@@ -18,6 +18,44 @@
namespace ams::pgl {
namespace {
struct PglEventObserverAllocator;
using RemoteAllocator = ams::sf::ExpHeapStaticAllocator<1_KB, PglEventObserverAllocator>;
using RemoteObjectFactory = ams::sf::ObjectFactory<typename RemoteAllocator::Policy>;
class StaticAllocatorInitializer {
public:
StaticAllocatorInitializer() {
RemoteAllocator::Initialize(lmem::CreateOption_None);
}
} g_static_allocator_initializer;
template<typename T, typename... Args>
T *AllocateFromStaticExpHeap(Args &&... args) {
T * const object = static_cast<T *>(RemoteAllocator::Allocate(sizeof(T)));
if (AMS_LIKELY(object != nullptr)) {
std::construct_at(object, std::forward<Args>(args)...);
}
return object;
}
template<typename T>
void FreeToStaticExpHeap(T *object) {
return RemoteAllocator::Deallocate(object, sizeof(T));
}
template<typename T, typename... Args> requires std::derived_from<T, impl::EventObserverInterface>
EventObserver::UniquePtr MakeUniqueFromStaticExpHeap(Args &&... args) {
return EventObserver::UniquePtr{AllocateFromStaticExpHeap<T>(std::forward<Args>(args)...)};
}
}
void EventObserver::Deleter::operator()(impl::EventObserverInterface *obj) {
FreeToStaticExpHeap(obj);
}
Result Initialize() {
return ::pglInitialize();
}
@@ -79,17 +117,16 @@ namespace ams::pgl {
::PglEventObserver obs;
R_TRY(::pglGetEventObserver(std::addressof(obs)));
/* TODO: Real allocator */
if (hos::GetVersion() >= hos::Version_12_0_0) {
auto observer_holder = std::make_unique<impl::EventObserverByTipc<RemoteEventObserver>>(obs);
auto observer_holder = MakeUniqueFromStaticExpHeap<impl::EventObserverByTipc<RemoteEventObserver>>(obs);
R_UNLESS(observer_holder != nullptr, pgl::ResultOutOfMemory());
*out = pgl::EventObserver(std::move(observer_holder));
} else {
auto remote_observer = ams::sf::CreateSharedObjectEmplaced<pgl::sf::IEventObserver, RemoteEventObserver>(obs);
auto remote_observer = RemoteObjectFactory::CreateSharedEmplaced<pgl::sf::IEventObserver, RemoteEventObserver>(obs);
R_UNLESS(remote_observer != nullptr, pgl::ResultOutOfMemory());
auto observer_holder = std::make_unique<impl::EventObserverByCmif>(std::move(remote_observer));
auto observer_holder = MakeUniqueFromStaticExpHeap<impl::EventObserverByCmif>(std::move(remote_observer));
R_UNLESS(observer_holder != nullptr, pgl::ResultOutOfMemory());
*out = pgl::EventObserver(std::move(observer_holder));

View File

@@ -24,12 +24,26 @@ namespace ams::scs {
u32 result;
};
struct ResponseProgramExited {
ResponseHeader header;
u64 process_id;
};
struct ResponseProgramLaunched {
ResponseHeader header;
u64 process_id;
};
constinit os::SdkMutex g_htcs_send_mutex;
}
os::SdkMutex &GetHtcsSendMutex() {
return g_htcs_send_mutex;
std::scoped_lock<os::SdkMutex> CommandProcessor::MakeSendGuardBlock() {
return std::scoped_lock<os::SdkMutex>{g_htcs_send_mutex};
}
void CommandProcessor::Send(s32 socket, const void *data, size_t size) {
htcs::Send(socket, data, size, 0);
}
void CommandProcessor::SendSuccess(s32 socket, const CommandHeader &header) {
@@ -41,8 +55,8 @@ namespace ams::scs {
};
/* Send the response. */
std::scoped_lock lk(GetHtcsSendMutex());
htcs::Send(socket, std::addressof(response), sizeof(response), 0);
auto lk = MakeSendGuardBlock();
Send(socket, std::addressof(response), sizeof(response));
}
void CommandProcessor::SendErrorResult(s32 socket, const CommandHeader &header, Result result) {
@@ -61,23 +75,65 @@ namespace ams::scs {
};
/* Send the response. */
std::scoped_lock lk(GetHtcsSendMutex());
htcs::Send(socket, std::addressof(response), sizeof(response), 0);
auto lk = MakeSendGuardBlock();
Send(socket, std::addressof(response), sizeof(response));
}
void CommandProcessor::SendExited(s32 socket, u64 id, u64 process_id) {
/* Build the response. */
const ResponseProgramExited response = {
.header = {
.id = id,
.response = Response_ProgramExited,
.body_size = sizeof(response) - sizeof(response.header),
},
.process_id = process_id,
};
/* Send the response. */
auto lk = MakeSendGuardBlock();
Send(socket, std::addressof(response), sizeof(response));
}
void CommandProcessor::SendJitDebug(s32 socket, u64 id) {
/* Build the response. */
const ResponseHeader response = {
.id = id,
.response = Response_JitDebug,
.body_size = 0,
};
/* Send the response. */
auto lk = MakeSendGuardBlock();
Send(socket, std::addressof(response), sizeof(response));
}
void CommandProcessor::SendLaunched(s32 socket, u64 id, u64 process_id) {
/* Build the response. */
const ResponseProgramLaunched response = {
.header = {
.id = id,
.response = Response_ProgramLaunched,
.body_size = sizeof(response) - sizeof(response.header),
},
.process_id = process_id,
};
/* Send the response. */
auto lk = MakeSendGuardBlock();
Send(socket, std::addressof(response), sizeof(response));
}
void CommandProcessor::OnProcessStart(u64 id, s32 socket, os::ProcessId process_id) {
/* TODO */
AMS_ABORT("CommandProcessor::OnProcessStart");
SendLaunched(socket, id, process_id.value);
}
void CommandProcessor::OnProcessExit(u64 id, s32 socket, os::ProcessId process_id) {
/* TODO */
AMS_ABORT("CommandProcessor::OnProcessExit");
SendExited(socket, id, process_id.value);
}
void CommandProcessor::OnProcessJitDebug(u64 id, s32 socket, os::ProcessId process_id) {
/* TODO */
AMS_ABORT("CommandProcessor::OnProcessJitDebug");
SendJitDebug(socket, id);
}
void CommandProcessor::Initialize() {

View File

@@ -29,9 +29,9 @@ namespace ams::scs {
u64 id;
s32 socket;
s32 info_id;
bool _18;
bool _19;
bool _1A;
bool started;
bool jit_debug;
bool launched_by_cs;
};
constexpr inline auto MaxSocketInfo = 2;
@@ -39,7 +39,7 @@ namespace ams::scs {
class SocketInfoManager {
private:
SocketInfo m_infos[MaxProgramInfo];
SocketInfo m_infos[MaxSocketInfo];
int m_count;
public:
constexpr SocketInfoManager() = default;
@@ -48,6 +48,49 @@ namespace ams::scs {
/* Clear our count. */
m_count = 0;
}
Result Register(s32 socket, u64 id) {
/* Check that the socket isn't already registered. */
for (auto i = 0; i < m_count; ++i) {
R_UNLESS(m_infos[i].socket != socket, scs::ResultNoSocket());
}
/* Check that we can allocate a new socket info. */
if (m_count >= MaxSocketInfo) {
/* NOTE: Nintendo aborts with this result here. */
R_ABORT_UNLESS(scs::ResultOutOfResource());
}
/* Create the new socket info. */
m_infos[m_count++] = {
.id = id,
.socket = socket,
};
return ResultSuccess();
}
void Unregister(s32 socket) {
/* Unregister the socket, if it's registered. */
for (auto i = 0; i < m_count; ++i) {
if (m_infos[i].socket == socket) {
/* Ensure that the valid socket infos remain in bounds. */
std::memcpy(m_infos + i, m_infos + i + 1, (m_count - (i + 1)) * sizeof(*m_infos));
/* Note that we now have one fewer socket info. */
--m_count;
break;
}
}
}
void InvokeHandler(ProcessEventHandler handler, os::ProcessId process_id) {
/* Invoke the handler on all our sockets. */
for (auto i = 0; i < m_count; ++i) {
handler(m_infos[i].id, m_infos[i].socket, process_id);
}
}
};
class ProgramInfoManager {
@@ -65,6 +108,74 @@ namespace ams::scs {
/* Clear our count. */
m_count = 0;
}
const ProgramInfo *Find(os::ProcessId process_id) const {
/* Find a matching program. */
for (auto i = 0; i < m_count; ++i) {
if (m_infos[i].process_id == process_id) {
return std::addressof(m_infos[i]);
}
}
return nullptr;
}
bool Register(os::ProcessId process_id) {
/* Allocate an info id. */
const auto info_id = m_next_info_id++;
/* Check that we have space for the program. */
if (m_count >= MaxProgramInfo) {
return false;
}
/* Create the new program info. */
m_infos[m_count++] = {
.process_id = process_id,
.id = 0,
.socket = 0,
.info_id = info_id,
.started = false,
.jit_debug = false,
.launched_by_cs = false,
};
return true;
}
void Unregister(os::ProcessId process_id) {
/* Unregister the program, if it's registered. */
for (auto i = 0; i < m_count; ++i) {
if (m_infos[i].process_id == process_id) {
/* Ensure that the valid program infos remain in bounds. */
std::memcpy(m_infos + i, m_infos + i + 1, (m_count - (i + 1)) * sizeof(*m_infos));
/* Note that we now have one fewer program info. */
--m_count;
break;
}
}
}
void SetStarted(os::ProcessId process_id) {
/* Start the program. */
for (auto i = 0; i < m_count; ++i) {
if (m_infos[i].process_id == process_id) {
m_infos[i].started = true;
break;
}
}
}
void SetJitDebug(os::ProcessId process_id) {
/* Set the program as jit debug. */
for (auto i = 0; i < m_count; ++i) {
if (m_infos[i].process_id == process_id) {
m_infos[i].jit_debug = true;
break;
}
}
}
};
alignas(os::ThreadStackAlignment) constinit u8 g_thread_stack[os::MemoryPageSize];
@@ -77,9 +188,132 @@ namespace ams::scs {
constinit SocketInfoManager g_socket_info_manager;
constinit ProgramInfoManager g_program_info_manager;
constinit os::SdkMutex g_manager_mutex;
void ProcessExitEvent(const pm::ProcessEventInfo &event_info) {
/* Unregister the target environment definition. */
htc::tenv::UnregisterDefinitionFilePath(event_info.process_id);
/* Unregister program info. */
ProgramInfo program_info;
bool found = false;
{
std::scoped_lock lk(g_manager_mutex);
if (const ProgramInfo *pi = g_program_info_manager.Find(event_info.process_id); pi != nullptr) {
program_info = *pi;
found = true;
g_program_info_manager.Unregister(event_info.process_id);
}
}
/* If we found the program, handle callbacks. */
if (found) {
/* Invoke the common exit handler. */
if (program_info.launched_by_cs) {
g_common_exit_handler(program_info.id, program_info.socket, program_info.process_id);
}
/* Notify the process event. */
if (program_info.started) {
std::scoped_lock lk(g_manager_mutex);
g_socket_info_manager.InvokeHandler(g_common_exit_handler, program_info.process_id);
}
}
}
void ProcessStartedEvent(const pm::ProcessEventInfo &event_info) {
/* Start the program (registering it, if needed). */
{
std::scoped_lock lk(g_manager_mutex);
if (g_program_info_manager.Find(event_info.process_id) == nullptr) {
AMS_ABORT_UNLESS(g_program_info_manager.Register(event_info.process_id));
}
g_program_info_manager.SetStarted(event_info.process_id);
}
/* Handle callbacks. */
{
std::scoped_lock lk(g_manager_mutex);
g_socket_info_manager.InvokeHandler(g_common_start_handler, event_info.process_id);
}
}
void ProcessExceptionEvent(const pm::ProcessEventInfo &event_info) {
/* Find the program info. */
ProgramInfo program_info;
bool found = false;
{
std::scoped_lock lk(g_manager_mutex);
if (const ProgramInfo *pi = g_program_info_manager.Find(event_info.process_id); pi != nullptr) {
program_info = *pi;
found = true;
}
/* Set the program as jit debug. */
g_program_info_manager.SetJitDebug(event_info.process_id);
}
/* If we found the program, handle callbacks. */
if (found) {
/* Invoke the common exception handler. */
if (program_info.launched_by_cs) {
g_common_jit_debug_handler(program_info.id, program_info.socket, program_info.process_id);
}
/* Notify the process event. */
if (program_info.started) {
std::scoped_lock lk(g_manager_mutex);
g_socket_info_manager.InvokeHandler(g_common_jit_debug_handler, program_info.process_id);
}
}
}
void EventHandlerThread(void *) {
/* TODO */
AMS_ABORT("scs::EventHandlerThread");
/* Get event observer. */
pgl::EventObserver observer;
R_ABORT_UNLESS(pgl::GetEventObserver(std::addressof(observer)));
/* Get the observer's event. */
os::SystemEventType shell_event;
R_ABORT_UNLESS(observer.GetSystemEvent(std::addressof(shell_event)));
/* Loop handling events. */
while (true) {
/* Wait for an event to come in. */
os::WaitSystemEvent(std::addressof(shell_event));
/* Loop processing event infos. */
while (true) {
/* Get the next event info. */
pm::ProcessEventInfo event_info;
if (R_FAILED(observer.GetProcessEventInfo(std::addressof(event_info)))) {
break;
}
/* Process the event. */
switch (event_info.GetProcessEvent()) {
case pm::ProcessEvent::Exited:
ProcessExitEvent(event_info);
break;
case pm::ProcessEvent::Started:
ProcessStartedEvent(event_info);
break;
case pm::ProcessEvent::Exception:
ProcessExceptionEvent(event_info);
break;
default:
break;
}
}
}
}
void StartEventHandlerThread() {
@@ -130,18 +364,30 @@ namespace ams::scs {
g_common_jit_debug_handler = on_jit_debug;
}
bool RegisterSocket(s32 socket);
void UnregisterSocket(s32 socket);
Result RegisterSocket(s32 socket, u64 id) {
/* Acquire exclusive access to the socket info manager. */
std::scoped_lock lk(g_manager_mutex);
Result LaunchProgram(os::ProcessId *out, ncm::ProgramId program_id, const void *args, size_t args_size, u32 process_flags) {
/* Register the socket. */
return g_socket_info_manager.Register(socket, id);
}
void UnregisterSocket(s32 socket) {
/* Acquire exclusive access to the socket info manager. */
std::scoped_lock lk(g_manager_mutex);
/* Unregister the socket. */
return g_socket_info_manager.Unregister(socket);
}
Result LaunchProgram(os::ProcessId *out, const ncm::ProgramLocation &loc, const void *args, size_t args_size, u32 process_flags) {
/* Set up the arguments. */
PrepareToLaunchProgram(program_id, args, args_size);
PrepareToLaunchProgram(loc.program_id, args, args_size);
/* Ensure arguments are managed correctly. */
ON_SCOPE_EXIT { FlushProgramArgument(program_id); };
ON_SCOPE_EXIT { FlushProgramArgument(loc.program_id); };
/* Launch the program. */
const ncm::ProgramLocation loc = ncm::ProgramLocation::Make(program_id, ncm::StorageId::BuiltInSystem);
R_TRY(pgl::LaunchProgram(out, loc, process_flags | pm::LaunchFlags_SignalOnExit, 0));
return ResultSuccess();

View File

@@ -17,6 +17,73 @@
namespace ams::scs {
namespace {
s32 CreateSocket() {
while (true) {
/* Try to create a socket. */
if (const auto desc = htcs::Socket(); desc >= 0) {
return desc;
}
/* Wait 100ms before trying again. */
os::SleepThread(TimeSpan::FromMilliSeconds(100));
}
}
s32 AcceptSocket(s32 listen_socket) {
htcs::SockAddrHtcs temp;
return htcs::Accept(listen_socket, std::addressof(temp));
}
s32 Bind(s32 socket, const htcs::HtcsPortName &port_name) {
/* Set up the bind address. */
htcs::SockAddrHtcs addr;
addr.family = htcs::HTCS_AF_HTCS;
addr.peer_name = htcs::GetPeerNameAny();
std::strcpy(addr.port_name.name, port_name.name);
/* Bind. */
return htcs::Bind(socket, std::addressof(addr));
}
htcs::ssize_t Receive(s32 socket, void *buffer, size_t size) {
u8 *dst = static_cast<u8 *>(buffer);
size_t received = 0;
while (received < size) {
const auto ret = htcs::Recv(socket, dst + received, size - received, 0);
if (ret <= 0) {
return ret;
}
received += ret;
}
return static_cast<htcs::ssize_t>(received);
}
bool ReceiveCommand(CommandHeader *header, void *buffer, size_t buffer_size, s32 socket) {
/* Receive the header. */
if (Receive(socket, header, sizeof(*header)) != sizeof(*header)) {
return false;
}
/* Check that the body will fit in the buffer. */
if (header->body_size >= buffer_size) {
return false;
}
/* Receive the body. */
if(Receive(socket, buffer, header->body_size) != static_cast<htcs::ssize_t>(header->body_size)) {
return false;
}
return true;
}
}
void ShellServer::Initialize(const char *port_name, void *stack, size_t stack_size, CommandProcessor *command_processor) {
/* Set our variables. */
m_command_processor = command_processor;
@@ -34,8 +101,53 @@ namespace ams::scs {
}
void ShellServer::DoShellServer() {
/* TODO */
AMS_ABORT("ShellServer::DoShellServer");
/* Loop servicing the shell server. */
while (true) {
/* Create a socket to listen on. */
const auto listen_socket = CreateSocket();
ON_SCOPE_EXIT { htcs::Close(listen_socket); };
/* Bind to the listen socket. */
if (Bind(listen_socket, m_port_name) != 0) {
continue;
}
/* Loop processing on our bound socket. */
while (true) {
/* Listen on the socket. */
if (const s32 listen_result = htcs::Listen(listen_socket, 0); listen_result != 0) {
/* TODO: logging. */
break;
}
/* Accept a socket. */
const s32 socket = AcceptSocket(listen_socket);
if (socket <= 0) {
/* TODO: logging. */
continue;
}
/* Ensure that the socket is cleaned up when we're done with it. */
ON_SCOPE_EXIT {
UnregisterSocket(socket);
htcs::Close(socket);
};
/* Loop servicing the socket. */
while (true) {
/* Receive a command header. */
CommandHeader header;
if (!ReceiveCommand(std::addressof(header), m_buffer, sizeof(m_buffer), socket)) {
break;
}
/* Process the command. */
if (!m_command_processor->ProcessCommand(header, m_buffer, socket)) {
break;
}
}
}
}
}
}

View File

@@ -49,6 +49,7 @@
#include <vapours/results/nim_results.hpp>
#include <vapours/results/ns_results.hpp>
#include <vapours/results/os_results.hpp>
#include <vapours/results/osdbg_results.hpp>
#include <vapours/results/pcv_results.hpp>
#include <vapours/results/pgl_results.hpp>
#include <vapours/results/pm_results.hpp>

View File

@@ -0,0 +1,27 @@
/*
* 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/>.
*/
#pragma once
#include <vapours/results/results_common.hpp>
namespace ams::osdbg {
R_DEFINE_NAMESPACE_RESULT_MODULE(7);
R_DEFINE_ERROR_RESULT(CannotGetThreadInfo, 1);
R_DEFINE_ERROR_RESULT(UnsupportedThreadVersion, 2);
}

View File

@@ -174,8 +174,9 @@ namespace ams::svc {
};
enum MesosphereMetaInfo : u64 {
MesosphereMetaInfo_KernelVersion = 0,
MesosphereMetaInfo_IsKTraceEnabled = 1,
MesosphereMetaInfo_KernelVersion = 0,
MesosphereMetaInfo_IsKTraceEnabled = 1,
MesosphereMetaInfo_IsSingleStepEnabled = 2,
};
enum SystemInfoType : u32 {
@@ -299,6 +300,9 @@ namespace ams::svc {
ThreadContextFlag_FpuControl = (1 << 3),
ThreadContextFlag_All = (ThreadContextFlag_General | ThreadContextFlag_Control | ThreadContextFlag_Fpu | ThreadContextFlag_FpuControl),
ThreadContextFlag_SetSingleStep = (1u << 30),
ThreadContextFlag_ClearSingleStep = (1u << 31),
};
enum ContinueFlag : u32 {

View File

@@ -121,7 +121,7 @@ namespace ams::svc {
struct DebugInfoCreateThread {
u64 thread_id;
u32 tls_address;
u32 entrypoint;
/* Removed in 11.0.0 u32 entrypoint; */
};
struct DebugInfoExitProcess {

View File

@@ -110,6 +110,11 @@ _ZN3ams4kern4arch5arm6422EL0IrqExceptionHandlerEv:
ldp x21, x22, [sp, #(EXCEPTION_CONTEXT_PC_PSR)]
ldr x23, [sp, #(EXCEPTION_CONTEXT_TPIDR)]
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
/* Since we're returning from an exception, set SPSR.SS so that we advance an instruction if single-stepping. */
orr x22, x22, #(1 << 21)
#endif
msr sp_el0, x20
msr elr_el1, x21
msr spsr_el1, x22

View File

@@ -235,7 +235,26 @@ _ZN3ams4kern10KScheduler12ScheduleImplEv:
mov x0, x22
RESTORE_THREAD_CONTEXT(x0, x1, x2, 9f)
9: /* We're done restoring the thread context, and can return safely. */
9: /* Configure single-step, if we should. */
#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP)
/* Get a reference to the new thread's stack parameters. */
add x2, sp, #0x1000
and x2, x2, #~(0x1000-1)
/* Read the single-step flag. */
ldurb w2, [x2, #-(THREAD_STACK_PARAMETERS_SIZE - THREAD_STACK_PARAMETERS_IS_SINGLE_STEP)]
/* Update the single-step bit in mdscr_el1. */
mrs x1, mdscr_el1
bic x1, x1, #1
orr x1, x1, x2
msr mdscr_el1, x1
isb
#endif
/* We're done restoring the thread context, and can return safely. */
ret
10: /* Our switch failed. */

View File

@@ -1,4 +1,4 @@
MODULES := loader ncm pm sm boot ams_mitm spl eclct.stub ro creport fatal dmnt boot2 erpt pgl jpegdec LogManager
MODULES := loader ncm pm sm boot ams_mitm spl eclct.stub ro creport fatal dmnt boot2 erpt pgl jpegdec LogManager cs htc TioServer dmnt.gen2
SUBFOLDERS := $(MODULES)

View File

@@ -152,13 +152,22 @@ namespace ams::creport {
std::memcpy(this->tls, thread_tls, sizeof(this->tls));
/* Try to detect libnx threads, and skip name parsing then. */
if (*(reinterpret_cast<u32 *>(&thread_tls[0x1E0])) != LibnxThreadVarMagic) {
u8 thread_type[0x1D0];
u8 thread_type[0x1C0];
const u64 thread_type_addr = *(reinterpret_cast<u64 *>(&thread_tls[0x1F8]));
if (R_SUCCEEDED(svcReadDebugProcessMemory(thread_type, debug_handle, thread_type_addr, sizeof(thread_type)))) {
/* Check thread name is actually at thread name. */
static_assert(0x1A8 - 0x188 == NameLengthMax, "NameLengthMax definition!");
if (*(reinterpret_cast<u64 *>(&thread_type[0x1A8])) == thread_type_addr + 0x188) {
std::memcpy(this->name, thread_type + 0x188, NameLengthMax);
/* Get the thread version. */
const u16 thread_version = *reinterpret_cast<u16 *>(&thread_type[0x46]);
if (thread_version == 0 || thread_version == 0xFFFF) {
/* Check thread name is actually at thread name. */
static_assert(0x1A8 - 0x188 == NameLengthMax, "NameLengthMax definition!");
if (*(reinterpret_cast<u64 *>(&thread_type[0x1A8])) == thread_type_addr + 0x188) {
std::memcpy(this->name, thread_type + 0x188, NameLengthMax);
}
} else if (thread_version == 1) {
static_assert(0x1A0 - 0x180 == NameLengthMax, "NameLengthMax definition!");
if (*(reinterpret_cast<u64 *>(&thread_type[0x1A0])) == thread_type_addr + 0x180) {
std::memcpy(this->name, thread_type + 0x180, NameLengthMax);
}
}
}
}

View File

@@ -7,7 +7,7 @@
"main_thread_priority": 48,
"default_cpu_id": 3,
"process_category": 0,
"is_retail": false,
"is_retail": true,
"pool_partition": 2,
"is_64_bit": true,
"address_space_type": 3,

View File

@@ -70,7 +70,7 @@ namespace ams::cs {
alignas(os::MemoryPageSize) constinit u8 g_heap_memory[32_KB];
alignas(0x40) constinit u8 g_htcs_buffer[1_KB];
alignas(0x40) constinit u8 g_htcs_buffer[2_KB];
constinit os::SdkMutex g_heap_mutex;
constinit lmem::HeapHandle g_heap_handle;
@@ -120,6 +120,7 @@ void __appInit(void) {
lr::Initialize();
R_ABORT_UNLESS(ldr::InitializeForShell());
R_ABORT_UNLESS(pgl::Initialize());
R_ABORT_UNLESS(setsysInitialize());
/* TODO: Other services? */
ams::CheckApiVersion();
@@ -127,6 +128,7 @@ void __appInit(void) {
void __appExit(void) {
/* TODO: Other services? */
setsysExit();
pgl::Finalize();
ldr::FinalizeForShell();
lr::Finalize();

View File

@@ -0,0 +1,113 @@
#---------------------------------------------------------------------------------
# pull in common stratosphere sysmodule configuration
#---------------------------------------------------------------------------------
include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export TOPDIR := $(CURDIR)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(call FIND_SOURCE_FILES,$(SOURCES),c)
CPPFILES := $(call FIND_SOURCE_FILES,$(SOURCES),cpp)
SFILES := $(call FIND_SOURCE_FILES,$(SOURCES),s)
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
export OFILES := $(addsuffix .o,$(BINFILES)) \
$(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC)
ifeq ($(strip $(CONFIG_JSON)),)
jsons := $(wildcard *.json)
ifneq (,$(findstring $(TARGET).json,$(jsons)))
export APP_JSON := $(TOPDIR)/$(TARGET).json
else
ifneq (,$(findstring config.json,$(jsons)))
export APP_JSON := $(TOPDIR)/config.json
endif
endif
else
export APP_JSON := $(TOPDIR)/$(CONFIG_JSON)
endif
.PHONY: $(BUILD) clean all
#---------------------------------------------------------------------------------
all: $(BUILD)
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(BUILD) $(TARGET).nsp $(TARGET).npdm $(TARGET).nso $(TARGET).elf
#---------------------------------------------------------------------------------
else
.PHONY: all
DEPENDS := $(OFILES:.o=.d)
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
all : $(OUTPUT).nsp
ifeq ($(strip $(APP_JSON)),)
$(OUTPUT).nsp : $(OUTPUT).nso
else
$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm
endif
$(OUTPUT).nso : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
#---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#---------------------------------------------------------------------------------
%.bin.o : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPENDS)
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------

View File

@@ -0,0 +1,110 @@
{
"name": "dmnt.gen2",
"title_id": "0x010000000000D609",
"title_id_range_min": "0x010000000000D609",
"title_id_range_max": "0x010000000000D609",
"main_thread_stack_size": "0x00001000",
"main_thread_priority": 39,
"default_cpu_id": 3,
"process_category": 0,
"is_retail": true,
"pool_partition": 2,
"is_64_bit": true,
"address_space_type": 3,
"disable_device_address_space_merge": true,
"filesystem_access": {
"permissions": "0xFFFFFFFFFFFFFFFF"
},
"service_access": ["pm:dmnt", "ldr:dmnt", "ro:dmnt", "ns:dev", "lr", "fsp-srv", "fatal:u", "pgl", "htcs"],
"service_host": [],
"kernel_capabilities": [{
"type": "kernel_flags",
"value": {
"highest_thread_priority": 63,
"lowest_thread_priority": 24,
"lowest_cpu_id": 0,
"highest_cpu_id": 3
}
}, {
"type": "syscalls",
"value": {
"svcSetHeapSize": "0x01",
"svcSetMemoryPermission": "0x02",
"svcSetMemoryAttribute": "0x03",
"svcMapMemory": "0x04",
"svcUnmapMemory": "0x05",
"svcQueryMemory": "0x06",
"svcExitProcess": "0x07",
"svcCreateThread": "0x08",
"svcStartThread": "0x09",
"svcExitThread": "0x0a",
"svcSleepThread": "0x0b",
"svcGetThreadPriority": "0x0c",
"svcSetThreadPriority": "0x0d",
"svcGetThreadCoreMask": "0x0e",
"svcSetThreadCoreMask": "0x0f",
"svcGetCurrentProcessorNumber": "0x10",
"svcSignalEvent": "0x11",
"svcClearEvent": "0x12",
"svcMapSharedMemory": "0x13",
"svcUnmapSharedMemory": "0x14",
"svcCreateTransferMemory": "0x15",
"svcCloseHandle": "0x16",
"svcResetSignal": "0x17",
"svcWaitSynchronization": "0x18",
"svcCancelSynchronization": "0x19",
"svcArbitrateLock": "0x1a",
"svcArbitrateUnlock": "0x1b",
"svcWaitProcessWideKeyAtomic": "0x1c",
"svcSignalProcessWideKey": "0x1d",
"svcGetSystemTick": "0x1e",
"svcConnectToNamedPort": "0x1f",
"svcSendSyncRequestLight": "0x20",
"svcSendSyncRequest": "0x21",
"svcSendSyncRequestWithUserBuffer": "0x22",
"svcSendAsyncRequestWithUserBuffer": "0x23",
"svcGetProcessId": "0x24",
"svcGetThreadId": "0x25",
"svcBreak": "0x26",
"svcOutputDebugString": "0x27",
"svcReturnFromException": "0x28",
"svcGetInfo": "0x29",
"svcWaitForAddress": "0x34",
"svcSignalToAddress": "0x35",
"svcCreateSession": "0x40",
"svcAcceptSession": "0x41",
"svcReplyAndReceiveLight": "0x42",
"svcReplyAndReceive": "0x43",
"svcReplyAndReceiveWithUserBuffer": "0x44",
"svcCreateEvent": "0x45",
"svcDebugActiveProcess": "0x60",
"svcBreakDebugProcess": "0x61",
"svcTerminateDebugProcess": "0x62",
"svcGetDebugEvent": "0x63",
"svcContinueDebugEvent": "0x64",
"svcGetProcessList": "0x65",
"svcGetThreadList": "0x66",
"svcGetDebugThreadContext": "0x67",
"svcSetDebugThreadContext": "0x68",
"svcQueryDebugProcessMemory": "0x69",
"svcReadDebugProcessMemory": "0x6a",
"svcWriteDebugProcessMemory": "0x6b",
"svcSetHardwareBreakPoint": "0x6c",
"svcGetDebugThreadParam": "0x6d",
"svcCallSecureMonitor": "0x7f"
}
}, {
"type": "min_kernel_version",
"value": "0x0030"
}, {
"type": "handle_table_size",
"value": 0
},
{
"type": "debug_flags",
"value": {
"allow_debug": false,
"force_debug": true
}
}]
}

View File

@@ -0,0 +1,54 @@
/*
* 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 <stratosphere.hpp>
#include "dmnt2_breakpoint_manager.hpp"
#include "dmnt2_debug_process.hpp"
#include "dmnt2_debug_log.hpp"
namespace ams::dmnt {
BreakPointManager::BreakPointManager(DebugProcess *debug_process) : BreakPointManagerBase(debug_process) {
/* ... */
}
void BreakPointManager::ClearStep() {
BreakPoint *bp = nullptr;
for (size_t i = 0; (bp = static_cast<BreakPoint *>(this->GetBreakPoint(i))) != nullptr; ++i) {
if (bp->m_in_use && bp->m_is_step) {
AMS_DMNT2_GDB_LOG_DEBUG("BreakPointManager::ClearStep %p 0x%lx (idx=%zu)\n", bp, bp->m_address, i);
bp->Clear(m_debug_process);
}
}
}
Result BreakPointManager::SetBreakPoint(uintptr_t address, size_t size, bool is_step) {
/* Get a free breakpoint. */
BreakPoint *bp = static_cast<BreakPoint *>(this->GetFreeBreakPoint());
/* Set the breakpoint. */
Result result = svc::ResultOutOfHandles();
if (bp != nullptr) {
result = bp->Set(m_debug_process, address, size, is_step);
}
if (R_FAILED(result)) {
AMS_DMNT2_GDB_LOG_DEBUG("BreakPointManager::SetBreakPoint %p 0x%lx !!! Fail 0x%08x !!!\n", bp, bp->m_address, result.GetValue());
}
return result;
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "dmnt2_breakpoint_manager_base.hpp"
namespace ams::dmnt {
class DebugProcess;
struct BreakPoint : public BreakPointBase {
bool m_is_step;
BreakPoint() { /* ... */ }
virtual Result Set(DebugProcess *debug_process, uintptr_t address, size_t size, bool is_step) = 0;
};
class BreakPointManager : public BreakPointManagerBase {
public:
explicit BreakPointManager(DebugProcess *debug_process);
void ClearStep();
Result SetBreakPoint(uintptr_t address, size_t size, bool is_step);
};
}

View File

@@ -0,0 +1,64 @@
/*
* 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 <stratosphere.hpp>
#include "dmnt2_breakpoint_manager_base.hpp"
#include "dmnt2_debug_process.hpp"
#include "dmnt2_debug_log.hpp"
namespace ams::dmnt {
BreakPointManagerBase::BreakPointManagerBase(DebugProcess *debug_process) : m_debug_process(debug_process) {
/* ... */
}
void BreakPointManagerBase::ClearAll() {
BreakPointBase *bp = nullptr;
for (size_t i = 0; (bp = static_cast<BreakPointBase *>(this->GetBreakPoint(i))) != nullptr; ++i) {
if (bp->m_in_use) {
bp->Clear(m_debug_process);
}
}
}
void BreakPointManagerBase::Reset() {
BreakPointBase *bp = nullptr;
for (size_t i = 0; (bp = static_cast<BreakPointBase *>(this->GetBreakPoint(i))) != nullptr; ++i) {
bp->Reset();
}
}
Result BreakPointManagerBase::ClearBreakPoint(uintptr_t address, size_t size) {
BreakPointBase *bp = nullptr;
for (size_t i = 0; (bp = static_cast<BreakPointBase *>(this->GetBreakPoint(i))) != nullptr; ++i) {
if (bp->m_in_use && bp->m_address == address) {
AMS_ABORT_UNLESS(bp->m_size == size);
return bp->Clear(m_debug_process);
}
}
return ResultSuccess();
}
BreakPointBase *BreakPointManagerBase::GetFreeBreakPoint() {
BreakPointBase *bp = nullptr;
for (size_t i = 0; (bp = static_cast<BreakPointBase *>(this->GetBreakPoint(i))) != nullptr; ++i) {
if (!bp->m_in_use) {
return bp;
}
}
return nullptr;
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::dmnt {
class DebugProcess;
struct BreakPointBase {
bool m_in_use;
uintptr_t m_address;
size_t m_size;
BreakPointBase() : m_in_use(false) { /* ... */ }
void Reset() { m_in_use = false; }
virtual Result Clear(DebugProcess *debug_process) = 0;
};
class BreakPointManagerBase {
protected:
DebugProcess *m_debug_process;
public:
explicit BreakPointManagerBase(DebugProcess *debug_process);
void ClearAll();
void Reset();
Result ClearBreakPoint(uintptr_t address, size_t size);
protected:
virtual BreakPointBase *GetBreakPoint(size_t index) = 0;
BreakPointBase *GetFreeBreakPoint();
};
}

View File

@@ -0,0 +1,179 @@
/*
* 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 <stratosphere.hpp>
#include "dmnt2_debug_log.hpp"
#define AMS_DMNT2_ENABLE_HTCS_DEBUG_LOG
#if defined(AMS_DMNT2_ENABLE_HTCS_DEBUG_LOG)
namespace ams::dmnt {
namespace {
constexpr size_t NumLogBuffers = 0x200;
constexpr size_t LogBufferSize = 0x100;
constexpr inline auto LogThreadPriority = os::HighestThreadPriority - 2;
constinit os::MessageQueueType g_buf_mq;
constinit os::MessageQueueType g_req_mq;
alignas(os::ThreadStackAlignment) u8 g_log_thread_stack[os::MemoryPageSize];
constinit char g_log_buffers[NumLogBuffers][LogBufferSize];
constinit uintptr_t g_buf_mq_storage[NumLogBuffers];
constinit uintptr_t g_req_mq_storage[NumLogBuffers];
constinit os::ThreadType g_log_thread;
util::optional<char *> GetLogBuffer() {
/* Try to get a log buffer. */
uintptr_t address;
if (os::TryReceiveMessageQueue(std::addressof(address), std::addressof(g_buf_mq))) {
return reinterpret_cast<char *>(address);
} else {
return util::nullopt;
}
}
/* TODO: This is a hack because we don't have a proper LogManager system module. */
/* Eventually, this should be refactored to use normal logging. */
void DebugLogThreadFunction(void *) {
/* Loop forever, servicing our log server. */
while (true) {
/* Get a socket. */
int fd;
while ((fd = htcs::Socket()) == -1) {
os::SleepThread(TimeSpan::FromSeconds(1));
}
/* Ensure we cleanup the socket when we're done with it. */
ON_SCOPE_EXIT {
htcs::Close(fd);
os::SleepThread(TimeSpan::FromSeconds(1));
};
/* Create a sock addr for our server. */
htcs::SockAddrHtcs addr;
addr.family = htcs::HTCS_AF_HTCS;
addr.peer_name = htcs::GetPeerNameAny();
std::strcpy(addr.port_name.name, "iywys@$dmnt2_log");
/* Bind. */
if (htcs::Bind(fd, std::addressof(addr)) == -1) {
continue;
}
/* Listen on our port. */
while (htcs::Listen(fd, 0) == 0) {
/* Continue accepting clients, so long as we can. */
int client_fd;
while (true) {
/* Try to accept a client. */
if (client_fd = htcs::Accept(fd, std::addressof(addr)); client_fd < 0) {
break;
}
/* Handle the client. */
while (true) {
/* Receive a log request. */
uintptr_t log_buffer_address;
os::ReceiveMessageQueue(std::addressof(log_buffer_address), std::addressof(g_req_mq));
/* Ensure we manage our request properly. */
ON_SCOPE_EXIT { os::SendMessageQueue(std::addressof(g_buf_mq), log_buffer_address); };
/* Send the data. */
const char * const log_buffer = reinterpret_cast<const char *>(log_buffer_address);
if (htcs::Send(client_fd, log_buffer, std::strlen(log_buffer), 0) < 0) {
break;
}
}
/* Close the client socket. */
htcs::Close(client_fd);
}
}
}
}
}
void InitializeDebugLog() {
/* Initialize logging message queues. */
os::InitializeMessageQueue(std::addressof(g_buf_mq), g_buf_mq_storage, NumLogBuffers);
os::InitializeMessageQueue(std::addressof(g_req_mq), g_req_mq_storage, NumLogBuffers);
/* Initially make all log buffers available. */
for (size_t i = 0; i < NumLogBuffers; ++i) {
os::SendMessageQueue(std::addressof(g_buf_mq), reinterpret_cast<uintptr_t>(g_log_buffers[i]));
}
/* Create and start the debug log thread. */
R_ABORT_UNLESS(os::CreateThread(std::addressof(g_log_thread), DebugLogThreadFunction, nullptr, g_log_thread_stack, sizeof(g_log_thread_stack), LogThreadPriority));
os::StartThread(std::addressof(g_log_thread));
}
void DebugLog(const char *prefix, const char *fmt, ...) {
/* Try to get a log buffer. */
const auto log_buffer_holder = GetLogBuffer();
if (!log_buffer_holder) {
return;
}
/* Format into the log buffer. */
char * const log_buffer = *log_buffer_holder;
{
const auto prefix_len = std::strlen(prefix);
std::memcpy(log_buffer, prefix, prefix_len);
{
std::va_list vl;
va_start(vl, fmt);
util::VSNPrintf(log_buffer + prefix_len, LogBufferSize - prefix_len, fmt, vl);
va_end(vl);
}
}
/* Request to log. */
os::SendMessageQueue(std::addressof(g_req_mq), reinterpret_cast<uintptr_t>(log_buffer));
}
}
#else
namespace ams::dmnt {
void InitializeDebugLog() {
/* Do nothing. */
}
void DebugLog(const char *prefix, const char *fmt, ...) {
/* Do nothing. */
}
}
#endif

View File

@@ -0,0 +1,32 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::dmnt {
void InitializeDebugLog();
void DebugLog(const char *prefix, const char *fmt, ...) __attribute((format(printf, 2, 3)));
#define AMS_DMNT2_DEBUG_LOG(fmt, ...) ::ams::dmnt::DebugLog("[dmnt2] ", fmt, ## __VA_ARGS__)
#define AMS_DMNT2_GDB_LOG_INFO(fmt, ...) ::ams::dmnt::DebugLog("[gdb-i] ", fmt, ## __VA_ARGS__)
#define AMS_DMNT2_GDB_LOG_WARN(fmt, ...) ::ams::dmnt::DebugLog("[gdb-w] ", fmt, ## __VA_ARGS__)
#define AMS_DMNT2_GDB_LOG_ERROR(fmt, ...) ::ams::dmnt::DebugLog("[gdb-e] ", fmt, ## __VA_ARGS__)
#define AMS_DMNT2_GDB_LOG_DEBUG(fmt, ...) ::ams::dmnt::DebugLog("[gdb-d] ", fmt, ## __VA_ARGS__)
}

View File

@@ -0,0 +1,525 @@
/*
* 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 <stratosphere.hpp>
#include "dmnt2_debug_log.hpp"
#include "dmnt2_debug_process.hpp"
namespace ams::dmnt {
namespace {
s32 SignExtend(u32 value, u32 bits) {
return static_cast<s32>(value << (32 - bits)) >> (32 - bits);
}
}
Result DebugProcess::Attach(os::ProcessId process_id) {
/* Attach to the process. */
R_TRY(svc::DebugActiveProcess(std::addressof(m_debug_handle), process_id.value));
/* Collect initial information. */
R_TRY(this->Start());
/* Get the attached modules. */
R_TRY(this->CollectModules());
/* Get our process id. */
u64 pid_value = 0;
svc::GetProcessId(std::addressof(pid_value), m_debug_handle);
m_process_id = { pid_value };
return ResultSuccess();
}
void DebugProcess::Detach() {
if (m_is_valid) {
m_software_breakpoints.ClearAll();
m_hardware_breakpoints.ClearAll();
m_hardware_watchpoints.ClearAll();
R_ABORT_UNLESS(svc::CloseHandle(m_debug_handle));
m_debug_handle = svc::InvalidHandle;
}
m_is_valid = false;
}
Result DebugProcess::Start() {
/* Process the initial debug events. */
s32 num_threads = 0;
bool attached = false;
while (num_threads == 0 || !attached) {
/* Wait for debug events to be available. */
s32 dummy_index;
R_ABORT_UNLESS(svc::WaitSynchronization(std::addressof(dummy_index), std::addressof(m_debug_handle), 1, svc::WaitInfinite));
/* Get debug event. */
svc::DebugEventInfo d;
R_ABORT_UNLESS(svc::GetDebugEvent(std::addressof(d), m_debug_handle));
/* Handle the debug event. */
switch (d.type) {
case svc::DebugEvent_CreateProcess:
{
/* Set our create process info. */
m_create_process_info = d.info.create_process;
/* Cache our bools. */
m_is_64_bit = (m_create_process_info.flags & svc::CreateProcessFlag_Is64Bit);
m_is_64_bit_address_space = (m_create_process_info.flags & svc::CreateProcessFlag_AddressSpaceMask) == svc::CreateProcessFlag_AddressSpace64Bit;
}
break;
case svc::DebugEvent_CreateThread:
{
++num_threads;
if (const s32 index = this->ThreadCreate(d.thread_id); index >= 0) {
const Result result = osdbg::InitializeThreadInfo(std::addressof(m_thread_infos[index]), m_debug_handle, std::addressof(m_create_process_info), std::addressof(d.info.create_thread));
if (R_FAILED(result)) {
AMS_DMNT2_GDB_LOG_WARN("DebugProcess::Start: InitializeThreadInfo(%lx) failed: %08x\n", d.thread_id, result.GetValue());
}
}
}
break;
case svc::DebugEvent_ExitThread:
{
--num_threads;
this->ThreadExit(d.thread_id);
}
break;
case svc::DebugEvent_Exception:
{
if (d.info.exception.type == svc::DebugException_DebuggerAttached) {
attached = true;
}
}
break;
default:
break;
}
}
/* Set ourselves as valid. */
m_is_valid = true;
this->SetDebugBreaked();
return ResultSuccess();
}
s32 DebugProcess::ThreadCreate(u64 thread_id) {
for (size_t i = 0; i < ThreadCountMax; ++i) {
if (!m_thread_valid[i]) {
m_thread_valid[i] = true;
m_thread_ids[i] = thread_id;
this->SetLastThreadId(thread_id);
this->SetLastSignal(GdbSignal_BreakpointTrap);
++m_thread_count;
return i;
}
}
return -1;
}
void DebugProcess::ThreadExit(u64 thread_id) {
for (size_t i = 0; i < ThreadCountMax; ++i) {
if (m_thread_valid[i] && m_thread_ids[i] == thread_id) {
m_thread_valid[i] = false;
m_thread_ids[i] = 0;
this->SetLastThreadId(thread_id);
this->SetLastSignal(GdbSignal_BreakpointTrap);
--m_thread_count;
break;
}
}
}
Result DebugProcess::CollectModules() {
/* Reset our module count. */
m_module_count = 0;
/* Traverse the address space, looking for modules. */
uintptr_t address = 0;
while (true) {
/* Query the current address. */
svc::MemoryInfo memory_info;
svc::PageInfo page_info;
if (R_SUCCEEDED(svc::QueryDebugProcessMemory(std::addressof(memory_info), std::addressof(page_info), m_debug_handle, address))) {
if (memory_info.perm == svc::MemoryPermission_ReadExecute && (memory_info.state == svc::MemoryState_Code || memory_info.state == svc::MemoryState_AliasCode)) {
/* Check that we can add the module. */
AMS_ABORT_UNLESS(m_module_count < ModuleCountMax);
/* Get module definition. */
auto &module = m_module_definitions[m_module_count++];
/* Set module address/size. */
module.SetAddressSize(memory_info.addr, memory_info.size);
/* Get module name buffer. */
char *module_name = module.GetNameBuffer();
module_name[0] = 0;
/* Read module path. */
struct {
u32 zero;
s32 path_length;
char path[ModuleDefinition::PathLengthMax];
} module_path;
if (R_SUCCEEDED(this->ReadMemory(std::addressof(module_path), memory_info.addr + memory_info.size, sizeof(module_path)))) {
if (module_path.zero == 0 && module_path.path_length == util::Strnlen(module_path.path, sizeof(module_path.path))) {
std::memcpy(module_name, module_path.path, ModuleDefinition::PathLengthMax);
}
}
/* Truncate module name. */
module_name[ModuleDefinition::PathLengthMax - 1] = 0;
}
}
/* Check if we're done. */
const uintptr_t next_address = memory_info.addr + memory_info.size;
if (memory_info.state == svc::MemoryState_Inaccessible) {
break;
}
if (next_address <= address) {
break;
}
address = next_address;
}
return ResultSuccess();
}
Result DebugProcess::GetThreadContext(svc::ThreadContext *out, u64 thread_id, u32 flags) {
return svc::GetDebugThreadContext(out, m_debug_handle, thread_id, flags);
}
Result DebugProcess::SetThreadContext(const svc::ThreadContext *ctx, u64 thread_id, u32 flags) {
return svc::SetDebugThreadContext(m_debug_handle, thread_id, ctx, flags);
}
Result DebugProcess::ReadMemory(void *dst, uintptr_t address, size_t size) {
return svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(dst), m_debug_handle, address, size);
}
Result DebugProcess::WriteMemory(const void *src, uintptr_t address, size_t size) {
return svc::WriteDebugProcessMemory(m_debug_handle, reinterpret_cast<uintptr_t>(src), address, size);
}
Result DebugProcess::Continue() {
AMS_DMNT2_GDB_LOG_DEBUG("DebugProcess::Continue() all\n");
u64 thread_ids[] = { 0 };
R_TRY(svc::ContinueDebugEvent(m_debug_handle, svc::ContinueFlag_ExceptionHandled | svc::ContinueFlag_EnableExceptionEvent | svc::ContinueFlag_ContinueAll, thread_ids, util::size(thread_ids)));
m_continue_thread_id = 0;
m_status = ProcessStatus_Running;
this->SetLastThreadId(0);
this->SetLastSignal(GdbSignal_Signal0);
return ResultSuccess();
}
Result DebugProcess::Continue(u64 thread_id) {
AMS_DMNT2_GDB_LOG_DEBUG("DebugProcess::Continue() thread_id=%lx\n", thread_id);
u64 thread_ids[] = { thread_id };
R_TRY(svc::ContinueDebugEvent(m_debug_handle, svc::ContinueFlag_ExceptionHandled | svc::ContinueFlag_EnableExceptionEvent, thread_ids, util::size(thread_ids)));
m_continue_thread_id = thread_id;
m_status = ProcessStatus_Running;
this->SetLastThreadId(0);
this->SetLastSignal(GdbSignal_Signal0);
return ResultSuccess();
}
Result DebugProcess::Step() {
AMS_DMNT2_GDB_LOG_DEBUG("DebugProcess::Step() all\n");
return this->Step(this->GetLastThreadId());
}
Result DebugProcess::Step(u64 thread_id) {
AMS_DMNT2_GDB_LOG_DEBUG("DebugProcess::Step() thread_id=%lx\n", thread_id);
/* Get the thread context. */
svc::ThreadContext ctx;
R_TRY(this->GetThreadContext(std::addressof(ctx), thread_id, svc::ThreadContextFlag_Control));
/* Note that we're stepping. */
m_stepping = true;
if (m_use_hardware_single_step) {
/* Set thread single step. */
R_TRY(this->SetThreadContext(std::addressof(ctx), thread_id, svc::ThreadContextFlag_SetSingleStep));
} else {
/* Determine where we're stepping to. */
u64 current_pc = ctx.pc;
u64 step_target = 0;
this->GetBranchTarget(ctx, thread_id, current_pc, step_target);
/* Ensure we end with valid breakpoints. */
auto bp_guard = SCOPE_GUARD { this->ClearStep(); };
/* Set step breakpoint on current pc. */
/* TODO: aarch32 breakpoints. */
if (current_pc) {
R_TRY(m_step_breakpoints.SetBreakPoint(current_pc, sizeof(u32), true));
}
if (step_target) {
R_TRY(m_step_breakpoints.SetBreakPoint(step_target, sizeof(u32), true));
}
bp_guard.Cancel();
}
return ResultSuccess();
}
void DebugProcess::ClearStep() {
/* If we should, clear our step breakpoints. */
if (m_stepping) {
m_step_breakpoints.ClearStep();
m_stepping = false;
}
}
Result DebugProcess::Break() {
if (this->GetStatus() == ProcessStatus_Running) {
AMS_DMNT2_GDB_LOG_DEBUG("DebugProcess::Break\n");
return svc::BreakDebugProcess(m_debug_handle);
} else {
AMS_DMNT2_GDB_LOG_ERROR("DebugProcess::Break called on non-running process!\n");
return ResultSuccess();
}
}
Result DebugProcess::Terminate() {
if (this->IsValid()) {
R_ABORT_UNLESS(svc::TerminateDebugProcess(m_debug_handle));
this->Detach();
}
return ResultSuccess();
}
void DebugProcess::GetBranchTarget(svc::ThreadContext &ctx, u64 thread_id, u64 &current_pc, u64 &target) {
/* Save pc, in case we modify it. */
const u64 pc = current_pc;
/* Clear the target. */
target = 0;
/* By default, we advance by four. */
current_pc += 4;
/* Get the instruction where we were. */
u32 insn = 0;
this->ReadMemory(std::addressof(insn), pc, sizeof(insn));
/* Handle by architecture. */
bool is_call = false;
if (this->Is64Bit()) {
if ((insn & 0x7C000000) == 0x14000000) {
/* Unconditional branch (b/bl) */
if (insn != 0x14000001) {
is_call = (insn & 0x80000000) == 0x80000000;
current_pc = 0;
target = SignExtend(((insn & 0x03FFFFFF) << 2), 28) + pc;
}
} else if ((insn & 0x7E000000) == 0x34000000) {
/* Compare/Branch (cbz/cbnz) */
target = SignExtend(((insn & 0x00FFFFE0) >> 3), 21) + pc;
} else if ((insn & 0x7E000000) == 0x36000000) {
/* Test and branch (tbz/tbnz) */
target = SignExtend(((insn & 0x0007FFE0) >> 3), 16) + pc;
} else if ((insn & 0xFF000010) == 0x54000000) {
/* Conditional branch (b.*) */
if ((insn & 0xF) == 0xE) {
/* Unconditional. */
current_pc = 0;
}
target = SignExtend(((insn & 0x00FFFFE0) >> 3), 21) + pc;
} else if ((insn & 0xFF8FFC1F) == 0xD60F0000) {
/* Unconditional branch */
is_call = (insn & 0x00F00000) == 0x00300000;
if (!is_call) {
current_pc = 0;
}
/* Get the register. */
svc::ThreadContext new_ctx;
if (R_SUCCEEDED(this->GetThreadContext(std::addressof(new_ctx), thread_id, svc::ThreadContextFlag_Control | svc::ThreadContextFlag_General))) {
const int reg = (insn & 0x03E0) >> 5;
if (reg < 29) {
target = new_ctx.r[reg];
} else if (reg == 29) {
target = new_ctx.fp;
} else if (reg == 30) {
target = new_ctx.lr;
} else if (reg == 31) {
target = new_ctx.sp;
}
}
}
} else {
/* TODO aarch32 branch decoding */
}
}
Result DebugProcess::SetBreakPoint(uintptr_t address, size_t size, bool is_step) {
return m_software_breakpoints.SetBreakPoint(address, size, is_step);
}
Result DebugProcess::ClearBreakPoint(uintptr_t address, size_t size) {
m_software_breakpoints.ClearBreakPoint(address, size);
return ResultSuccess();
}
Result DebugProcess::SetHardwareBreakPoint(uintptr_t address, size_t size, bool is_step) {
return m_hardware_breakpoints.SetBreakPoint(address, size, is_step);
}
Result DebugProcess::ClearHardwareBreakPoint(uintptr_t address, size_t size) {
m_hardware_breakpoints.ClearBreakPoint(address, size);
return ResultSuccess();
}
Result DebugProcess::SetWatchPoint(u64 address, u64 size, bool read, bool write) {
return m_hardware_watchpoints.SetWatchPoint(address, size, read, write);
}
Result DebugProcess::ClearWatchPoint(u64 address, u64 size) {
return m_hardware_watchpoints.ClearBreakPoint(address, size);
}
Result DebugProcess::GetWatchPointInfo(u64 address, bool &read, bool &write) {
return m_hardware_watchpoints.GetWatchPointInfo(address, read, write);
}
bool DebugProcess::IsValidWatchPoint(u64 address, u64 size) {
return HardwareWatchPointManager::IsValidWatchPoint(address, size);
}
Result DebugProcess::GetThreadCurrentCore(u32 *out, u64 thread_id) {
u64 dummy_value;
u32 val32 = 0;
R_TRY(svc::GetDebugThreadParam(std::addressof(dummy_value), std::addressof(val32), m_debug_handle, thread_id, svc::DebugThreadParam_CurrentCore));
*out = val32;
return ResultSuccess();
}
Result DebugProcess::GetProcessDebugEvent(svc::DebugEventInfo *out) {
/* Get the event. */
R_TRY(svc::GetDebugEvent(out, m_debug_handle));
/* Process the event. */
switch (out->type) {
case svc::DebugEvent_CreateProcess:
{
/* Set our create process info. */
m_create_process_info = out->info.create_process;
/* Cache our bools. */
m_is_64_bit = (m_create_process_info.flags & svc::CreateProcessFlag_Is64Bit);
m_is_64_bit_address_space = (m_create_process_info.flags & svc::CreateProcessFlag_AddressSpaceMask) == svc::CreateProcessFlag_AddressSpace64Bit;
}
break;
case svc::DebugEvent_CreateThread:
{
if (const s32 index = this->ThreadCreate(out->thread_id); index >= 0) {
const Result result = osdbg::InitializeThreadInfo(std::addressof(m_thread_infos[index]), m_debug_handle, std::addressof(m_create_process_info), std::addressof(out->info.create_thread));
if (R_FAILED(result)) {
AMS_DMNT2_GDB_LOG_WARN("DebugProcess::GetProcessDebugEvent: InitializeThreadInfo(%lx) failed: %08x\n", out->thread_id, result.GetValue());
}
}
}
break;
case svc::DebugEvent_ExitThread:
{
this->ThreadExit(out->thread_id);
}
break;
default:
break;
}
if (out->flags & svc::DebugEventFlag_Stopped) {
this->SetDebugBreaked();
}
return ResultSuccess();
}
u64 DebugProcess::GetLastThreadId() {
/* Select our first valid thread id. */
if (m_last_thread_id == 0) {
for (size_t i = 0; i < ThreadCountMax; ++i) {
if (m_thread_valid[i]) {
SetLastThreadId(m_thread_ids[i]);
break;
}
}
}
return m_last_thread_id;
}
Result DebugProcess::GetThreadList(s32 *out_count, u64 *out_thread_ids, size_t max_count) {
s32 count = 0;
for (size_t i = 0; i < ThreadCountMax; ++i) {
if (m_thread_valid[i]) {
if (count < static_cast<s32>(max_count)) {
out_thread_ids[count++] = m_thread_ids[i];
}
}
}
*out_count = count;
return ResultSuccess();
}
Result DebugProcess::GetThreadInfoList(s32 *out_count, osdbg::ThreadInfo **out_infos, size_t max_count) {
s32 count = 0;
for (size_t i = 0; i < ThreadCountMax; ++i) {
if (m_thread_valid[i]) {
if (count < static_cast<s32>(max_count)) {
out_infos[count++] = std::addressof(m_thread_infos[i]);
}
}
}
*out_count = count;
return ResultSuccess();
}
}

View File

@@ -0,0 +1,156 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "dmnt2_gdb_signal.hpp"
#include "dmnt2_module_definition.hpp"
#include "dmnt2_software_breakpoint.hpp"
#include "dmnt2_hardware_breakpoint.hpp"
#include "dmnt2_hardware_watchpoint.hpp"
namespace ams::dmnt {
class DebugProcess {
public:
static constexpr size_t ThreadCountMax = 0x100;
static constexpr size_t ModuleCountMax = 0x60;
enum ProcessStatus {
ProcessStatus_DebugBreak,
ProcessStatus_Running,
ProcessStatus_Exited,
};
enum ContinueMode {
ContinueMode_Stopped,
ContinueMode_Continue,
ContinueMode_Step,
};
private:
svc::Handle m_debug_handle{svc::InvalidHandle};
s32 m_thread_count{0};
bool m_is_valid{false};
bool m_is_64_bit{false};
bool m_is_64_bit_address_space{false};
ProcessStatus m_status{ProcessStatus_DebugBreak};
os::ProcessId m_process_id{os::InvalidProcessId};
u64 m_last_thread_id{};
u64 m_thread_id_override{};
u64 m_continue_thread_id{};
GdbSignal m_last_signal{};
bool m_stepping{false};
bool m_use_hardware_single_step{false};
bool m_thread_valid[ThreadCountMax]{};
u64 m_thread_ids[ThreadCountMax]{};
osdbg::ThreadInfo m_thread_infos[ThreadCountMax]{};
svc::DebugInfoCreateProcess m_create_process_info{};
SoftwareBreakPointManager m_software_breakpoints;
HardwareBreakPointManager m_hardware_breakpoints;
HardwareWatchPointManager m_hardware_watchpoints;
BreakPointManager &m_step_breakpoints;
ModuleDefinition m_module_definitions[ModuleCountMax]{};
size_t m_module_count{};
size_t m_main_module{};
public:
DebugProcess() : m_software_breakpoints(this), m_hardware_breakpoints(this), m_hardware_watchpoints(this), m_step_breakpoints(m_software_breakpoints) {
if (svc::IsKernelMesosphere()) {
uint64_t value = 0;
m_use_hardware_single_step = R_SUCCEEDED(::ams::svc::GetInfo(std::addressof(value), ::ams::svc::InfoType_MesosphereMeta, ::ams::svc::InvalidHandle, ::ams::svc::MesosphereMetaInfo_IsSingleStepEnabled)) && value != 0;
}
}
~DebugProcess() { this->Detach(); }
svc::Handle GetHandle() const { return m_debug_handle; }
bool IsValid() const { return m_is_valid; }
bool Is64Bit() const { return m_is_64_bit; }
bool Is64BitAddressSpace() const { return m_is_64_bit_address_space; }
size_t GetModuleCount() const { return m_module_count; }
size_t GetMainModuleIndex() const { return m_main_module; }
const char *GetModuleName(size_t ix) const { return m_module_definitions[ix].GetName(); }
uintptr_t GetBaseAddress(size_t ix) const { return m_module_definitions[ix].GetAddress(); }
ProcessStatus GetStatus() const { return m_status; }
os::ProcessId GetProcessId() const { return m_process_id; }
const char *GetProcessName() const { return m_create_process_info.name; }
void SetLastSignal(GdbSignal signal) { m_last_signal = signal; }
GdbSignal GetLastSignal() const { return m_last_signal; }
Result GetThreadList(s32 *out_count, u64 *out_thread_ids, size_t max_count);
Result GetThreadInfoList(s32 *out_count, osdbg::ThreadInfo **out_infos, size_t max_count);
u64 GetLastThreadId();
u64 GetThreadIdOverride() { this->GetLastThreadId(); return m_thread_id_override; }
void SetLastThreadId(u64 tid) {
m_last_thread_id = tid;
m_thread_id_override = tid;
}
void SetThreadIdOverride(u64 tid) {
m_thread_id_override = tid;
}
void SetDebugBreaked() {
m_status = ProcessStatus_DebugBreak;
}
public:
Result Attach(os::ProcessId process_id);
void Detach();
Result GetThreadContext(svc::ThreadContext *out, u64 thread_id, u32 flags);
Result SetThreadContext(const svc::ThreadContext *ctx, u64 thread_id, u32 flags);
Result ReadMemory(void *dst, uintptr_t address, size_t size);
Result WriteMemory(const void *src, uintptr_t address, size_t size);
Result Continue();
Result Continue(u64 thread_id);
Result Step();
Result Step(u64 thread_id);
void ClearStep();
Result Break();
Result Terminate();
Result SetBreakPoint(uintptr_t address, size_t size, bool is_step);
Result ClearBreakPoint(uintptr_t address, size_t size);
Result SetHardwareBreakPoint(uintptr_t address, size_t size, bool is_step);
Result ClearHardwareBreakPoint(uintptr_t address, size_t size);
Result SetWatchPoint(u64 address, u64 size, bool read, bool write);
Result ClearWatchPoint(u64 address, u64 size);
Result GetWatchPointInfo(u64 address, bool &read, bool &write);
static bool IsValidWatchPoint(u64 address, u64 size);
Result GetThreadCurrentCore(u32 *out, u64 thread_id);
Result GetProcessDebugEvent(svc::DebugEventInfo *out);
void GetBranchTarget(svc::ThreadContext &ctx, u64 thread_id, u64 &current_pc, u64 &target);
Result CollectModules();
private:
Result Start();
s32 ThreadCreate(u64 thread_id);
void ThreadExit(u64 thread_id);
};
}

View File

@@ -0,0 +1,194 @@
/*
* 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 <stratosphere.hpp>
#include "dmnt2_gdb_packet_io.hpp"
#include "dmnt2_debug_log.hpp"
namespace ams::dmnt {
namespace {
constexpr char BreakCharacter = '\x03'; /* ctrl-c */
constexpr int DecodeHex(char c) {
if ('a' <= c && c <= 'f') {
return 10 + (c - 'a');
} else if ('A' <= c && c <= 'F') {
return 10 + (c - 'A');
} else if ('0' <= c && c <= '9') {
return 0 + (c - '0');
} else {
return -1;
}
}
constexpr char EncodeHex(u8 v) {
return "0123456789abcdef"[v & 0xF];
}
}
void GdbPacketIo::SendPacket(bool *out_break, const char *src, HtcsSession *session) {
/* Default to not breaked. */
*out_break = false;
/* Send a packet. */
while (true) {
std::scoped_lock lk(m_mutex);
size_t len = 0;
u8 checksum = 0;
while (src[len] != 0) {
checksum += static_cast<u8>(src[len++]);
}
char buffer[1 + GdbPacketBufferSize + 4];
buffer[0] = '$';
std::memcpy(buffer + 1, src, len);
buffer[1 + len] = '#';
buffer[2 + len] = EncodeHex(checksum >> 4);
buffer[3 + len] = EncodeHex(checksum >> 0);
buffer[4 + len] = 0;
if (session->PutString(buffer) < 0) {
/* Log (truncated) copy of packet. */
AMS_DMNT2_GDB_LOG_ERROR("Failed to send packet %s\n", buffer);
return;
}
if (m_no_ack) {
return;
}
/* Check to see if we need to retransmit. */
bool retransmit = false;
do {
if (const auto char_holder = session->GetChar(); char_holder) {
switch (*char_holder) {
case BreakCharacter:
*out_break = true;
return;
case '+':
return;
case '-':
retransmit = true;
break;
default:
break;
}
} else {
/* Log (truncated) copy of packet. */
AMS_DMNT2_GDB_LOG_ERROR("Failed to receive ack for %s\n", buffer);
return;
}
} while (!retransmit);
}
}
char *GdbPacketIo::ReceivePacket(bool *out_break, char *dst, size_t size, HtcsSession *session) {
/* Default to not breaked. */
*out_break = false;
/* Receive a packet. */
while (true) {
/* Wait for data to be available. */
if (!session->WaitToBeReadable()) {
return nullptr;
}
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Verify that we still have data immediately available. */
if (!session->WaitToBeReadable(0)) {
continue;
}
/* Prepare to parse. */
enum class State {
Initial,
PacketData,
ChecksumHigh,
ChecksumLow
};
State state = State::Initial;
u8 checksum = 0;
int csum_high = -1, csum_low = -1;
size_t count = 0;
/* Read characters. */
while (true) {
if (const auto char_holder = session->GetChar(); char_holder) {
const auto c = *char_holder;
switch (state) {
case State::Initial:
if (c == '$') {
state = State::PacketData;
} else if (c == BreakCharacter) {
*out_break = true;
return nullptr;
}
break;
case State::PacketData:
/* TODO: Escaped characters. */
if (c == '#') {
dst[count] = 0;
state = State::ChecksumHigh;
} else {
AMS_ABORT_UNLESS(count < size - 1);
checksum += static_cast<u8>(c);
dst[count++] = c;
}
break;
case State::ChecksumHigh:
csum_high = DecodeHex(c);
state = State::ChecksumLow;
break;
case State::ChecksumLow:
csum_low = DecodeHex(c);
if (m_no_ack) {
return dst;
} else {
const u8 expectsum = (static_cast<u8>(csum_high) << 4) | (static_cast<u8>(csum_low) << 0);
if (csum_high < 0 || csum_low < 0 || checksum != expectsum) {
/* Request retransmission. */
state = State::Initial;
checksum = 0;
csum_high = -1;
csum_low = -1;
count = 0;
session->PutChar('-');
} else {
session->PutChar('+');
return dst;
}
}
break;
}
} else {
return nullptr;
}
}
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "dmnt2_htcs_session.hpp"
namespace ams::dmnt {
static constexpr size_t GdbPacketBufferSize = 16_KB;
class GdbPacketIo {
private:
os::SdkMutex m_mutex;
bool m_no_ack;
public:
GdbPacketIo() : m_mutex(), m_no_ack(false) { /* ... */ }
void SetNoAck() { m_no_ack = true; }
void SendPacket(bool *out_break, const char *src, HtcsSession *session);
char *ReceivePacket(bool *out_break, char *dst, size_t size, HtcsSession *session);
};
}

View File

@@ -0,0 +1,94 @@
/*
* 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 <stratosphere.hpp>
#include "dmnt2_debug_log.hpp"
#include "dmnt2_gdb_server.hpp"
#include "dmnt2_gdb_server_impl.hpp"
namespace ams::dmnt {
namespace {
constexpr size_t ServerThreadStackSize = util::AlignUp(4 * GdbPacketBufferSize + os::MemoryPageSize, os::ThreadStackAlignment);
alignas(os::ThreadStackAlignment) constinit u8 g_server_thread_stack[ServerThreadStackSize];
alignas(os::ThreadStackAlignment) constinit u8 g_events_thread_stack[util::AlignUp(2 * GdbPacketBufferSize + os::MemoryPageSize, os::ThreadStackAlignment)];
constinit os::ThreadType g_server_thread;
constinit util::TypedStorage<GdbServerImpl> g_gdb_server;
void GdbServerThreadFunction(void *) {
/* Loop forever, servicing our gdb server. */
while (true) {
/* Get a socket. */
int fd;
while ((fd = htcs::Socket()) == -1) {
os::SleepThread(TimeSpan::FromSeconds(1));
}
/* Ensure we cleanup the socket when we're done with it. */
ON_SCOPE_EXIT {
htcs::Close(fd);
os::SleepThread(TimeSpan::FromSeconds(1));
};
/* Create a sock addr for our server. */
htcs::SockAddrHtcs addr;
addr.family = htcs::HTCS_AF_HTCS;
addr.peer_name = htcs::GetPeerNameAny();
std::strcpy(addr.port_name.name, "iywys@$gdb");
/* Bind. */
if (htcs::Bind(fd, std::addressof(addr)) == -1) {
continue;
}
/* Listen on our port. */
while (htcs::Listen(fd, 0) == 0) {
/* Continue accepting clients, so long as we can. */
int client_fd;
while (true) {
/* Try to accept a client. */
if (client_fd = htcs::Accept(fd, std::addressof(addr)); client_fd < 0) {
break;
}
{
/* Create gdb server for the socket. */
util::ConstructAt(g_gdb_server, client_fd, g_events_thread_stack, sizeof(g_events_thread_stack));
ON_SCOPE_EXIT { util::DestroyAt(g_gdb_server); };
/* Process for the server. */
util::GetReference(g_gdb_server).LoopProcess();
}
/* Close the client socket. */
htcs::Close(client_fd);
}
}
}
}
}
void InitializeGdbServer() {
/* Create and start gdb server threads. */
R_ABORT_UNLESS(os::CreateThread(std::addressof(g_server_thread), GdbServerThreadFunction, nullptr, g_server_thread_stack, sizeof(g_server_thread_stack), os::HighestThreadPriority - 1));
os::StartThread(std::addressof(g_server_thread));
}
}

View File

@@ -0,0 +1,23 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::dmnt {
void InitializeGdbServer();
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,115 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "dmnt2_gdb_packet_io.hpp"
#include "dmnt2_gdb_signal.hpp"
#include "dmnt2_debug_process.hpp"
namespace ams::dmnt {
class GdbServerImpl {
private:
enum class State {
Initial,
Running,
Exited,
Destroyed,
};
private:
int m_socket;
HtcsSession m_session;
GdbPacketIo m_packet_io;
char *m_receive_packet{nullptr};
char *m_reply_packet{nullptr};
char m_buffer[GdbPacketBufferSize / 2];
bool m_killed{false};
os::ThreadType m_events_thread;
State m_state;
DebugProcess m_debug_process;
os::ProcessId m_process_id{os::InvalidProcessId};
os::Event m_event;
public:
GdbServerImpl(int socket, void *thread_stack, size_t stack_size);
~GdbServerImpl();
void LoopProcess();
private:
void ProcessPacket(char *receive, char *reply);
void SendPacket(bool *out_break, const char *src) { return m_packet_io.SendPacket(out_break, src, std::addressof(m_session)); }
char *ReceivePacket(bool *out_break, char *dst, size_t size) { return m_packet_io.ReceivePacket(out_break, dst, size, std::addressof(m_session)); }
private:
bool HasDebugProcess() const { return m_debug_process.IsValid(); }
bool Is64Bit() const { return m_debug_process.Is64Bit(); }
bool Is64BitAddressSpace() const { return m_debug_process.Is64BitAddressSpace(); }
private:
static void DebugEventsThreadEntry(void *arg) { static_cast<GdbServerImpl *>(arg)->DebugEventsThread(); }
void DebugEventsThread();
void ProcessDebugEvents();
void SetStopReplyPacket(GdbSignal signal);
private:
void D();
void G();
void H();
void Hg();
void M();
void P();
void Q();
void T();
void Z();
void c();
bool g();
void k();
void m();
void p();
void v();
void vAttach();
void vCont();
void q();
void qAttached();
void qC();
void qSupported();
void qXfer();
void qXferFeaturesRead();
void qXferLibrariesRead();
void qXferOsdataRead();
bool qXferThreadsRead();
void z();
void QuestionMark();
private:
Result ParseVCont(char * const token, u64 *thread_ids, u8 *continue_modes, s32 num_threads, DebugProcess::ContinueMode &default_continue_mode);
};
}

View File

@@ -0,0 +1,34 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::dmnt {
enum GdbSignal {
GdbSignal_Signal0 = 0,
GdbSignal_Interrupt = 2,
GdbSignal_IllegalInstruction = 4,
GdbSignal_BreakpointTrap = 5,
GdbSignal_EmulationTrap = 7,
GdbSignal_ArithmeticException = 8,
GdbSignal_Killed = 9,
GdbSignal_BusError = 10,
GdbSignal_SegmentationFault = 11,
GdbSignal_BadSystemCall = 12,
};
}

View File

@@ -0,0 +1,235 @@
/*
* 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 <stratosphere.hpp>
#include "dmnt2_hardware_breakpoint.hpp"
#include "dmnt2_debug_process.hpp"
#include "dmnt2_debug_log.hpp"
namespace ams::dmnt {
namespace {
constinit auto g_last_bp_register = -1;
constinit auto g_first_bp_ctx_register = -1;
constinit auto g_last_bp_ctx_register = -1;
constinit auto g_last_wp_register = -1;
constinit os::SdkMutex g_multicore_lock;
constinit bool g_multicore_started = false;
alignas(os::ThreadStackAlignment) constinit u8 g_multicore_thread_stack[16_KB];
constinit os::ThreadType g_multicore_thread;
constinit os::MessageQueueType g_multicore_request_queue;
constinit os::MessageQueueType g_multicore_response_queue;
constinit uintptr_t g_multicore_request_storage[2];
constinit uintptr_t g_multicore_response_storage[2];
void MultiCoreThread(void *) {
/* Get thread core mask. */
s32 cur_core;
u64 core_mask;
R_ABORT_UNLESS(svc::GetThreadCoreMask(std::addressof(cur_core), std::addressof(core_mask), svc::PseudoHandle::CurrentThread));
/* Service requests. */
while (true) {
/* Wait for a request to come in. */
uintptr_t request_v;
os::ReceiveMessageQueue(std::addressof(request_v), std::addressof(g_multicore_request_queue));
/* Process the request. */
const uintptr_t *request = reinterpret_cast<const uintptr_t *>(request_v);
bool success = true;
if (request[0] == 1) {
/* Set on each core. */
for (s32 core = 0; core < 4; ++core) {
/* Switch to the desired core. */
R_ABORT_UNLESS(svc::SetThreadCoreMask(svc::PseudoHandle::CurrentThread, core, (1 << core)));
/* Get the core mask. */
R_ABORT_UNLESS(svc::GetThreadCoreMask(std::addressof(cur_core), std::addressof(core_mask), svc::PseudoHandle::CurrentThread));
/* Set the breakpoint. */
const Result result = svc::SetHardwareBreakPoint(static_cast<svc::HardwareBreakPointRegisterName>(request[1]), request[2], request[3]);
if (R_FAILED(result)) {
success = false;
AMS_DMNT2_GDB_LOG_ERROR("SetHardwareBreakPoint FAIL 0x%08x, core=%d, reg=%lu, ctrl=%lx, val=%lx\n", result.GetValue(), core, request[1], request[2], request[3]);
break;
}
}
}
os::SendMessageQueue(std::addressof(g_multicore_response_queue), static_cast<uintptr_t>(success));
}
}
void EnsureMultiCoreStarted() {
std::scoped_lock lk(g_multicore_lock);
if (!g_multicore_started) {
os::InitializeMessageQueue(std::addressof(g_multicore_request_queue), g_multicore_request_storage, util::size(g_multicore_request_storage));
os::InitializeMessageQueue(std::addressof(g_multicore_response_queue), g_multicore_response_storage, util::size(g_multicore_response_storage));
R_ABORT_UNLESS(os::CreateThread(std::addressof(g_multicore_thread), MultiCoreThread, nullptr, g_multicore_thread_stack, sizeof(g_multicore_thread_stack), os::HighestThreadPriority - 1));
os::StartThread(std::addressof(g_multicore_thread));
g_multicore_started = true;
}
}
}
Result HardwareBreakPoint::Clear(DebugProcess *debug_process) {
Result result = svc::ResultInvalidArgument();
if (m_in_use) {
AMS_DMNT2_GDB_LOG_DEBUG("HardwareBreakPoint::Clear %p 0x%lx\n", this, m_address);
result = HardwareBreakPointManager::SetExecutionBreakPoint(m_reg, m_ctx, 0);
this->Reset();
}
return result;
}
Result HardwareBreakPoint::Set(DebugProcess *debug_process, uintptr_t address, size_t size, bool is_step) {
/* Set fields. */
m_is_step = is_step;
m_address = address;
m_size = size;
/* Set context breakpoint. */
R_TRY(HardwareBreakPointManager::SetContextBreakPoint(m_ctx, debug_process));
/* Set execution breakpoint. */
R_TRY(HardwareBreakPointManager::SetExecutionBreakPoint(m_reg, m_ctx, address));
/* Set as in-use. */
m_in_use = true;
return ResultSuccess();
}
HardwareBreakPointManager::HardwareBreakPointManager(DebugProcess *debug_process) : BreakPointManager(debug_process) {
/* Determine the number of breakpoint registers. */
CountBreakPointRegisters();
/* Initialize all breakpoints. */
for (size_t i = 0; i < util::size(m_breakpoints); ++i) {
m_breakpoints[i].Initialize(static_cast<svc::HardwareBreakPointRegisterName>(svc::HardwareBreakPointRegisterName_I0 + i), static_cast<svc::HardwareBreakPointRegisterName>(g_first_bp_ctx_register));
}
}
BreakPointBase *HardwareBreakPointManager::GetBreakPoint(size_t index) {
if (index < util::size(m_breakpoints)) {
return m_breakpoints + index;
} else {
return nullptr;
}
}
Result HardwareBreakPointManager::SetHardwareBreakPoint(u32 r, u64 dbgbcr, u64 value) {
/* Send request. */
const uintptr_t request[4] = {
1,
r,
dbgbcr,
value
};
R_UNLESS(SendMultiCoreRequest(request), dmnt::ResultUnknown());
return ResultSuccess();
}
Result HardwareBreakPointManager::SetContextBreakPoint(svc::HardwareBreakPointRegisterName ctx, DebugProcess *debug_process) {
/* Encode the register. */
const u64 dbgbcr = (0x3 << 20) | (0 << 16) | (0xF << 5) | 1;
const Result result = SetHardwareBreakPoint(ctx, dbgbcr, debug_process->GetHandle());
if (R_FAILED(result)) {
AMS_DMNT2_GDB_LOG_ERROR("SetContextBreakPoint FAIL 0x%08x ctx=%d\n", result.GetValue(), ctx);
}
return result;
}
svc::HardwareBreakPointRegisterName HardwareBreakPointManager::GetWatchPointContextRegister() {
CountBreakPointRegisters();
return static_cast<svc::HardwareBreakPointRegisterName>(g_first_bp_ctx_register + 1);
}
Result HardwareBreakPointManager::SetExecutionBreakPoint(svc::HardwareBreakPointRegisterName reg, svc::HardwareBreakPointRegisterName ctx, u64 address) {
/* Encode the register. */
const u64 dbgbcr = (0x1 << 20) | (ctx << 16) | (0xF << 5) | ((address != 0) ? 1 : 0);
const Result result = SetHardwareBreakPoint(reg, dbgbcr, address);
if (R_FAILED(result)) {
AMS_DMNT2_GDB_LOG_ERROR("SetContextBreakPoint FAIL 0x%08x reg=%d, ctx=%d, address=%lx\n", result.GetValue(), reg, ctx, address);
}
return result;
}
void HardwareBreakPointManager::CountBreakPointRegisters() {
/* Determine the valid breakpoint extents. */
if (g_last_bp_ctx_register == -1) {
/* Keep setting until we see a failure. */
for (int i = svc::HardwareBreakPointRegisterName_I0; i <= static_cast<int>(svc::HardwareBreakPointRegisterName_I15); ++i) {
if (R_FAILED(svc::SetHardwareBreakPoint(static_cast<svc::HardwareBreakPointRegisterName>(i), 0, 0))) {
break;
}
g_last_bp_register = i;
}
AMS_DMNT2_GDB_LOG_DEBUG("Last valid breakpoint=%d\n", g_last_bp_register);
/* Determine the context register range. */
const u64 dbgbcr = (0x3 << 20) | (0x0 << 16) | (0xF << 5) | 1;
g_last_bp_ctx_register = g_last_bp_register;
for (int i = g_last_bp_ctx_register; i >= static_cast<int>(svc::HardwareBreakPointRegisterName_I0); --i) {
const Result result = svc::SetHardwareBreakPoint(static_cast<svc::HardwareBreakPointRegisterName>(i), dbgbcr, svc::PseudoHandle::CurrentProcess);
svc::SetHardwareBreakPoint(static_cast<svc::HardwareBreakPointRegisterName>(i), 0, 0);
if (R_FAILED(result)) {
if (!svc::ResultInvalidHandle::Includes(result)) {
break;
}
}
g_first_bp_ctx_register = i;
}
AMS_DMNT2_GDB_LOG_DEBUG("Context BreakPoints = %d-%d\n", g_first_bp_ctx_register, g_last_bp_ctx_register);
/* Determine valid watchpoint registers. */
for (int i = svc::HardwareBreakPointRegisterName_D0; i <= static_cast<int>(svc::HardwareBreakPointRegisterName_D15); ++i) {
if (R_FAILED(svc::SetHardwareBreakPoint(static_cast<svc::HardwareBreakPointRegisterName>(i), 0, 0))) {
break;
}
g_last_wp_register = i - svc::HardwareBreakPointRegisterName_D0;
}
AMS_DMNT2_GDB_LOG_DEBUG("Last valid watchpoint=%d\n", g_last_wp_register);
}
}
bool HardwareBreakPointManager::SendMultiCoreRequest(const void *request) {
/* Ensure the multi core thread is active. */
EnsureMultiCoreStarted();
/* Send the request. */
os::SendMessageQueue(std::addressof(g_multicore_request_queue), reinterpret_cast<uintptr_t>(request));
/* Get the response. */
uintptr_t response;
os::ReceiveMessageQueue(std::addressof(response), std::addressof(g_multicore_response_queue));
return static_cast<bool>(response);
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "dmnt2_breakpoint_manager.hpp"
namespace ams::dmnt {
struct HardwareBreakPoint : public BreakPoint {
svc::HardwareBreakPointRegisterName m_reg;
svc::HardwareBreakPointRegisterName m_ctx;
void Initialize(svc::HardwareBreakPointRegisterName r, svc::HardwareBreakPointRegisterName c) {
m_reg = r;
m_ctx = c;
}
virtual Result Clear(DebugProcess *debug_process) override;
virtual Result Set(DebugProcess *debug_process, uintptr_t address, size_t size, bool is_step) override;
};
class HardwareBreakPointManager : public BreakPointManager {
public:
static constexpr size_t BreakPointCountMax = 0x10;
private:
HardwareBreakPoint m_breakpoints[BreakPointCountMax];
public:
static Result SetHardwareBreakPoint(u32 r, u64 dbgbcr, u64 value);
static Result SetContextBreakPoint(svc::HardwareBreakPointRegisterName ctx, DebugProcess *debug_process);
static svc::HardwareBreakPointRegisterName GetWatchPointContextRegister();
static Result SetExecutionBreakPoint(svc::HardwareBreakPointRegisterName reg, svc::HardwareBreakPointRegisterName ctx, u64 address);
public:
explicit HardwareBreakPointManager(DebugProcess *debug_process);
private:
virtual BreakPointBase *GetBreakPoint(size_t index) override;
private:
static void CountBreakPointRegisters();
static bool SendMultiCoreRequest(const void *request);
};
}

View File

@@ -0,0 +1,167 @@
/*
* 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 <stratosphere.hpp>
#include "dmnt2_hardware_watchpoint.hpp"
#include "dmnt2_hardware_breakpoint.hpp"
#include "dmnt2_debug_process.hpp"
#include "dmnt2_debug_log.hpp"
namespace ams::dmnt {
namespace {
Result SetDataBreakPoint(svc::HardwareBreakPointRegisterName reg, svc::HardwareBreakPointRegisterName ctx, u64 address, u64 size, bool read, bool write) {
/* Determine lsc. */
const u8 lsc = (read ? 1 : 0) | (write ? 2 : 0);
/* Check that the watchpoint is valid. */
if (lsc != 0) {
R_UNLESS(HardwareWatchPointManager::IsValidWatchPoint(address, size), svc::ResultInvalidArgument());
}
/* Determine bas/mask. */
u8 bas = 0, mask = 0;
if (size <= 8) {
bas = ((1 << size) - 1) << (address & 7);
address = util::AlignDown(address, 8);
} else {
bas = 0xFF;
mask = util::PopCount(size - 1);
}
/* Build dbgbcr value. */
const u64 dbgbcr = (mask << 24) | (ctx << 16) | (bas << 5) | (lsc << 3) | ((lsc != 0) ? 1 : 0);
/* Set the breakpoint. */
const Result result = HardwareBreakPointManager::SetHardwareBreakPoint(reg, dbgbcr, address);
if (R_FAILED(result)) {
AMS_DMNT2_GDB_LOG_ERROR("SetDataBreakPoint FAIL 0x%08x, reg=%d, address=0x%lx\n", result.GetValue(), reg, address);
}
return result;
}
}
bool HardwareWatchPointManager::IsValidWatchPoint(u64 address, u64 size) {
/* Check size. */
if (size == 0) {
AMS_DMNT2_GDB_LOG_ERROR("HardwareWatchPointManager::IsValidWatchPoint(%lx, %lx) FAIL size == 0\n", address, size);
return false;
}
/* Validate. */
if (size <= 8) {
/* Check that address is aligned. */
if (util::AlignDown(address, 8) != util::AlignDown(address + size - 1, 8)) {
AMS_DMNT2_GDB_LOG_ERROR("HardwareWatchPointManager::IsValidWatchPoint(%lx, %lx) FAIL range crosses qword boundary\n", address, size);
return false;
}
} else {
/* Check size is small enough. */
if (size > 0x80000000) {
AMS_DMNT2_GDB_LOG_ERROR("HardwareWatchPointManager::IsValidWatchPoint(%lx, %lx) FAIL size too big\n", address, size);
return false;
}
/* Check size is power of two. */
if (!util::IsPowerOfTwo(size)) {
AMS_DMNT2_GDB_LOG_ERROR("HardwareWatchPointManager::IsValidWatchPoint(%lx, %lx) FAIL size not power of two\n", address, size);
return false;
}
/* Check alignment. */
if (!util::IsAligned(address, size)) {
AMS_DMNT2_GDB_LOG_ERROR("HardwareWatchPointManager::IsValidWatchPoint(%lx, %lx) FAIL address not size-aligned\n", address, size);
return false;
}
}
return true;
}
Result WatchPoint::Clear(DebugProcess *debug_process) {
Result result = svc::ResultInvalidArgument();
if (m_in_use) {
AMS_DMNT2_GDB_LOG_DEBUG("WatchPoint::Clear %p 0x%lx\n", this, m_address);
result = SetDataBreakPoint(m_reg, m_ctx, 0, 0, false, false);
this->Reset();
}
return result;
}
Result WatchPoint::Set(DebugProcess *debug_process, uintptr_t address, size_t size, bool read, bool write) {
/* Set fields. */
m_address = address;
m_size = size;
m_read = read;
m_write = write;
/* Set context breakpoint. */
R_TRY(HardwareBreakPointManager::SetContextBreakPoint(m_ctx, debug_process));
/* Set watchpoint. */
R_TRY(SetDataBreakPoint(m_reg, m_ctx, address, size, read, write));
/* Set as in-use. */
m_in_use = true;
return ResultSuccess();
}
HardwareWatchPointManager::HardwareWatchPointManager(DebugProcess *debug_process) : BreakPointManagerBase(debug_process) {
const svc::HardwareBreakPointRegisterName ctx = HardwareBreakPointManager::GetWatchPointContextRegister();
for (size_t i = 0; i < util::size(m_breakpoints); ++i) {
m_breakpoints[i].Initialize(static_cast<svc::HardwareBreakPointRegisterName>(svc::HardwareBreakPointRegisterName_D0 + i), ctx);
}
}
BreakPointBase *HardwareWatchPointManager::GetBreakPoint(size_t index) {
if (index < util::size(m_breakpoints)) {
return m_breakpoints + index;
} else {
return nullptr;
}
}
Result HardwareWatchPointManager::SetWatchPoint(u64 address, u64 size, bool read, bool write) {
/* Get a free watchpoint. */
auto *bp = static_cast<WatchPoint *>(this->GetFreeBreakPoint());
R_UNLESS(bp != nullptr, svc::ResultOutOfHandles());
/* Set the watchpoint. */
return bp->Set(m_debug_process, address, size, read, write);
}
Result HardwareWatchPointManager::GetWatchPointInfo(u64 address, bool &read, bool &write) {
/* Find a matching watchpoint. */
for (const auto &bp : m_breakpoints) {
if (bp.m_in_use) {
if (bp.m_address <= address && address < bp.m_address + bp.m_size) {
read = bp.m_read;
write = bp.m_write;
return ResultSuccess();
}
}
}
/* Otherwise, we failed. */
AMS_DMNT2_GDB_LOG_ERROR("HardwareWatchPointManager::GetWatchPointInfo FAIL 0x%lx\n", address);
read = false;
write = false;
return svc::ResultInvalidArgument();
}
}

View File

@@ -0,0 +1,53 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "dmnt2_breakpoint_manager_base.hpp"
namespace ams::dmnt {
struct WatchPoint : public BreakPointBase {
svc::HardwareBreakPointRegisterName m_reg;
svc::HardwareBreakPointRegisterName m_ctx;
bool m_read;
bool m_write;
void Initialize(svc::HardwareBreakPointRegisterName r, svc::HardwareBreakPointRegisterName c) {
m_reg = r;
m_ctx = c;
}
virtual Result Clear(DebugProcess *debug_process) override;
Result Set(DebugProcess *debug_process, uintptr_t address, size_t size, bool read, bool write);
};
class HardwareWatchPointManager : public BreakPointManagerBase {
public:
static constexpr size_t BreakPointCountMax = 0x10;
private:
WatchPoint m_breakpoints[BreakPointCountMax];
public:
static bool IsValidWatchPoint(u64 address, u64 size);
public:
explicit HardwareWatchPointManager(DebugProcess *debug_process);
Result SetWatchPoint(u64 address, u64 size, bool read, bool write);
Result GetWatchPointInfo(u64 address, bool &read, bool &write);
private:
virtual BreakPointBase *GetBreakPoint(size_t index) override;
};
}

View File

@@ -0,0 +1,146 @@
/*
* 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 <stratosphere.hpp>
#include "dmnt2_htcs_receive_buffer.hpp"
namespace ams::dmnt {
ssize_t HtcsReceiveBuffer::Read(void *dst, size_t size) {
/* Acquire exclusive access to ourselves. */
std::scoped_lock lk(m_mutex);
/* Check that we're readable and valid. */
if (!(this->IsValid() && this->IsReadable())) {
return -1;
}
/* Check that we have data to read. */
const size_t readable = std::min(size, m_readable_size);
if (readable <= 0) {
return -1;
}
/* Copy the data. */
std::memcpy(dst, m_buffer + m_offset, readable);
/* Advance our pointers. */
m_readable_size -= readable;
m_offset += readable;
/* Handle the case where we're done consuming. */
if (m_readable_size == 0) {
m_offset = 0;
m_readable_event.Clear();
m_writable_event.Signal();
}
return readable;
}
ssize_t HtcsReceiveBuffer::Write(const void *src, size_t size) {
/* Acquire exclusive access to ourselves. */
std::scoped_lock lk(m_mutex);
/* Check that we're readable and valid. */
if (!(this->IsValid() && this->IsWritable())) {
return -1;
}
/* Copy the data to our buffer. */
std::memcpy(m_buffer, src, size);
/* Set our fields. */
m_readable_size = size;
m_offset = 0;
m_writable_event.Clear();
m_readable_event.Signal();
return size;
}
bool HtcsReceiveBuffer::WaitToBeReadable() {
/* Check if we're already readable. */
{
std::scoped_lock lk(m_mutex);
if (this->IsReadable()) {
return true;
} else if (!this->IsValid()) {
return false;
} else {
m_readable_event.Clear();
}
}
/* Wait for us to be readable. */
m_readable_event.Wait();
return this->IsValid();
}
bool HtcsReceiveBuffer::WaitToBeReadable(TimeSpan timeout) {
/* Check if we're already readable. */
{
std::scoped_lock lk(m_mutex);
if (this->IsReadable()) {
return true;
} else if (!this->IsValid()) {
return false;
} else {
m_readable_event.Clear();
}
}
/* Wait for us to be readable. */
const bool res = m_readable_event.TimedWait(timeout);
return res && this->IsValid();
}
bool HtcsReceiveBuffer::WaitToBeWritable() {
/* Check if we're already writable. */
{
std::scoped_lock lk(m_mutex);
if (this->IsWritable()) {
return true;
} else if (!this->IsValid()) {
return false;
} else {
m_writable_event.Clear();
}
}
/* Wait for us to be writable. */
m_writable_event.Wait();
return this->IsValid();
}
void HtcsReceiveBuffer::Invalidate() {
/* Acquire exclusive access to ourselves. */
std::scoped_lock lk(m_mutex);
/* Set ourselves as invalid. */
m_valid = false;
/* Signal our events. */
m_readable_event.Signal();
m_writable_event.Signal();
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::dmnt {
class HtcsReceiveBuffer {
public:
static constexpr size_t ReceiveBufferSize = 4_KB;
private:
u8 m_buffer[ReceiveBufferSize];
os::Event m_readable_event;
os::Event m_writable_event;
os::SdkMutex m_mutex;
size_t m_readable_size;
size_t m_offset;
bool m_valid;
public:
HtcsReceiveBuffer() : m_readable_event(os::EventClearMode_ManualClear), m_writable_event(os::EventClearMode_ManualClear), m_mutex(), m_readable_size(), m_offset(), m_valid(true) { /* ... */ }
ALWAYS_INLINE bool IsReadable() const { return m_readable_size != 0; }
ALWAYS_INLINE bool IsWritable() const { return m_readable_size == 0; }
ALWAYS_INLINE bool IsValid() const { return m_valid; }
ssize_t Read(void *dst, size_t size);
ssize_t Write(const void *src, size_t size);
bool WaitToBeReadable();
bool WaitToBeReadable(TimeSpan timeout);
bool WaitToBeWritable();
void Invalidate();
};
}

View File

@@ -0,0 +1,127 @@
/*
* 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 <stratosphere.hpp>
#include "dmnt2_htcs_session.hpp"
#include "dmnt2_debug_log.hpp"
namespace ams::dmnt {
HtcsSession::HtcsSession(int fd) : m_socket(fd), m_valid(true) {
/* Create our thread. */
R_ABORT_UNLESS(os::CreateThread(std::addressof(m_receive_thread), ReceiveThreadEntry, this, m_receive_thread_stack, sizeof(m_receive_thread_stack), os::HighestThreadPriority - 1));
/* Start our thread. */
os::StartThread(std::addressof(m_receive_thread));
/* Note that we connected. */
AMS_DMNT2_GDB_LOG_INFO("Created Session %d\n", m_socket);
}
HtcsSession::~HtcsSession() {
/* Note that we connected. */
AMS_DMNT2_GDB_LOG_INFO("Closing Session %d\n", m_socket);
/* Invalidate our receive buffer. */
m_receive_buffer.Invalidate();
/* Shutdown our socket. */
htcs::Shutdown(m_socket, htcs::HTCS_SHUT_RDWR);
/* Wait for our thread. */
os::WaitThread(std::addressof(m_receive_thread));
os::DestroyThread(std::addressof(m_receive_thread));
/* Close our socket. */
htcs::Close(m_socket);
}
bool HtcsSession::WaitToBeReadable() {
return m_receive_buffer.WaitToBeReadable();
}
bool HtcsSession::WaitToBeReadable(TimeSpan timeout) {
return m_receive_buffer.WaitToBeReadable(timeout);
}
util::optional<char> HtcsSession::GetChar() {
/* Wait for us to have data. */
m_receive_buffer.WaitToBeReadable();
/* Get our data. */
char c;
if (m_receive_buffer.Read(std::addressof(c), sizeof(c)) > 0) {
return c;
} else {
return util::nullopt;
}
}
ssize_t HtcsSession::PutChar(char c) {
/* Send the character. */
const auto sent = htcs::Send(m_socket, std::addressof(c), sizeof(c), 0);
if (sent < 0) {
m_valid = false;
}
return sent;
}
ssize_t HtcsSession::PutString(const char *str) {
/* Repeatedly send until all is sent. */
const size_t len = std::strlen(str);
size_t remaining = len;
while (remaining > 0) {
const auto sent = htcs::Send(m_socket, str, remaining, 0);
if (sent >= 0) {
remaining -= sent;
str += sent;
} else {
m_valid = false;
return sent;
}
}
return len;
}
void HtcsSession::ReceiveThreadFunction() {
/* Create temporary buffer. */
u8 buffer[HtcsReceiveBuffer::ReceiveBufferSize];
/* Loop receiving data. */
while (true) {
/* Receive data. */
const auto res = htcs::Recv(m_socket, buffer, sizeof(buffer), 0);
if (res > 0) {
/* Write the data to our buffer. */
m_receive_buffer.WaitToBeWritable();
m_receive_buffer.Write(buffer, res);
} else {
/* Otherwise, if we got an error other than "try again", we're done. */
if (htcs::GetLastError() != htcs::HTCS_EAGAIN) {
AMS_DMNT2_GDB_LOG_INFO("Session %d invalid, res=%ld, err=%d\n", m_socket, res, static_cast<int>(htcs::GetLastError()));
m_valid = false;
break;
}
}
}
/* Invalidate our receive buffer. */
m_receive_buffer.Invalidate();
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "dmnt2_htcs_receive_buffer.hpp"
namespace ams::dmnt {
class HtcsSession {
private:
alignas(os::ThreadStackAlignment) u8 m_receive_thread_stack[util::AlignUp(os::MemoryPageSize + HtcsReceiveBuffer::ReceiveBufferSize, os::ThreadStackAlignment)];
HtcsReceiveBuffer m_receive_buffer;
os::ThreadType m_receive_thread;
int m_socket;
bool m_valid;
public:
HtcsSession(int fd);
~HtcsSession();
ALWAYS_INLINE bool IsValid() const { return m_valid; }
bool WaitToBeReadable();
bool WaitToBeReadable(TimeSpan timeout);
util::optional<char> GetChar();
ssize_t PutChar(char c);
ssize_t PutString(const char *str);
private:
static void ReceiveThreadEntry(void *arg) {
static_cast<HtcsSession *>(arg)->ReceiveThreadFunction();
}
void ReceiveThreadFunction();
};
}

View File

@@ -0,0 +1,156 @@
/*
* 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 <stratosphere.hpp>
#include "dmnt2_debug_log.hpp"
#include "dmnt2_gdb_server.hpp"
extern "C" {
extern u32 __start__;
u32 __nx_applet_type = AppletType_None;
u32 __nx_fs_num_sessions = 1;
#define INNER_HEAP_SIZE 0x0
size_t nx_inner_heap_size = INNER_HEAP_SIZE;
char nx_inner_heap[INNER_HEAP_SIZE];
void __libnx_initheap(void);
void __appInit(void);
void __appExit(void);
void *__libnx_alloc(size_t size);
void *__libnx_aligned_alloc(size_t alignment, size_t size);
void __libnx_free(void *mem);
}
namespace ams {
ncm::ProgramId CurrentProgramId = ncm::SystemProgramId::DmntGen2;
}
using namespace ams;
#define AMS_DMNT2_SERVER_USE_FATAL_ERROR 1
#if AMS_DMNT2_SERVER_USE_FATAL_ERROR
extern "C" {
/* Exception handling. */
alignas(16) u8 __nx_exception_stack[ams::os::MemoryPageSize];
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
void __libnx_exception_handler(ThreadExceptionDump *ctx);
}
void __libnx_exception_handler(ThreadExceptionDump *ctx) {
ams::CrashHandler(ctx);
}
#endif
namespace ams::dmnt {
namespace {
alignas(0x40) constinit u8 g_htcs_buffer[4_KB];
}
}
void __libnx_initheap(void) {
void* addr = nx_inner_heap;
size_t size = nx_inner_heap_size;
/* Newlib */
extern char* fake_heap_start;
extern char* fake_heap_end;
fake_heap_start = (char*)addr;
fake_heap_end = (char*)addr + size;
}
void __appInit(void) {
hos::InitializeForStratosphere();
R_ABORT_UNLESS(sm::Initialize());
/* TODO */
ams::CheckApiVersion();
}
void __appExit(void) {
/* TODO */
}
namespace ams {
void *Malloc(size_t size) {
AMS_ABORT("ams::Malloc was called");
}
void Free(void *ptr) {
AMS_ABORT("ams::Free was called");
}
}
void *operator new(size_t size) {
AMS_ABORT("operator new(size_t) was called");
}
void operator delete(void *p) {
AMS_ABORT("operator delete(void *) was called");
}
void *__libnx_alloc(size_t size) {
AMS_ABORT("__libnx_alloc was called");
}
void *__libnx_aligned_alloc(size_t alignment, size_t size) {
AMS_ABORT("__libnx_aligned_alloc was called");
}
void __libnx_free(void *mem) {
AMS_ABORT("__libnx_free was called");
}
int main(int argc, char **argv)
{
/* TODO ThreadName */
/* Initialize htcs. */
constexpr auto HtcsSocketCountMax = 8;
const size_t buffer_size = htcs::GetWorkingMemorySize(HtcsSocketCountMax);
AMS_ABORT_UNLESS(sizeof(dmnt::g_htcs_buffer) >= buffer_size);
htcs::InitializeForSystem(dmnt::g_htcs_buffer, buffer_size, HtcsSocketCountMax);
/* Initialize debug log thread. */
dmnt::InitializeDebugLog();
/* Start GdbServer. */
dmnt::InitializeGdbServer();
/* TODO */
while (true) {
os::SleepThread(TimeSpan::FromDays(1));
}
return 0;
}

View File

@@ -0,0 +1,62 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::dmnt {
class ModuleDefinition {
NON_MOVEABLE(ModuleDefinition);
public:
static constexpr size_t PathLengthMax = 0x200;
private:
char m_name[PathLengthMax];
u64 m_address;
u64 m_size;
public:
constexpr ModuleDefinition() : m_name(), m_address(), m_size() { /* ... */ }
constexpr ~ModuleDefinition() { /* ... */ }
constexpr ModuleDefinition(const ModuleDefinition &rhs) = default;
constexpr ModuleDefinition &operator=(const ModuleDefinition &rhs) = default;
constexpr void Reset() {
m_name[0] = 0;
m_address = 0;
m_size = 0;
}
constexpr bool operator==(const ModuleDefinition &rhs) const {
return m_address == rhs.m_address && m_size == rhs.m_size && std::strcmp(m_name, rhs.m_name) == 0;
}
constexpr bool operator!=(const ModuleDefinition &rhs) const {
return !(*this == rhs);
}
constexpr char *GetNameBuffer() { return m_name; }
constexpr const char *GetName() const { return m_name; }
constexpr u64 GetAddress() const { return m_address; }
constexpr u64 GetSize() const { return m_size; }
constexpr void SetAddressSize(u64 address, u64 size) {
m_address = address;
m_size = size;
}
};
}

View File

@@ -0,0 +1,95 @@
/*
* 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 <stratosphere.hpp>
#include "dmnt2_software_breakpoint.hpp"
#include "dmnt2_debug_process.hpp"
#include "dmnt2_debug_log.hpp"
namespace ams::dmnt {
namespace {
constexpr const u8 Aarch64BreakInstruction[] = {0xFF, 0xFF, 0xFF, 0xE7};
constexpr const u8 Aarch32BreakInstruction[] = {0xFE, 0xDE, 0xFF, 0xE7};
constexpr const u8 Aarch32ThumbBreakInstruction[] = {0x80, 0xB6};
}
Result SoftwareBreakPoint::Clear(DebugProcess *debug_process) {
Result result = svc::ResultInvalidArgument();
if (m_in_use) {
if (m_address) {
result = debug_process->WriteMemory(std::addressof(m_insn), m_address, m_size);
if (R_SUCCEEDED(result)) {
AMS_DMNT2_GDB_LOG_DEBUG("SoftwareBreakPoint::Clear %p 0x%lx, insn=0x%x\n", this, m_address, m_insn);
} else {
AMS_DMNT2_GDB_LOG_DEBUG("SoftwareBreakPoint::Clear %p 0x%lx, insn=0x%x, !!! Fail %08x !!!\n", this, m_address, m_insn, result.GetValue());
}
this->Reset();
} else {
AMS_DMNT2_GDB_LOG_ERROR("SoftwareBreakPoint::Clear %p 0x%lx, insn=0x%x, !!! Null Address !!!\n", this, m_address, m_insn);
}
}
return result;
}
Result SoftwareBreakPoint::Set(DebugProcess *debug_process, uintptr_t address, size_t size, bool is_step) {
/* Set fields. */
m_is_step = is_step;
m_address = address;
m_size = size;
/* Read our instruction. */
Result result = debug_process->ReadMemory(std::addressof(m_insn), m_address, m_size);
AMS_DMNT2_GDB_LOG_DEBUG("SoftwareBreakPoint::Set %p 0x%lx, insn=0x%x\n", this, m_address, m_insn);
/* Set the breakpoint. */
if (debug_process->Is64Bit()) {
if (m_size == sizeof(Aarch64BreakInstruction)) {
result = debug_process->WriteMemory(Aarch64BreakInstruction, m_address, m_size);
} else {
result = svc::ResultInvalidArgument();
}
} else {
if (m_size == sizeof(Aarch32BreakInstruction) || m_size == sizeof(Aarch32ThumbBreakInstruction)) {
result = debug_process->WriteMemory(m_size == sizeof(Aarch32BreakInstruction) ? Aarch32BreakInstruction : Aarch32ThumbBreakInstruction, m_address, m_size);
} else {
result = svc::ResultInvalidArgument();
}
}
/* Check that we succeeded. */
if (R_SUCCEEDED(result)) {
m_in_use = true;
}
return result;
}
SoftwareBreakPointManager::SoftwareBreakPointManager(DebugProcess *debug_process) : BreakPointManager(debug_process) {
/* ... */
}
BreakPointBase *SoftwareBreakPointManager::GetBreakPoint(size_t index) {
if (index < util::size(m_breakpoints)) {
return m_breakpoints + index;
} else {
return nullptr;
}
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "dmnt2_breakpoint_manager.hpp"
namespace ams::dmnt {
struct SoftwareBreakPoint : public BreakPoint {
u32 m_insn;
virtual Result Clear(DebugProcess *debug_process) override;
virtual Result Set(DebugProcess *debug_process, uintptr_t address, size_t size, bool is_step) override;
};
class SoftwareBreakPointManager : public BreakPointManager {
public:
static constexpr size_t BreakPointCountMax = 0x80;
private:
SoftwareBreakPoint m_breakpoints[BreakPointCountMax];
public:
explicit SoftwareBreakPointManager(DebugProcess *debug_process);
private:
virtual BreakPointBase *GetBreakPoint(size_t index) override;
};
}