rexglue-skate3/src/codegen/test_support.cpp
2026-05-18 22:41:13 +01:00

117 lines
3.8 KiB
C++

/**
* @file codegen/test_support.cpp
* @brief Test support utilities
*
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
* All rights reserved.
*
* @license BSD 3-Clause License
* See LICENSE file in the project root for full license text.
*/
#include <rex/codegen/test_support.h>
#include <algorithm>
#include <vector>
#include <fmt/format.h>
#include "ppc/instruction.h"
#include <rex/codegen/codegen_context.h>
#include <rex/logging.h>
#include <rex/memory/utils.h>
#include <rex/system/binary_types.h>
#include <rex/types.h>
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<uint32_t>(size);
// Populate binary section for FunctionScanner to read instructions
binary_sections_.clear();
binary_sections_.push_back(runtime::BinarySection{
".text", base_address, static_cast<uint32_t>(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<size_t, std::string>& symbols, uint32_t baseAddress,
const uint8_t* data, size_t dataSize) {
// Extract only test_ prefixed symbols as function entry points
std::vector<std::pair<size_t, std::string>> 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<uint32_t>(testFunctions[i].first);
// Size extends to next test_ function or end of binary
uint32_t nextAddr = static_cast<uint32_t>(baseAddress + dataSize);
if (i + 1 < testFunctions.size()) {
nextAddr = static_cast<uint32_t>(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<uint32_t>(testFunctions[i].first);
uint32_t nextAddr = static_cast<uint32_t>(baseAddress + dataSize);
if (i + 1 < testFunctions.size()) {
nextAddr = static_cast<uint32_t>(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<uint32_t>(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