/** * @file codegen/test_support.cpp * @brief Test support utilities * * @copyright Copyright (c) 2026 Tom Clay * All rights reserved. * * @license BSD 3-Clause License * See LICENSE file in the project root for full license text. */ #include #include #include #include #include "ppc/instruction.h" #include #include #include #include #include using rex::codegen::ppc::decode_instruction; using rex::memory::load_and_swap; namespace rex::codegen { // --- TestModule --- TestModule::TestModule() : Module(nullptr) {} void TestModule::Load(uint32_t base_address, const uint8_t* data, size_t size) { base_address_ = base_address; size_ = static_cast(size); // Populate binary section for FunctionScanner to read instructions binary_sections_.clear(); binary_sections_.push_back(runtime::BinarySection{ ".text", base_address, static_cast(size), data, true, // executable false // writable }); } bool TestModule::ContainsAddress(uint32_t address) { return address >= base_address_ && address < base_address_ + size_; } // --- AnalyzeTestBinary --- void AnalyzeTestBinary(CodegenContext& ctx, std::string_view testName, const std::map& symbols, uint32_t baseAddress, const uint8_t* data, size_t dataSize) { // Extract only test_ prefixed symbols as function entry points std::vector> testFunctions; for (const auto& [addr, name] : symbols) { if (name.starts_with("test_")) { testFunctions.push_back({addr, name}); } } // Sort by address std::sort(testFunctions.begin(), testFunctions.end()); // First pass: add all functions and transition through state machine for (size_t i = 0; i < testFunctions.size(); i++) { uint32_t fnAddr = static_cast(testFunctions[i].first); // Size extends to next test_ function or end of binary uint32_t nextAddr = static_cast(baseAddress + dataSize); if (i + 1 < testFunctions.size()) { nextAddr = static_cast(testFunctions[i + 1].first); } uint32_t fnSize = nextAddr - fnAddr; auto* node = ctx.graph.addFunction(fnAddr, fnSize, FunctionAuthority::DISCOVERED, true); if (node) { // kRegistered -> kDiscovered -> kSealed node->discover({{fnAddr, fnSize}}, {}, {}); ctx.graph.setFunctionName(fnAddr, fmt::format("{}_{:X}", testName, fnAddr)); node->seal(); } } // Second pass: scan for bl instructions and register resolved call edges for (size_t i = 0; i < testFunctions.size(); i++) { uint32_t fnAddr = static_cast(testFunctions[i].first); uint32_t nextAddr = static_cast(baseAddress + dataSize); if (i + 1 < testFunctions.size()) { nextAddr = static_cast(testFunctions[i + 1].first); } uint32_t fnSize = nextAddr - fnAddr; // Scan each instruction in this function for (uint32_t offset = 0; offset < fnSize; offset += 4) { uint32_t pc = fnAddr + offset; uint32_t raw = load_and_swap(data + (fnAddr - baseAddress) + offset); auto decoded = decode_instruction(pc, raw); // Check for bl (branch with link = function call) if (decoded.is_call() && decoded.branch_target.has_value()) { uint32_t target = decoded.branch_target.value(); auto* targetNode = ctx.graph.getFunction(target); if (targetNode) { ctx.graph.addCallToFunction(fnAddr, pc, CallTarget::function(targetNode)); } } } } } } // namespace rex::codegen