Revert "hoc-clk: add live vdd2, live boost clock and basic pwm dimming"
This reverts commit 15b7df8ef1.
This commit is contained in:
@@ -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(×tamp)) {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user