Prechádzať zdrojové kódy

core: Add Reporter class to take/save reports

Zach Hilman 7 rokov pred
rodič
commit
2dde8f5cfe
5 zmenil súbory, kde vykonal 416 pridanie a 1 odobranie
  1. 3 1
      src/core/CMakeLists.txt
  2. 6 0
      src/core/core.cpp
  3. 3 0
      src/core/core.h
  4. 351 0
      src/core/reporter.cpp
  5. 53 0
      src/core/reporter.h

+ 3 - 1
src/core/CMakeLists.txt

@@ -452,6 +452,8 @@ add_library(core STATIC
     memory_setup.h
     perf_stats.cpp
     perf_stats.h
+    reporter.cpp
+    reporter.h
     settings.cpp
     settings.h
     telemetry_session.cpp
@@ -464,7 +466,7 @@ add_library(core STATIC
 create_target_directory_groups(core)
 
 target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
-target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt mbedtls opus unicorn open_source_archives)
+target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt json-headers mbedtls opus unicorn open_source_archives)
 if (ENABLE_WEB_SERVICE)
     target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
     target_link_libraries(core PRIVATE web_service)

+ 6 - 0
src/core/core.cpp

@@ -34,6 +34,7 @@
 #include "core/hle/service/sm/sm.h"
 #include "core/loader/loader.h"
 #include "core/perf_stats.h"
+#include "core/reporter.h"
 #include "core/settings.h"
 #include "core/telemetry_session.h"
 #include "file_sys/cheat_engine.h"
@@ -271,6 +272,7 @@ struct System::Impl {
     std::unique_ptr<Core::TelemetrySession> telemetry_session;
 
     std::map<VAddr, std::string, std::greater<>> modules;
+    Reporter reporter;
 
     ResultStatus status = ResultStatus::Success;
     std::string status_details = "";
@@ -519,6 +521,10 @@ const std::map<VAddr, std::string, std::greater<>>& System::GetRegisteredNSOModu
     return impl->modules;
 }
 
+const Reporter& System::GetReporter() const {
+    return impl->reporter;
+}
+
 System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
     return impl->Init(*this, emu_window);
 }

+ 3 - 0
src/core/core.h

@@ -69,6 +69,7 @@ class Cpu;
 class ExclusiveMonitor;
 class FrameLimiter;
 class PerfStats;
+class Reporter;
 class TelemetrySession;
 
 struct PerfStatsResults;
@@ -290,6 +291,8 @@ public:
 
     const std::map<VAddr, std::string, std::greater<>>& GetRegisteredNSOModules() const;
 
+    const Reporter& GetReporter() const;
+
 private:
     System();
 

+ 351 - 0
src/core/reporter.cpp

@@ -0,0 +1,351 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <fstream>
+#include <json.hpp>
+#include "common/file_util.h"
+#include "common/hex_util.h"
+#include "common/scm_rev.h"
+#include "core/arm/arm_interface.h"
+#include "core/core.h"
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/kernel/process.h"
+#include "core/reporter.h"
+#include "core/settings.h"
+#include "fmt/time.h"
+
+namespace {
+
+std::string GetPath(std::string_view type, u64 title_id, std::string_view timestamp) {
+    return fmt::format("{}{}/{:016X}_{}.json", FileUtil::GetUserPath(FileUtil::UserPath::LogDir),
+                       type, title_id, timestamp);
+}
+
+std::string GetTimestamp() {
+    const auto time = std::time(nullptr);
+    return fmt::format("{:%FT%H-%M-%S}", *std::localtime(&time));
+}
+
+using namespace nlohmann;
+
+void SaveToFile(const json& json, const std::string& filename) {
+    FileUtil::CreateFullPath(filename);
+    std::ofstream file(
+        FileUtil::SanitizePath(filename, FileUtil::DirectorySeparator::PlatformDefault));
+    file << std::setw(4) << json << std::endl;
+    file.flush();
+    file.close();
+}
+
+json GetYuzuVersionData() {
+    return {
+        {"scm_rev", std::string(Common::g_scm_rev)},
+        {"scm_branch", std::string(Common::g_scm_branch)},
+        {"scm_desc", std::string(Common::g_scm_desc)},
+        {"build_name", std::string(Common::g_build_name)},
+        {"build_date", std::string(Common::g_build_date)},
+        {"build_fullname", std::string(Common::g_build_fullname)},
+        {"build_version", std::string(Common::g_build_version)},
+        {"shader_cache_version", std::string(Common::g_shader_cache_version)},
+    };
+}
+
+json GetReportCommonData(u64 title_id, ResultCode result, const std::string& timestamp,
+                         std::optional<u128> user_id = {}) {
+    auto out = json{
+        {"title_id", fmt::format("{:016X}", title_id)},
+        {"result_raw", fmt::format("{:08X}", result.raw)},
+        {"result_module", fmt::format("{:08X}", static_cast<u32>(result.module.Value()))},
+        {"result_description", fmt::format("{:08X}", result.description.Value())},
+        {"timestamp", timestamp},
+    };
+    if (user_id.has_value())
+        out["user_id"] = fmt::format("{:016X}{:016X}", (*user_id)[1], (*user_id)[0]);
+    return std::move(out);
+}
+
+json GetProcessorStateData(const std::string& architecture, u64 entry_point, u64 sp, u64 pc,
+                           u64 pstate, std::array<u64, 31> registers,
+                           std::optional<std::array<u64, 32>> backtrace = {}) {
+    auto out = json{
+        {"entry_point", fmt::format("{:016X}", entry_point)},
+        {"sp", fmt::format("{:016X}", sp)},
+        {"pc", fmt::format("{:016X}", pc)},
+        {"pstate", fmt::format("{:016X}", pstate)},
+        {"architecture", architecture},
+    };
+
+    auto registers_out = json::object();
+    for (std::size_t i = 0; i < registers.size(); ++i) {
+        registers_out[fmt::format("X{:02d}", i)] = fmt::format("{:016X}", registers[i]);
+    }
+
+    out["registers"] = std::move(registers_out);
+
+    if (backtrace.has_value()) {
+        auto backtrace_out = json::array();
+        for (const auto& entry : *backtrace) {
+            backtrace_out.push_back(fmt::format("{:016X}", entry));
+        }
+        out["backtrace"] = std::move(backtrace_out);
+    }
+
+    return std::move(out);
+}
+
+json GetProcessorStateDataAuto() {
+    const auto* process{Core::CurrentProcess()};
+    const auto& vm_manager{process->VMManager()};
+    auto& arm{Core::CurrentArmInterface()};
+
+    Core::ARM_Interface::ThreadContext context{};
+    arm.SaveContext(context);
+
+    return GetProcessorStateData(process->Is64BitProcess() ? "AArch64" : "AArch32",
+                                 vm_manager.GetCodeRegionBaseAddress(), context.sp, context.pc,
+                                 context.pstate, context.cpu_registers);
+}
+
+json GetBacktraceData() {
+    auto out = json::array();
+    const auto& backtrace{Core::CurrentArmInterface().GetBacktrace()};
+    for (const auto& entry : backtrace) {
+        out.push_back({
+            {"module", entry.module},
+            {"address", fmt::format("{:016X}", entry.address)},
+            {"original_address", fmt::format("{:016X}", entry.original_address)},
+            {"offset", fmt::format("{:016X}", entry.offset)},
+            {"symbol_name", entry.name},
+        });
+    }
+
+    return std::move(out);
+}
+
+json GetFullDataAuto(const std::string& timestamp, u64 title_id) {
+    json out;
+
+    out["yuzu_version"] = GetYuzuVersionData();
+    out["report_common"] = GetReportCommonData(title_id, RESULT_SUCCESS, timestamp);
+    out["processor_state"] = GetProcessorStateDataAuto();
+    out["backtrace"] = GetBacktraceData();
+
+    return std::move(out);
+}
+
+template <bool read_value, typename DescriptorType>
+json GetHLEBufferDescriptorData(const std::vector<DescriptorType>& buffer) {
+    auto buffer_out = json::array();
+    for (const auto& desc : buffer) {
+        auto entry = json{
+            {"address", fmt::format("{:016X}", desc.Address())},
+            {"size", fmt::format("{:016X}", desc.Size())},
+        };
+
+        if constexpr (read_value) {
+            std::vector<u8> data(desc.Size());
+            Memory::ReadBlock(desc.Address(), data.data(), desc.Size());
+            entry["data"] = Common::HexVectorToString(data);
+        }
+
+        buffer_out.push_back(std::move(entry));
+    }
+
+    return std::move(buffer_out);
+}
+
+json GetHLERequestContextData(Kernel::HLERequestContext& ctx) {
+    json out;
+
+    auto cmd_buf = json::array();
+    for (std::size_t i = 0; i < IPC::COMMAND_BUFFER_LENGTH; ++i) {
+        cmd_buf.push_back(fmt::format("{:08X}", ctx.CommandBuffer()[i]));
+    }
+
+    out["command_buffer"] = std::move(cmd_buf);
+
+    out["buffer_descriptor_a"] = GetHLEBufferDescriptorData<true>(ctx.BufferDescriptorA());
+    out["buffer_descriptor_b"] = GetHLEBufferDescriptorData<false>(ctx.BufferDescriptorB());
+    out["buffer_descriptor_c"] = GetHLEBufferDescriptorData<false>(ctx.BufferDescriptorC());
+    out["buffer_descriptor_x"] = GetHLEBufferDescriptorData<true>(ctx.BufferDescriptorX());
+
+    return std::move(out);
+}
+
+} // Anonymous namespace
+
+namespace Core {
+
+Reporter::Reporter() = default;
+
+Reporter::~Reporter() = default;
+
+void Reporter::SaveCrashReport(u64 title_id, ResultCode result, u64 set_flags, u64 entry_point,
+                               u64 sp, u64 pc, u64 pstate, u64 afsr0, u64 afsr1, u64 esr, u64 far,
+                               const std::array<u64, 31>& registers,
+                               const std::array<u64, 32>& backtrace, u32 backtrace_size,
+                               const std::string& arch, u32 unk10) const {
+    if (!IsReportingEnabled())
+        return;
+
+    const auto timestamp{GetTimestamp()};
+    json out;
+
+    out["yuzu_version"] = GetYuzuVersionData();
+    out["report_common"] = GetReportCommonData(title_id, result, timestamp);
+
+    auto proc_out = GetProcessorStateData(arch, entry_point, sp, pc, pstate, registers, backtrace);
+    proc_out["set_flags"] = fmt::format("{:016X}", set_flags);
+    proc_out["afsr0"] = fmt::format("{:016X}", afsr0);
+    proc_out["afsr1"] = fmt::format("{:016X}", afsr1);
+    proc_out["esr"] = fmt::format("{:016X}", esr);
+    proc_out["far"] = fmt::format("{:016X}", far);
+    proc_out["backtrace_size"] = fmt::format("{:08X}", backtrace_size);
+    proc_out["unknown_10"] = fmt::format("{:08X}", unk10);
+
+    out["processor_state"] = std::move(proc_out);
+
+    SaveToFile(std::move(out), GetPath("crash_report", title_id, timestamp));
+}
+
+void Reporter::SaveSvcBreakReport(u32 type, bool signal_debugger, u64 info1, u64 info2,
+                                  std::optional<std::vector<u8>> resolved_buffer) const {
+    if (!IsReportingEnabled())
+        return;
+
+    const auto timestamp{GetTimestamp()};
+    const auto title_id{Core::CurrentProcess()->GetTitleID()};
+    auto out = GetFullDataAuto(timestamp, title_id);
+
+    auto break_out = json{
+        {"type", fmt::format("{:08X}", type)},
+        {"signal_debugger", fmt::format("{}", signal_debugger)},
+        {"info1", fmt::format("{:016X}", info1)},
+        {"info2", fmt::format("{:016X}", info2)},
+    };
+
+    if (resolved_buffer.has_value()) {
+        break_out["debug_buffer"] = Common::HexVectorToString(*resolved_buffer);
+    }
+
+    out["svc_break"] = std::move(break_out);
+
+    SaveToFile(std::move(out), GetPath("svc_break_report", title_id, timestamp));
+}
+
+void Reporter::SaveUnimplementedFunctionReport(Kernel::HLERequestContext& ctx, u32 command_id,
+                                               const std::string& name,
+                                               const std::string& service_name) const {
+    if (!IsReportingEnabled())
+        return;
+
+    const auto timestamp{GetTimestamp()};
+    const auto title_id{Core::CurrentProcess()->GetTitleID()};
+    auto out = GetFullDataAuto(timestamp, title_id);
+
+    auto function_out = GetHLERequestContextData(ctx);
+    function_out["command_id"] = command_id;
+    function_out["function_name"] = name;
+    function_out["service_name"] = service_name;
+
+    out["function"] = std::move(function_out);
+
+    SaveToFile(std::move(out), GetPath("unimpl_func_report", title_id, timestamp));
+}
+
+void Reporter::SaveUnimplementedAppletReport(
+    u32 applet_id, u32 common_args_version, u32 library_version, u32 theme_color,
+    bool startup_sound, u64 system_tick, std::vector<std::vector<u8>> normal_channel,
+    std::vector<std::vector<u8>> interactive_channel) const {
+    if (!IsReportingEnabled())
+        return;
+
+    const auto timestamp{GetTimestamp()};
+    const auto title_id{Core::CurrentProcess()->GetTitleID()};
+    auto out = GetFullDataAuto(timestamp, title_id);
+
+    out["applet_common_args"] = {
+        {"applet_id", fmt::format("{:02X}", applet_id)},
+        {"common_args_version", fmt::format("{:08X}", common_args_version)},
+        {"library_version", fmt::format("{:08X}", library_version)},
+        {"theme_color", fmt::format("{:08X}", theme_color)},
+        {"startup_sound", fmt::format("{}", startup_sound)},
+        {"system_tick", fmt::format("{:016X}", system_tick)},
+    };
+
+    auto normal_out = json::array();
+    for (const auto& data : normal_channel) {
+        normal_out.push_back(Common::HexVectorToString(data));
+    }
+
+    auto interactive_out = json::array();
+    for (const auto& data : interactive_channel) {
+        interactive_out.push_back(Common::HexVectorToString(data));
+    }
+
+    out["applet_normal_data"] = std::move(normal_out);
+    out["applet_interactive_data"] = std::move(interactive_out);
+
+    SaveToFile(std::move(out), GetPath("unimpl_applet_report", title_id, timestamp));
+}
+
+void Reporter::SavePlayReport(u64 title_id, u64 unk1, std::vector<std::vector<u8>> data,
+                              std::optional<u128> user_id) const {
+    if (!IsReportingEnabled())
+        return;
+
+    const auto timestamp{GetTimestamp()};
+    json out;
+
+    out["yuzu_version"] = GetYuzuVersionData();
+    out["report_common"] = GetReportCommonData(title_id, RESULT_SUCCESS, timestamp, user_id);
+
+    auto data_out = json::array();
+    for (const auto& d : data) {
+        data_out.push_back(Common::HexVectorToString(d));
+    }
+
+    out["play_report_unk1"] = fmt::format("{:016X}", unk1);
+    out["play_report_data"] = std::move(data_out);
+
+    SaveToFile(std::move(out), GetPath("play_report", title_id, timestamp));
+}
+
+void Reporter::SaveErrorReport(u64 title_id, ResultCode result,
+                               std::optional<std::string> custom_text_main,
+                               std::optional<std::string> custom_text_detail) const {
+    if (!IsReportingEnabled())
+        return;
+
+    const auto timestamp{GetTimestamp()};
+    json out;
+
+    out["yuzu_version"] = GetYuzuVersionData();
+    out["report_common"] = GetReportCommonData(title_id, result, timestamp);
+    out["processor_state"] = GetProcessorStateDataAuto();
+    out["backtrace"] = GetBacktraceData();
+
+    out["error_custom_text"] = {
+        {"main", *custom_text_main},
+        {"detail", *custom_text_detail},
+    };
+
+    SaveToFile(std::move(out), GetPath("error_report", title_id, timestamp));
+}
+
+void Reporter::SaveUserReport() const {
+    if (!IsReportingEnabled())
+        return;
+
+    const auto timestamp{GetTimestamp()};
+    const auto title_id{Core::CurrentProcess()->GetTitleID()};
+
+    SaveToFile(GetFullDataAuto(timestamp, title_id), GetPath("user_report", title_id, timestamp));
+}
+
+bool Reporter::IsReportingEnabled() const {
+    return Settings::values.reporting_services;
+}
+
+} // namespace Core

+ 53 - 0
src/core/reporter.h

@@ -0,0 +1,53 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <optional>
+#include <vector>
+#include "common/common_types.h"
+#include "core/hle/result.h"
+
+namespace Kernel {
+class HLERequestContext;
+} // namespace Kernel
+
+namespace Core {
+
+class Reporter {
+public:
+    Reporter();
+    ~Reporter();
+
+    void SaveCrashReport(u64 title_id, ResultCode result, u64 set_flags, u64 entry_point, u64 sp,
+                         u64 pc, u64 pstate, u64 afsr0, u64 afsr1, u64 esr, u64 far,
+                         const std::array<u64, 31>& registers, const std::array<u64, 32>& backtrace,
+                         u32 backtrace_size, const std::string& arch, u32 unk10) const;
+
+    void SaveSvcBreakReport(u32 type, bool signal_debugger, u64 info1, u64 info2,
+                            std::optional<std::vector<u8>> resolved_buffer = {}) const;
+
+    void SaveUnimplementedFunctionReport(Kernel::HLERequestContext& ctx, u32 command_id,
+                                         const std::string& name,
+                                         const std::string& service_name) const;
+
+    void SaveUnimplementedAppletReport(u32 applet_id, u32 common_args_version, u32 library_version,
+                                       u32 theme_color, bool startup_sound, u64 system_tick,
+                                       std::vector<std::vector<u8>> normal_channel,
+                                       std::vector<std::vector<u8>> interactive_channel) const;
+
+    void SavePlayReport(u64 title_id, u64 unk1, std::vector<std::vector<u8>> data,
+                        std::optional<u128> user_id = {}) const;
+
+    void SaveErrorReport(u64 title_id, ResultCode result,
+                         std::optional<std::string> custom_text_main = {},
+                         std::optional<std::string> custom_text_detail = {}) const;
+
+    void SaveUserReport() const;
+
+private:
+    bool IsReportingEnabled() const;
+};
+
+} // namespace Core