/* * 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_crash_report.hpp" #include "creport_utils.hpp" namespace sts::creport { 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; } 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)); } } 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); } 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"; } } } void CrashReport::BuildReport(u64 process_id, bool has_extra_info) { this->has_extra_info = has_extra_info; if (this->OpenProcess(process_id)) { ON_SCOPE_EXIT { this->Close(); }; /* 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()); /* Associate module list to threads. */ this->crashed_thread.SetModuleList(&this->module_list); this->thread_list.SetModuleList(&this->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 < 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. */ } } 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; } /* Parse out user data. */ const u64 address = this->process_info.user_exception_context_address + DyingMessageAddressOffset; u64 userdata_address = 0; u64 userdata_size = 0; if (!IsAddressReadable(this->debug_handle, address, sizeof(userdata_address) + sizeof(userdata_size))) { return; } /* Read userdata address. */ if (R_FAILED(svcReadDebugProcessMemory(&userdata_address, this->debug_handle, address, sizeof(userdata_address)))) { return; } /* Validate userdata address. */ if (userdata_address == 0 || userdata_address & 0xFFF) { return; } /* Read userdata size. */ if (R_FAILED(svcReadDebugProcessMemory(&userdata_size, this->debug_handle, address + sizeof(userdata_address), sizeof(userdata_size)))) { return; } /* Cap userdata size. */ userdata_size = std::min(size_t(userdata_size), sizeof(this->dying_message)); this->dying_message_address = userdata_address; this->dying_message_size = userdata_size; } 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) { if (IsAddressReadable(this->debug_handle, d.info.exception.specific.user_break.address, sizeof(this->result))) { svcReadDebugProcessMemory(&this->result, this->debug_handle, d.info.exception.specific.user_break.address, sizeof(this->result)); } } 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; } /* 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; } /* 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); } void CrashReport::SaveReport() { /* Ensure path exists. */ EnsureReportDirectories(); /* Get a timestamp. */ u64 timestamp; if (!TryGetCurrentTimestamp(×tamp)) { timestamp = svcGetSystemTick(); } /* Save files. */ { char file_path[FS_MAX_PATH]; /* 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; } } } 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)); /* 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)); } /* 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; } /* Crashed Thread Info. */ fprintf(f_report, "Crashed Thread Info:\n"); this->crashed_thread.SaveToFile(f_report); /* 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->module_list.GetFormattedAddressString(this->dying_message_address)); fprintf(f_report, " Size: 0x%016lx\n", 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); } }