diff options
Diffstat (limited to 'toolkit/xre/dllservices/mozglue/interceptor/Trampoline.h')
-rw-r--r-- | toolkit/xre/dllservices/mozglue/interceptor/Trampoline.h | 773 |
1 files changed, 773 insertions, 0 deletions
diff --git a/toolkit/xre/dllservices/mozglue/interceptor/Trampoline.h b/toolkit/xre/dllservices/mozglue/interceptor/Trampoline.h new file mode 100644 index 0000000000..befbd47215 --- /dev/null +++ b/toolkit/xre/dllservices/mozglue/interceptor/Trampoline.h @@ -0,0 +1,773 @@ +/* -*- 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_Trampoline_h +#define mozilla_interceptor_Trampoline_h + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/Maybe.h" +#include "mozilla/Types.h" +#include "mozilla/WindowsProcessMitigations.h" +#include "mozilla/WindowsUnwindInfo.h" + +namespace mozilla { +namespace interceptor { + +template <typename MMPolicy> +class MOZ_STACK_CLASS Trampoline final { + public: + Trampoline(const MMPolicy* aMMPolicy, uint8_t* const aLocalBase, + const uintptr_t aRemoteBase, const uint32_t aChunkSize) + : mMMPolicy(aMMPolicy), + mPrevLocalProt(0), + mLocalBase(aLocalBase), + mRemoteBase(aRemoteBase), + mOffset(0), + mExeOffset(0), +#ifdef _M_X64 + mCopyCodesEndOffset(0), + mExeEndOffset(0), +#endif // _M_X64 + mMaxOffset(aChunkSize), + mAccumulatedStatus(true) { + if (!::VirtualProtect(aLocalBase, aChunkSize, + MMPolicy::GetTrampWriteProtFlags(), + &mPrevLocalProt)) { + mPrevLocalProt = 0; + } + } + + Trampoline(Trampoline&& aOther) + : mMMPolicy(aOther.mMMPolicy), + mPrevLocalProt(aOther.mPrevLocalProt), + mLocalBase(aOther.mLocalBase), + mRemoteBase(aOther.mRemoteBase), + mOffset(aOther.mOffset), + mExeOffset(aOther.mExeOffset), +#ifdef _M_X64 + mCopyCodesEndOffset(aOther.mCopyCodesEndOffset), + mExeEndOffset(aOther.mExeEndOffset), +#endif // _M_X64 + mMaxOffset(aOther.mMaxOffset), + mAccumulatedStatus(aOther.mAccumulatedStatus) { + aOther.mPrevLocalProt = 0; + aOther.mAccumulatedStatus = false; + } + + MOZ_IMPLICIT Trampoline(decltype(nullptr)) + : mMMPolicy(nullptr), + mPrevLocalProt(0), + mLocalBase(nullptr), + mRemoteBase(0), + mOffset(0), + mExeOffset(0), +#ifdef _M_X64 + mCopyCodesEndOffset(0), + mExeEndOffset(0), +#endif // _M_X64 + mMaxOffset(0), + mAccumulatedStatus(false) { + } + + Trampoline(const Trampoline&) = delete; + Trampoline& operator=(const Trampoline&) = delete; + + Trampoline& operator=(Trampoline&& aOther) { + Clear(); + + mMMPolicy = aOther.mMMPolicy; + mPrevLocalProt = aOther.mPrevLocalProt; + mLocalBase = aOther.mLocalBase; + mRemoteBase = aOther.mRemoteBase; + mOffset = aOther.mOffset; + mExeOffset = aOther.mExeOffset; +#ifdef _M_X64 + mCopyCodesEndOffset = aOther.mCopyCodesEndOffset; + mExeEndOffset = aOther.mExeEndOffset; +#endif // _M_X64 + mMaxOffset = aOther.mMaxOffset; + mAccumulatedStatus = aOther.mAccumulatedStatus; + + aOther.mPrevLocalProt = 0; + aOther.mAccumulatedStatus = false; + + return *this; + } + + ~Trampoline() { Clear(); } + + explicit operator bool() const { + return IsNull() || + (mLocalBase && mRemoteBase && mPrevLocalProt && mAccumulatedStatus); + } + + bool IsNull() const { return !mMMPolicy; } + +#if defined(_M_ARM64) + + void WriteInstruction(uint32_t aInstruction) { + const uint32_t kDelta = sizeof(uint32_t); + + if (!mMMPolicy) { + // Null tramp, just track offset + mOffset += kDelta; + return; + } + + if (mOffset + kDelta > mMaxOffset) { + mAccumulatedStatus = false; + return; + } + + *reinterpret_cast<uint32_t*>(mLocalBase + mOffset) = aInstruction; + mOffset += kDelta; + } + + void WriteLoadLiteral(const uintptr_t aAddress, const uint8_t aReg) { + const uint32_t kDelta = sizeof(uint32_t) + sizeof(uintptr_t); + + if (!mMMPolicy) { + // Null tramp, just track offset + mOffset += kDelta; + return; + } + + // We grow the literal pool from the *end* of the tramp, + // so we need to ensure that there is enough room for both an instruction + // and a pointer + if (mOffset + kDelta > mMaxOffset) { + mAccumulatedStatus = false; + return; + } + + mMaxOffset -= sizeof(uintptr_t); + *reinterpret_cast<uintptr_t*>(mLocalBase + mMaxOffset) = aAddress; + + CheckedInt<intptr_t> pc(GetCurrentRemoteAddress()); + if (!pc.isValid()) { + mAccumulatedStatus = false; + return; + } + + CheckedInt<intptr_t> literal(reinterpret_cast<uintptr_t>(mLocalBase) + + mMaxOffset); + if (!literal.isValid()) { + mAccumulatedStatus = false; + return; + } + + CheckedInt<intptr_t> ptrOffset = (literal - pc); + if (!ptrOffset.isValid()) { + mAccumulatedStatus = false; + return; + } + + // ptrOffset must be properly aligned + MOZ_ASSERT((ptrOffset.value() % 4) == 0); + ptrOffset /= 4; + + CheckedInt<int32_t> offset(ptrOffset.value()); + if (!offset.isValid()) { + mAccumulatedStatus = false; + return; + } + + // Ensure that offset falls within the range of a signed 19-bit value + if (offset.value() < -0x40000 || offset.value() > 0x3FFFF) { + mAccumulatedStatus = false; + return; + } + + const int32_t kimm19Mask = 0x7FFFF; + int32_t masked = offset.value() & kimm19Mask; + + MOZ_ASSERT(aReg < 32); + uint32_t loadInstr = 0x58000000 | (masked << 5) | aReg; + WriteInstruction(loadInstr); + } + +#else + + void WriteByte(uint8_t aValue) { + const uint32_t kDelta = sizeof(uint8_t); + + if (!mMMPolicy) { + // Null tramp, just track offset + mOffset += kDelta; + return; + } + + if (mOffset >= mMaxOffset) { + mAccumulatedStatus = false; + return; + } + + *(mLocalBase + mOffset) = aValue; + ++mOffset; + } + + void WriteInteger(int32_t aValue) { + const uint32_t kDelta = sizeof(int32_t); + + if (!mMMPolicy) { + // Null tramp, just track offset + mOffset += kDelta; + return; + } + + if (mOffset + kDelta > mMaxOffset) { + mAccumulatedStatus = false; + return; + } + + *reinterpret_cast<int32_t*>(mLocalBase + mOffset) = aValue; + mOffset += kDelta; + } + + void WriteDisp32(uintptr_t aAbsTarget) { + const uint32_t kDelta = sizeof(int32_t); + + if (!mMMPolicy) { + // Null tramp, just track offset + mOffset += kDelta; + return; + } + + if (mOffset + kDelta > mMaxOffset) { + mAccumulatedStatus = false; + return; + } + + // This needs to be computed from the remote location + intptr_t remoteTrampPosition = static_cast<intptr_t>(mRemoteBase + mOffset); + + intptr_t diff = + static_cast<intptr_t>(aAbsTarget) - (remoteTrampPosition + kDelta); + + CheckedInt<int32_t> checkedDisp(diff); + MOZ_ASSERT(checkedDisp.isValid()); + if (!checkedDisp.isValid()) { + mAccumulatedStatus = false; + return; + } + + int32_t disp = checkedDisp.value(); + *reinterpret_cast<int32_t*>(mLocalBase + mOffset) = disp; + mOffset += kDelta; + } + + void WriteBytes(void* aAddr, size_t aSize) { + if (!mMMPolicy) { + // Null tramp, just track offset + mOffset += aSize; + return; + } + + if (mOffset + aSize > mMaxOffset) { + mAccumulatedStatus = false; + return; + } + + std::memcpy(reinterpret_cast<void*>(mLocalBase + mOffset), aAddr, aSize); + mOffset += aSize; + } + +#endif + + void WritePointer(uintptr_t aValue) { + const uint32_t kDelta = sizeof(uintptr_t); + + if (!mMMPolicy) { + // Null tramp, just track offset + mOffset += kDelta; + return; + } + + if (mOffset + kDelta > mMaxOffset) { + mAccumulatedStatus = false; + return; + } + + *reinterpret_cast<uintptr_t*>(mLocalBase + mOffset) = aValue; + mOffset += kDelta; + } + + void WriteEncodedPointer(void* aValue) { + uintptr_t encoded = ReadOnlyTargetFunction<MMPolicy>::EncodePtr(aValue); + WritePointer(encoded); + } + + Maybe<uintptr_t> ReadPointer() { + if (mOffset + sizeof(uintptr_t) > mMaxOffset) { + mAccumulatedStatus = false; + return Nothing(); + } + + auto result = Some(*reinterpret_cast<uintptr_t*>(mLocalBase + mOffset)); + mOffset += sizeof(uintptr_t); + return std::move(result); + } + + Maybe<uintptr_t> ReadEncodedPointer() { + Maybe<uintptr_t> encoded(ReadPointer()); + if (!encoded) { + return encoded; + } + + return Some(ReadOnlyTargetFunction<MMPolicy>::DecodePtr(encoded.value())); + } + +#if defined(_M_IX86) + // 32-bit only + void AdjustDisp32AtOffset(uint32_t aOffset, uintptr_t aAbsTarget) { + uint32_t effectiveOffset = mExeOffset + aOffset; + + if (effectiveOffset + sizeof(int32_t) > mMaxOffset) { + mAccumulatedStatus = false; + return; + } + + intptr_t diff = static_cast<intptr_t>(aAbsTarget) - + static_cast<intptr_t>(mRemoteBase + mExeOffset); + *reinterpret_cast<int32_t*>(mLocalBase + effectiveOffset) += diff; + } +#endif // defined(_M_IX86) + + void CopyFrom(uintptr_t aOrigBytes, uint32_t aNumBytes) { + if (!mMMPolicy) { + // Null tramp, just track offset + mOffset += aNumBytes; + return; + } + + if (!mMMPolicy || mOffset + aNumBytes > mMaxOffset) { + mAccumulatedStatus = false; + return; + } + + if (!mMMPolicy->Read(mLocalBase + mOffset, + reinterpret_cast<void*>(aOrigBytes), aNumBytes)) { + mAccumulatedStatus = false; + return; + } + + mOffset += aNumBytes; + } + + void CopyCodes(uintptr_t aOrigBytes, uint32_t aNumBytes) { +#ifdef _M_X64 + if (mOffset == mCopyCodesEndOffset) { + mCopyCodesEndOffset += aNumBytes; + } +#endif // _M_X64 + CopyFrom(aOrigBytes, aNumBytes); + } + + void Rewind() { mOffset = 0; } + + uintptr_t GetCurrentRemoteAddress() const { return mRemoteBase + mOffset; } + + void StartExecutableCode() { + MOZ_ASSERT(!mExeOffset); + mExeOffset = mOffset; +#ifdef _M_X64 + mCopyCodesEndOffset = mOffset; +#endif // _M_X64 + } + + void* EndExecutableCode() { + if (!mAccumulatedStatus || !mMMPolicy) { + return nullptr; + } + +#ifdef _M_X64 + mExeEndOffset = mOffset; +#endif // _M_X64 + + // This must always return the start address the executable code + // *in the target process* + return reinterpret_cast<void*>(mRemoteBase + mExeOffset); + } + + uint32_t GetCurrentExecutableCodeLen() const { return mOffset - mExeOffset; } + +#ifdef _M_X64 + + void Align(uint32_t aAlignment) { + // aAlignment should be a power of 2 + MOZ_ASSERT(!(aAlignment & (aAlignment - 1))); + + uint32_t alignedOffset = (mOffset + aAlignment - 1) & ~(aAlignment - 1); + if (alignedOffset > mMaxOffset) { + mAccumulatedStatus = false; + return; + } + mOffset = alignedOffset; + } + + // We assume that all instructions that are part of the prologue are left + // intact by detouring code, i.e. that they are copied using CopyCodes. This + // is not true for calls and jumps for example, but calls and jumps cannot be + // part of the prologue. This assumption allows us to copy unwind information + // as-is, because unwind information only refers to instructions within the + // prologue. + bool AddUnwindInfo(uintptr_t aOrigFuncAddr, uintptr_t aOrigFuncStopOffset) { + if constexpr (!MMPolicy::kSupportsUnwindInfo) { + return false; + } + + if (!mMMPolicy) { + return false; + } + + uint32_t origFuncOffsetFromBeginAddr = 0; + uint32_t origFuncOffsetToEndAddr = 0; + uintptr_t origImageBase = 0; + auto unwindInfoData = + mMMPolicy->LookupUnwindInfo(aOrigFuncAddr, &origFuncOffsetFromBeginAddr, + &origFuncOffsetToEndAddr, &origImageBase); + if (!unwindInfoData) { + // If the original function does not have unwind info, there is nothing + // more to do. + return true; + } + + // We do not support hooking at a location that isn't the beginning of a + // function. + MOZ_ASSERT(origFuncOffsetFromBeginAddr == 0); + if (origFuncOffsetFromBeginAddr != 0) { + return false; + } + + IterableUnwindInfo unwindInfoIt(unwindInfoData.get()); + auto& unwindInfo = unwindInfoIt.Info(); + + // The prologue should contain only instructions that we detour using + // CopyCodes. If not, there is most likely a mismatch between the unwind + // information and the actual code we are detouring, so we stop here. This + // is a best-effort safeguard intended to detect situations where e.g. + // third-party injected code would have altered the function we are + // detouring. + if (mCopyCodesEndOffset < aOrigFuncStopOffset && + unwindInfo.size_of_prolog > mCopyCodesEndOffset) { + return false; + } + + // According to the documentation, the array is sorted by descending order + // of offset in the prologue. Let's double check this assumption if in + // debug. This also checks that the full unwind information isn't + // ill-formed, thanks to all the MOZ_ASSERT in iteration code. +# ifdef DEBUG + uint8_t previousOffset = 0xFF; + for (const auto& unwindCode : unwindInfoIt) { + MOZ_ASSERT(unwindCode.offset_in_prolog <= previousOffset); + previousOffset = unwindCode.offset_in_prolog; + } +# endif // DEBUG + + // We skip entries that are not part of the code we have detoured. + // This code relies on the array being sorted by descending order of offset + // in the prolog. + uint8_t firstRelevantCode = 0; + uint8_t countOfCodes = 0; + auto it = unwindInfoIt.begin(); + for (; it != unwindInfoIt.end(); ++it) { + const auto& unwindCode = *it; + if (unwindCode.offset_in_prolog <= aOrigFuncStopOffset) { + // Found a relevant entry + firstRelevantCode = it.Index(); + countOfCodes = unwindInfo.count_of_codes - firstRelevantCode; + break; + } + } + + // Check that we encountered no ill-formed unwind codes. + if (!it.IsValid() && !it.IsAtEnd()) { + return false; + } + + // We do not support chained unwind info. We should add support for chained + // unwind info if we ever reach this assert. Since we hook functions at + // their start address, this should not happen. + if (unwindInfo.flags & UNW_FLAG_CHAININFO) { + MOZ_ASSERT( + false, + "Tried to detour at a location with chained unwind information"); + return false; + } + + // We do not support exception handler info either. This could be a problem + // if we detour code that does not belong to the prologue and contains a + // call instruction, as this handler would then not be found if unwinding + // from callees. The following assert checks that this does not happen. + // + // Our current assumption is that all the functions we hook either have no + // associated exception handlers, or it is __GSHandlerCheck. This handler + // is the most commonly found, for example it is present in LdrLoadDll, + // SendMessageTimeoutW, GetWindowInfo. It is added to functions that use + // stack buffers, in order to mitigate stack buffer overflows. We explain + // below why it is not a problem that we do not preserve __GSHandlerCheck + // information when we detour code. + // + // Preserving exception handler information would raise two challenges: + // + // (1) if the exception handler was not written in a generic way, it may + // behave differently when called for our detoured code compared to + // what it would do if called from the original location of the code; + // (2) the exception handler can be followed by handler-specific data, + // which we cannot copy because we do not know its size. + // + // __GSHandlerCheck checks that the stack cookie value wasn't overwritten + // before continuing to unwind and call further handlers. That is a + // security feature that we want to preserve. However, since these + // functions allocate stack space and write the stack cookie as part of + // their prologue, the 13 bytes that we detour are necessarily part of + // their prologue, which must contain at least the following instructions: + // + // 48 81 ec XX XX XX XX sub rsp, 0xXXXXXXXX + // 48 8b 05 XX XX XX XX mov rax, qword ptr [rip+__security_cookie] + // 48 33 c4 xor rax, rsp + // 48 89 84 24 XX XX XX XX mov qword ptr [RSP + 0xXXXXXXXX],RAX + // + // As a consequence, code associated with __GSHandlerCheck will necessarily + // satisfy (aOrigFuncStopOffset <= unwindInfo.size_of_prolog), and it is OK + // to not preserve handler info in that case. +# ifdef DEBUG + if (aOrigFuncStopOffset > unwindInfo.size_of_prolog) { + MOZ_ASSERT(!(unwindInfo.flags & (UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER))); + } +# endif // DEBUG + + // The unwind info must be DWORD-aligned + Align(sizeof(uint32_t)); + if (!mAccumulatedStatus) { + return false; + } + uintptr_t unwindInfoOffset = mOffset; + + unwindInfo.flags &= + ~(UNW_FLAG_CHAININFO | UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER); + unwindInfo.count_of_codes = countOfCodes; + if (aOrigFuncStopOffset < unwindInfo.size_of_prolog) { + unwindInfo.size_of_prolog = aOrigFuncStopOffset; + } + + WriteBytes(reinterpret_cast<void*>(&unwindInfo), + offsetof(UnwindInfo, unwind_code)); + if (!mAccumulatedStatus) { + return false; + } + + WriteBytes( + reinterpret_cast<void*>(&unwindInfo.unwind_code[firstRelevantCode]), + countOfCodes * sizeof(UnwindCode)); + if (!mAccumulatedStatus) { + return false; + } + + // The function table must be DWORD-aligned + Align(sizeof(uint32_t)); + if (!mAccumulatedStatus) { + return false; + } + uintptr_t functionTableOffset = mOffset; + + WriteInteger(mExeOffset); + if (!mAccumulatedStatus) { + return false; + } + + WriteInteger(mExeEndOffset); + if (!mAccumulatedStatus) { + return false; + } + + WriteInteger(unwindInfoOffset); + if (!mAccumulatedStatus) { + return false; + } + + return mMMPolicy->AddFunctionTable(mRemoteBase + functionTableOffset, 1, + mRemoteBase); + } + +#endif // _M_X64 + + Trampoline<MMPolicy>& operator--() { + MOZ_ASSERT(mOffset); + --mOffset; + return *this; + } + + private: + void Clear() { + if (!mLocalBase || !mPrevLocalProt) { + return; + } + + DebugOnly<bool> ok = !!::VirtualProtect(mLocalBase, mMaxOffset, + mPrevLocalProt, &mPrevLocalProt); + MOZ_ASSERT(ok); + + mLocalBase = nullptr; + mRemoteBase = 0; + mPrevLocalProt = 0; + mAccumulatedStatus = false; + } + + private: + const MMPolicy* mMMPolicy; + DWORD mPrevLocalProt; + uint8_t* mLocalBase; + uintptr_t mRemoteBase; + uint32_t mOffset; + uint32_t mExeOffset; +#ifdef _M_X64 + uint32_t mCopyCodesEndOffset; + uint32_t mExeEndOffset; +#endif // _M_X64 + uint32_t mMaxOffset; + bool mAccumulatedStatus; +}; + +template <typename MMPolicy> +class MOZ_STACK_CLASS TrampolineCollection final { + public: + class MOZ_STACK_CLASS TrampolineIterator final { + public: + Trampoline<MMPolicy> operator*() { + uint32_t offset = mCurTramp * mCollection.mTrampSize; + return Trampoline<MMPolicy>( + &mCollection.mMMPolicy, mCollection.mLocalBase + offset, + mCollection.mRemoteBase + offset, mCollection.mTrampSize); + } + + TrampolineIterator& operator++() { + ++mCurTramp; + return *this; + } + + bool operator!=(const TrampolineIterator& aOther) const { + return mCurTramp != aOther.mCurTramp; + } + + private: + explicit TrampolineIterator( + const TrampolineCollection<MMPolicy>& aCollection, + const uint32_t aCurTramp = 0) + : mCollection(aCollection), mCurTramp(aCurTramp) {} + + const TrampolineCollection<MMPolicy>& mCollection; + uint32_t mCurTramp; + + friend class TrampolineCollection<MMPolicy>; + }; + + explicit TrampolineCollection(const MMPolicy& aMMPolicy) + : mMMPolicy(aMMPolicy), + mLocalBase(0), + mRemoteBase(0), + mTrampSize(0), + mNumTramps(0), + mPrevProt(0), + mCS(nullptr) {} + + TrampolineCollection(const MMPolicy& aMMPolicy, uint8_t* const aLocalBase, + const uintptr_t aRemoteBase, const uint32_t aTrampSize, + const uint32_t aNumTramps) + : mMMPolicy(aMMPolicy), + mLocalBase(aLocalBase), + mRemoteBase(aRemoteBase), + mTrampSize(aTrampSize), + mNumTramps(aNumTramps), + mPrevProt(0), + mCS(nullptr) { + if (!aNumTramps) { + return; + } + + BOOL ok = mMMPolicy.Protect(aLocalBase, aNumTramps * aTrampSize, + PAGE_EXECUTE_READWRITE, &mPrevProt); + if (!ok) { + // When destroying a sandboxed process that uses + // MITIGATION_DYNAMIC_CODE_DISABLE, we won't be allowed to write to our + // executable memory so we just do nothing. If we fail to get access + // to memory for any other reason, we still don't want to crash but we + // do assert. + MOZ_ASSERT(IsDynamicCodeDisabled()); + mNumTramps = 0; + mPrevProt = 0; + } + } + + ~TrampolineCollection() { + if (!mPrevProt) { + return; + } + + mMMPolicy.Protect(mLocalBase, mNumTramps * mTrampSize, mPrevProt, + &mPrevProt); + + if (mCS) { + ::LeaveCriticalSection(mCS); + } + } + + void Lock(CRITICAL_SECTION& aCS) { + if (!mPrevProt || mCS) { + return; + } + + mCS = &aCS; + ::EnterCriticalSection(&aCS); + } + + TrampolineIterator begin() const { + if (!mPrevProt) { + return end(); + } + + return TrampolineIterator(*this); + } + + TrampolineIterator end() const { + return TrampolineIterator(*this, mNumTramps); + } + + TrampolineCollection(const TrampolineCollection&) = delete; + TrampolineCollection& operator=(const TrampolineCollection&) = delete; + TrampolineCollection& operator=(TrampolineCollection&&) = delete; + + TrampolineCollection(TrampolineCollection&& aOther) + : mMMPolicy(aOther.mMMPolicy), + mLocalBase(aOther.mLocalBase), + mRemoteBase(aOther.mRemoteBase), + mTrampSize(aOther.mTrampSize), + mNumTramps(aOther.mNumTramps), + mPrevProt(aOther.mPrevProt), + mCS(aOther.mCS) { + aOther.mPrevProt = 0; + aOther.mCS = nullptr; + } + + private: + const MMPolicy& mMMPolicy; + uint8_t* const mLocalBase; + const uintptr_t mRemoteBase; + const uint32_t mTrampSize; + uint32_t mNumTramps; + uint32_t mPrevProt; + CRITICAL_SECTION* mCS; + + friend class TrampolineIterator; +}; + +} // namespace interceptor +} // namespace mozilla + +#endif // mozilla_interceptor_Trampoline_h |