creport: Try to take screenshot of application crashes on 9.x+

This commit is contained in:
Michael Scire 2020-04-22 14:50:16 -07:00
parent 93e0c9194d
commit 6ac1ff6f24
9 changed files with 201 additions and 23 deletions

View file

@ -19,3 +19,4 @@
#include <stratosphere/capsrv/capsrv_screen_shot_decode_option.hpp> #include <stratosphere/capsrv/capsrv_screen_shot_decode_option.hpp>
#include <stratosphere/capsrv/server/capsrv_server_config.hpp> #include <stratosphere/capsrv/server/capsrv_server_config.hpp>
#include <stratosphere/capsrv/server/capsrv_server_decoder_api.hpp> #include <stratosphere/capsrv/server/capsrv_server_decoder_api.hpp>
#include <stratosphere/capsrv/capsrv_screen_shot_control_api.hpp>

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vapours.hpp>
#include <stratosphere/vi/vi_layer_stack.hpp>
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);
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere/vi/vi_layer_stack.hpp>

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vapours.hpp>
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,
};
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
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());
}
}

View file

@ -20,6 +20,7 @@
"service_access": [ "service_access": [
"csrng", "csrng",
"spl:", "spl:",
"caps:sc",
"erpt:c", "erpt:c",
"fatal:u", "fatal:u",
"ns:dev", "ns:dev",

View file

@ -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<u8 *>(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) { void CrashReport::BuildReport(os::ProcessId process_id, bool has_extra_info) {
this->has_extra_info = has_extra_info; this->has_extra_info = has_extra_info;
@ -89,12 +102,12 @@ namespace ams::creport {
/* Parse info from the crashed process. */ /* Parse info from the crashed process. */
this->ProcessExceptions(); this->ProcessExceptions();
this->module_list.FindModulesFromThreadInfo(this->debug_handle, this->crashed_thread); this->module_list->FindModulesFromThreadInfo(this->debug_handle, this->crashed_thread);
this->thread_list.ReadFromProcess(this->debug_handle, this->thread_tls_map, this->Is64Bit()); this->thread_list->ReadFromProcess(this->debug_handle, this->thread_tls_map, this->Is64Bit());
/* Associate module list to threads. */ /* Associate module list to threads. */
this->crashed_thread.SetModuleList(&this->module_list); this->crashed_thread.SetModuleList(this->module_list);
this->thread_list.SetModuleList(&this->module_list); this->thread_list->SetModuleList(this->module_list);
/* Process dying message for applications. */ /* Process dying message for applications. */
if (this->IsApplication()) { if (this->IsApplication()) {
@ -103,8 +116,13 @@ namespace ams::creport {
/* Nintendo's creport finds extra modules by looking at all threads if application, */ /* 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. */ /* but there's no reason for us not to always go looking. */
for (size_t i = 0; i < this->thread_list.GetThreadCount(); i++) { for (size_t i = 0; i < this->thread_list->GetThreadCount(); i++) {
this->module_list.FindModulesFromThreadInfo(this->debug_handle, this->thread_list.GetThreadInfo(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. */ /* 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); out->aarch64_ctx.stack_trace[i] = this->crashed_thread.GetStackTrace(i);
} }
if (this->module_list.GetModuleCount()) { if (this->module_base_address != 0) {
out->aarch64_ctx.SetBaseAddress(this->module_list.GetModuleStartAddress(0)); out->aarch64_ctx.SetBaseAddress(this->module_base_address);
} }
/* For ams fatal, which doesn't use afsr0, pass program_id instead. */ /* For ams fatal, which doesn't use afsr0, pass program_id instead. */
@ -194,7 +212,7 @@ namespace ams::creport {
} }
/* Cap userdata size. */ /* 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_address = userdata_address;
this->dying_message_size = userdata_size; this->dying_message_size = userdata_size;
@ -253,7 +271,7 @@ namespace ams::creport {
if (this->dying_message_address == 0 || this->dying_message_address & 0xFFF) { if (this->dying_message_address == 0 || this->dying_message_address & 0xFFF) {
return; return;
} }
if (this->dying_message_size > sizeof(this->dying_message)) { if (this->dying_message_size > DyingMessageSizeMax) {
return; return;
} }
@ -262,6 +280,11 @@ namespace ams::creport {
return; return;
} }
/* Verify that we have a dying message buffer. */
if (this->dying_message == nullptr) {
return;
}
/* Read the dying message. */ /* Read the dying message. */
svcReadDebugProcessMemory(this->dying_message, this->debug_handle, this->dying_message_address, this->dying_message_size); 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); ScopedFile file(file_path);
if (file.IsOpen()) { 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<capsrv::InitializeScreenShotControl, capsrv::FinalizeScreenShotControl> 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 ID: %016lx\n", this->process_info.process_id);
file.WriteFormat(" Process Flags: %08x\n", this->process_info.flags); file.WriteFormat(" Process Flags: %08x\n", this->process_info.flags);
if (hos::GetVersion() >= hos::Version_5_0_0) { 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. */ /* Exception Info. */
file.WriteFormat("Exception Info:\n"); file.WriteFormat("Exception Info:\n");
file.WriteFormat(" Type: %s\n", GetDebugExceptionString(this->exception_info.type)); 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) { switch (this->exception_info.type) {
case svc::DebugException_UndefinedInstruction: case svc::DebugException_UndefinedInstruction:
file.WriteFormat(" Opcode: %08x\n", this->exception_info.specific.undefined_instruction.insn); 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_DataAbort:
case svc::DebugException_AlignmentFault: case svc::DebugException_AlignmentFault:
if (this->exception_info.specific.raw != this->exception_info.address) { 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; break;
case svc::DebugException_UndefinedSystemCall: case svc::DebugException_UndefinedSystemCall:
@ -336,7 +386,7 @@ namespace ams::creport {
break; break;
case svc::DebugException_UserBreak: case svc::DebugException_UserBreak:
file.WriteFormat(" Break Reason: 0x%x\n", this->exception_info.specific.user_break.break_reason); 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); file.WriteFormat(" Break Size: 0x%lx\n", this->exception_info.specific.user_break.size);
break; break;
default: default:
@ -350,18 +400,18 @@ namespace ams::creport {
/* Dying Message. */ /* Dying Message. */
if (hos::GetVersion() >= hos::Version_5_0_0 && this->dying_message_size != 0) { if (hos::GetVersion() >= hos::Version_5_0_0 && this->dying_message_size != 0) {
file.WriteFormat("Dying Message Info:\n"); 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.WriteFormat(" Size: 0x%016lx\n", this->dying_message_size);
file.DumpMemory( " Dying Message: ", this->dying_message, this->dying_message_size); file.DumpMemory( " Dying Message: ", this->dying_message, this->dying_message_size);
} }
/* Module Info. */ /* Module Info. */
file.WriteFormat("Module Info:\n"); file.WriteFormat("Module Info:\n");
this->module_list.SaveToFile(file); this->module_list->SaveToFile(file);
/* Thread Info. */ /* Thread Info. */
file.WriteFormat("Thread Report:\n"); file.WriteFormat("Thread Report:\n");
this->thread_list.SaveToFile(file); this->thread_list->SaveToFile(file);
} }
} }

View file

@ -22,28 +22,35 @@ namespace ams::creport {
class CrashReport { class CrashReport {
private: private:
static constexpr size_t DyingMessageSizeMax = os::MemoryPageSize; 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: private:
Handle debug_handle = INVALID_HANDLE; Handle debug_handle = INVALID_HANDLE;
bool has_extra_info = true; bool has_extra_info = true;
Result result = ResultIncompleteReport(); Result result = ResultIncompleteReport();
/* Meta, used for building module/thread list. */
std::map<u64, u64> thread_tls_map;
/* Attach process info. */ /* Attach process info. */
svc::DebugInfoAttachProcess process_info = {}; svc::DebugInfoAttachProcess process_info = {};
u64 dying_message_address = 0; u64 dying_message_address = 0;
u64 dying_message_size = 0; u64 dying_message_size = 0;
u8 dying_message[DyingMessageSizeMax] = {}; u8 *dying_message = nullptr;
/* Exception info. */ /* Exception info. */
svc::DebugInfoException exception_info = {}; svc::DebugInfoException exception_info = {};
u64 module_base_address = 0;
u64 crashed_thread_id = 0; u64 crashed_thread_id = 0;
ThreadInfo crashed_thread; ThreadInfo crashed_thread;
/* Lists. */ /* Lists. */
ModuleList module_list; ModuleList *module_list = nullptr;
ThreadList thread_list; ThreadList *thread_list = nullptr;
/* Meta, used for building module/thread list. */ /* Memory heap. */
std::map<u64, u64> thread_tls_map; lmem::HeapHandle heap_handle;
u8 heap_storage[MemoryHeapSize];
public: public:
Result GetResult() const { Result GetResult() const {
return this->result; return this->result;
@ -80,6 +87,8 @@ namespace ams::creport {
} }
} }
void Initialize();
void BuildReport(os::ProcessId process_id, bool has_extra_info); void BuildReport(os::ProcessId process_id, bool has_extra_info);
void GetFatalContext(::FatalCpuContext *out) const; void GetFatalContext(::FatalCpuContext *out) const;
void SaveReport(); void SaveReport();

View file

@ -102,6 +102,9 @@ int main(int argc, char **argv) {
/* Parse crashed PID. */ /* Parse crashed PID. */
os::ProcessId crashed_pid = creport::ParseProcessIdArgument(argv[0]); os::ProcessId crashed_pid = creport::ParseProcessIdArgument(argv[0]);
/* Initialize the crash report. */
g_crash_report.Initialize();
/* Try to debug the crashed process. */ /* Try to debug the crashed process. */
g_crash_report.BuildReport(crashed_pid, argv[1][0] == '1'); g_crash_report.BuildReport(crashed_pid, argv[1][0] == '1');
if (!g_crash_report.IsComplete()) { if (!g_crash_report.IsComplete()) {