summaryrefslogtreecommitdiff
path: root/src/core/reporter.cpp
diff options
context:
space:
mode:
authorGravatar bunnei2019-06-21 14:05:18 -0400
committerGravatar GitHub2019-06-21 14:05:18 -0400
commit96412848a9db0643198ea882824688f23dc19606 (patch)
tree2824eafaf4bc026cc3fc0ee498d1c5c623f3aa65 /src/core/reporter.cpp
parentMerge pull request #2291 from DarkLordZach/homebrew-testing (diff)
parentloader: Move NSO module tracking to AppLoader (diff)
downloadyuzu-96412848a9db0643198ea882824688f23dc19606.tar.gz
yuzu-96412848a9db0643198ea882824688f23dc19606.tar.xz
yuzu-96412848a9db0643198ea882824688f23dc19606.zip
Merge pull request #2482 from DarkLordZach/prepo
core: Add detailed local reporting feature for development
Diffstat (limited to 'src/core/reporter.cpp')
-rw-r--r--src/core/reporter.cpp353
1 files changed, 353 insertions, 0 deletions
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp
new file mode 100644
index 000000000..8fe621aa0
--- /dev/null
+++ b/src/core/reporter.cpp
@@ -0,0 +1,353 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <fstream>
6#include <json.hpp>
7#include "common/file_util.h"
8#include "common/hex_util.h"
9#include "common/scm_rev.h"
10#include "core/arm/arm_interface.h"
11#include "core/core.h"
12#include "core/hle/kernel/hle_ipc.h"
13#include "core/hle/kernel/process.h"
14#include "core/hle/result.h"
15#include "core/reporter.h"
16#include "core/settings.h"
17#include "fmt/time.h"
18
19namespace {
20
21std::string GetPath(std::string_view type, u64 title_id, std::string_view timestamp) {
22 return fmt::format("{}{}/{:016X}_{}.json", FileUtil::GetUserPath(FileUtil::UserPath::LogDir),
23 type, title_id, timestamp);
24}
25
26std::string GetTimestamp() {
27 const auto time = std::time(nullptr);
28 return fmt::format("{:%FT%H-%M-%S}", *std::localtime(&time));
29}
30
31using namespace nlohmann;
32
33void SaveToFile(const json& json, const std::string& filename) {
34 if (!FileUtil::CreateFullPath(filename))
35 LOG_ERROR(Core, "Failed to create path for '{}' to save report!", filename);
36
37 std::ofstream file(
38 FileUtil::SanitizePath(filename, FileUtil::DirectorySeparator::PlatformDefault));
39 file << std::setw(4) << json << std::endl;
40}
41
42json GetYuzuVersionData() {
43 return {
44 {"scm_rev", std::string(Common::g_scm_rev)},
45 {"scm_branch", std::string(Common::g_scm_branch)},
46 {"scm_desc", std::string(Common::g_scm_desc)},
47 {"build_name", std::string(Common::g_build_name)},
48 {"build_date", std::string(Common::g_build_date)},
49 {"build_fullname", std::string(Common::g_build_fullname)},
50 {"build_version", std::string(Common::g_build_version)},
51 {"shader_cache_version", std::string(Common::g_shader_cache_version)},
52 };
53}
54
55json GetReportCommonData(u64 title_id, ResultCode result, const std::string& timestamp,
56 std::optional<u128> user_id = {}) {
57 auto out = json{
58 {"title_id", fmt::format("{:016X}", title_id)},
59 {"result_raw", fmt::format("{:08X}", result.raw)},
60 {"result_module", fmt::format("{:08X}", static_cast<u32>(result.module.Value()))},
61 {"result_description", fmt::format("{:08X}", result.description.Value())},
62 {"timestamp", timestamp},
63 };
64 if (user_id.has_value())
65 out["user_id"] = fmt::format("{:016X}{:016X}", (*user_id)[1], (*user_id)[0]);
66 return out;
67}
68
69json GetProcessorStateData(const std::string& architecture, u64 entry_point, u64 sp, u64 pc,
70 u64 pstate, std::array<u64, 31> registers,
71 std::optional<std::array<u64, 32>> backtrace = {}) {
72 auto out = json{
73 {"entry_point", fmt::format("{:016X}", entry_point)},
74 {"sp", fmt::format("{:016X}", sp)},
75 {"pc", fmt::format("{:016X}", pc)},
76 {"pstate", fmt::format("{:016X}", pstate)},
77 {"architecture", architecture},
78 };
79
80 auto registers_out = json::object();
81 for (std::size_t i = 0; i < registers.size(); ++i) {
82 registers_out[fmt::format("X{:02d}", i)] = fmt::format("{:016X}", registers[i]);
83 }
84
85 out["registers"] = std::move(registers_out);
86
87 if (backtrace.has_value()) {
88 auto backtrace_out = json::array();
89 for (const auto& entry : *backtrace) {
90 backtrace_out.push_back(fmt::format("{:016X}", entry));
91 }
92 out["backtrace"] = std::move(backtrace_out);
93 }
94
95 return out;
96}
97
98json GetProcessorStateDataAuto(Core::System& system) {
99 const auto* process{system.CurrentProcess()};
100 const auto& vm_manager{process->VMManager()};
101 auto& arm{system.CurrentArmInterface()};
102
103 Core::ARM_Interface::ThreadContext context{};
104 arm.SaveContext(context);
105
106 return GetProcessorStateData(process->Is64BitProcess() ? "AArch64" : "AArch32",
107 vm_manager.GetCodeRegionBaseAddress(), context.sp, context.pc,
108 context.pstate, context.cpu_registers);
109}
110
111json GetBacktraceData(Core::System& system) {
112 auto out = json::array();
113 const auto& backtrace{system.CurrentArmInterface().GetBacktrace()};
114 for (const auto& entry : backtrace) {
115 out.push_back({
116 {"module", entry.module},
117 {"address", fmt::format("{:016X}", entry.address)},
118 {"original_address", fmt::format("{:016X}", entry.original_address)},
119 {"offset", fmt::format("{:016X}", entry.offset)},
120 {"symbol_name", entry.name},
121 });
122 }
123
124 return out;
125}
126
127json GetFullDataAuto(const std::string& timestamp, u64 title_id, Core::System& system) {
128 json out;
129
130 out["yuzu_version"] = GetYuzuVersionData();
131 out["report_common"] = GetReportCommonData(title_id, RESULT_SUCCESS, timestamp);
132 out["processor_state"] = GetProcessorStateDataAuto(system);
133 out["backtrace"] = GetBacktraceData(system);
134
135 return out;
136}
137
138template <bool read_value, typename DescriptorType>
139json GetHLEBufferDescriptorData(const std::vector<DescriptorType>& buffer) {
140 auto buffer_out = json::array();
141 for (const auto& desc : buffer) {
142 auto entry = json{
143 {"address", fmt::format("{:016X}", desc.Address())},
144 {"size", fmt::format("{:016X}", desc.Size())},
145 };
146
147 if constexpr (read_value) {
148 std::vector<u8> data(desc.Size());
149 Memory::ReadBlock(desc.Address(), data.data(), desc.Size());
150 entry["data"] = Common::HexVectorToString(data);
151 }
152
153 buffer_out.push_back(std::move(entry));
154 }
155
156 return buffer_out;
157}
158
159json GetHLERequestContextData(Kernel::HLERequestContext& ctx) {
160 json out;
161
162 auto cmd_buf = json::array();
163 for (std::size_t i = 0; i < IPC::COMMAND_BUFFER_LENGTH; ++i) {
164 cmd_buf.push_back(fmt::format("{:08X}", ctx.CommandBuffer()[i]));
165 }
166
167 out["command_buffer"] = std::move(cmd_buf);
168
169 out["buffer_descriptor_a"] = GetHLEBufferDescriptorData<true>(ctx.BufferDescriptorA());
170 out["buffer_descriptor_b"] = GetHLEBufferDescriptorData<false>(ctx.BufferDescriptorB());
171 out["buffer_descriptor_c"] = GetHLEBufferDescriptorData<false>(ctx.BufferDescriptorC());
172 out["buffer_descriptor_x"] = GetHLEBufferDescriptorData<true>(ctx.BufferDescriptorX());
173
174 return std::move(out);
175}
176
177} // Anonymous namespace
178
179namespace Core {
180
181Reporter::Reporter(Core::System& system) : system(system) {}
182
183Reporter::~Reporter() = default;
184
185void Reporter::SaveCrashReport(u64 title_id, ResultCode result, u64 set_flags, u64 entry_point,
186 u64 sp, u64 pc, u64 pstate, u64 afsr0, u64 afsr1, u64 esr, u64 far,
187 const std::array<u64, 31>& registers,
188 const std::array<u64, 32>& backtrace, u32 backtrace_size,
189 const std::string& arch, u32 unk10) const {
190 if (!IsReportingEnabled())
191 return;
192
193 const auto timestamp = GetTimestamp();
194 json out;
195
196 out["yuzu_version"] = GetYuzuVersionData();
197 out["report_common"] = GetReportCommonData(title_id, result, timestamp);
198
199 auto proc_out = GetProcessorStateData(arch, entry_point, sp, pc, pstate, registers, backtrace);
200 proc_out["set_flags"] = fmt::format("{:016X}", set_flags);
201 proc_out["afsr0"] = fmt::format("{:016X}", afsr0);
202 proc_out["afsr1"] = fmt::format("{:016X}", afsr1);
203 proc_out["esr"] = fmt::format("{:016X}", esr);
204 proc_out["far"] = fmt::format("{:016X}", far);
205 proc_out["backtrace_size"] = fmt::format("{:08X}", backtrace_size);
206 proc_out["unknown_10"] = fmt::format("{:08X}", unk10);
207
208 out["processor_state"] = std::move(proc_out);
209
210 SaveToFile(std::move(out), GetPath("crash_report", title_id, timestamp));
211}
212
213void Reporter::SaveSvcBreakReport(u32 type, bool signal_debugger, u64 info1, u64 info2,
214 std::optional<std::vector<u8>> resolved_buffer) const {
215 if (!IsReportingEnabled())
216 return;
217
218 const auto timestamp = GetTimestamp();
219 const auto title_id = system.CurrentProcess()->GetTitleID();
220 auto out = GetFullDataAuto(timestamp, title_id, system);
221
222 auto break_out = json{
223 {"type", fmt::format("{:08X}", type)},
224 {"signal_debugger", fmt::format("{}", signal_debugger)},
225 {"info1", fmt::format("{:016X}", info1)},
226 {"info2", fmt::format("{:016X}", info2)},
227 };
228
229 if (resolved_buffer.has_value()) {
230 break_out["debug_buffer"] = Common::HexVectorToString(*resolved_buffer);
231 }
232
233 out["svc_break"] = std::move(break_out);
234
235 SaveToFile(std::move(out), GetPath("svc_break_report", title_id, timestamp));
236}
237
238void Reporter::SaveUnimplementedFunctionReport(Kernel::HLERequestContext& ctx, u32 command_id,
239 const std::string& name,
240 const std::string& service_name) const {
241 if (!IsReportingEnabled())
242 return;
243
244 const auto timestamp = GetTimestamp();
245 const auto title_id = system.CurrentProcess()->GetTitleID();
246 auto out = GetFullDataAuto(timestamp, title_id, system);
247
248 auto function_out = GetHLERequestContextData(ctx);
249 function_out["command_id"] = command_id;
250 function_out["function_name"] = name;
251 function_out["service_name"] = service_name;
252
253 out["function"] = std::move(function_out);
254
255 SaveToFile(std::move(out), GetPath("unimpl_func_report", title_id, timestamp));
256}
257
258void Reporter::SaveUnimplementedAppletReport(
259 u32 applet_id, u32 common_args_version, u32 library_version, u32 theme_color,
260 bool startup_sound, u64 system_tick, std::vector<std::vector<u8>> normal_channel,
261 std::vector<std::vector<u8>> interactive_channel) const {
262 if (!IsReportingEnabled())
263 return;
264
265 const auto timestamp = GetTimestamp();
266 const auto title_id = system.CurrentProcess()->GetTitleID();
267 auto out = GetFullDataAuto(timestamp, title_id, system);
268
269 out["applet_common_args"] = {
270 {"applet_id", fmt::format("{:02X}", applet_id)},
271 {"common_args_version", fmt::format("{:08X}", common_args_version)},
272 {"library_version", fmt::format("{:08X}", library_version)},
273 {"theme_color", fmt::format("{:08X}", theme_color)},
274 {"startup_sound", fmt::format("{}", startup_sound)},
275 {"system_tick", fmt::format("{:016X}", system_tick)},
276 };
277
278 auto normal_out = json::array();
279 for (const auto& data : normal_channel) {
280 normal_out.push_back(Common::HexVectorToString(data));
281 }
282
283 auto interactive_out = json::array();
284 for (const auto& data : interactive_channel) {
285 interactive_out.push_back(Common::HexVectorToString(data));
286 }
287
288 out["applet_normal_data"] = std::move(normal_out);
289 out["applet_interactive_data"] = std::move(interactive_out);
290
291 SaveToFile(std::move(out), GetPath("unimpl_applet_report", title_id, timestamp));
292}
293
294void Reporter::SavePlayReport(u64 title_id, u64 process_id, std::vector<std::vector<u8>> data,
295 std::optional<u128> user_id) const {
296 if (!IsReportingEnabled())
297 return;
298
299 const auto timestamp = GetTimestamp();
300 json out;
301
302 out["yuzu_version"] = GetYuzuVersionData();
303 out["report_common"] = GetReportCommonData(title_id, RESULT_SUCCESS, timestamp, user_id);
304
305 auto data_out = json::array();
306 for (const auto& d : data) {
307 data_out.push_back(Common::HexVectorToString(d));
308 }
309
310 out["play_report_process_id"] = fmt::format("{:016X}", process_id);
311 out["play_report_data"] = std::move(data_out);
312
313 SaveToFile(std::move(out), GetPath("play_report", title_id, timestamp));
314}
315
316void Reporter::SaveErrorReport(u64 title_id, ResultCode result,
317 std::optional<std::string> custom_text_main,
318 std::optional<std::string> custom_text_detail) const {
319 if (!IsReportingEnabled())
320 return;
321
322 const auto timestamp = GetTimestamp();
323 json out;
324
325 out["yuzu_version"] = GetYuzuVersionData();
326 out["report_common"] = GetReportCommonData(title_id, result, timestamp);
327 out["processor_state"] = GetProcessorStateDataAuto(system);
328 out["backtrace"] = GetBacktraceData(system);
329
330 out["error_custom_text"] = {
331 {"main", *custom_text_main},
332 {"detail", *custom_text_detail},
333 };
334
335 SaveToFile(std::move(out), GetPath("error_report", title_id, timestamp));
336}
337
338void Reporter::SaveUserReport() const {
339 if (!IsReportingEnabled())
340 return;
341
342 const auto timestamp = GetTimestamp();
343 const auto title_id = system.CurrentProcess()->GetTitleID();
344
345 SaveToFile(GetFullDataAuto(timestamp, title_id, system),
346 GetPath("user_report", title_id, timestamp));
347}
348
349bool Reporter::IsReportingEnabled() const {
350 return Settings::values.reporting_services;
351}
352
353} // namespace Core