From d81579ba51a1c581f37744ee7f2a8953d99a4d1f Mon Sep 17 00:00:00 2001 From: xbzk Date: Sun, 23 Aug 2026 13:56:19 -0300 Subject: [PATCH] [core] Add compact guest backtrace decoder --- src/core/arm/arm_interface.cpp | 196 ++++++++++++++++++++++++++++++--- 1 file changed, 182 insertions(+), 14 deletions(-) diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp index b7de9fc0f8..e2ad5db2bb 100644 --- a/src/core/arm/arm_interface.cpp +++ b/src/core/arm/arm_interface.cpp @@ -3,14 +3,158 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include +#include + #include "common/logging.h" #include "core/arm/arm_interface.h" #include "core/arm/debug.h" #include "core/core.h" +#include "core/hle/kernel/k_memory_block.h" #include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/svc_types.h" namespace Core { +namespace { + +constexpr std::size_t GuestStringProbeBytes = 0x100; +constexpr std::size_t StackProbeWords = 96; +constexpr std::size_t MaxGuestStringLogs = 16; +constexpr std::size_t MaxBacktraceFrames = 64; +constexpr std::size_t MinGuestStringLength = 4; + +std::string SanitizeGuestString(std::string_view text) { + std::string sanitized; + sanitized.reserve(text.size()); + + for (const char ch : text) { + switch (ch) { + case '\\': + sanitized += "\\\\"; + break; + case '"': + sanitized += "\\\""; + break; + default: + sanitized += ch; + break; + } + } + + return sanitized; +} + +bool IsUsefulGuestString(std::string_view text) { + if (text.size() < MinGuestStringLength) { + return false; + } + + std::size_t alpha_numeric_count{}; + for (const char ch : text) { + const auto byte = static_cast(ch); + if (std::isprint(byte) == 0) { + return false; + } + + if (std::isalnum(byte) != 0) { + alpha_numeric_count++; + } + } + + return alpha_numeric_count > 0; +} + +bool QueryGuestMemoryInfo(Kernel::KProcess* process, u64 address, + Kernel::Svc::MemoryInfo* out_info) { + if (address == 0) { + return false; + } + + Kernel::KMemoryInfo mem_info{}; + Kernel::Svc::PageInfo page_info{}; + if (process->GetPageTable().QueryInfo(&mem_info, &page_info, address).IsFailure()) { + return false; + } + + *out_info = mem_info.GetSvcMemoryInfo(); + return true; +} + +void LogGuestStringCandidate(Kernel::KProcess* process, u64 address, std::string_view label, + std::vector& logged_strings) { + if (logged_strings.size() >= MaxGuestStringLogs) { + return; + } + if (std::find(logged_strings.begin(), logged_strings.end(), address) != logged_strings.end()) { + return; + } + + Kernel::Svc::MemoryInfo mem_info{}; + if (!QueryGuestMemoryInfo(process, address, &mem_info)) { + return; + } + if (mem_info.state == Kernel::Svc::MemoryState::Free || + mem_info.permission == Kernel::Svc::MemoryPermission::None) { + return; + } + + if (address < mem_info.base_address) { + return; + } + + const u64 region_offset = address - mem_info.base_address; + if (region_offset >= mem_info.size) { + return; + } + + const u64 available_bytes = mem_info.size - region_offset; + const auto probe_size = + static_cast(std::min(GuestStringProbeBytes, available_bytes)); + if (probe_size < MinGuestStringLength || + !process->GetMemory().IsValidVirtualAddressRange(address, probe_size)) { + return; + } + + const auto text = process->GetMemory().ReadCString(address, probe_size); + if (!IsUsefulGuestString(text)) { + return; + } + + logged_strings.push_back(address); + LOG_ERROR(Core_ARM, "Guest backtrace string {:02}: {}={:016X} \"{}\"", + logged_strings.size() - 1, label, address, SanitizeGuestString(text)); +} + +void LogStackStringCandidates(Kernel::KProcess* process, u64 base, std::string_view label, + std::vector& logged_strings) { + if (base == 0) { + return; + } + + auto& memory = process->GetMemory(); + for (std::size_t i = 0; i < StackProbeWords; i++) { + const u64 address = base + i * sizeof(u64); + if (!memory.IsValidVirtualAddressRange(address, sizeof(u64))) { + break; + } + + u64 value{}; + if (!memory.ReadBlock(address, &value, sizeof(value))) { + continue; + } + + LogGuestStringCandidate(process, value, fmt::format("{}+{:03X}", label, i * sizeof(u64)), + logged_strings); + } +} + +} // namespace + void ArmInterface::LogBacktrace(Kernel::KProcess* process) const { Kernel::Svc::ThreadContext ctx; this->GetContext(ctx); @@ -26,21 +170,45 @@ void ArmInterface::LogBacktrace(Kernel::KProcess* process) const { ctx.r[28], ctx.fp, ctx.lr, ctx.sp, }; - std::string msg = fmt::format("Backtrace @ PC={:016X}\n", ctx.pc); - for (size_t i = 0; i < 32; i += 4) - msg += fmt::format("R{:02}={:016X} R{:02}={:016X} R{:02}={:016X} R{:02}={:016X}\n", - i + 0, xreg[i + 0], i + 1, xreg[i + 1], - i + 2, xreg[i + 2], i + 3, xreg[i + 3]); - for (size_t i = 0; i < 32; i += 2) - msg += fmt::format("V{:02}={:016X}_{:016X} V{:02}={:016X}_{:016X}\n", - i + 0, ctx.v[i + 0][0], ctx.v[i + 0][1], - i + 1, ctx.v[i + 1][0], ctx.v[i + 1][1]); - msg += fmt::format("PSTATE={:08X} FPCR={:08X} FPSR={:08X} TPIDR={:016X}\n", ctx.pstate, ctx.fpcr, ctx.fpsr, ctx.tpidr); - msg += fmt::format("{:20}{:20}{:20}{:20}{}\n", "Module", "Address", "Original Address", "Offset", "Symbol"); + LOG_ERROR(Core_ARM, + "Guest backtrace context: process=\"{}\" pid={} program_id={:016X} pc={:016X} " + "lr={:016X} fp={:016X} sp={:016X} pstate={:08X}", + process->GetName(), process->GetProcessId(), process->GetProgramId(), ctx.pc, + ctx.lr, ctx.fp, ctx.sp, ctx.pstate); + + std::vector logged_strings; + for (size_t i = 0; i < xreg.size(); i++) { + LogGuestStringCandidate(process, xreg[i], fmt::format("R{:02}", i), logged_strings); + } + LogStackStringCandidates(process, ctx.sp, "SP", logged_strings); + if (ctx.fp != ctx.sp) { + LogStackStringCandidates(process, ctx.fp, "FP", logged_strings); + } + if (logged_strings.empty()) { + LOG_ERROR(Core_ARM, "Guest backtrace strings: none found in registers or stack"); + } + auto const backtrace = GetBacktraceFromContext(process, ctx); - for (auto const& entry : backtrace) - msg += fmt::format("{:20}{:016X} {:016X} {:016X} {}\n", entry.module, entry.address, entry.original_address, entry.offset, entry.name); - LOG_ERROR(Core_ARM, "{}", msg); + for (size_t i = 0; i < std::min(backtrace.size(), MaxBacktraceFrames); i++) { + const auto& entry = backtrace[i]; + if (entry.original_address == 0) { + break; + } + + if (entry.name.empty()) { + LOG_ERROR(Core_ARM, "Guest backtrace frame {:03}: {}+0x{:X} pc={:016X} mapped={:016X}", + i, entry.module, entry.offset, entry.original_address, entry.address); + } else { + LOG_ERROR(Core_ARM, + "Guest backtrace frame {:03}: {}+0x{:X} pc={:016X} mapped={:016X} symbol={}", + i, entry.module, entry.offset, entry.original_address, entry.address, + entry.name); + } + } + if (backtrace.size() > MaxBacktraceFrames) { + LOG_ERROR(Core_ARM, "Guest backtrace truncated: logged={} total={}", MaxBacktraceFrames, + backtrace.size()); + } } const Kernel::DebugWatchpoint* ArmInterface::MatchingWatchpoint(