Implemented fatal:u properly (#1347)
* Implemented fatal:u properly fatal:u now is properly implemented with all the ipc cmds. Error reports/Crash reports are also now implemented for fatal:u. Crash reports save to yuzu/logs/crash_reports/ The register dump is currently known as sysmodules send all zeros. If there are any non zero values for the "registers" or the unknown values, let me know! * Fatal:U fixups * Made fatal:u execution break more clear * Fatal fixups
This commit is contained in:
parent
2513e086ab
commit
367c52ff0d
|
@ -2,8 +2,17 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstring>
|
||||||
|
#include <ctime>
|
||||||
|
#include <fmt/time.h>
|
||||||
|
#include "common/common_paths.h"
|
||||||
|
#include "common/file_util.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "common/scm_rev.h"
|
||||||
|
#include "common/swap.h"
|
||||||
#include "core/hle/ipc_helpers.h"
|
#include "core/hle/ipc_helpers.h"
|
||||||
|
#include "core/hle/kernel/process.h"
|
||||||
#include "core/hle/service/fatal/fatal.h"
|
#include "core/hle/service/fatal/fatal.h"
|
||||||
#include "core/hle/service/fatal/fatal_p.h"
|
#include "core/hle/service/fatal/fatal_p.h"
|
||||||
#include "core/hle/service/fatal/fatal_u.h"
|
#include "core/hle/service/fatal/fatal_u.h"
|
||||||
|
@ -15,16 +24,142 @@ Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
|
||||||
|
|
||||||
Module::Interface::~Interface() = default;
|
Module::Interface::~Interface() = default;
|
||||||
|
|
||||||
|
struct FatalInfo {
|
||||||
|
std::array<u64_le, 31> registers{}; // TODO(ogniK): See if this actually is registers or
|
||||||
|
// not(find a game which has non zero valeus)
|
||||||
|
u64_le unk0{};
|
||||||
|
u64_le unk1{};
|
||||||
|
u64_le unk2{};
|
||||||
|
u64_le unk3{};
|
||||||
|
u64_le unk4{};
|
||||||
|
u64_le unk5{};
|
||||||
|
u64_le unk6{};
|
||||||
|
|
||||||
|
std::array<u64_le, 32> backtrace{};
|
||||||
|
u64_le unk7{};
|
||||||
|
u64_le unk8{};
|
||||||
|
u32_le backtrace_size{};
|
||||||
|
u32_le unk9{};
|
||||||
|
u32_le unk10{}; // TODO(ogniK): Is this even used or is it just padding?
|
||||||
|
};
|
||||||
|
static_assert(sizeof(FatalInfo) == 0x250, "FatalInfo is an invalid size");
|
||||||
|
|
||||||
|
enum class FatalType : u32 {
|
||||||
|
ErrorReportAndScreen = 0,
|
||||||
|
ErrorReport = 1,
|
||||||
|
ErrorScreen = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void GenerateErrorReport(ResultCode error_code, const FatalInfo& info) {
|
||||||
|
const auto title_id = Core::CurrentProcess()->program_id;
|
||||||
|
std::string crash_report =
|
||||||
|
fmt::format("Yuzu {}-{} crash report\n"
|
||||||
|
"Title ID: {:016x}\n"
|
||||||
|
"Result: 0x{:X} ({:04}-{:04d})\n"
|
||||||
|
"\n",
|
||||||
|
Common::g_scm_branch, Common::g_scm_desc, title_id, error_code.raw,
|
||||||
|
2000 + static_cast<u32>(error_code.module.Value()),
|
||||||
|
static_cast<u32>(error_code.description.Value()), info.unk8, info.unk7);
|
||||||
|
if (info.backtrace_size != 0x0) {
|
||||||
|
crash_report += "Registers:\n";
|
||||||
|
// TODO(ogniK): This is just a guess, find a game which actually has non zero values
|
||||||
|
for (size_t i = 0; i < info.registers.size(); i++) {
|
||||||
|
crash_report +=
|
||||||
|
fmt::format(" X[{:02d}]: {:016x}\n", i, info.registers[i]);
|
||||||
|
}
|
||||||
|
crash_report += fmt::format(" Unknown 0: {:016x}\n", info.unk0);
|
||||||
|
crash_report += fmt::format(" Unknown 1: {:016x}\n", info.unk1);
|
||||||
|
crash_report += fmt::format(" Unknown 2: {:016x}\n", info.unk2);
|
||||||
|
crash_report += fmt::format(" Unknown 3: {:016x}\n", info.unk3);
|
||||||
|
crash_report += fmt::format(" Unknown 4: {:016x}\n", info.unk4);
|
||||||
|
crash_report += fmt::format(" Unknown 5: {:016x}\n", info.unk5);
|
||||||
|
crash_report += fmt::format(" Unknown 6: {:016x}\n", info.unk6);
|
||||||
|
crash_report += "\nBacktrace:\n";
|
||||||
|
for (size_t i = 0; i < info.backtrace_size; i++) {
|
||||||
|
crash_report +=
|
||||||
|
fmt::format(" Backtrace[{:02d}]: {:016x}\n", i, info.backtrace[i]);
|
||||||
|
}
|
||||||
|
crash_report += fmt::format("\nUnknown 7: 0x{:016x}\n", info.unk7);
|
||||||
|
crash_report += fmt::format("Unknown 8: 0x{:016x}\n", info.unk8);
|
||||||
|
crash_report += fmt::format("Unknown 9: 0x{:016x}\n", info.unk9);
|
||||||
|
crash_report += fmt::format("Unknown 10: 0x{:016x}\n", info.unk10);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR(Service_Fatal, "{}", crash_report);
|
||||||
|
|
||||||
|
const std::string crashreport_dir =
|
||||||
|
FileUtil::GetUserPath(FileUtil::UserPath::LogDir) + "crash_logs";
|
||||||
|
|
||||||
|
if (!FileUtil::CreateFullPath(crashreport_dir)) {
|
||||||
|
LOG_ERROR(
|
||||||
|
Service_Fatal,
|
||||||
|
"Unable to create crash report directory. Possible log directory permissions issue.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::time_t t = std::time(nullptr);
|
||||||
|
const std::string crashreport_filename =
|
||||||
|
fmt::format("{}/{:016x}-{:%F-%H%M%S}.log", crashreport_dir, title_id, *std::localtime(&t));
|
||||||
|
|
||||||
|
auto file = FileUtil::IOFile(crashreport_filename, "wb");
|
||||||
|
if (file.IsOpen()) {
|
||||||
|
file.WriteString(crash_report);
|
||||||
|
LOG_ERROR(Service_Fatal, "Saving error report to {}", crashreport_filename);
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Service_Fatal, "Failed to save error report to {}", crashreport_filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ThrowFatalError(ResultCode error_code, FatalType fatal_type, const FatalInfo& info) {
|
||||||
|
LOG_ERROR(Service_Fatal, "Threw fatal error type {}", static_cast<u32>(fatal_type));
|
||||||
|
switch (fatal_type) {
|
||||||
|
case FatalType::ErrorReportAndScreen:
|
||||||
|
GenerateErrorReport(error_code, info);
|
||||||
|
[[fallthrough]];
|
||||||
|
case FatalType::ErrorScreen:
|
||||||
|
// Since we have no fatal:u error screen. We should just kill execution instead
|
||||||
|
ASSERT(false);
|
||||||
|
break;
|
||||||
|
// Should not throw a fatal screen but should generate an error report
|
||||||
|
case FatalType::ErrorReport:
|
||||||
|
GenerateErrorReport(error_code, info);
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Module::Interface::ThrowFatal(Kernel::HLERequestContext& ctx) {
|
||||||
|
LOG_ERROR(Service_Fatal, "called");
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
auto error_code = rp.Pop<ResultCode>();
|
||||||
|
|
||||||
|
ThrowFatalError(error_code, FatalType::ErrorScreen, {});
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(RESULT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
void Module::Interface::ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx) {
|
void Module::Interface::ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx) {
|
||||||
|
LOG_ERROR(Service_Fatal, "called");
|
||||||
IPC::RequestParser rp(ctx);
|
IPC::RequestParser rp(ctx);
|
||||||
u32 error_code = rp.Pop<u32>();
|
auto error_code = rp.Pop<ResultCode>();
|
||||||
LOG_WARNING(Service_Fatal, "(STUBBED) called, error_code=0x{:X}", error_code);
|
auto fatal_type = rp.PopEnum<FatalType>();
|
||||||
|
|
||||||
|
ThrowFatalError(error_code, fatal_type, {}); // No info is passed with ThrowFatalWithPolicy
|
||||||
IPC::ResponseBuilder rb{ctx, 2};
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Module::Interface::ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx) {
|
void Module::Interface::ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx) {
|
||||||
LOG_WARNING(Service_Fatal, "(STUBBED) called");
|
LOG_ERROR(Service_Fatal, "called");
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
auto error_code = rp.Pop<ResultCode>();
|
||||||
|
auto fatal_type = rp.PopEnum<FatalType>();
|
||||||
|
auto fatal_info = ctx.ReadBuffer();
|
||||||
|
FatalInfo info{};
|
||||||
|
|
||||||
|
ASSERT_MSG(fatal_info.size() == sizeof(FatalInfo), "Invalid fatal info buffer size!");
|
||||||
|
std::memcpy(&info, fatal_info.data(), sizeof(FatalInfo));
|
||||||
|
|
||||||
|
ThrowFatalError(error_code, fatal_type, info);
|
||||||
IPC::ResponseBuilder rb{ctx, 2};
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ public:
|
||||||
explicit Interface(std::shared_ptr<Module> module, const char* name);
|
explicit Interface(std::shared_ptr<Module> module, const char* name);
|
||||||
~Interface() override;
|
~Interface() override;
|
||||||
|
|
||||||
|
void ThrowFatal(Kernel::HLERequestContext& ctx);
|
||||||
void ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx);
|
void ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx);
|
||||||
void ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx);
|
void ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace Service::Fatal {
|
||||||
|
|
||||||
Fatal_U::Fatal_U(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "fatal:u") {
|
Fatal_U::Fatal_U(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "fatal:u") {
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
{0, nullptr, "ThrowFatal"},
|
{0, &Fatal_U::ThrowFatal, "ThrowFatal"},
|
||||||
{1, &Fatal_U::ThrowFatalWithPolicy, "ThrowFatalWithPolicy"},
|
{1, &Fatal_U::ThrowFatalWithPolicy, "ThrowFatalWithPolicy"},
|
||||||
{2, &Fatal_U::ThrowFatalWithCpuContext, "ThrowFatalWithCpuContext"},
|
{2, &Fatal_U::ThrowFatalWithCpuContext, "ThrowFatalWithCpuContext"},
|
||||||
};
|
};
|
||||||
|
|
Reference in New Issue