Revert "hoc-clk: add live vdd2, live boost clock and basic pwm dimming"
This reverts commit 15b7df8ef1.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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__)
|
||||
|
||||
}
|
||||
@@ -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 ¤t_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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 ¤t_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);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user