mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2024-12-28 05:06:08 +00:00
creport: refactor to use sts:: namespace.
This commit is contained in:
parent
fc7f06dc78
commit
227a1d938d
17 changed files with 1239 additions and 1208 deletions
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
#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;
|
|
||||||
}
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <cstdio>
|
|
||||||
|
|
||||||
#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);
|
|
||||||
};
|
|
|
@ -14,104 +14,195 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstring>
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <switch.h>
|
|
||||||
|
|
||||||
#include "creport_crash_report.hpp"
|
#include "creport_crash_report.hpp"
|
||||||
#include "creport_debug_types.hpp"
|
#include "creport_utils.hpp"
|
||||||
|
|
||||||
void CrashReport::BuildReport(u64 pid, bool has_extra_info) {
|
namespace sts::creport {
|
||||||
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);
|
|
||||||
|
|
||||||
if (IsApplication()) {
|
namespace {
|
||||||
ProcessDyingMessage();
|
|
||||||
|
/* 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. */
|
bool TryGetCurrentTimestamp(u64 *out) {
|
||||||
for (u32 i = 0; i < this->thread_list.GetThreadCount(); i++) {
|
/* Clear output. */
|
||||||
this->code_list.ReadCodeRegionsFromThreadInfo(this->debug_handle, this->thread_list.GetThreadInfo(i));
|
*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<timeInitialize, timeExit>();
|
||||||
|
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<u32>(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) {
|
void CrashReport::BuildReport(u64 process_id, bool has_extra_info) {
|
||||||
ctx->aarch64_ctx.start_address = this->code_list.code_infos[0].start_address;
|
this->has_extra_info = has_extra_info;
|
||||||
}
|
|
||||||
|
|
||||||
/* For ams fatal... */
|
if (this->OpenProcess(process_id)) {
|
||||||
ctx->aarch64_ctx.afsr0 = this->process_info.title_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() {
|
/* Associate module list to threads. */
|
||||||
if (!IsOpen()) {
|
this->crashed_thread.SetModuleList(&this->module_list);
|
||||||
return;
|
this->thread_list.SetModuleList(&this->module_list);
|
||||||
}
|
|
||||||
|
|
||||||
DebugEventInfo d;
|
/* Process dying message for applications. */
|
||||||
while (R_SUCCEEDED(svcGetDebugEvent((u8 *)&d, this->debug_handle))) {
|
if (this->IsApplication()) {
|
||||||
switch (d.type) {
|
this->ProcessDyingMessage();
|
||||||
case DebugEventType::AttachProcess:
|
}
|
||||||
HandleAttachProcess(d);
|
|
||||||
break;
|
/* Nintendo's creport finds extra modules by looking at all threads if application, */
|
||||||
case DebugEventType::Exception:
|
/* but there's no reason for us not to always go looking. */
|
||||||
HandleException(d);
|
for (size_t i = 0; i < this->thread_list.GetThreadCount(); i++) {
|
||||||
break;
|
this->module_list.FindModulesFromThreadInfo(this->debug_handle, this->thread_list.GetThreadInfo(i));
|
||||||
case DebugEventType::AttachThread:
|
}
|
||||||
HandleAttachThread(d);
|
|
||||||
case DebugEventType::ExitProcess:
|
/* Nintendo's creport builds the report here, but we'll do it later. */
|
||||||
case DebugEventType::ExitThread:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Parse crashing thread info. */
|
void CrashReport::GetFatalContext(FatalContext *out) const {
|
||||||
this->crashed_thread_info.ReadFromProcess(this->thread_tls_map, this->debug_handle, this->crashed_thread_id, Is64Bit());
|
std::memset(out, 0, sizeof(*out));
|
||||||
}
|
|
||||||
|
out->is_aarch32 = false;
|
||||||
|
out->type = static_cast<u32>(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<u8 *>(&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. */
|
/* 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_address = 0;
|
||||||
u64 userdata_size = 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,274 +222,186 @@ void CrashReport::HandleAttachProcess(DebugEventInfo &d) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Cap userdata size. */
|
/* Cap userdata size. */
|
||||||
if (userdata_size > sizeof(this->dying_message)) {
|
userdata_size = std::min(size_t(userdata_size), sizeof(this->dying_message));
|
||||||
userdata_size = sizeof(this->dying_message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Assign. */
|
|
||||||
this->dying_message_address = userdata_address;
|
this->dying_message_address = userdata_address;
|
||||||
this->dying_message_size = userdata_size;
|
this->dying_message_size = userdata_size;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void CrashReport::HandleException(DebugEventInfo &d) {
|
void CrashReport::HandleDebugEventInfoAttachThread(const svc::DebugEventInfo &d) {
|
||||||
switch (d.info.exception.type) {
|
/* Save info on the thread's TLS address for later. */
|
||||||
case DebugExceptionType::UndefinedInstruction:
|
this->thread_tls_map[d.info.attach_thread.thread_id] = d.info.attach_thread.tls_address;
|
||||||
this->result = ResultCreportUndefinedInstruction;
|
}
|
||||||
break;
|
|
||||||
case DebugExceptionType::InstructionAbort:
|
void CrashReport::HandleDebugEventInfoException(const svc::DebugEventInfo &d) {
|
||||||
this->result = ResultCreportInstructionAbort;
|
switch (d.info.exception.type) {
|
||||||
d.info.exception.specific.raw = 0;
|
case svc::DebugExceptionType::UndefinedInstruction:
|
||||||
break;
|
this->result = ResultCreportUndefinedInstruction;
|
||||||
case DebugExceptionType::DataAbort:
|
break;
|
||||||
this->result = ResultCreportDataAbort;
|
case svc::DebugExceptionType::InstructionAbort:
|
||||||
break;
|
this->result = ResultCreportInstructionAbort;
|
||||||
case DebugExceptionType::AlignmentFault:
|
break;
|
||||||
this->result = ResultCreportAlignmentFault;
|
case svc::DebugExceptionType::DataAbort:
|
||||||
break;
|
this->result = ResultCreportDataAbort;
|
||||||
case DebugExceptionType::UserBreak:
|
break;
|
||||||
this->result = ResultCreportUserBreak;
|
case svc::DebugExceptionType::AlignmentFault:
|
||||||
/* Try to parse out the user break result. */
|
this->result = ResultCreportAlignmentFault;
|
||||||
if ((GetRuntimeFirmwareVersion() >= FirmwareVersion_500)) {
|
break;
|
||||||
Result user_result = 0;
|
case svc::DebugExceptionType::UserBreak:
|
||||||
if (IsAddressReadable(d.info.exception.specific.user_break.address, sizeof(user_result))) {
|
this->result = ResultCreportUserBreak;
|
||||||
svcReadDebugProcessMemory(&user_result, this->debug_handle, d.info.exception.specific.user_break.address, sizeof(user_result));
|
/* 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). */
|
break;
|
||||||
if (R_FAILED(user_result)) {
|
case svc::DebugExceptionType::UndefinedSystemCall:
|
||||||
this->result = user_result;
|
this->result = ResultCreportUndefinedSystemCall;
|
||||||
}
|
break;
|
||||||
}
|
case svc::DebugExceptionType::SystemMemoryError:
|
||||||
break;
|
this->result = ResultCreportSystemMemoryError;
|
||||||
case DebugExceptionType::BadSvc:
|
break;
|
||||||
this->result = ResultCreportBadSvc;
|
case svc::DebugExceptionType::DebuggerAttached:
|
||||||
break;
|
case svc::DebugExceptionType::BreakPoint:
|
||||||
case DebugExceptionType::SystemMemoryError:
|
case svc::DebugExceptionType::DebuggerBreak:
|
||||||
this->result = ResultCreportSystemMemoryError;
|
return;
|
||||||
d.info.exception.specific.raw = 0;
|
}
|
||||||
break;
|
|
||||||
case DebugExceptionType::DebuggerAttached:
|
/* Save exception info. */
|
||||||
case DebugExceptionType::BreakPoint:
|
this->exception_info = d.info.exception;
|
||||||
case DebugExceptionType::DebuggerBreak:
|
this->crashed_thread_id = d.thread_id;
|
||||||
default:
|
}
|
||||||
|
|
||||||
|
void CrashReport::ProcessDyingMessage() {
|
||||||
|
/* Dying message is only stored starting in 5.0.0. */
|
||||||
|
if (GetRuntimeFirmwareVersion() < FirmwareVersion_500) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->exception_info = d.info.exception;
|
|
||||||
this->crashed_thread_id = d.thread_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CrashReport::HandleAttachThread(DebugEventInfo &d) {
|
/* Validate address/size. */
|
||||||
this->thread_tls_map[d.info.attach_thread.thread_id] = d.info.attach_thread.tls_address;
|
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() {
|
/* Validate that the current report isn't garbage. */
|
||||||
/* Dying message is only stored starting in 5.0.0. */
|
if (!IsOpen() || !IsComplete()) {
|
||||||
if ((GetRuntimeFirmwareVersion() < FirmwareVersion_500)) {
|
return;
|
||||||
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. */
|
void CrashReport::SaveReport() {
|
||||||
if (this->dying_message_address == 0 || this->dying_message_address & 0xFFF) {
|
/* Ensure path exists. */
|
||||||
return;
|
EnsureReportDirectories();
|
||||||
}
|
|
||||||
if (this->dying_message_size > sizeof(this->dying_message)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Validate that the report isn't garbage. */
|
/* Get a timestamp. */
|
||||||
if (!IsOpen() || !WasSuccessful()) {
|
u64 timestamp;
|
||||||
return;
|
if (!TryGetCurrentTimestamp(×tamp)) {
|
||||||
}
|
timestamp = svcGetSystemTick();
|
||||||
|
}
|
||||||
|
|
||||||
if (!IsAddressReadable(this->dying_message_address, this->dying_message_size)) {
|
/* Save files. */
|
||||||
return;
|
{
|
||||||
}
|
char file_path[FS_MAX_PATH];
|
||||||
|
|
||||||
svcReadDebugProcessMemory(this->dying_message, this->debug_handle, this->dying_message_address, this->dying_message_size);
|
/* 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");
|
||||||
bool CrashReport::IsAddressReadable(u64 address, u64 size, MemoryInfo *o_mi) {
|
if (fp != nullptr) {
|
||||||
MemoryInfo mi;
|
this->SaveToFile(fp);
|
||||||
u32 pi;
|
fclose(fp);
|
||||||
|
fp = nullptr;
|
||||||
if (o_mi == NULL) {
|
}
|
||||||
o_mi = &mi;
|
|
||||||
}
|
/* 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);
|
||||||
if (R_FAILED(svcQueryDebugProcessMemory(o_mi, &pi, this->debug_handle, address))) {
|
fp = fopen(file_path, "wb");
|
||||||
return false;
|
if (fp != nullptr) {
|
||||||
}
|
this->thread_list.DumpBinary(fp, this->crashed_thread.GetThreadId());
|
||||||
|
fclose(fp);
|
||||||
/* Must be read or read-write */
|
fp = nullptr;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
if (!has_time_service) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Try to get the current time. */
|
void CrashReport::SaveToFile(FILE *f_report) {
|
||||||
bool success = true;
|
fprintf(f_report, "Atmosphère Crash Report (v1.4):\n");
|
||||||
DoWithSmSession([&]() {
|
fprintf(f_report, "Result: 0x%X (2%03d-%04d)\n\n", this->result, R_MODULE(this->result), R_DESCRIPTION(this->result));
|
||||||
success &= R_SUCCEEDED(timeInitialize());
|
|
||||||
});
|
|
||||||
if (success) {
|
|
||||||
success &= R_SUCCEEDED(timeGetCurrentTime(TimeType_LocalSystemClock, out));
|
|
||||||
timeExit();
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CrashReport::EnsureReportDirectories() {
|
/* Process Info. */
|
||||||
char path[FS_MAX_PATH];
|
char name_buf[0x10] = {};
|
||||||
strcpy(path, "sdmc:/atmosphere");
|
static_assert(sizeof(name_buf) >= sizeof(this->process_info.name), "buffer overflow!");
|
||||||
mkdir(path, S_IRWXU);
|
std::memcpy(name_buf, this->process_info.name, sizeof(this->process_info.name));
|
||||||
strcat(path, "/crash_reports");
|
fprintf(f_report, "Process Info:\n");
|
||||||
mkdir(path, S_IRWXU);
|
fprintf(f_report, " Process Name: %s\n", name_buf);
|
||||||
strcat(path, "/dumps");
|
fprintf(f_report, " Title ID: %016lx\n", this->process_info.title_id);
|
||||||
mkdir(path, S_IRWXU);
|
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() {
|
/* Exception Info. */
|
||||||
/* Save the report to the SD card. */
|
fprintf(f_report, "Exception Info:\n");
|
||||||
char report_path[FS_MAX_PATH];
|
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. */
|
/* Crashed Thread Info. */
|
||||||
EnsureReportDirectories();
|
fprintf(f_report, "Crashed Thread Info:\n");
|
||||||
|
this->crashed_thread.SaveToFile(f_report);
|
||||||
|
|
||||||
/* Get a timestamp. */
|
/* Dying Message. */
|
||||||
u64 timestamp;
|
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_500 && this->dying_message_size != 0) {
|
||||||
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) {
|
|
||||||
fprintf(f_report, "Dying Message Info:\n");
|
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);
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,94 +15,87 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
#include <stratosphere.hpp>
|
#include <stratosphere.hpp>
|
||||||
#include <cstdio>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
#include "creport_debug_types.hpp"
|
#include "creport_threads.hpp"
|
||||||
#include "creport_thread_info.hpp"
|
#include "creport_modules.hpp"
|
||||||
#include "creport_code_info.hpp"
|
|
||||||
|
|
||||||
class CrashReport {
|
namespace sts::creport {
|
||||||
private:
|
|
||||||
Handle debug_handle = INVALID_HANDLE;
|
|
||||||
bool has_extra_info;
|
|
||||||
Result result = ResultCreportIncompleteReport;
|
|
||||||
|
|
||||||
/* Attach Process Info. */
|
class CrashReport {
|
||||||
AttachProcessInfo process_info{};
|
private:
|
||||||
u64 dying_message_address = 0;
|
static constexpr size_t DyingMessageSizeMax = 0x1000;
|
||||||
u64 dying_message_size = 0;
|
private:
|
||||||
u8 dying_message[0x1000]{};
|
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. */
|
/* Exception info. */
|
||||||
ExceptionInfo exception_info{};
|
svc::DebugInfoException exception_info = {};
|
||||||
u64 crashed_thread_id = 0;
|
u64 crashed_thread_id = 0;
|
||||||
ThreadInfo crashed_thread_info;
|
ThreadInfo crashed_thread;
|
||||||
|
|
||||||
/* Extra Info. */
|
/* Lists. */
|
||||||
CodeList code_list;
|
ModuleList module_list;
|
||||||
ThreadList thread_list;
|
ThreadList thread_list;
|
||||||
|
|
||||||
/* Meta, used for building list. */
|
/* Meta, used for building module/thread list. */
|
||||||
std::map<u64, u64> thread_tls_map;
|
std::map<u64, u64> thread_tls_map;
|
||||||
|
public:
|
||||||
public:
|
Result GetResult() const {
|
||||||
void BuildReport(u64 pid, bool has_extra_info);
|
return this->result;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bool IsApplication() {
|
bool IsComplete() const {
|
||||||
return (process_info.flags & 0x40) != 0;
|
return this->result != ResultCreportIncompleteReport;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Is64Bit() {
|
bool IsOpen() const {
|
||||||
return (process_info.flags & 0x01) != 0;
|
return this->debug_handle != INVALID_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsUserBreak() {
|
bool IsApplication() const {
|
||||||
return this->exception_info.type == DebugExceptionType::UserBreak;
|
return (this->process_info.flags & svc::CreateProcessFlag_IsApplication) != 0;
|
||||||
}
|
}
|
||||||
private:
|
|
||||||
void ProcessExceptions();
|
|
||||||
void ProcessDyingMessage();
|
|
||||||
void HandleAttachProcess(DebugEventInfo &d);
|
|
||||||
void HandleException(DebugEventInfo &d);
|
|
||||||
void HandleAttachThread(DebugEventInfo &d);
|
|
||||||
|
|
||||||
void SaveToFile(FILE *f);
|
bool Is64Bit() const {
|
||||||
|
return (this->process_info.flags & svc::CreateProcessFlag_Is64Bit) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
void EnsureReportDirectories();
|
bool IsUserBreak() const {
|
||||||
bool GetCurrentTime(u64 *out);
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
|
|
||||||
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!");
|
|
|
@ -21,9 +21,9 @@
|
||||||
#include <malloc.h>
|
#include <malloc.h>
|
||||||
|
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
#include <stratosphere/firmware_version.hpp>
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
#include "creport_crash_report.hpp"
|
#include "creport_crash_report.hpp"
|
||||||
|
#include "creport_utils.hpp"
|
||||||
|
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
@ -81,65 +81,55 @@ void __appExit(void) {
|
||||||
fsExit();
|
fsExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
static u64 creport_parse_u64(char *s) {
|
static sts::creport::CrashReport g_Creport;
|
||||||
/* 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;
|
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
/* Validate arguments. */
|
/* Validate arguments. */
|
||||||
if (argc < 2) {
|
if (argc < 2) {
|
||||||
return 0;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < argc; i++) {
|
for (int i = 0; i < argc; i++) {
|
||||||
if (argv[i] == NULL) {
|
if (argv[i] == NULL) {
|
||||||
return 0;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Parse crashed PID. */
|
/* 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. */
|
/* Try to debug the crashed process. */
|
||||||
g_Creport.BuildReport(crashed_pid, argv[1][0] == '1');
|
g_Creport.BuildReport(crashed_pid, argv[1][0] == '1');
|
||||||
if (g_Creport.WasSuccessful()) {
|
if (!g_Creport.IsComplete()) {
|
||||||
g_Creport.SaveReport();
|
return EXIT_FAILURE;
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
/* Save report to file. */
|
||||||
|
g_Creport.SaveReport();
|
||||||
|
|
||||||
|
/* Try to terminate the process. */
|
||||||
|
{
|
||||||
|
auto ns_holder = sts::sm::ScopedServiceHolder<nsdevInitialize, nsdevExit>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
264
stratosphere/creport/source/creport_modules.cpp
Normal file
264
stratosphere/creport/source/creport_modules.cpp
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
66
stratosphere/creport/source/creport_modules.hpp
Normal file
66
stratosphere/creport/source/creport_modules.hpp
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
#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);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
#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<u64, u64> &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<u32 *>(&thread_tls[0x1E0])) != 0x21545624) {
|
|
||||||
u8 thread_type[0x1D0];
|
|
||||||
const u64 thread_type_addr = *(reinterpret_cast<u64 *>(&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<u64 *>(&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<u64, u64> &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++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
#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<u64, u64> &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<u64, u64> &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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
267
stratosphere/creport/source/creport_threads.cpp
Normal file
267
stratosphere/creport/source/creport_threads.cpp
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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<typename T>
|
||||||
|
struct StackFrame {
|
||||||
|
T fp;
|
||||||
|
T lr;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Helpers. */
|
||||||
|
template<typename T>
|
||||||
|
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<T> 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<u64, u64> &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<svc::ThreadState>(_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<u32 *>(&thread_tls[0x1E0])) != LibnxThreadVarMagic) {
|
||||||
|
u8 thread_type[0x1D0];
|
||||||
|
const u64 thread_type_addr = *(reinterpret_cast<u64 *>(&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<u64 *>(&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<u64>(&this->stack_trace_size, this->stack_trace, StackTraceSizeMax, debug_handle, this->context.fp);
|
||||||
|
} else {
|
||||||
|
ReadStackTrace<u32>(&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<u64, u64> &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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
113
stratosphere/creport/source/creport_threads.hpp
Normal file
113
stratosphere/creport/source/creport_threads.hpp
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
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<u64, u64> &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<u64, u64> &tls_map, bool is_64_bit);
|
||||||
|
void SaveToFile(FILE *f_report);
|
||||||
|
void DumpBinary(FILE *f_bin, u64 crashed_thread_id);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
72
stratosphere/creport/source/creport_utils.cpp
Normal file
72
stratosphere/creport/source/creport_utils.cpp
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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<const u8 *>(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
27
stratosphere/creport/source/creport_utils.hpp
Normal file
27
stratosphere/creport/source/creport_utils.hpp
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <switch.h>
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
namespace sts::creport {
|
||||||
|
|
||||||
|
/* Utility functions. */
|
||||||
|
void DumpMemoryHexToFile(FILE *f, const char *prefix, const void *data, size_t size);
|
||||||
|
u64 ParseProcessIdArgument(const char *s);
|
||||||
|
|
||||||
|
}
|
|
@ -1 +1 @@
|
||||||
Subproject commit 69dbb69e0b32aa7b977e144fe7a8355868abd04a
|
Subproject commit a8282205b5049a098f2a21e976b7261327d64b47
|
|
@ -28,51 +28,6 @@
|
||||||
#include "ldr_process_creation.hpp"
|
#include "ldr_process_creation.hpp"
|
||||||
#include "ldr_ro_manager.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 sts::ldr {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
|
@ -247,7 +247,10 @@ namespace sts::sm::impl {
|
||||||
bool IsMitmDisallowed(ncm::TitleId title_id) {
|
bool IsMitmDisallowed(ncm::TitleId title_id) {
|
||||||
/* Mitm used on certain titles can prevent the boot process from completing. */
|
/* 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. */
|
/* 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) {
|
Result AddFutureMitmDeclaration(ServiceName service) {
|
||||||
|
|
Loading…
Reference in a new issue