diff options
Diffstat (limited to 'src/core/arm/debug.cpp')
-rw-r--r-- | src/core/arm/debug.cpp | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/src/core/arm/debug.cpp b/src/core/arm/debug.cpp new file mode 100644 index 000000000..af1c34bc3 --- /dev/null +++ b/src/core/arm/debug.cpp @@ -0,0 +1,354 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/demangle.h" +#include "core/arm/debug.h" +#include "core/arm/symbols.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_thread.h" +#include "core/memory.h" + +namespace Core { + +namespace { + +std::optional<std::string> GetNameFromThreadType64(Core::Memory::Memory& memory, + const Kernel::KThread& thread) { + // Read thread type from TLS + const VAddr tls_thread_type{memory.Read64(thread.GetTlsAddress() + 0x1f8)}; + const VAddr argument_thread_type{thread.GetArgument()}; + + if (argument_thread_type && tls_thread_type != argument_thread_type) { + // Probably not created by nnsdk, no name available. + return std::nullopt; + } + + if (!tls_thread_type) { + return std::nullopt; + } + + const u16 version{memory.Read16(tls_thread_type + 0x46)}; + VAddr name_pointer{}; + if (version == 1) { + name_pointer = memory.Read64(tls_thread_type + 0x1a0); + } else { + name_pointer = memory.Read64(tls_thread_type + 0x1a8); + } + + if (!name_pointer) { + // No name provided. + return std::nullopt; + } + + return memory.ReadCString(name_pointer, 256); +} + +std::optional<std::string> GetNameFromThreadType32(Core::Memory::Memory& memory, + const Kernel::KThread& thread) { + // Read thread type from TLS + const VAddr tls_thread_type{memory.Read32(thread.GetTlsAddress() + 0x1fc)}; + const VAddr argument_thread_type{thread.GetArgument()}; + + if (argument_thread_type && tls_thread_type != argument_thread_type) { + // Probably not created by nnsdk, no name available. + return std::nullopt; + } + + if (!tls_thread_type) { + return std::nullopt; + } + + const u16 version{memory.Read16(tls_thread_type + 0x26)}; + VAddr name_pointer{}; + if (version == 1) { + name_pointer = memory.Read32(tls_thread_type + 0xe4); + } else { + name_pointer = memory.Read32(tls_thread_type + 0xe8); + } + + if (!name_pointer) { + // No name provided. + return std::nullopt; + } + + return memory.ReadCString(name_pointer, 256); +} + +constexpr std::array<u64, 2> SegmentBases{ + 0x60000000ULL, + 0x7100000000ULL, +}; + +void SymbolicateBacktrace(const Kernel::KProcess* process, std::vector<BacktraceEntry>& out) { + auto modules = FindModules(process); + + const bool is_64 = process->Is64Bit(); + + std::map<std::string, Symbols::Symbols> symbols; + for (const auto& module : modules) { + symbols.insert_or_assign(module.second, + Symbols::GetSymbols(module.first, process->GetMemory(), is_64)); + } + + for (auto& entry : out) { + VAddr base = 0; + for (auto iter = modules.rbegin(); iter != modules.rend(); ++iter) { + const auto& module{*iter}; + if (entry.original_address >= module.first) { + entry.module = module.second; + base = module.first; + break; + } + } + + entry.offset = entry.original_address - base; + entry.address = SegmentBases[is_64] + entry.offset; + + if (entry.module.empty()) { + entry.module = "unknown"; + } + + const auto symbol_set = symbols.find(entry.module); + if (symbol_set != symbols.end()) { + const auto symbol = Symbols::GetSymbolName(symbol_set->second, entry.offset); + if (symbol) { + entry.name = Common::DemangleSymbol(*symbol); + } + } + } +} + +std::vector<BacktraceEntry> GetAArch64Backtrace(const Kernel::KProcess* process, + const Kernel::Svc::ThreadContext& ctx) { + std::vector<BacktraceEntry> out; + auto& memory = process->GetMemory(); + auto pc = ctx.pc, lr = ctx.lr, fp = ctx.fp; + + out.push_back({"", 0, pc, 0, ""}); + + // fp (= x29) points to the previous frame record. + // Frame records are two words long: + // fp+0 : pointer to previous frame record + // fp+8 : value of lr for frame + for (size_t i = 0; i < 256; i++) { + out.push_back({"", 0, lr, 0, ""}); + if (!fp || (fp % 4 != 0) || !memory.IsValidVirtualAddressRange(fp, 16)) { + break; + } + lr = memory.Read64(fp + 8); + fp = memory.Read64(fp); + } + + SymbolicateBacktrace(process, out); + + return out; +} + +std::vector<BacktraceEntry> GetAArch32Backtrace(const Kernel::KProcess* process, + const Kernel::Svc::ThreadContext& ctx) { + std::vector<BacktraceEntry> out; + auto& memory = process->GetMemory(); + auto pc = ctx.pc, lr = ctx.lr, fp = ctx.fp; + + out.push_back({"", 0, pc, 0, ""}); + + // fp (= r11) points to the last frame record. + // Frame records are two words long: + // fp+0 : pointer to previous frame record + // fp+4 : value of lr for frame + for (size_t i = 0; i < 256; i++) { + out.push_back({"", 0, lr, 0, ""}); + if (!fp || (fp % 4 != 0) || !memory.IsValidVirtualAddressRange(fp, 8)) { + break; + } + lr = memory.Read32(fp + 4); + fp = memory.Read32(fp); + } + + SymbolicateBacktrace(process, out); + + return out; +} + +} // namespace + +std::optional<std::string> GetThreadName(const Kernel::KThread* thread) { + const auto* process = thread->GetOwnerProcess(); + if (process->Is64Bit()) { + return GetNameFromThreadType64(process->GetMemory(), *thread); + } else { + return GetNameFromThreadType32(process->GetMemory(), *thread); + } +} + +std::string_view GetThreadWaitReason(const Kernel::KThread* thread) { + switch (thread->GetWaitReasonForDebugging()) { + case Kernel::ThreadWaitReasonForDebugging::Sleep: + return "Sleep"; + case Kernel::ThreadWaitReasonForDebugging::IPC: + return "IPC"; + case Kernel::ThreadWaitReasonForDebugging::Synchronization: + return "Synchronization"; + case Kernel::ThreadWaitReasonForDebugging::ConditionVar: + return "ConditionVar"; + case Kernel::ThreadWaitReasonForDebugging::Arbitration: + return "Arbitration"; + case Kernel::ThreadWaitReasonForDebugging::Suspended: + return "Suspended"; + default: + return "Unknown"; + } +} + +std::string GetThreadState(const Kernel::KThread* thread) { + switch (thread->GetState()) { + case Kernel::ThreadState::Initialized: + return "Initialized"; + case Kernel::ThreadState::Waiting: + return fmt::format("Waiting ({})", GetThreadWaitReason(thread)); + case Kernel::ThreadState::Runnable: + return "Runnable"; + case Kernel::ThreadState::Terminated: + return "Terminated"; + default: + return "Unknown"; + } +} + +Kernel::KProcessAddress GetModuleEnd(const Kernel::KProcess* process, + Kernel::KProcessAddress base) { + Kernel::KMemoryInfo mem_info; + Kernel::Svc::MemoryInfo svc_mem_info; + Kernel::Svc::PageInfo page_info; + VAddr cur_addr{GetInteger(base)}; + auto& page_table = process->GetPageTable(); + + // Expect: r-x Code (.text) + R_ASSERT(page_table.QueryInfo(std::addressof(mem_info), std::addressof(page_info), cur_addr)); + svc_mem_info = mem_info.GetSvcMemoryInfo(); + cur_addr = svc_mem_info.base_address + svc_mem_info.size; + if (svc_mem_info.state != Kernel::Svc::MemoryState::Code || + svc_mem_info.permission != Kernel::Svc::MemoryPermission::ReadExecute) { + return cur_addr - 1; + } + + // Expect: r-- Code (.rodata) + R_ASSERT(page_table.QueryInfo(std::addressof(mem_info), std::addressof(page_info), cur_addr)); + svc_mem_info = mem_info.GetSvcMemoryInfo(); + cur_addr = svc_mem_info.base_address + svc_mem_info.size; + if (svc_mem_info.state != Kernel::Svc::MemoryState::Code || + svc_mem_info.permission != Kernel::Svc::MemoryPermission::Read) { + return cur_addr - 1; + } + + // Expect: rw- CodeData (.data) + R_ASSERT(page_table.QueryInfo(std::addressof(mem_info), std::addressof(page_info), cur_addr)); + svc_mem_info = mem_info.GetSvcMemoryInfo(); + cur_addr = svc_mem_info.base_address + svc_mem_info.size; + return cur_addr - 1; +} + +Loader::AppLoader::Modules FindModules(const Kernel::KProcess* process) { + Loader::AppLoader::Modules modules; + + auto& page_table = process->GetPageTable(); + auto& memory = process->GetMemory(); + VAddr cur_addr = 0; + + // Look for executable sections in Code or AliasCode regions. + while (true) { + Kernel::KMemoryInfo mem_info{}; + Kernel::Svc::PageInfo page_info{}; + R_ASSERT( + page_table.QueryInfo(std::addressof(mem_info), std::addressof(page_info), cur_addr)); + auto svc_mem_info = mem_info.GetSvcMemoryInfo(); + + if (svc_mem_info.permission == Kernel::Svc::MemoryPermission::ReadExecute && + (svc_mem_info.state == Kernel::Svc::MemoryState::Code || + svc_mem_info.state == Kernel::Svc::MemoryState::AliasCode)) { + // Try to read the module name from its path. + constexpr s32 PathLengthMax = 0x200; + struct { + u32 zero; + s32 path_length; + std::array<char, PathLengthMax> path; + } module_path; + + if (memory.ReadBlock(svc_mem_info.base_address + svc_mem_info.size, &module_path, + sizeof(module_path))) { + if (module_path.zero == 0 && module_path.path_length > 0) { + // Truncate module name. + module_path.path[PathLengthMax - 1] = '\0'; + + // Ignore leading directories. + char* path_pointer = module_path.path.data(); + char* path_end = + path_pointer + std::min(PathLengthMax, module_path.path_length); + + for (s32 i = 0; i < std::min(PathLengthMax, module_path.path_length) && + module_path.path[i] != '\0'; + i++) { + if (module_path.path[i] == '/' || module_path.path[i] == '\\') { + path_pointer = module_path.path.data() + i + 1; + } + } + + // Insert output. + modules.emplace(svc_mem_info.base_address, + std::string_view(path_pointer, path_end)); + } + } + } + + // Check if we're done. + const uintptr_t next_address = svc_mem_info.base_address + svc_mem_info.size; + if (next_address <= cur_addr) { + break; + } + + cur_addr = next_address; + } + + return modules; +} + +Kernel::KProcessAddress FindMainModuleEntrypoint(const Kernel::KProcess* process) { + // Do we have any loaded executable sections? + auto modules = FindModules(process); + + if (modules.size() >= 2) { + // If we have two or more, the first one is rtld and the second is main. + return std::next(modules.begin())->first; + } else if (!modules.empty()) { + // If we only have one, this is the main module. + return modules.begin()->first; + } + + // As a last resort, use the start of the code region. + return GetInteger(process->GetPageTable().GetCodeRegionStart()); +} + +void InvalidateInstructionCacheRange(const Kernel::KProcess* process, u64 address, u64 size) { + for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { + auto* interface = process->GetArmInterface(i); + if (interface) { + interface->InvalidateCacheRange(address, size); + } + } +} + +std::vector<BacktraceEntry> GetBacktraceFromContext(const Kernel::KProcess* process, + const Kernel::Svc::ThreadContext& ctx) { + if (process->Is64Bit()) { + return GetAArch64Backtrace(process, ctx); + } else { + return GetAArch32Backtrace(process, ctx); + } +} + +std::vector<BacktraceEntry> GetBacktrace(const Kernel::KThread* thread) { + Kernel::Svc::ThreadContext ctx = thread->GetContext(); + return GetBacktraceFromContext(thread->GetOwnerProcess(), ctx); +} + +} // namespace Core |