creport: refactor to use sts:: namespace.
This commit is contained in:
@@ -14,104 +14,195 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <switch.h>
|
||||
|
||||
#include "creport_crash_report.hpp"
|
||||
#include "creport_debug_types.hpp"
|
||||
#include "creport_utils.hpp"
|
||||
|
||||
void CrashReport::BuildReport(u64 pid, bool has_extra_info) {
|
||||
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);
|
||||
namespace sts::creport {
|
||||
|
||||
if (IsApplication()) {
|
||||
ProcessDyingMessage();
|
||||
namespace {
|
||||
|
||||
/* Convenience definitions. */
|
||||
constexpr size_t DyingMessageAddressOffset = 0x1C0;
|
||||
|
||||
/* Helper functions. */
|
||||
bool IsAddressReadable(Handle debug_handle, u64 address, size_t size) {
|
||||
MemoryInfo mi;
|
||||
u32 pi;
|
||||
if (R_FAILED(svcQueryDebugProcessMemory(&mi, &pi, debug_handle, address))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Must be read or read-write */
|
||||
if ((mi.perm | Perm_W) != Perm_Rw) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Must have space for both userdata address and userdata size. */
|
||||
if (address < mi.addr || mi.addr + mi.size < address + size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Real creport only does this if application, but there's no reason not to do it all the time. */
|
||||
for (u32 i = 0; i < this->thread_list.GetThreadCount(); i++) {
|
||||
this->code_list.ReadCodeRegionsFromThreadInfo(this->debug_handle, this->thread_list.GetThreadInfo(i));
|
||||
bool TryGetCurrentTimestamp(u64 *out) {
|
||||
/* Clear output. */
|
||||
*out = 0;
|
||||
|
||||
/* Check if we have time service. */
|
||||
{
|
||||
bool has_time_service = false;
|
||||
if (R_FAILED(sm::HasService(&has_time_service, sm::ServiceName::Encode("time:s"))) || !has_time_service) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to get the current time. */
|
||||
{
|
||||
auto time_holder = sm::ScopedServiceHolder<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) {
|
||||
ctx->aarch64_ctx.start_address = this->code_list.code_infos[0].start_address;
|
||||
}
|
||||
void CrashReport::BuildReport(u64 process_id, bool has_extra_info) {
|
||||
this->has_extra_info = has_extra_info;
|
||||
|
||||
/* For ams fatal... */
|
||||
ctx->aarch64_ctx.afsr0 = this->process_info.title_id;
|
||||
if (this->OpenProcess(process_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() {
|
||||
if (!IsOpen()) {
|
||||
return;
|
||||
}
|
||||
/* Associate module list to threads. */
|
||||
this->crashed_thread.SetModuleList(&this->module_list);
|
||||
this->thread_list.SetModuleList(&this->module_list);
|
||||
|
||||
DebugEventInfo d;
|
||||
while (R_SUCCEEDED(svcGetDebugEvent((u8 *)&d, this->debug_handle))) {
|
||||
switch (d.type) {
|
||||
case DebugEventType::AttachProcess:
|
||||
HandleAttachProcess(d);
|
||||
break;
|
||||
case DebugEventType::Exception:
|
||||
HandleException(d);
|
||||
break;
|
||||
case DebugEventType::AttachThread:
|
||||
HandleAttachThread(d);
|
||||
case DebugEventType::ExitProcess:
|
||||
case DebugEventType::ExitThread:
|
||||
default:
|
||||
break;
|
||||
/* Process dying message for applications. */
|
||||
if (this->IsApplication()) {
|
||||
this->ProcessDyingMessage();
|
||||
}
|
||||
|
||||
/* Nintendo's creport finds extra modules by looking at all threads if application, */
|
||||
/* but there's no reason for us not to always go looking. */
|
||||
for (size_t i = 0; i < this->thread_list.GetThreadCount(); i++) {
|
||||
this->module_list.FindModulesFromThreadInfo(this->debug_handle, this->thread_list.GetThreadInfo(i));
|
||||
}
|
||||
|
||||
/* Nintendo's creport builds the report here, but we'll do it later. */
|
||||
}
|
||||
}
|
||||
|
||||
/* Parse crashing thread info. */
|
||||
this->crashed_thread_info.ReadFromProcess(this->thread_tls_map, this->debug_handle, this->crashed_thread_id, Is64Bit());
|
||||
}
|
||||
void CrashReport::GetFatalContext(FatalContext *out) const {
|
||||
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. */
|
||||
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_size = 0;
|
||||
|
||||
if (!IsAddressReadable(address, sizeof(userdata_address) + sizeof(userdata_size))) {
|
||||
if (!IsAddressReadable(this->debug_handle, address, sizeof(userdata_address) + sizeof(userdata_size))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -131,274 +222,186 @@ void CrashReport::HandleAttachProcess(DebugEventInfo &d) {
|
||||
}
|
||||
|
||||
/* Cap userdata size. */
|
||||
if (userdata_size > sizeof(this->dying_message)) {
|
||||
userdata_size = sizeof(this->dying_message);
|
||||
}
|
||||
userdata_size = std::min(size_t(userdata_size), sizeof(this->dying_message));
|
||||
|
||||
/* Assign. */
|
||||
this->dying_message_address = userdata_address;
|
||||
this->dying_message_size = userdata_size;
|
||||
}
|
||||
}
|
||||
|
||||
void CrashReport::HandleException(DebugEventInfo &d) {
|
||||
switch (d.info.exception.type) {
|
||||
case DebugExceptionType::UndefinedInstruction:
|
||||
this->result = ResultCreportUndefinedInstruction;
|
||||
break;
|
||||
case DebugExceptionType::InstructionAbort:
|
||||
this->result = ResultCreportInstructionAbort;
|
||||
d.info.exception.specific.raw = 0;
|
||||
break;
|
||||
case DebugExceptionType::DataAbort:
|
||||
this->result = ResultCreportDataAbort;
|
||||
break;
|
||||
case DebugExceptionType::AlignmentFault:
|
||||
this->result = ResultCreportAlignmentFault;
|
||||
break;
|
||||
case DebugExceptionType::UserBreak:
|
||||
this->result = ResultCreportUserBreak;
|
||||
/* Try to parse out the user break result. */
|
||||
if ((GetRuntimeFirmwareVersion() >= FirmwareVersion_500)) {
|
||||
Result user_result = 0;
|
||||
if (IsAddressReadable(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));
|
||||
void CrashReport::HandleDebugEventInfoAttachThread(const svc::DebugEventInfo &d) {
|
||||
/* Save info on the thread's TLS address for later. */
|
||||
this->thread_tls_map[d.info.attach_thread.thread_id] = d.info.attach_thread.tls_address;
|
||||
}
|
||||
|
||||
void CrashReport::HandleDebugEventInfoException(const svc::DebugEventInfo &d) {
|
||||
switch (d.info.exception.type) {
|
||||
case svc::DebugExceptionType::UndefinedInstruction:
|
||||
this->result = ResultCreportUndefinedInstruction;
|
||||
break;
|
||||
case svc::DebugExceptionType::InstructionAbort:
|
||||
this->result = ResultCreportInstructionAbort;
|
||||
break;
|
||||
case svc::DebugExceptionType::DataAbort:
|
||||
this->result = ResultCreportDataAbort;
|
||||
break;
|
||||
case svc::DebugExceptionType::AlignmentFault:
|
||||
this->result = ResultCreportAlignmentFault;
|
||||
break;
|
||||
case svc::DebugExceptionType::UserBreak:
|
||||
this->result = ResultCreportUserBreak;
|
||||
/* Try to parse out the user break result. */
|
||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_500) {
|
||||
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). */
|
||||
if (R_FAILED(user_result)) {
|
||||
this->result = user_result;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DebugExceptionType::BadSvc:
|
||||
this->result = ResultCreportBadSvc;
|
||||
break;
|
||||
case DebugExceptionType::SystemMemoryError:
|
||||
this->result = ResultCreportSystemMemoryError;
|
||||
d.info.exception.specific.raw = 0;
|
||||
break;
|
||||
case DebugExceptionType::DebuggerAttached:
|
||||
case DebugExceptionType::BreakPoint:
|
||||
case DebugExceptionType::DebuggerBreak:
|
||||
default:
|
||||
break;
|
||||
case svc::DebugExceptionType::UndefinedSystemCall:
|
||||
this->result = ResultCreportUndefinedSystemCall;
|
||||
break;
|
||||
case svc::DebugExceptionType::SystemMemoryError:
|
||||
this->result = ResultCreportSystemMemoryError;
|
||||
break;
|
||||
case svc::DebugExceptionType::DebuggerAttached:
|
||||
case svc::DebugExceptionType::BreakPoint:
|
||||
case svc::DebugExceptionType::DebuggerBreak:
|
||||
return;
|
||||
}
|
||||
|
||||
/* Save exception info. */
|
||||
this->exception_info = d.info.exception;
|
||||
this->crashed_thread_id = d.thread_id;
|
||||
}
|
||||
|
||||
void CrashReport::ProcessDyingMessage() {
|
||||
/* Dying message is only stored starting in 5.0.0. */
|
||||
if (GetRuntimeFirmwareVersion() < FirmwareVersion_500) {
|
||||
return;
|
||||
}
|
||||
this->exception_info = d.info.exception;
|
||||
this->crashed_thread_id = d.thread_id;
|
||||
}
|
||||
}
|
||||
|
||||
void CrashReport::HandleAttachThread(DebugEventInfo &d) {
|
||||
this->thread_tls_map[d.info.attach_thread.thread_id] = d.info.attach_thread.tls_address;
|
||||
}
|
||||
/* Validate address/size. */
|
||||
if (this->dying_message_address == 0 || this->dying_message_address & 0xFFF) {
|
||||
return;
|
||||
}
|
||||
if (this->dying_message_size > sizeof(this->dying_message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
void CrashReport::ProcessDyingMessage() {
|
||||
/* Dying message is only stored starting in 5.0.0. */
|
||||
if ((GetRuntimeFirmwareVersion() < FirmwareVersion_500)) {
|
||||
return;
|
||||
/* Validate that the current report isn't garbage. */
|
||||
if (!IsOpen() || !IsComplete()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Validate that we can read the dying message. */
|
||||
if (!IsAddressReadable(this->debug_handle, this->dying_message_address, this->dying_message_size)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Read the dying message. */
|
||||
svcReadDebugProcessMemory(this->dying_message, this->debug_handle, this->dying_message_address, this->dying_message_size);
|
||||
}
|
||||
|
||||
/* Validate the message address/size. */
|
||||
if (this->dying_message_address == 0 || this->dying_message_address & 0xFFF) {
|
||||
return;
|
||||
}
|
||||
if (this->dying_message_size > sizeof(this->dying_message)) {
|
||||
return;
|
||||
}
|
||||
void CrashReport::SaveReport() {
|
||||
/* Ensure path exists. */
|
||||
EnsureReportDirectories();
|
||||
|
||||
/* Validate that the report isn't garbage. */
|
||||
if (!IsOpen() || !WasSuccessful()) {
|
||||
return;
|
||||
}
|
||||
/* Get a timestamp. */
|
||||
u64 timestamp;
|
||||
if (!TryGetCurrentTimestamp(×tamp)) {
|
||||
timestamp = svcGetSystemTick();
|
||||
}
|
||||
|
||||
if (!IsAddressReadable(this->dying_message_address, this->dying_message_size)) {
|
||||
return;
|
||||
}
|
||||
/* Save files. */
|
||||
{
|
||||
char file_path[FS_MAX_PATH];
|
||||
|
||||
svcReadDebugProcessMemory(this->dying_message, this->debug_handle, this->dying_message_address, this->dying_message_size);
|
||||
}
|
||||
|
||||
bool CrashReport::IsAddressReadable(u64 address, u64 size, MemoryInfo *o_mi) {
|
||||
MemoryInfo mi;
|
||||
u32 pi;
|
||||
|
||||
if (o_mi == NULL) {
|
||||
o_mi = &mi;
|
||||
}
|
||||
|
||||
if (R_FAILED(svcQueryDebugProcessMemory(o_mi, &pi, this->debug_handle, address))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Must be read or read-write */
|
||||
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;
|
||||
/* Save crash report. */
|
||||
std::snprintf(file_path, sizeof(file_path), "sdmc:/atmosphere/crash_reports/%011lu_%016lx.log", timestamp, this->process_info.title_id);
|
||||
FILE *fp = fopen(file_path, "w");
|
||||
if (fp != nullptr) {
|
||||
this->SaveToFile(fp);
|
||||
fclose(fp);
|
||||
fp = nullptr;
|
||||
}
|
||||
|
||||
/* Dump threads. */
|
||||
std::snprintf(file_path, sizeof(file_path), "sdmc:/atmosphere/crash_reports/dumps/%011lu_%016lx_thread_info.bin", timestamp, this->process_info.title_id);
|
||||
fp = fopen(file_path, "wb");
|
||||
if (fp != nullptr) {
|
||||
this->thread_list.DumpBinary(fp, this->crashed_thread.GetThreadId());
|
||||
fclose(fp);
|
||||
fp = nullptr;
|
||||
}
|
||||
});
|
||||
if (!has_time_service) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to get the current time. */
|
||||
bool success = true;
|
||||
DoWithSmSession([&]() {
|
||||
success &= R_SUCCEEDED(timeInitialize());
|
||||
});
|
||||
if (success) {
|
||||
success &= R_SUCCEEDED(timeGetCurrentTime(TimeType_LocalSystemClock, out));
|
||||
timeExit();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
void CrashReport::SaveToFile(FILE *f_report) {
|
||||
fprintf(f_report, "Atmosphère Crash Report (v1.4):\n");
|
||||
fprintf(f_report, "Result: 0x%X (2%03d-%04d)\n\n", this->result, R_MODULE(this->result), R_DESCRIPTION(this->result));
|
||||
|
||||
void CrashReport::EnsureReportDirectories() {
|
||||
char path[FS_MAX_PATH];
|
||||
strcpy(path, "sdmc:/atmosphere");
|
||||
mkdir(path, S_IRWXU);
|
||||
strcat(path, "/crash_reports");
|
||||
mkdir(path, S_IRWXU);
|
||||
strcat(path, "/dumps");
|
||||
mkdir(path, S_IRWXU);
|
||||
}
|
||||
/* Process Info. */
|
||||
char name_buf[0x10] = {};
|
||||
static_assert(sizeof(name_buf) >= sizeof(this->process_info.name), "buffer overflow!");
|
||||
std::memcpy(name_buf, this->process_info.name, sizeof(this->process_info.name));
|
||||
fprintf(f_report, "Process Info:\n");
|
||||
fprintf(f_report, " Process Name: %s\n", name_buf);
|
||||
fprintf(f_report, " Title ID: %016lx\n", this->process_info.title_id);
|
||||
fprintf(f_report, " Process ID: %016lx\n", this->process_info.process_id);
|
||||
fprintf(f_report, " Process Flags: %08x\n", this->process_info.flags);
|
||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_500) {
|
||||
fprintf(f_report, " User Exception Address: %s\n", this->module_list.GetFormattedAddressString(this->process_info.user_exception_context_address));
|
||||
}
|
||||
|
||||
void CrashReport::SaveReport() {
|
||||
/* Save the report to the SD card. */
|
||||
char report_path[FS_MAX_PATH];
|
||||
/* Exception Info. */
|
||||
fprintf(f_report, "Exception Info:\n");
|
||||
fprintf(f_report, " Type: %s\n", GetDebugExceptionTypeString(this->exception_info.type));
|
||||
fprintf(f_report, " Address: %s\n", this->module_list.GetFormattedAddressString(this->exception_info.address));
|
||||
switch (this->exception_info.type) {
|
||||
case svc::DebugExceptionType::UndefinedInstruction:
|
||||
fprintf(f_report, " Opcode: %08x\n", this->exception_info.specific.undefined_instruction.insn);
|
||||
break;
|
||||
case svc::DebugExceptionType::DataAbort:
|
||||
case svc::DebugExceptionType::AlignmentFault:
|
||||
if (this->exception_info.specific.raw != this->exception_info.address) {
|
||||
fprintf(f_report, " Fault Address: %s\n", this->module_list.GetFormattedAddressString(this->exception_info.specific.raw));
|
||||
}
|
||||
break;
|
||||
case svc::DebugExceptionType::UndefinedSystemCall:
|
||||
fprintf(f_report, " Svc Id: 0x%02x\n", this->exception_info.specific.undefined_system_call.id);
|
||||
break;
|
||||
case svc::DebugExceptionType::UserBreak:
|
||||
fprintf(f_report, " Break Reason: 0x%x\n", this->exception_info.specific.user_break.break_reason);
|
||||
fprintf(f_report, " Break Address: %s\n", this->module_list.GetFormattedAddressString(this->exception_info.specific.user_break.address));
|
||||
fprintf(f_report, " Break Size: 0x%lx\n", this->exception_info.specific.user_break.size);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Ensure path exists. */
|
||||
EnsureReportDirectories();
|
||||
/* Crashed Thread Info. */
|
||||
fprintf(f_report, "Crashed Thread Info:\n");
|
||||
this->crashed_thread.SaveToFile(f_report);
|
||||
|
||||
/* Get a timestamp. */
|
||||
u64 timestamp;
|
||||
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) {
|
||||
/* Dying Message. */
|
||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_500 && this->dying_message_size != 0) {
|
||||
fprintf(f_report, "Dying Message Info:\n");
|
||||
fprintf(f_report, " Address: 0x%s\n", this->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);
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user