mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2024-11-22 20:06:40 +00:00
creport: Try to take screenshot of application crashes on 9.x+
This commit is contained in:
parent
93e0c9194d
commit
6ac1ff6f24
9 changed files with 201 additions and 23 deletions
|
@ -19,3 +19,4 @@
|
|||
#include <stratosphere/capsrv/capsrv_screen_shot_decode_option.hpp>
|
||||
#include <stratosphere/capsrv/server/capsrv_server_config.hpp>
|
||||
#include <stratosphere/capsrv/server/capsrv_server_decoder_api.hpp>
|
||||
#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 <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);
|
||||
|
||||
}
|
19
libraries/libstratosphere/include/stratosphere/vi.hpp
Normal file
19
libraries/libstratosphere/include/stratosphere/vi.hpp
Normal 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>
|
|
@ -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,
|
||||
};
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -20,6 +20,7 @@
|
|||
"service_access": [
|
||||
"csrng",
|
||||
"spl:",
|
||||
"caps:sc",
|
||||
"erpt:c",
|
||||
"fatal:u",
|
||||
"ns:dev",
|
||||
|
|
|
@ -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) {
|
||||
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<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 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<u64, u64> 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<u64, u64> 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();
|
||||
|
|
|
@ -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()) {
|
||||
|
|
Loading…
Reference in a new issue