From d5725de26afafb8a8e36bfa6f04159581e858fbb Mon Sep 17 00:00:00 2001 From: MerryMage Date: Sat, 13 Jan 2018 18:04:19 +0000 Subject: [PATCH] tests/A64: Fuzz against unicorn --- CMakeLists.txt | 8 + CMakeModules/FindUnicorn.cmake | 17 +++ tests/A32/fuzz_arm.cpp | 2 + tests/A64/fuzz_with_unicorn.cpp | 168 +++++++++++++++++++++ tests/A64/inst_gen.cpp | 40 +++++ tests/A64/inst_gen.h | 37 +++++ tests/A64/unicorn_emu/unicorn.cpp | 199 +++++++++++++++++++++++++ tests/A64/unicorn_emu/unicorn.h | 64 ++++++++ tests/A64/unicorn_emu/unicorn_load.cpp | 22 +++ tests/CMakeLists.txt | 12 ++ 10 files changed, 569 insertions(+) create mode 100644 CMakeModules/FindUnicorn.cmake create mode 100644 tests/A64/fuzz_with_unicorn.cpp create mode 100644 tests/A64/inst_gen.cpp create mode 100644 tests/A64/inst_gen.h create mode 100644 tests/A64/unicorn_emu/unicorn.cpp create mode 100644 tests/A64/unicorn_emu/unicorn.h create mode 100644 tests/A64/unicorn_emu/unicorn_load.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b38ad37..2cc6e2b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ endif() option(DYNARMIC_USE_SYSTEM_BOOST "Use the system boost libraries" ON) option(DYNARMIC_USE_LLVM "Support disassembly of jitted x86_64 code using LLVM" OFF) option(DYNARMIC_TESTS "Build tests" ${MASTER_PROJECT}) +option(DYNARMIC_TESTS_USE_UNICORN "Enable fuzzing tests against unicorn" OFF) option(DYNARMIC_WARNINGS_AS_ERRORS "Warnings as errors" ${MASTER_PROJECT}) # Default to a Release build @@ -125,6 +126,13 @@ if (DYNARMIC_USE_LLVM) llvm_map_components_to_libnames(llvm_libs x86desc x86disassembler) endif() +if (DYNARMIC_TESTS_USE_UNICORN) + find_package(unicorn REQUIRED) + add_library(unicorn INTERFACE) + target_link_libraries(unicorn INTERFACE "${LIBUNICORN_LIBRARY}") + target_include_directories(unicorn INTERFACE "${LIBUNICORN_INCLUDE_DIR}") +endif() + # Pull in externals CMakeLists for libs where available add_subdirectory(externals) diff --git a/CMakeModules/FindUnicorn.cmake b/CMakeModules/FindUnicorn.cmake new file mode 100644 index 00000000..35590b0c --- /dev/null +++ b/CMakeModules/FindUnicorn.cmake @@ -0,0 +1,17 @@ +# Exports: +# LIBUNICORN_FOUND +# LIBUNICORN_INCLUDE_DIR +# LIBUNICORN_LIBRARY + +find_path(LIBUNICORN_INCLUDE_DIR + unicorn/unicorn.h + HINTS $ENV{UNICORNDIR} + PATH_SUFFIXES include) + +find_library(LIBUNICORN_LIBRARY + NAMES unicorn + HINTS $ENV{UNICORNDIR}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(unicorn DEFAULT_MSG LIBUNICORN_LIBRARY LIBUNICORN_INCLUDE_DIR) +mark_as_advanced(LIBUNICORN_INCLUDE_DIR LIBUNICORN_LIBRARY) diff --git a/tests/A32/fuzz_arm.cpp b/tests/A32/fuzz_arm.cpp index 79dd5d68..283972aa 100644 --- a/tests/A32/fuzz_arm.cpp +++ b/tests/A32/fuzz_arm.cpp @@ -159,6 +159,7 @@ static Dynarmic::A32::UserCallbacks GetUserCallbacks() { return user_callbacks; } +namespace { struct InstructionGenerator final { public: InstructionGenerator(const char* format, std::function is_valid = [](u32){ return true; }) : is_valid(is_valid) { @@ -207,6 +208,7 @@ private: u32 mask = 0; std::function is_valid; }; +} // namespace static bool DoesBehaviorMatch(const ARMul_State& interp, const Dynarmic::A32::Jit& jit, const std::vector& interp_write_records, const std::vector& jit_write_records) { return interp.Reg == jit.Regs() diff --git a/tests/A64/fuzz_with_unicorn.cpp b/tests/A64/fuzz_with_unicorn.cpp new file mode 100644 index 00000000..52467fba --- /dev/null +++ b/tests/A64/fuzz_with_unicorn.cpp @@ -0,0 +1,168 @@ +/* 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 + +#include +#include + +#include "frontend/A64/location_descriptor.h" +#include "frontend/A64/translate/translate.h" +#include "frontend/ir/basic_block.h" +#include "inst_gen.h" +#include "rand_int.h" +#include "testenv.h" +#include "unicorn_emu/unicorn.h" + +using namespace Dynarmic; + +TEST_CASE("A64: Unicorn sanity test", "[a64]") { + TestEnv env; + env.code_mem[0] = 0x8b020020; // ADD X0, X1, X2 + env.code_mem[1] = 0x14000000; // B . + + std::array regs { + 0, 1, 2, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0 + }; + + Unicorn unicorn{env}; + + unicorn.SetRegisters(regs); + unicorn.SetPC(0); + + env.ticks_left = 2; + unicorn.Run(); + + REQUIRE(unicorn.GetRegisters()[0] == 3); + REQUIRE(unicorn.GetRegisters()[1] == 1); + REQUIRE(unicorn.GetRegisters()[2] == 2); + REQUIRE(unicorn.GetPC() == 4); +} + +TEST_CASE("A64: Ensure 0xFFFF'FFFF'FFFF'FFFF is readable", "[a64]") { + TestEnv env; + + env.code_mem[0] = 0x385fed99; // LDRB W25, [X12, #0xfffffffffffffffe]! + env.code_mem[1] = 0x14000000; // B . + + std::array regs{}; + regs[12] = 1; + + Unicorn unicorn{env}; + + unicorn.SetRegisters(regs); + unicorn.SetPC(0); + + env.ticks_left = 2; + unicorn.Run(); + + REQUIRE(unicorn.GetPC() == 4); +} + +TEST_CASE("A64: Ensure is able to read across page boundaries", "[a64]") { + TestEnv env; + + env.code_mem[0] = 0xb85f93d9; // LDUR W25, [X30, #0xfffffffffffffff9] + env.code_mem[1] = 0x14000000; // B . + + std::array regs{}; + regs[30] = 4; + + Unicorn unicorn{env}; + + unicorn.SetRegisters(regs); + unicorn.SetPC(0); + + env.ticks_left = 2; + unicorn.Run(); + + REQUIRE(unicorn.GetPC() == 4); +} + +static std::vector instruction_generators = []{ + const std::vector> list { +#define INST(fn, name, bitstring) {#fn, bitstring}, +#include "frontend/A64/decoder/a64.inc" +#undef INST + }; + + std::vector result; + for (const auto& [fn, bitstring] : list) { + if (std::strcmp(fn, "UnallocatedEncoding") == 0) { + InstructionGenerator::AddInvalidInstruction(bitstring); + continue; + } + result.emplace_back(InstructionGenerator{bitstring}); + } + return result; +}(); + +static u32 GenRandomInst(u64 pc) { + const A64::LocationDescriptor location{pc, {}}; + +restart: + const size_t index = RandInt(0, instruction_generators.size() - 1); + const u32 instruction = instruction_generators[index].Generate(); + + IR::Block block{location}; + bool should_continue = A64::TranslateSingleInstruction(block, location, instruction); + if (!should_continue) + goto restart; + for (const auto& ir_inst : block) + if (ir_inst.CausesCPUException() || ir_inst.IsMemoryWrite() || ir_inst.GetOpcode() == IR::Opcode::A64ExceptionRaised) + goto restart; + + return instruction; +} + +static void TestInstance(const std::array& regs, const std::vector& instructions) { + TestEnv jit_env; + TestEnv uni_env; + + std::copy(instructions.begin(), instructions.end(), jit_env.code_mem.begin()); + std::copy(instructions.begin(), instructions.end(), uni_env.code_mem.begin()); + jit_env.code_mem[instructions.size()] = 0x14000000; // B . + uni_env.code_mem[instructions.size()] = 0x14000000; // B . + + Dynarmic::A64::Jit jit{Dynarmic::A64::UserConfig{&jit_env}}; + Unicorn uni{uni_env}; + + jit.SetRegisters(regs); + jit.SetPC(0); + jit.SetSP(0x8000000); + jit.SetPstate(0); + uni.SetRegisters(regs); + uni.SetPC(0); + uni.SetSP(0x8000000); + uni.SetPstate(0); + + jit_env.ticks_left = instructions.size(); + jit.Run(); + + uni_env.ticks_left = instructions.size(); + uni.Run(); + + REQUIRE(uni.GetRegisters() == jit.GetRegisters()); + REQUIRE(uni.GetPC() == jit.GetPC()); + REQUIRE(uni.GetSP() == jit.GetSP()); + REQUIRE((uni.GetPstate() & 0xF0000000) == (jit.GetPstate() & 0xF0000000)); +} + +TEST_CASE("A64: Single random instruction", "[a64]") { + for (size_t iteration = 0; iteration < 1000000; ++iteration) { + std::array regs; + std::generate_n(regs.begin(), 31, []{ return RandInt(0, ~u64(0)); }); + std::vector instructions; + instructions.push_back(GenRandomInst(0)); + + printf("%zu: %08x\n", iteration, instructions[0]); + + TestInstance(regs, instructions); + } +} \ No newline at end of file diff --git a/tests/A64/inst_gen.cpp b/tests/A64/inst_gen.cpp new file mode 100644 index 00000000..977531f6 --- /dev/null +++ b/tests/A64/inst_gen.cpp @@ -0,0 +1,40 @@ +/* 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 "common/assert.h" +#include "inst_gen.h" +#include "rand_int.h" + +InstructionGenerator::InstructionGenerator(const char* format){ + ASSERT(std::strlen(format) == 32); + + for (int i = 0; i < 32; i++) { + const u32 bit = 1u << (31 - i); + switch (format[i]) { + case '0': + mask |= bit; + break; + case '1': + bits |= bit; + mask |= bit; + break; + default: + // Do nothing + break; + } + } +} + +u32 InstructionGenerator::Generate() const { + u32 inst; + do { + u32 random = RandInt(0, 0xFFFFFFFF); + inst = bits | (random & ~mask); + } while (IsInvalidInstruction(inst)); + return inst; +} + +std::vector InstructionGenerator::invalid_instructions; diff --git a/tests/A64/inst_gen.h b/tests/A64/inst_gen.h new file mode 100644 index 00000000..cb29cebb --- /dev/null +++ b/tests/A64/inst_gen.h @@ -0,0 +1,37 @@ +/* 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 +#include + +#include "common/common_types.h" + +struct InstructionGenerator final { +public: + explicit InstructionGenerator(const char* format); + + u32 Generate() const; + u32 Bits() const { return bits; } + u32 Mask() const { return mask; } + bool Match(u32 inst) const { return (inst & mask) == bits; } + + static void AddInvalidInstruction(const char* format) { + invalid_instructions.emplace_back(InstructionGenerator{format}); + } + + static bool IsInvalidInstruction(u32 inst) { + for (const auto& invalid : invalid_instructions) + if (invalid.Match(inst)) + return true; + return false; + } + +private: + static std::vector invalid_instructions; + + u32 bits = 0; + u32 mask = 0; +}; diff --git a/tests/A64/unicorn_emu/unicorn.cpp b/tests/A64/unicorn_emu/unicorn.cpp new file mode 100644 index 00000000..62ec8924 --- /dev/null +++ b/tests/A64/unicorn_emu/unicorn.cpp @@ -0,0 +1,199 @@ +/* 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 "common/assert.h" +#include "unicorn.h" + +#define CHECKED(expr) \ + do { \ + if (auto cerr_ = (expr)) { \ + ASSERT_MSG(false, "Call " #expr " failed with error: %u (%s)\n", cerr_, \ + uc_strerror(cerr_)); \ + } \ + } while (0) + +constexpr u64 BEGIN_ADDRESS = 0; +constexpr u64 END_ADDRESS = ~u64(0); + +Unicorn::Unicorn(TestEnv& testenv) : testenv(testenv) { + CHECKED(uc_open(UC_ARCH_ARM64, UC_MODE_ARM, &uc)); + u64 fpv = 3 << 20; + CHECKED(uc_reg_write(uc, UC_ARM64_REG_CPACR_EL1, &fpv)); + 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)); +} + +Unicorn::~Unicorn() { + for (const auto& page : pages) { + CHECKED(uc_mem_unmap(uc, page->address, 4096)); + delete page; + } + CHECKED(uc_hook_del(uc, intr_hook)); + CHECKED(uc_hook_del(uc, mem_invalid_hook)); + CHECKED(uc_close(uc)); +} + +void Unicorn::Run() { + CHECKED(uc_emu_start(uc, GetPC(), END_ADDRESS, 0, testenv.ticks_left)); + testenv.ticks_left = 0; +} + +u64 Unicorn::GetSP() const { + u64 sp; + CHECKED(uc_reg_read(uc, UC_ARM64_REG_SP, &sp)); + return sp; +} +void Unicorn::SetSP(u64 value) { + CHECKED(uc_reg_write(uc, UC_ARM64_REG_SP, &value)); +} + +u64 Unicorn::GetPC() const { + u64 pc; + CHECKED(uc_reg_read(uc, UC_ARM64_REG_PC, &pc)); + return pc; +} + +void Unicorn::SetPC(u64 value) { + CHECKED(uc_reg_write(uc, UC_ARM64_REG_PC, &value)); +} + +constexpr size_t num_gprs = 31; +static const std::array gpr_ids { + 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, + 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, + 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, + 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 +}; + +std::array Unicorn::GetRegisters() const { + std::array regs; + std::array ptrs; + for (size_t i = 0; i < num_gprs; ++i) + ptrs[i] = ®s[i]; + + CHECKED(uc_reg_read_batch(uc, (int*)gpr_ids.data(), (void**)ptrs.data(), num_gprs)); + return regs; +} + +void Unicorn::SetRegisters(const std::array& value) { + std::array ptrs; + for (size_t i = 0; i < num_gprs; ++i) + ptrs[i] = &value[i]; + + CHECKED(uc_reg_write_batch(uc, (int*)gpr_ids.data(), (void**)ptrs.data(), num_gprs)); +} + +using Vector = Unicorn::Vector; +constexpr size_t num_vecs = 32; +static const std::array vec_ids { + 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, + 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, + 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, + 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 +}; + +std::array Unicorn::GetVectors() const { + std::array vecs; + std::array ptrs; + for (size_t i = 0; i < num_vecs; ++i) + ptrs[i] = &vecs[i]; + + CHECKED(uc_reg_read_batch(uc, (int*)vec_ids.data(), (void**)ptrs.data(), num_vecs)); + + return vecs; +} + +void Unicorn::SetVectors(const std::array& value) { + std::array ptrs; + for (size_t i = 0; i < num_vecs; ++i) + ptrs[i] = &value[i]; + + CHECKED(uc_reg_write_batch(uc, (int*)vec_ids.data(), (void**)ptrs.data(), num_vecs)); +} + +u32 Unicorn::GetFpcr() const { + u64 fpcr; + CHECKED(uc_reg_read(uc, UC_ARM64_REG_FPCR, &fpcr)); + return fpcr; +} + +void Unicorn::SetFpcr(u32 value) { + CHECKED(uc_reg_write(uc, UC_ARM64_REG_FPCR, &value)); +} + +u32 Unicorn::GetPstate() const { + u32 pstate; + CHECKED(uc_reg_read(uc, UC_ARM64_REG_NZCV, &pstate)); + return pstate; +} + +void Unicorn::SetPstate(u32 value) { + CHECKED(uc_reg_write(uc, UC_ARM64_REG_NZCV, &value)); +} + +void Unicorn::ClearPageCache() { + pages.clear(); +} + +void Unicorn::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%016" PRIx64 " end 0x%016" PRIx64 " perms 0x%08x\n", regions[i].begin, regions[i].end, regions[i].perms); + } + + CHECKED(uc_free(regions)); +} + +void Unicorn::InterruptHook(uc_engine* uc, u32, void* user_data) { + Unicorn* this_ = reinterpret_cast(user_data); + + u32 esr; + CHECKED(uc_reg_read(uc, UC_ARM64_REG_ESR, &esr)); + + auto ec = esr >> 26; + auto iss = esr & 0xFFFFFF; + + switch (ec) { + case 0x15: // SVC + this_->testenv.CallSVC(iss); + break; + default: + ASSERT_MSG(false, "Unhandled interrupt"); + } +} + +bool Unicorn::UnmappedMemoryHook(uc_engine* uc, uc_mem_type /*type*/, u64 start_address, int size, u64 /*value*/, void* user_data) { + Unicorn* this_ = reinterpret_cast(user_data); + + const auto generate_page = [&](u64 base_address) -> Page* { + printf("generate_page(%" PRIx64 ")\n", base_address); + + const u32 permissions = [&]{ + if (base_address < this_->testenv.code_mem.size() * 4) + return UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC; + return UC_PROT_READ | UC_PROT_WRITE; + }(); + + auto page = new Page(); + page->address = base_address; + for (size_t i = 0; i < page->data.size(); ++i) + page->data[i] = this_->testenv.MemoryRead8(base_address + i); + CHECKED(uc_mem_map_ptr(uc, base_address, page->data.size(), permissions, page->data.data())); + return page; + }; + + const u64 start_address_page = start_address & ~u64(0xFFF); + const u64 end_address = start_address + size - 1; + generate_page(start_address_page); + for (u64 addr = start_address_page + 0x1000; addr <= end_address && addr >= start_address_page; addr += 0x1000) { + this_->pages.emplace_back(generate_page(addr)); + } + + return true; +} diff --git a/tests/A64/unicorn_emu/unicorn.h b/tests/A64/unicorn_emu/unicorn.h new file mode 100644 index 00000000..ecc82aae --- /dev/null +++ b/tests/A64/unicorn_emu/unicorn.h @@ -0,0 +1,64 @@ +/* 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. + */ + +#pragma once + +#include +#include + +#include + +#include "common/common_types.h" +#include "../testenv.h" + +class Unicorn final { +public: + explicit Unicorn(TestEnv& testenv); + ~Unicorn(); + + void Run(); + + u64 GetSP() const; + void SetSP(u64 value); + + u64 GetPC() const; + void SetPC(u64 value); + + std::array GetRegisters() const; + void SetRegisters(const std::array& value); + + using Vector = std::array; + static_assert(sizeof(Vector) == sizeof(u64) * 2); + + std::array GetVectors() const; + void SetVectors(const std::array& value); + + u32 GetFpcr() const; + void SetFpcr(u32 value); + + u32 GetPstate() const; + void SetPstate(u32 value); + + void ClearPageCache(); + + void DumpMemoryInformation(); + +private: + static void InterruptHook(uc_engine* uc, u32 interrupt, void* user_data); + static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int size, u64 value, void* user_data); + + struct Page { + u64 address; + std::array data; + }; + + TestEnv& testenv; + uc_engine* uc{}; + uc_hook intr_hook{}; + uc_hook mem_invalid_hook{}; + + std::vector pages; +}; diff --git a/tests/A64/unicorn_emu/unicorn_load.cpp b/tests/A64/unicorn_emu/unicorn_load.cpp new file mode 100644 index 00000000..88e396de --- /dev/null +++ b/tests/A64/unicorn_emu/unicorn_load.cpp @@ -0,0 +1,22 @@ +/* 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. + */ + +// Load Unicorn DLL once on Windows using RAII +#ifdef _WIN32 +#include +#include + +static struct LoadDll { +private: + LoadDll() { + ASSERT(uc_dyn_load(NULL, 0)); + } + ~LoadDll() { + ASSERT(uc_dyn_free()); + } + static LoadDll g_load_dll; +} load_dll; +#endif \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 75023182..2e634c78 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -25,11 +25,23 @@ add_executable(dynarmic_tests A32/test_arm_disassembler.cpp A32/test_thumb_instructions.cpp A64/a64.cpp + A64/inst_gen.cpp + A64/inst_gen.h A64/testenv.h main.cpp rand_int.h ) +if (DYNARMIC_TESTS_USE_UNICORN) + target_sources(dynarmic_tests PRIVATE + A64/fuzz_with_unicorn.cpp + A64/unicorn_emu/unicorn.cpp + A64/unicorn_emu/unicorn.h + A64/unicorn_emu/unicorn_load.cpp + ) + target_link_libraries(dynarmic_tests PRIVATE unicorn) +endif() + include(CreateDirectoryGroups) create_target_directory_groups(dynarmic_tests)