From d40557b751b559d94f6dd2499c1355c8197ef7c7 Mon Sep 17 00:00:00 2001 From: Merry Date: Tue, 21 Jun 2022 21:30:24 +0100 Subject: [PATCH] A32/A64: Allow std::nullopt from MemoryReadCode Raise a fault at runtime if this block is executed --- .../frontend/A32/translate/translate_arm.cpp | 21 +++-- .../A32/translate/translate_callbacks.h | 7 +- .../A32/translate/translate_thumb.cpp | 87 +++++++++++-------- .../frontend/A64/translate/a64_translate.cpp | 11 ++- .../frontend/A64/translate/a64_translate.h | 3 +- src/dynarmic/interface/A32/config.h | 6 +- src/dynarmic/interface/A64/config.h | 6 +- .../ir/opt/a64_merge_interpret_blocks.cpp | 6 +- tests/A32/testenv.h | 8 +- tests/A64/testenv.h | 4 +- tests/print_info.cpp | 2 +- tests/unicorn_emu/a32_unicorn.cpp | 2 +- 12 files changed, 99 insertions(+), 64 deletions(-) diff --git a/src/dynarmic/frontend/A32/translate/translate_arm.cpp b/src/dynarmic/frontend/A32/translate/translate_arm.cpp index 95c704da..8db6cab9 100644 --- a/src/dynarmic/frontend/A32/translate/translate_arm.cpp +++ b/src/dynarmic/frontend/A32/translate/translate_arm.cpp @@ -28,19 +28,22 @@ IR::Block TranslateArm(LocationDescriptor descriptor, TranslateCallbacks* tcb, c bool should_continue = true; do { const u32 arm_pc = visitor.ir.current_location.PC(); - const u32 arm_instruction = tcb->MemoryReadCode(arm_pc); visitor.current_instruction_size = 4; - tcb->PreCodeTranslationHook(false, arm_pc, visitor.ir); + if (const auto arm_instruction = tcb->MemoryReadCode(arm_pc)) { + tcb->PreCodeTranslationHook(false, arm_pc, visitor.ir); - if (const auto vfp_decoder = DecodeVFP(arm_instruction)) { - should_continue = vfp_decoder->get().call(visitor, arm_instruction); - } else if (const auto asimd_decoder = DecodeASIMD(arm_instruction)) { - should_continue = asimd_decoder->get().call(visitor, arm_instruction); - } else if (const auto decoder = DecodeArm(arm_instruction)) { - should_continue = decoder->get().call(visitor, arm_instruction); + if (const auto vfp_decoder = DecodeVFP(*arm_instruction)) { + should_continue = vfp_decoder->get().call(visitor, *arm_instruction); + } else if (const auto asimd_decoder = DecodeASIMD(*arm_instruction)) { + should_continue = asimd_decoder->get().call(visitor, *arm_instruction); + } else if (const auto decoder = DecodeArm(*arm_instruction)) { + should_continue = decoder->get().call(visitor, *arm_instruction); + } else { + should_continue = visitor.arm_UDF(); + } } else { - should_continue = visitor.arm_UDF(); + should_continue = visitor.RaiseException(Exception::NoExecuteFault); } if (visitor.cond_state == ConditionalState::Break) { diff --git a/src/dynarmic/frontend/A32/translate/translate_callbacks.h b/src/dynarmic/frontend/A32/translate/translate_callbacks.h index 8e0bba3a..a9d8e043 100644 --- a/src/dynarmic/frontend/A32/translate/translate_callbacks.h +++ b/src/dynarmic/frontend/A32/translate/translate_callbacks.h @@ -4,18 +4,19 @@ */ #pragma once -#include +#include +#include namespace Dynarmic::A32 { -using VAddr = u32; +using VAddr = std::uint32_t; class IREmitter; struct TranslateCallbacks { // All reads through this callback are 4-byte aligned. // Memory must be interpreted as little endian. - virtual std::uint32_t MemoryReadCode(VAddr vaddr) = 0; + virtual std::optional MemoryReadCode(VAddr vaddr) = 0; // Thus function is called before the instruction at pc is interpreted. // IR code can be emitted by the callee prior to translation of the instruction. diff --git a/src/dynarmic/frontend/A32/translate/translate_thumb.cpp b/src/dynarmic/frontend/A32/translate/translate_thumb.cpp index 9716f11f..f2b39a6c 100644 --- a/src/dynarmic/frontend/A32/translate/translate_thumb.cpp +++ b/src/dynarmic/frontend/A32/translate/translate_thumb.cpp @@ -44,28 +44,40 @@ bool IsUnconditionalInstruction(bool is_thumb_16, u32 instruction) { return false; } -std::tuple ReadThumbInstruction(u32 arm_pc, TranslateCallbacks* tcb) { - u32 first_part = tcb->MemoryReadCode(arm_pc & 0xFFFFFFFC); - if ((arm_pc & 0x2) != 0) { - first_part >>= 16; - } - first_part &= 0xFFFF; +std::optional> ReadThumbInstruction(u32 arm_pc, TranslateCallbacks* tcb) { + u32 instruction; - if (IsThumb16(static_cast(first_part))) { + const std::optional first_part = tcb->MemoryReadCode(arm_pc & 0xFFFFFFFC); + if (!first_part) + return std::nullopt; + + if ((arm_pc & 0x2) != 0) { + instruction = *first_part >> 16; + } else { + instruction = *first_part & 0xFFFF; + } + + if (IsThumb16(static_cast(instruction))) { // 16-bit thumb instruction - return std::make_tuple(first_part, ThumbInstSize::Thumb16); + return std::make_tuple(instruction, ThumbInstSize::Thumb16); } // 32-bit thumb instruction // These always start with 0b11101, 0b11110 or 0b11111. - u32 second_part = tcb->MemoryReadCode((arm_pc + 2) & 0xFFFFFFFC); - if (((arm_pc + 2) & 0x2) != 0) { - second_part >>= 16; - } - second_part &= 0xFFFF; + instruction <<= 16; - return std::make_tuple(static_cast((first_part << 16) | second_part), ThumbInstSize::Thumb32); + const std::optional second_part = tcb->MemoryReadCode((arm_pc + 2) & 0xFFFFFFFC); + if (!second_part) + return std::nullopt; + + if (((arm_pc + 2) & 0x2) != 0) { + instruction |= *second_part >> 16; + } else { + instruction |= *second_part & 0xFFFF; + } + + return std::make_tuple(instruction, ThumbInstSize::Thumb32); } // Convert from thumb ASIMD format to ARM ASIMD format. @@ -97,43 +109,48 @@ IR::Block TranslateThumb(LocationDescriptor descriptor, TranslateCallbacks* tcb, bool should_continue = true; do { const u32 arm_pc = visitor.ir.current_location.PC(); - const auto [thumb_instruction, inst_size] = ReadThumbInstruction(arm_pc, tcb); - const bool is_thumb_16 = inst_size == ThumbInstSize::Thumb16; - visitor.current_instruction_size = is_thumb_16 ? 2 : 4; + if (const auto maybe_instruction = ReadThumbInstruction(arm_pc, tcb)) { + const auto [thumb_instruction, inst_size] = *maybe_instruction; + const bool is_thumb_16 = inst_size == ThumbInstSize::Thumb16; + visitor.current_instruction_size = is_thumb_16 ? 2 : 4; - tcb->PreCodeTranslationHook(false, arm_pc, visitor.ir); + tcb->PreCodeTranslationHook(false, arm_pc, visitor.ir); - if (IsUnconditionalInstruction(is_thumb_16, thumb_instruction) || visitor.ThumbConditionPassed()) { - if (is_thumb_16) { - if (const auto decoder = DecodeThumb16(static_cast(thumb_instruction))) { - should_continue = decoder->get().call(visitor, static_cast(thumb_instruction)); + if (IsUnconditionalInstruction(is_thumb_16, thumb_instruction) || visitor.ThumbConditionPassed()) { + if (is_thumb_16) { + if (const auto decoder = DecodeThumb16(static_cast(thumb_instruction))) { + should_continue = decoder->get().call(visitor, static_cast(thumb_instruction)); + } else { + should_continue = visitor.thumb16_UDF(); + } } else { - should_continue = visitor.thumb16_UDF(); - } - } else { - if (MaybeVFPOrASIMDInstruction(thumb_instruction)) { - if (const auto vfp_decoder = DecodeVFP(thumb_instruction)) { - should_continue = vfp_decoder->get().call(visitor, thumb_instruction); - } else if (const auto asimd_decoder = DecodeASIMD(ConvertASIMDInstruction(thumb_instruction))) { - should_continue = asimd_decoder->get().call(visitor, ConvertASIMDInstruction(thumb_instruction)); + if (MaybeVFPOrASIMDInstruction(thumb_instruction)) { + if (const auto vfp_decoder = DecodeVFP(thumb_instruction)) { + should_continue = vfp_decoder->get().call(visitor, thumb_instruction); + } else if (const auto asimd_decoder = DecodeASIMD(ConvertASIMDInstruction(thumb_instruction))) { + should_continue = asimd_decoder->get().call(visitor, ConvertASIMDInstruction(thumb_instruction)); + } else if (const auto decoder = DecodeThumb32(thumb_instruction)) { + should_continue = decoder->get().call(visitor, thumb_instruction); + } else { + should_continue = visitor.thumb32_UDF(); + } } else if (const auto decoder = DecodeThumb32(thumb_instruction)) { should_continue = decoder->get().call(visitor, thumb_instruction); } else { should_continue = visitor.thumb32_UDF(); } - } else if (const auto decoder = DecodeThumb32(thumb_instruction)) { - should_continue = decoder->get().call(visitor, thumb_instruction); - } else { - should_continue = visitor.thumb32_UDF(); } } + } else { + visitor.current_instruction_size = 2; + should_continue = visitor.RaiseException(Exception::NoExecuteFault); } if (visitor.cond_state == ConditionalState::Break) { break; } - visitor.ir.current_location = visitor.ir.current_location.AdvancePC(is_thumb_16 ? 2 : 4).AdvanceIT(); + visitor.ir.current_location = visitor.ir.current_location.AdvancePC(static_cast(visitor.current_instruction_size)).AdvanceIT(); block.CycleCount()++; } while (should_continue && CondCanContinue(visitor.cond_state, visitor.ir) && !single_step); diff --git a/src/dynarmic/frontend/A64/translate/a64_translate.cpp b/src/dynarmic/frontend/A64/translate/a64_translate.cpp index 84c5201a..05996aeb 100644 --- a/src/dynarmic/frontend/A64/translate/a64_translate.cpp +++ b/src/dynarmic/frontend/A64/translate/a64_translate.cpp @@ -22,12 +22,15 @@ IR::Block Translate(LocationDescriptor descriptor, MemoryReadCodeFuncType memory bool should_continue = true; do { const u64 pc = visitor.ir.current_location->PC(); - const u32 instruction = memory_read_code(pc); - if (auto decoder = Decode(instruction)) { - should_continue = decoder->get().call(visitor, instruction); + if (const auto instruction = memory_read_code(pc)) { + if (auto decoder = Decode(*instruction)) { + should_continue = decoder->get().call(visitor, *instruction); + } else { + should_continue = visitor.InterpretThisInstruction(); + } } else { - should_continue = visitor.InterpretThisInstruction(); + should_continue = visitor.RaiseException(Exception::NoExecuteFault); } visitor.ir.current_location = visitor.ir.current_location->AdvancePC(4); diff --git a/src/dynarmic/frontend/A64/translate/a64_translate.h b/src/dynarmic/frontend/A64/translate/a64_translate.h index 8befe5fa..61402a2f 100644 --- a/src/dynarmic/frontend/A64/translate/a64_translate.h +++ b/src/dynarmic/frontend/A64/translate/a64_translate.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include @@ -18,7 +19,7 @@ namespace A64 { class LocationDescriptor; -using MemoryReadCodeFuncType = std::function; +using MemoryReadCodeFuncType = std::function(u64 vaddr)>; struct TranslationOptions { /// This changes what IR we emit when we translate an unpredictable instruction. diff --git a/src/dynarmic/interface/A32/config.h b/src/dynarmic/interface/A32/config.h index 55eed113..ef13b0e0 100644 --- a/src/dynarmic/interface/A32/config.h +++ b/src/dynarmic/interface/A32/config.h @@ -9,6 +9,7 @@ #include #include #include +#include #include "dynarmic/frontend/A32/translate/translate_callbacks.h" #include "dynarmic/interface/A32/arch_version.h" @@ -51,6 +52,9 @@ enum class Exception { PreloadDataWithIntentToWrite, /// A PLI instruction was executed. (Hint instruction.) PreloadInstruction, + /// Attempted to execute a code block at an address for which MemoryReadCode returned std::nullopt. + /// (Intended to be used to emulate memory protection faults.) + NoExecuteFault, }; /// These function pointers may be inserted into compiled code. @@ -59,7 +63,7 @@ struct UserCallbacks : public TranslateCallbacks { // All reads through this callback are 4-byte aligned. // Memory must be interpreted as little endian. - std::uint32_t MemoryReadCode(VAddr vaddr) override { return MemoryRead32(vaddr); } + std::optional MemoryReadCode(VAddr vaddr) override { return MemoryRead32(vaddr); } // Thus function is called before the instruction at pc is interpreted. // IR code can be emitted by the callee prior to translation of the instruction. diff --git a/src/dynarmic/interface/A64/config.h b/src/dynarmic/interface/A64/config.h index f8ba8f49..35b6b1ad 100644 --- a/src/dynarmic/interface/A64/config.h +++ b/src/dynarmic/interface/A64/config.h @@ -9,6 +9,7 @@ #include #include #include +#include #include "dynarmic/interface/optimization_flags.h" @@ -45,6 +46,9 @@ enum class Exception { Yield, /// A BRK instruction was executed. (Hint instruction.) Breakpoint, + /// Attempted to execute a code block at an address for which MemoryReadCode returned std::nullopt. + /// (Intended to be used to emulate memory protection faults.) + NoExecuteFault, }; enum class DataCacheOperation { @@ -82,7 +86,7 @@ struct UserCallbacks { // All reads through this callback are 4-byte aligned. // Memory must be interpreted as little endian. - virtual std::uint32_t MemoryReadCode(VAddr vaddr) { return MemoryRead32(vaddr); } + virtual std::optional MemoryReadCode(VAddr vaddr) { return MemoryRead32(vaddr); } // Reads through these callbacks may not be aligned. virtual std::uint8_t MemoryRead8(VAddr vaddr) = 0; diff --git a/src/dynarmic/ir/opt/a64_merge_interpret_blocks.cpp b/src/dynarmic/ir/opt/a64_merge_interpret_blocks.cpp index d9b2558d..00a0e1b6 100644 --- a/src/dynarmic/ir/opt/a64_merge_interpret_blocks.cpp +++ b/src/dynarmic/ir/opt/a64_merge_interpret_blocks.cpp @@ -16,10 +16,12 @@ namespace Dynarmic::Optimization { void A64MergeInterpretBlocksPass(IR::Block& block, A64::UserCallbacks* cb) { const auto is_interpret_instruction = [cb](A64::LocationDescriptor location) { - const u32 instruction = cb->MemoryReadCode(location.PC()); + const auto instruction = cb->MemoryReadCode(location.PC()); + if (!instruction) + return false; IR::Block new_block{location}; - A64::TranslateSingleInstruction(new_block, location, instruction); + A64::TranslateSingleInstruction(new_block, location, *instruction); if (!new_block.Instructions().empty()) return false; diff --git a/tests/A32/testenv.h b/tests/A32/testenv.h index ac7921ec..bc6775ac 100644 --- a/tests/A32/testenv.h +++ b/tests/A32/testenv.h @@ -48,7 +48,7 @@ public: return vaddr < sizeof(InstructionType) * code_mem.size(); } - std::uint32_t MemoryReadCode(u32 vaddr) override { + std::optional MemoryReadCode(u32 vaddr) override { if (IsInCodeMem(vaddr)) { u32 value; std::memcpy(&value, &code_mem[vaddr / sizeof(InstructionType)], sizeof(u32)); @@ -95,11 +95,11 @@ public: MemoryWrite32(vaddr + 4, static_cast(value >> 32)); } - void InterpreterFallback(u32 pc, size_t num_instructions) override { ASSERT_MSG(false, "InterpreterFallback({:08x}, {}) code = {:08x}", pc, num_instructions, MemoryReadCode(pc)); } + void InterpreterFallback(u32 pc, size_t num_instructions) override { ASSERT_MSG(false, "InterpreterFallback({:08x}, {}) code = {:08x}", pc, num_instructions, *MemoryReadCode(pc)); } 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}) code = {:08x}", pc, MemoryReadCode(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) { @@ -135,7 +135,7 @@ public: memcpy(backing_memory + vaddr, &value, sizeof(T)); } - std::uint32_t MemoryReadCode(std::uint32_t vaddr) override { + std::optional MemoryReadCode(std::uint32_t vaddr) override { return read(vaddr); } diff --git a/tests/A64/testenv.h b/tests/A64/testenv.h index 596d0114..73525242 100644 --- a/tests/A64/testenv.h +++ b/tests/A64/testenv.h @@ -30,7 +30,7 @@ public: return vaddr >= code_mem_start_address && vaddr < code_mem_start_address + code_mem.size() * 4; } - std::uint32_t MemoryReadCode(u64 vaddr) override { + std::optional MemoryReadCode(u64 vaddr) override { if (!IsInCodeMem(vaddr)) { return 0x14000000; // B . } @@ -145,7 +145,7 @@ public: memcpy(backing_memory + vaddr, &value, sizeof(T)); } - std::uint32_t MemoryReadCode(u64 vaddr) override { + std::optional MemoryReadCode(u64 vaddr) override { return read(vaddr); } diff --git a/tests/print_info.cpp b/tests/print_info.cpp index d7978fd8..ac07e3ca 100644 --- a/tests/print_info.cpp +++ b/tests/print_info.cpp @@ -157,7 +157,7 @@ public: } void InterpreterFallback(u32 pc, size_t num_instructions) override { - fmt::print("> InterpreterFallback({:08x}, {}) code = {:08x}\n", pc, num_instructions, MemoryReadCode(pc)); + fmt::print("> InterpreterFallback({:08x}, {}) code = {:08x}\n", pc, num_instructions, *MemoryReadCode(pc)); } void CallSVC(std::uint32_t swi) override { fmt::print("> CallSVC({})\n", swi); diff --git a/tests/unicorn_emu/a32_unicorn.cpp b/tests/unicorn_emu/a32_unicorn.cpp index f3ffa0da..ed8be3d1 100644 --- a/tests/unicorn_emu/a32_unicorn.cpp +++ b/tests/unicorn_emu/a32_unicorn.cpp @@ -52,7 +52,7 @@ void A32Unicorn::Run() { return; } if (auto cerr_ = uc_emu_start(uc, pc, END_ADDRESS, 0, 1)) { - fmt::print("uc_emu_start failed @ {:08x} (code = {:08x}) with error {} ({})", pc, testenv.MemoryReadCode(pc), cerr_, uc_strerror(cerr_)); + fmt::print("uc_emu_start failed @ {:08x} (code = {:08x}) with error {} ({})", pc, *testenv.MemoryReadCode(pc), cerr_, uc_strerror(cerr_)); throw "A32Unicorn::Run() failure"; } testenv.ticks_left--;