fuzz_arm: Add fuzzing for thumb instructions

This commit is contained in:
MerryMage 2021-02-06 21:25:08 +00:00
parent 8b612edb75
commit 331a02e02e
3 changed files with 172 additions and 36 deletions

View file

@ -36,8 +36,8 @@
namespace { namespace {
using namespace Dynarmic; using namespace Dynarmic;
bool ShouldTestInst(u32 instruction, u32 pc, bool is_last_inst) { bool ShouldTestInst(u32 instruction, u32 pc, bool is_thumb, bool is_last_inst) {
const A32::LocationDescriptor location{pc, {}, {}}; const A32::LocationDescriptor location = A32::LocationDescriptor{pc, {}, {}}.SetTFlag(is_thumb);
IR::Block block{location}; IR::Block block{location};
const bool should_continue = A32::TranslateSingleInstruction(block, location, instruction); 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; return true;
} }
u32 GenRandomInst(u32 pc, bool is_last_inst) { u32 GenRandomArmInst(u32 pc, bool is_last_inst) {
static const struct InstructionGeneratorInfo { static const struct InstructionGeneratorInfo {
std::vector<InstructionGenerator> generators; std::vector<InstructionGenerator> generators;
std::vector<InstructionGenerator> invalid; std::vector<InstructionGenerator> invalid;
@ -139,13 +139,58 @@ u32 GenRandomInst(u32 pc, bool is_last_inst) {
continue; continue;
} }
if (ShouldTestInst(inst, pc, is_last_inst)) { if (ShouldTestInst(inst, pc, false, is_last_inst)) {
return inst; return inst;
} }
} }
} }
Dynarmic::A32::UserConfig GetUserConfig(ArmTestEnv& testenv) { std::vector<u16> GenRandomThumbInst(u32 pc, bool is_last_inst) {
static const struct InstructionGeneratorInfo {
std::vector<InstructionGenerator> generators;
std::vector<InstructionGenerator> invalid;
} instructions = []{
const std::vector<std::tuple<std::string, const char*>> list {
#define INST(fn, name, bitstring) {#fn, bitstring},
#include "frontend/A32/decoder/thumb16.inc"
#include "frontend/A32/decoder/thumb32.inc"
#undef INST
};
std::vector<InstructionGenerator> generators;
std::vector<InstructionGenerator> 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<size_t>(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<u16>(inst >> 16), static_cast<u16>(inst) };
return { static_cast<u16>(inst) };
}
}
}
template <typename TestEnv>
Dynarmic::A32::UserConfig GetUserConfig(TestEnv& testenv) {
Dynarmic::A32::UserConfig user_config; Dynarmic::A32::UserConfig user_config;
user_config.optimizations &= ~OptimizationFlag::FastDispatch; user_config.optimizations &= ~OptimizationFlag::FastDispatch;
user_config.callbacks = &testenv; user_config.callbacks = &testenv;
@ -153,22 +198,28 @@ Dynarmic::A32::UserConfig GetUserConfig(ArmTestEnv& testenv) {
return user_config; return user_config;
} }
static void RunTestInstance(Dynarmic::A32::Jit& jit, A32Unicorn<ArmTestEnv>& uni, template <typename TestEnv>
ArmTestEnv& jit_env, ArmTestEnv& uni_env, static void RunTestInstance(Dynarmic::A32::Jit& jit,
const A32Unicorn<ArmTestEnv>::RegisterArray& regs, A32Unicorn<TestEnv>& uni,
const A32Unicorn<ArmTestEnv>::ExtRegArray& vecs, TestEnv& jit_env,
const std::vector<u32>& instructions, const u32 cpsr, const u32 fpscr) { TestEnv& uni_env,
const typename A32Unicorn<TestEnv>::RegisterArray& regs,
const typename A32Unicorn<TestEnv>::ExtRegArray& vecs,
const std::vector<typename TestEnv::InstructionType>& instructions,
const u32 cpsr,
const u32 fpscr,
const size_t ticks_left) {
const u32 initial_pc = regs[15]; 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<u32>(instructions.size()); const u32 code_mem_size = num_words + static_cast<u32>(instructions.size());
jit_env.code_mem.resize(code_mem_size + 1); jit_env.code_mem.resize(code_mem_size);
uni_env.code_mem.resize(code_mem_size + 1); 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(), jit_env.code_mem.begin() + num_words);
std::copy(instructions.begin(), instructions.end(), uni_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 . jit_env.PadCodeMem();
uni_env.code_mem.back() = 0xEAFFFFFE; // B . uni_env.PadCodeMem();
jit_env.modified_memory.clear(); jit_env.modified_memory.clear();
uni_env.modified_memory.clear(); uni_env.modified_memory.clear();
jit_env.interrupts.clear(); jit_env.interrupts.clear();
@ -186,10 +237,23 @@ static void RunTestInstance(Dynarmic::A32::Jit& jit, A32Unicorn<ArmTestEnv>& uni
uni.SetCpsr(cpsr); uni.SetCpsr(cpsr);
uni.ClearPageCache(); uni.ClearPageCache();
jit_env.ticks_left = instructions.size(); jit_env.ticks_left = ticks_left;
jit.Run(); jit.Run();
uni_env.ticks_left = instructions.size(); uni_env.ticks_left = [&]{
if constexpr (std::is_same_v<TestEnv, ThumbTestEnv>) {
// 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(); uni.Run();
SCOPE_FAIL { SCOPE_FAIL {
@ -279,7 +343,7 @@ static void RunTestInstance(Dynarmic::A32::Jit& jit, A32Unicorn<ArmTestEnv>& uni
} }
} // Anonymous namespace } // Anonymous namespace
TEST_CASE("A32: Single random instruction", "[arm]") { TEST_CASE("A32: Single random arm instruction", "[arm]") {
ArmTestEnv jit_env{}; ArmTestEnv jit_env{};
ArmTestEnv uni_env{}; ArmTestEnv uni_env{};
@ -294,7 +358,7 @@ TEST_CASE("A32: Single random instruction", "[arm]") {
std::generate(regs.begin(), regs.end(), [] { return RandInt<u32>(0, ~u32(0)); }); std::generate(regs.begin(), regs.end(), [] { return RandInt<u32>(0, ~u32(0)); });
std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt<u32>(0, ~u32(0)); }); std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt<u32>(0, ~u32(0)); });
instructions[0] = GenRandomInst(0, true); instructions[0] = GenRandomArmInst(0, true);
const u32 start_address = 100; const u32 start_address = 100;
const u32 cpsr = (RandInt<u32>(0, 0xF) << 28) | 0x10; const u32 cpsr = (RandInt<u32>(0, 0xF) << 28) | 0x10;
@ -303,11 +367,11 @@ TEST_CASE("A32: Single random instruction", "[arm]") {
INFO("Instruction: 0x" << std::hex << instructions[0]); INFO("Instruction: 0x" << std::hex << instructions[0]);
regs[15] = start_address; 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 jit_env{};
ArmTestEnv uni_env{}; ArmTestEnv uni_env{};
@ -322,11 +386,11 @@ TEST_CASE("A32: Small random block", "[arm]") {
std::generate(regs.begin(), regs.end(), [] { return RandInt<u32>(0, ~u32(0)); }); std::generate(regs.begin(), regs.end(), [] { return RandInt<u32>(0, ~u32(0)); });
std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt<u32>(0, ~u32(0)); }); std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt<u32>(0, ~u32(0)); });
instructions[0] = GenRandomInst(0, false); instructions[0] = GenRandomArmInst(0, false);
instructions[1] = GenRandomInst(4, false); instructions[1] = GenRandomArmInst(4, false);
instructions[2] = GenRandomInst(8, false); instructions[2] = GenRandomArmInst(8, false);
instructions[3] = GenRandomInst(12, false); instructions[3] = GenRandomArmInst(12, false);
instructions[4] = GenRandomInst(16, true); instructions[4] = GenRandomArmInst(16, true);
const u32 start_address = 100; const u32 start_address = 100;
const u32 cpsr = (RandInt<u32>(0, 0xF) << 28) | 0x10; const u32 cpsr = (RandInt<u32>(0, 0xF) << 28) | 0x10;
@ -339,11 +403,11 @@ TEST_CASE("A32: Small random block", "[arm]") {
INFO("Instruction 5: 0x" << std::hex << instructions[4]); INFO("Instruction 5: 0x" << std::hex << instructions[4]);
regs[15] = start_address; 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 jit_env{};
ArmTestEnv uni_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<u32>(0, ~u32(0)); }); std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt<u32>(0, ~u32(0)); });
for (size_t j = 0; j < instruction_count; ++j) { 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; const u64 start_address = 100;
@ -369,6 +433,64 @@ TEST_CASE("A32: Large random block", "[arm]") {
const u32 fpcr = RandomFpcr(); const u32 fpcr = RandomFpcr();
regs[15] = start_address; 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<ThumbTestEnv> uni{uni_env};
A32Unicorn<ThumbTestEnv>::RegisterArray regs;
A32Unicorn<ThumbTestEnv>::ExtRegArray ext_reg;
std::vector<u16> instructions;
for (size_t iteration = 0; iteration < 100000; ++iteration) {
std::generate(regs.begin(), regs.end(), [] { return RandInt<u32>(0, ~u32(0)); });
std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt<u32>(0, ~u32(0)); });
instructions = GenRandomThumbInst(0, true);
const u32 start_address = 100;
const u32 cpsr = (RandInt<u32>(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<ThumbTestEnv> uni{uni_env};
A32Unicorn<ThumbTestEnv>::RegisterArray regs;
A32Unicorn<ThumbTestEnv>::ExtRegArray ext_reg;
std::vector<u16> instructions;
for (size_t iteration = 0; iteration < 100000; ++iteration) {
std::generate(regs.begin(), regs.end(), [] { return RandInt<u32>(0, ~u32(0)); });
std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt<u32>(0, ~u32(0)); });
instructions.clear();
for (size_t i = 0; i < 5; i++) {
const std::vector<u16> inst = GenRandomThumbInst(instructions.size() * 2, i == 4);
instructions.insert(instructions.end(), inst.begin(), inst.end());
}
const u32 start_address = 100;
const u32 cpsr = (RandInt<u32>(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);
} }
} }

View file

@ -16,19 +16,27 @@
#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_u32>
class A32TestEnv final : public Dynarmic::A32::UserCallbacks { class A32TestEnv final : public Dynarmic::A32::UserCallbacks {
public: public:
using InstructionType = InstructionType_; 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>;
static constexpr InstructionType infinite_loop = static_cast<InstructionType>(infinite_loop_u32);
u64 ticks_left = 0; u64 ticks_left = 0;
bool code_mem_modified_by_guest = false; bool code_mem_modified_by_guest = false;
std::vector<InstructionType> code_mem; std::vector<InstructionType> code_mem;
std::map<u32, u8> modified_memory; std::map<u32, u8> modified_memory;
std::vector<std::string> interrupts; std::vector<std::string> interrupts;
void PadCodeMem() {
do {
code_mem.push_back(infinite_loop);
} while (code_mem.size() % 2 != 0);
}
std::uint32_t MemoryReadCode(u32 vaddr) override { std::uint32_t MemoryReadCode(u32 vaddr) override {
const size_t index = vaddr / sizeof(InstructionType); const size_t index = vaddr / sizeof(InstructionType);
if (index < code_mem.size()) { if (index < code_mem.size()) {
@ -36,7 +44,7 @@ public:
std::memcpy(&value, &code_mem[index], sizeof(u32)); std::memcpy(&value, &code_mem[index], sizeof(u32));
return value; return value;
} }
return infinite_loop; // B . return infinite_loop_u32; // B .
} }
std::uint8_t MemoryRead8(u32 vaddr) override { 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 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 { void AddTicks(std::uint64_t ticks) override {
if (ticks > ticks_left) { if (ticks > ticks_left) {

View file

@ -35,10 +35,16 @@ u32 RandomFpcr() {
} }
InstructionGenerator::InstructionGenerator(const char* format){ 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++) { if (format_len == 16) {
const u32 bit = 1u << (31 - i); // 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]) { switch (format[i]) {
case '0': case '0':
mask |= bit; mask |= bit;