a32_unicorn.cpp
1 /* This file is part of the dynarmic project. 2 * Copyright (c) 2018 MerryMage 3 * SPDX-License-Identifier: 0BSD 4 */ 5 6 #include "./a32_unicorn.h" 7 8 #include <type_traits> 9 10 #include <mcl/assert.hpp> 11 #include <mcl/bit/bit_field.hpp> 12 13 #include "../A32/testenv.h" 14 15 #define CHECKED(expr) \ 16 do { \ 17 if (auto cerr_ = (expr)) { \ 18 ASSERT_MSG(false, "Call " #expr " failed with error: {} ({})\n", static_cast<size_t>(cerr_), \ 19 uc_strerror(cerr_)); \ 20 } \ 21 } while (0) 22 23 constexpr u32 BEGIN_ADDRESS = 0; 24 constexpr u32 END_ADDRESS = ~u32(0); 25 26 template<class TestEnvironment> 27 A32Unicorn<TestEnvironment>::A32Unicorn(TestEnvironment& testenv) 28 : testenv{testenv} { 29 constexpr uc_mode open_mode = std::is_same_v<TestEnvironment, ArmTestEnv> ? UC_MODE_ARM : UC_MODE_THUMB; 30 31 CHECKED(uc_open(UC_ARCH_ARM, open_mode, &uc)); 32 CHECKED(uc_hook_add(uc, &intr_hook, UC_HOOK_INTR, (void*)InterruptHook, this, BEGIN_ADDRESS, END_ADDRESS)); 33 CHECKED(uc_hook_add(uc, &mem_invalid_hook, UC_HOOK_MEM_INVALID, (void*)UnmappedMemoryHook, this, BEGIN_ADDRESS, END_ADDRESS)); 34 CHECKED(uc_hook_add(uc, &mem_write_prot_hook, UC_HOOK_MEM_WRITE, (void*)MemoryWriteHook, this, BEGIN_ADDRESS, END_ADDRESS)); 35 } 36 37 template<class TestEnvironment> 38 A32Unicorn<TestEnvironment>::~A32Unicorn() { 39 ClearPageCache(); 40 CHECKED(uc_hook_del(uc, intr_hook)); 41 CHECKED(uc_hook_del(uc, mem_invalid_hook)); 42 CHECKED(uc_close(uc)); 43 } 44 45 template<class TestEnvironment> 46 void A32Unicorn<TestEnvironment>::Run() { 47 // Thumb execution mode requires the LSB to be set to 1. 48 constexpr u64 pc_mask = std::is_same_v<TestEnvironment, ArmTestEnv> ? 0 : 1; 49 while (testenv.ticks_left > 0) { 50 const u32 pc = GetPC() | pc_mask; 51 if (!testenv.IsInCodeMem(pc)) { 52 return; 53 } 54 if (auto cerr_ = uc_emu_start(uc, pc, END_ADDRESS, 0, 1)) { 55 fmt::print("uc_emu_start failed @ {:08x} (code = {:08x}) with error {} ({})", pc, *testenv.MemoryReadCode(pc), static_cast<size_t>(cerr_), uc_strerror(cerr_)); 56 throw "A32Unicorn::Run() failure"; 57 } 58 testenv.ticks_left--; 59 if (!testenv.interrupts.empty() || testenv.code_mem_modified_by_guest) { 60 return; 61 } 62 } 63 64 const bool T = mcl::bit::get_bit<5>(GetCpsr()); 65 const u32 new_pc = GetPC() | (T ? 1 : 0); 66 SetPC(new_pc); 67 } 68 69 template<class TestEnvironment> 70 u32 A32Unicorn<TestEnvironment>::GetPC() const { 71 u32 pc; 72 CHECKED(uc_reg_read(uc, UC_ARM_REG_PC, &pc)); 73 return pc; 74 } 75 76 template<class TestEnvironment> 77 void A32Unicorn<TestEnvironment>::SetPC(u32 value) { 78 CHECKED(uc_reg_write(uc, UC_ARM_REG_PC, &value)); 79 } 80 81 template<class TestEnvironment> 82 u32 A32Unicorn<TestEnvironment>::GetSP() const { 83 u32 sp; 84 CHECKED(uc_reg_read(uc, UC_ARM_REG_SP, &sp)); 85 return sp; 86 } 87 88 template<class TestEnvironment> 89 void A32Unicorn<TestEnvironment>::SetSP(u32 value) { 90 CHECKED(uc_reg_write(uc, UC_ARM_REG_SP, &value)); 91 } 92 93 constexpr std::array<int, Unicorn::A32::num_gprs> gpr_ids{ 94 UC_ARM_REG_R0, 95 UC_ARM_REG_R1, 96 UC_ARM_REG_R2, 97 UC_ARM_REG_R3, 98 UC_ARM_REG_R4, 99 UC_ARM_REG_R5, 100 UC_ARM_REG_R6, 101 UC_ARM_REG_R7, 102 UC_ARM_REG_R8, 103 UC_ARM_REG_R9, 104 UC_ARM_REG_R10, 105 UC_ARM_REG_R11, 106 UC_ARM_REG_R12, 107 UC_ARM_REG_R13, 108 UC_ARM_REG_R14, 109 UC_ARM_REG_R15, 110 }; 111 112 template<class TestEnvironment> 113 Unicorn::A32::RegisterArray A32Unicorn<TestEnvironment>::GetRegisters() const { 114 Unicorn::A32::RegisterArray regs{}; 115 Unicorn::A32::RegisterPtrArray ptrs; 116 for (size_t i = 0; i < ptrs.size(); ++i) { 117 ptrs[i] = ®s[i]; 118 } 119 120 CHECKED(uc_reg_read_batch(uc, const_cast<int*>(gpr_ids.data()), 121 reinterpret_cast<void**>(ptrs.data()), static_cast<int>(Unicorn::A32::num_gprs))); 122 return regs; 123 } 124 125 template<class TestEnvironment> 126 void A32Unicorn<TestEnvironment>::SetRegisters(const RegisterArray& value) { 127 Unicorn::A32::RegisterConstPtrArray ptrs; 128 for (size_t i = 0; i < ptrs.size(); ++i) { 129 ptrs[i] = &value[i]; 130 } 131 132 CHECKED(uc_reg_write_batch(uc, const_cast<int*>(gpr_ids.data()), 133 reinterpret_cast<void**>(const_cast<u32**>(ptrs.data())), static_cast<int>(ptrs.size()))); 134 } 135 136 using DoubleExtRegPtrArray = std::array<Unicorn::A32::ExtRegArray::pointer, Unicorn::A32::num_ext_regs / 2>; 137 using DoubleExtRegConstPtrArray = std::array<Unicorn::A32::ExtRegArray::const_pointer, Unicorn::A32::num_ext_regs / 2>; 138 139 constexpr std::array<int, Unicorn::A32::num_ext_regs / 2> double_ext_reg_ids{ 140 UC_ARM_REG_D0, 141 UC_ARM_REG_D1, 142 UC_ARM_REG_D2, 143 UC_ARM_REG_D3, 144 UC_ARM_REG_D4, 145 UC_ARM_REG_D5, 146 UC_ARM_REG_D6, 147 UC_ARM_REG_D7, 148 UC_ARM_REG_D8, 149 UC_ARM_REG_D9, 150 UC_ARM_REG_D10, 151 UC_ARM_REG_D11, 152 UC_ARM_REG_D12, 153 UC_ARM_REG_D13, 154 UC_ARM_REG_D14, 155 UC_ARM_REG_D15, 156 UC_ARM_REG_D16, 157 UC_ARM_REG_D17, 158 UC_ARM_REG_D18, 159 UC_ARM_REG_D19, 160 UC_ARM_REG_D20, 161 UC_ARM_REG_D21, 162 UC_ARM_REG_D22, 163 UC_ARM_REG_D23, 164 UC_ARM_REG_D24, 165 UC_ARM_REG_D25, 166 UC_ARM_REG_D26, 167 UC_ARM_REG_D27, 168 UC_ARM_REG_D28, 169 UC_ARM_REG_D29, 170 UC_ARM_REG_D30, 171 UC_ARM_REG_D31, 172 }; 173 174 template<class TestEnvironment> 175 Unicorn::A32::ExtRegArray A32Unicorn<TestEnvironment>::GetExtRegs() const { 176 Unicorn::A32::ExtRegArray ext_regs{}; 177 DoubleExtRegPtrArray ptrs; 178 for (size_t i = 0; i < ptrs.size(); ++i) 179 ptrs[i] = &ext_regs[i * 2]; 180 181 CHECKED(uc_reg_read_batch(uc, const_cast<int*>(double_ext_reg_ids.data()), 182 reinterpret_cast<void**>(ptrs.data()), static_cast<int>(ptrs.size()))); 183 184 return ext_regs; 185 } 186 187 template<class TestEnvironment> 188 void A32Unicorn<TestEnvironment>::SetExtRegs(const ExtRegArray& value) { 189 DoubleExtRegConstPtrArray ptrs; 190 for (size_t i = 0; i < ptrs.size(); ++i) { 191 ptrs[i] = &value[i * 2]; 192 } 193 194 CHECKED(uc_reg_write_batch(uc, const_cast<int*>(double_ext_reg_ids.data()), 195 reinterpret_cast<void* const*>(const_cast<u32**>(ptrs.data())), static_cast<int>(ptrs.size()))); 196 } 197 198 template<class TestEnvironment> 199 u32 A32Unicorn<TestEnvironment>::GetFpscr() const { 200 u32 fpsr; 201 CHECKED(uc_reg_read(uc, UC_ARM_REG_FPSCR, &fpsr)); 202 return fpsr; 203 } 204 205 template<class TestEnvironment> 206 void A32Unicorn<TestEnvironment>::SetFpscr(u32 value) { 207 CHECKED(uc_reg_write(uc, UC_ARM_REG_FPSCR, &value)); 208 } 209 210 template<class TestEnvironment> 211 u32 A32Unicorn<TestEnvironment>::GetFpexc() const { 212 u32 value = 0; 213 CHECKED(uc_reg_read(uc, UC_ARM_REG_FPEXC, &value)); 214 return value; 215 } 216 217 template<class TestEnvironment> 218 void A32Unicorn<TestEnvironment>::SetFpexc(u32 value) { 219 CHECKED(uc_reg_write(uc, UC_ARM_REG_FPEXC, &value)); 220 } 221 222 template<class TestEnvironment> 223 u32 A32Unicorn<TestEnvironment>::GetCpsr() const { 224 u32 pstate; 225 CHECKED(uc_reg_read(uc, UC_ARM_REG_CPSR, &pstate)); 226 return pstate; 227 } 228 229 template<class TestEnvironment> 230 void A32Unicorn<TestEnvironment>::SetCpsr(u32 value) { 231 CHECKED(uc_reg_write(uc, UC_ARM_REG_CPSR, &value)); 232 } 233 234 template<class TestEnvironment> 235 void A32Unicorn<TestEnvironment>::EnableFloatingPointAccess() { 236 const u32 new_fpexc = GetFpexc() | (1U << 30); 237 SetFpexc(new_fpexc); 238 } 239 240 template<class TestEnvironment> 241 void A32Unicorn<TestEnvironment>::ClearPageCache() { 242 for (const auto& page : pages) { 243 CHECKED(uc_mem_unmap(uc, page->address, 4096)); 244 } 245 pages.clear(); 246 } 247 248 template<class TestEnvironment> 249 void A32Unicorn<TestEnvironment>::DumpMemoryInformation() { 250 uc_mem_region* regions; 251 u32 count; 252 CHECKED(uc_mem_regions(uc, ®ions, &count)); 253 254 for (u32 i = 0; i < count; ++i) { 255 printf("region: start 0x%08x end 0x%08x perms 0x%08x\n", static_cast<u32>(regions[i].begin), static_cast<u32>(regions[i].end), regions[i].perms); 256 } 257 258 CHECKED(uc_free(regions)); 259 } 260 261 template<class TestEnvironment> 262 void A32Unicorn<TestEnvironment>::InterruptHook(uc_engine* /*uc*/, u32 int_number, void* user_data) { 263 auto* this_ = static_cast<A32Unicorn*>(user_data); 264 265 u32 esr = 0; 266 // CHECKED(uc_reg_read(uc, UC_ARM_REG_ESR, &esr)); 267 268 auto ec = esr >> 26; 269 auto iss = esr & 0xFFFFFF; 270 271 switch (ec) { 272 case 0x15: // SVC 273 this_->testenv.CallSVC(iss); 274 break; 275 default: 276 this_->testenv.interrupts.emplace_back(fmt::format("Unhandled interrupt: int_number: {:#x}, esr: {:#x} (ec: {:#x}, iss: {:#x})", int_number, esr, ec, iss)); 277 break; 278 } 279 } 280 281 template<class TestEnvironment> 282 bool A32Unicorn<TestEnvironment>::UnmappedMemoryHook(uc_engine* uc, uc_mem_type /*type*/, u32 start_address, int size, u64 /*value*/, void* user_data) { 283 auto* this_ = static_cast<A32Unicorn*>(user_data); 284 285 const auto generate_page = [&](u32 base_address) { 286 // printf("generate_page(%x)\n", base_address); 287 288 const u32 permissions = [&]() -> u32 { 289 if (base_address < this_->testenv.code_mem.size() * sizeof(typename TestEnvironment::InstructionType)) { 290 return UC_PROT_READ | UC_PROT_EXEC; 291 } 292 return UC_PROT_READ; 293 }(); 294 295 auto page = std::make_unique<Page>(); 296 page->address = base_address; 297 for (size_t i = 0; i < page->data.size(); ++i) 298 page->data[i] = this_->testenv.MemoryRead8(static_cast<u32>(base_address + i)); 299 300 uc_err err = uc_mem_map_ptr(uc, base_address, page->data.size(), permissions, page->data.data()); 301 if (err == UC_ERR_MAP) 302 return; // page already exists 303 CHECKED(err); 304 305 this_->pages.emplace_back(std::move(page)); 306 }; 307 308 const auto is_in_range = [](u32 addr, u32 start, u32 end) { 309 if (start <= end) 310 return addr >= start && addr <= end; // fffff[tttttt]fffff 311 return addr >= start || addr <= end; // ttttt]ffffff[ttttt 312 }; 313 314 const u32 start_address_page = start_address & ~u32(0xFFF); 315 const u32 end_address = start_address + size - 1; 316 317 u32 current_address = start_address_page; 318 do { 319 generate_page(current_address); 320 current_address += 0x1000; 321 } while (is_in_range(current_address, start_address_page, end_address) && current_address != start_address_page); 322 323 return true; 324 } 325 326 template<class TestEnvironment> 327 bool A32Unicorn<TestEnvironment>::MemoryWriteHook(uc_engine* /*uc*/, uc_mem_type /*type*/, u32 start_address, int size, u64 value, void* user_data) { 328 auto* this_ = static_cast<A32Unicorn*>(user_data); 329 330 switch (size) { 331 case 1: 332 this_->testenv.MemoryWrite8(start_address, static_cast<u8>(value)); 333 break; 334 case 2: 335 this_->testenv.MemoryWrite16(start_address, static_cast<u16>(value)); 336 break; 337 case 4: 338 this_->testenv.MemoryWrite32(start_address, static_cast<u32>(value)); 339 break; 340 case 8: 341 this_->testenv.MemoryWrite64(start_address, value); 342 break; 343 default: 344 UNREACHABLE(); 345 } 346 347 return true; 348 } 349 350 template class A32Unicorn<ArmTestEnv>; 351 template class A32Unicorn<ThumbTestEnv>;