From 0fd7e1d7c250f3cc957c80011f9427ca4a67902f Mon Sep 17 00:00:00 2001 From: chaoticgd <43898262+chaoticgd@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:03:12 +0100 Subject: [PATCH] Debugger: Split out SymbolImporter into its own class --- .../Debugger/SymbolTree/SymbolTreeDelegates.h | 1 + .../Debugger/SymbolTree/SymbolTreeWidgets.cpp | 2 +- pcsx2/CMakeLists.txt | 2 + pcsx2/DebugTools/DebugInterface.cpp | 10 + pcsx2/DebugTools/DebugInterface.h | 16 +- pcsx2/DebugTools/SymbolGuardian.cpp | 400 +----------------- pcsx2/DebugTools/SymbolGuardian.h | 43 +- pcsx2/DebugTools/SymbolImporter.cpp | 397 +++++++++++++++++ pcsx2/DebugTools/SymbolImporter.h | 59 +++ pcsx2/VMManager.cpp | 25 +- pcsx2/pcsx2.vcxproj | 2 + pcsx2/pcsx2.vcxproj.filters | 6 + 12 files changed, 516 insertions(+), 447 deletions(-) create mode 100644 pcsx2/DebugTools/SymbolImporter.cpp create mode 100644 pcsx2/DebugTools/SymbolImporter.h diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.h b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.h index 03028297c5..d0aee3a4c3 100644 --- a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.h +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.h @@ -5,6 +5,7 @@ #include +#include "DebugTools/DebugInterface.h" #include "DebugTools/SymbolGuardian.h" class SymbolTreeValueDelegate : public QStyledItemDelegate diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp index f658157118..9b8b5f8d1d 100644 --- a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp @@ -61,7 +61,7 @@ void SymbolTreeWidget::reset() m_ui.treeView->setColumnHidden(SymbolTreeModel::SIZE, !m_show_size_column || !m_show_size_column->isChecked()); - m_cpu.GetSymbolGuardian().UpdateFunctionHashes(m_cpu); + m_cpu.GetSymbolImporter().UpdateFunctionHashes(m_cpu); SymbolFilters filters; std::unique_ptr root; diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index 0a229bd80e..804ab2e406 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -789,6 +789,7 @@ set(pcsx2DebugToolsSources DebugTools/MipsStackWalk.cpp DebugTools/Breakpoints.cpp DebugTools/SymbolGuardian.cpp + DebugTools/SymbolImporter.cpp DebugTools/DisR3000A.cpp DebugTools/DisR5900asm.cpp DebugTools/DisVU0Micro.cpp @@ -806,6 +807,7 @@ set(pcsx2DebugToolsHeaders DebugTools/MipsStackWalk.h DebugTools/Breakpoints.h DebugTools/SymbolGuardian.h + DebugTools/SymbolImporter.h DebugTools/Debug.h DebugTools/DisASM.h DebugTools/DisVUmicro.h diff --git a/pcsx2/DebugTools/DebugInterface.cpp b/pcsx2/DebugTools/DebugInterface.cpp index 951997bd81..222c54110f 100644 --- a/pcsx2/DebugTools/DebugInterface.cpp +++ b/pcsx2/DebugTools/DebugInterface.cpp @@ -894,6 +894,11 @@ SymbolGuardian& R5900DebugInterface::GetSymbolGuardian() const return R5900SymbolGuardian; } +SymbolImporter& R5900DebugInterface::GetSymbolImporter() const +{ + return R5900SymbolImporter; +} + std::vector> R5900DebugInterface::GetThreadList() const { return getEEThreads(); @@ -1213,6 +1218,11 @@ SymbolGuardian& R3000DebugInterface::GetSymbolGuardian() const return R3000SymbolGuardian; } +SymbolImporter& R3000DebugInterface::GetSymbolImporter() const +{ + return R3000SymbolImporter; +} + std::vector> R3000DebugInterface::GetThreadList() const { return getIOPThreads(); diff --git a/pcsx2/DebugTools/DebugInterface.h b/pcsx2/DebugTools/DebugInterface.h index ac793aa92a..13b200d41c 100644 --- a/pcsx2/DebugTools/DebugInterface.h +++ b/pcsx2/DebugTools/DebugInterface.h @@ -6,6 +6,7 @@ #include "MemoryTypes.h" #include "ExpressionParser.h" #include "SymbolGuardian.h" +#include "SymbolImporter.h" #include @@ -81,8 +82,9 @@ public: virtual bool isValidAddress(u32 address) = 0; virtual u32 getCycles() = 0; virtual BreakPointCpu getCpuType() = 0; - [[nodiscard]] virtual SymbolGuardian& GetSymbolGuardian() const = 0; - [[nodiscard]] virtual std::vector> GetThreadList() const = 0; + virtual SymbolGuardian& GetSymbolGuardian() const = 0; + virtual SymbolImporter& GetSymbolImporter() const = 0; + virtual std::vector> GetThreadList() const = 0; bool evaluateExpression(const char* expression, u64& dest); bool initExpression(const char* exp, PostfixExpression& dest); @@ -136,8 +138,9 @@ public: bool getCPCOND0() override; void setPc(u32 newPc) override; void setRegister(int cat, int num, u128 newValue) override; - [[nodiscard]] SymbolGuardian& GetSymbolGuardian() const override; - [[nodiscard]] std::vector> GetThreadList() const override; + SymbolGuardian& GetSymbolGuardian() const override; + SymbolImporter& GetSymbolImporter() const override; + std::vector> GetThreadList() const override; std::string disasm(u32 address, bool simplify) override; bool isValidAddress(u32 address) override; @@ -178,8 +181,9 @@ public: bool getCPCOND0() override; void setPc(u32 newPc) override; void setRegister(int cat, int num, u128 newValue) override; - [[nodiscard]] SymbolGuardian& GetSymbolGuardian() const override; - [[nodiscard]] std::vector> GetThreadList() const override; + SymbolGuardian& GetSymbolGuardian() const override; + SymbolImporter& GetSymbolImporter() const override; + std::vector> GetThreadList() const override; std::string disasm(u32 address, bool simplify) override; bool isValidAddress(u32 address) override; diff --git a/pcsx2/DebugTools/SymbolGuardian.cpp b/pcsx2/DebugTools/SymbolGuardian.cpp index 10c35bfa7c..6c2ddb78dd 100644 --- a/pcsx2/DebugTools/SymbolGuardian.cpp +++ b/pcsx2/DebugTools/SymbolGuardian.cpp @@ -3,402 +3,19 @@ #include "SymbolGuardian.h" -#include -#include -#include -#include - -#include "common/Console.h" -#include "common/FileSystem.h" -#include "common/StringUtil.h" -#include "common/Threading.h" - -#include "DebugInterface.h" -#include "MIPSAnalyst.h" -#include "Host.h" -#include "VMManager.h" - -#include - SymbolGuardian R5900SymbolGuardian; SymbolGuardian R3000SymbolGuardian; -struct DefaultBuiltInType -{ - const char* name; - ccc::ast::BuiltInClass bclass; -}; - -static const std::vector DEFAULT_BUILT_IN_TYPES = { - {"char", ccc::ast::BuiltInClass::UNQUALIFIED_8}, - {"signed char", ccc::ast::BuiltInClass::SIGNED_8}, - {"unsigned char", ccc::ast::BuiltInClass::UNSIGNED_8}, - {"short", ccc::ast::BuiltInClass::SIGNED_16}, - {"unsigned short", ccc::ast::BuiltInClass::UNSIGNED_16}, - {"int", ccc::ast::BuiltInClass::SIGNED_32}, - {"unsigned int", ccc::ast::BuiltInClass::UNSIGNED_32}, - {"unsigned", ccc::ast::BuiltInClass::UNSIGNED_32}, - {"long", ccc::ast::BuiltInClass::SIGNED_64}, - {"unsigned long", ccc::ast::BuiltInClass::UNSIGNED_64}, - {"long long", ccc::ast::BuiltInClass::SIGNED_64}, - {"unsigned long long", ccc::ast::BuiltInClass::UNSIGNED_64}, - {"bool", ccc::ast::BuiltInClass::BOOL_8}, - {"float", ccc::ast::BuiltInClass::FLOAT_32}, - {"double", ccc::ast::BuiltInClass::FLOAT_64}, - {"void", ccc::ast::BuiltInClass::VOID_TYPE}, - {"s8", ccc::ast::BuiltInClass::SIGNED_8}, - {"u8", ccc::ast::BuiltInClass::UNSIGNED_8}, - {"s16", ccc::ast::BuiltInClass::SIGNED_16}, - {"u16", ccc::ast::BuiltInClass::UNSIGNED_16}, - {"s32", ccc::ast::BuiltInClass::SIGNED_32}, - {"u32", ccc::ast::BuiltInClass::UNSIGNED_32}, - {"s64", ccc::ast::BuiltInClass::SIGNED_64}, - {"u64", ccc::ast::BuiltInClass::UNSIGNED_64}, - {"s128", ccc::ast::BuiltInClass::SIGNED_128}, - {"u128", ccc::ast::BuiltInClass::UNSIGNED_128}, - {"f32", ccc::ast::BuiltInClass::FLOAT_32}, - {"f64", ccc::ast::BuiltInClass::FLOAT_64}, -}; - -static void error_callback(const ccc::Error& error, ccc::ErrorLevel level) -{ - switch (level) - { - case ccc::ERROR_LEVEL_ERROR: - Console.Error("Error while importing symbol table: %s", error.message.c_str()); - break; - case ccc::ERROR_LEVEL_WARNING: - Console.Warning("Warning while importing symbol table: %s", error.message.c_str()); - break; - } -} - -SymbolGuardian::SymbolGuardian() -{ - ccc::set_custom_error_callback(error_callback); -} - -SymbolGuardian::~SymbolGuardian() -{ -} - void SymbolGuardian::Read(ReadCallback callback) const noexcept { - m_big_symbol_lock.lock_shared(); + std::shared_lock l(m_big_symbol_lock); callback(m_database); - m_big_symbol_lock.unlock_shared(); } void SymbolGuardian::ReadWrite(ReadWriteCallback callback) noexcept { - m_big_symbol_lock.lock(); + std::unique_lock l(m_big_symbol_lock); callback(m_database); - m_big_symbol_lock.unlock(); -} - -void SymbolGuardian::Reset() -{ - ShutdownWorkerThread(); - - ReadWrite([&](ccc::SymbolDatabase& database) { - database.clear(); - - ccc::Result source = database.get_symbol_source("Built-in"); - if (!source.success()) - return; - - // Create some built-in data type symbols so that users still have some - // types to use even if there isn't a symbol table loaded. Maybe in the - // future we could add PS2-specific types like DMA tags here too. - for (const DefaultBuiltInType& default_type : DEFAULT_BUILT_IN_TYPES) - { - ccc::Result symbol = database.data_types.create_symbol(default_type.name, *source, nullptr); - if (!symbol.success()) - return; - - std::unique_ptr type = std::make_unique(); - type->name = default_type.name; - type->size_bytes = ccc::ast::builtin_class_size(default_type.bclass); - type->bclass = default_type.bclass; - (*symbol)->set_type(std::move(type)); - } - }); -} - -void SymbolGuardian::ImportElf(std::vector elf, std::string elf_file_name, const std::string& nocash_path) -{ - ccc::Result parsed_elf = ccc::ElfFile::parse(std::move(elf)); - if (!parsed_elf.success()) - { - ccc::report_error(parsed_elf.error()); - return; - } - - ccc::ElfSymbolFile symbol_file(std::move(*parsed_elf), std::move(elf_file_name)); - - ShutdownWorkerThread(); - - m_import_thread = std::thread([this, nocash_path, worker_symbol_file = std::move(symbol_file)]() { - Threading::SetNameOfCurrentThread("Symbol Worker"); - - ccc::SymbolDatabase temp_database; - ImportSymbolTables(temp_database, worker_symbol_file, &m_interrupt_import_thread); - - if (m_interrupt_import_thread) - return; - - ImportNocashSymbols(temp_database, nocash_path); - - if (m_interrupt_import_thread) - return; - - ComputeOriginalFunctionHashes(temp_database, worker_symbol_file.elf()); - - if (m_interrupt_import_thread) - return; - - ReadWrite([&](ccc::SymbolDatabase& database) { - database.merge_from(temp_database); - - if (m_interrupt_import_thread) - return; - - const ccc::ElfProgramHeader* entry_segment = worker_symbol_file.elf().entry_point_segment(); - if (entry_segment) - { - ElfMemoryReader reader(worker_symbol_file.elf()); - MIPSAnalyst::ScanForFunctions(database, reader, entry_segment->vaddr, entry_segment->vaddr + entry_segment->filesz); - } - }); - }); -} - -void SymbolGuardian::ShutdownWorkerThread() -{ - m_interrupt_import_thread = true; - if (m_import_thread.joinable()) - m_import_thread.join(); - m_interrupt_import_thread = false; -} - - -ccc::ModuleHandle SymbolGuardian::ImportSymbolTables( - ccc::SymbolDatabase& database, const ccc::SymbolFile& symbol_file, const std::atomic_bool* interrupt) -{ - ccc::Result>> symbol_tables = symbol_file.get_all_symbol_tables(); - if (!symbol_tables.success()) - { - ccc::report_error(symbol_tables.error()); - return ccc::ModuleHandle(); - } - - ccc::DemanglerFunctions demangler; - demangler.cplus_demangle = cplus_demangle; - demangler.cplus_demangle_opname = cplus_demangle_opname; - - u32 importer_flags = - ccc::DEMANGLE_PARAMETERS | - ccc::DEMANGLE_RETURN_TYPE | - ccc::NO_MEMBER_FUNCTIONS | - ccc::NO_OPTIMIZED_OUT_FUNCTIONS | - ccc::UNIQUE_FUNCTIONS; - - ccc::Result module_handle = ccc::import_symbol_tables( - database, symbol_file.name(), *symbol_tables, importer_flags, demangler, interrupt); - if (!module_handle.success()) - { - ccc::report_error(module_handle.error()); - return ccc::ModuleHandle(); - } - - Console.WriteLn("Imported %d symbols.", database.symbol_count()); - - return *module_handle; -} - -bool SymbolGuardian::ImportNocashSymbols(ccc::SymbolDatabase& database, const std::string& file_name) -{ - ccc::Result source = database.get_symbol_source("Nocash Symbols"); - if (!source.success()) - return false; - - FILE* f = FileSystem::OpenCFile(file_name.c_str(), "r"); - if (!f) - return false; - - while (!feof(f)) - { - char line[256], value[256] = {0}; - char* p = fgets(line, 256, f); - if (p == NULL) - break; - - if (char* end = strchr(line, '\n')) - *end = '\0'; - - u32 address; - if (sscanf(line, "%08x %255s", &address, value) != 2) - continue; - if (address == 0 && strcmp(value, "0") == 0) - continue; - - if (value[0] == '.') - { - // data directives - char* s = strchr(value, ':'); - if (s != NULL) - { - *s = 0; - - u32 size = 0; - if (sscanf(s + 1, "%04x", &size) != 1) - continue; - - std::unique_ptr scalar_type = std::make_unique(); - if (StringUtil::Strcasecmp(value, ".byt") == 0) - { - scalar_type->size_bytes = 1; - scalar_type->bclass = ccc::ast::BuiltInClass::UNSIGNED_8; - } - else if (StringUtil::Strcasecmp(value, ".wrd") == 0) - { - scalar_type->size_bytes = 2; - scalar_type->bclass = ccc::ast::BuiltInClass::UNSIGNED_16; - } - else if (StringUtil::Strcasecmp(value, ".dbl") == 0) - { - scalar_type->size_bytes = 4; - scalar_type->bclass = ccc::ast::BuiltInClass::UNSIGNED_32; - } - else if (StringUtil::Strcasecmp(value, ".asc") == 0) - { - scalar_type->size_bytes = 1; - scalar_type->bclass = ccc::ast::BuiltInClass::UNQUALIFIED_8; - } - else - { - continue; - } - - ccc::Result global_variable = database.global_variables.create_symbol( - line, address, *source, nullptr); - if (!global_variable.success()) - { - fclose(f); - return false; - } - - if (scalar_type->size_bytes == (s32)size) - { - (*global_variable)->set_type(std::move(scalar_type)); - } - else - { - std::unique_ptr array = std::make_unique(); - array->size_bytes = (s32)size; - array->element_type = std::move(scalar_type); - array->element_count = size / array->element_type->size_bytes; - (*global_variable)->set_type(std::move(array)); - } - } - } - else - { // labels - u32 size = 1; - char* seperator = strchr(value, ','); - if (seperator != NULL) - { - *seperator = 0; - sscanf(seperator + 1, "%08x", &size); - } - - if (size != 1) - { - ccc::Result function = database.functions.create_symbol(value, address, *source, nullptr); - if (!function.success()) - { - fclose(f); - return false; - } - - (*function)->set_size(size); - } - else - { - ccc::Result label = database.labels.create_symbol(value, address, *source, nullptr); - if (!label.success()) - { - fclose(f); - return false; - } - } - } - } - - fclose(f); - return true; -} - -void SymbolGuardian::ComputeOriginalFunctionHashes(ccc::SymbolDatabase& database, const ccc::ElfFile& elf) -{ - for (ccc::Function& function : database.functions) - { - if (!function.address().valid()) - continue; - - if (function.size() == 0) - continue; - - ccc::Result> text = elf.get_array_virtual( - function.address().value, function.size() / 4); - if (!text.success()) - { - ccc::report_warning(text.error()); - break; - } - - ccc::FunctionHash hash; - for (u32 instruction : *text) - hash.update(instruction); - - function.set_original_hash(hash.get()); - } -} - -void SymbolGuardian::UpdateFunctionHashes(DebugInterface& cpu) -{ - ReadWrite([&](ccc::SymbolDatabase& database) { - for (ccc::Function& function : database.functions) - { - if (!function.address().valid()) - continue; - - if (function.size() == 0) - continue; - - ccc::FunctionHash hash; - for (u32 i = 0; i < function.size() / 4; i++) - hash.update(cpu.read32(function.address().value + i * 4)); - - function.set_current_hash(hash); - } - - for (ccc::SourceFile& source_file : database.source_files) - source_file.check_functions_match(database); - }); -} - -void SymbolGuardian::ClearIrxModules() -{ - ReadWrite([&](ccc::SymbolDatabase& database) { - std::vector irx_modules; - for (const ccc::Module& module : m_database.modules) - if (module.is_irx) - irx_modules.emplace_back(module.handle()); - - for (ccc::ModuleHandle module : irx_modules) - m_database.destroy_symbols_from_module(module, false); - }); } SymbolInfo SymbolGuardian::SymbolStartingAtAddress( @@ -531,3 +148,16 @@ FunctionInfo SymbolGuardian::FunctionOverlappingAddress(u32 address) const }); return info; } + +void SymbolGuardian::ClearIrxModules() +{ + ReadWrite([&](ccc::SymbolDatabase& database) { + std::vector irx_modules; + for (const ccc::Module& module : m_database.modules) + if (module.is_irx) + irx_modules.emplace_back(module.handle()); + + for (ccc::ModuleHandle module : irx_modules) + m_database.destroy_symbols_from_module(module, false); + }); +} diff --git a/pcsx2/DebugTools/SymbolGuardian.h b/pcsx2/DebugTools/SymbolGuardian.h index 7959a0707e..2d5dc2841e 100644 --- a/pcsx2/DebugTools/SymbolGuardian.h +++ b/pcsx2/DebugTools/SymbolGuardian.h @@ -8,13 +8,12 @@ #include #include +#include #include #include #include "common/Pcsx2Types.h" -class DebugInterface; - struct SymbolInfo { std::optional descriptor; @@ -33,13 +32,15 @@ struct FunctionInfo bool is_no_return = false; }; -struct SymbolGuardian +// Guardian of the ancient symbols. This class provides a thread safe API for +// accessing the symbol database. +class SymbolGuardian { public: - SymbolGuardian(); + SymbolGuardian() = default; SymbolGuardian(const SymbolGuardian& rhs) = delete; SymbolGuardian(SymbolGuardian&& rhs) = delete; - ~SymbolGuardian(); + ~SymbolGuardian() = default; SymbolGuardian& operator=(const SymbolGuardian& rhs) = delete; SymbolGuardian& operator=(SymbolGuardian&& rhs) = delete; @@ -52,32 +53,6 @@ public: // Take an exclusive lock on the symbol database and run the callback. void ReadWrite(ReadWriteCallback callback) noexcept; - // Delete all stored symbols and create some default built-ins. Should be - // called from the CPU thread. - void Reset(); - - // Import symbols from the ELF file, nocash symbols, and scan for functions. - // Should be called from the CPU thread. - void ImportElf(std::vector elf, std::string elf_file_name, const std::string& nocash_path); - - // Interrupt the import thread. Should be called from the CPU thread. - void ShutdownWorkerThread(); - - static ccc::ModuleHandle ImportSymbolTables( - ccc::SymbolDatabase& database, const ccc::SymbolFile& symbol_file, const std::atomic_bool* interrupt); - static bool ImportNocashSymbols(ccc::SymbolDatabase& database, const std::string& file_name); - - // Compute original hashes for all the functions based on the code stored in - // the ELF file. - static void ComputeOriginalFunctionHashes(ccc::SymbolDatabase& database, const ccc::ElfFile& elf); - - // Compute new hashes for all the functions to check if any of them have - // been overwritten. - void UpdateFunctionHashes(DebugInterface& cpu); - - // Delete all symbols from modules that have the "is_irx" flag set. - void ClearIrxModules(); - // Copy commonly used attributes of a symbol into a temporary object. SymbolInfo SymbolStartingAtAddress( u32 address, u32 descriptors = ccc::ALL_SYMBOL_TYPES) const; @@ -96,12 +71,12 @@ public: FunctionInfo FunctionStartingAtAddress(u32 address) const; FunctionInfo FunctionOverlappingAddress(u32 address) const; + // Delete all symbols from modules that have the "is_irx" flag set. + void ClearIrxModules(); + protected: ccc::SymbolDatabase m_database; mutable std::shared_mutex m_big_symbol_lock; - - std::thread m_import_thread; - std::atomic_bool m_interrupt_import_thread = false; }; extern SymbolGuardian R5900SymbolGuardian; diff --git a/pcsx2/DebugTools/SymbolImporter.cpp b/pcsx2/DebugTools/SymbolImporter.cpp new file mode 100644 index 0000000000..f35f887e5f --- /dev/null +++ b/pcsx2/DebugTools/SymbolImporter.cpp @@ -0,0 +1,397 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "SymbolImporter.h" + +#include "CDVD/CDVD.h" +#include "DebugInterface.h" +#include "Elfheader.h" +#include "MIPSAnalyst.h" +#include "VMManager.h" + +#include "common/Console.h" +#include "common/Error.h" +#include "common/FileSystem.h" +#include "common/StringUtil.h" +#include "common/Threading.h" + +#include +#include +#include +#include + +#include + +SymbolImporter R3000SymbolImporter(R3000SymbolGuardian); +SymbolImporter R5900SymbolImporter(R5900SymbolGuardian); + +struct DefaultBuiltInType +{ + const char* name; + ccc::ast::BuiltInClass bclass; +}; + +static const std::vector DEFAULT_BUILT_IN_TYPES = { + {"char", ccc::ast::BuiltInClass::UNQUALIFIED_8}, + {"signed char", ccc::ast::BuiltInClass::SIGNED_8}, + {"unsigned char", ccc::ast::BuiltInClass::UNSIGNED_8}, + {"short", ccc::ast::BuiltInClass::SIGNED_16}, + {"unsigned short", ccc::ast::BuiltInClass::UNSIGNED_16}, + {"int", ccc::ast::BuiltInClass::SIGNED_32}, + {"unsigned int", ccc::ast::BuiltInClass::UNSIGNED_32}, + {"unsigned", ccc::ast::BuiltInClass::UNSIGNED_32}, + {"long", ccc::ast::BuiltInClass::SIGNED_64}, + {"unsigned long", ccc::ast::BuiltInClass::UNSIGNED_64}, + {"long long", ccc::ast::BuiltInClass::SIGNED_64}, + {"unsigned long long", ccc::ast::BuiltInClass::UNSIGNED_64}, + {"bool", ccc::ast::BuiltInClass::BOOL_8}, + {"float", ccc::ast::BuiltInClass::FLOAT_32}, + {"double", ccc::ast::BuiltInClass::FLOAT_64}, + {"void", ccc::ast::BuiltInClass::VOID_TYPE}, + {"s8", ccc::ast::BuiltInClass::SIGNED_8}, + {"u8", ccc::ast::BuiltInClass::UNSIGNED_8}, + {"s16", ccc::ast::BuiltInClass::SIGNED_16}, + {"u16", ccc::ast::BuiltInClass::UNSIGNED_16}, + {"s32", ccc::ast::BuiltInClass::SIGNED_32}, + {"u32", ccc::ast::BuiltInClass::UNSIGNED_32}, + {"s64", ccc::ast::BuiltInClass::SIGNED_64}, + {"u64", ccc::ast::BuiltInClass::UNSIGNED_64}, + {"s128", ccc::ast::BuiltInClass::SIGNED_128}, + {"u128", ccc::ast::BuiltInClass::UNSIGNED_128}, + {"f32", ccc::ast::BuiltInClass::FLOAT_32}, + {"f64", ccc::ast::BuiltInClass::FLOAT_64}, +}; + +static void error_callback(const ccc::Error& error, ccc::ErrorLevel level) +{ + switch (level) + { + case ccc::ERROR_LEVEL_ERROR: + Console.Error("Error while importing symbol table: %s", error.message.c_str()); + break; + case ccc::ERROR_LEVEL_WARNING: + Console.Warning("Warning while importing symbol table: %s", error.message.c_str()); + break; + } +} + +SymbolImporter::SymbolImporter(SymbolGuardian& guardian) + : m_guardian(guardian) +{ + ccc::set_custom_error_callback(error_callback); +} + +void SymbolImporter::OnElfChanged(std::vector elf, const std::string& elf_file_name) +{ + Reset(); + AnalyseElf(std::move(elf), elf_file_name); +} + +void SymbolImporter::Reset() +{ + ShutdownWorkerThread(); + + m_guardian.ReadWrite([&](ccc::SymbolDatabase& database) { + database.clear(); + + ccc::Result source = database.get_symbol_source("Built-in"); + if (!source.success()) + return; + + // Create some built-in data type symbols so that users still have some + // types to use even if there isn't a symbol table loaded. Maybe in the + // future we could add PS2-specific types like DMA tags here too. + for (const DefaultBuiltInType& default_type : DEFAULT_BUILT_IN_TYPES) + { + ccc::Result symbol = database.data_types.create_symbol(default_type.name, *source, nullptr); + if (!symbol.success()) + return; + + std::unique_ptr type = std::make_unique(); + type->name = default_type.name; + type->size_bytes = ccc::ast::builtin_class_size(default_type.bclass); + type->bclass = default_type.bclass; + (*symbol)->set_type(std::move(type)); + } + }); +} + +void SymbolImporter::AnalyseElf(std::vector elf, const std::string& elf_file_name) +{ + // Search for a .sym file to load symbols from. + std::string nocash_path; + CDVD_SourceType source_type = CDVDsys_GetSourceType(); + if (source_type == CDVD_SourceType::Iso) + { + std::string iso_file_path = CDVDsys_GetFile(source_type); + + std::string::size_type n = iso_file_path.rfind('.'); + if (n == std::string::npos) + nocash_path = iso_file_path + ".sym"; + else + nocash_path = iso_file_path.substr(0, n) + ".sym"; + } + + ccc::Result parsed_elf = ccc::ElfFile::parse(std::move(elf)); + if (!parsed_elf.success()) + { + ccc::report_error(parsed_elf.error()); + return; + } + + ccc::ElfSymbolFile symbol_file(std::move(*parsed_elf), std::move(elf_file_name)); + + ShutdownWorkerThread(); + + m_import_thread = std::thread([this, nocash_path, worker_symbol_file = std::move(symbol_file)]() { + Threading::SetNameOfCurrentThread("Symbol Worker"); + + ccc::SymbolDatabase temp_database; + ImportSymbolTables(temp_database, worker_symbol_file, &m_interrupt_import_thread); + + if (m_interrupt_import_thread) + return; + + ImportNocashSymbols(temp_database, nocash_path); + + if (m_interrupt_import_thread) + return; + + const ccc::ElfProgramHeader* entry_segment = worker_symbol_file.elf().entry_point_segment(); + if (entry_segment) + { + ElfMemoryReader reader(worker_symbol_file.elf()); + MIPSAnalyst::ScanForFunctions(temp_database, reader, entry_segment->vaddr, entry_segment->vaddr + entry_segment->filesz); + } + + if (m_interrupt_import_thread) + return; + + ComputeOriginalFunctionHashes(temp_database, worker_symbol_file.elf()); + + if (m_interrupt_import_thread) + return; + + m_guardian.ReadWrite([&](ccc::SymbolDatabase& database) { + database.merge_from(temp_database); + }); + }); +} + +void SymbolImporter::ShutdownWorkerThread() +{ + m_interrupt_import_thread = true; + if (m_import_thread.joinable()) + m_import_thread.join(); + m_interrupt_import_thread = false; +} + + +ccc::ModuleHandle SymbolImporter::ImportSymbolTables( + ccc::SymbolDatabase& database, const ccc::SymbolFile& symbol_file, const std::atomic_bool* interrupt) +{ + ccc::Result>> symbol_tables = symbol_file.get_all_symbol_tables(); + if (!symbol_tables.success()) + { + ccc::report_error(symbol_tables.error()); + return ccc::ModuleHandle(); + } + + ccc::DemanglerFunctions demangler; + demangler.cplus_demangle = cplus_demangle; + demangler.cplus_demangle_opname = cplus_demangle_opname; + + u32 importer_flags = + ccc::DEMANGLE_PARAMETERS | + ccc::DEMANGLE_RETURN_TYPE | + ccc::NO_MEMBER_FUNCTIONS | + ccc::NO_OPTIMIZED_OUT_FUNCTIONS | + ccc::UNIQUE_FUNCTIONS; + + ccc::Result module_handle = ccc::import_symbol_tables( + database, symbol_file.name(), *symbol_tables, importer_flags, demangler, interrupt); + if (!module_handle.success()) + { + ccc::report_error(module_handle.error()); + return ccc::ModuleHandle(); + } + + Console.WriteLn("Imported %d symbols.", database.symbol_count()); + + return *module_handle; +} + +bool SymbolImporter::ImportNocashSymbols(ccc::SymbolDatabase& database, const std::string& file_name) +{ + ccc::Result source = database.get_symbol_source("Nocash Symbols"); + if (!source.success()) + return false; + + FILE* f = FileSystem::OpenCFile(file_name.c_str(), "r"); + if (!f) + return false; + + while (!feof(f)) + { + char line[256], value[256] = {0}; + char* p = fgets(line, 256, f); + if (p == NULL) + break; + + if (char* end = strchr(line, '\n')) + *end = '\0'; + + u32 address; + if (sscanf(line, "%08x %255s", &address, value) != 2) + continue; + if (address == 0 && strcmp(value, "0") == 0) + continue; + + if (value[0] == '.') + { + // data directives + char* s = strchr(value, ':'); + if (s != NULL) + { + *s = 0; + + u32 size = 0; + if (sscanf(s + 1, "%04x", &size) != 1) + continue; + + std::unique_ptr scalar_type = std::make_unique(); + if (StringUtil::Strcasecmp(value, ".byt") == 0) + { + scalar_type->size_bytes = 1; + scalar_type->bclass = ccc::ast::BuiltInClass::UNSIGNED_8; + } + else if (StringUtil::Strcasecmp(value, ".wrd") == 0) + { + scalar_type->size_bytes = 2; + scalar_type->bclass = ccc::ast::BuiltInClass::UNSIGNED_16; + } + else if (StringUtil::Strcasecmp(value, ".dbl") == 0) + { + scalar_type->size_bytes = 4; + scalar_type->bclass = ccc::ast::BuiltInClass::UNSIGNED_32; + } + else if (StringUtil::Strcasecmp(value, ".asc") == 0) + { + scalar_type->size_bytes = 1; + scalar_type->bclass = ccc::ast::BuiltInClass::UNQUALIFIED_8; + } + else + { + continue; + } + + ccc::Result global_variable = database.global_variables.create_symbol( + line, address, *source, nullptr); + if (!global_variable.success()) + { + fclose(f); + return false; + } + + if (scalar_type->size_bytes == (s32)size) + { + (*global_variable)->set_type(std::move(scalar_type)); + } + else + { + std::unique_ptr array = std::make_unique(); + array->size_bytes = (s32)size; + array->element_type = std::move(scalar_type); + array->element_count = size / array->element_type->size_bytes; + (*global_variable)->set_type(std::move(array)); + } + } + } + else + { // labels + u32 size = 1; + char* seperator = strchr(value, ','); + if (seperator != NULL) + { + *seperator = 0; + sscanf(seperator + 1, "%08x", &size); + } + + if (size != 1) + { + ccc::Result function = database.functions.create_symbol(value, address, *source, nullptr); + if (!function.success()) + { + fclose(f); + return false; + } + + (*function)->set_size(size); + } + else + { + ccc::Result label = database.labels.create_symbol(value, address, *source, nullptr); + if (!label.success()) + { + fclose(f); + return false; + } + } + } + } + + fclose(f); + return true; +} + +void SymbolImporter::ComputeOriginalFunctionHashes(ccc::SymbolDatabase& database, const ccc::ElfFile& elf) +{ + for (ccc::Function& function : database.functions) + { + if (!function.address().valid()) + continue; + + if (function.size() == 0) + continue; + + ccc::Result> text = elf.get_array_virtual( + function.address().value, function.size() / 4); + if (!text.success()) + { + ccc::report_warning(text.error()); + break; + } + + ccc::FunctionHash hash; + for (u32 instruction : *text) + hash.update(instruction); + + function.set_original_hash(hash.get()); + } +} + +void SymbolImporter::UpdateFunctionHashes(DebugInterface& cpu) +{ + m_guardian.ReadWrite([&](ccc::SymbolDatabase& database) { + for (ccc::Function& function : database.functions) + { + if (!function.address().valid()) + continue; + + if (function.size() == 0) + continue; + + if (function.original_hash() == 0) + continue; + + ccc::FunctionHash hash; + for (u32 i = 0; i < function.size() / 4; i++) + hash.update(cpu.read32(function.address().value + i * 4)); + + function.set_current_hash(hash); + } + + for (ccc::SourceFile& source_file : database.source_files) + source_file.check_functions_match(database); + }); +} diff --git a/pcsx2/DebugTools/SymbolImporter.h b/pcsx2/DebugTools/SymbolImporter.h new file mode 100644 index 0000000000..9ef761ffe0 --- /dev/null +++ b/pcsx2/DebugTools/SymbolImporter.h @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#pragma once + +#include "SymbolGuardian.h" + +class DebugInterface; + +struct SymbolImporterOptions +{ + std::vector symbols_to_destroy; + +}; + +class SymbolImporter +{ +public: + SymbolImporter(SymbolGuardian& guardian); + + // These functions are used to receive events from the rest of the emulator + // that are used to determine when symbol tables should be loaded, and + // should be called from the CPU thread. + void OnElfChanged(std::vector elf, const std::string& elf_file_name); + + void AutoAnalyse(); + + // Delete all stored symbols and create some default built-ins. Should be + // called from the CPU thread. + void Reset(); + + // Import symbols from the ELF file, nocash symbols, and scan for functions. + // Should be called from the CPU thread. + void AnalyseElf(std::vector elf, const std::string& elf_file_name); + + // Interrupt the import thread. Should be called from the CPU thread. + void ShutdownWorkerThread(); + + static ccc::ModuleHandle ImportSymbolTables( + ccc::SymbolDatabase& database, const ccc::SymbolFile& symbol_file, const std::atomic_bool* interrupt); + static bool ImportNocashSymbols(ccc::SymbolDatabase& database, const std::string& file_name); + + // Compute original hashes for all the functions based on the code stored in + // the ELF file. + static void ComputeOriginalFunctionHashes(ccc::SymbolDatabase& database, const ccc::ElfFile& elf); + + // Compute new hashes for all the functions to check if any of them have + // been overwritten. + void UpdateFunctionHashes(DebugInterface& cpu); + +protected: + SymbolGuardian& m_guardian; + + std::thread m_import_thread; + std::atomic_bool m_interrupt_import_thread = false; +}; + +extern SymbolImporter R3000SymbolImporter; +extern SymbolImporter R5900SymbolImporter; diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp index a9811a56bf..a0cd190fae 100644 --- a/pcsx2/VMManager.cpp +++ b/pcsx2/VMManager.cpp @@ -7,7 +7,7 @@ #include "Counters.h" #include "DEV9/DEV9.h" #include "DebugTools/DebugInterface.h" -#include "DebugTools/SymbolGuardian.h" +#include "DebugTools/SymbolImporter.h" #include "Elfheader.h" #include "FW.h" #include "GS.h" @@ -447,8 +447,8 @@ void VMManager::Internal::CPUThreadShutdown() // Ensure emulog gets flushed. Log::SetFileOutputLevel(LOGLEVEL_NONE, std::string()); - R3000SymbolGuardian.ShutdownWorkerThread(); - R5900SymbolGuardian.ShutdownWorkerThread(); + R3000SymbolImporter.ShutdownWorkerThread(); + R5900SymbolImporter.ShutdownWorkerThread(); } void VMManager::Internal::SetFileLogPath(std::string path) @@ -1154,24 +1154,7 @@ void VMManager::UpdateELFInfo(std::string elf_path) s_elf_text_range = elfo.GetTextRange(); s_elf_path = std::move(elf_path); - R5900SymbolGuardian.Reset(); - - // Search for a .sym file to load symbols from. - std::string nocash_path; - CDVD_SourceType source_type = CDVDsys_GetSourceType(); - if (source_type == CDVD_SourceType::Iso) - { - std::string iso_file_path = CDVDsys_GetFile(source_type); - - std::string::size_type n = iso_file_path.rfind('.'); - if (n == std::string::npos) - nocash_path = iso_file_path + ".sym"; - else - nocash_path = iso_file_path.substr(0, n) + ".sym"; - } - - // Load the symbols stored in the ELF file. - R5900SymbolGuardian.ImportElf(elfo.ReleaseData(), s_elf_path, nocash_path); + R5900SymbolImporter.OnElfChanged(elfo.ReleaseData(), s_elf_path); } void VMManager::ClearELFInfo() diff --git a/pcsx2/pcsx2.vcxproj b/pcsx2/pcsx2.vcxproj index a99413cf08..41ff04385a 100644 --- a/pcsx2/pcsx2.vcxproj +++ b/pcsx2/pcsx2.vcxproj @@ -155,6 +155,7 @@ + @@ -600,6 +601,7 @@ + diff --git a/pcsx2/pcsx2.vcxproj.filters b/pcsx2/pcsx2.vcxproj.filters index bbb34a6b10..fd225c4d16 100644 --- a/pcsx2/pcsx2.vcxproj.filters +++ b/pcsx2/pcsx2.vcxproj.filters @@ -1398,6 +1398,9 @@ System\Ps2\Debug + + System\Ps2\Debug + System\Ps2\USB\usb-eyetoy @@ -2342,6 +2345,9 @@ System\Ps2\Debug + + System\Ps2\Debug + System\Ps2\USB\usb-eyetoy