aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/core/arm/debug.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/arm/debug.cpp')
-rw-r--r--src/core/arm/debug.cpp351
1 files changed, 351 insertions, 0 deletions
diff --git a/src/core/arm/debug.cpp b/src/core/arm/debug.cpp
new file mode 100644
index 000000000..1fe37b8ee
--- /dev/null
+++ b/src/core/arm/debug.cpp
@@ -0,0 +1,351 @@
+// 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();
+
+ 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, path_pointer);
+ }
+ }
+ }
+
+ // 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