Revert "hoc-clk: add live vdd2, live boost clock and basic pwm dimming"

This reverts commit 15b7df8ef1.
This commit is contained in:
souldbminersmwc
2025-11-09 16:14:52 -05:00
parent 22ec140738
commit 21a3f953d7
3804 changed files with 435 additions and 570162 deletions

View File

@@ -1,54 +0,0 @@
/*
* Copyright (c) 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, address, result.GetValue());
}
R_RETURN(result);
}
}

View File

@@ -1,41 +0,0 @@
/*
* Copyright (c) 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

@@ -1,64 +0,0 @@
/*
* Copyright (c) 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);
R_RETURN(bp->Clear(m_debug_process));
}
}
R_SUCCEED();
}
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

@@ -1,50 +0,0 @@
/*
* Copyright (c) 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

@@ -1,243 +0,0 @@
/*
* Copyright (c) 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"
// TODO: This should be converted to use log manager. */
//#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 {
//#define AMS_DMNT2_ENABLE_SD_CARD_DEBUG_LOG
#if defined(AMS_DMNT2_ENABLE_SD_CARD_DEBUG_LOG)
namespace {
alignas(0x40) constinit u8 g_buffer[os::MemoryPageSize * 4];
constinit lmem::HeapHandle g_debug_log_heap;
constinit fs::FileHandle g_debug_log_file;
constinit os::SdkMutex g_fs_mutex;
constinit s64 g_fs_offset = 0;
void *Allocate(size_t size) {
return lmem::AllocateFromExpHeap(g_debug_log_heap, size);
}
void Deallocate(void *p, size_t size) {
AMS_UNUSED(size);
return lmem::FreeToExpHeap(g_debug_log_heap, p);
}
}
void InitializeDebugLog() {
g_debug_log_heap = lmem::CreateExpHeap(g_buffer, sizeof(g_buffer), lmem::CreateOption_ThreadSafe);
fs::SetAllocator(Allocate, Deallocate);
fs::InitializeForSystem();
fs::SetEnabledAutoAbort(false);
R_ABORT_UNLESS(fs::MountSdCard("sdmc"));
fs::DeleteFile("sdmc:/dmnt2.log");
R_ABORT_UNLESS(fs::CreateFile("sdmc:/dmnt2.log", 0));
R_ABORT_UNLESS(fs::OpenFile(std::addressof(g_debug_log_file), "sdmc:/dmnt2.log", fs::OpenMode_Write | fs::OpenMode_AllowAppend));
}
void DebugLog(const char *prefix, const char *fmt, ...) {
/* Do nothing. */
char buffer[0x200];
{
const auto prefix_len = std::strlen(prefix);
std::memcpy(buffer, prefix, prefix_len);
std::va_list vl;
va_start(vl, fmt);
util::VSNPrintf(buffer + prefix_len, sizeof(buffer) - prefix_len, fmt, vl);
va_end(vl);
}
const auto len = std::strlen(buffer);
std::scoped_lock lk(g_fs_mutex);
R_ABORT_UNLESS(fs::WriteFile(g_debug_log_file, g_fs_offset, buffer, len, fs::WriteOption::Flush));
g_fs_offset += len;
}
#else
void InitializeDebugLog() {
/* ... */
}
void DebugLog(const char *prefix, const char *fmt, ...) {
AMS_UNUSED(prefix, fmt);
}
#endif
}
#endif

View File

@@ -1,32 +0,0 @@
/*
* Copyright (c) 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

@@ -1,619 +0,0 @@
/*
* Copyright (c) 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, bool start_process) {
/* Attach to the process. */
R_TRY(svc::DebugActiveProcess(std::addressof(m_debug_handle), process_id.value));
/* If necessary, start the process. */
if (start_process) {
R_ABORT_UNLESS(pm::dmnt::StartProcess(process_id));
}
/* 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 };
/* Get process info. */
this->CollectProcessInfo();
R_SUCCEED();
}
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();
R_SUCCEED();
}
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.permission == 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.base_address, memory_info.size);
/* Get module name buffer. */
char *module_name = module.GetNameBuffer();
std::memset(module_name, 0, ModuleDefinition::PathLengthMax);
/* 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.base_address + memory_info.size, sizeof(module_path)))) {
if (module_path.zero == 0 && module_path.path_length > 0) {
std::memcpy(module_name, module_path.path, std::min<size_t>(ModuleDefinition::PathLengthMax, module_path.path_length));
}
} else {
module_path.path_length = 0;
}
/* Truncate module name. */
module_name[ModuleDefinition::PathLengthMax - 1] = 0;
/* Set default module name start. */
module.SetNameStart(0);
/* Ignore leading directories. */
for (size_t i = 0; i < std::min<size_t>(ModuleDefinition::PathLengthMax, module_path.path_length) && module_name[i] != 0; ++i) {
if (module_name[i] == '/' || module_name[i] == '\\') {
module.SetNameStart(i + 1);
}
}
}
}
/* Check if we're done. */
const uintptr_t next_address = memory_info.base_address + memory_info.size;
if (memory_info.state == svc::MemoryState_Inaccessible) {
break;
}
if (next_address <= address) {
break;
}
address = next_address;
}
R_SUCCEED();
}
void DebugProcess::CollectProcessInfo() {
/* Define helper for getting process info. */
auto CollectProcessInfoImpl = [&](os::NativeHandle handle) -> Result {
/* Collect all values. */
R_TRY(svc::GetInfo(std::addressof(m_process_alias_address), svc::InfoType_AliasRegionAddress, handle, 0));
R_TRY(svc::GetInfo(std::addressof(m_process_alias_size), svc::InfoType_AliasRegionSize, handle, 0));
R_TRY(svc::GetInfo(std::addressof(m_process_heap_address), svc::InfoType_HeapRegionAddress, handle, 0));
R_TRY(svc::GetInfo(std::addressof(m_process_heap_size), svc::InfoType_HeapRegionSize, handle, 0));
R_TRY(svc::GetInfo(std::addressof(m_process_aslr_address), svc::InfoType_AslrRegionAddress, handle, 0));
R_TRY(svc::GetInfo(std::addressof(m_process_aslr_size), svc::InfoType_AslrRegionSize, handle, 0));
R_TRY(svc::GetInfo(std::addressof(m_process_stack_address), svc::InfoType_StackRegionAddress, handle, 0));
R_TRY(svc::GetInfo(std::addressof(m_process_stack_size), svc::InfoType_StackRegionSize, handle, 0));
if (m_program_location.program_id == ncm::InvalidProgramId) {
R_TRY(svc::GetInfo(std::addressof(m_program_location.program_id.value), svc::InfoType_ProgramId, handle, 0));
}
u64 value;
R_TRY(svc::GetInfo(std::addressof(value), svc::InfoType_IsApplication, handle, 0));
m_is_application = value != 0;
R_SUCCEED();
};
/* Get process info/status. */
os::NativeHandle process_handle;
if (R_FAILED(pm::dmnt::AtmosphereGetProcessInfo(std::addressof(process_handle), std::addressof(m_program_location), std::addressof(m_process_override_status), m_process_id))) {
process_handle = os::InvalidNativeHandle;
m_program_location = { ncm::InvalidProgramId, };
m_process_override_status = {};
}
ON_SCOPE_EXIT { os::CloseNativeHandle(process_handle); };
/* Try collecting from our debug handle, then the process handle. */
if (R_FAILED(CollectProcessInfoImpl(m_debug_handle)) && R_FAILED(CollectProcessInfoImpl(process_handle))) {
m_process_alias_address = 0;
m_process_alias_size = 0;
m_process_heap_address = 0;
m_process_heap_size = 0;
m_process_aslr_address = 0;
m_process_aslr_size = 0;
m_process_stack_address = 0;
m_process_stack_size = 0;
m_is_application = false;
}
}
Result DebugProcess::GetThreadContext(svc::ThreadContext *out, u64 thread_id, u32 flags) {
R_RETURN(svc::GetDebugThreadContext(out, m_debug_handle, thread_id, flags));
}
Result DebugProcess::SetThreadContext(const svc::ThreadContext *ctx, u64 thread_id, u32 flags) {
R_RETURN(svc::SetDebugThreadContext(m_debug_handle, thread_id, ctx, flags));
}
Result DebugProcess::ReadMemory(void *dst, uintptr_t address, size_t size) {
R_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) {
R_RETURN(svc::WriteDebugProcessMemory(m_debug_handle, reinterpret_cast<uintptr_t>(src), address, size));
}
Result DebugProcess::QueryMemory(svc::MemoryInfo *out, uintptr_t address) {
svc::PageInfo dummy;
R_RETURN(svc::QueryDebugProcessMemory(out, std::addressof(dummy), m_debug_handle, address));
}
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);
R_SUCCEED();
}
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);
R_SUCCEED();
}
Result DebugProcess::Step() {
AMS_DMNT2_GDB_LOG_DEBUG("DebugProcess::Step() all\n");
R_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();
}
R_SUCCEED();
}
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");
R_RETURN(svc::BreakDebugProcess(m_debug_handle));
} else {
AMS_DMNT2_GDB_LOG_ERROR("DebugProcess::Break called on non-running process!\n");
R_SUCCEED();
}
}
Result DebugProcess::Terminate() {
if (this->IsValid()) {
R_ABORT_UNLESS(svc::TerminateDebugProcess(m_debug_handle));
this->Detach();
}
R_SUCCEED();
}
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 */
AMS_UNUSED(ctx);
}
}
Result DebugProcess::SetBreakPoint(uintptr_t address, size_t size, bool is_step) {
R_RETURN(m_software_breakpoints.SetBreakPoint(address, size, is_step));
}
Result DebugProcess::ClearBreakPoint(uintptr_t address, size_t size) {
m_software_breakpoints.ClearBreakPoint(address, size);
R_SUCCEED();
}
Result DebugProcess::SetHardwareBreakPoint(uintptr_t address, size_t size, bool is_step) {
R_RETURN(m_hardware_breakpoints.SetBreakPoint(address, size, is_step));
}
Result DebugProcess::ClearHardwareBreakPoint(uintptr_t address, size_t size) {
m_hardware_breakpoints.ClearBreakPoint(address, size);
R_SUCCEED();
}
Result DebugProcess::SetWatchPoint(u64 address, u64 size, bool read, bool write) {
R_RETURN(m_hardware_watchpoints.SetWatchPoint(address, size, read, write));
}
Result DebugProcess::ClearWatchPoint(u64 address, u64 size) {
R_RETURN(m_hardware_watchpoints.ClearBreakPoint(address, size));
}
Result DebugProcess::GetWatchPointInfo(u64 address, bool &read, bool &write) {
R_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;
R_SUCCEED();
}
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();
}
R_SUCCEED();
}
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;
R_SUCCEED();
}
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;
R_SUCCEED();
}
void DebugProcess::GetThreadName(char *dst, u64 thread_id) const {
for (size_t i = 0; i < ThreadCountMax; ++i) {
if (m_thread_valid[i] && m_thread_ids[i] == thread_id) {
if (R_FAILED(osdbg::GetThreadName(dst, std::addressof(m_thread_infos[i])))) {
if (m_thread_infos[i]._thread_type != 0) {
if (m_thread_infos[i]._thread_type_type == osdbg::ThreadTypeType_Libnx) {
util::TSNPrintf(dst, os::ThreadNameLengthMax, "libnx Thread_0x%010lx", reinterpret_cast<uintptr_t>(m_thread_infos[i]._thread_type));
} else {
util::TSNPrintf(dst, os::ThreadNameLengthMax, "Thread_0x%010lx", reinterpret_cast<uintptr_t>(m_thread_infos[i]._thread_type));
}
} else {
break;
}
}
return;
}
}
util::TSNPrintf(dst, os::ThreadNameLengthMax, "Thread_ID=%lu", thread_id);
}
}

View File

@@ -1,192 +0,0 @@
/*
* Copyright (c) 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:
os::NativeHandle m_debug_handle{os::InvalidNativeHandle};
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{};
u64 m_preferred_debug_break_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{};
u64 m_process_alias_address{};
u64 m_process_alias_size{};
u64 m_process_heap_address{};
u64 m_process_heap_size{};
u64 m_process_aslr_address{};
u64 m_process_aslr_size{};
u64 m_process_stack_address{};
u64 m_process_stack_size{};
ncm::ProgramLocation m_program_location{};
cfg::OverrideStatus m_process_override_status{};
bool m_is_application{false};
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(); }
os::NativeHandle 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 GetModuleBaseAddress(size_t ix) const { return m_module_definitions[ix].GetAddress(); }
uintptr_t GetModuleSize(size_t ix) const { return m_module_definitions[ix].GetSize(); }
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; }
u64 GetPreferredDebuggerBreakThreadId() { return m_preferred_debug_break_thread_id; }
void SetLastThreadId(u64 tid) {
m_last_thread_id = tid;
m_thread_id_override = tid;
}
void SetThreadIdOverride(u64 tid) {
m_thread_id_override = tid;
m_preferred_debug_break_thread_id = tid;
}
void SetDebugBreaked() {
m_status = ProcessStatus_DebugBreak;
}
u64 GetAliasRegionAddress() const { return m_process_alias_address; }
u64 GetAliasRegionSize() const { return m_process_alias_size; }
u64 GetHeapRegionAddress() const { return m_process_heap_address; }
u64 GetHeapRegionSize() const { return m_process_heap_size; }
u64 GetAslrRegionAddress() const { return m_process_aslr_address; }
u64 GetAslrRegionSize() const { return m_process_aslr_size; }
u64 GetStackRegionAddress() const { return m_process_stack_address; }
u64 GetStackRegionSize() const { return m_process_stack_size; }
const ncm::ProgramLocation &GetProgramLocation() const { return m_program_location; }
const cfg::OverrideStatus &GetOverrideStatus() const { return m_process_override_status; }
bool IsApplication() const { return m_is_application; }
public:
Result Attach(os::ProcessId process_id, bool start_process = false);
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 QueryMemory(svc::MemoryInfo *out, uintptr_t address);
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();
void GetThreadName(char *dst, u64 thread_id) const;
private:
Result Start();
void CollectProcessInfo();
s32 ThreadCreate(u64 thread_id);
void ThreadExit(u64 thread_id);
};
}

View File

@@ -1,194 +0,0 @@
/*
* Copyright (c) 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, TransportSession *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, TransportSession *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

@@ -1,37 +0,0 @@
/*
* Copyright (c) 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_transport_session.hpp"
namespace ams::dmnt {
static constexpr size_t GdbPacketBufferSize = 32_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, TransportSession *session);
char *ReceivePacket(bool *out_break, char *dst, size_t size, TransportSession *session);
};
}

View File

@@ -1,89 +0,0 @@
/*
* Copyright (c) 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_transport_layer.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 = transport::Socket()) == -1) {
os::SleepThread(TimeSpan::FromSeconds(1));
}
/* Ensure we cleanup the socket when we're done with it. */
ON_SCOPE_EXIT {
transport::Close(fd);
os::SleepThread(TimeSpan::FromSeconds(1));
};
/* Bind. */
if (transport::Bind(fd, transport::PortName_GdbServer) == -1) {
continue;
}
/* Listen on our port. */
while (transport::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 = transport::Accept(fd); 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. */
transport::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

@@ -1,23 +0,0 @@
/*
* Copyright (c) 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();
}

View File

@@ -1,120 +0,0 @@
/*
* Copyright (c) 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;
TransportSession m_session;
GdbPacketIo m_packet_io;
char *m_receive_packet{nullptr};
char *m_reply_cur{nullptr};
char *m_reply_end{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;
os::ProcessId m_wait_process_id{os::InvalidProcessId};
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 AppendStopReplyPacket(GdbSignal signal);
private:
void D();
void G();
void H();
void Hg();
void M();
void P();
void Q();
void QStartNoAckMode();
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 qRcmd();
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

@@ -1,34 +0,0 @@
/*
* Copyright (c) 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

@@ -1,237 +0,0 @@
/*
* Copyright (c) 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) {
AMS_UNUSED(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();
}
R_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;
R_SUCCEED();
}
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());
R_SUCCEED();
}
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);
}
R_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);
}
R_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

@@ -1,55 +0,0 @@
/*
* Copyright (c) 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

@@ -1,169 +0,0 @@
/*
* Copyright (c) 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);
}
R_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) {
AMS_UNUSED(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();
}
R_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;
R_SUCCEED();
}
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. */
R_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;
R_SUCCEED();
}
}
}
/* Otherwise, we failed. */
AMS_DMNT2_GDB_LOG_ERROR("HardwareWatchPointManager::GetWatchPointInfo FAIL 0x%lx\n", address);
read = false;
write = false;
R_THROW(svc::ResultInvalidArgument());
}
}

View File

@@ -1,53 +0,0 @@
/*
* Copyright (c) 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

@@ -1,93 +0,0 @@
/*
* Copyright (c) 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_transport_layer.hpp"
namespace ams {
namespace {
bool IsHtcEnabled() {
u8 enable_htc = 0;
settings::fwdbg::GetSettingsItemValue(std::addressof(enable_htc), sizeof(enable_htc), "atmosphere", "enable_htc");
return enable_htc != 0;
}
bool IsStandaloneGdbstubEnabled() {
u8 enable_gdbstub = 0;
settings::fwdbg::GetSettingsItemValue(std::addressof(enable_gdbstub), sizeof(enable_gdbstub), "atmosphere", "enable_standalone_gdbstub");
return enable_gdbstub != 0;
}
}
namespace init {
void InitializeSystemModule() {
/* Initialize our connection to sm. */
R_ABORT_UNLESS(sm::Initialize());
/* Initialize other services we need. */
R_ABORT_UNLESS(pmdmntInitialize());
/* Verify that we can sanely execute. */
ams::CheckApiVersion();
}
void FinalizeSystemModule() { /* ... */ }
void Startup() { /* ... */ }
}
void Main() {
/* Set thread name. */
os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(dmnt, Main));
AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(dmnt, Main));
bool use_htcs = false, use_tcp = false;
{
R_ABORT_UNLESS(::setsysInitialize());
ON_SCOPE_EXIT { ::setsysExit(); };
use_htcs = IsHtcEnabled();
use_tcp = IsStandaloneGdbstubEnabled();
}
/* Initialize transport layer. */
if (use_htcs) {
dmnt::transport::InitializeByHtcs();
} else if (use_tcp) {
dmnt::transport::InitializeByTcp();
} else {
return;
}
/* Initialize debug log thread. */
dmnt::InitializeDebugLog();
/* Start GdbServer. */
dmnt::InitializeGdbServer();
/* TODO */
while (true) {
os::SleepThread(TimeSpan::FromDays(1));
}
}
}

View File

@@ -1,68 +0,0 @@
/*
* Copyright (c) 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;
size_t m_name_start;
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;
m_name_start = 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 + m_name_start; }
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;
}
constexpr void SetNameStart(size_t offset) {
m_name_start = offset;
}
};
}

View File

@@ -1,95 +0,0 @@
/*
* Copyright (c) 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);
}
}
R_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;
}
R_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

@@ -1,40 +0,0 @@
/*
* Copyright (c) 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;
};
}

View File

@@ -1,196 +0,0 @@
/*
* Copyright (c) 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_transport_layer.hpp"
namespace ams::dmnt::transport {
namespace {
enum SocketMode {
SocketMode_Invalid,
SocketMode_Htcs,
SocketMode_Tcp,
};
constexpr inline const u16 ListenPort_GdbServer = 22225;
constexpr inline const u16 ListenPort_GdbDebugLog = 22227;
constinit os::SdkMutex g_socket_init_mutex;
constinit SocketMode g_socket_mode = SocketMode_Invalid;
constexpr inline size_t RequiredAlignment = std::max(os::ThreadStackAlignment, os::MemoryPageSize);
using SocketConfigType = socket::SystemConfigLightDefault;
/* TODO: If we ever use resolvers, increase this. */
constexpr inline size_t SocketAllocatorSize = 4_KB;
constexpr inline size_t SocketMemoryPoolSize = util::AlignUp(SocketConfigType::PerTcpSocketWorstCaseMemoryPoolSize + SocketConfigType::PerUdpSocketWorstCaseMemoryPoolSize, os::MemoryPageSize);
constexpr inline size_t SocketRequiredSize = util::AlignUp(SocketMemoryPoolSize + SocketAllocatorSize, os::MemoryPageSize);
/* Declare the memory pool. */
alignas(RequiredAlignment) constinit u8 g_socket_memory[SocketRequiredSize];
constexpr inline const SocketConfigType SocketConfig(g_socket_memory, SocketRequiredSize, SocketAllocatorSize, 2);
}
void InitializeByHtcs() {
std::scoped_lock lk(g_socket_init_mutex);
AMS_ABORT_UNLESS(g_socket_mode == SocketMode_Invalid);
constexpr auto HtcsSocketCountMax = 8;
const size_t buffer_size = htcs::GetWorkingMemorySize(HtcsSocketCountMax);
AMS_ABORT_UNLESS(sizeof(g_socket_memory) >= buffer_size);
htcs::InitializeForSystem(g_socket_memory, sizeof(g_socket_memory), HtcsSocketCountMax);
g_socket_mode = SocketMode_Htcs;
}
void InitializeByTcp() {
std::scoped_lock lk(g_socket_init_mutex);
AMS_ABORT_UNLESS(g_socket_mode == SocketMode_Invalid);
R_ABORT_UNLESS(socket::Initialize(SocketConfig));
g_socket_mode = SocketMode_Tcp;
}
s32 Socket() {
switch (g_socket_mode) {
case SocketMode_Htcs: return htcs::Socket();
case SocketMode_Tcp: return socket::Socket(socket::Family::Af_Inet, socket::Type::Sock_Stream, socket::Protocol::IpProto_Tcp);
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
s32 Close(s32 desc) {
switch (g_socket_mode) {
case SocketMode_Htcs: return htcs::Close(desc);
case SocketMode_Tcp: return socket::Close(desc);
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
s32 Bind(s32 desc, PortName port_name) {
switch (g_socket_mode) {
case SocketMode_Htcs:
{
htcs::SockAddrHtcs addr;
addr.family = htcs::HTCS_AF_HTCS;
addr.peer_name = htcs::GetPeerNameAny();
switch (port_name) {
case PortName_GdbServer: std::strcpy(addr.port_name.name, "iywys@$gdb"); break;
case PortName_GdbDebugLog: std::strcpy(addr.port_name.name, "iywys@$dmnt2_log"); break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
return htcs::Bind(desc, std::addressof(addr));
}
break;
case SocketMode_Tcp:
{
socket::SockAddrIn addr = {};
addr.sin_family = socket::Family::Af_Inet;
addr.sin_addr.s_addr = socket::InAddr_Any;
switch (port_name){
case PortName_GdbServer: addr.sin_port = socket::InetHtons(static_cast<u16>(ListenPort_GdbServer)); break;
case PortName_GdbDebugLog: addr.sin_port = socket::InetHtons(static_cast<u16>(ListenPort_GdbDebugLog)); break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
return socket::Bind(desc, reinterpret_cast<socket::SockAddr *>(std::addressof(addr)), sizeof(addr));
}
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
s32 Listen(s32 desc, s32 backlog_count) {
switch (g_socket_mode) {
case SocketMode_Htcs: return htcs::Listen(desc, backlog_count);
case SocketMode_Tcp: return socket::Listen(desc, backlog_count);
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
s32 Accept(s32 desc) {
switch (g_socket_mode) {
case SocketMode_Htcs:
{
htcs::SockAddrHtcs addr;
addr.family = htcs::HTCS_AF_HTCS;
addr.peer_name = htcs::GetPeerNameAny();
addr.port_name.name[0] = '\x00';
return htcs::Accept(desc, std::addressof(addr));
}
break;
case SocketMode_Tcp:
{
socket::SockAddrIn addr = {};
socket::SockLenT addr_len = sizeof(addr);
return socket::Accept(desc, reinterpret_cast<socket::SockAddr *>(std::addressof(addr)), std::addressof(addr_len));
}
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
s32 Shutdown(s32 desc) {
switch (g_socket_mode) {
case SocketMode_Htcs: return htcs::Shutdown(desc, htcs::HTCS_SHUT_RDWR);
case SocketMode_Tcp: return socket::Shutdown(desc, socket::ShutdownMethod::Shut_RdWr);
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
ssize_t Recv(s32 desc, void *buffer, size_t buffer_size, s32 flags) {
switch (g_socket_mode) {
case SocketMode_Htcs: return htcs::Recv(desc, buffer, buffer_size, flags);
case SocketMode_Tcp: return socket::Recv(desc, buffer, buffer_size, static_cast<socket::MsgFlag>(flags));
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
ssize_t Send(s32 desc, const void *buffer, size_t buffer_size, s32 flags) {
switch (g_socket_mode) {
case SocketMode_Htcs: return htcs::Send(desc, buffer, buffer_size, flags);
case SocketMode_Tcp: return socket::Send(desc, buffer, buffer_size, static_cast<socket::MsgFlag>(flags));
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
s32 GetLastError() {
switch (g_socket_mode) {
case SocketMode_Htcs: return htcs::GetLastError();
case SocketMode_Tcp: return static_cast<s32>(socket::GetLastError());
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
bool IsLastErrorEAgain() {
switch (g_socket_mode) {
case SocketMode_Htcs: return htcs::GetLastError() == htcs::HTCS_EAGAIN;
case SocketMode_Tcp: return socket::GetLastError() == socket::Errno::EAgain;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
}

View File

@@ -1,42 +0,0 @@
/*
* Copyright (c) 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::transport {
void InitializeByHtcs();
void InitializeByTcp();
enum PortName {
PortName_GdbServer,
PortName_GdbDebugLog,
};
s32 Socket();
s32 Close(s32 desc);
s32 Bind(s32 desc, PortName port_name);
s32 Listen(s32 desc, s32 backlog_count);
s32 Accept(s32 desc);
s32 Shutdown(s32 desc);
ssize_t Recv(s32 desc, void *buffer, size_t buffer_size, s32 flags);
ssize_t Send(s32 desc, const void *buffer, size_t buffer_size, s32 flags);
s32 GetLastError();
bool IsLastErrorEAgain();
}

View File

@@ -1,146 +0,0 @@
/*
* Copyright (c) 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_transport_receive_buffer.hpp"
namespace ams::dmnt {
ssize_t TransportReceiveBuffer::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 TransportReceiveBuffer::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 TransportReceiveBuffer::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 TransportReceiveBuffer::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 TransportReceiveBuffer::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 TransportReceiveBuffer::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

@@ -1,49 +0,0 @@
/*
* Copyright (c) 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 TransportReceiveBuffer {
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:
TransportReceiveBuffer() : 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

@@ -1,128 +0,0 @@
/*
* Copyright (c) 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_transport_layer.hpp"
#include "dmnt2_transport_session.hpp"
#include "dmnt2_debug_log.hpp"
namespace ams::dmnt {
TransportSession::TransportSession(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);
}
TransportSession::~TransportSession() {
/* 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. */
transport::Shutdown(m_socket);
/* Wait for our thread. */
os::WaitThread(std::addressof(m_receive_thread));
os::DestroyThread(std::addressof(m_receive_thread));
/* Close our socket. */
transport::Close(m_socket);
}
bool TransportSession::WaitToBeReadable() {
return m_receive_buffer.WaitToBeReadable();
}
bool TransportSession::WaitToBeReadable(TimeSpan timeout) {
return m_receive_buffer.WaitToBeReadable(timeout);
}
util::optional<char> TransportSession::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 TransportSession::PutChar(char c) {
/* Send the character. */
const auto sent = transport::Send(m_socket, std::addressof(c), sizeof(c), 0);
if (sent < 0) {
m_valid = false;
}
return sent;
}
ssize_t TransportSession::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 = transport::Send(m_socket, str, remaining, 0);
if (sent >= 0) {
remaining -= sent;
str += sent;
} else {
m_valid = false;
return sent;
}
}
return len;
}
void TransportSession::ReceiveThreadFunction() {
/* Create temporary buffer. */
u8 buffer[TransportReceiveBuffer::ReceiveBufferSize];
/* Loop receiving data. */
while (true) {
/* Receive data. */
const auto res = transport::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 (!transport::IsLastErrorEAgain()) {
AMS_DMNT2_GDB_LOG_INFO("Session %d invalid, res=%ld, err=%d\n", m_socket, res, static_cast<int>(transport::GetLastError()));
m_valid = false;
break;
}
}
}
/* Invalidate our receive buffer. */
m_receive_buffer.Invalidate();
}
}

View File

@@ -1,50 +0,0 @@
/*
* Copyright (c) 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_transport_receive_buffer.hpp"
namespace ams::dmnt {
class TransportSession {
private:
alignas(os::ThreadStackAlignment) u8 m_receive_thread_stack[util::AlignUp(os::MemoryPageSize + TransportReceiveBuffer::ReceiveBufferSize, os::ThreadStackAlignment)];
TransportReceiveBuffer m_receive_buffer;
os::ThreadType m_receive_thread;
int m_socket;
bool m_valid;
public:
TransportSession(int fd);
~TransportSession();
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<TransportSession *>(arg)->ReceiveThreadFunction();
}
void ReceiveThreadFunction();
};
}