A32: Fuzz instructions using unicorn

While skyeye was OK previously, now that we have an AArch64 backend,
this also means that we eventually have to support the AArch32
counterpart to it. Unfortunately, SkyEye is only compatible up to
ARMv6K, so we woud need to do a lot of work to bring the interpreter up
to speed with things to even begin testing new instruction
implementations.

For the AArch64 side of things, we already use Unicorn, so we can toss
out SkyEye in favor of it instead.
This commit is contained in:
Lioncash 2019-04-13 05:56:55 -04:00 committed by MerryMage
parent 1e1e9c17c7
commit d29582a0e1
5 changed files with 230 additions and 136 deletions

View file

@ -31,19 +31,18 @@
#include "ir_opt/passes.h" #include "ir_opt/passes.h"
#include "rand_int.h" #include "rand_int.h"
#include "testenv.h" #include "testenv.h"
#include "A32/skyeye_interpreter/dyncom/arm_dyncom_interpreter.h" #include "unicorn_emu/a32_unicorn.h"
#include "A32/skyeye_interpreter/skyeye_common/armstate.h"
using Dynarmic::Common::Bits; using Dynarmic::Common::Bits;
static Dynarmic::A32::UserConfig GetUserConfig(ArmTestEnv* testenv) { namespace {
Dynarmic::A32::UserConfig GetUserConfig(ArmTestEnv* testenv) {
Dynarmic::A32::UserConfig user_config; Dynarmic::A32::UserConfig user_config;
user_config.enable_fast_dispatch = false; user_config.enable_fast_dispatch = false;
user_config.callbacks = testenv; user_config.callbacks = testenv;
return user_config; return user_config;
} }
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) {
@ -92,16 +91,16 @@ private:
u32 mask = 0; u32 mask = 0;
std::function<bool(u32)> is_valid; std::function<bool(u32)> is_valid;
}; };
} // namespace
using WriteRecords = std::map<u32, u8>; using WriteRecords = std::map<u32, u8>;
static bool DoesBehaviorMatch(const ARMul_State& interp, const Dynarmic::A32::Jit& jit, const WriteRecords& interp_write_records, const WriteRecords& jit_write_records) { bool DoesBehaviorMatch(const A32Unicorn<ArmTestEnv>& uni, const Dynarmic::A32::Jit& jit,
return interp.Reg == jit.Regs() const WriteRecords& interp_write_records, const WriteRecords& jit_write_records) {
&& interp.ExtReg == jit.ExtRegs() return uni.GetRegisters() == jit.Regs() &&
&& interp.Cpsr == jit.Cpsr() uni.GetExtRegs() == jit.ExtRegs() &&
//&& interp.VFP[VFP_FPSCR] == jit.Fpscr() uni.GetCpsr() == jit.Cpsr() &&
&& interp_write_records == jit_write_records; // uni.GetFpscr() == jit.Fpscr() &&
interp_write_records == jit_write_records;
} }
void FuzzJitArm(const size_t instruction_count, const size_t instructions_to_execute_count, const size_t run_count, const std::function<u32()> instruction_generator) { void FuzzJitArm(const size_t instruction_count, const size_t instructions_to_execute_count, const size_t run_count, const std::function<u32()> instruction_generator) {
@ -112,34 +111,32 @@ void FuzzJitArm(const size_t instruction_count, const size_t instructions_to_exe
test_env.code_mem.back() = 0xEAFFFFFE; // b +#0 test_env.code_mem.back() = 0xEAFFFFFE; // b +#0
// Prepare test subjects // Prepare test subjects
ARMul_State interp{USER32MODE}; A32Unicorn<ArmTestEnv> uni{test_env};
interp.user_callbacks = &test_env;
Dynarmic::A32::Jit jit{GetUserConfig(&test_env)}; Dynarmic::A32::Jit jit{GetUserConfig(&test_env)};
for (size_t run_number = 0; run_number < run_count; run_number++) { for (size_t run_number = 0; run_number < run_count; run_number++) {
interp.instruction_cache.clear(); uni.ClearPageCache();
InterpreterClearCache();
jit.ClearCache(); jit.ClearCache();
// Setup initial state // Setup initial state
u32 initial_cpsr = 0x000001D0; const u32 initial_cpsr = 0x000001D0;
ArmTestEnv::RegisterArray initial_regs; ArmTestEnv::RegisterArray initial_regs;
std::generate_n(initial_regs.begin(), 15, []{ return RandInt<u32>(0, 0xFFFFFFFF); }); std::generate_n(initial_regs.begin(), initial_regs.size() - 1, []{ return RandInt<u32>(0, 0xFFFFFFFF); });
initial_regs[15] = 0; initial_regs[15] = 0;
ArmTestEnv::ExtRegsArray initial_extregs; ArmTestEnv::ExtRegsArray initial_extregs;
std::generate(initial_extregs.begin(), initial_extregs.end(), std::generate(initial_extregs.begin(), initial_extregs.end(),
[]{ return RandInt<u32>(0, 0xFFFFFFFF); }); []{ return RandInt<u32>(0, 0xFFFFFFFF); });
u32 initial_fpscr = 0x01000000 | (RandInt<u32>(0, 3) << 22); const u32 initial_fpscr = 0x01000000 | (RandInt<u32>(0, 3) << 22);
interp.UnsetExclusiveMemoryAddress(); uni.SetCpsr(initial_cpsr);
interp.Cpsr = initial_cpsr; uni.SetRegisters(initial_regs);
interp.Reg = initial_regs; uni.SetExtRegs(initial_extregs);
interp.ExtReg = initial_extregs; uni.SetFpscr(initial_fpscr);
interp.VFP[VFP_FPSCR] = initial_fpscr; uni.EnableFloatingPointAccess();
jit.Reset(); jit.Reset();
jit.SetCpsr(initial_cpsr); jit.SetCpsr(initial_cpsr);
jit.Regs() = initial_regs; jit.Regs() = initial_regs;
@ -157,35 +154,37 @@ void FuzzJitArm(const size_t instruction_count, const size_t instructions_to_exe
} }
printf("\nInitial Register Listing: \n"); printf("\nInitial Register Listing: \n");
for (int i = 0; i <= 15; i++) { for (size_t i = 0; i < initial_regs.size(); i++) {
auto reg = Dynarmic::A32::RegToString(static_cast<Dynarmic::A32::Reg>(i)); const auto reg = Dynarmic::A32::RegToString(static_cast<Dynarmic::A32::Reg>(i));
printf("%4s: %08x\n", reg, initial_regs[i]); printf("%4s: %08x\n", reg, initial_regs[i]);
} }
printf("CPSR: %08x\n", initial_cpsr); printf("CPSR: %08x\n", initial_cpsr);
printf("FPSCR:%08x\n", initial_fpscr); printf("FPSCR:%08x\n", initial_fpscr);
for (int i = 0; i <= 63; i++) { for (size_t i = 0; i < initial_extregs.size(); i++) {
printf("S%3i: %08x\n", i, initial_extregs[i]); printf("S%3zu: %08x\n", i, initial_extregs[i]);
} }
printf("\nFinal Register Listing: \n"); printf("\nFinal Register Listing: \n");
printf(" interp jit\n"); printf(" unicorn jit\n");
for (int i = 0; i <= 15; i++) { const auto uni_registers = uni.GetRegisters();
auto reg = Dynarmic::A32::RegToString(static_cast<Dynarmic::A32::Reg>(i)); for (size_t i = 0; i < uni_registers.size(); i++) {
printf("%4s: %08x %08x %s\n", reg, interp.Reg[i], jit.Regs()[i], interp.Reg[i] != jit.Regs()[i] ? "*" : ""); const auto reg = Dynarmic::A32::RegToString(static_cast<Dynarmic::A32::Reg>(i));
printf("%4s: %08x %08x %s\n", reg, uni_registers[i], jit.Regs()[i], uni_registers[i] != jit.Regs()[i] ? "*" : "");
} }
printf("CPSR: %08x %08x %s\n", interp.Cpsr, jit.Cpsr(), interp.Cpsr != jit.Cpsr() ? "*" : ""); printf("CPSR: %08x %08x %s\n", uni.GetCpsr(), jit.Cpsr(), uni.GetCpsr() != jit.Cpsr() ? "*" : "");
printf("FPSCR:%08x %08x %s\n", interp.VFP[VFP_FPSCR], jit.Fpscr(), interp.VFP[VFP_FPSCR] != jit.Fpscr() ? "*" : ""); printf("FPSCR:%08x %08x %s\n", uni.GetFpscr(), jit.Fpscr(), uni.GetFpscr() != jit.Fpscr() ? "*" : "");
for (int i = 0; i <= 63; i++) { const auto uni_ext_regs = uni.GetExtRegs();
printf("S%3i: %08x %08x %s\n", i, interp.ExtReg[i], jit.ExtRegs()[i], interp.ExtReg[i] != jit.ExtRegs()[i] ? "*" : ""); for (size_t i = 0; i < uni_ext_regs.size(); i++) {
printf("S%3zu: %08x %08x %s\n", i, uni_ext_regs[i], jit.ExtRegs()[i], uni_ext_regs[i] != jit.ExtRegs()[i] ? "*" : "");
} }
printf("\nInterp Write Records:\n"); printf("\nInterp Write Records:\n");
for (auto& record : interp_write_records) { for (const auto& record : interp_write_records) {
printf("[%08x] = %02x\n", record.first, record.second); printf("[%08x] = %02x\n", record.first, record.second);
} }
printf("\nJIT Write Records:\n"); printf("\nJIT Write Records:\n");
for (auto& record : jit_write_records) { for (const auto& record : jit_write_records) {
printf("[%08x] = %02x\n", record.first, record.second); printf("[%08x] = %02x\n", record.first, record.second);
} }
@ -209,12 +208,14 @@ void FuzzJitArm(const size_t instruction_count, const size_t instructions_to_exe
// Run interpreter // Run interpreter
test_env.modified_memory.clear(); test_env.modified_memory.clear();
interp.NumInstrsToExecute = static_cast<unsigned>(instructions_to_execute_count); test_env.ticks_left = instructions_to_execute_count;
InterpreterMainLoop(&interp); uni.Run();
interp_write_records = test_env.modified_memory; interp_write_records = test_env.modified_memory;
{ {
bool T = Dynarmic::Common::Bit<5>(interp.Cpsr); const bool T = Dynarmic::Common::Bit<5>(uni.GetCpsr());
interp.Reg[15] &= T ? 0xFFFFFFFE : 0xFFFFFFFC; const u32 mask = T ? 0xFFFFFFFE : 0xFFFFFFFC;
const u32 new_pc = uni.GetPC() & mask;
uni.SetPC(new_pc);
} }
// Run jit // Run jit
@ -223,9 +224,10 @@ void FuzzJitArm(const size_t instruction_count, const size_t instructions_to_exe
jit.Run(); jit.Run();
jit_write_records = test_env.modified_memory; jit_write_records = test_env.modified_memory;
REQUIRE(DoesBehaviorMatch(interp, jit, interp_write_records, jit_write_records)); REQUIRE(DoesBehaviorMatch(uni, jit, interp_write_records, jit_write_records));
} }
} }
} // Anonymous namespace
TEST_CASE( "arm: Optimization Failure (Randomized test case)", "[arm][A32]" ) { TEST_CASE( "arm: Optimization Failure (Randomized test case)", "[arm][A32]" ) {
// This was a randomized test-case that was failing. // This was a randomized test-case that was failing.

View file

@ -27,8 +27,7 @@
#include "ir_opt/passes.h" #include "ir_opt/passes.h"
#include "rand_int.h" #include "rand_int.h"
#include "testenv.h" #include "testenv.h"
#include "A32/skyeye_interpreter/dyncom/arm_dyncom_interpreter.h" #include "unicorn_emu/a32_unicorn.h"
#include "A32/skyeye_interpreter/skyeye_common/armstate.h"
static Dynarmic::A32::UserConfig GetUserConfig(ThumbTestEnv* testenv) { static Dynarmic::A32::UserConfig GetUserConfig(ThumbTestEnv* testenv) {
Dynarmic::A32::UserConfig user_config; Dynarmic::A32::UserConfig user_config;
@ -64,7 +63,7 @@ public:
u16 inst; u16 inst;
do { do {
u16 random = RandInt<u16>(0, 0xFFFF); const u16 random = RandInt<u16>(0, 0xFFFF);
inst = bits | (random & ~mask); inst = bits | (random & ~mask);
} while (!is_valid(inst)); } while (!is_valid(inst));
@ -78,46 +77,52 @@ private:
std::function<bool(u16)> is_valid; std::function<bool(u16)> is_valid;
}; };
static bool DoesBehaviorMatch(const ARMul_State& interp, const Dynarmic::A32::Jit& jit, WriteRecords& interp_write_records, WriteRecords& jit_write_records) { static bool DoesBehaviorMatch(const A32Unicorn<ThumbTestEnv>& uni, const Dynarmic::A32::Jit& jit,
const auto interp_regs = interp.Reg; const WriteRecords& interp_write_records, const WriteRecords& jit_write_records) {
const auto interp_regs = uni.GetRegisters();
const auto jit_regs = jit.Regs(); const auto jit_regs = jit.Regs();
return std::equal(interp_regs.begin(), interp_regs.end(), jit_regs.begin(), jit_regs.end()) return std::equal(interp_regs.begin(), interp_regs.end(), jit_regs.begin(), jit_regs.end()) &&
&& interp.Cpsr == jit.Cpsr() uni.GetCpsr() == jit.Cpsr() &&
&& interp_write_records == jit_write_records; interp_write_records == jit_write_records;
} }
static void RunInstance(size_t run_number, ThumbTestEnv& test_env, ARMul_State& interp, Dynarmic::A32::Jit& jit, const ThumbTestEnv::RegisterArray& initial_regs, static void RunInstance(size_t run_number, ThumbTestEnv& test_env, A32Unicorn<ThumbTestEnv>& uni, Dynarmic::A32::Jit& jit, const ThumbTestEnv::RegisterArray& initial_regs,
size_t instruction_count, size_t instructions_to_execute_count) { size_t instruction_count, size_t instructions_to_execute_count) {
interp.instruction_cache.clear(); uni.ClearPageCache();
InterpreterClearCache();
jit.ClearCache(); jit.ClearCache();
// Setup initial state // Setup initial state
interp.Cpsr = 0x000001F0; uni.SetCpsr(0x000001F0);
interp.Reg = initial_regs; uni.SetRegisters(initial_regs);
jit.SetCpsr(0x000001F0); jit.SetCpsr(0x000001F0);
jit.Regs() = initial_regs; jit.Regs() = initial_regs;
// Run interpreter // Run interpreter
test_env.modified_memory.clear(); test_env.modified_memory.clear();
interp.NumInstrsToExecute = static_cast<unsigned>(instructions_to_execute_count); test_env.ticks_left = instructions_to_execute_count;
InterpreterMainLoop(&interp); uni.SetPC(uni.GetPC() | 1);
auto interp_write_records = test_env.modified_memory; uni.Run();
{ const bool uni_code_memory_modified = test_env.code_mem_modified_by_guest;
bool T = Dynarmic::Common::Bit<5>(interp.Cpsr); const auto interp_write_records = test_env.modified_memory;
interp.Reg[15] &= T ? 0xFFFFFFFE : 0xFFFFFFFC;
}
// Run jit // Run jit
test_env.code_mem_modified_by_guest = false;
test_env.modified_memory.clear(); test_env.modified_memory.clear();
test_env.ticks_left = instructions_to_execute_count; test_env.ticks_left = instructions_to_execute_count;
jit.Run(); jit.Run();
auto jit_write_records = test_env.modified_memory; const bool jit_code_memory_modified = test_env.code_mem_modified_by_guest;
const auto jit_write_records = test_env.modified_memory;
test_env.code_mem_modified_by_guest = false;
REQUIRE(uni_code_memory_modified == jit_code_memory_modified);
if (uni_code_memory_modified) {
return;
}
// Compare // Compare
if (!DoesBehaviorMatch(interp, jit, interp_write_records, jit_write_records)) { if (!DoesBehaviorMatch(uni, jit, interp_write_records, jit_write_records)) {
printf("Failed at execution number %zu\n", run_number); printf("Failed at execution number %zu\n", run_number);
printf("\nInstruction Listing: \n"); printf("\nInstruction Listing: \n");
@ -126,24 +131,25 @@ static void RunInstance(size_t run_number, ThumbTestEnv& test_env, ARMul_State&
} }
printf("\nInitial Register Listing: \n"); printf("\nInitial Register Listing: \n");
for (int i = 0; i <= 15; i++) { for (size_t i = 0; i < initial_regs.size(); i++) {
printf("%4i: %08x\n", i, initial_regs[i]); printf("%4zu: %08x\n", i, initial_regs[i]);
} }
printf("\nFinal Register Listing: \n"); printf("\nFinal Register Listing: \n");
printf(" interp jit\n"); printf(" unicorn jit\n");
for (int i = 0; i <= 15; i++) { const auto uni_registers = uni.GetRegisters();
printf("%4i: %08x %08x %s\n", i, interp.Reg[i], jit.Regs()[i], interp.Reg[i] != jit.Regs()[i] ? "*" : ""); for (size_t i = 0; i < uni_registers.size(); i++) {
printf("%4zu: %08x %08x %s\n", i, uni_registers[i], jit.Regs()[i], uni_registers[i] != jit.Regs()[i] ? "*" : "");
} }
printf("CPSR: %08x %08x %s\n", interp.Cpsr, jit.Cpsr(), interp.Cpsr != jit.Cpsr() ? "*" : ""); printf("CPSR: %08x %08x %s\n", uni.GetCpsr(), jit.Cpsr(), uni.GetCpsr() != jit.Cpsr() ? "*" : "");
printf("\nInterp Write Records:\n"); printf("\nUnicorn Write Records:\n");
for (auto& record : interp_write_records) { for (const auto& record : interp_write_records) {
printf("[%08x] = %02x\n", record.first, record.second); printf("[%08x] = %02x\n", record.first, record.second);
} }
printf("\nJIT Write Records:\n"); printf("\nJIT Write Records:\n");
for (auto& record : jit_write_records) { for (const auto& record : jit_write_records) {
printf("[%08x] = %02x\n", record.first, record.second); printf("[%08x] = %02x\n", record.first, record.second);
} }
@ -175,28 +181,27 @@ static void RunInstance(size_t run_number, ThumbTestEnv& test_env, ARMul_State&
void FuzzJitThumb(const size_t instruction_count, const size_t instructions_to_execute_count, const size_t run_count, const std::function<u16()> instruction_generator) { void FuzzJitThumb(const size_t instruction_count, const size_t instructions_to_execute_count, const size_t run_count, const std::function<u16()> instruction_generator) {
ThumbTestEnv test_env; ThumbTestEnv test_env;
// Prepare memory // Prepare memory.
test_env.code_mem.resize(instruction_count + 1); test_env.code_mem.resize(instruction_count + 1);
test_env.code_mem.back() = 0xE7FE; // b +#0 test_env.code_mem.back() = 0xE7FE; // b +#0
// Prepare test subjects // Prepare test subjects
ARMul_State interp{USER32MODE}; A32Unicorn uni{test_env};
interp.user_callbacks = &test_env;
Dynarmic::A32::Jit jit{GetUserConfig(&test_env)}; Dynarmic::A32::Jit jit{GetUserConfig(&test_env)};
for (size_t run_number = 0; run_number < run_count; run_number++) { for (size_t run_number = 0; run_number < run_count; run_number++) {
ThumbTestEnv::RegisterArray initial_regs; ThumbTestEnv::RegisterArray initial_regs;
std::generate_n(initial_regs.begin(), 15, []{ return RandInt<u32>(0, 0xFFFFFFFF); }); std::generate_n(initial_regs.begin(), initial_regs.size() - 1, []{ return RandInt<u32>(0, 0xFFFFFFFF); });
initial_regs[15] = 0; initial_regs[15] = 0;
std::generate_n(test_env.code_mem.begin(), instruction_count, instruction_generator); std::generate_n(test_env.code_mem.begin(), instruction_count, instruction_generator);
RunInstance(run_number, test_env, interp, jit, initial_regs, instruction_count, instructions_to_execute_count); RunInstance(run_number, test_env, uni, jit, initial_regs, instruction_count, instructions_to_execute_count);
} }
} }
TEST_CASE("Fuzz Thumb instructions set 1", "[JitX64][Thumb]") { TEST_CASE("Fuzz Thumb instructions set 1", "[JitX64][Thumb]") {
const std::array<ThumbInstGen, 25> instructions = {{ const std::array instructions = {
ThumbInstGen("00000xxxxxxxxxxx"), // LSL <Rd>, <Rm>, #<imm5> ThumbInstGen("00000xxxxxxxxxxx"), // LSL <Rd>, <Rm>, #<imm5>
ThumbInstGen("00001xxxxxxxxxxx"), // LSR <Rd>, <Rm>, #<imm5> ThumbInstGen("00001xxxxxxxxxxx"), // LSR <Rd>, <Rm>, #<imm5>
ThumbInstGen("00010xxxxxxxxxxx"), // ASR <Rd>, <Rm>, #<imm5> ThumbInstGen("00010xxxxxxxxxxx"), // ASR <Rd>, <Rm>, #<imm5>
@ -224,11 +229,23 @@ TEST_CASE("Fuzz Thumb instructions set 1", "[JitX64][Thumb]") {
[](u16 inst){ return Dynarmic::Common::Bits<0, 7>(inst) != 0; }), // Empty reg_list is UNPREDICTABLE [](u16 inst){ return Dynarmic::Common::Bits<0, 7>(inst) != 0; }), // Empty reg_list is UNPREDICTABLE
ThumbInstGen("10111100xxxxxxxx", // POP (P = 0) ThumbInstGen("10111100xxxxxxxx", // POP (P = 0)
[](u16 inst){ return Dynarmic::Common::Bits<0, 7>(inst) != 0; }), // Empty reg_list is UNPREDICTABLE [](u16 inst){ return Dynarmic::Common::Bits<0, 7>(inst) != 0; }), // Empty reg_list is UNPREDICTABLE
ThumbInstGen("1100xxxxxxxxxxxx"), // STMIA/LDMIA ThumbInstGen("1100xxxxxxxxxxxx", // STMIA/LDMIA
[](u16 inst) {
// Ensure that the architecturally undefined case of
// the base register being within the list isn't hit.
const u32 rn = Dynarmic::Common::Bits<8, 10>(inst);
return (inst & (1U << rn)) == 0;
}),
// TODO: We should properly test against swapped
// endianness cases, however Unicorn doesn't
// expose the intended endianness of a load/store
// operation to memory through its hooks.
#if 0
ThumbInstGen("101101100101x000"), // SETEND ThumbInstGen("101101100101x000"), // SETEND
}}; #endif
};
auto instruction_select = [&]() -> u16 { const auto instruction_select = [&]() -> u16 {
size_t inst_index = RandInt<size_t>(0, instructions.size() - 1); size_t inst_index = RandInt<size_t>(0, instructions.size() - 1);
return instructions[inst_index].Generate(); return instructions[inst_index].Generate();
@ -248,26 +265,39 @@ TEST_CASE("Fuzz Thumb instructions set 1", "[JitX64][Thumb]") {
} }
TEST_CASE("Fuzz Thumb instructions set 2 (affects PC)", "[JitX64][Thumb]") { TEST_CASE("Fuzz Thumb instructions set 2 (affects PC)", "[JitX64][Thumb]") {
const std::array<ThumbInstGen, 8> instructions = {{ const std::array instructions = {
// TODO: We currently can't test BX/BLX as we have
// no way of preventing the unpredictable
// condition from occurring with the current interface.
// (bits zero and one within the specified register
// must not be address<1:0> == '10'.
#if 0
ThumbInstGen("01000111xmmmm000", // BLX/BX ThumbInstGen("01000111xmmmm000", // BLX/BX
[](u16 inst){ [](u16 inst){
u32 Rm = Dynarmic::Common::Bits<3, 6>(inst); const u32 Rm = Dynarmic::Common::Bits<3, 6>(inst);
return Rm != 15; return Rm != 15;
}), }),
#endif
ThumbInstGen("1010oxxxxxxxxxxx"), // add to pc/sp ThumbInstGen("1010oxxxxxxxxxxx"), // add to pc/sp
ThumbInstGen("11100xxxxxxxxxxx"), // B ThumbInstGen("11100xxxxxxxxxxx"), // B
ThumbInstGen("01000100h0xxxxxx"), // ADD (high registers) ThumbInstGen("01000100h0xxxxxx"), // ADD (high registers)
ThumbInstGen("01000110h0xxxxxx"), // MOV (high registers) ThumbInstGen("01000110h0xxxxxx"), // MOV (high registers)
ThumbInstGen("1101ccccxxxxxxxx", // B<cond> ThumbInstGen("1101ccccxxxxxxxx", // B<cond>
[](u16 inst){ [](u16 inst){
u32 c = Dynarmic::Common::Bits<9, 12>(inst); const u32 c = Dynarmic::Common::Bits<9, 12>(inst);
return c < 0b1110; // Don't want SWI or undefined instructions. return c < 0b1110; // Don't want SWI or undefined instructions.
}), }),
ThumbInstGen("10110110011x0xxx"), // CPS ThumbInstGen("10110110011x0xxx"), // CPS
ThumbInstGen("10111101xxxxxxxx"), // POP (R = 1)
}};
auto instruction_select = [&]() -> u16 { // TODO: We currently have no control over the generated
// values when creating new pages, so we can't
// reliably test this yet.
#if 0
ThumbInstGen("10111101xxxxxxxx"), // POP (R = 1)
#endif
};
const auto instruction_select = [&]() -> u16 {
size_t inst_index = RandInt<size_t>(0, instructions.size() - 1); size_t inst_index = RandInt<size_t>(0, instructions.size() - 1);
return instructions[inst_index].Generate(); return instructions[inst_index].Generate();
@ -280,8 +310,7 @@ TEST_CASE("Verify fix for off by one error in MemoryRead32 worked", "[Thumb]") {
ThumbTestEnv test_env; ThumbTestEnv test_env;
// Prepare test subjects // Prepare test subjects
ARMul_State interp{USER32MODE}; A32Unicorn<ThumbTestEnv> uni{test_env};
interp.user_callbacks = &test_env;
Dynarmic::A32::Jit jit{GetUserConfig(&test_env)}; Dynarmic::A32::Jit jit{GetUserConfig(&test_env)};
constexpr ThumbTestEnv::RegisterArray initial_regs { constexpr ThumbTestEnv::RegisterArray initial_regs {
@ -312,5 +341,5 @@ TEST_CASE("Verify fix for off by one error in MemoryRead32 worked", "[Thumb]") {
0xE7FE, // b +#0 0xE7FE, // b +#0
}; };
RunInstance(1, test_env, interp, jit, initial_regs, 5, 5); RunInstance(1, test_env, uni, jit, initial_regs, 5, 5);
} }

View file

@ -17,9 +17,10 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/common_types.h" #include "common/common_types.h"
template <typename InstructionType, u32 infinite_loop> template <typename InstructionType_, u32 infinite_loop>
class A32TestEnv final : public Dynarmic::A32::UserCallbacks { class A32TestEnv final : public Dynarmic::A32::UserCallbacks {
public: public:
using InstructionType = InstructionType_;
using RegisterArray = std::array<u32, 16>; using RegisterArray = std::array<u32, 16>;
using ExtRegsArray = std::array<u32, 64>; using ExtRegsArray = std::array<u32, 64>;

View file

@ -4,6 +4,7 @@
* General Public License version 2 or any later version. * General Public License version 2 or any later version.
*/ */
#include <type_traits>
#include "A32/testenv.h" #include "A32/testenv.h"
#include "a32_unicorn.h" #include "a32_unicorn.h"
#include "common/assert.h" #include "common/assert.h"
@ -19,23 +20,31 @@
constexpr u32 BEGIN_ADDRESS = 0; constexpr u32 BEGIN_ADDRESS = 0;
constexpr u32 END_ADDRESS = ~u32(0); constexpr u32 END_ADDRESS = ~u32(0);
A32Unicorn::A32Unicorn(ArmTestEnv& testenv) : testenv(testenv) { template <class TestEnvironment>
CHECKED(uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc)); A32Unicorn<TestEnvironment>::A32Unicorn(TestEnvironment& testenv) : testenv{testenv} {
constexpr uc_mode open_mode = std::is_same_v<TestEnvironment, ArmTestEnv> ? UC_MODE_ARM : UC_MODE_THUMB;
CHECKED(uc_open(UC_ARCH_ARM, open_mode, &uc));
CHECKED(uc_hook_add(uc, &intr_hook, UC_HOOK_INTR, (void*)InterruptHook, this, BEGIN_ADDRESS, END_ADDRESS)); 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)); CHECKED(uc_hook_add(uc, &mem_invalid_hook, UC_HOOK_MEM_INVALID, (void*)UnmappedMemoryHook, this, BEGIN_ADDRESS, END_ADDRESS));
CHECKED(uc_hook_add(uc, &mem_write_prot_hook, UC_HOOK_MEM_WRITE, (void*)MemoryWriteHook, this, BEGIN_ADDRESS, END_ADDRESS)); CHECKED(uc_hook_add(uc, &mem_write_prot_hook, UC_HOOK_MEM_WRITE, (void*)MemoryWriteHook, this, BEGIN_ADDRESS, END_ADDRESS));
} }
A32Unicorn::~A32Unicorn() { template <class TestEnvironment>
A32Unicorn<TestEnvironment>::~A32Unicorn() {
ClearPageCache(); ClearPageCache();
CHECKED(uc_hook_del(uc, intr_hook)); CHECKED(uc_hook_del(uc, intr_hook));
CHECKED(uc_hook_del(uc, mem_invalid_hook)); CHECKED(uc_hook_del(uc, mem_invalid_hook));
CHECKED(uc_close(uc)); CHECKED(uc_close(uc));
} }
void A32Unicorn::Run() { template <class TestEnvironment>
void A32Unicorn<TestEnvironment>::Run() {
// Thumb execution mode requires the LSB to be set to 1.
constexpr u64 mask = std::is_same_v<TestEnvironment, ArmTestEnv> ? 0 : 1;
while (testenv.ticks_left > 0) { while (testenv.ticks_left > 0) {
CHECKED(uc_emu_start(uc, GetPC(), END_ADDRESS, 0, 1)); CHECKED(uc_emu_start(uc, GetPC() | mask, END_ADDRESS, 0, 1));
testenv.ticks_left--; testenv.ticks_left--;
if (!testenv.interrupts.empty() || testenv.code_mem_modified_by_guest) { if (!testenv.interrupts.empty() || testenv.code_mem_modified_by_guest) {
return; return;
@ -43,63 +52,72 @@ void A32Unicorn::Run() {
} }
} }
u32 A32Unicorn::GetPC() const { template <class TestEnvironment>
u32 A32Unicorn<TestEnvironment>::GetPC() const {
u32 pc; u32 pc;
CHECKED(uc_reg_read(uc, UC_ARM_REG_PC, &pc)); CHECKED(uc_reg_read(uc, UC_ARM_REG_PC, &pc));
return pc; return pc;
} }
void A32Unicorn::SetPC(u32 value) { template <class TestEnvironment>
void A32Unicorn<TestEnvironment>::SetPC(u32 value) {
CHECKED(uc_reg_write(uc, UC_ARM_REG_PC, &value)); CHECKED(uc_reg_write(uc, UC_ARM_REG_PC, &value));
} }
u32 A32Unicorn::GetSP() const { template <class TestEnvironment>
u32 A32Unicorn<TestEnvironment>::GetSP() const {
u32 sp; u32 sp;
CHECKED(uc_reg_read(uc, UC_ARM_REG_SP, &sp)); CHECKED(uc_reg_read(uc, UC_ARM_REG_SP, &sp));
return sp; return sp;
} }
void A32Unicorn::SetSP(u32 value) { template <class TestEnvironment>
void A32Unicorn<TestEnvironment>::SetSP(u32 value) {
CHECKED(uc_reg_write(uc, UC_ARM_REG_SP, &value)); CHECKED(uc_reg_write(uc, UC_ARM_REG_SP, &value));
} }
constexpr std::array<int, A32Unicorn::num_gprs> gpr_ids{ constexpr std::array<int, Unicorn::A32::num_gprs> gpr_ids{
UC_ARM_REG_R0, UC_ARM_REG_R1, UC_ARM_REG_R2, UC_ARM_REG_R3, UC_ARM_REG_R4, UC_ARM_REG_R5, UC_ARM_REG_R6, UC_ARM_REG_R7, UC_ARM_REG_R0, UC_ARM_REG_R1, UC_ARM_REG_R2, UC_ARM_REG_R3, UC_ARM_REG_R4, UC_ARM_REG_R5, UC_ARM_REG_R6, UC_ARM_REG_R7,
UC_ARM_REG_R8, UC_ARM_REG_R9, UC_ARM_REG_R10, UC_ARM_REG_R11, UC_ARM_REG_R12, UC_ARM_REG_R13, UC_ARM_REG_R14, UC_ARM_REG_R15, UC_ARM_REG_R8, UC_ARM_REG_R9, UC_ARM_REG_R10, UC_ARM_REG_R11, UC_ARM_REG_R12, UC_ARM_REG_R13, UC_ARM_REG_R14, UC_ARM_REG_R15,
}; };
A32Unicorn::RegisterArray A32Unicorn::GetRegisters() const { template <class TestEnvironment>
RegisterArray regs; Unicorn::A32::RegisterArray A32Unicorn<TestEnvironment>::GetRegisters() const {
RegisterPtrArray ptrs; Unicorn::A32::RegisterArray regs;
for (size_t i = 0; i < ptrs.size(); ++i) Unicorn::A32::RegisterPtrArray ptrs;
for (size_t i = 0; i < ptrs.size(); ++i) {
ptrs[i] = &regs[i]; ptrs[i] = &regs[i];
}
CHECKED(uc_reg_read_batch(uc, const_cast<int*>(gpr_ids.data()), CHECKED(uc_reg_read_batch(uc, const_cast<int*>(gpr_ids.data()),
reinterpret_cast<void**>(ptrs.data()), num_gprs)); reinterpret_cast<void**>(ptrs.data()), Unicorn::A32::num_gprs));
return regs; return regs;
} }
void A32Unicorn::SetRegisters(const RegisterArray& value) { template <class TestEnvironment>
RegisterConstPtrArray ptrs; void A32Unicorn<TestEnvironment>::SetRegisters(const RegisterArray& value) {
for (size_t i = 0; i < ptrs.size(); ++i) Unicorn::A32::RegisterConstPtrArray ptrs;
for (size_t i = 0; i < ptrs.size(); ++i) {
ptrs[i] = &value[i]; ptrs[i] = &value[i];
}
CHECKED(uc_reg_write_batch(uc, const_cast<int*>(gpr_ids.data()), CHECKED(uc_reg_write_batch(uc, const_cast<int*>(gpr_ids.data()),
reinterpret_cast<void**>(const_cast<u32**>(ptrs.data())), ptrs.size())); reinterpret_cast<void**>(const_cast<u32**>(ptrs.data())), ptrs.size()));
} }
using DoubleExtRegPtrArray = std::array<A32Unicorn::ExtRegArray::pointer, A32Unicorn::num_ext_regs/2>; using DoubleExtRegPtrArray = std::array<Unicorn::A32::ExtRegArray::pointer, Unicorn::A32::num_ext_regs/2>;
using DoubleExtRegConstPtrArray = std::array<A32Unicorn::ExtRegArray::const_pointer, A32Unicorn::num_ext_regs/2>; using DoubleExtRegConstPtrArray = std::array<Unicorn::A32::ExtRegArray::const_pointer, Unicorn::A32::num_ext_regs/2>;
constexpr std::array<int, A32Unicorn::num_ext_regs/2> double_ext_reg_ids{ constexpr std::array<int, Unicorn::A32::num_ext_regs/2> double_ext_reg_ids{
UC_ARM_REG_D0, UC_ARM_REG_D1, UC_ARM_REG_D2, UC_ARM_REG_D3, UC_ARM_REG_D4, UC_ARM_REG_D5, UC_ARM_REG_D6, UC_ARM_REG_D7, UC_ARM_REG_D0, UC_ARM_REG_D1, UC_ARM_REG_D2, UC_ARM_REG_D3, UC_ARM_REG_D4, UC_ARM_REG_D5, UC_ARM_REG_D6, UC_ARM_REG_D7,
UC_ARM_REG_D8, UC_ARM_REG_D9, UC_ARM_REG_D10, UC_ARM_REG_D11, UC_ARM_REG_D12, UC_ARM_REG_D13, UC_ARM_REG_D14, UC_ARM_REG_D15, UC_ARM_REG_D8, UC_ARM_REG_D9, UC_ARM_REG_D10, UC_ARM_REG_D11, UC_ARM_REG_D12, UC_ARM_REG_D13, UC_ARM_REG_D14, UC_ARM_REG_D15,
UC_ARM_REG_D16, UC_ARM_REG_D17, UC_ARM_REG_D18, UC_ARM_REG_D19, UC_ARM_REG_D20, UC_ARM_REG_D21, UC_ARM_REG_D22, UC_ARM_REG_D23, UC_ARM_REG_D16, UC_ARM_REG_D17, UC_ARM_REG_D18, UC_ARM_REG_D19, UC_ARM_REG_D20, UC_ARM_REG_D21, UC_ARM_REG_D22, UC_ARM_REG_D23,
UC_ARM_REG_D24, UC_ARM_REG_D25, UC_ARM_REG_D26, UC_ARM_REG_D27, UC_ARM_REG_D28, UC_ARM_REG_D29, UC_ARM_REG_D30, UC_ARM_REG_D31, UC_ARM_REG_D24, UC_ARM_REG_D25, UC_ARM_REG_D26, UC_ARM_REG_D27, UC_ARM_REG_D28, UC_ARM_REG_D29, UC_ARM_REG_D30, UC_ARM_REG_D31,
}; };
A32Unicorn::ExtRegArray A32Unicorn::GetExtRegs() const { template <class TestEnvironment>
ExtRegArray ext_regs; Unicorn::A32::ExtRegArray A32Unicorn<TestEnvironment>::GetExtRegs() const {
Unicorn::A32::ExtRegArray ext_regs;
DoubleExtRegPtrArray ptrs; DoubleExtRegPtrArray ptrs;
for (size_t i = 0; i < ptrs.size(); ++i) for (size_t i = 0; i < ptrs.size(); ++i)
ptrs[i] = &ext_regs[i*2]; ptrs[i] = &ext_regs[i*2];
@ -110,43 +128,69 @@ A32Unicorn::ExtRegArray A32Unicorn::GetExtRegs() const {
return ext_regs; return ext_regs;
} }
void A32Unicorn::SetExtRegs(const ExtRegArray& value) { template <class TestEnvironment>
void A32Unicorn<TestEnvironment>::SetExtRegs(const ExtRegArray& value) {
DoubleExtRegConstPtrArray ptrs; DoubleExtRegConstPtrArray ptrs;
for (size_t i = 0; i < ptrs.size(); ++i) for (size_t i = 0; i < ptrs.size(); ++i) {
ptrs[i] = &value[i*2]; ptrs[i] = &value[i*2];
}
CHECKED(uc_reg_write_batch(uc, const_cast<int*>(double_ext_reg_ids.data()), CHECKED(uc_reg_write_batch(uc, const_cast<int*>(double_ext_reg_ids.data()),
reinterpret_cast<void* const *>(const_cast<u32**>(ptrs.data())), ptrs.size())); reinterpret_cast<void* const *>(const_cast<u32**>(ptrs.data())), ptrs.size()));
} }
u32 A32Unicorn::GetFpscr() const { template <class TestEnvironment>
u32 A32Unicorn<TestEnvironment>::GetFpscr() const {
u32 fpsr; u32 fpsr;
CHECKED(uc_reg_read(uc, UC_ARM_REG_FPSCR, &fpsr)); CHECKED(uc_reg_read(uc, UC_ARM_REG_FPSCR, &fpsr));
return fpsr; return fpsr;
} }
void A32Unicorn::SetFpscr(u32 value) { template <class TestEnvironment>
void A32Unicorn<TestEnvironment>::SetFpscr(u32 value) {
CHECKED(uc_reg_write(uc, UC_ARM_REG_FPSCR, &value)); CHECKED(uc_reg_write(uc, UC_ARM_REG_FPSCR, &value));
} }
u32 A32Unicorn::GetCpsr() const { template <class TestEnvironment>
u32 A32Unicorn<TestEnvironment>::GetFpexc() const {
u32 value = 0;
CHECKED(uc_reg_read(uc, UC_ARM_REG_FPEXC, &value));
return value;
}
template <class TestEnvironment>
void A32Unicorn<TestEnvironment>::SetFpexc(u32 value) {
CHECKED(uc_reg_write(uc, UC_ARM_REG_FPEXC, &value));
}
template <class TestEnvironment>
u32 A32Unicorn<TestEnvironment>::GetCpsr() const {
u32 pstate; u32 pstate;
CHECKED(uc_reg_read(uc, UC_ARM_REG_CPSR, &pstate)); CHECKED(uc_reg_read(uc, UC_ARM_REG_CPSR, &pstate));
return pstate; return pstate;
} }
void A32Unicorn::SetCpsr(u32 value) { template <class TestEnvironment>
void A32Unicorn<TestEnvironment>::SetCpsr(u32 value) {
CHECKED(uc_reg_write(uc, UC_ARM_REG_CPSR, &value)); CHECKED(uc_reg_write(uc, UC_ARM_REG_CPSR, &value));
} }
void A32Unicorn::ClearPageCache() { template <class TestEnvironment>
void A32Unicorn<TestEnvironment>::EnableFloatingPointAccess() {
const u32 new_fpexc = GetFpexc() | (1U << 30);
SetFpexc(new_fpexc);
}
template <class TestEnvironment>
void A32Unicorn<TestEnvironment>::ClearPageCache() {
for (const auto& page : pages) { for (const auto& page : pages) {
CHECKED(uc_mem_unmap(uc, page->address, 4096)); CHECKED(uc_mem_unmap(uc, page->address, 4096));
} }
pages.clear(); pages.clear();
} }
void A32Unicorn::DumpMemoryInformation() { template <class TestEnvironment>
void A32Unicorn<TestEnvironment>::DumpMemoryInformation() {
uc_mem_region* regions; uc_mem_region* regions;
u32 count; u32 count;
CHECKED(uc_mem_regions(uc, &regions, &count)); CHECKED(uc_mem_regions(uc, &regions, &count));
@ -158,7 +202,8 @@ void A32Unicorn::DumpMemoryInformation() {
CHECKED(uc_free(regions)); CHECKED(uc_free(regions));
} }
void A32Unicorn::InterruptHook(uc_engine* /*uc*/, u32 int_number, void* user_data) { template <class TestEnvironment>
void A32Unicorn<TestEnvironment>::InterruptHook(uc_engine* /*uc*/, u32 int_number, void* user_data) {
auto* this_ = static_cast<A32Unicorn*>(user_data); auto* this_ = static_cast<A32Unicorn*>(user_data);
u32 esr = 0; u32 esr = 0;
@ -177,15 +222,17 @@ void A32Unicorn::InterruptHook(uc_engine* /*uc*/, u32 int_number, void* user_dat
} }
} }
bool A32Unicorn::UnmappedMemoryHook(uc_engine* uc, uc_mem_type /*type*/, u32 start_address, int size, u64 /*value*/, void* user_data) { template <class TestEnvironment>
bool A32Unicorn<TestEnvironment>::UnmappedMemoryHook(uc_engine* uc, uc_mem_type /*type*/, u32 start_address, int size, u64 /*value*/, void* user_data) {
auto* this_ = static_cast<A32Unicorn*>(user_data); auto* this_ = static_cast<A32Unicorn*>(user_data);
const auto generate_page = [&](u32 base_address) { const auto generate_page = [&](u32 base_address) {
// printf("generate_page(%x)\n", base_address); // printf("generate_page(%x)\n", base_address);
const u32 permissions = [&]() -> u32 { const u32 permissions = [&]() -> u32 {
if (base_address < this_->testenv.code_mem.size() * 4) if (base_address < this_->testenv.code_mem.size() * sizeof(typename TestEnvironment::InstructionType)) {
return UC_PROT_READ | UC_PROT_EXEC; return UC_PROT_READ | UC_PROT_EXEC;
}
return UC_PROT_READ; return UC_PROT_READ;
}(); }();
@ -220,7 +267,8 @@ bool A32Unicorn::UnmappedMemoryHook(uc_engine* uc, uc_mem_type /*type*/, u32 sta
return true; return true;
} }
bool A32Unicorn::MemoryWriteHook(uc_engine* /*uc*/, uc_mem_type /*type*/, u32 start_address, int size, u64 value, void* user_data) { template <class TestEnvironment>
bool A32Unicorn<TestEnvironment>::MemoryWriteHook(uc_engine* /*uc*/, uc_mem_type /*type*/, u32 start_address, int size, u64 value, void* user_data) {
auto* this_ = static_cast<A32Unicorn*>(user_data); auto* this_ = static_cast<A32Unicorn*>(user_data);
switch (size) { switch (size) {
@ -242,3 +290,6 @@ bool A32Unicorn::MemoryWriteHook(uc_engine* /*uc*/, uc_mem_type /*type*/, u32 st
return true; return true;
} }
template class A32Unicorn<ArmTestEnv>;
template class A32Unicorn<ThumbTestEnv>;

View file

@ -15,17 +15,23 @@
#include "A32/testenv.h" #include "A32/testenv.h"
namespace Unicorn::A32 {
static constexpr size_t num_gprs = 16;
static constexpr size_t num_ext_regs = 64;
using ExtRegArray = std::array<u32, num_ext_regs>;
using RegisterArray = std::array<u32, num_gprs>;
using RegisterPtrArray = std::array<RegisterArray::pointer, num_gprs>;
using RegisterConstPtrArray = std::array<RegisterArray::const_pointer, num_gprs>;
} // namespace Unicorn::A32
template <class TestEnvironment>
class A32Unicorn final { class A32Unicorn final {
public: public:
static constexpr size_t num_gprs = 16; using ExtRegArray = Unicorn::A32::ExtRegArray;
using RegisterArray = std::array<u32, num_gprs>; using RegisterArray = Unicorn::A32::RegisterArray;
using RegisterPtrArray = std::array<RegisterArray::pointer, num_gprs>;
using RegisterConstPtrArray = std::array<RegisterArray::const_pointer, num_gprs>;
static constexpr size_t num_ext_regs = 64; explicit A32Unicorn(TestEnvironment& testenv);
using ExtRegArray = std::array<u32, num_ext_regs>;
explicit A32Unicorn(ArmTestEnv& testenv);
~A32Unicorn(); ~A32Unicorn();
void Run(); void Run();
@ -45,9 +51,14 @@ public:
u32 GetFpscr() const; u32 GetFpscr() const;
void SetFpscr(u32 value); void SetFpscr(u32 value);
u32 GetFpexc() const;
void SetFpexc(u32 value);
u32 GetCpsr() const; u32 GetCpsr() const;
void SetCpsr(u32 value); void SetCpsr(u32 value);
void EnableFloatingPointAccess();
void ClearPageCache(); void ClearPageCache();
void DumpMemoryInformation(); void DumpMemoryInformation();
@ -62,7 +73,7 @@ private:
std::array<u8, 4096> data; std::array<u8, 4096> data;
}; };
ArmTestEnv& testenv; TestEnvironment& testenv;
uc_engine* uc{}; uc_engine* uc{};
uc_hook intr_hook{}; uc_hook intr_hook{};
uc_hook mem_invalid_hook{}; uc_hook mem_invalid_hook{};