Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166// target.cpp — Target base: machine ownership, CPU save/restore,// isolated/nested calls, RAM snapshots.//// call() uses the library stub region (detail::kTrampoline/kStubSp);// the derivees map that region in create().
#include "core/target.hpp"#include "core/internal.hpp"
#include <unicorn/unicorn.h>
#include <bit>#include <cstdint>#include <cstdio>#include <memory>#include <span>#include <vector>
namespace mf {
Target::~Target() { if (uc_ != nullptr) { uc_close(uc_); }}
bool Target::read_u16(const uint32_t address, uint16_t &value) const noexcept { return uc_mem_read(uc_, address, &value, sizeof(value)) == UC_ERR_OK;}
bool Target::read_u32(const uint32_t address, uint32_t &value) const noexcept { return uc_mem_read(uc_, address, &value, sizeof(value)) == UC_ERR_OK;}
CpuState Target::save_state() const { CpuState st; for (int i = 0; i < 13; i++) { uc_reg_read(uc_, UC_ARM_REG_R0 + i, &st.r[i]); } uc_reg_read(uc_, UC_ARM_REG_SP, &st.sp); uc_reg_read(uc_, UC_ARM_REG_LR, &st.lr); uc_reg_read(uc_, UC_ARM_REG_PC, &st.pc); uc_reg_read(uc_, UC_ARM_REG_XPSR, &st.xpsr); for (int i = 0; i < 32; i++) { uc_reg_read(uc_, UC_ARM_REG_S0 + i, &st.s[i]); } uc_reg_read(uc_, UC_ARM_REG_FPSCR, &st.fpscr); return st;}
void Target::restore_state(const CpuState &st) { for (int i = 0; i < 13; i++) { uc_reg_write(uc_, UC_ARM_REG_R0 + i, &st.r[i]); } uc_reg_write(uc_, UC_ARM_REG_SP, &st.sp); uc_reg_write(uc_, UC_ARM_REG_LR, &st.lr); uc_reg_write(uc_, UC_ARM_REG_PC, &st.pc); uc_reg_write(uc_, UC_ARM_REG_XPSR, &st.xpsr); for (int i = 0; i < 32; i++) { uc_reg_write(uc_, UC_ARM_REG_S0 + i, &st.s[i]); } uc_reg_write(uc_, UC_ARM_REG_FPSCR, &st.fpscr);}
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) — (base, size) pair; only derivees call this with constants.Target::Target(uint32_t ram_base, size_t ram_size, DiagFn diag) : ram_base_(ram_base), ram_size_(ram_size), diag_(std::move(diag)) {}
void Target::loginfo(const char *msg) { if (diag_) { diag_(LogLevel::info, msg); }}
CallResult Target::call(uint32_t func_addr, const CallArgs &args) { // Both host-side and hook-site calls share one mechanism: save // the full CPU state (from inside a hook this includes the PC of // the hook site), run on the stub stack with a trampoline LR, // capture the returns, restore everything. Restoring the PC is // what makes a call from a hook resume exactly at the hooked // instruction. const CpuState saved = save_state();
// Stub stack + stack arguments (outgoing args live at [sp..], // like the reference harnesses' call_fn; sp stays 8-byte aligned). const uint32_t sp = (args.stack_top != 0 ? args.stack_top : detail::kStubSp) & ~7U; for (size_t i = 0; i < args.stack.size(); i++) { if (uc_mem_write(uc_, sp + (4 * i), &args.stack[i], 4) != UC_ERR_OK) { restore_state(saved); throw Error{Status::emulator, "stack arg write failed"}; } }
const uint32_t lr = detail::kTrampoline | 1U; uc_reg_write(uc_, UC_ARM_REG_R0, args.r.data()); uc_reg_write(uc_, UC_ARM_REG_R1, &args.r[1]); uc_reg_write(uc_, UC_ARM_REG_R2, &args.r[2]); uc_reg_write(uc_, UC_ARM_REG_R3, &args.r[3]); const uint32_t zero = 0; for (int i = 4; i <= 12; i++) { // deterministic r4-r12, as call_fn uc_reg_write(uc_, UC_ARM_REG_R0 + i, &zero); } for (int i = 0; i < 16; i++) { uc_reg_write(uc_, UC_ARM_REG_S0 + i, &args.s[i]); } uc_reg_write(uc_, UC_ARM_REG_SP, &sp); uc_reg_write(uc_, UC_ARM_REG_LR, &lr);
const uc_err err = emu_start(func_addr | 1U, detail::kTrampoline);
uint32_t r0 = 0; uint32_t r1 = 0; uint32_t s0 = 0; uint32_t s1 = 0; if (err == UC_ERR_OK) { uc_reg_read(uc_, UC_ARM_REG_R0, &r0); uc_reg_read(uc_, UC_ARM_REG_R1, &r1); uc_reg_read(uc_, UC_ARM_REG_S0, &s0); uc_reg_read(uc_, UC_ARM_REG_S1, &s1); } uint32_t pc = 0; uc_reg_read(uc_, UC_ARM_REG_PC, &pc);
restore_state(saved);
if (err != UC_ERR_OK) { std::array<char, 96> msg{}; std::snprintf(msg.data(), msg.size(), "call @0x%08X: %s", func_addr, detail::uc_err_str(err)); throw Error{Status::emulator, msg.data()}; } // Stopped cleanly at the trampoline = normal return. Any other stop // point is a firmware/control-flow fault in the normal unbounded path. if ((pc & ~1U) != detail::kTrampoline) { std::array<char, 128> msg{}; std::snprintf(msg.data(), msg.size(), "call @0x%08X: did not return to trampoline", func_addr); throw Error{Status::fault, msg.data()}; }
CallResult res; res.r0 = r0; res.r1 = r1; res.s0 = std::bit_cast<float>(s0); res.s1 = std::bit_cast<float>(s1); return res;}
RamSnapshot Target::save_ram() { std::vector<std::byte> buf(ram_size_); if (uc_mem_read(uc_, ram_base_, buf.data(), buf.size()) != UC_ERR_OK) { throw Error{Status::emulator, "RAM read failed"}; } return RamSnapshot(ram_base_, buf);}
void Target::restore_ram(const RamSnapshot &image) { if (image.size() != ram_size_) { throw Error{Status::argument, "snapshot size mismatch"}; } if (uc_mem_write(uc_, ram_base_, image.bytes().data(), image.bytes().size()) != UC_ERR_OK) { throw Error{Status::emulator, "RAM write failed"}; }}
} // namespace mf