/ tests / unicorn_emu / a64_unicorn.cpp
a64_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 "./a64_unicorn.h"
  7  
  8  #include <mcl/assert.hpp>
  9  
 10  #define CHECKED(expr)                                                                                    \
 11      do {                                                                                                 \
 12          if (auto cerr_ = (expr)) {                                                                       \
 13              ASSERT_MSG(false, "Call " #expr " failed with error: {} ({})\n", static_cast<size_t>(cerr_), \
 14                         uc_strerror(cerr_));                                                              \
 15          }                                                                                                \
 16      } while (0)
 17  
 18  constexpr u64 BEGIN_ADDRESS = 0;
 19  constexpr u64 END_ADDRESS = ~u64(0);
 20  
 21  A64Unicorn::A64Unicorn(A64TestEnv& testenv)
 22          : testenv(testenv) {
 23      CHECKED(uc_open(UC_ARCH_ARM64, UC_MODE_ARM, &uc));
 24      u64 fpv = 3 << 20;
 25      CHECKED(uc_reg_write(uc, UC_ARM64_REG_CPACR_EL1, &fpv));
 26      CHECKED(uc_hook_add(uc, &intr_hook, UC_HOOK_INTR, (void*)InterruptHook, this, BEGIN_ADDRESS, END_ADDRESS));
 27      CHECKED(uc_hook_add(uc, &mem_invalid_hook, UC_HOOK_MEM_INVALID, (void*)UnmappedMemoryHook, this, BEGIN_ADDRESS, END_ADDRESS));
 28      CHECKED(uc_hook_add(uc, &mem_write_prot_hook, UC_HOOK_MEM_WRITE, (void*)MemoryWriteHook, this, BEGIN_ADDRESS, END_ADDRESS));
 29  }
 30  
 31  A64Unicorn::~A64Unicorn() {
 32      ClearPageCache();
 33      CHECKED(uc_hook_del(uc, intr_hook));
 34      CHECKED(uc_hook_del(uc, mem_invalid_hook));
 35      CHECKED(uc_close(uc));
 36  }
 37  
 38  void A64Unicorn::Run() {
 39      while (testenv.ticks_left > 0) {
 40          CHECKED(uc_emu_start(uc, GetPC(), END_ADDRESS, 0, 1));
 41          testenv.ticks_left--;
 42          if (!testenv.interrupts.empty() || testenv.code_mem_modified_by_guest) {
 43              return;
 44          }
 45      }
 46  }
 47  
 48  u64 A64Unicorn::GetSP() const {
 49      u64 sp;
 50      CHECKED(uc_reg_read(uc, UC_ARM64_REG_SP, &sp));
 51      return sp;
 52  }
 53  void A64Unicorn::SetSP(u64 value) {
 54      CHECKED(uc_reg_write(uc, UC_ARM64_REG_SP, &value));
 55  }
 56  
 57  u64 A64Unicorn::GetPC() const {
 58      u64 pc;
 59      CHECKED(uc_reg_read(uc, UC_ARM64_REG_PC, &pc));
 60      return pc;
 61  }
 62  
 63  void A64Unicorn::SetPC(u64 value) {
 64      CHECKED(uc_reg_write(uc, UC_ARM64_REG_PC, &value));
 65  }
 66  
 67  constexpr std::array<int, A64Unicorn::num_gprs> gpr_ids{
 68      UC_ARM64_REG_X0, UC_ARM64_REG_X1, UC_ARM64_REG_X2, UC_ARM64_REG_X3, UC_ARM64_REG_X4, UC_ARM64_REG_X5, UC_ARM64_REG_X6, UC_ARM64_REG_X7,
 69      UC_ARM64_REG_X8, UC_ARM64_REG_X9, UC_ARM64_REG_X10, UC_ARM64_REG_X11, UC_ARM64_REG_X12, UC_ARM64_REG_X13, UC_ARM64_REG_X14, UC_ARM64_REG_X15,
 70      UC_ARM64_REG_X16, UC_ARM64_REG_X17, UC_ARM64_REG_X18, UC_ARM64_REG_X19, UC_ARM64_REG_X20, UC_ARM64_REG_X21, UC_ARM64_REG_X22, UC_ARM64_REG_X23,
 71      UC_ARM64_REG_X24, UC_ARM64_REG_X25, UC_ARM64_REG_X26, UC_ARM64_REG_X27, UC_ARM64_REG_X28, UC_ARM64_REG_X29, UC_ARM64_REG_X30};
 72  
 73  A64Unicorn::RegisterArray A64Unicorn::GetRegisters() const {
 74      RegisterArray regs{};
 75      RegisterPtrArray ptrs;
 76      for (size_t i = 0; i < ptrs.size(); ++i)
 77          ptrs[i] = &regs[i];
 78  
 79      CHECKED(uc_reg_read_batch(uc, const_cast<int*>(gpr_ids.data()),
 80                                reinterpret_cast<void**>(ptrs.data()), static_cast<int>(num_gprs)));
 81      return regs;
 82  }
 83  
 84  void A64Unicorn::SetRegisters(const RegisterArray& value) {
 85      RegisterConstPtrArray ptrs;
 86      for (size_t i = 0; i < ptrs.size(); ++i)
 87          ptrs[i] = &value[i];
 88  
 89      CHECKED(uc_reg_write_batch(uc, const_cast<int*>(gpr_ids.data()),
 90                                 reinterpret_cast<void**>(const_cast<u64**>(ptrs.data())), static_cast<int>(num_gprs)));
 91  }
 92  
 93  constexpr std::array<int, A64Unicorn::num_vecs> vec_ids{
 94      UC_ARM64_REG_Q0, UC_ARM64_REG_Q1, UC_ARM64_REG_Q2, UC_ARM64_REG_Q3, UC_ARM64_REG_Q4, UC_ARM64_REG_Q5, UC_ARM64_REG_Q6, UC_ARM64_REG_Q7,
 95      UC_ARM64_REG_Q8, UC_ARM64_REG_Q9, UC_ARM64_REG_Q10, UC_ARM64_REG_Q11, UC_ARM64_REG_Q12, UC_ARM64_REG_Q13, UC_ARM64_REG_Q14, UC_ARM64_REG_Q15,
 96      UC_ARM64_REG_Q16, UC_ARM64_REG_Q17, UC_ARM64_REG_Q18, UC_ARM64_REG_Q19, UC_ARM64_REG_Q20, UC_ARM64_REG_Q21, UC_ARM64_REG_Q22, UC_ARM64_REG_Q23,
 97      UC_ARM64_REG_Q24, UC_ARM64_REG_Q25, UC_ARM64_REG_Q26, UC_ARM64_REG_Q27, UC_ARM64_REG_Q28, UC_ARM64_REG_Q29, UC_ARM64_REG_Q30, UC_ARM64_REG_Q31};
 98  
 99  A64Unicorn::VectorArray A64Unicorn::GetVectors() const {
100      VectorArray vecs{};
101      VectorPtrArray ptrs;
102      for (size_t i = 0; i < ptrs.size(); ++i)
103          ptrs[i] = &vecs[i];
104  
105      CHECKED(uc_reg_read_batch(uc, const_cast<int*>(vec_ids.data()),
106                                reinterpret_cast<void**>(ptrs.data()), static_cast<int>(num_vecs)));
107  
108      return vecs;
109  }
110  
111  void A64Unicorn::SetVectors(const VectorArray& value) {
112      VectorConstPtrArray ptrs;
113      for (size_t i = 0; i < ptrs.size(); ++i)
114          ptrs[i] = &value[i];
115  
116      CHECKED(uc_reg_write_batch(uc, const_cast<int*>(vec_ids.data()),
117                                 reinterpret_cast<void* const*>(const_cast<Vector**>(ptrs.data())), static_cast<int>(num_vecs)));
118  }
119  
120  u32 A64Unicorn::GetFpcr() const {
121      u32 fpcr;
122      CHECKED(uc_reg_read(uc, UC_ARM64_REG_FPCR, &fpcr));
123      return fpcr;
124  }
125  
126  void A64Unicorn::SetFpcr(u32 value) {
127      CHECKED(uc_reg_write(uc, UC_ARM64_REG_FPCR, &value));
128  }
129  
130  u32 A64Unicorn::GetFpsr() const {
131      u32 fpsr;
132      CHECKED(uc_reg_read(uc, UC_ARM64_REG_FPSR, &fpsr));
133      return fpsr;
134  }
135  
136  void A64Unicorn::SetFpsr(u32 value) {
137      CHECKED(uc_reg_write(uc, UC_ARM64_REG_FPSR, &value));
138  }
139  
140  u32 A64Unicorn::GetPstate() const {
141      u32 pstate;
142      CHECKED(uc_reg_read(uc, UC_ARM64_REG_NZCV, &pstate));
143      return pstate;
144  }
145  
146  void A64Unicorn::SetPstate(u32 value) {
147      CHECKED(uc_reg_write(uc, UC_ARM64_REG_NZCV, &value));
148  }
149  
150  void A64Unicorn::ClearPageCache() {
151      for (const auto& page : pages) {
152          CHECKED(uc_mem_unmap(uc, page->address, 4096));
153      }
154      pages.clear();
155  }
156  
157  void A64Unicorn::DumpMemoryInformation() {
158      uc_mem_region* regions;
159      u32 count;
160      CHECKED(uc_mem_regions(uc, &regions, &count));
161  
162      for (u32 i = 0; i < count; ++i) {
163          printf("region: start 0x%016" PRIx64 " end 0x%016" PRIx64 " perms 0x%08x\n", regions[i].begin, regions[i].end, regions[i].perms);
164      }
165  
166      CHECKED(uc_free(regions));
167  }
168  
169  void A64Unicorn::InterruptHook(uc_engine* uc, u32 int_number, void* user_data) {
170      auto* this_ = static_cast<A64Unicorn*>(user_data);
171  
172      u32 esr;
173      CHECKED(uc_reg_read(uc, UC_ARM64_REG_ESR, &esr));
174  
175      auto ec = esr >> 26;
176      auto iss = esr & 0xFFFFFF;
177  
178      switch (ec) {
179      case 0x15:  // SVC
180          this_->testenv.CallSVC(iss);
181          break;
182      default:
183          this_->testenv.interrupts.emplace_back(fmt::format("Unhandled interrupt: int_number: {:#x}, esr: {:#x} (ec: {:#x}, iss: {:#x})", int_number, esr, ec, iss));
184          break;
185      }
186  }
187  
188  bool A64Unicorn::UnmappedMemoryHook(uc_engine* uc, uc_mem_type /*type*/, u64 start_address, int size, u64 /*value*/, void* user_data) {
189      auto* this_ = static_cast<A64Unicorn*>(user_data);
190  
191      const auto generate_page = [&](u64 base_address) {
192          // printf("generate_page(%" PRIx64 ")\n", base_address);
193  
194          const u32 permissions = [&]() -> u32 {
195              if (base_address < this_->testenv.code_mem.size() * 4)
196                  return UC_PROT_READ | UC_PROT_EXEC;
197              return UC_PROT_READ;
198          }();
199  
200          auto page = std::make_unique<Page>();
201          page->address = base_address;
202          for (size_t i = 0; i < page->data.size(); ++i)
203              page->data[i] = this_->testenv.MemoryRead8(base_address + i);
204  
205          uc_err err = uc_mem_map_ptr(uc, base_address, page->data.size(), permissions, page->data.data());
206          if (err == UC_ERR_MAP)
207              return;  // page already exists
208          CHECKED(err);
209  
210          this_->pages.emplace_back(std::move(page));
211      };
212  
213      const auto is_in_range = [](u64 addr, u64 start, u64 end) {
214          if (start <= end)
215              return addr >= start && addr <= end;  // fffff[tttttt]fffff
216          return addr >= start || addr <= end;      // ttttt]ffffff[ttttt
217      };
218  
219      const u64 start_address_page = start_address & ~u64(0xFFF);
220      const u64 end_address = start_address + size - 1;
221  
222      u64 current_address = start_address_page;
223      do {
224          generate_page(current_address);
225          current_address += 0x1000;
226      } while (is_in_range(current_address, start_address_page, end_address) && current_address != start_address_page);
227  
228      return true;
229  }
230  
231  bool A64Unicorn::MemoryWriteHook(uc_engine* /*uc*/, uc_mem_type /*type*/, u64 start_address, int size, u64 value, void* user_data) {
232      auto* this_ = static_cast<A64Unicorn*>(user_data);
233  
234      switch (size) {
235      case 1:
236          this_->testenv.MemoryWrite8(start_address, static_cast<u8>(value));
237          break;
238      case 2:
239          this_->testenv.MemoryWrite16(start_address, static_cast<u16>(value));
240          break;
241      case 4:
242          this_->testenv.MemoryWrite32(start_address, static_cast<u32>(value));
243          break;
244      case 8:
245          this_->testenv.MemoryWrite64(start_address, value);
246          break;
247      default:
248          UNREACHABLE();
249      }
250  
251      return true;
252  }