tests/A64: Fuzz against unicorn
This commit is contained in:
parent
a0ef6eda19
commit
d5725de26a
10 changed files with 569 additions and 0 deletions
|
@ -12,6 +12,7 @@ endif()
|
||||||
option(DYNARMIC_USE_SYSTEM_BOOST "Use the system boost libraries" ON)
|
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_USE_LLVM "Support disassembly of jitted x86_64 code using LLVM" OFF)
|
||||||
option(DYNARMIC_TESTS "Build tests" ${MASTER_PROJECT})
|
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})
|
option(DYNARMIC_WARNINGS_AS_ERRORS "Warnings as errors" ${MASTER_PROJECT})
|
||||||
|
|
||||||
# Default to a Release build
|
# Default to a Release build
|
||||||
|
@ -125,6 +126,13 @@ if (DYNARMIC_USE_LLVM)
|
||||||
llvm_map_components_to_libnames(llvm_libs x86desc x86disassembler)
|
llvm_map_components_to_libnames(llvm_libs x86desc x86disassembler)
|
||||||
endif()
|
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
|
# Pull in externals CMakeLists for libs where available
|
||||||
add_subdirectory(externals)
|
add_subdirectory(externals)
|
||||||
|
|
||||||
|
|
17
CMakeModules/FindUnicorn.cmake
Normal file
17
CMakeModules/FindUnicorn.cmake
Normal file
|
@ -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)
|
|
@ -159,6 +159,7 @@ static Dynarmic::A32::UserCallbacks GetUserCallbacks() {
|
||||||
return user_callbacks;
|
return user_callbacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
struct InstructionGenerator final {
|
struct InstructionGenerator final {
|
||||||
public:
|
public:
|
||||||
InstructionGenerator(const char* format, std::function<bool(u32)> is_valid = [](u32){ return true; }) : is_valid(is_valid) {
|
InstructionGenerator(const char* format, std::function<bool(u32)> is_valid = [](u32){ return true; }) : is_valid(is_valid) {
|
||||||
|
@ -207,6 +208,7 @@ private:
|
||||||
u32 mask = 0;
|
u32 mask = 0;
|
||||||
std::function<bool(u32)> is_valid;
|
std::function<bool(u32)> is_valid;
|
||||||
};
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
static bool DoesBehaviorMatch(const ARMul_State& interp, const Dynarmic::A32::Jit& jit, const std::vector<WriteRecord>& interp_write_records, const std::vector<WriteRecord>& jit_write_records) {
|
static bool DoesBehaviorMatch(const ARMul_State& interp, const Dynarmic::A32::Jit& jit, const std::vector<WriteRecord>& interp_write_records, const std::vector<WriteRecord>& jit_write_records) {
|
||||||
return interp.Reg == jit.Regs()
|
return interp.Reg == jit.Regs()
|
||||||
|
|
168
tests/A64/fuzz_with_unicorn.cpp
Normal file
168
tests/A64/fuzz_with_unicorn.cpp
Normal file
|
@ -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 <cstring>
|
||||||
|
|
||||||
|
#include <catch.hpp>
|
||||||
|
#include <unicorn/arm64.h>
|
||||||
|
|
||||||
|
#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<u64, 31> 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<u64, 31> 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<u64, 31> 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<InstructionGenerator> instruction_generators = []{
|
||||||
|
const std::vector<std::tuple<const char*, const char*>> list {
|
||||||
|
#define INST(fn, name, bitstring) {#fn, bitstring},
|
||||||
|
#include "frontend/A64/decoder/a64.inc"
|
||||||
|
#undef INST
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<InstructionGenerator> 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<size_t>(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<u64, 31>& regs, const std::vector<u32>& 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<u64, 31> regs;
|
||||||
|
std::generate_n(regs.begin(), 31, []{ return RandInt<u64>(0, ~u64(0)); });
|
||||||
|
std::vector<u32> instructions;
|
||||||
|
instructions.push_back(GenRandomInst(0));
|
||||||
|
|
||||||
|
printf("%zu: %08x\n", iteration, instructions[0]);
|
||||||
|
|
||||||
|
TestInstance(regs, instructions);
|
||||||
|
}
|
||||||
|
}
|
40
tests/A64/inst_gen.cpp
Normal file
40
tests/A64/inst_gen.cpp
Normal file
|
@ -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<u32>(0, 0xFFFFFFFF);
|
||||||
|
inst = bits | (random & ~mask);
|
||||||
|
} while (IsInvalidInstruction(inst));
|
||||||
|
return inst;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<InstructionGenerator> InstructionGenerator::invalid_instructions;
|
37
tests/A64/inst_gen.h
Normal file
37
tests/A64/inst_gen.h
Normal file
|
@ -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 <cstring>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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<InstructionGenerator> invalid_instructions;
|
||||||
|
|
||||||
|
u32 bits = 0;
|
||||||
|
u32 mask = 0;
|
||||||
|
};
|
199
tests/A64/unicorn_emu/unicorn.cpp
Normal file
199
tests/A64/unicorn_emu/unicorn.cpp
Normal file
|
@ -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<int, num_gprs> 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<u64, 31> Unicorn::GetRegisters() const {
|
||||||
|
std::array<u64, num_gprs> regs;
|
||||||
|
std::array<u64*, num_gprs> 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<u64, 31>& value) {
|
||||||
|
std::array<const u64*, num_gprs> 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<int, num_vecs> 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<Vector, 32> Unicorn::GetVectors() const {
|
||||||
|
std::array<Vector, num_vecs> vecs;
|
||||||
|
std::array<Vector*, num_vecs> 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<Vector, 32>& value) {
|
||||||
|
std::array<const Vector*, num_vecs> 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<Unicorn*>(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<Unicorn*>(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;
|
||||||
|
}
|
64
tests/A64/unicorn_emu/unicorn.h
Normal file
64
tests/A64/unicorn_emu/unicorn.h
Normal file
|
@ -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 <array>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <unicorn/unicorn.h>
|
||||||
|
|
||||||
|
#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<u64, 31> GetRegisters() const;
|
||||||
|
void SetRegisters(const std::array<u64, 31>& value);
|
||||||
|
|
||||||
|
using Vector = std::array<u64, 2>;
|
||||||
|
static_assert(sizeof(Vector) == sizeof(u64) * 2);
|
||||||
|
|
||||||
|
std::array<Vector, 32> GetVectors() const;
|
||||||
|
void SetVectors(const std::array<Vector, 32>& 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<u8, 4096> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
TestEnv& testenv;
|
||||||
|
uc_engine* uc{};
|
||||||
|
uc_hook intr_hook{};
|
||||||
|
uc_hook mem_invalid_hook{};
|
||||||
|
|
||||||
|
std::vector<Page*> pages;
|
||||||
|
};
|
22
tests/A64/unicorn_emu/unicorn_load.cpp
Normal file
22
tests/A64/unicorn_emu/unicorn_load.cpp
Normal file
|
@ -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 <unicorn/arm64.h>
|
||||||
|
#include <unicorn_dynload.h>
|
||||||
|
|
||||||
|
static struct LoadDll {
|
||||||
|
private:
|
||||||
|
LoadDll() {
|
||||||
|
ASSERT(uc_dyn_load(NULL, 0));
|
||||||
|
}
|
||||||
|
~LoadDll() {
|
||||||
|
ASSERT(uc_dyn_free());
|
||||||
|
}
|
||||||
|
static LoadDll g_load_dll;
|
||||||
|
} load_dll;
|
||||||
|
#endif
|
|
@ -25,11 +25,23 @@ add_executable(dynarmic_tests
|
||||||
A32/test_arm_disassembler.cpp
|
A32/test_arm_disassembler.cpp
|
||||||
A32/test_thumb_instructions.cpp
|
A32/test_thumb_instructions.cpp
|
||||||
A64/a64.cpp
|
A64/a64.cpp
|
||||||
|
A64/inst_gen.cpp
|
||||||
|
A64/inst_gen.h
|
||||||
A64/testenv.h
|
A64/testenv.h
|
||||||
main.cpp
|
main.cpp
|
||||||
rand_int.h
|
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)
|
include(CreateDirectoryGroups)
|
||||||
create_target_directory_groups(dynarmic_tests)
|
create_target_directory_groups(dynarmic_tests)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue