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

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

View File

@@ -1,438 +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 "creport_crash_report.hpp"
#include "creport_utils.hpp"
namespace ams::creport {
namespace {
/* Convenience definitions. */
constexpr size_t DyingMessageAddressOffset = 0x1C0;
static_assert(DyingMessageAddressOffset == AMS_OFFSETOF(ams::svc::aarch64::ProcessLocalRegion, dying_message_region_address));
static_assert(DyingMessageAddressOffset == AMS_OFFSETOF(ams::svc::aarch32::ProcessLocalRegion, dying_message_region_address));
constexpr size_t CrashReportDataCacheSize = 256_KB;
/* Helper functions. */
bool TryGetCurrentTimestamp(u64 *out) {
/* Clear output. */
*out = 0;
/* Check if we have time service. */
{
bool has_time_service = false;
if (R_FAILED(sm::HasService(&has_time_service, sm::ServiceName::Encode("time:s"))) || !has_time_service) {
return false;
}
}
/* Try to get the current time. */
{
if (R_FAILED(::timeInitialize())) {
return false;
}
ON_SCOPE_EXIT { ::timeExit(); };
return R_SUCCEEDED(::timeGetCurrentTime(TimeType_LocalSystemClock, out));
}
}
void TryCreateReportDirectories() {
fs::EnsureDirectory("sdmc:/atmosphere/crash_reports/dumps");
fs::EnsureDirectory("sdmc:/atmosphere/fatal_reports/dumps");
}
constexpr const char *GetDebugExceptionString(const svc::DebugException type) {
switch (type) {
case svc::DebugException_UndefinedInstruction:
return "Undefined Instruction";
case svc::DebugException_InstructionAbort:
return "Instruction Abort";
case svc::DebugException_DataAbort:
return "Data Abort";
case svc::DebugException_AlignmentFault:
return "Alignment Fault";
case svc::DebugException_DebuggerAttached:
return "Debugger Attached";
case svc::DebugException_BreakPoint:
return "Break Point";
case svc::DebugException_UserBreak:
return "User Break";
case svc::DebugException_DebuggerBreak:
return "Debugger Break";
case svc::DebugException_UndefinedSystemCall:
return "Undefined System Call";
case svc::DebugException_MemorySystemError:
return "System Memory Error";
default:
return "Unknown";
}
}
}
void CrashReport::Initialize() {
/* Initialize the heap. */
m_heap_handle = lmem::CreateExpHeap(m_heap_storage, sizeof(m_heap_storage), lmem::CreateOption_None);
/* Allocate members. */
m_module_list = std::construct_at(static_cast<ModuleList *>(lmem::AllocateFromExpHeap(m_heap_handle, sizeof(ModuleList))));
m_thread_list = std::construct_at(static_cast<ThreadList *>(lmem::AllocateFromExpHeap(m_heap_handle, sizeof(ThreadList))));
m_dying_message = static_cast<u8 *>(lmem::AllocateFromExpHeap(m_heap_handle, DyingMessageSizeMax));
if (m_dying_message != nullptr) {
std::memset(m_dying_message, 0, DyingMessageSizeMax);
}
}
void CrashReport::BuildReport(os::ProcessId process_id, bool has_extra_info) {
m_has_extra_info = has_extra_info;
if (this->OpenProcess(process_id)) {
/* Parse info from the crashed process. */
this->ProcessExceptions();
m_module_list->FindModulesFromThreadInfo(m_debug_handle, m_crashed_thread, this->Is64Bit());
m_thread_list->ReadFromProcess(m_debug_handle, m_thread_tls_map, this->Is64Bit());
/* Associate module list to threads. */
m_crashed_thread.SetModuleList(m_module_list);
m_thread_list->SetModuleList(m_module_list);
/* Process dying message for applications. */
if (this->IsApplication()) {
this->ProcessDyingMessage();
}
/* Nintendo's creport finds extra modules by looking at all threads if application, */
/* but there's no reason for us not to always go looking. */
for (size_t i = 0; i < m_thread_list->GetThreadCount(); i++) {
m_module_list->FindModulesFromThreadInfo(m_debug_handle, m_thread_list->GetThreadInfo(i), this->Is64Bit());
}
/* Cache the module base address to send to fatal. */
if (m_module_list->GetModuleCount()) {
m_module_base_address = m_module_list->GetModuleStartAddress(0);
}
/* Nintendo's creport saves the report to erpt here, but we'll save to SD card later. */
}
}
void CrashReport::GetFatalContext(::FatalCpuContext *_out) const {
static_assert(sizeof(*_out) == sizeof(ams::fatal::CpuContext));
ams::fatal::CpuContext *out = reinterpret_cast<ams::fatal::CpuContext *>(_out);
std::memset(out, 0, sizeof(*out));
/* TODO: Support generating 32-bit fatal contexts? */
out->architecture = fatal::CpuContext::Architecture_Aarch64;
out->type = static_cast<u32>(m_exception_info.type);
for (size_t i = 0; i < fatal::aarch64::RegisterName_FP; i++) {
out->aarch64_ctx.SetRegisterValue(static_cast<fatal::aarch64::RegisterName>(i), m_crashed_thread.GetGeneralPurposeRegister(i));
}
out->aarch64_ctx.SetRegisterValue(fatal::aarch64::RegisterName_FP, m_crashed_thread.GetFP());
out->aarch64_ctx.SetRegisterValue(fatal::aarch64::RegisterName_LR, m_crashed_thread.GetLR());
out->aarch64_ctx.SetRegisterValue(fatal::aarch64::RegisterName_SP, m_crashed_thread.GetSP());
out->aarch64_ctx.SetRegisterValue(fatal::aarch64::RegisterName_PC, m_crashed_thread.GetPC());
out->aarch64_ctx.stack_trace_size = m_crashed_thread.GetStackTraceSize();
for (size_t i = 0; i < out->aarch64_ctx.stack_trace_size; i++) {
out->aarch64_ctx.stack_trace[i] = m_crashed_thread.GetStackTrace(i);
}
if (m_module_base_address != 0) {
out->aarch64_ctx.SetBaseAddress(m_module_base_address);
}
/* For ams fatal, which doesn't use afsr0, pass program_id instead. */
out->aarch64_ctx.SetProgramIdForAtmosphere(ncm::ProgramId{m_process_info.program_id});
}
void CrashReport::ProcessExceptions() {
/* Loop all debug events. */
svc::DebugEventInfo d;
while (R_SUCCEEDED(svc::GetDebugEvent(std::addressof(d), m_debug_handle))) {
switch (d.type) {
case svc::DebugEvent_CreateProcess:
this->HandleDebugEventInfoCreateProcess(d);
break;
case svc::DebugEvent_CreateThread:
this->HandleDebugEventInfoCreateThread(d);
break;
case svc::DebugEvent_Exception:
this->HandleDebugEventInfoException(d);
break;
case svc::DebugEvent_ExitProcess:
case svc::DebugEvent_ExitThread:
break;
}
}
/* Parse crashed thread info. */
m_crashed_thread.ReadFromProcess(m_debug_handle, m_thread_tls_map, m_crashed_thread_id, this->Is64Bit());
}
void CrashReport::HandleDebugEventInfoCreateProcess(const svc::DebugEventInfo &d) {
m_process_info = d.info.create_process;
/* On 5.0.0+, we want to parse out a dying message from application crashes. */
if (hos::GetVersion() < hos::Version_5_0_0 || !IsApplication()) {
return;
}
/* Parse out user data. */
const u64 address = m_process_info.user_exception_context_address + DyingMessageAddressOffset;
u64 userdata_address = 0;
u64 userdata_size = 0;
/* Read userdata address. */
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(userdata_address)), m_debug_handle, address, sizeof(userdata_address)))) {
return;
}
/* Validate userdata address. */
if (userdata_address == 0 || userdata_address & 0xFFF) {
return;
}
/* Read userdata size. */
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(userdata_size)), m_debug_handle, address + sizeof(userdata_address), sizeof(userdata_size)))) {
return;
}
/* Cap userdata size. */
userdata_size = std::min(size_t(userdata_size), DyingMessageSizeMax);
m_dying_message_address = userdata_address;
m_dying_message_size = userdata_size;
}
void CrashReport::HandleDebugEventInfoCreateThread(const svc::DebugEventInfo &d) {
/* Save info on the thread's TLS address for later. */
m_thread_tls_map.SetThreadTls(d.info.create_thread.thread_id, d.info.create_thread.tls_address);
}
void CrashReport::HandleDebugEventInfoException(const svc::DebugEventInfo &d) {
switch (d.info.exception.type) {
case svc::DebugException_UndefinedInstruction:
m_result = creport::ResultUndefinedInstruction();
break;
case svc::DebugException_InstructionAbort:
m_result = creport::ResultInstructionAbort();
break;
case svc::DebugException_DataAbort:
m_result = creport::ResultDataAbort();
break;
case svc::DebugException_AlignmentFault:
m_result = creport::ResultAlignmentFault();
break;
case svc::DebugException_UserBreak:
m_result = creport::ResultUserBreak();
/* Try to parse out the user break result. */
if (hos::GetVersion() >= hos::Version_5_0_0) {
svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(m_result)), m_debug_handle, d.info.exception.specific.user_break.address, sizeof(m_result));
}
break;
case svc::DebugException_UndefinedSystemCall:
m_result = creport::ResultUndefinedSystemCall();
break;
case svc::DebugException_MemorySystemError:
m_result = creport::ResultMemorySystemError();
break;
case svc::DebugException_DebuggerAttached:
case svc::DebugException_BreakPoint:
case svc::DebugException_DebuggerBreak:
return;
}
/* Save exception info. */
m_exception_info = d.info.exception;
m_crashed_thread_id = d.thread_id;
}
void CrashReport::ProcessDyingMessage() {
/* Dying message is only stored starting in 5.0.0. */
if (hos::GetVersion() < hos::Version_5_0_0) {
return;
}
/* Validate address/size. */
if (m_dying_message_address == 0 || m_dying_message_address & 0xFFF) {
return;
}
if (m_dying_message_size > DyingMessageSizeMax) {
return;
}
/* Validate that the current report isn't garbage. */
if (!IsOpen() || !IsComplete()) {
return;
}
/* Verify that we have a dying message buffer. */
if (m_dying_message == nullptr) {
return;
}
/* Read the dying message. */
svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(m_dying_message), m_debug_handle, m_dying_message_address, m_dying_message_size);
}
void CrashReport::SaveReport(bool enable_screenshot) {
/* Try to ensure path exists. */
TryCreateReportDirectories();
/* Get a timestamp. */
u64 timestamp;
if (!TryGetCurrentTimestamp(&timestamp)) {
timestamp = os::GetSystemTick().GetInt64Value();
}
/* Save files. */
{
char file_path[fs::EntryNameLengthMax + 1];
/* Save crash report. */
util::SNPrintf(file_path, sizeof(file_path), "sdmc:/atmosphere/crash_reports/%011lu_%016lx.log", timestamp, m_process_info.program_id);
{
/* Try to allocate data cache. */
void * const data_cache = lmem::AllocateFromExpHeap(m_heap_handle, CrashReportDataCacheSize + os::MemoryPageSize);
ON_SCOPE_EXIT { if (data_cache != nullptr) { lmem::FreeToExpHeap(m_heap_handle, data_cache); } };
/* Align up the data cache. This is safe because null will align up to null. */
void * const aligned_cache = reinterpret_cast<void *>(util::AlignUp(reinterpret_cast<uintptr_t>(data_cache), os::MemoryPageSize));
/* Open and save the file using the cache. */
ScopedFile file(file_path, aligned_cache, aligned_cache != nullptr ? CrashReportDataCacheSize : 0);
if (file.IsOpen()) {
this->SaveToFile(file);
}
}
/* Dump threads. */
util::SNPrintf(file_path, sizeof(file_path), "sdmc:/atmosphere/crash_reports/dumps/%011lu_%016lx_thread_info.bin", timestamp, m_process_info.program_id);
{
ScopedFile file(file_path);
if (file.IsOpen()) {
m_thread_list->DumpBinary(file, m_crashed_thread.GetThreadId());
}
}
/* If we're open, we need to close here. */
if (this->IsOpen()) {
this->Close();
}
/* Finalize our heap. */
std::destroy_at(m_module_list);
std::destroy_at(m_thread_list);
lmem::FreeToExpHeap(m_heap_handle, m_module_list);
lmem::FreeToExpHeap(m_heap_handle, m_thread_list);
if (m_dying_message != nullptr) {
lmem::FreeToExpHeap(m_heap_handle, m_dying_message);
}
m_module_list = nullptr;
m_thread_list = nullptr;
m_dying_message = nullptr;
/* Try to take a screenshot. */
/* NOTE: Nintendo validates that enable_screenshot is true here, and validates that the application id is not in a blacklist. */
/* Since we save reports only locally and do not send them via telemetry, we will skip this. */
AMS_UNUSED(enable_screenshot);
if (hos::GetVersion() >= hos::Version_9_0_0 && this->IsApplication()) {
if (R_SUCCEEDED(capsrv::InitializeScreenShotControl())) {
ON_SCOPE_EXIT { capsrv::FinalizeScreenShotControl(); };
u64 jpeg_size;
if (R_SUCCEEDED(capsrv::CaptureJpegScreenshot(std::addressof(jpeg_size), m_heap_storage, sizeof(m_heap_storage), vi::LayerStack_ApplicationForDebug, TimeSpan::FromSeconds(10)))) {
util::SNPrintf(file_path, sizeof(file_path), "sdmc:/atmosphere/crash_reports/%011lu_%016lx.jpg", timestamp, m_process_info.program_id);
ScopedFile file(file_path);
if (file.IsOpen()) {
file.Write(m_heap_storage, jpeg_size);
}
}
}
}
}
}
void CrashReport::SaveToFile(ScopedFile &file) {
file.WriteFormat("Atmosphère Crash Report (v1.7):\n");
file.WriteFormat("Result: 0x%X (2%03d-%04d)\n\n", m_result.GetValue(), m_result.GetModule(), m_result.GetDescription());
/* Process Info. */
char name_buf[0x10] = {};
static_assert(sizeof(name_buf) >= sizeof(m_process_info.name), "buffer overflow!");
std::memcpy(name_buf, m_process_info.name, sizeof(m_process_info.name));
file.WriteFormat("Process Info:\n");
file.WriteFormat(" Process Name: %s\n", name_buf);
file.WriteFormat(" Program ID: %016lx\n", m_process_info.program_id);
file.WriteFormat(" Process ID: %016lx\n", m_process_info.process_id);
file.WriteFormat(" Process Flags: %08x\n", m_process_info.flags);
if (hos::GetVersion() >= hos::Version_5_0_0) {
file.WriteFormat(" User Exception Address: %s\n", m_module_list->GetFormattedAddressString(m_process_info.user_exception_context_address));
}
/* Exception Info. */
file.WriteFormat("Exception Info:\n");
file.WriteFormat(" Type: %s\n", GetDebugExceptionString(m_exception_info.type));
file.WriteFormat(" Address: %s\n", m_module_list->GetFormattedAddressString(m_exception_info.address));
switch (m_exception_info.type) {
case svc::DebugException_UndefinedInstruction:
file.WriteFormat(" Opcode: %08x\n", m_exception_info.specific.undefined_instruction.insn);
break;
case svc::DebugException_DataAbort:
case svc::DebugException_AlignmentFault:
if (m_exception_info.specific.raw != m_exception_info.address) {
file.WriteFormat(" Fault Address: %s\n", m_module_list->GetFormattedAddressString(m_exception_info.specific.raw));
}
break;
case svc::DebugException_UndefinedSystemCall:
file.WriteFormat(" Svc Id: 0x%02x\n", m_exception_info.specific.undefined_system_call.id);
break;
case svc::DebugException_UserBreak:
file.WriteFormat(" Break Reason: 0x%x\n", m_exception_info.specific.user_break.break_reason);
file.WriteFormat(" Break Address: %s\n", m_module_list->GetFormattedAddressString(m_exception_info.specific.user_break.address));
file.WriteFormat(" Break Size: 0x%lx\n", m_exception_info.specific.user_break.size);
break;
default:
break;
}
/* Crashed Thread Info. */
file.WriteFormat("Crashed Thread Info:\n");
m_crashed_thread.SaveToFile(file);
/* Dying Message. */
if (hos::GetVersion() >= hos::Version_5_0_0 && m_dying_message_size != 0) {
file.WriteFormat("Dying Message Info:\n");
file.WriteFormat(" Address: 0x%s\n", m_module_list->GetFormattedAddressString(m_dying_message_address));
file.WriteFormat(" Size: 0x%016lx\n", m_dying_message_size);
file.DumpMemory( " Dying Message: ", m_dying_message, m_dying_message_size);
}
/* Module Info. */
file.WriteFormat("Module Info:\n");
m_module_list->SaveToFile(file);
/* Thread Info. */
file.WriteFormat("Thread Report:\n");
m_thread_list->SaveToFile(file);
}
}

View File

@@ -1,105 +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 "creport_threads.hpp"
#include "creport_modules.hpp"
namespace ams::creport {
class CrashReport {
private:
static constexpr size_t DyingMessageSizeMax = os::MemoryPageSize;
static constexpr size_t MemoryHeapSize = 512_KB;
static_assert(MemoryHeapSize >= DyingMessageSizeMax + sizeof(ModuleList) + sizeof(ThreadList) + os::MemoryPageSize);
private:
os::NativeHandle m_debug_handle = os::InvalidNativeHandle;
bool m_has_extra_info = true;
Result m_result = creport::ResultIncompleteReport();
/* Meta, used for building module/thread list. */
ThreadTlsMap m_thread_tls_map = {};
/* Attach process info. */
svc::DebugInfoCreateProcess m_process_info = {};
u64 m_dying_message_address = 0;
u64 m_dying_message_size = 0;
u8 *m_dying_message = nullptr;
/* Exception info. */
svc::DebugInfoException m_exception_info = {};
u64 m_module_base_address = 0;
u64 m_crashed_thread_id = 0;
ThreadInfo m_crashed_thread;
/* Lists. */
ModuleList *m_module_list = nullptr;
ThreadList *m_thread_list = nullptr;
/* Memory heap. */
lmem::HeapHandle m_heap_handle = nullptr;
u8 m_heap_storage[MemoryHeapSize] = {};
public:
constexpr CrashReport() = default;
Result GetResult() const {
return m_result;
}
bool IsComplete() const {
return !ResultIncompleteReport::Includes(m_result);
}
bool IsOpen() const {
return m_debug_handle != os::InvalidNativeHandle;
}
bool IsApplication() const {
return (m_process_info.flags & svc::CreateProcessFlag_IsApplication) != 0;
}
bool Is64Bit() const {
return (m_process_info.flags & svc::CreateProcessFlag_Is64Bit) != 0;
}
bool IsUserBreak() const {
return m_exception_info.type == svc::DebugException_UserBreak;
}
bool OpenProcess(os::ProcessId process_id) {
return R_SUCCEEDED(svc::DebugActiveProcess(std::addressof(m_debug_handle), process_id.value));
}
void Close() {
os::CloseNativeHandle(m_debug_handle);
m_debug_handle = os::InvalidNativeHandle;
}
void Initialize();
void BuildReport(os::ProcessId process_id, bool has_extra_info);
void GetFatalContext(::FatalCpuContext *out) const;
void SaveReport(bool enable_screenshot);
private:
void ProcessExceptions();
void ProcessDyingMessage();
void HandleDebugEventInfoCreateProcess(const svc::DebugEventInfo &d);
void HandleDebugEventInfoCreateThread(const svc::DebugEventInfo &d);
void HandleDebugEventInfoException(const svc::DebugEventInfo &d);
void SaveToFile(ScopedFile &file);
};
}

View File

@@ -1,160 +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 "creport_crash_report.hpp"
#include "creport_utils.hpp"
namespace ams {
namespace creport {
namespace {
constinit u8 g_fs_heap_memory[4_KB];
lmem::HeapHandle g_fs_heap_handle;
void *AllocateForFs(size_t size) {
return lmem::AllocateFromExpHeap(g_fs_heap_handle, size);
}
void DeallocateForFs(void *p, size_t size) {
AMS_UNUSED(size);
return lmem::FreeToExpHeap(g_fs_heap_handle, p);
}
void InitializeFsHeap() {
g_fs_heap_handle = lmem::CreateExpHeap(g_fs_heap_memory, sizeof(g_fs_heap_memory), lmem::CreateOption_None);
}
}
}
namespace init {
void InitializeSystemModule() {
/* Initialize heap. */
creport::InitializeFsHeap();
/* Initialize our connection to sm. */
R_ABORT_UNLESS(sm::Initialize());
/* Initialize fs. */
fs::InitializeForSystem();
fs::SetAllocator(creport::AllocateForFs, creport::DeallocateForFs);
fs::SetEnabledAutoAbort(false);
/* Mount the SD card. */
R_ABORT_UNLESS(fs::MountSdCard("sdmc"));
}
void FinalizeSystemModule() { /* ... */ }
void Startup() { /* ... */ }
}
namespace {
constinit creport::CrashReport g_crash_report;
}
void Main() {
/* Set thread name. */
os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(creport, Main));
AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(creport, Main));
/* Get arguments. */
const int num_args = os::GetHostArgc();
char ** const args = os::GetHostArgv();
/* Validate arguments. */
if (num_args < 2) {
return;
}
for (auto i = 0; i < num_args; ++i) {
if (args[i] == nullptr) {
return;
}
}
/* Parse arguments. */
const os::ProcessId crashed_pid = creport::ParseProcessIdArgument(args[0]);
const bool has_extra_info = args[1][0] == '1';
const bool enable_screenshot = num_args >= 3 && args[2][0] == '1';
const bool enable_jit_debug = num_args >= 4 && args[3][0] == '1';
/* Initialize the crash report. */
g_crash_report.Initialize();
/* Try to debug the crashed process. */
{
g_crash_report.BuildReport(crashed_pid, has_extra_info);
ON_SCOPE_EXIT { if (g_crash_report.IsOpen()) { g_crash_report.Close(); } };
if (!g_crash_report.IsComplete()) {
return;
}
/* Save report to file. */
g_crash_report.SaveReport(enable_screenshot);
}
/* Try to terminate the process, if we should. */
const auto fw_ver = hos::GetVersion();
if (fw_ver < hos::Version_11_0_0 || !enable_jit_debug) {
if (fw_ver >= hos::Version_10_0_0) {
/* Use pgl to terminate. */
if (R_SUCCEEDED(pgl::Initialize())) {
ON_SCOPE_EXIT { pgl::Finalize(); };
pgl::TerminateProcess(crashed_pid);
}
} else {
/* Use ns to terminate. */
if (R_SUCCEEDED(::nsdevInitialize())) {
ON_SCOPE_EXIT { ::nsdevExit(); };
nsdevTerminateProcess(crashed_pid.value);
}
}
}
/* If we're on 5.0.0+ and an application crashed, or if we have extra info, we don't need to fatal. */
if (fw_ver >= hos::Version_5_0_0) {
if (g_crash_report.IsApplication()) {
return;
}
} else if (has_extra_info) {
return;
}
/* We also don't need to fatal on user break. */
if (g_crash_report.IsUserBreak()) {
return;
}
/* Throw fatal error. */
{
::FatalCpuContext ctx;
g_crash_report.GetFatalContext(std::addressof(ctx));
fatalThrowWithContext(g_crash_report.GetResult().GetValue(), FatalPolicy_ErrorScreen, std::addressof(ctx));
}
}
}

View File

@@ -1,430 +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 "creport_modules.hpp"
#include "creport_utils.hpp"
namespace ams::creport {
namespace {
/* Convenience definitions/types. */
constexpr size_t ModulePathLengthMax = 0x200;
constexpr u8 GnuSignature[4] = {'G', 'N', 'U', 0};
struct ModulePath {
u32 zero;
s32 path_length;
char path[ModulePathLengthMax];
};
static_assert(sizeof(ModulePath) == 0x208, "ModulePath definition!");
struct RoDataStart {
union {
u64 deprecated_rwdata_offset;
ModulePath module_path;
};
};
static_assert(sizeof(RoDataStart) == sizeof(ModulePath), "RoDataStart definition!");
/* Globals. */
u8 g_last_rodata_pages[2 * os::MemoryPageSize];
}
void ModuleList::SaveToFile(ScopedFile &file) {
file.WriteFormat(" Number of Modules: %02zu\n", m_num_modules);
for (size_t i = 0; i < m_num_modules; i++) {
const auto& module = m_modules[i];
file.WriteFormat(" Module %02zu:\n", i);
file.WriteFormat(" Address: %016lx-%016lx\n", module.start_address, module.end_address);
if (std::strcmp(m_modules[i].name, "") != 0) {
file.WriteFormat(" Name: %s\n", module.name);
}
file.DumpMemory(" Module Id: ", module.module_id, sizeof(module.module_id));
}
}
void ModuleList::FindModulesFromThreadInfo(os::NativeHandle debug_handle, const ThreadInfo &thread, bool is_64_bit) {
/* Set the debug handle, for access in other member functions. */
m_debug_handle = debug_handle;
/* Try to add the thread's PC. */
this->TryAddModule(thread.GetPC(), is_64_bit);
/* Try to add the thread's LR. */
this->TryAddModule(thread.GetLR(), is_64_bit);
/* Try to add all the addresses in the thread's stacktrace. */
for (size_t i = 0; i < thread.GetStackTraceSize(); i++) {
this->TryAddModule(thread.GetStackTrace(i), is_64_bit);
}
}
void ModuleList::TryAddModule(uintptr_t guess, bool is_64_bit) {
/* Try to locate module from guess. */
uintptr_t base_address = 0;
if (!this->TryFindModule(std::addressof(base_address), guess, is_64_bit)) {
return;
}
/* Check whether we already have this module. */
for (size_t i = 0; i < m_num_modules; i++) {
if (m_modules[i].start_address <= base_address && base_address < m_modules[i].end_address) {
return;
}
}
/* Add all contiguous modules. */
uintptr_t cur_address = base_address;
while (m_num_modules < ModuleCountMax) {
/* Get the region extents. */
svc::MemoryInfo mi;
svc::PageInfo pi;
if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), m_debug_handle, cur_address))) {
break;
}
/* Parse module. */
if (mi.permission == svc::MemoryPermission_ReadExecute) {
auto& module = m_modules[m_num_modules++];
module.start_address = mi.base_address;
module.end_address = mi.base_address + mi.size;
GetModuleName(module.name, module.start_address, module.end_address);
GetModuleId(module.module_id, module.end_address);
/* Default to no symbol table. */
module.has_sym_table = false;
if (std::strcmp(module.name, "") == 0) {
/* Some homebrew won't have a name. Add a fake one for readability. */
util::SNPrintf(module.name, sizeof(module.name), "[%02x%02x%02x%02x]", module.module_id[0], module.module_id[1], module.module_id[2], module.module_id[3]);
} else {
/* The module has a name, and so might have a symbol table. Try to add it, if it does. */
if (is_64_bit) {
DetectModuleSymbolTable(module);
}
}
}
/* If we're out of readable memory, we're done reading code. */
if (mi.state == svc::MemoryState_Free || mi.state == svc::MemoryState_Inaccessible) {
break;
}
/* Verify we're not getting stuck in an infinite loop. */
if (mi.size == 0 || cur_address + mi.size <= cur_address) {
break;
}
cur_address += mi.size;
}
}
bool ModuleList::TryFindModule(uintptr_t *out_address, uintptr_t guess, bool is_64_bit) {
AMS_UNUSED(is_64_bit);
/* Query the memory region our guess falls in. */
svc::MemoryInfo mi;
svc::PageInfo pi;
if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), m_debug_handle, guess))) {
return false;
}
/* If we fall into a RW region, it may be rwdata. Query the region before it, which may be rodata or text. */
if (mi.permission == svc::MemoryPermission_ReadWrite) {
if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), m_debug_handle, mi.base_address - 4))) {
return false;
}
}
/* If we fall into an RO region, it may be rodata. Query the region before it, which should be text. */
if (mi.permission == svc::MemoryPermission_Read) {
if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), m_debug_handle, mi.base_address - 4))) {
return false;
}
}
/* We should, at this point, be looking at an executable region (text). */
if (mi.permission != svc::MemoryPermission_ReadExecute) {
return false;
}
/* Modules are a series of contiguous (text/rodata/rwdata) regions. */
/* Iterate backwards until we find unmapped memory, to find the start of the set of modules loaded here. */
while (mi.base_address > 0) {
if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), m_debug_handle, mi.base_address - 4))) {
return false;
}
if (mi.state == svc::MemoryState_Free) {
/* We've found unmapped memory, so output the mapped memory afterwards. */
*out_address = mi.base_address + mi.size;
return true;
}
}
/* Something weird happened here. */
return false;
}
void ModuleList::GetModuleName(char *out_name, uintptr_t text_start_address, uintptr_t ro_start_address) {
/* Clear output. */
std::memset(out_name, 0, ModuleNameLengthMax);
/* Read module path from process memory. */
RoDataStart rodata_start;
{
svc::MemoryInfo mi;
svc::PageInfo pi;
/* Verify .rodata is read-only. */
if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), m_debug_handle, ro_start_address)) || mi.permission != svc::MemoryPermission_Read) {
return;
}
/* Calculate start of rwdata. */
const u64 rw_start_address = mi.base_address + mi.size;
/* Read start of .rodata. */
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(rodata_start)), m_debug_handle, ro_start_address, sizeof(rodata_start)))) {
return;
}
/* If data is valid under deprecated format, there's no name. */
if (text_start_address + rodata_start.deprecated_rwdata_offset == rw_start_address) {
return;
}
/* Also validate that we're looking at a valid name. */
if (rodata_start.module_path.zero != 0 || rodata_start.module_path.path_length <= 0) {
return;
}
}
/* Start after last slash in path. */
const char *path = rodata_start.module_path.path;
int ofs;
for (ofs = std::min<size_t>(rodata_start.module_path.path_length, sizeof(rodata_start.module_path.path)); ofs >= 0; ofs--) {
if (path[ofs] == '/' || path[ofs] == '\\') {
break;
}
}
ofs++;
/* Copy name to output. */
const size_t name_size = std::min(ModuleNameLengthMax, std::min<size_t>(rodata_start.module_path.path_length, sizeof(rodata_start.module_path.path)) - ofs);
std::memcpy(out_name, path + ofs, name_size);
out_name[ModuleNameLengthMax - 1] = '\x00';
}
void ModuleList::GetModuleId(u8 *out, uintptr_t ro_start_address) {
/* Clear output. */
std::memset(out, 0, ModuleIdSize);
/* Verify .rodata is read-only. */
svc::MemoryInfo mi;
svc::PageInfo pi;
if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), m_debug_handle, ro_start_address)) || mi.permission != svc::MemoryPermission_Read) {
return;
}
/* We want to read the last two pages of .rodata. */
const size_t read_size = mi.size >= sizeof(g_last_rodata_pages) ? sizeof(g_last_rodata_pages) : (sizeof(g_last_rodata_pages) / 2);
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(g_last_rodata_pages), m_debug_handle, mi.base_address + mi.size - read_size, read_size))) {
return;
}
/* Find GNU\x00 to locate start of module id (GNU build id). */
for (int ofs = read_size - sizeof(GnuSignature) - ModuleIdSize; ofs >= 0; ofs--) {
if (std::memcmp(g_last_rodata_pages + ofs, GnuSignature, sizeof(GnuSignature)) == 0) {
std::memcpy(out, g_last_rodata_pages + ofs + sizeof(GnuSignature), ModuleIdSize);
break;
}
}
}
void ModuleList::DetectModuleSymbolTable(ModuleInfo &module) {
/* If we already have a symbol table, no more parsing is needed. */
if (module.has_sym_table) {
return;
}
/* Declare temporaries. */
u64 temp_64;
u32 temp_32;
/* Get module state. */
svc::MemoryInfo mi;
svc::PageInfo pi;
if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), m_debug_handle, module.start_address))) {
return;
}
const auto module_state = mi.state;
/* Verify .rodata is read-only with same state as .text. */
if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), m_debug_handle, module.end_address)) || mi.permission != svc::MemoryPermission_Read || mi.state != module_state) {
return;
}
/* We want to find the symbol table/.dynamic. */
uintptr_t dyn_address = 0;
uintptr_t sym_tab = 0;
uintptr_t str_tab = 0;
size_t num_sym = 0;
/* Locate .dyn using rocrt::ModuleHeader. */
{
/* Determine the ModuleHeader offset. */
u32 mod_offset;
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(mod_offset)), m_debug_handle, module.start_address + sizeof(u32), sizeof(u32)))) {
return;
}
/* Read the signature. */
constexpr u32 SignatureFieldOffset = AMS_OFFSETOF(rocrt::ModuleHeader, signature);
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(temp_32)), m_debug_handle, module.start_address + mod_offset + SignatureFieldOffset, sizeof(u32)))) {
return;
}
/* Check that the module signature is expected. */
if (temp_32 != rocrt::ModuleHeaderVersion) { /* MOD0 */
return;
}
/* Determine the dynamic offset. */
constexpr u32 DynamicFieldOffset = AMS_OFFSETOF(rocrt::ModuleHeader, dynamic_offset);
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(temp_32)), m_debug_handle, module.start_address + mod_offset + DynamicFieldOffset, sizeof(u32)))) {
return;
}
dyn_address = module.start_address + mod_offset + temp_32;
}
/* Locate tables inside .dyn. */
for (size_t ofs = 0; /* ... */; ofs += 0x10) {
/* Read the DynamicTag. */
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(temp_64)), m_debug_handle, dyn_address + ofs, sizeof(u64)))) {
return;
}
if (temp_64 == 0) {
/* We're done parsing .dyn. */
break;
} else if (temp_64 == 4) {
/* We found DT_HASH */
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(temp_64)), m_debug_handle, dyn_address + ofs + sizeof(u64), sizeof(u64)))) {
return;
}
/* Read nchain, to get the number of symbols. */
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(temp_32)), m_debug_handle, module.start_address + temp_64 + sizeof(u32), sizeof(u32)))) {
return;
}
num_sym = temp_32;
} else if (temp_64 == 5) {
/* We found DT_STRTAB */
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(temp_64)), m_debug_handle, dyn_address + ofs + sizeof(u64), sizeof(u64)))) {
return;
}
str_tab = module.start_address + temp_64;
} else if (temp_64 == 6) {
/* We found DT_SYMTAB */
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(temp_64)), m_debug_handle, dyn_address + ofs + sizeof(u64), sizeof(u64)))) {
return;
}
sym_tab = module.start_address + temp_64;
}
}
/* Check that we found all the tables. */
if (!(sym_tab != 0 && str_tab != 0 && num_sym != 0)) {
return;
}
module.has_sym_table = true;
module.sym_tab = sym_tab;
module.str_tab = str_tab;
module.num_sym = static_cast<u32>(num_sym);
}
const char *ModuleList::GetFormattedAddressString(uintptr_t address) {
/* Print default formatted string. */
util::SNPrintf(m_address_str_buf, sizeof(m_address_str_buf), "%016lx", address);
/* See if the address is inside a module, for pretty-printing. */
for (size_t i = 0; i < m_num_modules; i++) {
const auto& module = m_modules[i];
if (module.start_address <= address && address < module.end_address) {
if (module.has_sym_table) {
/* Try to locate an appropriate symbol. */
for (size_t j = 0; j < module.num_sym; ++j) {
/* Read symbol from the module's symbol table. */
struct {
u32 st_name;
u8 st_info;
u8 st_other;
u16 st_shndx;
u64 st_value;
u64 st_size;
} sym;
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(sym)), m_debug_handle, module.sym_tab + j * sizeof(sym), sizeof(sym)))) {
break;
}
/* Check the symbol is valid/STT_FUNC. */
if (sym.st_shndx == 0 || ((sym.st_shndx & 0xFF00) == 0xFF00)) {
continue;
}
if ((sym.st_info & 0xF) != 2) {
continue;
}
/* Check the address. */
const uintptr_t func_start = module.start_address + sym.st_value;
if (func_start <= address && address < func_start + sym.st_size) {
/* Read the symbol name. */
const uintptr_t sym_address = module.str_tab + sym.st_name;
char sym_name[0x80];
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(sym_name), m_debug_handle, sym_address, sizeof(sym_name)))) {
break;
}
/* Ensure null-termination. */
sym_name[sizeof(sym_name) - 1] = '\x00';
/* Print the symbol. */
util::SNPrintf(m_address_str_buf, sizeof(m_address_str_buf), "%016lx (%s + 0x%lx) (%s + 0x%lx)", address, module.name, address - module.start_address, sym_name, address - func_start);
return m_address_str_buf;
}
}
}
util::SNPrintf(m_address_str_buf, sizeof(m_address_str_buf), "%016lx (%s + 0x%lx)", address, module.name, address - module.start_address);
return m_address_str_buf;
}
}
return m_address_str_buf;
}
}

View File

@@ -1,69 +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 "creport_scoped_file.hpp"
#include "creport_threads.hpp"
namespace ams::creport {
class ModuleList {
private:
static constexpr size_t ModuleCountMax = 0x60;
static constexpr size_t ModuleNameLengthMax = 0x20;
static constexpr size_t ModuleIdSize = 0x20;
struct ModuleInfo {
char name[ModuleNameLengthMax];
u8 module_id[ModuleIdSize];
u64 start_address;
u64 end_address;
bool has_sym_table;
u64 sym_tab;
u64 str_tab;
u32 num_sym;
};
private:
os::NativeHandle m_debug_handle;
size_t m_num_modules;
ModuleInfo m_modules[ModuleCountMax];
/* For pretty-printing. */
char m_address_str_buf[1_KB];
public:
ModuleList() : m_debug_handle(os::InvalidNativeHandle), m_num_modules(0) {
std::memset(m_modules, 0, sizeof(m_modules));
}
size_t GetModuleCount() const {
return m_num_modules;
}
u64 GetModuleStartAddress(size_t i) const {
return m_modules[i].start_address;
}
void FindModulesFromThreadInfo(os::NativeHandle debug_handle, const ThreadInfo &thread, bool is_64_bit);
const char *GetFormattedAddressString(uintptr_t address);
void SaveToFile(ScopedFile &file);
private:
bool TryFindModule(uintptr_t *out_address, uintptr_t guess, bool is_64_bit);
void TryAddModule(uintptr_t guess, bool is_64_bit);
void GetModuleName(char *out_name, uintptr_t text_start, uintptr_t ro_start);
void GetModuleId(u8 *out, uintptr_t ro_start);
void DetectModuleSymbolTable(ModuleInfo &module);
};
}

View File

@@ -1,124 +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 "creport_scoped_file.hpp"
namespace ams::creport {
namespace {
/* Convenience definitions. */
constexpr size_t MaximumLineLength = 0x20;
constinit os::SdkMutex g_format_lock;
constinit char g_format_buffer[2 * os::MemoryPageSize];
}
void ScopedFile::WriteString(const char *str) {
this->Write(str, std::strlen(str));
}
void ScopedFile::WriteFormat(const char *fmt, ...) {
/* Acquire exclusive access to the format buffer. */
std::scoped_lock lk(g_format_lock);
/* Format to the buffer. */
{
std::va_list vl;
va_start(vl, fmt);
util::VSNPrintf(g_format_buffer, sizeof(g_format_buffer), fmt, vl);
va_end(vl);
}
/* Write data. */
this->WriteString(g_format_buffer);
}
void ScopedFile::DumpMemory(const char *prefix, const void *data, size_t size) {
const u8 *data_u8 = reinterpret_cast<const u8 *>(data);
const int prefix_len = std::strlen(prefix);
size_t data_ofs = 0;
size_t remaining = size;
bool first = true;
while (remaining) {
const size_t cur_size = std::min(MaximumLineLength, remaining);
/* Print the line prefix. */
if (first) {
this->Write(prefix, prefix_len);
first = false;
} else {
std::memset(g_format_buffer, ' ', prefix_len);
this->Write(g_format_buffer, prefix_len);
}
/* Dump up to 0x20 of hex memory. */
{
char hex[MaximumLineLength * 2 + 2] = {};
for (size_t i = 0; i < cur_size; i++) {
hex[i * 2 + 0] = "0123456789ABCDEF"[data_u8[data_ofs] >> 4];
hex[i * 2 + 1] = "0123456789ABCDEF"[data_u8[data_ofs] & 0xF];
++data_ofs;
}
hex[cur_size * 2 + 0] = '\n';
this->Write(hex, cur_size * 2 + 1);
}
/* Continue. */
remaining -= cur_size;
}
}
void ScopedFile::Write(const void *data, size_t size) {
/* If we're not open, we can't write. */
if (!this->IsOpen()) {
return;
}
/* If we have a cache, write to it. */
if (m_cache != nullptr) {
/* Write into the cache, if we can. */
if (m_cache_size - m_cache_offset >= size || R_SUCCEEDED(this->TryWriteCache())) {
std::memcpy(m_cache + m_cache_offset, data, size);
m_cache_offset += size;
}
} else {
/* Advance, if we write successfully. */
if (R_SUCCEEDED(fs::WriteFile(m_file, m_offset, data, size, fs::WriteOption::None))) {
m_offset += size;
}
}
}
Result ScopedFile::TryWriteCache() {
/* If there's no cached data, there's nothing to do. */
R_SUCCEED_IF(m_cache_offset == 0);
/* Try to write any cached data. */
R_TRY(fs::WriteFile(m_file, m_offset, m_cache, m_cache_offset, fs::WriteOption::None));
/* Update our extents. */
m_offset += m_cache_offset;
m_cache_offset = 0;
R_SUCCEED();
}
}

View File

@@ -1,61 +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::creport {
class ScopedFile {
NON_COPYABLE(ScopedFile);
NON_MOVEABLE(ScopedFile);
private:
fs::FileHandle m_file;
s64 m_offset;
bool m_opened;
u8 *m_cache;
const size_t m_cache_size;
s64 m_cache_offset;
public:
ScopedFile(const char *path, void *cache = nullptr, size_t cache_size = 0) : m_file(), m_offset(), m_opened(false), m_cache(static_cast<u8*>(cache)), m_cache_size(cache_size), m_cache_offset(0) {
if (R_SUCCEEDED(fs::CreateFile(path, 0))) {
m_opened = R_SUCCEEDED(fs::OpenFile(std::addressof(m_file), path, fs::OpenMode_Write | fs::OpenMode_AllowAppend));
}
}
~ScopedFile() {
if (m_opened) {
if (m_cache != nullptr) {
this->TryWriteCache();
}
fs::FlushFile(m_file);
fs::CloseFile(m_file);
}
}
bool IsOpen() const {
return m_opened;
}
void WriteString(const char *str);
void WriteFormat(const char *fmt, ...) __attribute__((format(printf, 2, 3)));
void DumpMemory(const char *prefix, const void *data, size_t size);
void Write(const void *data, size_t size);
private:
Result TryWriteCache();
};
}

View File

@@ -1,276 +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 "creport_threads.hpp"
#include "creport_modules.hpp"
namespace ams::creport {
namespace {
/* Convenience definitions. */
constexpr u32 LibnxThreadVarMagic = util::FourCC<'!','T','V','$'>::Code;
constexpr u32 DumpedThreadInfoMagic = util::FourCC<'D','T','I','2'>::Code;
/* Types. */
template<typename T>
struct StackFrame {
T fp;
T lr;
};
/* Helpers. */
template<typename T>
void ReadStackTrace(size_t *out_trace_size, u64 *out_trace, size_t max_out_trace_size, os::NativeHandle debug_handle, u64 fp) {
size_t trace_size = 0;
u64 cur_fp = fp;
for (size_t i = 0; i < max_out_trace_size; i++) {
/* Validate the current frame. */
if (cur_fp == 0 || (cur_fp % sizeof(T) != 0)) {
break;
}
/* Read a new frame. */
StackFrame<T> cur_frame;
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(cur_frame)), debug_handle, cur_fp, sizeof(cur_frame)))) {
break;
}
/* Advance to the next frame. */
out_trace[trace_size++] = cur_frame.lr;
cur_fp = cur_frame.fp;
}
*out_trace_size = trace_size;
}
}
void ThreadList::SaveToFile(ScopedFile &file) {
file.WriteFormat("Number of Threads: %02zu\n", m_thread_count);
for (size_t i = 0; i < m_thread_count; i++) {
file.WriteFormat("Threads[%02zu]:\n", i);
m_threads[i].SaveToFile(file);
}
}
void ThreadInfo::SaveToFile(ScopedFile &file) {
file.WriteFormat(" Thread ID: %016lx\n", m_thread_id);
if (std::strcmp(m_name, "") != 0) {
file.WriteFormat(" Thread Name: %s\n", m_name);
}
if (m_stack_top != 0) {
file.WriteFormat(" Stack Region: %016lx-%016lx\n", m_stack_bottom, m_stack_top);
}
file.WriteFormat(" Registers:\n");
{
for (unsigned int i = 0; i <= 28; i++) {
file.WriteFormat(" X[%02u]: %s\n", i, m_module_list->GetFormattedAddressString(m_context.r[i]));
}
file.WriteFormat(" FP: %s\n", m_module_list->GetFormattedAddressString(m_context.fp));
file.WriteFormat(" LR: %s\n", m_module_list->GetFormattedAddressString(m_context.lr));
file.WriteFormat(" SP: %s\n", m_module_list->GetFormattedAddressString(m_context.sp));
file.WriteFormat(" PC: %s\n", m_module_list->GetFormattedAddressString(m_context.pc));
}
if (m_stack_trace_size != 0) {
file.WriteFormat(" Stack Trace:\n");
for (size_t i = 0; i < m_stack_trace_size; i++) {
file.WriteFormat(" ReturnAddress[%02zu]: %s\n", i, m_module_list->GetFormattedAddressString(m_stack_trace[i]));
}
}
if (m_stack_dump_base != 0) {
file.WriteFormat(" Stack Dump: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n");
for (size_t i = 0; i < 0x10; i++) {
const size_t ofs = i * 0x10;
file.WriteFormat(" %012lx %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
m_stack_dump_base + ofs, m_stack_dump[ofs + 0], m_stack_dump[ofs + 1], m_stack_dump[ofs + 2], m_stack_dump[ofs + 3], m_stack_dump[ofs + 4], m_stack_dump[ofs + 5], m_stack_dump[ofs + 6], m_stack_dump[ofs + 7],
m_stack_dump[ofs + 8], m_stack_dump[ofs + 9], m_stack_dump[ofs + 10], m_stack_dump[ofs + 11], m_stack_dump[ofs + 12], m_stack_dump[ofs + 13], m_stack_dump[ofs + 14], m_stack_dump[ofs + 15]);
}
}
if (m_tls_address != 0) {
file.WriteFormat(" TLS Address: %016lx\n", m_tls_address);
file.WriteFormat(" TLS Dump: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n");
for (size_t i = 0; i < 0x10; i++) {
const size_t ofs = i * 0x10;
file.WriteFormat(" %012lx %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
m_tls_address + ofs, m_tls[ofs + 0], m_tls[ofs + 1], m_tls[ofs + 2], m_tls[ofs + 3], m_tls[ofs + 4], m_tls[ofs + 5], m_tls[ofs + 6], m_tls[ofs + 7],
m_tls[ofs + 8], m_tls[ofs + 9], m_tls[ofs + 10], m_tls[ofs + 11], m_tls[ofs + 12], m_tls[ofs + 13], m_tls[ofs + 14], m_tls[ofs + 15]);
}
}
}
bool ThreadInfo::ReadFromProcess(os::NativeHandle debug_handle, ThreadTlsMap &tls_map, u64 thread_id, bool is_64_bit) {
/* Set thread id. */
m_thread_id = thread_id;
/* Verify that the thread is running or waiting. */
{
u64 _;
u32 _thread_state;
if (R_FAILED(svc::GetDebugThreadParam(&_, &_thread_state, debug_handle, m_thread_id, svc::DebugThreadParam_State))) {
return false;
}
const svc::ThreadState thread_state = static_cast<svc::ThreadState>(_thread_state);
if (thread_state != svc::ThreadState_Waiting && thread_state != svc::ThreadState_Running) {
return false;
}
}
/* Get the thread context. */
if (R_FAILED(svc::GetDebugThreadContext(std::addressof(m_context), debug_handle, m_thread_id, svc::ThreadContextFlag_All))) {
return false;
}
/* In aarch32 mode svc::GetDebugThreadContext does not set the LR, FP, and SP registers correctly. */
if (!is_64_bit) {
m_context.fp = m_context.r[11];
m_context.sp = m_context.r[13];
m_context.lr = m_context.r[14];
}
/* Read TLS, if present. */
/* TODO: struct definitions for nnSdk's ThreadType/TLS Layout? */
m_tls_address = 0;
if (tls_map.GetThreadTls(std::addressof(m_tls_address), thread_id)) {
u8 thread_tls[sizeof(svc::ThreadLocalRegion)];
if (R_SUCCEEDED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(thread_tls), debug_handle, m_tls_address, sizeof(thread_tls)))) {
std::memcpy(m_tls, thread_tls, sizeof(m_tls));
/* Try to detect libnx threads, and skip name parsing then. */
if (*(reinterpret_cast<u32 *>(std::addressof(thread_tls[0x1E0]))) != LibnxThreadVarMagic) {
u8 thread_type[0x1C0];
const u64 thread_type_addr = *(reinterpret_cast<u64 *>(std::addressof(thread_tls[0x1F8])));
if (R_SUCCEEDED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(thread_type), debug_handle, thread_type_addr, sizeof(thread_type)))) {
/* Get the thread version. */
const u16 thread_version = *reinterpret_cast<u16 *>(std::addressof(thread_type[0x46]));
if (thread_version == 0 || thread_version == 0xFFFF) {
/* Check thread name is actually at thread name. */
static_assert(0x1A8 - 0x188 == NameLengthMax, "NameLengthMax definition!");
if (*(reinterpret_cast<u64 *>(std::addressof(thread_type[0x1A8]))) == thread_type_addr + 0x188) {
std::memcpy(m_name, thread_type + 0x188, NameLengthMax);
}
} else if (thread_version == 1) {
static_assert(0x1A0 - 0x180 == NameLengthMax, "NameLengthMax definition!");
if (*(reinterpret_cast<u64 *>(std::addressof(thread_type[0x1A0]))) == thread_type_addr + 0x180) {
std::memcpy(m_name, thread_type + 0x180, NameLengthMax);
}
}
}
}
}
}
/* Parse stack extents and dump stack. */
this->TryGetStackInfo(debug_handle);
/* Dump stack trace. */
if (is_64_bit) {
ReadStackTrace<u64>(std::addressof(m_stack_trace_size), m_stack_trace, StackTraceSizeMax, debug_handle, m_context.fp);
} else {
ReadStackTrace<u32>(std::addressof(m_stack_trace_size), m_stack_trace, StackTraceSizeMax, debug_handle, m_context.fp);
}
return true;
}
void ThreadInfo::TryGetStackInfo(os::NativeHandle debug_handle) {
/* Query stack region. */
svc::MemoryInfo mi;
svc::PageInfo pi;
if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), debug_handle, m_context.sp))) {
return;
}
/* Check if sp points into the stack. */
if (mi.state != svc::MemoryState_Stack) {
/* It's possible that sp is below the stack... */
if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), debug_handle, mi.base_address + mi.size)) || mi.state != svc::MemoryState_Stack) {
return;
}
}
/* Save stack extents. */
m_stack_bottom = mi.base_address;
m_stack_top = mi.base_address + mi.size;
/* We always want to dump 0x100 of stack, starting from the lowest 0x10-byte aligned address below the stack pointer. */
/* Note: if the stack pointer is below the stack bottom, we will start dumping from the stack bottom. */
m_stack_dump_base = std::min(std::max(m_context.sp & ~0xFul, m_stack_bottom), m_stack_top - sizeof(m_stack_dump));
/* Try to read stack. */
if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(m_stack_dump), debug_handle, m_stack_dump_base, sizeof(m_stack_dump)))) {
m_stack_dump_base = 0;
}
}
void ThreadInfo::DumpBinary(ScopedFile &file) {
/* Dump id and context. */
file.Write(std::addressof(m_thread_id), sizeof(m_thread_id));
file.Write(std::addressof(m_context), sizeof(m_context));
/* Dump TLS info and name. */
file.Write(std::addressof(m_tls_address), sizeof(m_tls_address));
file.Write(std::addressof(m_tls), sizeof(m_tls));
file.Write(std::addressof(m_name), sizeof(m_name));
/* Dump stack extents and stack dump. */
file.Write(std::addressof(m_stack_bottom), sizeof(m_stack_bottom));
file.Write(std::addressof(m_stack_top), sizeof(m_stack_top));
file.Write(std::addressof(m_stack_dump_base), sizeof(m_stack_dump_base));
file.Write(std::addressof(m_stack_dump), sizeof(m_stack_dump));
/* Dump stack trace. */
{
const u64 sts = m_stack_trace_size;
file.Write(std::addressof(sts), sizeof(sts));
}
file.Write(m_stack_trace, m_stack_trace_size);
}
void ThreadList::DumpBinary(ScopedFile &file, u64 crashed_thread_id) {
const u32 magic = DumpedThreadInfoMagic;
const u32 count = m_thread_count;
file.Write(std::addressof(magic), sizeof(magic));
file.Write(std::addressof(count), sizeof(count));
file.Write(std::addressof(crashed_thread_id), sizeof(crashed_thread_id));
for (size_t i = 0; i < m_thread_count; i++) {
m_threads[i].DumpBinary(file);
}
}
void ThreadList::ReadFromProcess(os::NativeHandle debug_handle, ThreadTlsMap &tls_map, bool is_64_bit) {
m_thread_count = 0;
/* Get thread list. */
s32 num_threads;
u64 thread_ids[ThreadCountMax];
{
if (R_FAILED(svc::GetThreadList(std::addressof(num_threads), thread_ids, ThreadCountMax, debug_handle))) {
return;
}
num_threads = std::min(size_t(num_threads), ThreadCountMax);
}
/* Parse thread infos. */
for (s32 i = 0; i < num_threads; i++) {
if (m_threads[m_thread_count].ReadFromProcess(debug_handle, tls_map, thread_ids[i], is_64_bit)) {
m_thread_count++;
}
}
}
}

View File

@@ -1,143 +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 "creport_scoped_file.hpp"
namespace ams::creport {
/* Forward declare ModuleList class. */
class ModuleList;
static constexpr size_t ThreadCountMax = 0x60;
template<size_t MaxThreadCount>
class ThreadTlsMapImpl {
private:
std::pair<u64, u64> m_map[MaxThreadCount];
size_t m_index;
public:
constexpr ThreadTlsMapImpl() : m_map(), m_index(0) { /* ... */ }
constexpr void ResetThreadTlsMap() {
m_index = 0;
}
constexpr void SetThreadTls(u64 thread_id, u64 tls) {
if (m_index < util::size(m_map)) {
m_map[m_index++] = std::make_pair(thread_id, tls);
}
}
constexpr bool GetThreadTls(u64 *out, u64 thread_id) const {
for (size_t i = 0; i < m_index; ++i) {
if (m_map[i].first == thread_id) {
*out = m_map[i].second;
return true;
}
}
return false;
}
};
using ThreadTlsMap = ThreadTlsMapImpl<ThreadCountMax>;
class ThreadInfo {
private:
static constexpr size_t StackTraceSizeMax = 0x20;
static constexpr size_t NameLengthMax = 0x20;
private:
svc::ThreadContext m_context = {};
u64 m_thread_id = 0;
u64 m_stack_top = 0;
u64 m_stack_bottom = 0;
u64 m_stack_trace[StackTraceSizeMax] = {};
size_t m_stack_trace_size = 0;
u64 m_tls_address = 0;
u8 m_tls[0x100] = {};
u64 m_stack_dump_base = 0;
u8 m_stack_dump[0x100] = {};
char m_name[NameLengthMax + 1] = {};
ModuleList *m_module_list = nullptr;
public:
u64 GetGeneralPurposeRegister(size_t i) const {
return m_context.r[i];
}
u64 GetPC() const {
return m_context.pc;
}
u64 GetLR() const {
return m_context.lr;
}
u64 GetFP() const {
return m_context.fp;
}
u64 GetSP() const {
return m_context.sp;
}
u64 GetThreadId() const {
return m_thread_id;
}
size_t GetStackTraceSize() const {
return m_stack_trace_size;
}
u64 GetStackTrace(size_t i) const {
return m_stack_trace[i];
}
void SetModuleList(ModuleList *ml) {
m_module_list = ml;
}
bool ReadFromProcess(os::NativeHandle debug_handle, ThreadTlsMap &tls_map, u64 thread_id, bool is_64_bit);
void SaveToFile(ScopedFile &file);
void DumpBinary(ScopedFile &file);
private:
void TryGetStackInfo(os::NativeHandle debug_handle);
};
class ThreadList {
private:
size_t m_thread_count = 0;
ThreadInfo m_threads[ThreadCountMax];
public:
size_t GetThreadCount() const {
return m_thread_count;
}
const ThreadInfo &GetThreadInfo(size_t i) const {
return m_threads[i];
}
void SetModuleList(ModuleList *ml) {
for (size_t i = 0; i < m_thread_count; i++) {
m_threads[i].SetModuleList(ml);
}
}
void ReadFromProcess(os::NativeHandle debug_handle, ThreadTlsMap &tls_map, bool is_64_bit);
void SaveToFile(ScopedFile &file);
void DumpBinary(ScopedFile &file, u64 crashed_thread_id);
};
}

View File

@@ -1,37 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "creport_utils.hpp"
namespace ams::creport {
os::ProcessId ParseProcessIdArgument(const char *s) {
/* Official creport uses this custom parsing logic... */
u64 out_val = 0;
for (unsigned int i = 0; i < 20 && s[i]; i++) {
if ('0' <= s[i] && s[i] <= '9') {
out_val *= 10;
out_val += (s[i] - '0');
} else {
break;
}
}
return os::ProcessId{out_val};
}
}

View File

@@ -1,24 +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::creport {
/* Utility functions. */
os::ProcessId ParseProcessIdArgument(const char *s);
}