/ tests / unicorn_emu / a32_unicorn.cpp
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] = &regs[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, &regions, &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>;