From 6ac1ff6f24df19f1f3c8b0cf71e9893640aac3e4 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Wed, 22 Apr 2020 14:50:16 -0700 Subject: [PATCH] creport: Try to take screenshot of application crashes on 9.x+ --- .../include/stratosphere/capsrv.hpp | 1 + .../capsrv/capsrv_screen_shot_control_api.hpp | 30 +++++++ .../include/stratosphere/vi.hpp | 19 ++++ .../stratosphere/vi/vi_layer_stack.hpp | 33 +++++++ .../capsrv/capsrv_screen_shot_control_api.cpp | 32 +++++++ stratosphere/creport/creport.json | 1 + .../creport/source/creport_crash_report.cpp | 86 +++++++++++++++---- .../creport/source/creport_crash_report.hpp | 19 ++-- stratosphere/creport/source/creport_main.cpp | 3 + 9 files changed, 201 insertions(+), 23 deletions(-) create mode 100644 libraries/libstratosphere/include/stratosphere/capsrv/capsrv_screen_shot_control_api.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/vi.hpp create mode 100644 libraries/libstratosphere/include/stratosphere/vi/vi_layer_stack.hpp create mode 100644 libraries/libstratosphere/source/capsrv/capsrv_screen_shot_control_api.cpp diff --git a/libraries/libstratosphere/include/stratosphere/capsrv.hpp b/libraries/libstratosphere/include/stratosphere/capsrv.hpp index eb9c3c3ca..12285a693 100644 --- a/libraries/libstratosphere/include/stratosphere/capsrv.hpp +++ b/libraries/libstratosphere/include/stratosphere/capsrv.hpp @@ -19,3 +19,4 @@ #include #include #include +#include diff --git a/libraries/libstratosphere/include/stratosphere/capsrv/capsrv_screen_shot_control_api.hpp b/libraries/libstratosphere/include/stratosphere/capsrv/capsrv_screen_shot_control_api.hpp new file mode 100644 index 000000000..f3fdc23ae --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/capsrv/capsrv_screen_shot_control_api.hpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2019-2020 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 ams::capsrv { + + constexpr inline s32 DefaultCaptureTimeoutMilliSeconds = 100; + + Result InitializeScreenShotControl(); + void FinalizeScreenShotControl(); + + Result CaptureJpegScreenshot(u64 *out_size, void *dst, size_t dst_size, vi::LayerStack layer_stack, TimeSpan timeout); + +} diff --git a/libraries/libstratosphere/include/stratosphere/vi.hpp b/libraries/libstratosphere/include/stratosphere/vi.hpp new file mode 100644 index 000000000..ed95b4f06 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/vi.hpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2018-2020 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 diff --git a/libraries/libstratosphere/include/stratosphere/vi/vi_layer_stack.hpp b/libraries/libstratosphere/include/stratosphere/vi/vi_layer_stack.hpp new file mode 100644 index 000000000..f18deab90 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/vi/vi_layer_stack.hpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018-2020 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 + +namespace ams::vi { + + enum LayerStack { + LayerStack_Default = 0, + LayerStack_Lcd = 1, + LayerStack_Screenshot = 2, + LayerStack_Recording = 3, + LayerStack_LastFrame = 4, + LayerStack_Arbitrary = 5, + LayerStack_ApplicationForDebug = 6, + LayerStack_Null = 10, + }; + +} diff --git a/libraries/libstratosphere/source/capsrv/capsrv_screen_shot_control_api.cpp b/libraries/libstratosphere/source/capsrv/capsrv_screen_shot_control_api.cpp new file mode 100644 index 000000000..a262f47de --- /dev/null +++ b/libraries/libstratosphere/source/capsrv/capsrv_screen_shot_control_api.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018-2020 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 + +namespace ams::capsrv { + + Result InitializeScreenShotControl() { + return ::capsscInitialize(); + } + + void FinalizeScreenShotControl() { + return ::capsscExit(); + } + + Result CaptureJpegScreenshot(u64 *out_size, void *dst, size_t dst_size, vi::LayerStack layer_stack, TimeSpan timeout) { + return ::capsscCaptureJpegScreenShot(out_size, dst, dst_size, static_cast<::ViLayerStack>(layer_stack), timeout.GetNanoSeconds()); + } + +} diff --git a/stratosphere/creport/creport.json b/stratosphere/creport/creport.json index 609147d2e..8c53f4b08 100644 --- a/stratosphere/creport/creport.json +++ b/stratosphere/creport/creport.json @@ -20,6 +20,7 @@ "service_access": [ "csrng", "spl:", + "caps:sc", "erpt:c", "fatal:u", "ns:dev", diff --git a/stratosphere/creport/source/creport_crash_report.cpp b/stratosphere/creport/source/creport_crash_report.cpp index bbdc3569b..c66a31aae 100644 --- a/stratosphere/creport/source/creport_crash_report.cpp +++ b/stratosphere/creport/source/creport_crash_report.cpp @@ -81,6 +81,19 @@ namespace ams::creport { } + void CrashReport::Initialize() { + /* Initialize the heap. */ + this->heap_handle = lmem::CreateExpHeap(this->heap_storage, sizeof(this->heap_storage), lmem::CreateOption_None); + + /* Allocate members. */ + this->module_list = new (lmem::AllocateFromExpHeap(this->heap_handle, sizeof(ModuleList))) ModuleList; + this->thread_list = new (lmem::AllocateFromExpHeap(this->heap_handle, sizeof(ThreadList))) ThreadList; + this->dying_message = static_cast(lmem::AllocateFromExpHeap(this->heap_handle, DyingMessageSizeMax)); + if (this->dying_message != nullptr) { + std::memset(this->dying_message, 0, DyingMessageSizeMax); + } + } + void CrashReport::BuildReport(os::ProcessId process_id, bool has_extra_info) { this->has_extra_info = has_extra_info; @@ -89,12 +102,12 @@ namespace ams::creport { /* 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()); + 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); + this->crashed_thread.SetModuleList(this->module_list); + this->thread_list->SetModuleList(this->module_list); /* Process dying message for applications. */ if (this->IsApplication()) { @@ -103,8 +116,13 @@ namespace ams::creport { /* 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)); + for (size_t i = 0; i < this->thread_list->GetThreadCount(); i++) { + this->module_list->FindModulesFromThreadInfo(this->debug_handle, this->thread_list->GetThreadInfo(i)); + } + + /* Cache the module base address to send to fatal. */ + if (this->module_list->GetModuleCount()) { + this->module_base_address = this->module_list->GetModuleStartAddress(0); } /* Nintendo's creport saves the report to erpt here, but we'll save to SD card later. */ @@ -133,8 +151,8 @@ namespace ams::creport { out->aarch64_ctx.stack_trace[i] = this->crashed_thread.GetStackTrace(i); } - if (this->module_list.GetModuleCount()) { - out->aarch64_ctx.SetBaseAddress(this->module_list.GetModuleStartAddress(0)); + if (this->module_base_address != 0) { + out->aarch64_ctx.SetBaseAddress(this->module_base_address); } /* For ams fatal, which doesn't use afsr0, pass program_id instead. */ @@ -194,7 +212,7 @@ namespace ams::creport { } /* Cap userdata size. */ - userdata_size = std::min(size_t(userdata_size), sizeof(this->dying_message)); + userdata_size = std::min(size_t(userdata_size), DyingMessageSizeMax); this->dying_message_address = userdata_address; this->dying_message_size = userdata_size; @@ -253,7 +271,7 @@ namespace ams::creport { if (this->dying_message_address == 0 || this->dying_message_address & 0xFFF) { return; } - if (this->dying_message_size > sizeof(this->dying_message)) { + if (this->dying_message_size > DyingMessageSizeMax) { return; } @@ -262,6 +280,11 @@ namespace ams::creport { return; } + /* Verify that we have a dying message buffer. */ + if (this->dying_message == nullptr) { + return; + } + /* Read the dying message. */ svcReadDebugProcessMemory(this->dying_message, this->debug_handle, this->dying_message_address, this->dying_message_size); } @@ -294,7 +317,34 @@ namespace ams::creport { { ScopedFile file(file_path); if (file.IsOpen()) { - this->thread_list.DumpBinary(file, this->crashed_thread.GetThreadId()); + this->thread_list->DumpBinary(file, this->crashed_thread.GetThreadId()); + } + } + + /* Finalize our heap. */ + this->module_list->~ModuleList(); + this->thread_list->~ThreadList(); + lmem::FreeToExpHeap(this->heap_handle, this->module_list); + lmem::FreeToExpHeap(this->heap_handle, this->thread_list); + if (this->dying_message != nullptr) { + lmem::FreeToExpHeap(this->heap_handle, this->dying_message); + } + this->module_list = nullptr; + this->thread_list = nullptr; + this->dying_message = nullptr; + + /* Try to take a screenshot. */ + if (hos::GetVersion() >= hos::Version_9_0_0 && this->IsApplication()) { + sm::ScopedServiceHolder capssc_holder; + if (capssc_holder) { + u64 jpeg_size; + if (R_SUCCEEDED(capsrv::CaptureJpegScreenshot(std::addressof(jpeg_size), this->heap_storage, sizeof(this->heap_storage), vi::LayerStack_ApplicationForDebug, TimeSpan::FromSeconds(10)))) { + std::snprintf(file_path, sizeof(file_path), "sdmc:/atmosphere/crash_reports/%011lu_%016lx.jpg", timestamp, this->process_info.program_id); + ScopedFile file(file_path); + if (file.IsOpen()) { + file.Write(this->heap_storage, jpeg_size); + } + } } } } @@ -314,13 +364,13 @@ namespace ams::creport { file.WriteFormat(" Process ID: %016lx\n", this->process_info.process_id); file.WriteFormat(" Process Flags: %08x\n", this->process_info.flags); if (hos::GetVersion() >= hos::Version_5_0_0) { - file.WriteFormat(" User Exception Address: %s\n", this->module_list.GetFormattedAddressString(this->process_info.user_exception_context_address)); + file.WriteFormat(" User Exception Address: %s\n", this->module_list->GetFormattedAddressString(this->process_info.user_exception_context_address)); } /* Exception Info. */ file.WriteFormat("Exception Info:\n"); file.WriteFormat(" Type: %s\n", GetDebugExceptionString(this->exception_info.type)); - file.WriteFormat(" Address: %s\n", this->module_list.GetFormattedAddressString(this->exception_info.address)); + file.WriteFormat(" Address: %s\n", this->module_list->GetFormattedAddressString(this->exception_info.address)); switch (this->exception_info.type) { case svc::DebugException_UndefinedInstruction: file.WriteFormat(" Opcode: %08x\n", this->exception_info.specific.undefined_instruction.insn); @@ -328,7 +378,7 @@ namespace ams::creport { case svc::DebugException_DataAbort: case svc::DebugException_AlignmentFault: if (this->exception_info.specific.raw != this->exception_info.address) { - file.WriteFormat(" Fault Address: %s\n", this->module_list.GetFormattedAddressString(this->exception_info.specific.raw)); + file.WriteFormat(" Fault Address: %s\n", this->module_list->GetFormattedAddressString(this->exception_info.specific.raw)); } break; case svc::DebugException_UndefinedSystemCall: @@ -336,7 +386,7 @@ namespace ams::creport { break; case svc::DebugException_UserBreak: file.WriteFormat(" Break Reason: 0x%x\n", this->exception_info.specific.user_break.break_reason); - file.WriteFormat(" Break Address: %s\n", this->module_list.GetFormattedAddressString(this->exception_info.specific.user_break.address)); + file.WriteFormat(" Break Address: %s\n", this->module_list->GetFormattedAddressString(this->exception_info.specific.user_break.address)); file.WriteFormat(" Break Size: 0x%lx\n", this->exception_info.specific.user_break.size); break; default: @@ -350,18 +400,18 @@ namespace ams::creport { /* Dying Message. */ if (hos::GetVersion() >= hos::Version_5_0_0 && this->dying_message_size != 0) { file.WriteFormat("Dying Message Info:\n"); - file.WriteFormat(" Address: 0x%s\n", this->module_list.GetFormattedAddressString(this->dying_message_address)); + file.WriteFormat(" Address: 0x%s\n", this->module_list->GetFormattedAddressString(this->dying_message_address)); file.WriteFormat(" Size: 0x%016lx\n", this->dying_message_size); file.DumpMemory( " Dying Message: ", this->dying_message, this->dying_message_size); } /* Module Info. */ file.WriteFormat("Module Info:\n"); - this->module_list.SaveToFile(file); + this->module_list->SaveToFile(file); /* Thread Info. */ file.WriteFormat("Thread Report:\n"); - this->thread_list.SaveToFile(file); + this->thread_list->SaveToFile(file); } } diff --git a/stratosphere/creport/source/creport_crash_report.hpp b/stratosphere/creport/source/creport_crash_report.hpp index 8f444a0f5..07174c33b 100644 --- a/stratosphere/creport/source/creport_crash_report.hpp +++ b/stratosphere/creport/source/creport_crash_report.hpp @@ -22,28 +22,35 @@ namespace ams::creport { class CrashReport { private: static constexpr size_t DyingMessageSizeMax = os::MemoryPageSize; + static constexpr size_t MemoryHeapSize = 512_KB; + static_assert(MemoryHeapSize >= DyingMessageSizeMax + sizeof(ModuleList) + sizeof(ThreadList) + os::MemoryPageSize); private: Handle debug_handle = INVALID_HANDLE; bool has_extra_info = true; Result result = ResultIncompleteReport(); + /* Meta, used for building module/thread list. */ + std::map thread_tls_map; + /* Attach process info. */ svc::DebugInfoAttachProcess process_info = {}; u64 dying_message_address = 0; u64 dying_message_size = 0; - u8 dying_message[DyingMessageSizeMax] = {}; + u8 *dying_message = nullptr; /* Exception info. */ svc::DebugInfoException exception_info = {}; + u64 module_base_address = 0; u64 crashed_thread_id = 0; ThreadInfo crashed_thread; /* Lists. */ - ModuleList module_list; - ThreadList thread_list; + ModuleList *module_list = nullptr; + ThreadList *thread_list = nullptr; - /* Meta, used for building module/thread list. */ - std::map thread_tls_map; + /* Memory heap. */ + lmem::HeapHandle heap_handle; + u8 heap_storage[MemoryHeapSize]; public: Result GetResult() const { return this->result; @@ -80,6 +87,8 @@ namespace ams::creport { } } + void Initialize(); + void BuildReport(os::ProcessId process_id, bool has_extra_info); void GetFatalContext(::FatalCpuContext *out) const; void SaveReport(); diff --git a/stratosphere/creport/source/creport_main.cpp b/stratosphere/creport/source/creport_main.cpp index ae0c72373..3eb716049 100644 --- a/stratosphere/creport/source/creport_main.cpp +++ b/stratosphere/creport/source/creport_main.cpp @@ -102,6 +102,9 @@ int main(int argc, char **argv) { /* Parse crashed PID. */ os::ProcessId crashed_pid = creport::ParseProcessIdArgument(argv[0]); + /* Initialize the crash report. */ + g_crash_report.Initialize(); + /* Try to debug the crashed process. */ g_crash_report.BuildReport(crashed_pid, argv[1][0] == '1'); if (!g_crash_report.IsComplete()) {