/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #ifndef mozilla_interceptor_Arm64_h #define mozilla_interceptor_Arm64_h #include #include "mozilla/Assertions.h" #include "mozilla/CheckedInt.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/Maybe.h" #include "mozilla/Result.h" #include "mozilla/Saturate.h" #include "mozilla/Types.h" namespace mozilla { namespace interceptor { namespace arm64 { // clang-format off enum class IntegerConditionCode : uint8_t { // From the ARMv8 Architectural Reference Manual, Section C1.2.4 // Description Condition Flags EQ = 0b0000, // == Z == 1 NE = 0b0001, // != Z == 0 CS = 0b0010, // carry set C == 1 HS = 0b0010, // carry set (alias) C == 1 CC = 0b0011, // carry clear C == 0 LO = 0b0011, // carry clear (alias) C == 0 MI = 0b0100, // < 0 N == 1 PL = 0b0101, // >= 0 N == 0 VS = 0b0110, // overflow V == 1 VC = 0b0111, // no overflow V == 0 HI = 0b1000, // unsigned > C == 1 && Z == 0 LS = 0b1001, // unsigned <= !(C == 1 && Z == 0) GE = 0b1010, // signed >= N == V LT = 0b1011, // signed < N != V GT = 0b1100, // signed > Z == 0 && N == V LE = 0b1101, // signed <= !(Z == 0 && N == V) AL = 0b1110, // unconditional NV = 0b1111 // unconditional (but AL is the preferred encoding) }; // clang-format on struct LoadOrBranch { enum class Type { Load, Branch, }; // Load constructor LoadOrBranch(const uintptr_t aAbsAddress, const uint8_t aDestReg) : mType(Type::Load), mAbsAddress(aAbsAddress), mDestReg(aDestReg) { MOZ_ASSERT(aDestReg < 32); } // Unconditional branch constructor explicit LoadOrBranch(const uintptr_t aAbsAddress) : mType(Type::Branch), mAbsAddress(aAbsAddress), mCond(IntegerConditionCode::AL) {} // Conditional branch constructor LoadOrBranch(const uintptr_t aAbsAddress, const IntegerConditionCode aCond) : mType(Type::Branch), mAbsAddress(aAbsAddress), mCond(aCond) {} Type mType; // The absolute address to be loaded into a register, or branched to uintptr_t mAbsAddress; union { // The destination register for the load uint8_t mDestReg; // The condition code for the branch IntegerConditionCode mCond; }; }; enum class PCRelCheckError { InstructionNotPCRel, NoDecoderAvailable, }; MFBT_API Result CheckForPCRel( const uintptr_t aPC, const uint32_t aInst); /** * Casts |aValue| to a |ResultT| via sign extension. * * This function should be used when extracting signed immediate values from * an instruction. * * @param aValue The value to be sign extended. This value should already be * isolated from the remainder of the instruction's bits and * shifted all the way to the right. * @param aNumValidBits The number of bits in |aValue| that contain the * immediate signed value, including the sign bit. */ template inline ResultT SignExtend(const uint32_t aValue, const uint8_t aNumValidBits) { static_assert(std::is_integral_v && std::is_signed_v, "ResultT must be a signed integral type"); MOZ_ASSERT(aNumValidBits < 32U && aNumValidBits > 1); using UnsignedResultT = std::decay_t>; const uint8_t kResultWidthBits = sizeof(ResultT) * 8; // Shift left unsigned const uint8_t shiftAmt = kResultWidthBits - aNumValidBits; UnsignedResultT shiftedLeft = static_cast(aValue) << shiftAmt; // Now shift right signed auto result = static_cast(shiftedLeft); result >>= shiftAmt; return result; } inline static uint32_t BuildUnconditionalBranchToRegister(const uint32_t aReg) { MOZ_ASSERT(aReg < 32); // BR aReg return 0xD61F0000 | (aReg << 5); } MFBT_API LoadOrBranch BUncondImmDecode(const uintptr_t aPC, const uint32_t aInst); /** * If |aTarget| is more than 128MB away from |aPC|, we need to use a veneer. */ inline static bool IsVeneerRequired(const uintptr_t aPC, const uintptr_t aTarget) { detail::Saturate saturated(aTarget); saturated -= aPC; uintptr_t absDiff = Abs(saturated.value()); return absDiff >= 0x08000000U; } inline static bool IsUnconditionalBranchImm(const uint32_t aInst) { return (aInst & 0xFC000000U) == 0x14000000U; } inline static Maybe BuildUnconditionalBranchImm( const uintptr_t aPC, const uintptr_t aTarget) { detail::Saturate saturated(aTarget); saturated -= aPC; CheckedInt offset(saturated.value()); if (!offset.isValid()) { return Nothing(); } // offset should be a multiple of 4 MOZ_ASSERT(offset.value() % 4 == 0); if (offset.value() % 4) { return Nothing(); } offset /= 4; if (!offset.isValid()) { return Nothing(); } int32_t signbits = offset.value() & 0xFE000000; // Ensure that offset is small enough to fit into the 26 bit region. // We check that the sign bits are either all ones or all zeros. MOZ_ASSERT(signbits == 0xFE000000 || !signbits); if (signbits && signbits != 0xFE000000) { return Nothing(); } int32_t masked = offset.value() & 0x03FFFFFF; // B imm26 return Some(0x14000000U | masked); } /** * Allocate and construct a veneer that provides an absolute 64-bit branch to * the hook function. */ template inline static uintptr_t MakeVeneer(TrampPoolT& aTrampPool, void* aPrimaryTramp, const uintptr_t aDestAddress) { auto maybeVeneer = aTrampPool.GetNextTrampoline(); if (!maybeVeneer) { return 0; } Trampoline veneer( std::move(maybeVeneer.ref())); // Write the same header information that is used for trampolines veneer.WriteEncodedPointer(nullptr); veneer.WriteEncodedPointer(aPrimaryTramp); veneer.StartExecutableCode(); // Register 16 is explicitly intended for veneers in ARM64, so we use that // register without fear of clobbering anything important. veneer.WriteLoadLiteral(aDestAddress, 16); veneer.WriteInstruction(BuildUnconditionalBranchToRegister(16)); return reinterpret_cast(veneer.EndExecutableCode()); } } // namespace arm64 } // namespace interceptor } // namespace mozilla #endif // mozilla_interceptor_Arm64_h