From 331a02e02e50994063b37b37b2a9c8533d6fc6bb Mon Sep 17 00:00:00 2001 From: MerryMage Date: Sat, 6 Feb 2021 21:25:08 +0000 Subject: [PATCH] fuzz_arm: Add fuzzing for thumb instructions --- tests/A32/fuzz_arm.cpp | 182 ++++++++++++++++++++++++++++++++++------- tests/A32/testenv.h | 14 +++- tests/fuzz_util.cpp | 12 ++- 3 files changed, 172 insertions(+), 36 deletions(-) diff --git a/tests/A32/fuzz_arm.cpp b/tests/A32/fuzz_arm.cpp index b377995a..2cd9b47e 100644 --- a/tests/A32/fuzz_arm.cpp +++ b/tests/A32/fuzz_arm.cpp @@ -36,8 +36,8 @@ namespace { using namespace Dynarmic; -bool ShouldTestInst(u32 instruction, u32 pc, bool is_last_inst) { - const A32::LocationDescriptor location{pc, {}, {}}; +bool ShouldTestInst(u32 instruction, u32 pc, bool is_thumb, bool is_last_inst) { + const A32::LocationDescriptor location = A32::LocationDescriptor{pc, {}, {}}.SetTFlag(is_thumb); IR::Block block{location}; const bool should_continue = A32::TranslateSingleInstruction(block, location, instruction); @@ -74,7 +74,7 @@ bool ShouldTestInst(u32 instruction, u32 pc, bool is_last_inst) { return true; } -u32 GenRandomInst(u32 pc, bool is_last_inst) { +u32 GenRandomArmInst(u32 pc, bool is_last_inst) { static const struct InstructionGeneratorInfo { std::vector generators; std::vector invalid; @@ -139,13 +139,58 @@ u32 GenRandomInst(u32 pc, bool is_last_inst) { continue; } - if (ShouldTestInst(inst, pc, is_last_inst)) { + if (ShouldTestInst(inst, pc, false, is_last_inst)) { return inst; } } } -Dynarmic::A32::UserConfig GetUserConfig(ArmTestEnv& testenv) { +std::vector GenRandomThumbInst(u32 pc, bool is_last_inst) { + static const struct InstructionGeneratorInfo { + std::vector generators; + std::vector invalid; + } instructions = []{ + const std::vector> list { +#define INST(fn, name, bitstring) {#fn, bitstring}, +#include "frontend/A32/decoder/thumb16.inc" +#include "frontend/A32/decoder/thumb32.inc" +#undef INST + }; + + std::vector generators; + std::vector invalid; + + // List of instructions not to test + static constexpr std::array do_not_test { + "thumb16_SETEND", + "thumb16_BKPT", + }; + + for (const auto& [fn, bitstring] : list) { + if (std::find(do_not_test.begin(), do_not_test.end(), fn) != do_not_test.end()) { + invalid.emplace_back(InstructionGenerator{bitstring}); + continue; + } + generators.emplace_back(InstructionGenerator{bitstring}); + } + return InstructionGeneratorInfo{generators, invalid}; + }(); + + while (true) { + const size_t index = RandInt(0, instructions.generators.size() - 1); + const u32 inst = instructions.generators[index].Generate(); + const bool is_four_bytes = (inst >> 16) != 0; + + if (ShouldTestInst(is_four_bytes ? Common::SwapHalves32(inst) : inst, pc, true, is_last_inst)) { + if (is_four_bytes) + return { static_cast(inst >> 16), static_cast(inst) }; + return { static_cast(inst) }; + } + } +} + +template +Dynarmic::A32::UserConfig GetUserConfig(TestEnv& testenv) { Dynarmic::A32::UserConfig user_config; user_config.optimizations &= ~OptimizationFlag::FastDispatch; user_config.callbacks = &testenv; @@ -153,22 +198,28 @@ Dynarmic::A32::UserConfig GetUserConfig(ArmTestEnv& testenv) { return user_config; } -static void RunTestInstance(Dynarmic::A32::Jit& jit, A32Unicorn& uni, - ArmTestEnv& jit_env, ArmTestEnv& uni_env, - const A32Unicorn::RegisterArray& regs, - const A32Unicorn::ExtRegArray& vecs, - const std::vector& instructions, const u32 cpsr, const u32 fpscr) { +template +static void RunTestInstance(Dynarmic::A32::Jit& jit, + A32Unicorn& uni, + TestEnv& jit_env, + TestEnv& uni_env, + const typename A32Unicorn::RegisterArray& regs, + const typename A32Unicorn::ExtRegArray& vecs, + const std::vector& instructions, + const u32 cpsr, + const u32 fpscr, + const size_t ticks_left) { const u32 initial_pc = regs[15]; - const u32 num_words = initial_pc / sizeof(u32); + const u32 num_words = initial_pc / sizeof(typename TestEnv::InstructionType); const u32 code_mem_size = num_words + static_cast(instructions.size()); - jit_env.code_mem.resize(code_mem_size + 1); - uni_env.code_mem.resize(code_mem_size + 1); + jit_env.code_mem.resize(code_mem_size); + uni_env.code_mem.resize(code_mem_size); std::copy(instructions.begin(), instructions.end(), jit_env.code_mem.begin() + num_words); std::copy(instructions.begin(), instructions.end(), uni_env.code_mem.begin() + num_words); - jit_env.code_mem.back() = 0xEAFFFFFE; // B . - uni_env.code_mem.back() = 0xEAFFFFFE; // B . + jit_env.PadCodeMem(); + uni_env.PadCodeMem(); jit_env.modified_memory.clear(); uni_env.modified_memory.clear(); jit_env.interrupts.clear(); @@ -186,10 +237,23 @@ static void RunTestInstance(Dynarmic::A32::Jit& jit, A32Unicorn& uni uni.SetCpsr(cpsr); uni.ClearPageCache(); - jit_env.ticks_left = instructions.size(); + jit_env.ticks_left = ticks_left; jit.Run(); - uni_env.ticks_left = instructions.size(); + uni_env.ticks_left = [&]{ + if constexpr (std::is_same_v) { + // Unicorn counts thumb instructions weirdly: + // A 32-bit thumb instruction counts as two. + // Except for branch instructions which count as one??? + if (instructions.size() <= 1) + return ticks_left; + if ((instructions[instructions.size() - 2] & 0xF800) <= 0xE800) + return instructions.size(); + return instructions.size() - 1; + } else { + return ticks_left; + } + }(); uni.Run(); SCOPE_FAIL { @@ -279,7 +343,7 @@ static void RunTestInstance(Dynarmic::A32::Jit& jit, A32Unicorn& uni } } // Anonymous namespace -TEST_CASE("A32: Single random instruction", "[arm]") { +TEST_CASE("A32: Single random arm instruction", "[arm]") { ArmTestEnv jit_env{}; ArmTestEnv uni_env{}; @@ -294,7 +358,7 @@ TEST_CASE("A32: Single random instruction", "[arm]") { std::generate(regs.begin(), regs.end(), [] { return RandInt(0, ~u32(0)); }); std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt(0, ~u32(0)); }); - instructions[0] = GenRandomInst(0, true); + instructions[0] = GenRandomArmInst(0, true); const u32 start_address = 100; const u32 cpsr = (RandInt(0, 0xF) << 28) | 0x10; @@ -303,11 +367,11 @@ TEST_CASE("A32: Single random instruction", "[arm]") { INFO("Instruction: 0x" << std::hex << instructions[0]); regs[15] = start_address; - RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr); + RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr, 1); } } -TEST_CASE("A32: Small random block", "[arm]") { +TEST_CASE("A32: Small random arm block", "[arm]") { ArmTestEnv jit_env{}; ArmTestEnv uni_env{}; @@ -322,11 +386,11 @@ TEST_CASE("A32: Small random block", "[arm]") { std::generate(regs.begin(), regs.end(), [] { return RandInt(0, ~u32(0)); }); std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt(0, ~u32(0)); }); - instructions[0] = GenRandomInst(0, false); - instructions[1] = GenRandomInst(4, false); - instructions[2] = GenRandomInst(8, false); - instructions[3] = GenRandomInst(12, false); - instructions[4] = GenRandomInst(16, true); + instructions[0] = GenRandomArmInst(0, false); + instructions[1] = GenRandomArmInst(4, false); + instructions[2] = GenRandomArmInst(8, false); + instructions[3] = GenRandomArmInst(12, false); + instructions[4] = GenRandomArmInst(16, true); const u32 start_address = 100; const u32 cpsr = (RandInt(0, 0xF) << 28) | 0x10; @@ -339,11 +403,11 @@ TEST_CASE("A32: Small random block", "[arm]") { INFO("Instruction 5: 0x" << std::hex << instructions[4]); regs[15] = start_address; - RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr); + RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr, 5); } } -TEST_CASE("A32: Large random block", "[arm]") { +TEST_CASE("A32: Large random arm block", "[arm]") { ArmTestEnv jit_env{}; ArmTestEnv uni_env{}; @@ -361,7 +425,7 @@ TEST_CASE("A32: Large random block", "[arm]") { std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt(0, ~u32(0)); }); for (size_t j = 0; j < instruction_count; ++j) { - instructions[j] = GenRandomInst(j * 4, j == instruction_count - 1); + instructions[j] = GenRandomArmInst(j * 4, j == instruction_count - 1); } const u64 start_address = 100; @@ -369,6 +433,64 @@ TEST_CASE("A32: Large random block", "[arm]") { const u32 fpcr = RandomFpcr(); regs[15] = start_address; - RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr); + RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr, 100); + } +} + +TEST_CASE("A32: Single random thumb instruction", "[thumb]") { + ThumbTestEnv jit_env{}; + ThumbTestEnv uni_env{}; + + Dynarmic::A32::Jit jit{GetUserConfig(jit_env)}; + A32Unicorn uni{uni_env}; + + A32Unicorn::RegisterArray regs; + A32Unicorn::ExtRegArray ext_reg; + std::vector instructions; + + for (size_t iteration = 0; iteration < 100000; ++iteration) { + std::generate(regs.begin(), regs.end(), [] { return RandInt(0, ~u32(0)); }); + std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt(0, ~u32(0)); }); + + instructions = GenRandomThumbInst(0, true); + + const u32 start_address = 100; + const u32 cpsr = (RandInt(0, 0xF) << 28) | 0x1F0; + const u32 fpcr = RandomFpcr(); + + INFO("Instruction: 0x" << std::hex << instructions[0]); + + regs[15] = start_address; + RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr, 1); + } +} + +TEST_CASE("A32: Small random thumb block", "[thumb]") { + ThumbTestEnv jit_env{}; + ThumbTestEnv uni_env{}; + + Dynarmic::A32::Jit jit{GetUserConfig(jit_env)}; + A32Unicorn uni{uni_env}; + + A32Unicorn::RegisterArray regs; + A32Unicorn::ExtRegArray ext_reg; + std::vector instructions; + + for (size_t iteration = 0; iteration < 100000; ++iteration) { + std::generate(regs.begin(), regs.end(), [] { return RandInt(0, ~u32(0)); }); + std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt(0, ~u32(0)); }); + + instructions.clear(); + for (size_t i = 0; i < 5; i++) { + const std::vector inst = GenRandomThumbInst(instructions.size() * 2, i == 4); + instructions.insert(instructions.end(), inst.begin(), inst.end()); + } + + const u32 start_address = 100; + const u32 cpsr = (RandInt(0, 0xF) << 28) | 0x1F0; + const u32 fpcr = RandomFpcr(); + + regs[15] = start_address; + RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr, 5); } } diff --git a/tests/A32/testenv.h b/tests/A32/testenv.h index 93f207a4..75e15841 100644 --- a/tests/A32/testenv.h +++ b/tests/A32/testenv.h @@ -16,19 +16,27 @@ #include "common/assert.h" #include "common/common_types.h" -template +template class A32TestEnv final : public Dynarmic::A32::UserCallbacks { public: using InstructionType = InstructionType_; using RegisterArray = std::array; using ExtRegsArray = std::array; + static constexpr InstructionType infinite_loop = static_cast(infinite_loop_u32); + u64 ticks_left = 0; bool code_mem_modified_by_guest = false; std::vector code_mem; std::map modified_memory; std::vector interrupts; + void PadCodeMem() { + do { + code_mem.push_back(infinite_loop); + } while (code_mem.size() % 2 != 0); + } + std::uint32_t MemoryReadCode(u32 vaddr) override { const size_t index = vaddr / sizeof(InstructionType); if (index < code_mem.size()) { @@ -36,7 +44,7 @@ public: std::memcpy(&value, &code_mem[index], sizeof(u32)); return value; } - return infinite_loop; // B . + return infinite_loop_u32; // B . } std::uint8_t MemoryRead8(u32 vaddr) override { @@ -81,7 +89,7 @@ public: void CallSVC(std::uint32_t swi) override { ASSERT_MSG(false, "CallSVC({})", swi); } - void ExceptionRaised(u32 pc, Dynarmic::A32::Exception /*exception*/) override { ASSERT_MSG(false, "ExceptionRaised({:08x})", pc); } + void ExceptionRaised(u32 pc, Dynarmic::A32::Exception /*exception*/) override { ASSERT_MSG(false, "ExceptionRaised({:08x}) code = {:08x}", pc, MemoryReadCode(pc)); } void AddTicks(std::uint64_t ticks) override { if (ticks > ticks_left) { diff --git a/tests/fuzz_util.cpp b/tests/fuzz_util.cpp index f6f610c7..3acbc0dd 100644 --- a/tests/fuzz_util.cpp +++ b/tests/fuzz_util.cpp @@ -35,10 +35,16 @@ u32 RandomFpcr() { } InstructionGenerator::InstructionGenerator(const char* format){ - ASSERT(std::strlen(format) == 32); + const size_t format_len = std::strlen(format); + ASSERT(format_len == 16 || format_len == 32); - for (int i = 0; i < 32; i++) { - const u32 bit = 1u << (31 - i); + if (format_len == 16) { + // Begin with 16 zeros + mask |= 0xFFFF0000; + } + + for (size_t i = 0; i < format_len; i++) { + const u32 bit = 1u << (format_len - i - 1); switch (format[i]) { case '0': mask |= bit;