/* This file is part of the dynarmic project. * Copyright (c) 2018 MerryMage * This software may be used and distributed according to the terms of the GNU * General Public License version 2 or any later version. */ #include "A32/testenv.h" #include "a32_unicorn.h" #include "common/assert.h" #define CHECKED(expr) \ do { \ if (auto cerr_ = (expr)) { \ ASSERT_MSG(false, "Call " #expr " failed with error: {} ({})\n", cerr_, \ uc_strerror(cerr_)); \ } \ } while (0) constexpr u32 BEGIN_ADDRESS = 0; constexpr u32 END_ADDRESS = ~u32(0); A32Unicorn::A32Unicorn(ArmTestEnv& testenv) : testenv(testenv) { CHECKED(uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc)); CHECKED(uc_hook_add(uc, &intr_hook, UC_HOOK_INTR, (void*)InterruptHook, this, BEGIN_ADDRESS, END_ADDRESS)); CHECKED(uc_hook_add(uc, &mem_invalid_hook, UC_HOOK_MEM_INVALID, (void*)UnmappedMemoryHook, this, BEGIN_ADDRESS, END_ADDRESS)); CHECKED(uc_hook_add(uc, &mem_write_prot_hook, UC_HOOK_MEM_WRITE, (void*)MemoryWriteHook, this, BEGIN_ADDRESS, END_ADDRESS)); } A32Unicorn::~A32Unicorn() { ClearPageCache(); CHECKED(uc_hook_del(uc, intr_hook)); CHECKED(uc_hook_del(uc, mem_invalid_hook)); CHECKED(uc_close(uc)); } void A32Unicorn::Run() { while (testenv.ticks_left > 0) { CHECKED(uc_emu_start(uc, GetPC(), END_ADDRESS, 0, 1)); testenv.ticks_left--; if (!testenv.interrupts.empty() || testenv.code_mem_modified_by_guest) { return; } } } constexpr std::array gpr_ids{ UC_ARM_REG_R0, UC_ARM_REG_R1, UC_ARM_REG_R2, UC_ARM_REG_R3, UC_ARM_REG_R4, UC_ARM_REG_R5, UC_ARM_REG_R6, UC_ARM_REG_R7, UC_ARM_REG_R8, UC_ARM_REG_R9, UC_ARM_REG_R10, UC_ARM_REG_R11, UC_ARM_REG_R12, UC_ARM_REG_R13, UC_ARM_REG_R14, UC_ARM_REG_R15, }; A32Unicorn::RegisterArray A32Unicorn::GetRegisters() const { RegisterArray regs; RegisterPtrArray ptrs; for (size_t i = 0; i < ptrs.size(); ++i) ptrs[i] = ®s[i]; CHECKED(uc_reg_read_batch(uc, const_cast(gpr_ids.data()), reinterpret_cast(ptrs.data()), num_gprs)); return regs; } void A32Unicorn::SetRegisters(const RegisterArray& value) { RegisterConstPtrArray ptrs; for (size_t i = 0; i < ptrs.size(); ++i) ptrs[i] = &value[i]; CHECKED(uc_reg_write_batch(uc, const_cast(gpr_ids.data()), reinterpret_cast(const_cast(ptrs.data())), ptrs.size())); } using DoubleExtRegPtrArray = std::array; using DoubleExtRegConstPtrArray = std::array; constexpr std::array double_ext_reg_ids{ UC_ARM_REG_D0, UC_ARM_REG_D1, UC_ARM_REG_D2, UC_ARM_REG_D3, UC_ARM_REG_D4, UC_ARM_REG_D5, UC_ARM_REG_D6, UC_ARM_REG_D7, UC_ARM_REG_D8, UC_ARM_REG_D9, UC_ARM_REG_D10, UC_ARM_REG_D11, UC_ARM_REG_D12, UC_ARM_REG_D13, UC_ARM_REG_D14, UC_ARM_REG_D15, UC_ARM_REG_D16, UC_ARM_REG_D17, UC_ARM_REG_D18, UC_ARM_REG_D19, UC_ARM_REG_D20, UC_ARM_REG_D21, UC_ARM_REG_D22, UC_ARM_REG_D23, UC_ARM_REG_D24, UC_ARM_REG_D25, UC_ARM_REG_D26, UC_ARM_REG_D27, UC_ARM_REG_D28, UC_ARM_REG_D29, UC_ARM_REG_D30, UC_ARM_REG_D31, }; A32Unicorn::ExtRegArray A32Unicorn::GetExtRegs() const { ExtRegArray ext_regs; DoubleExtRegPtrArray ptrs; for (size_t i = 0; i < ptrs.size(); ++i) ptrs[i] = &ext_regs[i*2]; CHECKED(uc_reg_read_batch(uc, const_cast(double_ext_reg_ids.data()), reinterpret_cast(ptrs.data()), ptrs.size())); return ext_regs; } void A32Unicorn::SetExtRegs(const ExtRegArray& value) { DoubleExtRegConstPtrArray ptrs; for (size_t i = 0; i < ptrs.size(); ++i) ptrs[i] = &value[i*2]; CHECKED(uc_reg_write_batch(uc, const_cast(double_ext_reg_ids.data()), reinterpret_cast(const_cast(ptrs.data())), ptrs.size())); } u32 A32Unicorn::GetPC() const { u32 pc; CHECKED(uc_reg_read(uc, UC_ARM_REG_R15, &pc)); return pc; } u32 A32Unicorn::GetFpscr() const { u32 fpsr; CHECKED(uc_reg_read(uc, UC_ARM_REG_FPSCR, &fpsr)); return fpsr; } void A32Unicorn::SetFpscr(u32 value) { CHECKED(uc_reg_write(uc, UC_ARM_REG_FPSCR, &value)); } u32 A32Unicorn::GetCpsr() const { u32 pstate; CHECKED(uc_reg_read(uc, UC_ARM_REG_CPSR, &pstate)); return pstate; } void A32Unicorn::SetCpsr(u32 value) { CHECKED(uc_reg_write(uc, UC_ARM_REG_CPSR, &value)); } void A32Unicorn::ClearPageCache() { for (const auto& page : pages) { CHECKED(uc_mem_unmap(uc, page->address, 4096)); } pages.clear(); } void A32Unicorn::DumpMemoryInformation() { uc_mem_region* regions; u32 count; CHECKED(uc_mem_regions(uc, ®ions, &count)); for (u32 i = 0; i < count; ++i) { printf("region: start 0x%08x end 0x%08x perms 0x%08x\n", static_cast(regions[i].begin), static_cast(regions[i].end), regions[i].perms); } CHECKED(uc_free(regions)); } void A32Unicorn::InterruptHook(uc_engine* /*uc*/, u32 int_number, void* user_data) { auto* this_ = static_cast(user_data); u32 esr = 0; //CHECKED(uc_reg_read(uc, UC_ARM_REG_ESR, &esr)); auto ec = esr >> 26; auto iss = esr & 0xFFFFFF; switch (ec) { case 0x15: // SVC this_->testenv.CallSVC(iss); break; default: this_->testenv.interrupts.emplace_back(fmt::format("Unhandled interrupt: int_number: {:#x}, esr: {:#x} (ec: {:#x}, iss: {:#x})", int_number, esr, ec, iss)); break; } } bool A32Unicorn::UnmappedMemoryHook(uc_engine* uc, uc_mem_type /*type*/, u32 start_address, int size, u64 /*value*/, void* user_data) { auto* this_ = static_cast(user_data); const auto generate_page = [&](u32 base_address) { // printf("generate_page(%x)\n", base_address); const u32 permissions = [&]() -> u32 { if (base_address < this_->testenv.code_mem.size() * 4) return UC_PROT_READ | UC_PROT_EXEC; return UC_PROT_READ; }(); auto page = std::make_unique(); page->address = base_address; for (size_t i = 0; i < page->data.size(); ++i) page->data[i] = this_->testenv.MemoryRead8(static_cast(base_address + i)); uc_err err = uc_mem_map_ptr(uc, base_address, page->data.size(), permissions, page->data.data()); if (err == UC_ERR_MAP) return; // page already exists CHECKED(err); this_->pages.emplace_back(std::move(page)); }; const auto is_in_range = [](u32 addr, u32 start, u32 end) { if (start <= end) return addr >= start && addr <= end; // fffff[tttttt]fffff return addr >= start || addr <= end; // ttttt]ffffff[ttttt }; const u32 start_address_page = start_address & ~u32(0xFFF); const u32 end_address = start_address + size - 1; u32 current_address = start_address_page; do { generate_page(current_address); current_address += 0x1000; } while (is_in_range(current_address, start_address_page, end_address) && current_address != start_address_page); return true; } bool A32Unicorn::MemoryWriteHook(uc_engine* /*uc*/, uc_mem_type /*type*/, u32 start_address, int size, u64 value, void* user_data) { auto* this_ = static_cast(user_data); switch (size) { case 1: this_->testenv.MemoryWrite8(start_address, static_cast(value)); break; case 2: this_->testenv.MemoryWrite16(start_address, static_cast(value)); break; case 4: this_->testenv.MemoryWrite32(start_address, static_cast(value)); break; case 8: this_->testenv.MemoryWrite64(start_address, value); break; default: UNREACHABLE(); } return true; }