diff --git a/stratosphere/creport/source/creport_code_info.cpp b/stratosphere/creport/source/creport_code_info.cpp deleted file mode 100644 index 70d9d21af..000000000 --- a/stratosphere/creport/source/creport_code_info.cpp +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (c) 2018-2019 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 . - */ - -#include -#include - -#include "creport_code_info.hpp" -#include "creport_crash_report.hpp" - -void CodeList::SaveToFile(FILE *f_report) { - fprintf(f_report, " Number of Code Regions: %u\n", this->code_count); - for (unsigned int i = 0; i < this->code_count; i++) { - fprintf(f_report, " Code Region %02u:\n", i); - fprintf(f_report, " Address: %016lx-%016lx\n", this->code_infos[i].start_address, this->code_infos[i].end_address); - if (this->code_infos[i].name[0]) { - fprintf(f_report, " Name: %s\n", this->code_infos[i].name); - } - CrashReport::Memdump(f_report, " Build Id: ", this->code_infos[i].build_id, sizeof(this->code_infos[i].build_id)); - } -} - -void CodeList::ReadCodeRegionsFromThreadInfo(Handle debug_handle, const ThreadInfo *thread) { - u64 code_base; - - /* Try to add the thread's PC. */ - if (TryFindCodeRegion(debug_handle, thread->GetPC(), &code_base)) { - AddCodeRegion(debug_handle, code_base); - } - - /* Try to add the thread's LR. */ - if (TryFindCodeRegion(debug_handle, thread->GetLR(), &code_base)) { - AddCodeRegion(debug_handle, code_base); - } - - /* Try to add all the addresses in the thread's stacktrace. */ - for (u32 i = 0; i < thread->GetStackTraceSize(); i++) { - if (TryFindCodeRegion(debug_handle, thread->GetStackTrace(i), &code_base)) { - AddCodeRegion(debug_handle, code_base); - } - } -} - -void CodeList::AddCodeRegion(u64 debug_handle, u64 code_address) { - /* Check whether we already have this code region. */ - for (size_t i = 0; i < this->code_count; i++) { - if (this->code_infos[i].start_address <= code_address && code_address < this->code_infos[i].end_address) { - return; - } - } - - /* Add all contiguous code regions. */ - u64 cur_ptr = code_address; - while (this->code_count < max_code_count) { - MemoryInfo mi; - u32 pi; - if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, cur_ptr))) { - break; - } - - if (mi.perm == Perm_Rx) { - /* Parse CodeInfo. */ - this->code_infos[this->code_count].start_address = mi.addr; - this->code_infos[this->code_count].end_address = mi.addr + mi.size; - GetCodeInfoName(debug_handle, mi.addr, mi.addr + mi.size, this->code_infos[this->code_count].name); - GetCodeInfoBuildId(debug_handle, mi.addr + mi.size, this->code_infos[this->code_count].build_id); - if (this->code_infos[this->code_count].name[0] == '\x00') { - snprintf(this->code_infos[this->code_count].name, 0x1F, "[%02x%02x%02x%02x]", this->code_infos[this->code_count].build_id[0], - this->code_infos[this->code_count].build_id[1], - this->code_infos[this->code_count].build_id[2], - this->code_infos[this->code_count].build_id[3]); - } - this->code_count++; - } - - /* If we're out of readable memory, we're done reading code. */ - if (mi.type == MemType_Unmapped || mi.type == MemType_Reserved) { - break; - } - - /* Verify we're not getting stuck in an infinite loop. */ - if (mi.size == 0 || U64_MAX - mi.size <= cur_ptr) { - break; - } - - cur_ptr += mi.size; - } -} - -bool CodeList::TryFindCodeRegion(Handle debug_handle, u64 guess, u64 *address) { - MemoryInfo mi; - u32 pi; - if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, guess))) { - return false; - } - - if (mi.perm == Perm_Rw) { - guess = mi.addr - 4; - if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, guess))) { - return false; - } - } - - if (mi.perm == Perm_R) { - guess = mi.addr - 4; - if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, guess))) { - return false; - } - } - - if (mi.perm != Perm_Rx) { - return false; - } - - /* Iterate backwards until we find the memory before the code region. */ - while (mi.addr > 0) { - if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, guess))) { - return false; - } - - if (mi.type == MemType_Unmapped) { - /* Code region will be at the end of the unmapped region preceding it. */ - *address = mi.addr + mi.size; - return true; - } - - guess = mi.addr - 4; - } - return false; -} - -void CodeList::GetCodeInfoName(u64 debug_handle, u64 rx_address, u64 rodata_addr, char *name) { - char name_in_proc[0x200]; - - /* Clear name. */ - memset(name, 0, 0x20); - - /* Check whether this NSO *has* a name... */ - { - u64 rodata_start[0x20/sizeof(u64)]; - MemoryInfo mi; - u32 pi; - u64 rw_address; - - /* Verify .rodata is read-only. */ - if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, rodata_addr)) || mi.perm != Perm_R) { - return; - } - - /* rwdata is after rodata. */ - rw_address = mi.addr + mi.size; - - /* Read start of .rodata. */ - if (R_FAILED(svcReadDebugProcessMemory(rodata_start, debug_handle, rodata_addr, sizeof(rodata_start)))) { - return; - } - - /* Check if name section is present. */ - if (rodata_start[0] == (rw_address - rx_address)) { - return; - } - } - - /* Read name out of .rodata. */ - if (R_FAILED(svcReadDebugProcessMemory(name_in_proc, debug_handle, rodata_addr + 8, sizeof(name_in_proc)))) { - return; - } - - /* Start after last slash in path. */ - int ofs = strnlen(name_in_proc, sizeof(name_in_proc)); - while (ofs >= 0 && name_in_proc[ofs] != '/' && name_in_proc[ofs] != '\\') { - ofs--; - } - - strncpy(name, name_in_proc + ofs + 1, 0x20); - name[0x1F] = '\x00'; -} - -void CodeList::GetCodeInfoBuildId(u64 debug_handle, u64 rodata_addr, u8 *build_id) { - MemoryInfo mi; - u32 pi; - - /* Clear build id. */ - memset(build_id, 0, 0x20); - - /* Verify .rodata is read-only. */ - if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, rodata_addr)) || mi.perm != Perm_R) { - return; - } - - /* We want to read the last two pages of .rodata. */ - u8 last_pages[0x2000]; - size_t last_pages_size = mi.size >= 0x2000 ? 0x2000 : 0x1000; - if (R_FAILED(svcReadDebugProcessMemory(last_pages, debug_handle, mi.addr + mi.size - last_pages_size, last_pages_size))) { - return; - } - - /* Find GNU\x00 to locate start of Build ID. */ - for (int ofs = last_pages_size - 0x24; ofs >= 0; ofs--) { - if (memcmp(last_pages + ofs, "GNU\x00", 4) == 0) { - memcpy(build_id, last_pages + ofs + 4, 0x20); - } - } -} - - -const char *CodeList::GetFormattedAddressString(u64 address) { - memset(this->address_str_buf, 0, sizeof(this->address_str_buf)); - for (unsigned int i = 0; i < this->code_count; i++) { - if (this->code_infos[i].start_address <= address && address < this->code_infos[i].end_address) { - snprintf(this->address_str_buf, sizeof(this->address_str_buf) - 1, "%016lx (%s + 0x%lx)", address, this->code_infos[i].name, address - this->code_infos[i].start_address); - return this->address_str_buf; - } - } - snprintf(this->address_str_buf, sizeof(this->address_str_buf) - 1, "%016lx", address); - return this->address_str_buf; -} \ No newline at end of file diff --git a/stratosphere/creport/source/creport_code_info.hpp b/stratosphere/creport/source/creport_code_info.hpp deleted file mode 100644 index bd62a2c13..000000000 --- a/stratosphere/creport/source/creport_code_info.hpp +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2018-2019 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 . - */ - -#pragma once -#include -#include - -#include "creport_debug_types.hpp" -#include "creport_thread_info.hpp" - -struct CodeInfo { - char name[0x20]; - u8 build_id[0x20]; - u64 start_address; - u64 end_address; -}; - -class CodeList { - public: - static const size_t max_code_count = 0x60; - u32 code_count = 0; - CodeInfo code_infos[max_code_count]; - - /* For pretty-printing. */ - char address_str_buf[0x280]; - public: - void ReadCodeRegionsFromThreadInfo(Handle debug_handle, const ThreadInfo *thread); - const char *GetFormattedAddressString(u64 address); - void SaveToFile(FILE *f_report); - private: - bool TryFindCodeRegion(Handle debug_handle, u64 guess, u64 *address); - void AddCodeRegion(u64 debug_handle, u64 code_address); - void GetCodeInfoName(u64 debug_handle, u64 rx_address, u64 ro_address, char *name); - void GetCodeInfoBuildId(u64 debug_handle, u64 ro_address, u8 *build_id); -}; diff --git a/stratosphere/creport/source/creport_crash_report.cpp b/stratosphere/creport/source/creport_crash_report.cpp index df0031276..4e3ddd737 100644 --- a/stratosphere/creport/source/creport_crash_report.cpp +++ b/stratosphere/creport/source/creport_crash_report.cpp @@ -14,104 +14,195 @@ * along with this program. If not, see . */ -#include -#include + #include #include -#include #include "creport_crash_report.hpp" -#include "creport_debug_types.hpp" +#include "creport_utils.hpp" -void CrashReport::BuildReport(u64 pid, bool has_extra_info) { - this->has_extra_info = has_extra_info; - if (OpenProcess(pid)) { - ProcessExceptions(); - this->code_list.ReadCodeRegionsFromThreadInfo(this->debug_handle, &this->crashed_thread_info); - this->thread_list.ReadThreadsFromProcess(this->thread_tls_map, this->debug_handle, Is64Bit()); - this->crashed_thread_info.SetCodeList(&this->code_list); - this->thread_list.SetCodeList(&this->code_list); +namespace sts::creport { - if (IsApplication()) { - ProcessDyingMessage(); + namespace { + + /* Convenience definitions. */ + constexpr size_t DyingMessageAddressOffset = 0x1C0; + + /* Helper functions. */ + bool IsAddressReadable(Handle debug_handle, u64 address, size_t size) { + MemoryInfo mi; + u32 pi; + if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, address))) { + return false; + } + + /* Must be read or read-write */ + if ((mi.perm | Perm_W) != Perm_Rw) { + return false; + } + + /* Must have space for both userdata address and userdata size. */ + if (address < mi.addr || mi.addr + mi.size < address + size) { + return false; + } + + return true; } - /* Real creport only does this if application, but there's no reason not to do it all the time. */ - for (u32 i = 0; i < this->thread_list.GetThreadCount(); i++) { - this->code_list.ReadCodeRegionsFromThreadInfo(this->debug_handle, this->thread_list.GetThreadInfo(i)); + 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. */ + { + auto time_holder = sm::ScopedServiceHolder(); + return R_SUCCEEDED(time_holder.GetResult()) && R_SUCCEEDED(timeGetCurrentTime(TimeType_LocalSystemClock, out)); + } } - /* Real creport builds the report here. We do it later. */ + void EnsureReportDirectories() { + mkdir("sdmc:/atmosphere", S_IRWXU); + mkdir("sdmc:/atmosphere/crash_reports", S_IRWXU); + mkdir("sdmc:/atmosphere/crash_reports/dumps", S_IRWXU); + mkdir("sdmc:/atmosphere/fatal_reports", S_IRWXU); + mkdir("sdmc:/atmosphere/fatal_reports/dumps", S_IRWXU); + } - Close(); - } -} + constexpr const char *GetDebugExceptionTypeString(const svc::DebugExceptionType type) { + switch (type) { + case svc::DebugExceptionType::UndefinedInstruction: + return "Undefined Instruction"; + case svc::DebugExceptionType::InstructionAbort: + return "Instruction Abort"; + case svc::DebugExceptionType::DataAbort: + return "Data Abort"; + case svc::DebugExceptionType::AlignmentFault: + return "Alignment Fault"; + case svc::DebugExceptionType::DebuggerAttached: + return "Debugger Attached"; + case svc::DebugExceptionType::BreakPoint: + return "Break Point"; + case svc::DebugExceptionType::UserBreak: + return "User Break"; + case svc::DebugExceptionType::DebuggerBreak: + return "Debugger Break"; + case svc::DebugExceptionType::UndefinedSystemCall: + return "Undefined System Call"; + case svc::DebugExceptionType::SystemMemoryError: + return "System Memory Error"; + default: + return "Unknown"; + } + } -FatalContext *CrashReport::GetFatalContext() { - FatalContext *ctx = new FatalContext; - *ctx = (FatalContext){0}; - - ctx->is_aarch32 = false; - ctx->type = static_cast(this->exception_info.type); - - for (size_t i = 0; i < 29; i++) { - ctx->aarch64_ctx.x[i] = this->crashed_thread_info.context.cpu_gprs[i].x; - } - ctx->aarch64_ctx.fp = this->crashed_thread_info.context.fp; - ctx->aarch64_ctx.lr = this->crashed_thread_info.context.lr; - ctx->aarch64_ctx.pc = this->crashed_thread_info.context.pc.x; - - ctx->aarch64_ctx.stack_trace_size = this->crashed_thread_info.stack_trace_size; - for (size_t i = 0; i < ctx->aarch64_ctx.stack_trace_size; i++) { - ctx->aarch64_ctx.stack_trace[i] = this->crashed_thread_info.stack_trace[i]; } - if (this->code_list.code_count) { - ctx->aarch64_ctx.start_address = this->code_list.code_infos[0].start_address; - } + void CrashReport::BuildReport(u64 process_id, bool has_extra_info) { + this->has_extra_info = has_extra_info; - /* For ams fatal... */ - ctx->aarch64_ctx.afsr0 = this->process_info.title_id; + if (this->OpenProcess(process_id)) { + ON_SCOPE_EXIT { this->Close(); }; - return ctx; -} + /* Parse info from the crashed process. */ + this->ProcessExceptions(); + this->module_list.FindModulesFromThreadInfo(this->debug_handle, this->crashed_thread); + this->thread_list.ReadFromProcess(this->debug_handle, this->thread_tls_map, this->Is64Bit()); -void CrashReport::ProcessExceptions() { - if (!IsOpen()) { - return; - } + /* Associate module list to threads. */ + this->crashed_thread.SetModuleList(&this->module_list); + this->thread_list.SetModuleList(&this->module_list); - DebugEventInfo d; - while (R_SUCCEEDED(svcGetDebugEvent((u8 *)&d, this->debug_handle))) { - switch (d.type) { - case DebugEventType::AttachProcess: - HandleAttachProcess(d); - break; - case DebugEventType::Exception: - HandleException(d); - break; - case DebugEventType::AttachThread: - HandleAttachThread(d); - case DebugEventType::ExitProcess: - case DebugEventType::ExitThread: - default: - break; + /* 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 < this->thread_list.GetThreadCount(); i++) { + this->module_list.FindModulesFromThreadInfo(this->debug_handle, this->thread_list.GetThreadInfo(i)); + } + + /* Nintendo's creport builds the report here, but we'll do it later. */ } } - /* Parse crashing thread info. */ - this->crashed_thread_info.ReadFromProcess(this->thread_tls_map, this->debug_handle, this->crashed_thread_id, Is64Bit()); -} + void CrashReport::GetFatalContext(FatalContext *out) const { + std::memset(out, 0, sizeof(*out)); + + out->is_aarch32 = false; + out->type = static_cast(this->exception_info.type); + + for (size_t i = 0; i < 29; i++) { + out->aarch64_ctx.x[i] = this->crashed_thread.GetGeneralPurposeRegister(i); + } + out->aarch64_ctx.fp = this->crashed_thread.GetFP(); + out->aarch64_ctx.lr = this->crashed_thread.GetLR(); + out->aarch64_ctx.pc = this->crashed_thread.GetPC(); + + out->aarch64_ctx.stack_trace_size = this->crashed_thread.GetStackTraceSize(); + for (size_t i = 0; i < out->aarch64_ctx.stack_trace_size; i++) { + out->aarch64_ctx.stack_trace[i] = this->crashed_thread.GetStackTrace(i); + } + + if (this->module_list.GetModuleCount()) { + out->aarch64_ctx.start_address = this->module_list.GetModuleStartAddress(0); + } + + /* For ams fatal, which doesn't use afsr0, pass title_id instead. */ + out->aarch64_ctx.afsr0 = this->process_info.title_id; + } + + void CrashReport::ProcessExceptions() { + if (!this->IsOpen()) { + return; + } + + /* Loop all debug events. */ + svc::DebugEventInfo d; + while (R_SUCCEEDED(svcGetDebugEvent(reinterpret_cast(&d), this->debug_handle))) { + switch (d.type) { + case svc::DebugEventType::AttachProcess: + this->HandleDebugEventInfoAttachProcess(d); + break; + case svc::DebugEventType::AttachThread: + this->HandleDebugEventInfoAttachThread(d); + break; + case svc::DebugEventType::Exception: + this->HandleDebugEventInfoException(d); + break; + case svc::DebugEventType::ExitProcess: + case svc::DebugEventType::ExitThread: + break; + } + } + + /* Parse crashed thread info. */ + this->crashed_thread.ReadFromProcess(this->debug_handle, this->thread_tls_map, this->crashed_thread_id, this->Is64Bit()); + } + + void CrashReport::HandleDebugEventInfoAttachProcess(const svc::DebugEventInfo &d) { + this->process_info = d.info.attach_process; + + /* On 5.0.0+, we want to parse out a dying message from application crashes. */ + if (GetRuntimeFirmwareVersion() < FirmwareVersion_500 || !IsApplication()) { + return; + } -void CrashReport::HandleAttachProcess(DebugEventInfo &d) { - this->process_info = d.info.attach_process; - if ((GetRuntimeFirmwareVersion() >= FirmwareVersion_500) && IsApplication()) { /* Parse out user data. */ - u64 address = this->process_info.user_exception_context_address; + const u64 address = this->process_info.user_exception_context_address + DyingMessageAddressOffset; u64 userdata_address = 0; u64 userdata_size = 0; - if (!IsAddressReadable(address, sizeof(userdata_address) + sizeof(userdata_size))) { + if (!IsAddressReadable(this->debug_handle, address, sizeof(userdata_address) + sizeof(userdata_size))) { return; } @@ -131,274 +222,186 @@ void CrashReport::HandleAttachProcess(DebugEventInfo &d) { } /* Cap userdata size. */ - if (userdata_size > sizeof(this->dying_message)) { - userdata_size = sizeof(this->dying_message); - } + userdata_size = std::min(size_t(userdata_size), sizeof(this->dying_message)); - /* Assign. */ this->dying_message_address = userdata_address; this->dying_message_size = userdata_size; } -} -void CrashReport::HandleException(DebugEventInfo &d) { - switch (d.info.exception.type) { - case DebugExceptionType::UndefinedInstruction: - this->result = ResultCreportUndefinedInstruction; - break; - case DebugExceptionType::InstructionAbort: - this->result = ResultCreportInstructionAbort; - d.info.exception.specific.raw = 0; - break; - case DebugExceptionType::DataAbort: - this->result = ResultCreportDataAbort; - break; - case DebugExceptionType::AlignmentFault: - this->result = ResultCreportAlignmentFault; - break; - case DebugExceptionType::UserBreak: - this->result = ResultCreportUserBreak; - /* Try to parse out the user break result. */ - if ((GetRuntimeFirmwareVersion() >= FirmwareVersion_500)) { - Result user_result = 0; - if (IsAddressReadable(d.info.exception.specific.user_break.address, sizeof(user_result))) { - svcReadDebugProcessMemory(&user_result, this->debug_handle, d.info.exception.specific.user_break.address, sizeof(user_result)); + void CrashReport::HandleDebugEventInfoAttachThread(const svc::DebugEventInfo &d) { + /* Save info on the thread's TLS address for later. */ + this->thread_tls_map[d.info.attach_thread.thread_id] = d.info.attach_thread.tls_address; + } + + void CrashReport::HandleDebugEventInfoException(const svc::DebugEventInfo &d) { + switch (d.info.exception.type) { + case svc::DebugExceptionType::UndefinedInstruction: + this->result = ResultCreportUndefinedInstruction; + break; + case svc::DebugExceptionType::InstructionAbort: + this->result = ResultCreportInstructionAbort; + break; + case svc::DebugExceptionType::DataAbort: + this->result = ResultCreportDataAbort; + break; + case svc::DebugExceptionType::AlignmentFault: + this->result = ResultCreportAlignmentFault; + break; + case svc::DebugExceptionType::UserBreak: + this->result = ResultCreportUserBreak; + /* Try to parse out the user break result. */ + if (GetRuntimeFirmwareVersion() >= FirmwareVersion_500) { + Result user_result = ResultSuccess; + if (IsAddressReadable(this->debug_handle, d.info.exception.specific.user_break.address, sizeof(user_result))) { + svcReadDebugProcessMemory(&user_result, this->debug_handle, d.info.exception.specific.user_break.address, sizeof(user_result)); + } + /* Only copy over the user result if it gives us information (as by default nnSdk uses the success code, which is confusing). */ + if (R_FAILED(user_result)) { + this->result = user_result; + } } - /* Only copy over the user result if it gives us information (as by default nnSdk uses the success code, which is confusing). */ - if (R_FAILED(user_result)) { - this->result = user_result; - } - } - break; - case DebugExceptionType::BadSvc: - this->result = ResultCreportBadSvc; - break; - case DebugExceptionType::SystemMemoryError: - this->result = ResultCreportSystemMemoryError; - d.info.exception.specific.raw = 0; - break; - case DebugExceptionType::DebuggerAttached: - case DebugExceptionType::BreakPoint: - case DebugExceptionType::DebuggerBreak: - default: + break; + case svc::DebugExceptionType::UndefinedSystemCall: + this->result = ResultCreportUndefinedSystemCall; + break; + case svc::DebugExceptionType::SystemMemoryError: + this->result = ResultCreportSystemMemoryError; + break; + case svc::DebugExceptionType::DebuggerAttached: + case svc::DebugExceptionType::BreakPoint: + case svc::DebugExceptionType::DebuggerBreak: + return; + } + + /* Save exception info. */ + this->exception_info = d.info.exception; + this->crashed_thread_id = d.thread_id; + } + + void CrashReport::ProcessDyingMessage() { + /* Dying message is only stored starting in 5.0.0. */ + if (GetRuntimeFirmwareVersion() < FirmwareVersion_500) { return; - } - this->exception_info = d.info.exception; - this->crashed_thread_id = d.thread_id; -} + } -void CrashReport::HandleAttachThread(DebugEventInfo &d) { - this->thread_tls_map[d.info.attach_thread.thread_id] = d.info.attach_thread.tls_address; -} + /* Validate address/size. */ + if (this->dying_message_address == 0 || this->dying_message_address & 0xFFF) { + return; + } + if (this->dying_message_size > sizeof(this->dying_message)) { + return; + } -void CrashReport::ProcessDyingMessage() { - /* Dying message is only stored starting in 5.0.0. */ - if ((GetRuntimeFirmwareVersion() < FirmwareVersion_500)) { - return; + /* Validate that the current report isn't garbage. */ + if (!IsOpen() || !IsComplete()) { + return; + } + + /* Validate that we can read the dying message. */ + if (!IsAddressReadable(this->debug_handle, this->dying_message_address, this->dying_message_size)) { + return; + } + + /* Read the dying message. */ + svcReadDebugProcessMemory(this->dying_message, this->debug_handle, this->dying_message_address, this->dying_message_size); } - /* Validate the message address/size. */ - if (this->dying_message_address == 0 || this->dying_message_address & 0xFFF) { - return; - } - if (this->dying_message_size > sizeof(this->dying_message)) { - return; - } + void CrashReport::SaveReport() { + /* Ensure path exists. */ + EnsureReportDirectories(); - /* Validate that the report isn't garbage. */ - if (!IsOpen() || !WasSuccessful()) { - return; - } + /* Get a timestamp. */ + u64 timestamp; + if (!TryGetCurrentTimestamp(×tamp)) { + timestamp = svcGetSystemTick(); + } - if (!IsAddressReadable(this->dying_message_address, this->dying_message_size)) { - return; - } + /* Save files. */ + { + char file_path[FS_MAX_PATH]; - svcReadDebugProcessMemory(this->dying_message, this->debug_handle, this->dying_message_address, this->dying_message_size); -} - -bool CrashReport::IsAddressReadable(u64 address, u64 size, MemoryInfo *o_mi) { - MemoryInfo mi; - u32 pi; - - if (o_mi == NULL) { - o_mi = &mi; - } - - if (R_FAILED(svcQueryDebugProcessMemory(o_mi, &pi, this->debug_handle, address))) { - return false; - } - - /* Must be read or read-write */ - if ((o_mi->perm | Perm_W) != Perm_Rw) { - return false; - } - - /* Must have space for both userdata address and userdata size. */ - if (address < o_mi->addr || o_mi->addr + o_mi->size < address + size) { - return false; - } - - return true; -} - -bool CrashReport::GetCurrentTime(u64 *out) { - *out = 0; - - /* Verify that pcv isn't dead. */ - { - bool has_time_service; - DoWithSmSession([&]() { - Handle dummy; - if (R_SUCCEEDED(smRegisterService(&dummy, "time:s", false, 0x20))) { - svcCloseHandle(dummy); - has_time_service = false; - } else { - has_time_service = true; + /* Save crash report. */ + std::snprintf(file_path, sizeof(file_path), "sdmc:/atmosphere/crash_reports/%011lu_%016lx.log", timestamp, this->process_info.title_id); + FILE *fp = fopen(file_path, "w"); + if (fp != nullptr) { + this->SaveToFile(fp); + fclose(fp); + fp = nullptr; + } + + /* Dump threads. */ + std::snprintf(file_path, sizeof(file_path), "sdmc:/atmosphere/crash_reports/dumps/%011lu_%016lx_thread_info.bin", timestamp, this->process_info.title_id); + fp = fopen(file_path, "wb"); + if (fp != nullptr) { + this->thread_list.DumpBinary(fp, this->crashed_thread.GetThreadId()); + fclose(fp); + fp = nullptr; } - }); - if (!has_time_service) { - return false; } } - /* Try to get the current time. */ - bool success = true; - DoWithSmSession([&]() { - success &= R_SUCCEEDED(timeInitialize()); - }); - if (success) { - success &= R_SUCCEEDED(timeGetCurrentTime(TimeType_LocalSystemClock, out)); - timeExit(); - } - return success; -} + void CrashReport::SaveToFile(FILE *f_report) { + fprintf(f_report, "Atmosphère Crash Report (v1.4):\n"); + fprintf(f_report, "Result: 0x%X (2%03d-%04d)\n\n", this->result, R_MODULE(this->result), R_DESCRIPTION(this->result)); -void CrashReport::EnsureReportDirectories() { - char path[FS_MAX_PATH]; - strcpy(path, "sdmc:/atmosphere"); - mkdir(path, S_IRWXU); - strcat(path, "/crash_reports"); - mkdir(path, S_IRWXU); - strcat(path, "/dumps"); - mkdir(path, S_IRWXU); -} + /* Process Info. */ + char name_buf[0x10] = {}; + static_assert(sizeof(name_buf) >= sizeof(this->process_info.name), "buffer overflow!"); + std::memcpy(name_buf, this->process_info.name, sizeof(this->process_info.name)); + fprintf(f_report, "Process Info:\n"); + fprintf(f_report, " Process Name: %s\n", name_buf); + fprintf(f_report, " Title ID: %016lx\n", this->process_info.title_id); + fprintf(f_report, " Process ID: %016lx\n", this->process_info.process_id); + fprintf(f_report, " Process Flags: %08x\n", this->process_info.flags); + if (GetRuntimeFirmwareVersion() >= FirmwareVersion_500) { + fprintf(f_report, " User Exception Address: %s\n", this->module_list.GetFormattedAddressString(this->process_info.user_exception_context_address)); + } -void CrashReport::SaveReport() { - /* Save the report to the SD card. */ - char report_path[FS_MAX_PATH]; + /* Exception Info. */ + fprintf(f_report, "Exception Info:\n"); + fprintf(f_report, " Type: %s\n", GetDebugExceptionTypeString(this->exception_info.type)); + fprintf(f_report, " Address: %s\n", this->module_list.GetFormattedAddressString(this->exception_info.address)); + switch (this->exception_info.type) { + case svc::DebugExceptionType::UndefinedInstruction: + fprintf(f_report, " Opcode: %08x\n", this->exception_info.specific.undefined_instruction.insn); + break; + case svc::DebugExceptionType::DataAbort: + case svc::DebugExceptionType::AlignmentFault: + if (this->exception_info.specific.raw != this->exception_info.address) { + fprintf(f_report, " Fault Address: %s\n", this->module_list.GetFormattedAddressString(this->exception_info.specific.raw)); + } + break; + case svc::DebugExceptionType::UndefinedSystemCall: + fprintf(f_report, " Svc Id: 0x%02x\n", this->exception_info.specific.undefined_system_call.id); + break; + case svc::DebugExceptionType::UserBreak: + fprintf(f_report, " Break Reason: 0x%x\n", this->exception_info.specific.user_break.break_reason); + fprintf(f_report, " Break Address: %s\n", this->module_list.GetFormattedAddressString(this->exception_info.specific.user_break.address)); + fprintf(f_report, " Break Size: 0x%lx\n", this->exception_info.specific.user_break.size); + break; + default: + break; + } - /* Ensure path exists. */ - EnsureReportDirectories(); + /* Crashed Thread Info. */ + fprintf(f_report, "Crashed Thread Info:\n"); + this->crashed_thread.SaveToFile(f_report); - /* Get a timestamp. */ - u64 timestamp; - if (!GetCurrentTime(×tamp)) { - timestamp = svcGetSystemTick(); - } - - /* Open report file. */ - snprintf(report_path, sizeof(report_path) - 1, "sdmc:/atmosphere/crash_reports/%011lu_%016lx.log", timestamp, process_info.title_id); - FILE *f_report = fopen(report_path, "w"); - if (f_report == NULL) { - return; - } - this->SaveToFile(f_report); - fclose(f_report); - - /* Dump threads. */ - snprintf(report_path, sizeof(report_path) - 1, "sdmc:/atmosphere/crash_reports/dumps/%011lu_%016lx_thread_info.bin", timestamp, process_info.title_id); - f_report = fopen(report_path, "wb"); - this->thread_list.DumpBinary(f_report, this->crashed_thread_info.GetId()); - fclose(f_report); -} - -void CrashReport::SaveToFile(FILE *f_report) { - char buf[0x10] = {0}; - fprintf(f_report, "Atmosphère Crash Report (v1.3):\n"); - fprintf(f_report, "Result: 0x%X (2%03d-%04d)\n\n", this->result, R_MODULE(this->result), R_DESCRIPTION(this->result)); - - /* Process Info. */ - memcpy(buf, this->process_info.name, sizeof(this->process_info.name)); - fprintf(f_report, "Process Info:\n"); - fprintf(f_report, " Process Name: %s\n", buf); - fprintf(f_report, " Title ID: %016lx\n", this->process_info.title_id); - fprintf(f_report, " Process ID: %016lx\n", this->process_info.process_id); - fprintf(f_report, " Process Flags: %08x\n", this->process_info.flags); - if ((GetRuntimeFirmwareVersion() >= FirmwareVersion_500)) { - fprintf(f_report, " User Exception Address: %s\n", this->code_list.GetFormattedAddressString(this->process_info.user_exception_context_address)); - } - - fprintf(f_report, "Exception Info:\n"); - fprintf(f_report, " Type: %s\n", GetDebugExceptionTypeStr(this->exception_info.type)); - fprintf(f_report, " Address: %s\n", this->code_list.GetFormattedAddressString(this->exception_info.address)); - switch (this->exception_info.type) { - case DebugExceptionType::UndefinedInstruction: - fprintf(f_report, " Opcode: %08x\n", this->exception_info.specific.undefined_instruction.insn); - break; - case DebugExceptionType::DataAbort: - case DebugExceptionType::AlignmentFault: - if (this->exception_info.specific.raw != this->exception_info.address) { - fprintf(f_report, " Fault Address: %s\n", this->code_list.GetFormattedAddressString(this->exception_info.specific.raw)); - } - break; - case DebugExceptionType::BadSvc: - fprintf(f_report, " Svc Id: 0x%02x\n", this->exception_info.specific.bad_svc.id); - break; - case DebugExceptionType::UserBreak: - fprintf(f_report, " Break Reason: 0x%lx\n", this->exception_info.specific.user_break.break_reason); - fprintf(f_report, " Break Address: %s\n", this->code_list.GetFormattedAddressString(this->exception_info.specific.user_break.address)); - fprintf(f_report, " Break Size: 0x%lx\n", this->exception_info.specific.user_break.size); - break; - default: - break; - } - - fprintf(f_report, "Crashed Thread Info:\n"); - this->crashed_thread_info.SaveToFile(f_report); - - if ((GetRuntimeFirmwareVersion() >= FirmwareVersion_500)) { - if (this->dying_message_size) { + /* Dying Message. */ + if (GetRuntimeFirmwareVersion() >= FirmwareVersion_500 && this->dying_message_size != 0) { fprintf(f_report, "Dying Message Info:\n"); - fprintf(f_report, " Address: 0x%s\n", this->code_list.GetFormattedAddressString(this->dying_message_address)); + fprintf(f_report, " Address: 0x%s\n", this->module_list.GetFormattedAddressString(this->dying_message_address)); fprintf(f_report, " Size: 0x%016lx\n", this->dying_message_size); - CrashReport::Memdump(f_report, " Dying Message: ", this->dying_message, this->dying_message_size); + DumpMemoryHexToFile(f_report, " Dying Message: ", this->dying_message, this->dying_message_size); } + + /* Module Info. */ + fprintf(f_report, "Module Info:\n"); + this->module_list.SaveToFile(f_report); + + /* Thread Info. */ + fprintf(f_report, "\nThread Report:\n"); + this->thread_list.SaveToFile(f_report); } - fprintf(f_report, "Code Region Info:\n"); - this->code_list.SaveToFile(f_report); - fprintf(f_report, "\nThread Report:\n"); - this->thread_list.SaveToFile(f_report); -} - -/* Lifted from hactool. */ -void CrashReport::Memdump(FILE *f, const char *prefix, const void *data, size_t size) { - uint8_t *p = (uint8_t *)data; - - unsigned int prefix_len = strlen(prefix); - size_t offset = 0; - int first = 1; - - while (size) { - unsigned int max = 32; - - if (max > size) { - max = size; - } - - if (first) { - fprintf(f, "%s", prefix); - first = 0; - } else { - fprintf(f, "%*s", prefix_len, ""); - } - - for (unsigned int i = 0; i < max; i++) { - fprintf(f, "%02X", p[offset++]); - } - - fprintf(f, "\n"); - - size -= max; - } + } diff --git a/stratosphere/creport/source/creport_crash_report.hpp b/stratosphere/creport/source/creport_crash_report.hpp index 7c7e2e706..b6c7a8196 100644 --- a/stratosphere/creport/source/creport_crash_report.hpp +++ b/stratosphere/creport/source/creport_crash_report.hpp @@ -15,94 +15,87 @@ */ #pragma once - #include #include -#include #include -#include "creport_debug_types.hpp" -#include "creport_thread_info.hpp" -#include "creport_code_info.hpp" +#include "creport_threads.hpp" +#include "creport_modules.hpp" -class CrashReport { - private: - Handle debug_handle = INVALID_HANDLE; - bool has_extra_info; - Result result = ResultCreportIncompleteReport; +namespace sts::creport { - /* Attach Process Info. */ - AttachProcessInfo process_info{}; - u64 dying_message_address = 0; - u64 dying_message_size = 0; - u8 dying_message[0x1000]{}; + class CrashReport { + private: + static constexpr size_t DyingMessageSizeMax = 0x1000; + private: + Handle debug_handle = INVALID_HANDLE; + bool has_extra_info = true; + Result result = ResultCreportIncompleteReport; - static_assert(sizeof(dying_message) == 0x1000, "Incorrect definition for dying message!"); + /* Attach process info. */ + svc::DebugInfoAttachProcess process_info = {}; + u64 dying_message_address = 0; + u64 dying_message_size = 0; + u8 dying_message[DyingMessageSizeMax] = {}; - /* Exception Info. */ - ExceptionInfo exception_info{}; - u64 crashed_thread_id = 0; - ThreadInfo crashed_thread_info; + /* Exception info. */ + svc::DebugInfoException exception_info = {}; + u64 crashed_thread_id = 0; + ThreadInfo crashed_thread; - /* Extra Info. */ - CodeList code_list; - ThreadList thread_list; + /* Lists. */ + ModuleList module_list; + ThreadList thread_list; - /* Meta, used for building list. */ - std::map thread_tls_map; - - public: - void BuildReport(u64 pid, bool has_extra_info); - FatalContext *GetFatalContext(); - void SaveReport(); - - bool IsAddressReadable(u64 address, u64 size, MemoryInfo *mi = NULL); - - static void Memdump(FILE *f, const char *prefix, const void *data, size_t size); - - Result GetResult() { - return this->result; - } - - bool WasSuccessful() { - return this->result != ResultCreportIncompleteReport; - } - - bool OpenProcess(u64 pid) { - return R_SUCCEEDED(svcDebugActiveProcess(&debug_handle, pid)); - } - - bool IsOpen() { - return this->debug_handle != INVALID_HANDLE; - } - - void Close() { - if (IsOpen()) { - svcCloseHandle(debug_handle); - debug_handle = INVALID_HANDLE; + /* Meta, used for building module/thread list. */ + std::map thread_tls_map; + public: + Result GetResult() const { + return this->result; } - } - bool IsApplication() { - return (process_info.flags & 0x40) != 0; - } + bool IsComplete() const { + return this->result != ResultCreportIncompleteReport; + } - bool Is64Bit() { - return (process_info.flags & 0x01) != 0; - } + bool IsOpen() const { + return this->debug_handle != INVALID_HANDLE; + } - bool IsUserBreak() { - return this->exception_info.type == DebugExceptionType::UserBreak; - } - private: - void ProcessExceptions(); - void ProcessDyingMessage(); - void HandleAttachProcess(DebugEventInfo &d); - void HandleException(DebugEventInfo &d); - void HandleAttachThread(DebugEventInfo &d); + bool IsApplication() const { + return (this->process_info.flags & svc::CreateProcessFlag_IsApplication) != 0; + } - void SaveToFile(FILE *f); + bool Is64Bit() const { + return (this->process_info.flags & svc::CreateProcessFlag_Is64Bit) != 0; + } - void EnsureReportDirectories(); - bool GetCurrentTime(u64 *out); -}; + bool IsUserBreak() const { + return this->exception_info.type == svc::DebugExceptionType::UserBreak; + } + + bool OpenProcess(u64 process_id) { + return R_SUCCEEDED(svcDebugActiveProcess(&this->debug_handle, process_id)); + } + + void Close() { + if (this->IsOpen()) { + svcCloseHandle(this->debug_handle); + this->debug_handle = INVALID_HANDLE; + } + } + + void BuildReport(u64 process_id, bool has_extra_info); + void GetFatalContext(FatalContext *out) const; + void SaveReport(); + private: + void ProcessExceptions(); + void ProcessDyingMessage(); + void HandleDebugEventInfoAttachProcess(const svc::DebugEventInfo &d); + void HandleDebugEventInfoAttachThread(const svc::DebugEventInfo &d); + void HandleDebugEventInfoException(const svc::DebugEventInfo &d); + + void SaveToFile(FILE *f_report); + }; + +} diff --git a/stratosphere/creport/source/creport_debug_types.hpp b/stratosphere/creport/source/creport_debug_types.hpp deleted file mode 100644 index 8c5661581..000000000 --- a/stratosphere/creport/source/creport_debug_types.hpp +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (c) 2018-2019 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 . - */ - -#pragma once -#include - -union StackFrame { - struct { - u64 fp; - u64 lr; - } frame_64; - struct { - u32 fp; - u32 lr; - } frame_32; -}; - -struct AttachProcessInfo { - u64 title_id; - u64 process_id; - char name[0xC]; - u32 flags; - u64 user_exception_context_address; /* 5.0.0+ */ -}; - -struct AttachThreadInfo { - u64 thread_id; - u64 tls_address; - u64 entrypoint; -}; - -/* TODO: ExitProcessInfo */ -/* TODO: ExitThreadInfo */ - -enum class DebugExceptionType : u32 { - UndefinedInstruction = 0, - InstructionAbort = 1, - DataAbort = 2, - AlignmentFault = 3, - DebuggerAttached = 4, - BreakPoint = 5, - UserBreak = 6, - DebuggerBreak = 7, - BadSvc = 8, - SystemMemoryError = 9, -}; - -static inline const char *GetDebugExceptionTypeStr(DebugExceptionType type) { - switch (type) { - case DebugExceptionType::UndefinedInstruction: - return "Undefined Instruction"; - case DebugExceptionType::InstructionAbort: - return "Instruction Abort"; - case DebugExceptionType::DataAbort: - return "Data Abort"; - case DebugExceptionType::AlignmentFault: - return "Alignment Fault"; - case DebugExceptionType::DebuggerAttached: - return "Debugger Attached"; - case DebugExceptionType::BreakPoint: - return "Break Point"; - case DebugExceptionType::UserBreak: - return "User Break"; - case DebugExceptionType::DebuggerBreak: - return "Debugger Break"; - case DebugExceptionType::BadSvc: - return "Bad Svc"; - case DebugExceptionType::SystemMemoryError: - return "System Memory Error"; - default: - return "Unknown"; - } -} - -struct UndefinedInstructionInfo { - u32 insn; -}; - -struct DataAbortInfo { - u64 address; -}; - -struct AlignmentFaultInfo { - u64 address; -}; - -struct UserBreakInfo { - u64 break_reason; - u64 address; - u64 size; -}; - -struct BadSvcInfo { - u32 id; -}; - -union SpecificExceptionInfo { - UndefinedInstructionInfo undefined_instruction; - DataAbortInfo data_abort; - AlignmentFaultInfo alignment_fault; - UserBreakInfo user_break; - BadSvcInfo bad_svc; - u64 raw; -}; - -struct ExceptionInfo { - DebugExceptionType type; - u64 address; - SpecificExceptionInfo specific; -}; - - -enum class DebugEventType : u32 { - AttachProcess = 0, - AttachThread = 1, - ExitProcess = 2, - ExitThread = 3, - Exception = 4 -}; - -union DebugInfo { - AttachProcessInfo attach_process; - AttachThreadInfo attach_thread; - ExceptionInfo exception; -}; - -struct DebugEventInfo { - DebugEventType type; - u32 flags; - u64 thread_id; - union { - DebugInfo info; - u64 _[0x40/sizeof(u64)]; - }; -}; - -static_assert(sizeof(DebugEventInfo) >= 0x50, "Incorrect DebugEventInfo definition!"); \ No newline at end of file diff --git a/stratosphere/creport/source/creport_main.cpp b/stratosphere/creport/source/creport_main.cpp index 8f7937645..637ef003b 100644 --- a/stratosphere/creport/source/creport_main.cpp +++ b/stratosphere/creport/source/creport_main.cpp @@ -21,9 +21,9 @@ #include #include -#include - +#include #include "creport_crash_report.hpp" +#include "creport_utils.hpp" extern "C" { @@ -81,65 +81,55 @@ void __appExit(void) { fsExit(); } -static u64 creport_parse_u64(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 out_val; -} - -static CrashReport g_Creport; +static sts::creport::CrashReport g_Creport; int main(int argc, char **argv) { /* Validate arguments. */ if (argc < 2) { - return 0; + return EXIT_FAILURE; } for (int i = 0; i < argc; i++) { if (argv[i] == NULL) { - return 0; + return EXIT_FAILURE; } } /* Parse crashed PID. */ - u64 crashed_pid = creport_parse_u64(argv[0]); + u64 crashed_pid = sts::creport::ParseProcessIdArgument(argv[0]); /* Try to debug the crashed process. */ g_Creport.BuildReport(crashed_pid, argv[1][0] == '1'); - if (g_Creport.WasSuccessful()) { - g_Creport.SaveReport(); - - DoWithSmSession([&]() { - if (R_SUCCEEDED(nsdevInitialize())) { - nsdevTerminateProcess(crashed_pid); - nsdevExit(); - } - }); - - /* Don't fatal if we have extra info. */ - if ((GetRuntimeFirmwareVersion() >= FirmwareVersion_500)) { - if (g_Creport.IsApplication()) { - return 0; - } - } else if (argv[1][0] == '1') { - return 0; - } - - /* Also don't fatal if we're a user break. */ - if (g_Creport.IsUserBreak()) { - return 0; - } - - FatalContext *ctx = g_Creport.GetFatalContext(); - - fatalWithContext(g_Creport.GetResult(), FatalType_ErrorScreen, ctx); + if (!g_Creport.IsComplete()) { + return EXIT_FAILURE; } -} \ No newline at end of file + /* Save report to file. */ + g_Creport.SaveReport(); + + /* Try to terminate the process. */ + { + auto ns_holder = sts::sm::ScopedServiceHolder(); + if (R_SUCCEEDED(ns_holder.GetResult())) { + nsdevTerminateProcess(crashed_pid); + } + } + + /* Don't fatal if we have extra info, or if we're 5.0.0+ and an application crashed. */ + if (GetRuntimeFirmwareVersion() >= FirmwareVersion_500) { + if (g_Creport.IsApplication()) { + return EXIT_SUCCESS; + } + } else if (argv[1][0] == '1') { + return EXIT_SUCCESS; + } + + /* Also don't fatal if we're a user break. */ + if (g_Creport.IsUserBreak()) { + return EXIT_SUCCESS; + } + + /* Throw fatal error. */ + FatalContext ctx; + g_Creport.GetFatalContext(&ctx); + fatalWithContext(g_Creport.GetResult(), FatalType_ErrorScreen, &ctx); +} diff --git a/stratosphere/creport/source/creport_modules.cpp b/stratosphere/creport/source/creport_modules.cpp new file mode 100644 index 000000000..734cf3802 --- /dev/null +++ b/stratosphere/creport/source/creport_modules.cpp @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2018-2019 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 . + */ + +#include "creport_modules.hpp" +#include "creport_utils.hpp" + +namespace sts::creport { + + namespace { + + /* Convenience definitions/types. */ + constexpr size_t ModulePathLengthMax = 0x200; + constexpr u8 GnuSignature[4] = {'G', 'N', 'U', 0}; + + struct ModulePath { + u32 zero; + u32 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!"); + + } + + void ModuleList::SaveToFile(FILE *f_report) { + fprintf(f_report, " Number of Modules: %zu\n", this->num_modules); + for (size_t i = 0; i < this->num_modules; i++) { + const auto& module = this->modules[i]; + fprintf(f_report, " Module %02zu:\n", i); + fprintf(f_report, " Address: %016lx-%016lx\n", module.start_address, module.end_address); + if (std::strcmp(this->modules[i].name, "") != 0) { + fprintf(f_report, " Name: %s\n", module.name); + } + DumpMemoryHexToFile(f_report, " Build Id: ", module.build_id, sizeof(module.build_id)); + } + } + + void ModuleList::FindModulesFromThreadInfo(Handle debug_handle, const ThreadInfo &thread) { + /* Set the debug handle, for access in other member functions. */ + this->debug_handle = debug_handle; + + /* Try to add the thread's PC. */ + this->TryAddModule(thread.GetPC()); + + /* Try to add the thread's LR. */ + this->TryAddModule(thread.GetLR()); + + /* 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)); + } + } + + void ModuleList::TryAddModule(uintptr_t guess) { + /* Try to locate module from guess. */ + uintptr_t base_address = 0; + if (!this->TryFindModule(&base_address, guess)) { + return; + } + + /* Check whether we already have this module. */ + for (size_t i = 0; i < this->num_modules; i++) { + if (this->modules[i].start_address <= base_address && base_address < this->modules[i].end_address) { + return; + } + } + + /* Add all contiguous modules. */ + uintptr_t cur_address = base_address; + while (this->num_modules < ModuleCountMax) { + /* Get the region extents. */ + MemoryInfo mi; + u32 pi; + if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, this->debug_handle, cur_address))) { + break; + } + + /* Parse module. */ + if (mi.perm == Perm_Rx) { + auto& module = this->modules[this->num_modules++]; + module.start_address = mi.addr; + module.end_address = mi.addr + mi.size; + GetModuleName(module.name, module.start_address, module.end_address); + GetModuleBuildId(module.build_id, module.end_address); + /* Some homebrew won't have a name. Add a fake one for readability. */ + if (std::strcmp(module.name, "") == 0) { + std::snprintf(module.name, sizeof(module.name), "[%02x%02x%02x%02x]", module.build_id[0], module.build_id[1], module.build_id[2], module.build_id[3]); + } + } + + /* If we're out of readable memory, we're done reading code. */ + if (mi.type == MemType_Unmapped || mi.type == MemType_Reserved) { + 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) { + /* Query the memory region our guess falls in. */ + MemoryInfo mi; + u32 pi; + if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, this->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.perm == Perm_Rw) { + if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, mi.addr - 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.perm == Perm_R) { + if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, mi.addr - 4))) { + return false; + } + } + + /* We should, at this point, be looking at an executable region (text). */ + if (mi.perm != Perm_Rx) { + 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.addr > 0) { + if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, mi.addr - 4))) { + return false; + } + + if (mi.type == MemType_Unmapped) { + /* We've found unmapped memory, so output the mapped memory afterwards. */ + *out_address = mi.addr + 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; + { + MemoryInfo mi; + u32 pi; + + /* Verify .rodata is read-only. */ + if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, this->debug_handle, ro_start_address)) || mi.perm != Perm_R) { + return; + } + + /* Calculate start of rwdata. */ + const u64 rw_start_address = mi.addr + mi.size; + + /* Read start of .rodata. */ + if (R_FAILED(svcReadDebugProcessMemory(&rodata_start, this->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 != strnlen(rodata_start.module_path.path, sizeof(rodata_start.module_path.path))) { + return; + } + } + + + /* Start after last slash in path. */ + const char *path = rodata_start.module_path.path; + int ofs; + for (ofs = rodata_start.module_path.path_length; ofs >= 0; ofs--) { + if (path[ofs] == '/' || path[ofs] == '\\') { + break; + } + } + ofs++; + + /* Copy name to output. */ + const size_t name_size = std::min(ModuleNameLengthMax, sizeof(rodata_start.module_path.path) - ofs); + std::strncpy(out_name, path + ofs, name_size); + out_name[ModuleNameLengthMax - 1] = '\x00'; + } + + void ModuleList::GetModuleBuildId(u8 *out_build_id, uintptr_t ro_start_address) { + /* Clear output. */ + std::memset(out_build_id, 0, ModuleBuildIdLength); + + /* Verify .rodata is read-only. */ + MemoryInfo mi; + u32 pi; + if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, this->debug_handle, ro_start_address)) || mi.perm != Perm_R) { + return; + } + + /* We want to read the last two pages of .rodata. */ + static u8 s_last_rodata_pages[2 * 0x1000]; + const size_t read_size = mi.size >= sizeof(s_last_rodata_pages) ? sizeof(s_last_rodata_pages) : (sizeof(s_last_rodata_pages) / 2); + if (R_FAILED(svcReadDebugProcessMemory(s_last_rodata_pages, this->debug_handle, mi.addr + mi.size - read_size, read_size))) { + return; + } + + /* Find GNU\x00 to locate start of build id. */ + for (int ofs = read_size - sizeof(GnuSignature) - ModuleBuildIdLength; ofs >= 0; ofs--) { + if (std::memcmp(s_last_rodata_pages + ofs, GnuSignature, sizeof(GnuSignature)) == 0) { + std::memcpy(out_build_id, s_last_rodata_pages + ofs + sizeof(GnuSignature), ModuleBuildIdLength); + break; + } + } + } + + const char *ModuleList::GetFormattedAddressString(uintptr_t address) { + /* Print default formatted string. */ + std::snprintf(this->address_str_buf, sizeof(this->address_str_buf), "%016lx", address); + + /* See if the address is inside a module, for pretty-printing. */ + for (size_t i = 0; i < this->num_modules; i++) { + const auto& module = this->modules[i]; + if (module.start_address <= address && address < module.end_address) { + std::snprintf(this->address_str_buf, sizeof(this->address_str_buf), "%016lx (%s + 0x%lx)", address, module.name, address - module.start_address); + break; + } + } + + return this->address_str_buf; + } + +} diff --git a/stratosphere/creport/source/creport_modules.hpp b/stratosphere/creport/source/creport_modules.hpp new file mode 100644 index 000000000..ac17c8c39 --- /dev/null +++ b/stratosphere/creport/source/creport_modules.hpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2018-2019 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 . + */ + +#pragma once +#include +#include +#include "creport_threads.hpp" + +namespace sts::creport { + + class ModuleList { + private: + static constexpr size_t ModuleCountMax = 0x60; + static constexpr size_t ModuleNameLengthMax = 0x20; + static constexpr size_t ModuleBuildIdLength = 0x20; + + struct ModuleInfo { + char name[ModuleNameLengthMax]; + u8 build_id[ModuleBuildIdLength]; + u64 start_address; + u64 end_address; + }; + private: + Handle debug_handle; + size_t num_modules; + ModuleInfo modules[ModuleCountMax]; + + /* For pretty-printing. */ + char address_str_buf[0x280]; + public: + ModuleList() : debug_handle(INVALID_HANDLE), num_modules(0) { + std::memset(this->modules, 0, sizeof(this->modules)); + } + + size_t GetModuleCount() const { + return this->num_modules; + } + + u64 GetModuleStartAddress(size_t i) const { + return this->modules[i].start_address; + } + + void FindModulesFromThreadInfo(Handle debug_handle, const ThreadInfo &thread); + const char *GetFormattedAddressString(uintptr_t address); + void SaveToFile(FILE *f_report); + private: + bool TryFindModule(uintptr_t *out_address, uintptr_t guess); + void TryAddModule(uintptr_t guess); + void GetModuleName(char *out_name, uintptr_t text_start, uintptr_t ro_start); + void GetModuleBuildId(u8 *out_build_id, uintptr_t ro_start); + }; + +} diff --git a/stratosphere/creport/source/creport_thread_info.cpp b/stratosphere/creport/source/creport_thread_info.cpp deleted file mode 100644 index 3c05fe124..000000000 --- a/stratosphere/creport/source/creport_thread_info.cpp +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (c) 2018-2019 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 . - */ - -#include -#include - -#include "creport_thread_info.hpp" -#include "creport_crash_report.hpp" - -void ThreadInfo::SaveToFile(FILE *f_report) { - fprintf(f_report, " Thread ID: %016lx\n", this->thread_id); - if (strcmp(name, "") != 0) { - fprintf(f_report, " Thread Name: %s\n", this->name); - } - if (stack_top) { - fprintf(f_report, " Stack: %016lx-%016lx\n", this->stack_bottom, this->stack_top); - } - fprintf(f_report, " Registers:\n"); - { - for (unsigned int i = 0; i <= 28; i++) { - fprintf(f_report, " X[%02u]: %s\n", i, this->code_list->GetFormattedAddressString(this->context.cpu_gprs[i].x)); - } - fprintf(f_report, " FP: %s\n", this->code_list->GetFormattedAddressString(this->context.fp)); - fprintf(f_report, " LR: %s\n", this->code_list->GetFormattedAddressString(this->context.lr)); - fprintf(f_report, " SP: %s\n", this->code_list->GetFormattedAddressString(this->context.sp)); - fprintf(f_report, " PC: %s\n", this->code_list->GetFormattedAddressString(this->context.pc.x)); - } - fprintf(f_report, " Stack Trace:\n"); - for (unsigned int i = 0; i < this->stack_trace_size; i++) { - fprintf(f_report, " ReturnAddress[%02u]: %s\n", i, this->code_list->GetFormattedAddressString(this->stack_trace[i])); - } - if (this->tls_address != 0) { - fprintf(f_report, " TLS Address: %016lx\n", this->tls_address); - fprintf(f_report, " 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 u32 ofs = i * 0x10; - fprintf(f_report, " %012lx %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", - this->tls_address + ofs, this->tls[ofs + 0], this->tls[ofs + 1], this->tls[ofs + 2], this->tls[ofs + 3], this->tls[ofs + 4], this->tls[ofs + 5], this->tls[ofs + 6], this->tls[ofs + 7], - this->tls[ofs + 8], this->tls[ofs + 9], this->tls[ofs + 10], this->tls[ofs + 11], this->tls[ofs + 12], this->tls[ofs + 13], this->tls[ofs + 14], this->tls[ofs + 15]); - } - } -} - -bool ThreadInfo::ReadFromProcess(std::map &tls_map, Handle debug_handle, u64 thread_id, bool is_64_bit) { - this->thread_id = thread_id; - - /* Verify that the thread is running or waiting. */ - { - u64 _; - u32 thread_state; - if (R_FAILED(svcGetDebugThreadParam(&_, &thread_state, debug_handle, this->thread_id, DebugThreadParam_State))) { - return false; - } - - if (thread_state > 1) { - return false; - } - } - - /* Get the thread context. */ - if (R_FAILED(svcGetDebugThreadContext(&this->context, debug_handle, this->thread_id, 0xF))) { - return false; - } - - /* In AArch32 mode the LR, FP, and SP registers aren't set correctly in the ThreadContext by svcGetDebugThreadParam... */ - if (!is_64_bit) { - this->context.fp = this->context.cpu_gprs[11].x; - this->context.sp = this->context.cpu_gprs[13].x; - this->context.lr = this->context.cpu_gprs[14].x; - } - - /* Parse information from TLS if present. */ - if (tls_map.find(thread_id) != tls_map.end()) { - this->tls_address = tls_map[thread_id]; - u8 thread_tls[0x200]; - if (R_SUCCEEDED(svcReadDebugProcessMemory(thread_tls, debug_handle, this->tls_address, sizeof(thread_tls)))) { - std::memcpy(this->tls, thread_tls, sizeof(this->tls)); - /* Try to detect libnx threads, and skip name parsing then. */ - if (*(reinterpret_cast(&thread_tls[0x1E0])) != 0x21545624) { - u8 thread_type[0x1D0]; - const u64 thread_type_addr = *(reinterpret_cast(&thread_tls[0x1F8])); - if (R_SUCCEEDED(svcReadDebugProcessMemory(thread_type, debug_handle, thread_type_addr, sizeof(thread_type)))) { - /* Check thread name is actually at thread name. */ - if (*(reinterpret_cast(&thread_type[0x1A8])) == thread_type_addr + 0x188) { - std::memcpy(this->name, thread_type + 0x188, 0x20); - } - } - } - } - } - - /* Try to locate stack top/bottom. */ - TryGetStackInfo(debug_handle); - - u64 cur_fp = this->context.fp; - - if (is_64_bit) { - for (unsigned int i = 0; i < sizeof(this->stack_trace)/sizeof(u64); i++) { - /* Validate the current frame. */ - if (cur_fp == 0 || (cur_fp & 0xF)) { - break; - } - - /* Read a new frame. */ - StackFrame cur_frame; - if (R_FAILED(svcReadDebugProcessMemory(&cur_frame, debug_handle, cur_fp, sizeof(cur_frame.frame_64)))) { - break; - } - - /* Advance to the next frame. */ - this->stack_trace[this->stack_trace_size++] = cur_frame.frame_64.lr; - cur_fp = cur_frame.frame_64.fp; - } - } else { - for (unsigned int i = 0; i < sizeof(this->stack_trace)/sizeof(u64); i++) { - /* Validate the current frame. */ - if (cur_fp == 0 || (cur_fp & 0x7)) { - break; - } - - /* Read a new frame. */ - StackFrame cur_frame; - if (R_FAILED(svcReadDebugProcessMemory(&cur_frame, debug_handle, cur_fp, sizeof(cur_frame.frame_32)))) { - break; - } - - /* Advance to the next frame. */ - this->stack_trace[this->stack_trace_size++] = cur_frame.frame_32.lr; - cur_fp = cur_frame.frame_32.fp; - } - } - - return true; -} - -void ThreadInfo::TryGetStackInfo(Handle debug_handle) { - MemoryInfo mi; - u32 pi; - if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, this->context.sp))) { - return; - } - - /* Check if sp points into the stack. */ - if (mi.type == MemType_MappedMemory) { - this->stack_bottom = mi.addr; - this->stack_top = mi.addr + mi.size; - return; - } - - /* It's possible that sp is below the stack... */ - if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, mi.addr + mi.size))) { - return; - } - - if (mi.type == MemType_MappedMemory) { - this->stack_bottom = mi.addr; - this->stack_top = mi.addr + mi.size; - } -} - -void ThreadInfo::DumpBinary(FILE *f_bin) { - fwrite(&this->thread_id, sizeof(this->thread_id), 1, f_bin); - fwrite(&this->context, sizeof(this->context), 1, f_bin); - - fwrite(&this->tls_address, sizeof(this->tls_address), 1, f_bin); - fwrite(&this->tls, sizeof(this->tls), 1, f_bin); - fwrite(&this->name, sizeof(this->name), 1, f_bin); - - u64 sts = this->stack_trace_size; - fwrite(&sts, sizeof(sts), 1, f_bin); - fwrite(this->stack_trace, sizeof(u64), this->stack_trace_size, f_bin); - fwrite(&this->stack_bottom, sizeof(this->stack_bottom), 1, f_bin); - fwrite(&this->stack_top, sizeof(this->stack_top), 1, f_bin); -} - -void ThreadList::DumpBinary(FILE *f_bin, u64 crashed_id) { - u32 magic = 0x31495444; /* 'DTI1' */ - fwrite(&magic, sizeof(magic), 1, f_bin); - fwrite(&this->thread_count, sizeof(u32), 1, f_bin); - fwrite(&crashed_id, sizeof(crashed_id), 1, f_bin); - for (unsigned int i = 0; i < this->thread_count; i++) { - this->thread_infos[i].DumpBinary(f_bin); - } -} - -void ThreadList::SaveToFile(FILE *f_report) { - fprintf(f_report, "Number of Threads: %02u\n", this->thread_count); - for (unsigned int i = 0; i < this->thread_count; i++) { - fprintf(f_report, "Threads[%02u]:\n", i); - this->thread_infos[i].SaveToFile(f_report); - } -} - -void ThreadList::ReadThreadsFromProcess(std::map &tls_map, Handle debug_handle, bool is_64_bit) { - u32 thread_count; - u64 thread_ids[max_thread_count]; - - if (R_FAILED(svcGetThreadList(&thread_count, thread_ids, max_thread_count, debug_handle))) { - this->thread_count = 0; - return; - } - - if (thread_count > max_thread_count) { - thread_count = max_thread_count; - } - - for (unsigned int i = 0; i < thread_count; i++) { - if (this->thread_infos[this->thread_count].ReadFromProcess(tls_map, debug_handle, thread_ids[this->thread_count], is_64_bit)) { - this->thread_count++; - } - } -} \ No newline at end of file diff --git a/stratosphere/creport/source/creport_thread_info.hpp b/stratosphere/creport/source/creport_thread_info.hpp deleted file mode 100644 index bec1f6a4f..000000000 --- a/stratosphere/creport/source/creport_thread_info.hpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2018-2019 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 . - */ - -#pragma once -#include -#include -#include - -#include "creport_debug_types.hpp" - -class CodeList; - -class ThreadInfo { - public: - ThreadContext context{}; - u64 thread_id = 0; - u64 stack_top = 0; - u64 stack_bottom = 0; - u64 stack_trace[0x20]{}; - u32 stack_trace_size = 0; - u64 tls_address = 0; - u8 tls[0x100]{}; - char name[0x40]{}; - CodeList *code_list; - public: - u64 GetPC() const { return context.pc.x; } - u64 GetLR() const { return context.lr; } - u64 GetId() const { return thread_id; } - u32 GetStackTraceSize() const { return stack_trace_size; } - u64 GetStackTrace(u32 i) const { return stack_trace[i]; } - - bool ReadFromProcess(std::map &tls_map, Handle debug_handle, u64 thread_id, bool is_64_bit); - void SaveToFile(FILE *f_report); - void DumpBinary(FILE *f_bin); - void SetCodeList(CodeList *cl) { this->code_list = cl; } - private: - void TryGetStackInfo(Handle debug_handle); -}; - -class ThreadList { - private: - static const size_t max_thread_count = 0x60; - u32 thread_count = 0; - ThreadInfo thread_infos[max_thread_count]; - public: - u32 GetThreadCount() const { return thread_count; } - const ThreadInfo *GetThreadInfo(u32 i) const { return &thread_infos[i]; } - - void SaveToFile(FILE *f_report); - void DumpBinary(FILE *f_bin, u64 crashed_id); - void ReadThreadsFromProcess(std::map &tls_map, Handle debug_handle, bool is_64_bit); - void SetCodeList(CodeList *cl) { - for (u32 i = 0; i < thread_count; i++) { - thread_infos[i].SetCodeList(cl); - } - } -}; diff --git a/stratosphere/creport/source/creport_threads.cpp b/stratosphere/creport/source/creport_threads.cpp new file mode 100644 index 000000000..f8019f9ee --- /dev/null +++ b/stratosphere/creport/source/creport_threads.cpp @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2018-2019 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 . + */ + +#include "creport_threads.hpp" +#include "creport_modules.hpp" + +namespace sts::creport { + + namespace { + + /* Convenience definitions. */ + constexpr u32 LibnxThreadVarMagic = 0x21545624; /* !TV$ */ + constexpr u32 DumpedThreadInfoMagic = 0x32495444; /* DTI2 */ + + /* Types. */ + template + struct StackFrame { + T fp; + T lr; + }; + + /* Helpers. */ + template + void ReadStackTrace(size_t *out_trace_size, u64 *out_trace, size_t max_out_trace_size, Handle 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 cur_frame; + if (R_FAILED(svcReadDebugProcessMemory(&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(FILE *f_report) { + fprintf(f_report, "Number of Threads: %02zu\n", this->thread_count); + for (size_t i = 0; i < this->thread_count; i++) { + fprintf(f_report, "Threads[%02zu]:\n", i); + this->threads[i].SaveToFile(f_report); + } + } + + void ThreadInfo::SaveToFile(FILE *f_report) { + fprintf(f_report, " Thread ID: %016lx\n", this->thread_id); + if (std::strcmp(this->name, "") != 0) { + fprintf(f_report, " Thread Name: %s\n", this->name); + } + if (this->stack_top != 0) { + fprintf(f_report, " Stack Region: %016lx-%016lx\n", this->stack_bottom, this->stack_top); + } + fprintf(f_report, " Registers:\n"); + { + for (unsigned int i = 0; i <= 28; i++) { + fprintf(f_report, " X[%02u]: %s\n", i, this->module_list->GetFormattedAddressString(this->context.cpu_gprs[i].x)); + } + fprintf(f_report, " FP: %s\n", this->module_list->GetFormattedAddressString(this->context.fp)); + fprintf(f_report, " LR: %s\n", this->module_list->GetFormattedAddressString(this->context.lr)); + fprintf(f_report, " SP: %s\n", this->module_list->GetFormattedAddressString(this->context.sp)); + fprintf(f_report, " PC: %s\n", this->module_list->GetFormattedAddressString(this->context.pc.x)); + } + if (this->stack_trace_size != 0) { + fprintf(f_report, " Stack Trace:\n"); + for (size_t i = 0; i < this->stack_trace_size; i++) { + fprintf(f_report, " ReturnAddress[%02zu]: %s\n", i, this->module_list->GetFormattedAddressString(this->stack_trace[i])); + } + } + if (this->stack_dump_base != 0) { + fprintf(f_report, " 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; + fprintf(f_report, " %012lx %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + this->stack_dump_base + ofs, this->stack_dump[ofs + 0], this->stack_dump[ofs + 1], this->stack_dump[ofs + 2], this->stack_dump[ofs + 3], this->stack_dump[ofs + 4], this->stack_dump[ofs + 5], this->stack_dump[ofs + 6], this->stack_dump[ofs + 7], + this->stack_dump[ofs + 8], this->stack_dump[ofs + 9], this->stack_dump[ofs + 10], this->stack_dump[ofs + 11], this->stack_dump[ofs + 12], this->stack_dump[ofs + 13], this->stack_dump[ofs + 14], this->stack_dump[ofs + 15]); + } + } + if (this->tls_address != 0) { + fprintf(f_report, " TLS Address: %016lx\n", this->tls_address); + fprintf(f_report, " 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; + fprintf(f_report, " %012lx %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + this->tls_address + ofs, this->tls[ofs + 0], this->tls[ofs + 1], this->tls[ofs + 2], this->tls[ofs + 3], this->tls[ofs + 4], this->tls[ofs + 5], this->tls[ofs + 6], this->tls[ofs + 7], + this->tls[ofs + 8], this->tls[ofs + 9], this->tls[ofs + 10], this->tls[ofs + 11], this->tls[ofs + 12], this->tls[ofs + 13], this->tls[ofs + 14], this->tls[ofs + 15]); + } + } + } + + bool ThreadInfo::ReadFromProcess(Handle debug_handle, std::map &tls_map, u64 thread_id, bool is_64_bit) { + /* Set thread id. */ + this->thread_id = thread_id; + + /* Verify that the thread is running or waiting. */ + { + u64 _; + u32 _thread_state; + if (R_FAILED(svcGetDebugThreadParam(&_, &_thread_state, debug_handle, this->thread_id, DebugThreadParam_State))) { + return false; + } + + const svc::ThreadState thread_state = static_cast(_thread_state); + if (thread_state != svc::ThreadState::Waiting && thread_state != svc::ThreadState::Running) { + return false; + } + } + + /* Get the thread context. */ + if (R_FAILED(svcGetDebugThreadContext(&this->context, debug_handle, this->thread_id, svc::ThreadContextFlag_All))) { + return false; + } + + /* In aarch32 mode svcGetDebugThreadParam does not set the LR, FP, and SP registers correctly. */ + if (!is_64_bit) { + this->context.fp = this->context.cpu_gprs[11].x; + this->context.sp = this->context.cpu_gprs[13].x; + this->context.lr = this->context.cpu_gprs[14].x; + } + + /* Read TLS, if present. */ + /* TODO: struct definitions for nnSdk's ThreadType/TLS Layout? */ + if (tls_map.find(thread_id) != tls_map.end()) { + this->tls_address = tls_map[thread_id]; + u8 thread_tls[0x200]; + if (R_SUCCEEDED(svcReadDebugProcessMemory(thread_tls, debug_handle, this->tls_address, sizeof(thread_tls)))) { + std::memcpy(this->tls, thread_tls, sizeof(this->tls)); + /* Try to detect libnx threads, and skip name parsing then. */ + if (*(reinterpret_cast(&thread_tls[0x1E0])) != LibnxThreadVarMagic) { + u8 thread_type[0x1D0]; + const u64 thread_type_addr = *(reinterpret_cast(&thread_tls[0x1F8])); + if (R_SUCCEEDED(svcReadDebugProcessMemory(thread_type, debug_handle, thread_type_addr, sizeof(thread_type)))) { + /* Check thread name is actually at thread name. */ + static_assert(0x1A8 - 0x188 == NameLengthMax, "NameLengthMax definition!"); + if (*(reinterpret_cast(&thread_type[0x1A8])) == thread_type_addr + 0x188) { + std::memcpy(this->name, thread_type + 0x188, NameLengthMax); + } + } + } + } + } + + /* Parse stack extents and dump stack. */ + this->TryGetStackInfo(debug_handle); + + /* Dump stack trace. */ + if (is_64_bit) { + ReadStackTrace(&this->stack_trace_size, this->stack_trace, StackTraceSizeMax, debug_handle, this->context.fp); + } else { + ReadStackTrace(&this->stack_trace_size, this->stack_trace, StackTraceSizeMax, debug_handle, this->context.fp); + } + + return true; + } + + void ThreadInfo::TryGetStackInfo(Handle debug_handle) { + /* Query stack region. */ + MemoryInfo mi; + u32 pi; + if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, this->context.sp))) { + return; + } + + /* Check if sp points into the stack. */ + if (mi.type != MemType_MappedMemory) { + /* It's possible that sp is below the stack... */ + if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, mi.addr + mi.size)) || mi.type != MemType_MappedMemory) { + return; + } + } + + /* Save stack extents. */ + this->stack_bottom = mi.addr; + this->stack_top = mi.addr + 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. */ + this->stack_dump_base = std::min(std::max(this->context.sp & ~0xFul, this->stack_bottom), this->stack_top - sizeof(this->stack_dump)); + + /* Try to read stack. */ + if (R_FAILED(svcReadDebugProcessMemory(this->stack_dump, debug_handle, this->stack_dump_base, sizeof(this->stack_dump)))) { + this->stack_dump_base = 0; + } + } + + void ThreadInfo::DumpBinary(FILE *f_bin) { + /* Dump id and context. */ + fwrite(&this->thread_id, sizeof(this->thread_id), 1, f_bin); + fwrite(&this->context, sizeof(this->context), 1, f_bin); + + /* Dump TLS info and name. */ + fwrite(&this->tls_address, sizeof(this->tls_address), 1, f_bin); + fwrite(&this->tls, sizeof(this->tls), 1, f_bin); + fwrite(&this->name, sizeof(this->name), 1, f_bin); + + /* Dump stack extents and stack dump. */ + fwrite(&this->stack_bottom, sizeof(this->stack_bottom), 1, f_bin); + fwrite(&this->stack_top, sizeof(this->stack_top), 1, f_bin); + fwrite(&this->stack_dump_base, sizeof(this->stack_dump_base), 1, f_bin); + fwrite(&this->stack_dump, sizeof(this->stack_dump), 1, f_bin); + + /* Dump stack trace. */ + { + const u64 sts = this->stack_trace_size; + fwrite(&sts, sizeof(sts), 1, f_bin); + } + fwrite(this->stack_trace, sizeof(this->stack_trace[0]), this->stack_trace_size, f_bin); + } + + void ThreadList::DumpBinary(FILE *f_bin, u64 crashed_thread_id) { + const u32 magic = DumpedThreadInfoMagic; + const u32 count = this->thread_count; + fwrite(&magic, sizeof(magic), 1, f_bin); + fwrite(&count, sizeof(count), 1, f_bin); + fwrite(&crashed_thread_id, sizeof(crashed_thread_id), 1, f_bin); + for (size_t i = 0; i < this->thread_count; i++) { + this->threads[i].DumpBinary(f_bin); + } + } + + void ThreadList::ReadFromProcess(Handle debug_handle, std::map &tls_map, bool is_64_bit) { + this->thread_count = 0; + + /* Get thread list. */ + u32 num_threads; + u64 thread_ids[ThreadCountMax]; + { + if (R_FAILED(svcGetThreadList(&num_threads, thread_ids, ThreadCountMax, debug_handle))) { + return; + } + num_threads = std::min(size_t(num_threads), ThreadCountMax); + } + + /* Parse thread infos. */ + for (size_t i = 0; i < num_threads; i++) { + if (this->threads[this->thread_count].ReadFromProcess(debug_handle, tls_map, thread_ids[i], is_64_bit)) { + this->thread_count++; + } + } + } + +} diff --git a/stratosphere/creport/source/creport_threads.hpp b/stratosphere/creport/source/creport_threads.hpp new file mode 100644 index 000000000..e350a470b --- /dev/null +++ b/stratosphere/creport/source/creport_threads.hpp @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2018-2019 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 . + */ + +#pragma once +#include +#include +#include + +namespace sts::creport { + + /* Forward declare ModuleList class. */ + class ModuleList; + + class ThreadInfo { + private: + static constexpr size_t StackTraceSizeMax = 0x20; + static constexpr size_t NameLengthMax = 0x20; + private: + ThreadContext context = {}; + u64 thread_id = 0; + u64 stack_top = 0; + u64 stack_bottom = 0; + u64 stack_trace[StackTraceSizeMax] = {}; + size_t stack_trace_size = 0; + u64 tls_address = 0; + u8 tls[0x100] = {}; + u64 stack_dump_base = 0; + u8 stack_dump[0x100] = {}; + char name[NameLengthMax + 1] = {}; + ModuleList *module_list = nullptr; + public: + u64 GetGeneralPurposeRegister(size_t i) const { + return this->context.cpu_gprs[i].x; + } + u64 GetPC() const { + return this->context.pc.x; + } + + u64 GetLR() const { + return this->context.lr; + } + + u64 GetFP() const { + return this->context.fp; + } + + u64 GetSP() const { + return this->context.sp; + } + + u64 GetThreadId() const { + return this->thread_id; + } + + size_t GetStackTraceSize() const { + return this->stack_trace_size; + } + + u64 GetStackTrace(size_t i) const { + return this->stack_trace[i]; + } + + void SetModuleList(ModuleList *ml) { + this->module_list = ml; + } + + bool ReadFromProcess(Handle debug_handle, std::map &tls_map, u64 thread_id, bool is_64_bit); + void SaveToFile(FILE *f_report); + void DumpBinary(FILE *f_bin); + private: + void TryGetStackInfo(Handle debug_handle); + }; + + class ThreadList { + private: + static constexpr size_t ThreadCountMax = 0x60; + private: + size_t thread_count = 0; + ThreadInfo threads[ThreadCountMax]; + public: + size_t GetThreadCount() const { + return this->thread_count; + } + + const ThreadInfo &GetThreadInfo(size_t i) const { + return this->threads[i]; + } + + void SetModuleList(ModuleList *ml) { + for (size_t i = 0; i < this->thread_count; i++) { + this->threads[i].SetModuleList(ml); + } + } + + void ReadFromProcess(Handle debug_handle, std::map &tls_map, bool is_64_bit); + void SaveToFile(FILE *f_report); + void DumpBinary(FILE *f_bin, u64 crashed_thread_id); + }; + +} diff --git a/stratosphere/creport/source/creport_utils.cpp b/stratosphere/creport/source/creport_utils.cpp new file mode 100644 index 000000000..751d044a9 --- /dev/null +++ b/stratosphere/creport/source/creport_utils.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2018-2019 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 . + */ + +#include "creport_utils.hpp" + +namespace sts::creport { + + namespace { + + /* Convenience definitions. */ + constexpr size_t MaximumLineLength = 0x20; + + } + + void DumpMemoryHexToFile(FILE *f, const char *prefix, const void *data, size_t size) { + const u8 *data_u8 = reinterpret_cast(data); + const int prefix_len = std::strlen(prefix); + size_t offset = 0; + size_t remaining = size; + bool first = true; + while (remaining) { + const size_t cur_size = std::max(MaximumLineLength, remaining); + + /* Print the line prefix. */ + if (first) { + fprintf(f, "%s", prefix); + first = false; + } else { + fprintf(f, "%*s", prefix_len, ""); + } + + /* Dump up to 0x20 of hex memory. */ + for (size_t i = 0; i < cur_size; i++) { + fprintf(f, "%02X", data_u8[offset++]); + } + + /* End line. */ + fprintf(f, "\n"); + remaining -= cur_size; + } + } + + u64 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 out_val; + } + +} \ No newline at end of file diff --git a/stratosphere/creport/source/creport_utils.hpp b/stratosphere/creport/source/creport_utils.hpp new file mode 100644 index 000000000..cf6404155 --- /dev/null +++ b/stratosphere/creport/source/creport_utils.hpp @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018-2019 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 . + */ + +#pragma once +#include +#include + +namespace sts::creport { + + /* Utility functions. */ + void DumpMemoryHexToFile(FILE *f, const char *prefix, const void *data, size_t size); + u64 ParseProcessIdArgument(const char *s); + +} \ No newline at end of file diff --git a/stratosphere/libstratosphere b/stratosphere/libstratosphere index 69dbb69e0..a8282205b 160000 --- a/stratosphere/libstratosphere +++ b/stratosphere/libstratosphere @@ -1 +1 @@ -Subproject commit 69dbb69e0b32aa7b977e144fe7a8355868abd04a +Subproject commit a8282205b5049a098f2a21e976b7261327d64b47 diff --git a/stratosphere/loader/source/ldr_process_creation.cpp b/stratosphere/loader/source/ldr_process_creation.cpp index d26ca32d9..04ddcc3ee 100644 --- a/stratosphere/loader/source/ldr_process_creation.cpp +++ b/stratosphere/loader/source/ldr_process_creation.cpp @@ -28,51 +28,6 @@ #include "ldr_process_creation.hpp" #include "ldr_ro_manager.hpp" -/* TODO: Move into libstratosphere header? */ -namespace sts::svc { - - namespace { - - enum CreateProcessFlag : u32 { - /* Is 64 bit? */ - CreateProcessFlag_Is64Bit = (1 << 0), - - /* What kind of address space? */ - CreateProcessFlag_AddressSpaceShift = 1, - CreateProcessFlag_AddressSpaceMask = (7 << CreateProcessFlag_AddressSpaceShift), - CreateProcessFlag_AddressSpace32Bit = (ldr::Npdm::AddressSpaceType_32Bit << CreateProcessFlag_AddressSpaceShift), - CreateProcessFlag_AddressSpace64BitDeprecated = (ldr::Npdm::AddressSpaceType_64BitDeprecated << CreateProcessFlag_AddressSpaceShift), - CreateProcessFlag_AddressSpace32BitWithoutAlias = (ldr::Npdm::AddressSpaceType_32BitWithoutAlias << CreateProcessFlag_AddressSpaceShift), - CreateProcessFlag_AddressSpace64Bit = (ldr::Npdm::AddressSpaceType_64Bit << CreateProcessFlag_AddressSpaceShift), - - /* Should JIT debug be done on crash? */ - CreateProcessFlag_EnableDebug = (1 << 4), - - /* Should ASLR be enabled for the process? */ - CreateProcessFlag_EnableAslr = (1 << 5), - - /* Is the process an application? */ - CreateProcessFlag_IsApplication = (1 << 6), - - /* 4.x deprecated: Should use secure memory? */ - CreateProcessFlag_DeprecatedUseSecureMemory = (1 << 7), - - /* 5.x+ Pool partition type. */ - CreateProcessFlag_PoolPartitionShift = 7, - CreateProcessFlag_PoolPartitionMask = (0xF << CreateProcessFlag_PoolPartitionShift), - CreateProcessFlag_PoolPartitionApplication = (ldr::Acid::PoolPartition_Application << CreateProcessFlag_PoolPartitionShift), - CreateProcessFlag_PoolPartitionApplet = (ldr::Acid::PoolPartition_Applet << CreateProcessFlag_PoolPartitionShift), - CreateProcessFlag_PoolPartitionSystem = (ldr::Acid::PoolPartition_System << CreateProcessFlag_PoolPartitionShift), - CreateProcessFlag_PoolPartitionSystemNonSecure = (ldr::Acid::PoolPartition_SystemNonSecure << CreateProcessFlag_PoolPartitionShift), - - /* 7.x+ Should memory allocation be optimized? This requires IsApplication. */ - CreateProcessFlag_OptimizeMemoryAllocation = (1 << 11), - }; - - } - -} - namespace sts::ldr { namespace { diff --git a/stratosphere/sm/source/impl/sm_service_manager.cpp b/stratosphere/sm/source/impl/sm_service_manager.cpp index d9363ad5f..76fdd234a 100644 --- a/stratosphere/sm/source/impl/sm_service_manager.cpp +++ b/stratosphere/sm/source/impl/sm_service_manager.cpp @@ -247,7 +247,10 @@ namespace sts::sm::impl { bool IsMitmDisallowed(ncm::TitleId title_id) { /* Mitm used on certain titles can prevent the boot process from completing. */ /* TODO: Is there a way to do this that's less hardcoded? Needs design thought. */ - return title_id == ncm::TitleId::Loader || title_id == ncm::TitleId::Boot || title_id == ncm::TitleId::AtmosphereMitm; + return title_id == ncm::TitleId::Loader || + title_id == ncm::TitleId::Boot || + title_id == ncm::TitleId::AtmosphereMitm || + title_id == ncm::TitleId::Creport; } Result AddFutureMitmDeclaration(ServiceName service) {