fuzz_arm: Add fuzzing for thumb instructions
This commit is contained in:
parent
8b612edb75
commit
331a02e02e
3 changed files with 172 additions and 36 deletions
|
@ -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<InstructionGenerator> generators;
|
||||
std::vector<InstructionGenerator> 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<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;
|
||||
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<ArmTestEnv>& uni,
|
||||
ArmTestEnv& jit_env, ArmTestEnv& uni_env,
|
||||
const A32Unicorn<ArmTestEnv>::RegisterArray& regs,
|
||||
const A32Unicorn<ArmTestEnv>::ExtRegArray& vecs,
|
||||
const std::vector<u32>& instructions, const u32 cpsr, const u32 fpscr) {
|
||||
template <typename TestEnv>
|
||||
static void RunTestInstance(Dynarmic::A32::Jit& jit,
|
||||
A32Unicorn<TestEnv>& uni,
|
||||
TestEnv& jit_env,
|
||||
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 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());
|
||||
|
||||
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<ArmTestEnv>& 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<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();
|
||||
|
||||
SCOPE_FAIL {
|
||||
|
@ -279,7 +343,7 @@ static void RunTestInstance(Dynarmic::A32::Jit& jit, A32Unicorn<ArmTestEnv>& 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<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 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]);
|
||||
|
||||
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<u32>(0, ~u32(0)); });
|
||||
std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt<u32>(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<u32>(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<u32>(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<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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,19 +16,27 @@
|
|||
#include "common/assert.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 {
|
||||
public:
|
||||
using InstructionType = InstructionType_;
|
||||
using RegisterArray = std::array<u32, 16>;
|
||||
using ExtRegsArray = std::array<u32, 64>;
|
||||
|
||||
static constexpr InstructionType infinite_loop = static_cast<InstructionType>(infinite_loop_u32);
|
||||
|
||||
u64 ticks_left = 0;
|
||||
bool code_mem_modified_by_guest = false;
|
||||
std::vector<InstructionType> code_mem;
|
||||
std::map<u32, u8> modified_memory;
|
||||
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 {
|
||||
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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue