--- a/dom/plugins/ipc/FunctionHook.cpp
+++ b/dom/plugins/ipc/FunctionHook.cpp
@@ -53,29 +53,33 @@ FunctionHook::HookFunctions(int aQuirks)
}
}
#if defined(XP_WIN)
// This cache is created when a DLL is registered with a FunctionHook.
// It is cleared on a call to ClearDllInterceptorCache(). It
// must be freed before exit to avoid leaks.
-typedef nsClassHashtable<nsCStringHashKey, WindowsDllInterceptor> DllInterceptors;
+typedef nsClassHashtable<nsStringHashKey, WindowsDllInterceptor> DllInterceptors;
DllInterceptors* sDllInterceptorCache = nullptr;
WindowsDllInterceptor*
FunctionHook::GetDllInterceptorFor(const char* aModuleName)
{
if (!sDllInterceptorCache) {
sDllInterceptorCache = new DllInterceptors();
}
+ MOZ_ASSERT(NS_IsAscii(aModuleName), "Non-ASCII module names are not supported");
+ NS_ConvertASCIItoUTF16 moduleName(aModuleName);
+
WindowsDllInterceptor* ret =
- sDllInterceptorCache->LookupOrAdd(nsCString(aModuleName), aModuleName);
+ sDllInterceptorCache->LookupOrAdd(moduleName);
MOZ_ASSERT(ret);
+ ret->Init(moduleName.get());
return ret;
}
void
FunctionHook::ClearDllInterceptorCache()
{
delete sDllInterceptorCache;
sDllInterceptorCache = nullptr;
new file mode 100644
--- /dev/null
+++ b/mozglue/misc/interceptor/MMPolicies.h
@@ -0,0 +1,224 @@
+/* -*- 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_MMPolicies_h
+#define mozilla_interceptor_MMPolicies_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Types.h"
+
+#include <windows.h>
+
+namespace mozilla {
+namespace interceptor {
+
+class MMPolicyBase
+{
+public:
+ static DWORD ComputeAllocationSize(const uint32_t aRequestedSize)
+ {
+ MOZ_ASSERT(aRequestedSize);
+ DWORD result = aRequestedSize;
+
+ const uint32_t granularity = GetAllocGranularity();
+
+ uint32_t mod = aRequestedSize % granularity;
+ if (mod) {
+ result += (granularity - mod);
+ }
+
+ return result;
+ }
+
+ static DWORD GetAllocGranularity()
+ {
+ static const DWORD kAllocGranularity = []() -> DWORD {
+ SYSTEM_INFO sysInfo;
+ ::GetSystemInfo(&sysInfo);
+ return sysInfo.dwAllocationGranularity;
+ }();
+
+ return kAllocGranularity;
+ }
+
+ static DWORD GetPageSize()
+ {
+ static const DWORD kPageSize = []() -> DWORD {
+ SYSTEM_INFO sysInfo;
+ ::GetSystemInfo(&sysInfo);
+ return sysInfo.dwPageSize;
+ }();
+
+ return kPageSize;
+ }
+};
+
+class MMPolicyInProcess : public MMPolicyBase
+{
+public:
+ typedef MMPolicyInProcess MMPolicyT;
+
+ explicit MMPolicyInProcess()
+ : mBase(nullptr)
+ , mReservationSize(0)
+ , mCommitOffset(0)
+ {
+ }
+
+ MMPolicyInProcess(const MMPolicyInProcess&) = delete;
+ MMPolicyInProcess& operator=(const MMPolicyInProcess&) = delete;
+
+ MMPolicyInProcess(MMPolicyInProcess&& aOther)
+ : mBase(nullptr)
+ , mReservationSize(0)
+ , mCommitOffset(0)
+ {
+ *this = Move(aOther);
+ }
+
+ MMPolicyInProcess& operator=(MMPolicyInProcess&& aOther)
+ {
+ mBase = aOther.mBase;
+ aOther.mBase = nullptr;
+
+ mCommitOffset = aOther.mCommitOffset;
+ aOther.mCommitOffset = 0;
+
+ mReservationSize = aOther.mReservationSize;
+ aOther.mReservationSize = 0;
+
+ return *this;
+ }
+
+ // We always leak mBase
+ ~MMPolicyInProcess() = default;
+
+ explicit operator bool() const
+ {
+ return !!mBase;
+ }
+
+ /**
+ * Should we unhook everything upon destruction?
+ */
+ bool ShouldUnhookUponDestruction() const
+ {
+ return true;
+ }
+
+ bool Read(void* aToPtr, const void* aFromPtr, size_t aLen) const
+ {
+ ::memcpy(aToPtr, aFromPtr, aLen);
+ return true;
+ }
+
+ bool Write(void* aToPtr, const void* aFromPtr, size_t aLen) const
+ {
+ ::memcpy(aToPtr, aFromPtr, aLen);
+ return true;
+ }
+
+ bool Protect(void* aVAddress, size_t aSize, uint32_t aProtFlags,
+ uint32_t* aPrevProtFlags) const
+ {
+ MOZ_ASSERT(aPrevProtFlags);
+ BOOL ok = ::VirtualProtect(aVAddress, aSize, aProtFlags,
+ reinterpret_cast<PDWORD>(aPrevProtFlags));
+ MOZ_ASSERT(ok);
+ return !!ok;
+ }
+
+ /**
+ * @return true if the page that hosts aVAddress is accessible.
+ */
+ bool IsPageAccessible(void* aVAddress) const
+ {
+ MEMORY_BASIC_INFORMATION mbi;
+ SIZE_T result = ::VirtualQuery(aVAddress, &mbi, sizeof(mbi));
+
+ return result && mbi.AllocationProtect && mbi.Protect != PAGE_NOACCESS &&
+ mbi.State == MEM_COMMIT;
+ }
+
+ bool FlushInstructionCache() const
+ {
+ return !!::FlushInstructionCache(::GetCurrentProcess(), nullptr, 0);
+ }
+
+protected:
+ uint8_t* GetLocalView() const
+ {
+ return mBase;
+ }
+
+ uintptr_t GetRemoteView() const
+ {
+ // Same as local view for in-process
+ return reinterpret_cast<uintptr_t>(mBase);
+ }
+
+ /**
+ * @return the effective number of bytes reserved, or 0 on failure
+ */
+ uint32_t Reserve(const uint32_t aSize)
+ {
+ if (!aSize) {
+ return 0;
+ }
+
+ if (mBase) {
+ MOZ_ASSERT(mReservationSize >= aSize);
+ return mReservationSize;
+ }
+
+ mReservationSize = ComputeAllocationSize(aSize);
+ mBase = static_cast<uint8_t*>(::VirtualAlloc(nullptr, mReservationSize,
+ MEM_RESERVE, PAGE_NOACCESS));
+ if (!mBase) {
+ return 0;
+ }
+ return mReservationSize;
+ }
+
+ bool MaybeCommitNextPage(const uint32_t aRequestedOffset,
+ const uint32_t aRequestedLength)
+ {
+ if (!(*this)) {
+ return false;
+ }
+
+ uint32_t limit = aRequestedOffset + aRequestedLength - 1;
+ if (limit < mCommitOffset) {
+ // No commit required
+ return true;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mCommitOffset < mReservationSize);
+ if (mCommitOffset >= mReservationSize) {
+ return false;
+ }
+
+ PVOID local = ::VirtualAlloc(mBase + mCommitOffset, GetPageSize(),
+ MEM_COMMIT, PAGE_EXECUTE_READ);
+ if (!local) {
+ return false;
+ }
+
+ mCommitOffset += GetPageSize();
+ return true;
+ }
+
+private:
+ uint8_t* mBase;
+ uint32_t mReservationSize;
+ uint32_t mCommitOffset;
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_MMPolicies_h
+
rename from xpcom/build/nsWindowsDllInterceptor.h
rename to mozglue/misc/interceptor/PatcherBase.h
--- a/xpcom/build/nsWindowsDllInterceptor.h
+++ b/mozglue/misc/interceptor/PatcherBase.h
@@ -1,1495 +1,84 @@
/* -*- 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 http://mozilla.org/MPL/2.0/. */
-
-#ifndef NS_WINDOWS_DLL_INTERCEPTOR_H_
-#define NS_WINDOWS_DLL_INTERCEPTOR_H_
-
-#include "mozilla/Assertions.h"
-#include "mozilla/ArrayUtils.h"
-#include "mozilla/UniquePtr.h"
-#include "nsWindowsHelpers.h"
-
-#include <wchar.h>
-#include <windows.h>
-#include <winternl.h>
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
-/*
- * Simple function interception.
- *
- * We have two separate mechanisms for intercepting a function: We can use the
- * built-in nop space, if it exists, or we can create a detour.
- *
- * Using the built-in nop space works as follows: On x86-32, DLL functions
- * begin with a two-byte nop (mov edi, edi) and are preceeded by five bytes of
- * NOP instructions.
- *
- * When we detect a function with this prelude, we do the following:
- *
- * 1. Write a long jump to our interceptor function into the five bytes of NOPs
- * before the function.
- *
- * 2. Write a short jump -5 into the two-byte nop at the beginning of the function.
- *
- * This mechanism is nice because it's thread-safe. It's even safe to do if
- * another thread is currently running the function we're modifying!
- *
- * When the WindowsDllNopSpacePatcher is destroyed, we overwrite the short jump
- * but not the long jump, so re-intercepting the same function won't work,
- * because its prelude won't match.
- *
- *
- * Unfortunately nop space patching doesn't work on functions which don't have
- * this magic prelude (and in particular, x86-64 never has the prelude). So
- * when we can't use the built-in nop space, we fall back to using a detour,
- * which works as follows:
- *
- * 1. Save first N bytes of OrigFunction to trampoline, where N is a
- * number of bytes >= 5 that are instruction aligned.
- *
- * 2. Replace first 5 bytes of OrigFunction with a jump to the Hook
- * function.
- *
- * 3. After N bytes of the trampoline, add a jump to OrigFunction+N to
- * continue original program flow.
- *
- * 4. Hook function needs to call the trampoline during its execution,
- * to invoke the original function (so address of trampoline is
- * returned).
- *
- * When the WindowsDllDetourPatcher object is destructed, OrigFunction is
- * patched again to jump directly to the trampoline instead of going through
- * the hook function. As such, re-intercepting the same function won't work, as
- * jump instructions are not supported.
- *
- * Note that this is not thread-safe. Sad day.
- *
- */
+#ifndef mozilla_interceptor_PatcherBase_h
+#define mozilla_interceptor_PatcherBase_h
-#include <stdint.h>
-
-#define COPY_CODES(NBYTES) do { \
- memcpy(&tramp[nTrampBytes], &origBytes[nOrigBytes], NBYTES); \
- nOrigBytes += NBYTES; \
- nTrampBytes += NBYTES; \
-} while (0)
+#include "mozilla/interceptor/TargetFunction.h"
namespace mozilla {
-namespace internal {
-
-class AutoVirtualProtect
-{
-public:
- AutoVirtualProtect(void* aFunc, size_t aSize, DWORD aProtect)
- : mFunc(aFunc), mSize(aSize), mNewProtect(aProtect), mOldProtect(0),
- mSuccess(false)
- {}
-
- ~AutoVirtualProtect()
- {
- if (mSuccess) {
- VirtualProtectEx(GetCurrentProcess(), mFunc, mSize, mOldProtect,
- &mOldProtect);
- }
- }
-
- bool Protect()
- {
- mSuccess = !!VirtualProtectEx(GetCurrentProcess(), mFunc, mSize,
- mNewProtect, &mOldProtect);
- if (!mSuccess) {
- // printf("VirtualProtectEx failed! %d\n", GetLastError());
- }
- return mSuccess;
- }
-
-private:
- void* const mFunc;
- size_t const mSize;
- DWORD const mNewProtect;
- DWORD mOldProtect;
- bool mSuccess;
-};
-
-class WindowsDllNopSpacePatcher
-{
- typedef uint8_t* byteptr_t;
- HMODULE mModule;
-
- // Dumb array for remembering the addresses of functions we've patched.
- // (This should be nsTArray, but non-XPCOM code uses this class.)
- static const size_t maxPatchedFns = 16;
- byteptr_t mPatchedFns[maxPatchedFns];
- size_t mPatchedFnsLen;
-
-public:
- WindowsDllNopSpacePatcher()
- : mModule(0)
- , mPatchedFnsLen(0)
- {}
-
-#if defined(_M_IX86)
- ~WindowsDllNopSpacePatcher()
- {
- // Restore the mov edi, edi to the beginning of each function we patched.
-
- for (size_t i = 0; i < mPatchedFnsLen; i++) {
- byteptr_t fn = mPatchedFns[i];
-
- // Ensure we can write to the code.
- AutoVirtualProtect protect(fn, 2, PAGE_EXECUTE_READWRITE);
- if (!protect.Protect()) {
- continue;
- }
-
- // mov edi, edi
- *((uint16_t*)fn) = 0xff8b;
-
- // I don't think this is actually necessary, but it can't hurt.
- FlushInstructionCache(GetCurrentProcess(),
- /* ignored */ nullptr,
- /* ignored */ 0);
- }
- }
-
- void Init(const char* aModuleName)
- {
- if (!IsCompatible()) {
-#if defined(MOZILLA_INTERNAL_API)
- NS_WARNING("NOP space patching is unavailable for compatibility reasons");
-#endif
- return;
- }
-
- mModule = LoadLibraryExA(aModuleName, nullptr, 0);
- if (!mModule) {
- //printf("LoadLibraryEx for '%s' failed\n", aModuleName);
- return;
- }
- }
+namespace interceptor {
- /**
- * NVIDIA Optimus drivers utilize Microsoft Detours 2.x to patch functions
- * in our address space. There is a bug in Detours 2.x that causes it to
- * patch at the wrong address when attempting to detour code that is already
- * NOP space patched. This function is an effort to detect the presence of
- * this NVIDIA code in our address space and disable NOP space patching if it
- * is. We also check AppInit_DLLs since this is the mechanism that the Optimus
- * drivers use to inject into our process.
- */
- static bool IsCompatible()
- {
- // These DLLs are known to have bad interactions with this style of patching
- const wchar_t* kIncompatibleDLLs[] = {
- L"detoured.dll",
- L"_etoured.dll",
- L"nvd3d9wrap.dll",
- L"nvdxgiwrap.dll"
- };
- // See if the infringing DLLs are already loaded
- for (unsigned int i = 0; i < mozilla::ArrayLength(kIncompatibleDLLs); ++i) {
- if (GetModuleHandleW(kIncompatibleDLLs[i])) {
- return false;
- }
- }
- if (GetModuleHandleW(L"user32.dll")) {
- // user32 is loaded but the infringing DLLs are not, assume we're safe to
- // proceed.
- return true;
- }
- // If user32 has not loaded yet, check AppInit_DLLs to ensure that Optimus
- // won't be loaded once user32 is initialized.
- HKEY hkey = NULL;
- if (!RegOpenKeyExW(HKEY_LOCAL_MACHINE,
- L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows",
- 0, KEY_QUERY_VALUE, &hkey)) {
- nsAutoRegKey key(hkey);
- DWORD numBytes = 0;
- const wchar_t kAppInitDLLs[] = L"AppInit_DLLs";
- // Query for required buffer size
- LONG status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr,
- nullptr, nullptr, &numBytes);
- mozilla::UniquePtr<wchar_t[]> data;
- if (!status) {
- // Allocate the buffer and query for the actual data
- data = mozilla::MakeUnique<wchar_t[]>(numBytes / sizeof(wchar_t));
- status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr,
- nullptr, (LPBYTE)data.get(), &numBytes);
- }
- if (!status) {
- // For each token, split up the filename components and then check the
- // name of the file.
- const wchar_t kDelimiters[] = L", ";
- wchar_t* tokenContext = nullptr;
- wchar_t* token = wcstok_s(data.get(), kDelimiters, &tokenContext);
- while (token) {
- wchar_t fname[_MAX_FNAME] = {0};
- if (!_wsplitpath_s(token, nullptr, 0, nullptr, 0,
- fname, mozilla::ArrayLength(fname),
- nullptr, 0)) {
- // nvinit.dll is responsible for bootstrapping the DLL injection, so
- // that is the library that we check for here
- const wchar_t kNvInitName[] = L"nvinit";
- if (!_wcsnicmp(fname, kNvInitName,
- mozilla::ArrayLength(kNvInitName))) {
- return false;
- }
- }
- token = wcstok_s(nullptr, kDelimiters, &tokenContext);
- }
- }
- }
- return true;
- }
-
- bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc)
- {
- if (!mModule) {
- return false;
- }
-
- if (!IsCompatible()) {
-#if defined(MOZILLA_INTERNAL_API)
- NS_WARNING("NOP space patching is unavailable for compatibility reasons");
-#endif
- return false;
- }
-
- MOZ_RELEASE_ASSERT(mPatchedFnsLen < maxPatchedFns, "No room for the hook");
-
- byteptr_t fn = reinterpret_cast<byteptr_t>(GetProcAddress(mModule, aName));
- if (!fn) {
- //printf ("GetProcAddress failed\n");
- return false;
- }
-
- fn = ResolveRedirectedAddress(fn);
-
- // Ensure we can read and write starting at fn - 5 (for the long jmp we're
- // going to write) and ending at fn + 2 (for the short jmp up to the long
- // jmp). These bytes may span two pages with different protection.
- AutoVirtualProtect protectBefore(fn - 5, 5, PAGE_EXECUTE_READWRITE);
- AutoVirtualProtect protectAfter(fn, 2, PAGE_EXECUTE_READWRITE);
- if (!protectBefore.Protect() || !protectAfter.Protect()) {
- return false;
- }
+template <typename VMPolicy>
+class WindowsDllPatcherBase
+{
+protected:
+ typedef typename VMPolicy::MMPolicyT MMPolicyT;
- bool rv = WriteHook(fn, aHookDest, aOrigFunc);
-
- if (rv) {
- mPatchedFns[mPatchedFnsLen] = fn;
- mPatchedFnsLen++;
- }
-
- return rv;
- }
-
- bool WriteHook(byteptr_t aFn, intptr_t aHookDest, void** aOrigFunc)
- {
- // Check that the 5 bytes before aFn are NOP's or INT 3's,
- // and that the 2 bytes after aFn are mov(edi, edi).
- //
- // It's safe to read aFn[-5] because we set it to PAGE_EXECUTE_READWRITE
- // before calling WriteHook.
-
- for (int i = -5; i <= -1; i++) {
- if (aFn[i] != 0x90 && aFn[i] != 0xcc) { // nop or int 3
- return false;
- }
- }
-
- // mov edi, edi. Yes, there are two ways to encode the same thing:
- //
- // 0x89ff == mov r/m, r
- // 0x8bff == mov r, r/m
- //
- // where "r" is register and "r/m" is register or memory. Windows seems to
- // use 8bff; I include 89ff out of paranoia.
- if ((aFn[0] != 0x8b && aFn[0] != 0x89) || aFn[1] != 0xff) {
- return false;
- }
-
- // Write a long jump into the space above the function.
- aFn[-5] = 0xe9; // jmp
- *((intptr_t*)(aFn - 4)) = aHookDest - (uintptr_t)(aFn); // target displacement
-
- // Set aOrigFunc here, because after this point, aHookDest might be called,
- // and aHookDest might use the aOrigFunc pointer.
- *aOrigFunc = aFn + 2;
-
- // Short jump up into our long jump.
- *((uint16_t*)(aFn)) = 0xf9eb; // jmp $-5
-
- // I think this routine is safe without this, but it can't hurt.
- FlushInstructionCache(GetCurrentProcess(),
- /* ignored */ nullptr,
- /* ignored */ 0);
-
- return true;
- }
-
-private:
- static byteptr_t ResolveRedirectedAddress(const byteptr_t aOriginalFunction)
- {
- // If function entry is jmp rel8 stub to the internal implementation, we
- // resolve redirected address from the jump target.
- if (aOriginalFunction[0] == 0xeb) {
- int8_t offset = (int8_t)(aOriginalFunction[1]);
- if (offset <= 0) {
- // Bail out for negative offset: probably already patched by some
- // third-party code.
- return aOriginalFunction;
- }
-
- for (int8_t i = 0; i < offset; i++) {
- if (aOriginalFunction[2 + i] != 0x90) {
- // Bail out on insufficient nop space.
- return aOriginalFunction;
- }
- }
-
- return aOriginalFunction + 2 + offset;
- }
-
- // If function entry is jmp [disp32] such as used by kernel32,
- // we resolve redirected address from import table.
- if (aOriginalFunction[0] == 0xff && aOriginalFunction[1] == 0x25) {
- return (byteptr_t)(**((uint32_t**) (aOriginalFunction + 2)));
- }
-
- return aOriginalFunction;
- }
-#else
- void Init(const char* aModuleName)
- {
- // Not implemented except on x86-32.
- }
-
- bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc)
- {
- // Not implemented except on x86-32.
- return false;
- }
-#endif
-};
-
-class WindowsDllDetourPatcher
-{
- typedef unsigned char* byteptr_t;
-public:
- WindowsDllDetourPatcher()
- : mModule(0), mHookPage(0), mMaxHooks(0), mCurHooks(0)
+ template <typename... Args>
+ explicit WindowsDllPatcherBase(Args... aArgs)
+ : mVMPolicy(mozilla::Forward<Args>(aArgs)...)
{
}
- ~WindowsDllDetourPatcher()
- {
- int i;
- byteptr_t p;
- for (i = 0, p = mHookPage; i < mCurHooks; i++, p += kHookSize) {
-#if defined(_M_IX86)
- size_t nBytes = 1 + sizeof(intptr_t);
-#elif defined(_M_X64)
- size_t nBytes = 2 + sizeof(intptr_t);
-#else
-#error "Unknown processor type"
-#endif
- byteptr_t origBytes = (byteptr_t)DecodePointer(*((byteptr_t*)p));
-
- // ensure we can modify the original code
- AutoVirtualProtect protect(origBytes, nBytes, PAGE_EXECUTE_READWRITE);
- if (!protect.Protect()) {
- continue;
- }
-
- // Remove the hook by making the original function jump directly
- // in the trampoline.
- intptr_t dest = (intptr_t)(p + sizeof(void*));
-#if defined(_M_IX86)
- // Ensure the JMP from CreateTrampoline is where we expect it to be.
- if (origBytes[0] != 0xE9)
- continue;
- *((intptr_t*)(origBytes + 1)) =
- dest - (intptr_t)(origBytes + 5); // target displacement
-#elif defined(_M_X64)
- // Ensure the MOV R11 from CreateTrampoline is where we expect it to be.
- if (origBytes[0] != 0x49 || origBytes[1] != 0xBB)
- continue;
- *((intptr_t*)(origBytes + 2)) = dest;
-#else
-#error "Unknown processor type"
-#endif
- }
- }
-
- void Init(const char* aModuleName, int aNumHooks = 0)
- {
- if (mModule) {
- return;
- }
-
- mModule = LoadLibraryExA(aModuleName, nullptr, 0);
- if (!mModule) {
- //printf("LoadLibraryEx for '%s' failed\n", aModuleName);
- return;
- }
-
- int hooksPerPage = 4096 / kHookSize;
- if (aNumHooks == 0) {
- aNumHooks = hooksPerPage;
- }
-
- mMaxHooks = aNumHooks + (hooksPerPage % aNumHooks);
-
- mHookPage = (byteptr_t)VirtualAllocEx(GetCurrentProcess(), nullptr,
- mMaxHooks * kHookSize,
- MEM_COMMIT | MEM_RESERVE,
- PAGE_EXECUTE_READ);
- if (!mHookPage) {
- mModule = 0;
- return;
- }
- }
-
- bool Initialized() { return !!mModule; }
-
- bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc)
- {
- if (!mModule) {
- return false;
- }
-
- void* pAddr = (void*)GetProcAddress(mModule, aName);
- if (!pAddr) {
- //printf ("GetProcAddress failed\n");
- return false;
- }
-
- pAddr = ResolveRedirectedAddress((byteptr_t)pAddr);
-
- CreateTrampoline(pAddr, aHookDest, aOrigFunc);
- if (!*aOrigFunc) {
- //printf ("CreateTrampoline failed\n");
- return false;
- }
-
- return true;
- }
-
-protected:
- const static int kPageSize = 4096;
- const static int kHookSize = 128;
-
- HMODULE mModule;
- byteptr_t mHookPage;
- int mMaxHooks;
- int mCurHooks;
-
- // rex bits
- static const BYTE kMaskHighNibble = 0xF0;
- static const BYTE kRexOpcode = 0x40;
- static const BYTE kMaskRexW = 0x08;
- static const BYTE kMaskRexR = 0x04;
- static const BYTE kMaskRexX = 0x02;
- static const BYTE kMaskRexB = 0x01;
-
- // mod r/m bits
- static const BYTE kRegFieldShift = 3;
- static const BYTE kMaskMod = 0xC0;
- static const BYTE kMaskReg = 0x38;
- static const BYTE kMaskRm = 0x07;
- static const BYTE kRmNeedSib = 0x04;
- static const BYTE kModReg = 0xC0;
- static const BYTE kModDisp32 = 0x80;
- static const BYTE kModDisp8 = 0x40;
- static const BYTE kModNoRegDisp = 0x00;
- static const BYTE kRmNoRegDispDisp32 = 0x05;
-
- // sib bits
- static const BYTE kMaskSibScale = 0xC0;
- static const BYTE kMaskSibIndex = 0x38;
- static const BYTE kMaskSibBase = 0x07;
- static const BYTE kSibBaseEbp = 0x05;
-
- // Register bit IDs.
- static const BYTE kRegAx = 0x0;
- static const BYTE kRegCx = 0x1;
- static const BYTE kRegDx = 0x2;
- static const BYTE kRegBx = 0x3;
- static const BYTE kRegSp = 0x4;
- static const BYTE kRegBp = 0x5;
- static const BYTE kRegSi = 0x6;
- static const BYTE kRegDi = 0x7;
-
- // Special ModR/M codes. These indicate operands that cannot be simply
- // memcpy-ed.
- // Operand is a 64-bit RIP-relative address.
- static const int kModOperand64 = -2;
- // Operand is not yet handled by our trampoline.
- static const int kModUnknown = -1;
-
- /**
- * Returns the number of bytes taken by the ModR/M byte, SIB (if present)
- * and the instruction's operand. In special cases, the special MODRM codes
- * above are returned.
- * aModRm points to the ModR/M byte of the instruction.
- * On return, aSubOpcode (if present) is filled with the subopcode/register
- * code found in the ModR/M byte.
- */
- int CountModRmSib(const BYTE *aModRm, BYTE* aSubOpcode = nullptr)
- {
- if (!aModRm) {
- MOZ_ASSERT(aModRm, "Missing ModRM byte");
- return kModUnknown;
- }
- int numBytes = 1; // Start with 1 for mod r/m byte itself
- switch (*aModRm & kMaskMod) {
- case kModReg:
- return numBytes;
- case kModDisp8:
- numBytes += 1;
- break;
- case kModDisp32:
- numBytes += 4;
- break;
- case kModNoRegDisp:
- if ((*aModRm & kMaskRm) == kRmNoRegDispDisp32) {
-#if defined(_M_X64)
- if (aSubOpcode) {
- *aSubOpcode = (*aModRm & kMaskReg) >> kRegFieldShift;
- }
- return kModOperand64;
-#else
- // On IA-32, all ModR/M instruction modes address memory relative to 0
- numBytes += 4;
-#endif
- } else if (((*aModRm & kMaskRm) == kRmNeedSib &&
- (*(aModRm + 1) & kMaskSibBase) == kSibBaseEbp)) {
- numBytes += 4;
- }
- break;
- default:
- // This should not be reachable
- MOZ_ASSERT_UNREACHABLE("Impossible value for modr/m byte mod bits");
- return kModUnknown;
- }
- if ((*aModRm & kMaskRm) == kRmNeedSib) {
- // SIB byte
- numBytes += 1;
- }
- if (aSubOpcode) {
- *aSubOpcode = (*aModRm & kMaskReg) >> kRegFieldShift;
- }
- return numBytes;
- }
-
-#if defined(_M_X64)
- // To patch for JMP and JE
-
- enum JumpType {
- Je,
- Jne,
- Jmp,
- Call
- };
-
- struct JumpPatch {
- JumpPatch()
- : mHookOffset(0), mJumpAddress(0), mType(JumpType::Jmp)
- {
- }
-
- JumpPatch(size_t aOffset, intptr_t aAddress, JumpType aType = JumpType::Jmp)
- : mHookOffset(aOffset), mJumpAddress(aAddress), mType(aType)
- {
- }
-
- size_t GenerateJump(uint8_t* aCode)
- {
- size_t offset = mHookOffset;
- if (mType == JumpType::Je) {
- // JNE RIP+14
- aCode[offset] = 0x75;
- aCode[offset + 1] = 14;
- offset += 2;
- } else if (mType == JumpType::Jne) {
- // JE RIP+14
- aCode[offset] = 0x74;
- aCode[offset + 1] = 14;
- offset += 2;
- }
-
- // Near call/jmp, absolute indirect, address given in r/m32
- if (mType == JumpType::Call) {
- // CALL [RIP+0]
- aCode[offset] = 0xff;
- aCode[offset + 1] = 0x15;
- // The offset to jump destination -- ie it is placed 2 bytes after the offset.
- *reinterpret_cast<int32_t*>(aCode + offset + 2) = 2;
- aCode[offset + 2 + 4] = 0xeb; // JMP +8 (jump over mJumpAddress)
- aCode[offset + 2 + 4 + 1] = 8;
- *reinterpret_cast<int64_t*>(aCode + offset + 2 + 4 + 2) = mJumpAddress;
- return offset + 2 + 4 + 2 + 8;
- } else {
- // JMP [RIP+0]
- aCode[offset] = 0xff;
- aCode[offset + 1] = 0x25;
- // The offset to jump destination is 0
- *reinterpret_cast<int32_t*>(aCode + offset + 2) = 0;
- *reinterpret_cast<int64_t*>(aCode + offset + 2 + 4) = mJumpAddress;
- return offset + 2 + 4 + 8;
- }
- }
-
- size_t mHookOffset;
- intptr_t mJumpAddress;
- JumpType mType;
- };
-
-#endif
-
- enum ePrefixGroupBits
- {
- eNoPrefixes = 0,
- ePrefixGroup1 = (1 << 0),
- ePrefixGroup2 = (1 << 1),
- ePrefixGroup3 = (1 << 2),
- ePrefixGroup4 = (1 << 3)
- };
-
- int CountPrefixBytes(byteptr_t aBytes, const int aBytesIndex,
- unsigned char* aOutGroupBits)
- {
- unsigned char& groupBits = *aOutGroupBits;
- groupBits = eNoPrefixes;
- int index = aBytesIndex;
- while (true) {
- switch (aBytes[index]) {
- // Group 1
- case 0xF0: // LOCK
- case 0xF2: // REPNZ
- case 0xF3: // REP / REPZ
- if (groupBits & ePrefixGroup1) {
- return -1;
- }
- groupBits |= ePrefixGroup1;
- ++index;
- break;
-
- // Group 2
- case 0x2E: // CS override / branch not taken
- case 0x36: // SS override
- case 0x3E: // DS override / branch taken
- case 0x64: // FS override
- case 0x65: // GS override
- if (groupBits & ePrefixGroup2) {
- return -1;
- }
- groupBits |= ePrefixGroup2;
- ++index;
- break;
-
- // Group 3
- case 0x66: // operand size override
- if (groupBits & ePrefixGroup3) {
- return -1;
- }
- groupBits |= ePrefixGroup3;
- ++index;
- break;
-
- // Group 4
- case 0x67: // Address size override
- if (groupBits & ePrefixGroup4) {
- return -1;
- }
- groupBits |= ePrefixGroup4;
- ++index;
- break;
-
- default:
- return index - aBytesIndex;
- }
- }
- }
-
- // Return a ModR/M byte made from the 2 Mod bits, the register used for the
- // reg bits and the register used for the R/M bits.
- BYTE BuildModRmByte(BYTE aModBits, BYTE aReg, BYTE aRm)
- {
- MOZ_ASSERT((aRm & kMaskRm) == aRm);
- MOZ_ASSERT((aModBits & kMaskMod) == aModBits);
- MOZ_ASSERT(((aReg << kRegFieldShift) & kMaskReg) == (aReg << kRegFieldShift));
- return aModBits | (aReg << kRegFieldShift) | aRm;
- }
-
- void CreateTrampoline(void* aOrigFunction, intptr_t aDest, void** aOutTramp)
+ ReadOnlyTargetFunction<MMPolicyT>
+ ResolveRedirectedAddress(const void* aOriginalFunction)
{
- *aOutTramp = nullptr;
-
- AutoVirtualProtect protectHookPage(mHookPage, mMaxHooks * kHookSize,
- PAGE_EXECUTE_READWRITE);
- if (!protectHookPage.Protect()) {
- return;
- }
-
- byteptr_t tramp = FindTrampolineSpace();
- if (!tramp) {
- return;
- }
-
- // We keep the address of the original function in the first bytes of
- // the trampoline buffer
- *((void**)tramp) = EncodePointer(aOrigFunction);
- tramp += sizeof(void*);
-
- byteptr_t origBytes = (byteptr_t)aOrigFunction;
-
- // # of bytes of the original function that we can overwrite.
- int nOrigBytes = 0;
-
-#if defined(_M_IX86)
- int pJmp32 = -1;
- while (nOrigBytes < 5) {
- // Understand some simple instructions that might be found in a
- // prologue; we might need to extend this as necessary.
- //
- // Note! If we ever need to understand jump instructions, we'll
- // need to rewrite the displacement argument.
- unsigned char prefixGroups;
- int numPrefixBytes = CountPrefixBytes(origBytes, nOrigBytes, &prefixGroups);
- if (numPrefixBytes < 0 || (prefixGroups & (ePrefixGroup3 | ePrefixGroup4))) {
- // Either the prefix sequence was bad, or there are prefixes that
- // we don't currently support (groups 3 and 4)
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- nOrigBytes += numPrefixBytes;
- if (origBytes[nOrigBytes] >= 0x88 &&
- origBytes[nOrigBytes] <= 0x8B) {
- // various MOVs
- ++nOrigBytes;
- int len = CountModRmSib(origBytes + nOrigBytes);
- if (len < 0) {
- MOZ_ASSERT_UNREACHABLE("Unrecognized MOV opcode sequence");
- return;
- }
- nOrigBytes += len;
- } else if (origBytes[nOrigBytes] == 0x0f &&
- (origBytes[nOrigBytes + 1] == 0x10 ||
- origBytes[nOrigBytes + 1] == 0x11)) {
- // SSE: movups xmm, xmm/m128
- // movups xmm/m128, xmm
- nOrigBytes += 2;
- int len = CountModRmSib(origBytes + nOrigBytes);
- if (len < 0) {
- MOZ_ASSERT_UNREACHABLE("Unrecognized MOV opcode sequence");
- return;
- }
- nOrigBytes += len;
- } else if (origBytes[nOrigBytes] == 0xA1) {
- // MOV eax, [seg:offset]
- nOrigBytes += 5;
- } else if (origBytes[nOrigBytes] == 0xB8) {
- // MOV 0xB8: http://ref.x86asm.net/coder32.html#xB8
- nOrigBytes += 5;
- } else if (origBytes[nOrigBytes] == 0x33 &&
- (origBytes[nOrigBytes+1] & kMaskMod) == kModReg) {
- // XOR r32, r32
- nOrigBytes += 2;
- } else if ((origBytes[nOrigBytes] & 0xf8) == 0x40) {
- // INC r32
- nOrigBytes += 1;
- } else if (origBytes[nOrigBytes] == 0x83) {
- // ADD|ODR|ADC|SBB|AND|SUB|XOR|CMP r/m, imm8
- unsigned char b = origBytes[nOrigBytes + 1];
- if ((b & 0xc0) == 0xc0) {
- // ADD|ODR|ADC|SBB|AND|SUB|XOR|CMP r, imm8
- nOrigBytes += 3;
- } else {
- // bail
- MOZ_ASSERT_UNREACHABLE("Unrecognized bit opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x68) {
- // PUSH with 4-byte operand
- nOrigBytes += 5;
- } else if ((origBytes[nOrigBytes] & 0xf0) == 0x50) {
- // 1-byte PUSH/POP
- nOrigBytes++;
- } else if (origBytes[nOrigBytes] == 0x6A) {
- // PUSH imm8
- nOrigBytes += 2;
- } else if (origBytes[nOrigBytes] == 0xe9) {
- pJmp32 = nOrigBytes;
- // jmp 32bit offset
- nOrigBytes += 5;
- } else if (origBytes[nOrigBytes] == 0xff &&
- origBytes[nOrigBytes + 1] == 0x25) {
- // jmp [disp32]
- nOrigBytes += 6;
- } else if (origBytes[nOrigBytes] == 0xc2) {
- // ret imm16. We can't handle this but it happens. We don't ASSERT but we do fail to hook.
-#if defined(MOZILLA_INTERNAL_API)
- NS_WARNING("Cannot hook method -- RET opcode found");
-#endif
- return;
- } else {
- //printf ("Unknown x86 instruction byte 0x%02x, aborting trampoline\n", origBytes[nOrigBytes]);
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- }
-
- // The trampoline is a copy of the instructions that we just traced,
- // followed by a jump that we add below.
- memcpy(tramp, aOrigFunction, nOrigBytes);
-#elif defined(_M_X64)
- // The number of bytes used by the trampoline.
- int nTrampBytes = 0;
- bool foundJmp = false;
-
- while (nOrigBytes < 13) {
- // If we found JMP 32bit offset, we require that the next bytes must
- // be NOP or INT3. There is no reason to copy them.
- // TODO: This used to trigger for Je as well. Now that I allow
- // instructions after CALL and JE, I don't think I need that.
- // The only real value of this condition is that if code follows a JMP
- // then its _probably_ the target of a JMP somewhere else and we
- // will be overwriting it, which would be tragic. This seems
- // highly unlikely.
- if (foundJmp) {
- if (origBytes[nOrigBytes] == 0x90 || origBytes[nOrigBytes] == 0xcc) {
- nOrigBytes++;
- continue;
- }
- MOZ_ASSERT_UNREACHABLE("Opcode sequence includes commands after JMP");
- return;
- }
- if (origBytes[nOrigBytes] == 0x0f) {
- COPY_CODES(1);
- if (origBytes[nOrigBytes] == 0x1f) {
- // nop (multibyte)
- COPY_CODES(1);
- if ((origBytes[nOrigBytes] & 0xc0) == 0x40 &&
- (origBytes[nOrigBytes] & 0x7) == 0x04) {
- COPY_CODES(3);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x05) {
- // syscall
- COPY_CODES(1);
- } else if (origBytes[nOrigBytes] == 0x10 ||
- origBytes[nOrigBytes] == 0x11) {
- // SSE: movups xmm, xmm/m128
- // movups xmm/m128, xmm
- COPY_CODES(1);
- int nModRmSibBytes = CountModRmSib(&origBytes[nOrigBytes]);
- if (nModRmSibBytes < 0) {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- } else {
- COPY_CODES(nModRmSibBytes);
- }
- } else if (origBytes[nOrigBytes] == 0x84) {
- // je rel32
- JumpPatch jump(nTrampBytes - 1, // overwrite the 0x0f we copied above
- (intptr_t)(origBytes + nOrigBytes + 5 +
- *(reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 1))),
- JumpType::Je);
- nTrampBytes = jump.GenerateJump(tramp);
- nOrigBytes += 5;
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x40 ||
- origBytes[nOrigBytes] == 0x41) {
- // Plain REX or REX.B
- COPY_CODES(1);
- if ((origBytes[nOrigBytes] & 0xf0) == 0x50) {
- // push/pop with Rx register
- COPY_CODES(1);
- } else if (origBytes[nOrigBytes] >= 0xb8 && origBytes[nOrigBytes] <= 0xbf) {
- // mov r32, imm32
- COPY_CODES(5);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x44) {
- // REX.R
- COPY_CODES(1);
-
- // TODO: Combine with the "0x89" case below in the REX.W section
- if (origBytes[nOrigBytes] == 0x89) {
- // mov r/m32, r32
- COPY_CODES(1);
- int len = CountModRmSib(origBytes + nOrigBytes);
- if (len < 0) {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- COPY_CODES(len);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x45) {
- // REX.R & REX.B
- COPY_CODES(1);
-
- if (origBytes[nOrigBytes] == 0x33) {
- // xor r32, r32
- COPY_CODES(2);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if ((origBytes[nOrigBytes] & 0xfa) == 0x48) {
- // REX.W | REX.WR | REX.WRB | REX.WB
- COPY_CODES(1);
-
- if (origBytes[nOrigBytes] == 0x81 &&
- (origBytes[nOrigBytes + 1] & 0xf8) == 0xe8) {
- // sub r, dword
- COPY_CODES(6);
- } else if (origBytes[nOrigBytes] == 0x83 &&
- (origBytes[nOrigBytes + 1] & 0xf8) == 0xe8) {
- // sub r, byte
- COPY_CODES(3);
- } else if (origBytes[nOrigBytes] == 0x83 &&
- (origBytes[nOrigBytes + 1] & (kMaskMod|kMaskReg)) == kModReg) {
- // add r, byte
- COPY_CODES(3);
- } else if (origBytes[nOrigBytes] == 0x83 &&
- (origBytes[nOrigBytes + 1] & 0xf8) == 0x60) {
- // and [r+d], imm8
- COPY_CODES(5);
- } else if (origBytes[nOrigBytes] == 0x2b &&
- (origBytes[nOrigBytes + 1] & kMaskMod) == kModReg) {
- // sub r64, r64
- COPY_CODES(2);
- } else if (origBytes[nOrigBytes] == 0x85) {
- // 85 /r => TEST r/m32, r32
- if ((origBytes[nOrigBytes + 1] & 0xc0) == 0xc0) {
- COPY_CODES(2);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if ((origBytes[nOrigBytes] & 0xfd) == 0x89) {
- // MOV r/m64, r64 | MOV r64, r/m64
- BYTE reg;
- int len = CountModRmSib(origBytes + nOrigBytes + 1, ®);
- if (len < 0) {
- MOZ_ASSERT(len == kModOperand64);
- if (len != kModOperand64) {
- return;
- }
- nOrigBytes += 2; // skip the MOV and MOD R/M bytes
-
- // The instruction MOVs 64-bit data from a RIP-relative memory
- // address (determined with a 32-bit offset from RIP) into a
- // 64-bit register.
- int64_t* absAddr =
- reinterpret_cast<int64_t*>(origBytes + nOrigBytes + 4 +
- *reinterpret_cast<int32_t*>(origBytes + nOrigBytes));
- nOrigBytes += 4;
-
- if (reg == kRegAx) {
- // Destination is RAX. Encode instruction as MOVABS with a
- // 64-bit absolute address as its immediate operand.
- tramp[nTrampBytes] = 0xa1;
- ++nTrampBytes;
- int64_t** trampOperandPtr = reinterpret_cast<int64_t**>(tramp + nTrampBytes);
- *trampOperandPtr = absAddr;
- nTrampBytes += 8;
- } else {
- // The MOV must be done in two steps. First, we MOVABS the
- // absolute 64-bit address into our target register.
- // Then, we MOV from that address into the register
- // using register-indirect addressing.
- tramp[nTrampBytes] = 0xb8 + reg;
- ++nTrampBytes;
- int64_t** trampOperandPtr = reinterpret_cast<int64_t**>(tramp + nTrampBytes);
- *trampOperandPtr = absAddr;
- nTrampBytes += 8;
- tramp[nTrampBytes] = 0x48;
- tramp[nTrampBytes+1] = 0x8b;
- tramp[nTrampBytes+2] = BuildModRmByte(kModNoRegDisp, reg, reg);
- nTrampBytes += 3;
- }
- } else {
- COPY_CODES(len+1);
- }
- } else if (origBytes[nOrigBytes] == 0xc7) {
- // MOV r/m64, imm32
- if (origBytes[nOrigBytes + 1] == 0x44) {
- // MOV [r64+disp8], imm32
- // ModR/W + SIB + disp8 + imm32
- COPY_CODES(8);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0xff) {
- // JMP /4
- if ((origBytes[nOrigBytes + 1] & 0xc0) == 0x0 &&
- (origBytes[nOrigBytes + 1] & 0x07) == 0x5) {
- // [rip+disp32]
- // convert JMP 32bit offset to JMP 64bit direct
- JumpPatch jump(nTrampBytes - 1, // overwrite the REX.W/REX.WR we copied above
- *reinterpret_cast<intptr_t*>(origBytes + nOrigBytes + 6 +
- *reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 2)),
- JumpType::Jmp);
- nTrampBytes = jump.GenerateJump(tramp);
- nOrigBytes += 6;
- foundJmp = true;
- } else {
- // not support yet!
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x8d) {
- // LEA reg, addr
- if ((origBytes[nOrigBytes + 1] & kMaskMod) == 0x0 &&
- (origBytes[nOrigBytes + 1] & kMaskRm) == 0x5) {
- // [rip+disp32]
- // convert 32bit offset to 64bit direct and convert instruction
- // to a simple 64-bit mov
- BYTE reg = (origBytes[nOrigBytes + 1] & kMaskReg) >> kRegFieldShift;
- intptr_t absAddr =
- reinterpret_cast<intptr_t>(origBytes + nOrigBytes + 6 +
- *reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 2));
- nOrigBytes += 6;
- tramp[nTrampBytes] = 0xb8 + reg; // mov
- ++nTrampBytes;
- intptr_t* trampOperandPtr = reinterpret_cast<intptr_t*>(tramp + nTrampBytes);
- *trampOperandPtr = absAddr;
- nTrampBytes += 8;
- } else {
- // Above we dealt with RIP-relative instructions. Any other
- // operand form can simply be copied.
- int len = CountModRmSib(origBytes + nOrigBytes + 1);
- // We handled the kModOperand64 -- ie RIP-relative -- case above
- MOZ_ASSERT(len > 0);
- COPY_CODES(len + 1);
- }
- } else if (origBytes[nOrigBytes] == 0x63 &&
- (origBytes[nOrigBytes + 1] & kMaskMod) == kModReg) {
- // movsxd r64, r32 (move + sign extend)
- COPY_CODES(2);
- } else {
- // not support yet!
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x66) {
- // operand override prefix
- COPY_CODES(1);
- // This is the same as the x86 version
- if (origBytes[nOrigBytes] >= 0x88 && origBytes[nOrigBytes] <= 0x8B) {
- // various MOVs
- unsigned char b = origBytes[nOrigBytes + 1];
- if (((b & 0xc0) == 0xc0) ||
- (((b & 0xc0) == 0x00) &&
- ((b & 0x07) != 0x04) && ((b & 0x07) != 0x05))) {
- // REG=r, R/M=r or REG=r, R/M=[r]
- COPY_CODES(2);
- } else if ((b & 0xc0) == 0x40) {
- if ((b & 0x07) == 0x04) {
- // REG=r, R/M=[SIB + disp8]
- COPY_CODES(4);
- } else {
- // REG=r, R/M=[r + disp8]
- COPY_CODES(3);
- }
- } else {
- // complex MOV, bail
- MOZ_ASSERT_UNREACHABLE("Unrecognized MOV opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x44 &&
- origBytes[nOrigBytes+1] == 0x89) {
- // mov word ptr [reg+disp8], reg
- COPY_CODES(2);
- int len = CountModRmSib(origBytes + nOrigBytes);
- if (len < 0) {
- // no way to support this yet.
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- COPY_CODES(len);
- }
- } else if ((origBytes[nOrigBytes] & 0xf0) == 0x50) {
- // 1-byte push/pop
- COPY_CODES(1);
- } else if (origBytes[nOrigBytes] == 0x65) {
- // GS prefix
- //
- // The entry of GetKeyState on Windows 10 has the following code.
- // 65 48 8b 04 25 30 00 00 00 mov rax,qword ptr gs:[30h]
- // (GS prefix + REX + MOV (0x8b) ...)
- if (origBytes[nOrigBytes + 1] == 0x48 &&
- (origBytes[nOrigBytes + 2] >= 0x88 && origBytes[nOrigBytes + 2] <= 0x8b)) {
- COPY_CODES(3);
- int len = CountModRmSib(origBytes + nOrigBytes);
- if (len < 0) {
- // no way to support this yet.
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- COPY_CODES(len);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x80 &&
- origBytes[nOrigBytes + 1] == 0x3d) {
- // cmp byte ptr [rip-relative address], imm8
- // We'll compute the absolute address and do the cmp in r11
-
- // push r11 (to save the old value)
- tramp[nTrampBytes] = 0x49;
- ++nTrampBytes;
- tramp[nTrampBytes] = 0x53;
- ++nTrampBytes;
-
- byteptr_t absAddr =
- reinterpret_cast<byteptr_t>(origBytes + nOrigBytes + 7 +
- *reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 2));
- nOrigBytes += 6;
-
- // mov r11, absolute address
- tramp[nTrampBytes] = 0x49;
- ++nTrampBytes;
- tramp[nTrampBytes] = 0xbb;
- ++nTrampBytes;
-
- *reinterpret_cast<byteptr_t*>(tramp + nTrampBytes) = absAddr;
- nTrampBytes += 8;
-
- // cmp byte ptr [r11],...
- tramp[nTrampBytes] = 0x41;
- ++nTrampBytes;
- tramp[nTrampBytes] = 0x80;
- ++nTrampBytes;
- tramp[nTrampBytes] = 0x3b;
- ++nTrampBytes;
-
- // ...imm8
- COPY_CODES(1);
-
- // pop r11 (doesn't affect the flags from the cmp)
- tramp[nTrampBytes] = 0x49;
- ++nTrampBytes;
- tramp[nTrampBytes] = 0x5b;
- ++nTrampBytes;
- } else if (origBytes[nOrigBytes] == 0x90) {
- // nop
- COPY_CODES(1);
- } else if ((origBytes[nOrigBytes] & 0xf8) == 0xb8) {
- // MOV r32, imm32
- COPY_CODES(5);
- } else if (origBytes[nOrigBytes] == 0x33) {
- // xor r32, r/m32
- COPY_CODES(2);
- } else if (origBytes[nOrigBytes] == 0xf6) {
- // test r/m8, imm8 (used by ntdll on Windows 10 x64)
- // (no flags are affected by near jmp since there is no task switch,
- // so it is ok for a jmp to be written immediately after a test)
- BYTE subOpcode = 0;
- int nModRmSibBytes = CountModRmSib(&origBytes[nOrigBytes + 1], &subOpcode);
- if (nModRmSibBytes < 0 || subOpcode != 0) {
- // Unsupported
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- COPY_CODES(2 + nModRmSibBytes);
- } else if (origBytes[nOrigBytes] == 0x85) {
- // test r/m32, r32
- int nModRmSibBytes = CountModRmSib(&origBytes[nOrigBytes + 1]);
- if (nModRmSibBytes < 0) {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- COPY_CODES(1 + nModRmSibBytes);
- } else if (origBytes[nOrigBytes] == 0xd1 &&
- (origBytes[nOrigBytes+1] & kMaskMod) == kModReg) {
- // bit shifts/rotates : (SA|SH|RO|RC)(R|L) r32
- // (e.g. 0xd1 0xe0 is SAL, 0xd1 0xc8 is ROR)
- COPY_CODES(2);
- } else if (origBytes[nOrigBytes] == 0xc3) {
- // ret
- COPY_CODES(1);
- } else if (origBytes[nOrigBytes] == 0xcc) {
- // int 3
- COPY_CODES(1);
- } else if (origBytes[nOrigBytes] == 0xe8 ||
- origBytes[nOrigBytes] == 0xe9) {
- // CALL (0xe8) or JMP (0xe9) 32bit offset
- foundJmp = origBytes[nOrigBytes] == 0xe9;
- JumpPatch jump(nTrampBytes,
- (intptr_t)(origBytes + nOrigBytes + 5 +
- *(reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 1))),
- origBytes[nOrigBytes] == 0xe8 ? JumpType::Call : JumpType::Jmp);
- nTrampBytes = jump.GenerateJump(tramp);
- nOrigBytes += 5;
- } else if (origBytes[nOrigBytes] == 0x74 || // je rel8 (0x74)
- origBytes[nOrigBytes] == 0x75) { // jne rel8 (0x75)
- char offset = origBytes[nOrigBytes + 1];
- auto jumpType = JumpType::Je;
- if (origBytes[nOrigBytes] == 0x75)
- jumpType = JumpType::Jne;
- JumpPatch jump(nTrampBytes,
- (intptr_t)(origBytes + nOrigBytes + 2 + offset), jumpType);
- nTrampBytes = jump.GenerateJump(tramp);
- nOrigBytes += 2;
- } else if (origBytes[nOrigBytes] == 0xff) {
- if ((origBytes[nOrigBytes + 1] & (kMaskMod|kMaskReg)) == 0xf0) {
- // push r64
- COPY_CODES(2);
- } else if (origBytes[nOrigBytes + 1] == 0x25) {
- // jmp absolute indirect m32
- foundJmp = true;
- int32_t offset = *(reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 2));
- int64_t* ptrToJmpDest = reinterpret_cast<int64_t*>(origBytes + nOrigBytes + 6 + offset);
- intptr_t jmpDest = static_cast<intptr_t>(*ptrToJmpDest);
- JumpPatch jump(nTrampBytes, jmpDest, JumpType::Jmp);
- nTrampBytes = jump.GenerateJump(tramp);
- nOrigBytes += 6;
- } else if ((origBytes[nOrigBytes + 1] & (kMaskMod|kMaskReg)) == BuildModRmByte(kModReg, 2, 0)) {
- // CALL reg (ff nn)
- COPY_CODES(2);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x83 &&
- (origBytes[nOrigBytes + 1] & 0xf8) == 0x60) {
- // and [r+d], imm8
- COPY_CODES(5);
- } else if (origBytes[nOrigBytes] == 0xc6) {
- // mov [r+d], imm8
- int len = CountModRmSib(&origBytes[nOrigBytes + 1]);
- if (len < 0) {
- // RIP-relative not yet supported
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- COPY_CODES(len + 1);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- }
-#else
-#error "Unknown processor type"
-#endif
-
- if (nOrigBytes > 100) {
- //printf ("Too big!");
- return;
- }
-
- // target address of the final jmp instruction in the trampoline
- byteptr_t trampDest = origBytes + nOrigBytes;
-
-#if defined(_M_IX86)
- if (pJmp32 >= 0) {
- // Jump directly to the original target of the jump instead of jumping to the
- // original function.
- // Adjust jump target displacement to jump location in the trampoline.
- *((intptr_t*)(tramp + pJmp32 + 1)) += origBytes - tramp;
- } else {
- tramp[nOrigBytes] = 0xE9; // jmp
- *((intptr_t*)(tramp + nOrigBytes + 1)) =
- (intptr_t)trampDest - (intptr_t)(tramp + nOrigBytes + 5); // target displacement
- }
-#elif defined(_M_X64)
- // If the we found a Jmp, we don't need to add another instruction. However,
- // if we found a _conditional_ jump or a CALL (or no control operations
- // at all) then we still need to run the rest of aOriginalFunction.
- if (!foundJmp) {
- JumpPatch patch(nTrampBytes, reinterpret_cast<intptr_t>(trampDest));
- patch.GenerateJump(tramp);
- }
-#endif
-
- // The trampoline is now valid.
- *aOutTramp = tramp;
-
- // ensure we can modify the original code
- AutoVirtualProtect protect(aOrigFunction, nOrigBytes, PAGE_EXECUTE_READWRITE);
- if (!protect.Protect()) {
- return;
- }
-
-#if defined(_M_IX86)
- // now modify the original bytes
- origBytes[0] = 0xE9; // jmp
- *((intptr_t*)(origBytes + 1)) =
- aDest - (intptr_t)(origBytes + 5); // target displacement
-#elif defined(_M_X64)
- // mov r11, address
- origBytes[0] = 0x49;
- origBytes[1] = 0xbb;
-
- *((intptr_t*)(origBytes + 2)) = aDest;
-
- // jmp r11
- origBytes[10] = 0x41;
- origBytes[11] = 0xff;
- origBytes[12] = 0xe3;
-#endif
- }
-
- byteptr_t FindTrampolineSpace()
- {
- if (mCurHooks >= mMaxHooks) {
- return 0;
- }
-
- byteptr_t p = mHookPage + mCurHooks * kHookSize;
-
- mCurHooks++;
-
- return p;
- }
-
- static void* ResolveRedirectedAddress(const byteptr_t aOriginalFunction)
- {
+ ReadOnlyTargetFunction<MMPolicyT> origFn(mVMPolicy, aOriginalFunction);
// If function entry is jmp rel8 stub to the internal implementation, we
// resolve redirected address from the jump target.
- if (aOriginalFunction[0] == 0xeb) {
- int8_t offset = (int8_t)(aOriginalFunction[1]);
+ if (origFn[0] == 0xeb) {
+ int8_t offset = (int8_t)(origFn[1]);
if (offset <= 0) {
// Bail out for negative offset: probably already patched by some
// third-party code.
- return aOriginalFunction;
+ return Move(origFn);
}
for (int8_t i = 0; i < offset; i++) {
- if (aOriginalFunction[2 + i] != 0x90) {
+ if (origFn[2 + i] != 0x90) {
// Bail out on insufficient nop space.
- return aOriginalFunction;
+ return Move(origFn);
}
}
- return aOriginalFunction + 2 + offset;
+ origFn += 2 + offset;
+ return Move(origFn);
}
#if defined(_M_IX86)
// If function entry is jmp [disp32] such as used by kernel32,
// we resolve redirected address from import table.
- if (aOriginalFunction[0] == 0xff && aOriginalFunction[1] == 0x25) {
- return (void*)(**((uint32_t**) (aOriginalFunction + 2)));
+ if (origFn[0] == 0xff && origFn[1] == 0x25) {
+ return ReadOnlyTargetFunction<MMPolicyT>(mVMPolicy,
+ reinterpret_cast<const void*>((origFn + 2).template ChasePointer<uintptr_t*>()));
}
#elif defined(_M_X64)
- if (aOriginalFunction[0] == 0xe9) {
+ // If function entry is jmp [disp32] such as used by kernel32,
+ // we resolve redirected address from import table.
+ if (origFn[0] == 0x48 && origFn[1] == 0xff && origFn[2] == 0x25) {
+ return ReadOnlyTargetFunction<MMPolicyT>(mVMPolicy,
+ reinterpret_cast<const void*>((origFn + 3).ChasePointerFromDisp()));
+ }
+
+ if (origFn[0] == 0xe9) {
// require for TestDllInterceptor with --disable-optimize
- int32_t offset = *((int32_t*)(aOriginalFunction + 1));
- return aOriginalFunction + 5 + offset;
+ uintptr_t abstarget = (origFn + 1).ReadDisp32AsAbsolute();
+ return ReadOnlyTargetFunction<MMPolicyT>(mVMPolicy, abstarget);
}
#endif
- return aOriginalFunction;
- }
-};
-
-} // namespace internal
-
-class WindowsDllInterceptor
-{
- internal::WindowsDllNopSpacePatcher mNopSpacePatcher;
- internal::WindowsDllDetourPatcher mDetourPatcher;
-
- const char* mModuleName;
- int mNHooks;
-
-public:
- explicit WindowsDllInterceptor(const char* aModuleName = nullptr,
- int aNumHooks = 0)
- : mModuleName(nullptr)
- , mNHooks(0)
- {
- if (aModuleName) {
- Init(aModuleName, aNumHooks);
- }
- }
-
- void Init(const char* aModuleName, int aNumHooks = 0)
- {
- if (mModuleName) {
- return;
- }
-
- mModuleName = aModuleName;
- mNHooks = aNumHooks;
- mNopSpacePatcher.Init(aModuleName);
-
- // Lazily initialize mDetourPatcher, since it allocates memory and we might
- // not need it.
+ return Move(origFn);
}
- /**
- * Hook/detour the method aName from the DLL we set in Init so that it calls
- * aHookDest instead. Returns the original method pointer in aOrigFunc
- * and returns true if successful.
- *
- * IMPORTANT: If you use this method, please add your case to the
- * TestDllInterceptor in order to detect future failures. Even if this
- * succeeds now, updates to the hooked DLL could cause it to fail in
- * the future.
- */
- bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc)
- {
- // Use a nop space patch if possible, otherwise fall back to a detour.
- // This should be the preferred method for adding hooks.
-
- if (!mModuleName) {
- return false;
- }
-
- if (mNopSpacePatcher.AddHook(aName, aHookDest, aOrigFunc)) {
- return true;
- }
-
- return AddDetour(aName, aHookDest, aOrigFunc);
- }
-
- /**
- * Detour the method aName from the DLL we set in Init so that it calls
- * aHookDest instead. Returns the original method pointer in aOrigFunc
- * and returns true if successful.
- *
- * IMPORTANT: If you use this method, please add your case to the
- * TestDllInterceptor in order to detect future failures. Even if this
- * succeeds now, updates to the detoured DLL could cause it to fail in
- * the future.
- */
- bool AddDetour(const char* aName, intptr_t aHookDest, void** aOrigFunc)
- {
- // Generally, code should not call this method directly. Use AddHook unless
- // there is a specific need to avoid nop space patches.
-
- if (!mModuleName) {
- return false;
- }
-
- if (!mDetourPatcher.Initialized()) {
- mDetourPatcher.Init(mModuleName, mNHooks);
- }
-
- return mDetourPatcher.AddHook(aName, aHookDest, aOrigFunc);
- }
+protected:
+ VMPolicy mVMPolicy;
};
+} // namespace interceptor
} // namespace mozilla
-#endif /* NS_WINDOWS_DLL_INTERCEPTOR_H_ */
+#endif // mozilla_interceptor_PatcherBase_h
copy from xpcom/build/nsWindowsDllInterceptor.h
copy to mozglue/misc/interceptor/PatcherDetour.h
--- a/xpcom/build/nsWindowsDllInterceptor.h
+++ b/mozglue/misc/interceptor/PatcherDetour.h
@@ -1,497 +1,183 @@
/* -*- 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 http://mozilla.org/MPL/2.0/. */
-
-#ifndef NS_WINDOWS_DLL_INTERCEPTOR_H_
-#define NS_WINDOWS_DLL_INTERCEPTOR_H_
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
-#include "mozilla/Assertions.h"
-#include "mozilla/ArrayUtils.h"
-#include "mozilla/UniquePtr.h"
-#include "nsWindowsHelpers.h"
-
-#include <wchar.h>
-#include <windows.h>
-#include <winternl.h>
+#ifndef mozilla_interceptor_PatcherDetour_h
+#define mozilla_interceptor_PatcherDetour_h
-/*
- * Simple function interception.
- *
- * We have two separate mechanisms for intercepting a function: We can use the
- * built-in nop space, if it exists, or we can create a detour.
- *
- * Using the built-in nop space works as follows: On x86-32, DLL functions
- * begin with a two-byte nop (mov edi, edi) and are preceeded by five bytes of
- * NOP instructions.
- *
- * When we detect a function with this prelude, we do the following:
- *
- * 1. Write a long jump to our interceptor function into the five bytes of NOPs
- * before the function.
- *
- * 2. Write a short jump -5 into the two-byte nop at the beginning of the function.
- *
- * This mechanism is nice because it's thread-safe. It's even safe to do if
- * another thread is currently running the function we're modifying!
- *
- * When the WindowsDllNopSpacePatcher is destroyed, we overwrite the short jump
- * but not the long jump, so re-intercepting the same function won't work,
- * because its prelude won't match.
- *
- *
- * Unfortunately nop space patching doesn't work on functions which don't have
- * this magic prelude (and in particular, x86-64 never has the prelude). So
- * when we can't use the built-in nop space, we fall back to using a detour,
- * which works as follows:
- *
- * 1. Save first N bytes of OrigFunction to trampoline, where N is a
- * number of bytes >= 5 that are instruction aligned.
- *
- * 2. Replace first 5 bytes of OrigFunction with a jump to the Hook
- * function.
- *
- * 3. After N bytes of the trampoline, add a jump to OrigFunction+N to
- * continue original program flow.
- *
- * 4. Hook function needs to call the trampoline during its execution,
- * to invoke the original function (so address of trampoline is
- * returned).
- *
- * When the WindowsDllDetourPatcher object is destructed, OrigFunction is
- * patched again to jump directly to the trampoline instead of going through
- * the hook function. As such, re-intercepting the same function won't work, as
- * jump instructions are not supported.
- *
- * Note that this is not thread-safe. Sad day.
- *
- */
+#include "mozilla/interceptor/PatcherBase.h"
+#include "mozilla/interceptor/Trampoline.h"
-#include <stdint.h>
+#include "mozilla/ScopeExit.h"
#define COPY_CODES(NBYTES) do { \
- memcpy(&tramp[nTrampBytes], &origBytes[nOrigBytes], NBYTES); \
- nOrigBytes += NBYTES; \
- nTrampBytes += NBYTES; \
+ tramp.CopyFrom(origBytes.GetAddress(), NBYTES); \
+ origBytes += NBYTES; \
} while (0)
namespace mozilla {
-namespace internal {
+namespace interceptor {
-class AutoVirtualProtect
+template <typename VMPolicy>
+class WindowsDllDetourPatcher final : public WindowsDllPatcherBase<VMPolicy>
{
public:
- AutoVirtualProtect(void* aFunc, size_t aSize, DWORD aProtect)
- : mFunc(aFunc), mSize(aSize), mNewProtect(aProtect), mOldProtect(0),
- mSuccess(false)
- {}
-
- ~AutoVirtualProtect()
- {
- if (mSuccess) {
- VirtualProtectEx(GetCurrentProcess(), mFunc, mSize, mOldProtect,
- &mOldProtect);
- }
- }
-
- bool Protect()
- {
- mSuccess = !!VirtualProtectEx(GetCurrentProcess(), mFunc, mSize,
- mNewProtect, &mOldProtect);
- if (!mSuccess) {
- // printf("VirtualProtectEx failed! %d\n", GetLastError());
- }
- return mSuccess;
- }
-
-private:
- void* const mFunc;
- size_t const mSize;
- DWORD const mNewProtect;
- DWORD mOldProtect;
- bool mSuccess;
-};
-
-class WindowsDllNopSpacePatcher
-{
- typedef uint8_t* byteptr_t;
- HMODULE mModule;
-
- // Dumb array for remembering the addresses of functions we've patched.
- // (This should be nsTArray, but non-XPCOM code uses this class.)
- static const size_t maxPatchedFns = 16;
- byteptr_t mPatchedFns[maxPatchedFns];
- size_t mPatchedFnsLen;
-
-public:
- WindowsDllNopSpacePatcher()
- : mModule(0)
- , mPatchedFnsLen(0)
- {}
-
-#if defined(_M_IX86)
- ~WindowsDllNopSpacePatcher()
- {
- // Restore the mov edi, edi to the beginning of each function we patched.
-
- for (size_t i = 0; i < mPatchedFnsLen; i++) {
- byteptr_t fn = mPatchedFns[i];
-
- // Ensure we can write to the code.
- AutoVirtualProtect protect(fn, 2, PAGE_EXECUTE_READWRITE);
- if (!protect.Protect()) {
- continue;
- }
-
- // mov edi, edi
- *((uint16_t*)fn) = 0xff8b;
-
- // I don't think this is actually necessary, but it can't hurt.
- FlushInstructionCache(GetCurrentProcess(),
- /* ignored */ nullptr,
- /* ignored */ 0);
- }
- }
-
- void Init(const char* aModuleName)
- {
- if (!IsCompatible()) {
-#if defined(MOZILLA_INTERNAL_API)
- NS_WARNING("NOP space patching is unavailable for compatibility reasons");
-#endif
- return;
- }
-
- mModule = LoadLibraryExA(aModuleName, nullptr, 0);
- if (!mModule) {
- //printf("LoadLibraryEx for '%s' failed\n", aModuleName);
- return;
- }
- }
-
- /**
- * NVIDIA Optimus drivers utilize Microsoft Detours 2.x to patch functions
- * in our address space. There is a bug in Detours 2.x that causes it to
- * patch at the wrong address when attempting to detour code that is already
- * NOP space patched. This function is an effort to detect the presence of
- * this NVIDIA code in our address space and disable NOP space patching if it
- * is. We also check AppInit_DLLs since this is the mechanism that the Optimus
- * drivers use to inject into our process.
- */
- static bool IsCompatible()
- {
- // These DLLs are known to have bad interactions with this style of patching
- const wchar_t* kIncompatibleDLLs[] = {
- L"detoured.dll",
- L"_etoured.dll",
- L"nvd3d9wrap.dll",
- L"nvdxgiwrap.dll"
- };
- // See if the infringing DLLs are already loaded
- for (unsigned int i = 0; i < mozilla::ArrayLength(kIncompatibleDLLs); ++i) {
- if (GetModuleHandleW(kIncompatibleDLLs[i])) {
- return false;
- }
- }
- if (GetModuleHandleW(L"user32.dll")) {
- // user32 is loaded but the infringing DLLs are not, assume we're safe to
- // proceed.
- return true;
- }
- // If user32 has not loaded yet, check AppInit_DLLs to ensure that Optimus
- // won't be loaded once user32 is initialized.
- HKEY hkey = NULL;
- if (!RegOpenKeyExW(HKEY_LOCAL_MACHINE,
- L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows",
- 0, KEY_QUERY_VALUE, &hkey)) {
- nsAutoRegKey key(hkey);
- DWORD numBytes = 0;
- const wchar_t kAppInitDLLs[] = L"AppInit_DLLs";
- // Query for required buffer size
- LONG status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr,
- nullptr, nullptr, &numBytes);
- mozilla::UniquePtr<wchar_t[]> data;
- if (!status) {
- // Allocate the buffer and query for the actual data
- data = mozilla::MakeUnique<wchar_t[]>(numBytes / sizeof(wchar_t));
- status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr,
- nullptr, (LPBYTE)data.get(), &numBytes);
- }
- if (!status) {
- // For each token, split up the filename components and then check the
- // name of the file.
- const wchar_t kDelimiters[] = L", ";
- wchar_t* tokenContext = nullptr;
- wchar_t* token = wcstok_s(data.get(), kDelimiters, &tokenContext);
- while (token) {
- wchar_t fname[_MAX_FNAME] = {0};
- if (!_wsplitpath_s(token, nullptr, 0, nullptr, 0,
- fname, mozilla::ArrayLength(fname),
- nullptr, 0)) {
- // nvinit.dll is responsible for bootstrapping the DLL injection, so
- // that is the library that we check for here
- const wchar_t kNvInitName[] = L"nvinit";
- if (!_wcsnicmp(fname, kNvInitName,
- mozilla::ArrayLength(kNvInitName))) {
- return false;
- }
- }
- token = wcstok_s(nullptr, kDelimiters, &tokenContext);
- }
- }
- }
- return true;
- }
-
- bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc)
- {
- if (!mModule) {
- return false;
- }
-
- if (!IsCompatible()) {
-#if defined(MOZILLA_INTERNAL_API)
- NS_WARNING("NOP space patching is unavailable for compatibility reasons");
-#endif
- return false;
- }
-
- MOZ_RELEASE_ASSERT(mPatchedFnsLen < maxPatchedFns, "No room for the hook");
-
- byteptr_t fn = reinterpret_cast<byteptr_t>(GetProcAddress(mModule, aName));
- if (!fn) {
- //printf ("GetProcAddress failed\n");
- return false;
- }
-
- fn = ResolveRedirectedAddress(fn);
-
- // Ensure we can read and write starting at fn - 5 (for the long jmp we're
- // going to write) and ending at fn + 2 (for the short jmp up to the long
- // jmp). These bytes may span two pages with different protection.
- AutoVirtualProtect protectBefore(fn - 5, 5, PAGE_EXECUTE_READWRITE);
- AutoVirtualProtect protectAfter(fn, 2, PAGE_EXECUTE_READWRITE);
- if (!protectBefore.Protect() || !protectAfter.Protect()) {
- return false;
- }
-
- bool rv = WriteHook(fn, aHookDest, aOrigFunc);
-
- if (rv) {
- mPatchedFns[mPatchedFnsLen] = fn;
- mPatchedFnsLen++;
- }
-
- return rv;
- }
-
- bool WriteHook(byteptr_t aFn, intptr_t aHookDest, void** aOrigFunc)
- {
- // Check that the 5 bytes before aFn are NOP's or INT 3's,
- // and that the 2 bytes after aFn are mov(edi, edi).
- //
- // It's safe to read aFn[-5] because we set it to PAGE_EXECUTE_READWRITE
- // before calling WriteHook.
-
- for (int i = -5; i <= -1; i++) {
- if (aFn[i] != 0x90 && aFn[i] != 0xcc) { // nop or int 3
- return false;
- }
- }
-
- // mov edi, edi. Yes, there are two ways to encode the same thing:
- //
- // 0x89ff == mov r/m, r
- // 0x8bff == mov r, r/m
- //
- // where "r" is register and "r/m" is register or memory. Windows seems to
- // use 8bff; I include 89ff out of paranoia.
- if ((aFn[0] != 0x8b && aFn[0] != 0x89) || aFn[1] != 0xff) {
- return false;
- }
-
- // Write a long jump into the space above the function.
- aFn[-5] = 0xe9; // jmp
- *((intptr_t*)(aFn - 4)) = aHookDest - (uintptr_t)(aFn); // target displacement
-
- // Set aOrigFunc here, because after this point, aHookDest might be called,
- // and aHookDest might use the aOrigFunc pointer.
- *aOrigFunc = aFn + 2;
-
- // Short jump up into our long jump.
- *((uint16_t*)(aFn)) = 0xf9eb; // jmp $-5
-
- // I think this routine is safe without this, but it can't hurt.
- FlushInstructionCache(GetCurrentProcess(),
- /* ignored */ nullptr,
- /* ignored */ 0);
-
- return true;
- }
-
-private:
- static byteptr_t ResolveRedirectedAddress(const byteptr_t aOriginalFunction)
- {
- // If function entry is jmp rel8 stub to the internal implementation, we
- // resolve redirected address from the jump target.
- if (aOriginalFunction[0] == 0xeb) {
- int8_t offset = (int8_t)(aOriginalFunction[1]);
- if (offset <= 0) {
- // Bail out for negative offset: probably already patched by some
- // third-party code.
- return aOriginalFunction;
- }
-
- for (int8_t i = 0; i < offset; i++) {
- if (aOriginalFunction[2 + i] != 0x90) {
- // Bail out on insufficient nop space.
- return aOriginalFunction;
- }
- }
-
- return aOriginalFunction + 2 + offset;
- }
-
- // If function entry is jmp [disp32] such as used by kernel32,
- // we resolve redirected address from import table.
- if (aOriginalFunction[0] == 0xff && aOriginalFunction[1] == 0x25) {
- return (byteptr_t)(**((uint32_t**) (aOriginalFunction + 2)));
- }
-
- return aOriginalFunction;
- }
-#else
- void Init(const char* aModuleName)
- {
- // Not implemented except on x86-32.
- }
-
- bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc)
- {
- // Not implemented except on x86-32.
- return false;
- }
-#endif
-};
-
-class WindowsDllDetourPatcher
-{
- typedef unsigned char* byteptr_t;
-public:
- WindowsDllDetourPatcher()
- : mModule(0), mHookPage(0), mMaxHooks(0), mCurHooks(0)
+ template <typename... Args>
+ explicit WindowsDllDetourPatcher(Args... aArgs)
+ : WindowsDllPatcherBase<VMPolicy>(mozilla::Forward<Args>(aArgs)...)
{
}
~WindowsDllDetourPatcher()
{
- int i;
- byteptr_t p;
- for (i = 0, p = mHookPage; i < mCurHooks; i++, p += kHookSize) {
-#if defined(_M_IX86)
- size_t nBytes = 1 + sizeof(intptr_t);
-#elif defined(_M_X64)
- size_t nBytes = 2 + sizeof(intptr_t);
-#else
-#error "Unknown processor type"
-#endif
- byteptr_t origBytes = (byteptr_t)DecodePointer(*((byteptr_t*)p));
+ Clear();
+ }
+
+ WindowsDllDetourPatcher(const WindowsDllDetourPatcher&) = delete;
+ WindowsDllDetourPatcher(WindowsDllDetourPatcher&&) = delete;
+ WindowsDllDetourPatcher& operator=(const WindowsDllDetourPatcher&) = delete;
+ WindowsDllDetourPatcher& operator=(WindowsDllDetourPatcher&&) = delete;
- // ensure we can modify the original code
- AutoVirtualProtect protect(origBytes, nBytes, PAGE_EXECUTE_READWRITE);
- if (!protect.Protect()) {
- continue;
- }
+ void Clear()
+ {
+ if (!mVMPolicy.ShouldUnhookUponDestruction()) {
+ return;
+ }
- // Remove the hook by making the original function jump directly
- // in the trampoline.
- intptr_t dest = (intptr_t)(p + sizeof(void*));
#if defined(_M_IX86)
- // Ensure the JMP from CreateTrampoline is where we expect it to be.
- if (origBytes[0] != 0xE9)
- continue;
- *((intptr_t*)(origBytes + 1)) =
- dest - (intptr_t)(origBytes + 5); // target displacement
+ size_t nBytes = 1 + sizeof(intptr_t);
#elif defined(_M_X64)
- // Ensure the MOV R11 from CreateTrampoline is where we expect it to be.
- if (origBytes[0] != 0x49 || origBytes[1] != 0xBB)
- continue;
- *((intptr_t*)(origBytes + 2)) = dest;
+ size_t nBytes = 2 + sizeof(intptr_t);
#else
#error "Unknown processor type"
#endif
+
+ const auto& tramps = mVMPolicy.Items();
+ for (auto&& tramp : tramps) {
+ // First we read the pointer to the interceptor instance.
+ Maybe<uintptr_t> instance = tramp.ReadEncodedPointer();
+ if (!instance) {
+ continue;
+ }
+
+ if (instance.value() != reinterpret_cast<uintptr_t>(this)) {
+ // tramp does not belong to this interceptor instance.
+ continue;
+ }
+
+ auto clearInstance = MakeScopeExit([&tramp]() -> void {
+ // Clear the instance pointer so that no future instances with the same
+ // |this| pointer will attempt to reset its hook.
+ tramp.Rewind();
+ tramp.WriteEncodedPointer(nullptr);
+ });
+
+ // Now we read the pointer to the intercepted function.
+ Maybe<uintptr_t> interceptedFn = tramp.ReadEncodedPointer();
+ if (!interceptedFn) {
+ continue;
+ }
+
+ WritableTargetFunction<MMPolicyT> origBytes(mVMPolicy,
+ interceptedFn.value(), nBytes);
+ if (!origBytes) {
+ continue;
+ }
+
+ Maybe<uint8_t> maybeOpcode1 = origBytes.ReadByte();
+ if (!maybeOpcode1) {
+ continue;
+ }
+
+ uint8_t opcode1 = maybeOpcode1.value();
+
+#if defined(_M_IX86)
+ // Ensure the JMP from CreateTrampoline is where we expect it to be.
+ if (opcode1 != 0xE9) {
+ continue;
+ }
+
+ intptr_t startOfTrampInstructions =
+ static_cast<intptr_t>(tramp.GetCurrentRemoteAddress());
+
+ origBytes.WriteDisp32(startOfTrampInstructions);
+ if (!origBytes) {
+ continue;
+ }
+#elif defined(_M_X64)
+ // Ensure the MOV R11 from CreateTrampoline is where we expect it to be.
+ if (opcode1 != 0x49) {
+ continue;
+ }
+
+ Maybe<uint8_t> maybeOpcode2 = origBytes.ReadByte();
+ if (!maybeOpcode2) {
+ continue;
+ }
+
+ uint8_t opcode2 = maybeOpcode2.value();
+ if (opcode2 != 0xBB) {
+ continue;
+ }
+
+ origBytes.WritePointer(tramp.GetCurrentRemoteAddress());
+ if (!origBytes) {
+ continue;
+ }
+#else
+#error "Unknown processor type"
+#endif
+
+ origBytes.Commit();
}
+
+ mVMPolicy.Clear();
}
- void Init(const char* aModuleName, int aNumHooks = 0)
+ void Init(int aNumHooks = 0)
{
- if (mModule) {
- return;
- }
-
- mModule = LoadLibraryExA(aModuleName, nullptr, 0);
- if (!mModule) {
- //printf("LoadLibraryEx for '%s' failed\n", aModuleName);
+ if (Initialized()) {
return;
}
- int hooksPerPage = 4096 / kHookSize;
if (aNumHooks == 0) {
- aNumHooks = hooksPerPage;
+ // Win32 allocates VM addresses at a 64KiB granularity, so by default we
+ // might as well utilize that entire 64KiB reservation instead of
+ // artifically constraining ourselves to the page size.
+ aNumHooks = mVMPolicy.GetAllocGranularity() / kHookSize;
}
- mMaxHooks = aNumHooks + (hooksPerPage % aNumHooks);
-
- mHookPage = (byteptr_t)VirtualAllocEx(GetCurrentProcess(), nullptr,
- mMaxHooks * kHookSize,
- MEM_COMMIT | MEM_RESERVE,
- PAGE_EXECUTE_READ);
- if (!mHookPage) {
- mModule = 0;
- return;
- }
+ mVMPolicy.Reserve(aNumHooks);
}
- bool Initialized() { return !!mModule; }
-
- bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc)
+ bool Initialized() const
{
- if (!mModule) {
- return false;
- }
+ return !!mVMPolicy;
+ }
- void* pAddr = (void*)GetProcAddress(mModule, aName);
- if (!pAddr) {
- //printf ("GetProcAddress failed\n");
- return false;
- }
+ bool AddHook(FARPROC aTargetFn, intptr_t aHookDest, void** aOrigFunc)
+ {
+ ReadOnlyTargetFunction<MMPolicyT> target(ResolveRedirectedAddress(aTargetFn));
- pAddr = ResolveRedirectedAddress((byteptr_t)pAddr);
-
- CreateTrampoline(pAddr, aHookDest, aOrigFunc);
+ CreateTrampoline(target, aHookDest, aOrigFunc);
if (!*aOrigFunc) {
- //printf ("CreateTrampoline failed\n");
return false;
}
return true;
}
protected:
const static int kPageSize = 4096;
const static int kHookSize = 128;
- HMODULE mModule;
- byteptr_t mHookPage;
- int mMaxHooks;
- int mCurHooks;
-
// rex bits
static const BYTE kMaskHighNibble = 0xF0;
static const BYTE kRexOpcode = 0x40;
static const BYTE kMaskRexW = 0x08;
static const BYTE kMaskRexR = 0x04;
static const BYTE kMaskRexX = 0x02;
static const BYTE kMaskRexB = 0x01;
@@ -533,22 +219,19 @@ protected:
/**
* Returns the number of bytes taken by the ModR/M byte, SIB (if present)
* and the instruction's operand. In special cases, the special MODRM codes
* above are returned.
* aModRm points to the ModR/M byte of the instruction.
* On return, aSubOpcode (if present) is filled with the subopcode/register
* code found in the ModR/M byte.
*/
- int CountModRmSib(const BYTE *aModRm, BYTE* aSubOpcode = nullptr)
+ int CountModRmSib(const ReadOnlyTargetFunction<MMPolicyT>& aModRm,
+ BYTE* aSubOpcode = nullptr)
{
- if (!aModRm) {
- MOZ_ASSERT(aModRm, "Missing ModRM byte");
- return kModUnknown;
- }
int numBytes = 1; // Start with 1 for mod r/m byte itself
switch (*aModRm & kMaskMod) {
case kModReg:
return numBytes;
case kModDisp8:
numBytes += 1;
break;
case kModDisp32:
@@ -581,90 +264,74 @@ protected:
}
if (aSubOpcode) {
*aSubOpcode = (*aModRm & kMaskReg) >> kRegFieldShift;
}
return numBytes;
}
#if defined(_M_X64)
- // To patch for JMP and JE
-
- enum JumpType {
+ enum class JumpType
+ {
Je,
Jne,
Jmp,
Call
};
- struct JumpPatch {
- JumpPatch()
- : mHookOffset(0), mJumpAddress(0), mType(JumpType::Jmp)
- {
- }
-
- JumpPatch(size_t aOffset, intptr_t aAddress, JumpType aType = JumpType::Jmp)
- : mHookOffset(aOffset), mJumpAddress(aAddress), mType(aType)
- {
+ static bool
+ GenerateJump(Trampoline<MMPolicyT>& aTramp, uintptr_t aAbsTargetAddress,
+ const JumpType aType)
+ {
+ // Near call, absolute indirect, address given in r/m32
+ if (aType == JumpType::Call) {
+ // CALL [RIP+0]
+ aTramp.WriteByte(0xff);
+ aTramp.WriteByte(0x15);
+ // The offset to jump destination -- 2 bytes after the current position.
+ aTramp.WriteInteger(2);
+ aTramp.WriteByte(0xeb); // JMP + 8 (jump over target address)
+ aTramp.WriteByte(8);
+ aTramp.WritePointer(aAbsTargetAddress);
+ return !!aTramp;
}
- size_t GenerateJump(uint8_t* aCode)
- {
- size_t offset = mHookOffset;
- if (mType == JumpType::Je) {
- // JNE RIP+14
- aCode[offset] = 0x75;
- aCode[offset + 1] = 14;
- offset += 2;
- } else if (mType == JumpType::Jne) {
- // JE RIP+14
- aCode[offset] = 0x74;
- aCode[offset + 1] = 14;
- offset += 2;
- }
-
- // Near call/jmp, absolute indirect, address given in r/m32
- if (mType == JumpType::Call) {
- // CALL [RIP+0]
- aCode[offset] = 0xff;
- aCode[offset + 1] = 0x15;
- // The offset to jump destination -- ie it is placed 2 bytes after the offset.
- *reinterpret_cast<int32_t*>(aCode + offset + 2) = 2;
- aCode[offset + 2 + 4] = 0xeb; // JMP +8 (jump over mJumpAddress)
- aCode[offset + 2 + 4 + 1] = 8;
- *reinterpret_cast<int64_t*>(aCode + offset + 2 + 4 + 2) = mJumpAddress;
- return offset + 2 + 4 + 2 + 8;
- } else {
- // JMP [RIP+0]
- aCode[offset] = 0xff;
- aCode[offset + 1] = 0x25;
- // The offset to jump destination is 0
- *reinterpret_cast<int32_t*>(aCode + offset + 2) = 0;
- *reinterpret_cast<int64_t*>(aCode + offset + 2 + 4) = mJumpAddress;
- return offset + 2 + 4 + 8;
- }
+ if (aType == JumpType::Je) {
+ // JNE RIP+14
+ aTramp.WriteByte(0x75);
+ aTramp.WriteByte(14);
+ } else if (aType == JumpType::Jne) {
+ // JE RIP+14
+ aTramp.WriteByte(0x74);
+ aTramp.WriteByte(14);
}
- size_t mHookOffset;
- intptr_t mJumpAddress;
- JumpType mType;
- };
+ // Near jmp, absolute indirect, address given in r/m32
+ // JMP [RIP+0]
+ aTramp.WriteByte(0xff);
+ aTramp.WriteByte(0x25);
+ // The offset to jump destination is 0
+ aTramp.WriteInteger(0);
+ aTramp.WritePointer(aAbsTargetAddress);
+ return !!aTramp;
+ }
#endif
enum ePrefixGroupBits
{
eNoPrefixes = 0,
ePrefixGroup1 = (1 << 0),
ePrefixGroup2 = (1 << 1),
ePrefixGroup3 = (1 << 2),
ePrefixGroup4 = (1 << 3)
};
- int CountPrefixBytes(byteptr_t aBytes, const int aBytesIndex,
+ int CountPrefixBytes(const ReadOnlyTargetFunction<MMPolicyT>& aBytes,
+ const int aBytesIndex,
unsigned char* aOutGroupBits)
{
unsigned char& groupBits = *aOutGroupBits;
groupBits = eNoPrefixes;
int index = aBytesIndex;
while (true) {
switch (aBytes[index]) {
// Group 1
@@ -720,387 +387,398 @@ protected:
BYTE BuildModRmByte(BYTE aModBits, BYTE aReg, BYTE aRm)
{
MOZ_ASSERT((aRm & kMaskRm) == aRm);
MOZ_ASSERT((aModBits & kMaskMod) == aModBits);
MOZ_ASSERT(((aReg << kRegFieldShift) & kMaskReg) == (aReg << kRegFieldShift));
return aModBits | (aReg << kRegFieldShift) | aRm;
}
- void CreateTrampoline(void* aOrigFunction, intptr_t aDest, void** aOutTramp)
+ void CreateTrampoline(ReadOnlyTargetFunction<MMPolicyT>& origBytes,
+ intptr_t aDest, void** aOutTramp)
{
*aOutTramp = nullptr;
- AutoVirtualProtect protectHookPage(mHookPage, mMaxHooks * kHookSize,
- PAGE_EXECUTE_READWRITE);
- if (!protectHookPage.Protect()) {
+ Trampoline<MMPolicyT> tramp(mVMPolicy.GetNextTrampoline());
+ if (!tramp) {
return;
}
- byteptr_t tramp = FindTrampolineSpace();
+ // The beginning of the trampoline contains two pointer-width slots:
+ // [0]: |this|, so that we know whether the trampoline belongs to us;
+ // [1]: Pointer to original function, so that we can reset the hook upon
+ // destruction.
+ tramp.WriteEncodedPointer(this);
if (!tramp) {
return;
}
- // We keep the address of the original function in the first bytes of
- // the trampoline buffer
- *((void**)tramp) = EncodePointer(aOrigFunction);
- tramp += sizeof(void*);
+ auto clearInstanceOnFailure = MakeScopeExit([aOutTramp, &tramp]() -> void {
+ // *aOutTramp is not set until CreateTrampoline has completed successfully,
+ // so we can use that to check for success.
+ if (*aOutTramp) {
+ return;
+ }
- byteptr_t origBytes = (byteptr_t)aOrigFunction;
+ // Clear the instance pointer so that we don't try to reset a nonexistent
+ // hook.
+ tramp.Rewind();
+ tramp.WriteEncodedPointer(nullptr);
+ });
- // # of bytes of the original function that we can overwrite.
- int nOrigBytes = 0;
+ tramp.WritePointer(origBytes.AsEncodedPtr());
+ if (!tramp) {
+ return;
+ }
+
+ tramp.StartExecutableCode();
#if defined(_M_IX86)
int pJmp32 = -1;
- while (nOrigBytes < 5) {
+ while (origBytes.GetOffset() < 5) {
// Understand some simple instructions that might be found in a
// prologue; we might need to extend this as necessary.
//
// Note! If we ever need to understand jump instructions, we'll
// need to rewrite the displacement argument.
unsigned char prefixGroups;
- int numPrefixBytes = CountPrefixBytes(origBytes, nOrigBytes, &prefixGroups);
+ int numPrefixBytes = CountPrefixBytes(origBytes, origBytes.GetOffset(), &prefixGroups);
if (numPrefixBytes < 0 || (prefixGroups & (ePrefixGroup3 | ePrefixGroup4))) {
// Either the prefix sequence was bad, or there are prefixes that
// we don't currently support (groups 3 and 4)
MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
return;
}
- nOrigBytes += numPrefixBytes;
- if (origBytes[nOrigBytes] >= 0x88 &&
- origBytes[nOrigBytes] <= 0x8B) {
+
+ origBytes += numPrefixBytes;
+ if (*origBytes >= 0x88 && *origBytes <= 0x8B) {
// various MOVs
- ++nOrigBytes;
- int len = CountModRmSib(origBytes + nOrigBytes);
+ ++origBytes;
+ int len = CountModRmSib(origBytes);
if (len < 0) {
MOZ_ASSERT_UNREACHABLE("Unrecognized MOV opcode sequence");
return;
}
- nOrigBytes += len;
- } else if (origBytes[nOrigBytes] == 0x0f &&
- (origBytes[nOrigBytes + 1] == 0x10 ||
- origBytes[nOrigBytes + 1] == 0x11)) {
+ origBytes += len;
+ } else if (*origBytes == 0x0f &&
+ (origBytes[1] == 0x10 ||
+ origBytes[1] == 0x11)) {
// SSE: movups xmm, xmm/m128
// movups xmm/m128, xmm
- nOrigBytes += 2;
- int len = CountModRmSib(origBytes + nOrigBytes);
+ origBytes += 2;
+ int len = CountModRmSib(origBytes);
if (len < 0) {
MOZ_ASSERT_UNREACHABLE("Unrecognized MOV opcode sequence");
return;
}
- nOrigBytes += len;
- } else if (origBytes[nOrigBytes] == 0xA1) {
+ origBytes += len;
+ } else if (*origBytes == 0xA1) {
// MOV eax, [seg:offset]
- nOrigBytes += 5;
- } else if (origBytes[nOrigBytes] == 0xB8) {
+ origBytes += 5;
+ } else if (*origBytes == 0xB8) {
// MOV 0xB8: http://ref.x86asm.net/coder32.html#xB8
- nOrigBytes += 5;
- } else if (origBytes[nOrigBytes] == 0x33 &&
- (origBytes[nOrigBytes+1] & kMaskMod) == kModReg) {
+ origBytes += 5;
+ } else if (*origBytes == 0x33 &&
+ (origBytes[1] & kMaskMod) == kModReg) {
// XOR r32, r32
- nOrigBytes += 2;
- } else if ((origBytes[nOrigBytes] & 0xf8) == 0x40) {
+ origBytes += 2;
+ } else if ((*origBytes & 0xf8) == 0x40) {
// INC r32
- nOrigBytes += 1;
- } else if (origBytes[nOrigBytes] == 0x83) {
+ origBytes += 1;
+ } else if (*origBytes == 0x83) {
// ADD|ODR|ADC|SBB|AND|SUB|XOR|CMP r/m, imm8
- unsigned char b = origBytes[nOrigBytes + 1];
+ unsigned char b = origBytes[1];
if ((b & 0xc0) == 0xc0) {
// ADD|ODR|ADC|SBB|AND|SUB|XOR|CMP r, imm8
- nOrigBytes += 3;
+ origBytes += 3;
} else {
// bail
MOZ_ASSERT_UNREACHABLE("Unrecognized bit opcode sequence");
return;
}
- } else if (origBytes[nOrigBytes] == 0x68) {
+ } else if (*origBytes == 0x68) {
// PUSH with 4-byte operand
- nOrigBytes += 5;
- } else if ((origBytes[nOrigBytes] & 0xf0) == 0x50) {
+ origBytes += 5;
+ } else if ((*origBytes & 0xf0) == 0x50) {
// 1-byte PUSH/POP
- nOrigBytes++;
- } else if (origBytes[nOrigBytes] == 0x6A) {
+ ++origBytes;
+ } else if (*origBytes == 0x6A) {
// PUSH imm8
- nOrigBytes += 2;
- } else if (origBytes[nOrigBytes] == 0xe9) {
- pJmp32 = nOrigBytes;
+ origBytes += 2;
+ } else if (*origBytes == 0xe9) {
+ pJmp32 = origBytes.GetOffset();
// jmp 32bit offset
- nOrigBytes += 5;
- } else if (origBytes[nOrigBytes] == 0xff &&
- origBytes[nOrigBytes + 1] == 0x25) {
+ origBytes += 5;
+ } else if (*origBytes == 0xff &&
+ origBytes[1] == 0x25) {
// jmp [disp32]
- nOrigBytes += 6;
- } else if (origBytes[nOrigBytes] == 0xc2) {
+ origBytes += 6;
+ } else if (*origBytes == 0xc2) {
// ret imm16. We can't handle this but it happens. We don't ASSERT but we do fail to hook.
#if defined(MOZILLA_INTERNAL_API)
NS_WARNING("Cannot hook method -- RET opcode found");
#endif
return;
} else {
- //printf ("Unknown x86 instruction byte 0x%02x, aborting trampoline\n", origBytes[nOrigBytes]);
+ //printf ("Unknown x86 instruction byte 0x%02x, aborting trampoline\n", *origBytes);
MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
return;
}
}
// The trampoline is a copy of the instructions that we just traced,
// followed by a jump that we add below.
- memcpy(tramp, aOrigFunction, nOrigBytes);
+ tramp.CopyFrom(origBytes.GetBaseAddress(), origBytes.GetOffset());
+ if (!tramp) {
+ return;
+ }
#elif defined(_M_X64)
- // The number of bytes used by the trampoline.
- int nTrampBytes = 0;
bool foundJmp = false;
- while (nOrigBytes < 13) {
+ while (origBytes.GetOffset() < 13) {
// If we found JMP 32bit offset, we require that the next bytes must
// be NOP or INT3. There is no reason to copy them.
// TODO: This used to trigger for Je as well. Now that I allow
// instructions after CALL and JE, I don't think I need that.
// The only real value of this condition is that if code follows a JMP
// then its _probably_ the target of a JMP somewhere else and we
// will be overwriting it, which would be tragic. This seems
// highly unlikely.
if (foundJmp) {
- if (origBytes[nOrigBytes] == 0x90 || origBytes[nOrigBytes] == 0xcc) {
- nOrigBytes++;
+ if (*origBytes == 0x90 || *origBytes == 0xcc) {
+ ++origBytes;
continue;
}
MOZ_ASSERT_UNREACHABLE("Opcode sequence includes commands after JMP");
return;
}
- if (origBytes[nOrigBytes] == 0x0f) {
+ if (*origBytes == 0x0f) {
COPY_CODES(1);
- if (origBytes[nOrigBytes] == 0x1f) {
+ if (*origBytes == 0x1f) {
// nop (multibyte)
COPY_CODES(1);
- if ((origBytes[nOrigBytes] & 0xc0) == 0x40 &&
- (origBytes[nOrigBytes] & 0x7) == 0x04) {
+ if ((*origBytes & 0xc0) == 0x40 &&
+ (*origBytes & 0x7) == 0x04) {
COPY_CODES(3);
} else {
MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
return;
}
- } else if (origBytes[nOrigBytes] == 0x05) {
+ } else if (*origBytes == 0x05) {
// syscall
COPY_CODES(1);
- } else if (origBytes[nOrigBytes] == 0x10 ||
- origBytes[nOrigBytes] == 0x11) {
+ } else if (*origBytes == 0x10 ||
+ *origBytes == 0x11) {
// SSE: movups xmm, xmm/m128
// movups xmm/m128, xmm
COPY_CODES(1);
- int nModRmSibBytes = CountModRmSib(&origBytes[nOrigBytes]);
+ int nModRmSibBytes = CountModRmSib(origBytes);
if (nModRmSibBytes < 0) {
MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
return;
} else {
COPY_CODES(nModRmSibBytes);
}
- } else if (origBytes[nOrigBytes] == 0x84) {
+ } else if (*origBytes == 0x84) {
// je rel32
- JumpPatch jump(nTrampBytes - 1, // overwrite the 0x0f we copied above
- (intptr_t)(origBytes + nOrigBytes + 5 +
- *(reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 1))),
- JumpType::Je);
- nTrampBytes = jump.GenerateJump(tramp);
- nOrigBytes += 5;
+ ++origBytes;
+ --tramp; // overwrite the 0x0f we copied above
+
+ if (!GenerateJump(tramp,
+ origBytes.ReadDisp32AsAbsolute(),
+ JumpType::Je)) {
+ return;
+ }
} else {
MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
return;
}
- } else if (origBytes[nOrigBytes] == 0x40 ||
- origBytes[nOrigBytes] == 0x41) {
+ } else if (*origBytes >= 0x88 && *origBytes <= 0x8B) {
+ // various 32-bit MOVs
+ COPY_CODES(1);
+ int len = CountModRmSib(origBytes);
+ if (len < 0) {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized MOV opcode sequence");
+ return;
+ }
+ COPY_CODES(len);
+ } else if (*origBytes == 0x40 ||
+ *origBytes == 0x41) {
// Plain REX or REX.B
COPY_CODES(1);
- if ((origBytes[nOrigBytes] & 0xf0) == 0x50) {
+ if ((*origBytes & 0xf0) == 0x50) {
// push/pop with Rx register
COPY_CODES(1);
- } else if (origBytes[nOrigBytes] >= 0xb8 && origBytes[nOrigBytes] <= 0xbf) {
+ } else if (*origBytes >= 0xb8 && *origBytes <= 0xbf) {
// mov r32, imm32
COPY_CODES(5);
} else {
MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
return;
}
- } else if (origBytes[nOrigBytes] == 0x44) {
+ } else if (*origBytes == 0x44) {
// REX.R
COPY_CODES(1);
// TODO: Combine with the "0x89" case below in the REX.W section
- if (origBytes[nOrigBytes] == 0x89) {
+ if (*origBytes == 0x89) {
// mov r/m32, r32
COPY_CODES(1);
- int len = CountModRmSib(origBytes + nOrigBytes);
+ int len = CountModRmSib(origBytes);
if (len < 0) {
MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
return;
}
COPY_CODES(len);
} else {
MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
return;
}
- } else if (origBytes[nOrigBytes] == 0x45) {
+ } else if (*origBytes == 0x45) {
// REX.R & REX.B
COPY_CODES(1);
- if (origBytes[nOrigBytes] == 0x33) {
+ if (*origBytes == 0x33) {
// xor r32, r32
COPY_CODES(2);
} else {
MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
return;
}
- } else if ((origBytes[nOrigBytes] & 0xfa) == 0x48) {
+ } else if ((*origBytes & 0xfa) == 0x48) {
// REX.W | REX.WR | REX.WRB | REX.WB
COPY_CODES(1);
- if (origBytes[nOrigBytes] == 0x81 &&
- (origBytes[nOrigBytes + 1] & 0xf8) == 0xe8) {
+ if (*origBytes == 0x81 &&
+ (origBytes[1] & 0xf8) == 0xe8) {
// sub r, dword
COPY_CODES(6);
- } else if (origBytes[nOrigBytes] == 0x83 &&
- (origBytes[nOrigBytes + 1] & 0xf8) == 0xe8) {
+ } else if (*origBytes == 0x83 &&
+ (origBytes[1] & 0xf8) == 0xe8) {
// sub r, byte
COPY_CODES(3);
- } else if (origBytes[nOrigBytes] == 0x83 &&
- (origBytes[nOrigBytes + 1] & (kMaskMod|kMaskReg)) == kModReg) {
+ } else if (*origBytes == 0x83 &&
+ (origBytes[1] & (kMaskMod|kMaskReg)) == kModReg) {
// add r, byte
COPY_CODES(3);
- } else if (origBytes[nOrigBytes] == 0x83 &&
- (origBytes[nOrigBytes + 1] & 0xf8) == 0x60) {
+ } else if (*origBytes == 0x83 &&
+ (origBytes[1] & 0xf8) == 0x60) {
// and [r+d], imm8
COPY_CODES(5);
- } else if (origBytes[nOrigBytes] == 0x2b &&
- (origBytes[nOrigBytes + 1] & kMaskMod) == kModReg) {
+ } else if (*origBytes == 0x2b &&
+ (origBytes[1] & kMaskMod) == kModReg) {
// sub r64, r64
COPY_CODES(2);
- } else if (origBytes[nOrigBytes] == 0x85) {
+ } else if (*origBytes == 0x85) {
// 85 /r => TEST r/m32, r32
- if ((origBytes[nOrigBytes + 1] & 0xc0) == 0xc0) {
+ if ((origBytes[1] & 0xc0) == 0xc0) {
COPY_CODES(2);
} else {
MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
return;
}
- } else if ((origBytes[nOrigBytes] & 0xfd) == 0x89) {
+ } else if ((*origBytes & 0xfd) == 0x89) {
// MOV r/m64, r64 | MOV r64, r/m64
BYTE reg;
- int len = CountModRmSib(origBytes + nOrigBytes + 1, ®);
+ int len = CountModRmSib(origBytes + 1, ®);
if (len < 0) {
MOZ_ASSERT(len == kModOperand64);
if (len != kModOperand64) {
return;
}
- nOrigBytes += 2; // skip the MOV and MOD R/M bytes
+ origBytes += 2; // skip the MOV and MOD R/M bytes
// The instruction MOVs 64-bit data from a RIP-relative memory
// address (determined with a 32-bit offset from RIP) into a
// 64-bit register.
- int64_t* absAddr =
- reinterpret_cast<int64_t*>(origBytes + nOrigBytes + 4 +
- *reinterpret_cast<int32_t*>(origBytes + nOrigBytes));
- nOrigBytes += 4;
+ uintptr_t absAddr = origBytes.ReadDisp32AsAbsolute();
if (reg == kRegAx) {
// Destination is RAX. Encode instruction as MOVABS with a
// 64-bit absolute address as its immediate operand.
- tramp[nTrampBytes] = 0xa1;
- ++nTrampBytes;
- int64_t** trampOperandPtr = reinterpret_cast<int64_t**>(tramp + nTrampBytes);
- *trampOperandPtr = absAddr;
- nTrampBytes += 8;
+ tramp.WriteByte(0xa1);
+ tramp.WritePointer(absAddr);
} else {
// The MOV must be done in two steps. First, we MOVABS the
// absolute 64-bit address into our target register.
// Then, we MOV from that address into the register
// using register-indirect addressing.
- tramp[nTrampBytes] = 0xb8 + reg;
- ++nTrampBytes;
- int64_t** trampOperandPtr = reinterpret_cast<int64_t**>(tramp + nTrampBytes);
- *trampOperandPtr = absAddr;
- nTrampBytes += 8;
- tramp[nTrampBytes] = 0x48;
- tramp[nTrampBytes+1] = 0x8b;
- tramp[nTrampBytes+2] = BuildModRmByte(kModNoRegDisp, reg, reg);
- nTrampBytes += 3;
+ tramp.WriteByte(0xb8 + reg);
+ tramp.WritePointer(absAddr);
+ tramp.WriteByte(0x48);
+ tramp.WriteByte(0x8b);
+ tramp.WriteByte(BuildModRmByte(kModNoRegDisp, reg, reg));
}
} else {
COPY_CODES(len+1);
}
- } else if (origBytes[nOrigBytes] == 0xc7) {
+ } else if (*origBytes == 0xc7) {
// MOV r/m64, imm32
- if (origBytes[nOrigBytes + 1] == 0x44) {
+ if (origBytes[1] == 0x44) {
// MOV [r64+disp8], imm32
// ModR/W + SIB + disp8 + imm32
COPY_CODES(8);
} else {
MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
return;
}
- } else if (origBytes[nOrigBytes] == 0xff) {
+ } else if (*origBytes == 0xff) {
// JMP /4
- if ((origBytes[nOrigBytes + 1] & 0xc0) == 0x0 &&
- (origBytes[nOrigBytes + 1] & 0x07) == 0x5) {
- // [rip+disp32]
- // convert JMP 32bit offset to JMP 64bit direct
- JumpPatch jump(nTrampBytes - 1, // overwrite the REX.W/REX.WR we copied above
- *reinterpret_cast<intptr_t*>(origBytes + nOrigBytes + 6 +
- *reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 2)),
- JumpType::Jmp);
- nTrampBytes = jump.GenerateJump(tramp);
- nOrigBytes += 6;
+ if ((origBytes[1] & 0xc0) == 0x0 &&
+ (origBytes[1] & 0x07) == 0x5) {
+ origBytes += 2;
+ --tramp; // overwrite the REX.W/REX.RW we copied above
+
+ if (!GenerateJump(tramp, origBytes.ChasePointerFromDisp(),
+ JumpType::Jmp)) {
+ return;
+ }
+
foundJmp = true;
} else {
// not support yet!
MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
return;
}
- } else if (origBytes[nOrigBytes] == 0x8d) {
+ } else if (*origBytes == 0x8d) {
// LEA reg, addr
- if ((origBytes[nOrigBytes + 1] & kMaskMod) == 0x0 &&
- (origBytes[nOrigBytes + 1] & kMaskRm) == 0x5) {
+ if ((origBytes[1] & kMaskMod) == 0x0 &&
+ (origBytes[1] & kMaskRm) == 0x5) {
// [rip+disp32]
// convert 32bit offset to 64bit direct and convert instruction
// to a simple 64-bit mov
- BYTE reg = (origBytes[nOrigBytes + 1] & kMaskReg) >> kRegFieldShift;
- intptr_t absAddr =
- reinterpret_cast<intptr_t>(origBytes + nOrigBytes + 6 +
- *reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 2));
- nOrigBytes += 6;
- tramp[nTrampBytes] = 0xb8 + reg; // mov
- ++nTrampBytes;
- intptr_t* trampOperandPtr = reinterpret_cast<intptr_t*>(tramp + nTrampBytes);
- *trampOperandPtr = absAddr;
- nTrampBytes += 8;
+ BYTE reg = (origBytes[1] & kMaskReg) >> kRegFieldShift;
+ origBytes += 2;
+ uintptr_t absAddr = origBytes.ReadDisp32AsAbsolute();
+ tramp.WriteByte(0xb8 + reg); // move
+ tramp.WritePointer(absAddr);
} else {
// Above we dealt with RIP-relative instructions. Any other
// operand form can simply be copied.
- int len = CountModRmSib(origBytes + nOrigBytes + 1);
+ int len = CountModRmSib(origBytes + 1);
// We handled the kModOperand64 -- ie RIP-relative -- case above
MOZ_ASSERT(len > 0);
COPY_CODES(len + 1);
}
- } else if (origBytes[nOrigBytes] == 0x63 &&
- (origBytes[nOrigBytes + 1] & kMaskMod) == kModReg) {
+ } else if (*origBytes == 0x63 &&
+ (origBytes[1] & kMaskMod) == kModReg) {
// movsxd r64, r32 (move + sign extend)
COPY_CODES(2);
} else {
// not support yet!
MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
return;
}
- } else if (origBytes[nOrigBytes] == 0x66) {
+ } else if (*origBytes == 0x66) {
// operand override prefix
COPY_CODES(1);
// This is the same as the x86 version
- if (origBytes[nOrigBytes] >= 0x88 && origBytes[nOrigBytes] <= 0x8B) {
+ if (*origBytes >= 0x88 && *origBytes <= 0x8B) {
// various MOVs
- unsigned char b = origBytes[nOrigBytes + 1];
+ unsigned char b = origBytes[1];
if (((b & 0xc0) == 0xc0) ||
(((b & 0xc0) == 0x00) &&
((b & 0x07) != 0x04) && ((b & 0x07) != 0x05))) {
// REG=r, R/M=r or REG=r, R/M=[r]
COPY_CODES(2);
} else if ((b & 0xc0) == 0x40) {
if ((b & 0x07) == 0x04) {
// REG=r, R/M=[SIB + disp8]
@@ -1109,387 +787,241 @@ protected:
// REG=r, R/M=[r + disp8]
COPY_CODES(3);
}
} else {
// complex MOV, bail
MOZ_ASSERT_UNREACHABLE("Unrecognized MOV opcode sequence");
return;
}
- } else if (origBytes[nOrigBytes] == 0x44 &&
- origBytes[nOrigBytes+1] == 0x89) {
+ } else if (*origBytes == 0x44 &&
+ origBytes[1] == 0x89) {
// mov word ptr [reg+disp8], reg
COPY_CODES(2);
- int len = CountModRmSib(origBytes + nOrigBytes);
+ int len = CountModRmSib(origBytes);
if (len < 0) {
// no way to support this yet.
MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
return;
}
COPY_CODES(len);
}
- } else if ((origBytes[nOrigBytes] & 0xf0) == 0x50) {
+ } else if ((*origBytes & 0xf0) == 0x50) {
// 1-byte push/pop
COPY_CODES(1);
- } else if (origBytes[nOrigBytes] == 0x65) {
+ } else if (*origBytes == 0x65) {
// GS prefix
//
// The entry of GetKeyState on Windows 10 has the following code.
// 65 48 8b 04 25 30 00 00 00 mov rax,qword ptr gs:[30h]
// (GS prefix + REX + MOV (0x8b) ...)
- if (origBytes[nOrigBytes + 1] == 0x48 &&
- (origBytes[nOrigBytes + 2] >= 0x88 && origBytes[nOrigBytes + 2] <= 0x8b)) {
+ if (origBytes[1] == 0x48 &&
+ (origBytes[2] >= 0x88 && origBytes[2] <= 0x8b)) {
COPY_CODES(3);
- int len = CountModRmSib(origBytes + nOrigBytes);
+ int len = CountModRmSib(origBytes);
if (len < 0) {
// no way to support this yet.
MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
return;
}
COPY_CODES(len);
} else {
MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
return;
}
- } else if (origBytes[nOrigBytes] == 0x80 &&
- origBytes[nOrigBytes + 1] == 0x3d) {
+ } else if (*origBytes == 0x80 &&
+ origBytes[1] == 0x3d) {
+ origBytes += 2;
+
// cmp byte ptr [rip-relative address], imm8
// We'll compute the absolute address and do the cmp in r11
// push r11 (to save the old value)
- tramp[nTrampBytes] = 0x49;
- ++nTrampBytes;
- tramp[nTrampBytes] = 0x53;
- ++nTrampBytes;
+ tramp.WriteByte(0x49);
+ tramp.WriteByte(0x53);
- byteptr_t absAddr =
- reinterpret_cast<byteptr_t>(origBytes + nOrigBytes + 7 +
- *reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 2));
- nOrigBytes += 6;
+ uintptr_t absAddr = origBytes.ReadDisp32AsAbsolute();
// mov r11, absolute address
- tramp[nTrampBytes] = 0x49;
- ++nTrampBytes;
- tramp[nTrampBytes] = 0xbb;
- ++nTrampBytes;
-
- *reinterpret_cast<byteptr_t*>(tramp + nTrampBytes) = absAddr;
- nTrampBytes += 8;
+ tramp.WriteByte(0x49);
+ tramp.WriteByte(0xbb);
+ tramp.WritePointer(absAddr);
// cmp byte ptr [r11],...
- tramp[nTrampBytes] = 0x41;
- ++nTrampBytes;
- tramp[nTrampBytes] = 0x80;
- ++nTrampBytes;
- tramp[nTrampBytes] = 0x3b;
- ++nTrampBytes;
+ tramp.WriteByte(0x41);
+ tramp.WriteByte(0x80);
+ tramp.WriteByte(0x3b);
// ...imm8
COPY_CODES(1);
// pop r11 (doesn't affect the flags from the cmp)
- tramp[nTrampBytes] = 0x49;
- ++nTrampBytes;
- tramp[nTrampBytes] = 0x5b;
- ++nTrampBytes;
- } else if (origBytes[nOrigBytes] == 0x90) {
+ tramp.WriteByte(0x49);
+ tramp.WriteByte(0x5b);
+ } else if (*origBytes == 0x90) {
// nop
COPY_CODES(1);
- } else if ((origBytes[nOrigBytes] & 0xf8) == 0xb8) {
+ } else if ((*origBytes & 0xf8) == 0xb8) {
// MOV r32, imm32
COPY_CODES(5);
- } else if (origBytes[nOrigBytes] == 0x33) {
+ } else if (*origBytes == 0x33) {
// xor r32, r/m32
COPY_CODES(2);
- } else if (origBytes[nOrigBytes] == 0xf6) {
+ } else if (*origBytes == 0xf6) {
// test r/m8, imm8 (used by ntdll on Windows 10 x64)
// (no flags are affected by near jmp since there is no task switch,
// so it is ok for a jmp to be written immediately after a test)
BYTE subOpcode = 0;
- int nModRmSibBytes = CountModRmSib(&origBytes[nOrigBytes + 1], &subOpcode);
+ int nModRmSibBytes = CountModRmSib(origBytes + 1, &subOpcode);
if (nModRmSibBytes < 0 || subOpcode != 0) {
// Unsupported
MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
return;
}
COPY_CODES(2 + nModRmSibBytes);
- } else if (origBytes[nOrigBytes] == 0x85) {
+ } else if (*origBytes == 0x85) {
// test r/m32, r32
- int nModRmSibBytes = CountModRmSib(&origBytes[nOrigBytes + 1]);
+ int nModRmSibBytes = CountModRmSib(origBytes + 1);
if (nModRmSibBytes < 0) {
MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
return;
}
COPY_CODES(1 + nModRmSibBytes);
- } else if (origBytes[nOrigBytes] == 0xd1 &&
- (origBytes[nOrigBytes+1] & kMaskMod) == kModReg) {
+ } else if (*origBytes == 0xd1 &&
+ (origBytes[1] & kMaskMod) == kModReg) {
// bit shifts/rotates : (SA|SH|RO|RC)(R|L) r32
// (e.g. 0xd1 0xe0 is SAL, 0xd1 0xc8 is ROR)
COPY_CODES(2);
- } else if (origBytes[nOrigBytes] == 0xc3) {
+ } else if (*origBytes == 0xc3) {
// ret
COPY_CODES(1);
- } else if (origBytes[nOrigBytes] == 0xcc) {
+ } else if (*origBytes == 0xcc) {
// int 3
COPY_CODES(1);
- } else if (origBytes[nOrigBytes] == 0xe8 ||
- origBytes[nOrigBytes] == 0xe9) {
+ } else if (*origBytes == 0xe8 ||
+ *origBytes == 0xe9) {
// CALL (0xe8) or JMP (0xe9) 32bit offset
- foundJmp = origBytes[nOrigBytes] == 0xe9;
- JumpPatch jump(nTrampBytes,
- (intptr_t)(origBytes + nOrigBytes + 5 +
- *(reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 1))),
- origBytes[nOrigBytes] == 0xe8 ? JumpType::Call : JumpType::Jmp);
- nTrampBytes = jump.GenerateJump(tramp);
- nOrigBytes += 5;
- } else if (origBytes[nOrigBytes] == 0x74 || // je rel8 (0x74)
- origBytes[nOrigBytes] == 0x75) { // jne rel8 (0x75)
- char offset = origBytes[nOrigBytes + 1];
+ foundJmp = *origBytes == 0xe9;
+ ++origBytes;
+
+ if (!GenerateJump(tramp, origBytes.ReadDisp32AsAbsolute(),
+ foundJmp ? JumpType::Jmp : JumpType::Call)) {
+ return;
+ }
+ } else if (*origBytes == 0x74 || // je rel8 (0x74)
+ *origBytes == 0x75) { // jne rel8 (0x75)
+ uint8_t offset = origBytes[1];
auto jumpType = JumpType::Je;
- if (origBytes[nOrigBytes] == 0x75)
+ if (*origBytes == 0x75) {
jumpType = JumpType::Jne;
- JumpPatch jump(nTrampBytes,
- (intptr_t)(origBytes + nOrigBytes + 2 + offset), jumpType);
- nTrampBytes = jump.GenerateJump(tramp);
- nOrigBytes += 2;
- } else if (origBytes[nOrigBytes] == 0xff) {
- if ((origBytes[nOrigBytes + 1] & (kMaskMod|kMaskReg)) == 0xf0) {
+ }
+
+ origBytes += 2;
+
+ if (!GenerateJump(tramp, origBytes.OffsetToAbsolute(offset), jumpType)) {
+ return;
+ }
+ } else if (*origBytes == 0xff) {
+ if ((origBytes[1] & (kMaskMod|kMaskReg)) == 0xf0) {
// push r64
COPY_CODES(2);
- } else if (origBytes[nOrigBytes + 1] == 0x25) {
+ } else if (origBytes[1] == 0x25) {
// jmp absolute indirect m32
foundJmp = true;
- int32_t offset = *(reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 2));
- int64_t* ptrToJmpDest = reinterpret_cast<int64_t*>(origBytes + nOrigBytes + 6 + offset);
- intptr_t jmpDest = static_cast<intptr_t>(*ptrToJmpDest);
- JumpPatch jump(nTrampBytes, jmpDest, JumpType::Jmp);
- nTrampBytes = jump.GenerateJump(tramp);
- nOrigBytes += 6;
- } else if ((origBytes[nOrigBytes + 1] & (kMaskMod|kMaskReg)) == BuildModRmByte(kModReg, 2, 0)) {
+
+ origBytes += 2;
+
+ uintptr_t jmpDest = origBytes.ChasePointerFromDisp();
+
+ if (!GenerateJump(tramp, jmpDest, JumpType::Jmp)) {
+ return;
+ }
+ } else if ((origBytes[1] & (kMaskMod|kMaskReg)) == BuildModRmByte(kModReg, 2, 0)) {
// CALL reg (ff nn)
COPY_CODES(2);
} else {
MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
return;
}
- } else if (origBytes[nOrigBytes] == 0x83 &&
- (origBytes[nOrigBytes + 1] & 0xf8) == 0x60) {
+ } else if (*origBytes == 0x83 &&
+ (origBytes[1] & 0xf8) == 0x60) {
// and [r+d], imm8
COPY_CODES(5);
- } else if (origBytes[nOrigBytes] == 0xc6) {
+ } else if (*origBytes == 0xc6) {
// mov [r+d], imm8
- int len = CountModRmSib(&origBytes[nOrigBytes + 1]);
+ int len = CountModRmSib(origBytes + 1);
if (len < 0) {
// RIP-relative not yet supported
MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
return;
}
COPY_CODES(len + 1);
} else {
MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
return;
}
}
#else
#error "Unknown processor type"
#endif
- if (nOrigBytes > 100) {
+ if (origBytes.GetOffset() > 100) {
//printf ("Too big!");
return;
}
- // target address of the final jmp instruction in the trampoline
- byteptr_t trampDest = origBytes + nOrigBytes;
-
#if defined(_M_IX86)
if (pJmp32 >= 0) {
// Jump directly to the original target of the jump instead of jumping to the
// original function.
// Adjust jump target displacement to jump location in the trampoline.
- *((intptr_t*)(tramp + pJmp32 + 1)) += origBytes - tramp;
+ tramp.AdjustDisp32AtOffset(pJmp32 + 1, origBytes.GetBaseAddress());
} else {
- tramp[nOrigBytes] = 0xE9; // jmp
- *((intptr_t*)(tramp + nOrigBytes + 1)) =
- (intptr_t)trampDest - (intptr_t)(tramp + nOrigBytes + 5); // target displacement
+ tramp.WriteByte(0xe9); // jmp
+ tramp.WriteDisp32(origBytes.GetAddress());
}
#elif defined(_M_X64)
// If the we found a Jmp, we don't need to add another instruction. However,
// if we found a _conditional_ jump or a CALL (or no control operations
// at all) then we still need to run the rest of aOriginalFunction.
if (!foundJmp) {
- JumpPatch patch(nTrampBytes, reinterpret_cast<intptr_t>(trampDest));
- patch.GenerateJump(tramp);
+ if (!GenerateJump(tramp, origBytes.GetAddress(), JumpType::Jmp)) {
+ return;
+ }
}
#endif
- // The trampoline is now valid.
- *aOutTramp = tramp;
+ // The trampoline is now complete.
+ *aOutTramp = tramp.EndExecutableCode();
+ if (!(*aOutTramp)) {
+ return;
+ }
- // ensure we can modify the original code
- AutoVirtualProtect protect(aOrigFunction, nOrigBytes, PAGE_EXECUTE_READWRITE);
- if (!protect.Protect()) {
+ WritableTargetFunction<MMPolicyT> target(origBytes.Promote());
+ if (!target) {
return;
}
#if defined(_M_IX86)
// now modify the original bytes
- origBytes[0] = 0xE9; // jmp
- *((intptr_t*)(origBytes + 1)) =
- aDest - (intptr_t)(origBytes + 5); // target displacement
+ target.WriteByte(0xe9); //jmp
+ target.WriteDisp32(aDest); // hook displacement
#elif defined(_M_X64)
// mov r11, address
- origBytes[0] = 0x49;
- origBytes[1] = 0xbb;
-
- *((intptr_t*)(origBytes + 2)) = aDest;
+ target.WriteByte(0x49);
+ target.WriteByte(0xbb);
+ target.WritePointer(aDest);
// jmp r11
- origBytes[10] = 0x41;
- origBytes[11] = 0xff;
- origBytes[12] = 0xe3;
-#endif
- }
-
- byteptr_t FindTrampolineSpace()
- {
- if (mCurHooks >= mMaxHooks) {
- return 0;
- }
-
- byteptr_t p = mHookPage + mCurHooks * kHookSize;
-
- mCurHooks++;
-
- return p;
- }
-
- static void* ResolveRedirectedAddress(const byteptr_t aOriginalFunction)
- {
- // If function entry is jmp rel8 stub to the internal implementation, we
- // resolve redirected address from the jump target.
- if (aOriginalFunction[0] == 0xeb) {
- int8_t offset = (int8_t)(aOriginalFunction[1]);
- if (offset <= 0) {
- // Bail out for negative offset: probably already patched by some
- // third-party code.
- return aOriginalFunction;
- }
-
- for (int8_t i = 0; i < offset; i++) {
- if (aOriginalFunction[2 + i] != 0x90) {
- // Bail out on insufficient nop space.
- return aOriginalFunction;
- }
- }
-
- return aOriginalFunction + 2 + offset;
- }
-
-#if defined(_M_IX86)
- // If function entry is jmp [disp32] such as used by kernel32,
- // we resolve redirected address from import table.
- if (aOriginalFunction[0] == 0xff && aOriginalFunction[1] == 0x25) {
- return (void*)(**((uint32_t**) (aOriginalFunction + 2)));
- }
-#elif defined(_M_X64)
- if (aOriginalFunction[0] == 0xe9) {
- // require for TestDllInterceptor with --disable-optimize
- int32_t offset = *((int32_t*)(aOriginalFunction + 1));
- return aOriginalFunction + 5 + offset;
- }
+ target.WriteByte(0x41);
+ target.WriteByte(0xff);
+ target.WriteByte(0xe3);
#endif
- return aOriginalFunction;
+ target.Commit();
}
};
-} // namespace internal
-
-class WindowsDllInterceptor
-{
- internal::WindowsDllNopSpacePatcher mNopSpacePatcher;
- internal::WindowsDllDetourPatcher mDetourPatcher;
-
- const char* mModuleName;
- int mNHooks;
-
-public:
- explicit WindowsDllInterceptor(const char* aModuleName = nullptr,
- int aNumHooks = 0)
- : mModuleName(nullptr)
- , mNHooks(0)
- {
- if (aModuleName) {
- Init(aModuleName, aNumHooks);
- }
- }
-
- void Init(const char* aModuleName, int aNumHooks = 0)
- {
- if (mModuleName) {
- return;
- }
-
- mModuleName = aModuleName;
- mNHooks = aNumHooks;
- mNopSpacePatcher.Init(aModuleName);
-
- // Lazily initialize mDetourPatcher, since it allocates memory and we might
- // not need it.
- }
-
- /**
- * Hook/detour the method aName from the DLL we set in Init so that it calls
- * aHookDest instead. Returns the original method pointer in aOrigFunc
- * and returns true if successful.
- *
- * IMPORTANT: If you use this method, please add your case to the
- * TestDllInterceptor in order to detect future failures. Even if this
- * succeeds now, updates to the hooked DLL could cause it to fail in
- * the future.
- */
- bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc)
- {
- // Use a nop space patch if possible, otherwise fall back to a detour.
- // This should be the preferred method for adding hooks.
-
- if (!mModuleName) {
- return false;
- }
-
- if (mNopSpacePatcher.AddHook(aName, aHookDest, aOrigFunc)) {
- return true;
- }
-
- return AddDetour(aName, aHookDest, aOrigFunc);
- }
-
- /**
- * Detour the method aName from the DLL we set in Init so that it calls
- * aHookDest instead. Returns the original method pointer in aOrigFunc
- * and returns true if successful.
- *
- * IMPORTANT: If you use this method, please add your case to the
- * TestDllInterceptor in order to detect future failures. Even if this
- * succeeds now, updates to the detoured DLL could cause it to fail in
- * the future.
- */
- bool AddDetour(const char* aName, intptr_t aHookDest, void** aOrigFunc)
- {
- // Generally, code should not call this method directly. Use AddHook unless
- // there is a specific need to avoid nop space patches.
-
- if (!mModuleName) {
- return false;
- }
-
- if (!mDetourPatcher.Initialized()) {
- mDetourPatcher.Init(mModuleName, mNHooks);
- }
-
- return mDetourPatcher.AddHook(aName, aHookDest, aOrigFunc);
- }
-};
-
+} // namespace interceptor
} // namespace mozilla
-#endif /* NS_WINDOWS_DLL_INTERCEPTOR_H_ */
+#endif // mozilla_interceptor_PatcherDetour_h
+
copy from xpcom/build/nsWindowsDllInterceptor.h
copy to mozglue/misc/interceptor/PatcherNopSpace.h
--- a/xpcom/build/nsWindowsDllInterceptor.h
+++ b/mozglue/misc/interceptor/PatcherNopSpace.h
@@ -1,178 +1,64 @@
/* -*- 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 http://mozilla.org/MPL/2.0/. */
-
-#ifndef NS_WINDOWS_DLL_INTERCEPTOR_H_
-#define NS_WINDOWS_DLL_INTERCEPTOR_H_
-
-#include "mozilla/Assertions.h"
-#include "mozilla/ArrayUtils.h"
-#include "mozilla/UniquePtr.h"
-#include "nsWindowsHelpers.h"
-
-#include <wchar.h>
-#include <windows.h>
-#include <winternl.h>
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
-/*
- * Simple function interception.
- *
- * We have two separate mechanisms for intercepting a function: We can use the
- * built-in nop space, if it exists, or we can create a detour.
- *
- * Using the built-in nop space works as follows: On x86-32, DLL functions
- * begin with a two-byte nop (mov edi, edi) and are preceeded by five bytes of
- * NOP instructions.
- *
- * When we detect a function with this prelude, we do the following:
- *
- * 1. Write a long jump to our interceptor function into the five bytes of NOPs
- * before the function.
- *
- * 2. Write a short jump -5 into the two-byte nop at the beginning of the function.
- *
- * This mechanism is nice because it's thread-safe. It's even safe to do if
- * another thread is currently running the function we're modifying!
- *
- * When the WindowsDllNopSpacePatcher is destroyed, we overwrite the short jump
- * but not the long jump, so re-intercepting the same function won't work,
- * because its prelude won't match.
- *
- *
- * Unfortunately nop space patching doesn't work on functions which don't have
- * this magic prelude (and in particular, x86-64 never has the prelude). So
- * when we can't use the built-in nop space, we fall back to using a detour,
- * which works as follows:
- *
- * 1. Save first N bytes of OrigFunction to trampoline, where N is a
- * number of bytes >= 5 that are instruction aligned.
- *
- * 2. Replace first 5 bytes of OrigFunction with a jump to the Hook
- * function.
- *
- * 3. After N bytes of the trampoline, add a jump to OrigFunction+N to
- * continue original program flow.
- *
- * 4. Hook function needs to call the trampoline during its execution,
- * to invoke the original function (so address of trampoline is
- * returned).
- *
- * When the WindowsDllDetourPatcher object is destructed, OrigFunction is
- * patched again to jump directly to the trampoline instead of going through
- * the hook function. As such, re-intercepting the same function won't work, as
- * jump instructions are not supported.
- *
- * Note that this is not thread-safe. Sad day.
- *
- */
+#ifndef mozilla_interceptor_PatcherNopSpace_h
+#define mozilla_interceptor_PatcherNopSpace_h
-#include <stdint.h>
+#if defined(_M_IX86)
-#define COPY_CODES(NBYTES) do { \
- memcpy(&tramp[nTrampBytes], &origBytes[nOrigBytes], NBYTES); \
- nOrigBytes += NBYTES; \
- nTrampBytes += NBYTES; \
-} while (0)
+#include "mozilla/interceptor/PatcherBase.h"
namespace mozilla {
-namespace internal {
+namespace interceptor {
-class AutoVirtualProtect
+template <typename VMPolicy>
+class WindowsDllNopSpacePatcher final : public WindowsDllPatcherBase<VMPolicy>
{
+ // For remembering the addresses of functions we've patched.
+ mozilla::Vector<void*> mPatchedFns;
+
public:
- AutoVirtualProtect(void* aFunc, size_t aSize, DWORD aProtect)
- : mFunc(aFunc), mSize(aSize), mNewProtect(aProtect), mOldProtect(0),
- mSuccess(false)
+ template <typename... Args>
+ explicit WindowsDllNopSpacePatcher(Args... aArgs)
+ : WindowsDllPatcherBase<VMPolicy>(mozilla::Forward<Args>(aArgs)...)
{}
- ~AutoVirtualProtect()
+ ~WindowsDllNopSpacePatcher()
{
- if (mSuccess) {
- VirtualProtectEx(GetCurrentProcess(), mFunc, mSize, mOldProtect,
- &mOldProtect);
- }
- }
-
- bool Protect()
- {
- mSuccess = !!VirtualProtectEx(GetCurrentProcess(), mFunc, mSize,
- mNewProtect, &mOldProtect);
- if (!mSuccess) {
- // printf("VirtualProtectEx failed! %d\n", GetLastError());
- }
- return mSuccess;
+ Clear();
}
-private:
- void* const mFunc;
- size_t const mSize;
- DWORD const mNewProtect;
- DWORD mOldProtect;
- bool mSuccess;
-};
-
-class WindowsDllNopSpacePatcher
-{
- typedef uint8_t* byteptr_t;
- HMODULE mModule;
+ WindowsDllNopSpacePatcher(const WindowsDllNopSpacePatcher&) = delete;
+ WindowsDllNopSpacePatcher(WindowsDllNopSpacePatcher&&) = delete;
+ WindowsDllNopSpacePatcher& operator=(const WindowsDllNopSpacePatcher&) = delete;
+ WindowsDllNopSpacePatcher& operator=(WindowsDllNopSpacePatcher&&) = delete;
- // Dumb array for remembering the addresses of functions we've patched.
- // (This should be nsTArray, but non-XPCOM code uses this class.)
- static const size_t maxPatchedFns = 16;
- byteptr_t mPatchedFns[maxPatchedFns];
- size_t mPatchedFnsLen;
-
-public:
- WindowsDllNopSpacePatcher()
- : mModule(0)
- , mPatchedFnsLen(0)
- {}
-
-#if defined(_M_IX86)
- ~WindowsDllNopSpacePatcher()
+ void Clear()
{
// Restore the mov edi, edi to the beginning of each function we patched.
- for (size_t i = 0; i < mPatchedFnsLen; i++) {
- byteptr_t fn = mPatchedFns[i];
-
- // Ensure we can write to the code.
- AutoVirtualProtect protect(fn, 2, PAGE_EXECUTE_READWRITE);
- if (!protect.Protect()) {
+ for (auto&& ptr : mPatchedFns) {
+ WritableTargetFunction<MMPolicyT> fn(mVMPolicy,
+ reinterpret_cast<uintptr_t>(ptr),
+ sizeof(uint16_t));
+ if (!fn) {
continue;
}
// mov edi, edi
- *((uint16_t*)fn) = 0xff8b;
-
- // I don't think this is actually necessary, but it can't hurt.
- FlushInstructionCache(GetCurrentProcess(),
- /* ignored */ nullptr,
- /* ignored */ 0);
- }
- }
-
- void Init(const char* aModuleName)
- {
- if (!IsCompatible()) {
-#if defined(MOZILLA_INTERNAL_API)
- NS_WARNING("NOP space patching is unavailable for compatibility reasons");
-#endif
- return;
+ fn.WriteShort(0xff8b);
+ fn.Commit();
}
- mModule = LoadLibraryExA(aModuleName, nullptr, 0);
- if (!mModule) {
- //printf("LoadLibraryEx for '%s' failed\n", aModuleName);
- return;
- }
+ mPatchedFns.clear();
}
/**
* NVIDIA Optimus drivers utilize Microsoft Detours 2.x to patch functions
* in our address space. There is a bug in Detours 2.x that causes it to
* patch at the wrong address when attempting to detour code that is already
* NOP space patched. This function is an effort to detect the presence of
* this NVIDIA code in our address space and disable NOP space patching if it
@@ -209,17 +95,17 @@ public:
DWORD numBytes = 0;
const wchar_t kAppInitDLLs[] = L"AppInit_DLLs";
// Query for required buffer size
LONG status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr,
nullptr, nullptr, &numBytes);
mozilla::UniquePtr<wchar_t[]> data;
if (!status) {
// Allocate the buffer and query for the actual data
- data = mozilla::MakeUnique<wchar_t[]>(numBytes / sizeof(wchar_t));
+ data = mozilla::MakeUnique<wchar_t[]>((numBytes + 1) / sizeof(wchar_t));
status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr,
nullptr, (LPBYTE)data.get(), &numBytes);
}
if (!status) {
// For each token, split up the filename components and then check the
// name of the file.
const wchar_t kDelimiters[] = L", ";
wchar_t* tokenContext = nullptr;
@@ -239,1257 +125,93 @@ public:
}
token = wcstok_s(nullptr, kDelimiters, &tokenContext);
}
}
}
return true;
}
- bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc)
+ bool AddHook(FARPROC aTargetFn, intptr_t aHookDest, void** aOrigFunc)
{
- if (!mModule) {
- return false;
- }
-
if (!IsCompatible()) {
#if defined(MOZILLA_INTERNAL_API)
NS_WARNING("NOP space patching is unavailable for compatibility reasons");
#endif
return false;
}
- MOZ_RELEASE_ASSERT(mPatchedFnsLen < maxPatchedFns, "No room for the hook");
-
- byteptr_t fn = reinterpret_cast<byteptr_t>(GetProcAddress(mModule, aName));
- if (!fn) {
- //printf ("GetProcAddress failed\n");
+ MOZ_ASSERT(aTargetFn);
+ if (!aTargetFn) {
return false;
}
- fn = ResolveRedirectedAddress(fn);
+ ReadOnlyTargetFunction<MMPolicyT> readOnlyTargetFn(
+ ResolveRedirectedAddress(aTargetFn));
- // Ensure we can read and write starting at fn - 5 (for the long jmp we're
- // going to write) and ending at fn + 2 (for the short jmp up to the long
- // jmp). These bytes may span two pages with different protection.
- AutoVirtualProtect protectBefore(fn - 5, 5, PAGE_EXECUTE_READWRITE);
- AutoVirtualProtect protectAfter(fn, 2, PAGE_EXECUTE_READWRITE);
- if (!protectBefore.Protect() || !protectAfter.Protect()) {
+ if (!WriteHook(readOnlyTargetFn, aHookDest, aOrigFunc)) {
return false;
}
- bool rv = WriteHook(fn, aHookDest, aOrigFunc);
-
- if (rv) {
- mPatchedFns[mPatchedFnsLen] = fn;
- mPatchedFnsLen++;
- }
-
- return rv;
+ mPatchedFns.append(reinterpret_cast<void*>(readOnlyTargetFn.GetBaseAddress()));
+ return true;
}
- bool WriteHook(byteptr_t aFn, intptr_t aHookDest, void** aOrigFunc)
+ bool WriteHook(const ReadOnlyTargetFunction<MMPolicyT>& aFn,
+ intptr_t aHookDest, void** aOrigFunc)
{
- // Check that the 5 bytes before aFn are NOP's or INT 3's,
- // and that the 2 bytes after aFn are mov(edi, edi).
- //
- // It's safe to read aFn[-5] because we set it to PAGE_EXECUTE_READWRITE
- // before calling WriteHook.
-
- for (int i = -5; i <= -1; i++) {
- if (aFn[i] != 0x90 && aFn[i] != 0xcc) { // nop or int 3
- return false;
- }
+ // Ensure we can read and write starting at fn - 5 (for the long jmp we're
+ // going to write) and ending at fn + 2 (for the short jmp up to the long
+ // jmp). These bytes may span two pages with different protection.
+ WritableTargetFunction<MMPolicyT> writableFn(aFn.Promote(7, -5));
+ if (!writableFn) {
+ return false;
}
- // mov edi, edi. Yes, there are two ways to encode the same thing:
+ // Check that the 5 bytes before the function are NOP's or INT 3's,
+ const uint8_t nopOrBp[] = { 0x90, 0xCC };
+ if (!writableFn.VerifyValuesAreOneOf<uint8_t, 5>(nopOrBp)) {
+ return false;
+ }
+
+ // ... and that the first 2 bytes of the function are mov(edi, edi).
+ // There are two ways to encode the same thing:
//
- // 0x89ff == mov r/m, r
- // 0x8bff == mov r, r/m
+ // 0x89 0xff == mov r/m, r
+ // 0x8b 0xff == mov r, r/m
//
- // where "r" is register and "r/m" is register or memory. Windows seems to
- // use 8bff; I include 89ff out of paranoia.
- if ((aFn[0] != 0x8b && aFn[0] != 0x89) || aFn[1] != 0xff) {
+ // where "r" is register and "r/m" is register or memory.
+ // Windows seems to use 0x8B 0xFF. We include 0x89 0xFF out of paranoia.
+
+ // (These look backwards because little-endian)
+ const uint16_t possibleEncodings[] = { 0xFF8B, 0xFF89 };
+ if (!writableFn.VerifyValuesAreOneOf<uint16_t, 1>(possibleEncodings, 5)) {
return false;
}
// Write a long jump into the space above the function.
- aFn[-5] = 0xe9; // jmp
- *((intptr_t*)(aFn - 4)) = aHookDest - (uintptr_t)(aFn); // target displacement
-
- // Set aOrigFunc here, because after this point, aHookDest might be called,
- // and aHookDest might use the aOrigFunc pointer.
- *aOrigFunc = aFn + 2;
-
- // Short jump up into our long jump.
- *((uint16_t*)(aFn)) = 0xf9eb; // jmp $-5
-
- // I think this routine is safe without this, but it can't hurt.
- FlushInstructionCache(GetCurrentProcess(),
- /* ignored */ nullptr,
- /* ignored */ 0);
-
- return true;
- }
-
-private:
- static byteptr_t ResolveRedirectedAddress(const byteptr_t aOriginalFunction)
- {
- // If function entry is jmp rel8 stub to the internal implementation, we
- // resolve redirected address from the jump target.
- if (aOriginalFunction[0] == 0xeb) {
- int8_t offset = (int8_t)(aOriginalFunction[1]);
- if (offset <= 0) {
- // Bail out for negative offset: probably already patched by some
- // third-party code.
- return aOriginalFunction;
- }
-
- for (int8_t i = 0; i < offset; i++) {
- if (aOriginalFunction[2 + i] != 0x90) {
- // Bail out on insufficient nop space.
- return aOriginalFunction;
- }
- }
-
- return aOriginalFunction + 2 + offset;
- }
-
- // If function entry is jmp [disp32] such as used by kernel32,
- // we resolve redirected address from import table.
- if (aOriginalFunction[0] == 0xff && aOriginalFunction[1] == 0x25) {
- return (byteptr_t)(**((uint32_t**) (aOriginalFunction + 2)));
- }
-
- return aOriginalFunction;
- }
-#else
- void Init(const char* aModuleName)
- {
- // Not implemented except on x86-32.
- }
-
- bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc)
- {
- // Not implemented except on x86-32.
- return false;
- }
-#endif
-};
-
-class WindowsDllDetourPatcher
-{
- typedef unsigned char* byteptr_t;
-public:
- WindowsDllDetourPatcher()
- : mModule(0), mHookPage(0), mMaxHooks(0), mCurHooks(0)
- {
- }
-
- ~WindowsDllDetourPatcher()
- {
- int i;
- byteptr_t p;
- for (i = 0, p = mHookPage; i < mCurHooks; i++, p += kHookSize) {
-#if defined(_M_IX86)
- size_t nBytes = 1 + sizeof(intptr_t);
-#elif defined(_M_X64)
- size_t nBytes = 2 + sizeof(intptr_t);
-#else
-#error "Unknown processor type"
-#endif
- byteptr_t origBytes = (byteptr_t)DecodePointer(*((byteptr_t*)p));
-
- // ensure we can modify the original code
- AutoVirtualProtect protect(origBytes, nBytes, PAGE_EXECUTE_READWRITE);
- if (!protect.Protect()) {
- continue;
- }
-
- // Remove the hook by making the original function jump directly
- // in the trampoline.
- intptr_t dest = (intptr_t)(p + sizeof(void*));
-#if defined(_M_IX86)
- // Ensure the JMP from CreateTrampoline is where we expect it to be.
- if (origBytes[0] != 0xE9)
- continue;
- *((intptr_t*)(origBytes + 1)) =
- dest - (intptr_t)(origBytes + 5); // target displacement
-#elif defined(_M_X64)
- // Ensure the MOV R11 from CreateTrampoline is where we expect it to be.
- if (origBytes[0] != 0x49 || origBytes[1] != 0xBB)
- continue;
- *((intptr_t*)(origBytes + 2)) = dest;
-#else
-#error "Unknown processor type"
-#endif
- }
- }
-
- void Init(const char* aModuleName, int aNumHooks = 0)
- {
- if (mModule) {
- return;
- }
-
- mModule = LoadLibraryExA(aModuleName, nullptr, 0);
- if (!mModule) {
- //printf("LoadLibraryEx for '%s' failed\n", aModuleName);
- return;
- }
-
- int hooksPerPage = 4096 / kHookSize;
- if (aNumHooks == 0) {
- aNumHooks = hooksPerPage;
- }
-
- mMaxHooks = aNumHooks + (hooksPerPage % aNumHooks);
-
- mHookPage = (byteptr_t)VirtualAllocEx(GetCurrentProcess(), nullptr,
- mMaxHooks * kHookSize,
- MEM_COMMIT | MEM_RESERVE,
- PAGE_EXECUTE_READ);
- if (!mHookPage) {
- mModule = 0;
- return;
- }
- }
-
- bool Initialized() { return !!mModule; }
-
- bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc)
- {
- if (!mModule) {
+ writableFn.WriteByte(0xe9); // jmp
+ if (!writableFn) {
return false;
}
- void* pAddr = (void*)GetProcAddress(mModule, aName);
- if (!pAddr) {
- //printf ("GetProcAddress failed\n");
- return false;
- }
-
- pAddr = ResolveRedirectedAddress((byteptr_t)pAddr);
-
- CreateTrampoline(pAddr, aHookDest, aOrigFunc);
- if (!*aOrigFunc) {
- //printf ("CreateTrampoline failed\n");
+ writableFn.WriteDisp32(aHookDest); // target
+ if (!writableFn) {
return false;
}
- return true;
- }
-
-protected:
- const static int kPageSize = 4096;
- const static int kHookSize = 128;
-
- HMODULE mModule;
- byteptr_t mHookPage;
- int mMaxHooks;
- int mCurHooks;
-
- // rex bits
- static const BYTE kMaskHighNibble = 0xF0;
- static const BYTE kRexOpcode = 0x40;
- static const BYTE kMaskRexW = 0x08;
- static const BYTE kMaskRexR = 0x04;
- static const BYTE kMaskRexX = 0x02;
- static const BYTE kMaskRexB = 0x01;
-
- // mod r/m bits
- static const BYTE kRegFieldShift = 3;
- static const BYTE kMaskMod = 0xC0;
- static const BYTE kMaskReg = 0x38;
- static const BYTE kMaskRm = 0x07;
- static const BYTE kRmNeedSib = 0x04;
- static const BYTE kModReg = 0xC0;
- static const BYTE kModDisp32 = 0x80;
- static const BYTE kModDisp8 = 0x40;
- static const BYTE kModNoRegDisp = 0x00;
- static const BYTE kRmNoRegDispDisp32 = 0x05;
-
- // sib bits
- static const BYTE kMaskSibScale = 0xC0;
- static const BYTE kMaskSibIndex = 0x38;
- static const BYTE kMaskSibBase = 0x07;
- static const BYTE kSibBaseEbp = 0x05;
-
- // Register bit IDs.
- static const BYTE kRegAx = 0x0;
- static const BYTE kRegCx = 0x1;
- static const BYTE kRegDx = 0x2;
- static const BYTE kRegBx = 0x3;
- static const BYTE kRegSp = 0x4;
- static const BYTE kRegBp = 0x5;
- static const BYTE kRegSi = 0x6;
- static const BYTE kRegDi = 0x7;
-
- // Special ModR/M codes. These indicate operands that cannot be simply
- // memcpy-ed.
- // Operand is a 64-bit RIP-relative address.
- static const int kModOperand64 = -2;
- // Operand is not yet handled by our trampoline.
- static const int kModUnknown = -1;
-
- /**
- * Returns the number of bytes taken by the ModR/M byte, SIB (if present)
- * and the instruction's operand. In special cases, the special MODRM codes
- * above are returned.
- * aModRm points to the ModR/M byte of the instruction.
- * On return, aSubOpcode (if present) is filled with the subopcode/register
- * code found in the ModR/M byte.
- */
- int CountModRmSib(const BYTE *aModRm, BYTE* aSubOpcode = nullptr)
- {
- if (!aModRm) {
- MOZ_ASSERT(aModRm, "Missing ModRM byte");
- return kModUnknown;
- }
- int numBytes = 1; // Start with 1 for mod r/m byte itself
- switch (*aModRm & kMaskMod) {
- case kModReg:
- return numBytes;
- case kModDisp8:
- numBytes += 1;
- break;
- case kModDisp32:
- numBytes += 4;
- break;
- case kModNoRegDisp:
- if ((*aModRm & kMaskRm) == kRmNoRegDispDisp32) {
-#if defined(_M_X64)
- if (aSubOpcode) {
- *aSubOpcode = (*aModRm & kMaskReg) >> kRegFieldShift;
- }
- return kModOperand64;
-#else
- // On IA-32, all ModR/M instruction modes address memory relative to 0
- numBytes += 4;
-#endif
- } else if (((*aModRm & kMaskRm) == kRmNeedSib &&
- (*(aModRm + 1) & kMaskSibBase) == kSibBaseEbp)) {
- numBytes += 4;
- }
- break;
- default:
- // This should not be reachable
- MOZ_ASSERT_UNREACHABLE("Impossible value for modr/m byte mod bits");
- return kModUnknown;
- }
- if ((*aModRm & kMaskRm) == kRmNeedSib) {
- // SIB byte
- numBytes += 1;
- }
- if (aSubOpcode) {
- *aSubOpcode = (*aModRm & kMaskReg) >> kRegFieldShift;
- }
- return numBytes;
- }
-
-#if defined(_M_X64)
- // To patch for JMP and JE
-
- enum JumpType {
- Je,
- Jne,
- Jmp,
- Call
- };
-
- struct JumpPatch {
- JumpPatch()
- : mHookOffset(0), mJumpAddress(0), mType(JumpType::Jmp)
- {
- }
-
- JumpPatch(size_t aOffset, intptr_t aAddress, JumpType aType = JumpType::Jmp)
- : mHookOffset(aOffset), mJumpAddress(aAddress), mType(aType)
- {
- }
-
- size_t GenerateJump(uint8_t* aCode)
- {
- size_t offset = mHookOffset;
- if (mType == JumpType::Je) {
- // JNE RIP+14
- aCode[offset] = 0x75;
- aCode[offset + 1] = 14;
- offset += 2;
- } else if (mType == JumpType::Jne) {
- // JE RIP+14
- aCode[offset] = 0x74;
- aCode[offset + 1] = 14;
- offset += 2;
- }
-
- // Near call/jmp, absolute indirect, address given in r/m32
- if (mType == JumpType::Call) {
- // CALL [RIP+0]
- aCode[offset] = 0xff;
- aCode[offset + 1] = 0x15;
- // The offset to jump destination -- ie it is placed 2 bytes after the offset.
- *reinterpret_cast<int32_t*>(aCode + offset + 2) = 2;
- aCode[offset + 2 + 4] = 0xeb; // JMP +8 (jump over mJumpAddress)
- aCode[offset + 2 + 4 + 1] = 8;
- *reinterpret_cast<int64_t*>(aCode + offset + 2 + 4 + 2) = mJumpAddress;
- return offset + 2 + 4 + 2 + 8;
- } else {
- // JMP [RIP+0]
- aCode[offset] = 0xff;
- aCode[offset + 1] = 0x25;
- // The offset to jump destination is 0
- *reinterpret_cast<int32_t*>(aCode + offset + 2) = 0;
- *reinterpret_cast<int64_t*>(aCode + offset + 2 + 4) = mJumpAddress;
- return offset + 2 + 4 + 8;
- }
- }
-
- size_t mHookOffset;
- intptr_t mJumpAddress;
- JumpType mType;
- };
-
-#endif
-
- enum ePrefixGroupBits
- {
- eNoPrefixes = 0,
- ePrefixGroup1 = (1 << 0),
- ePrefixGroup2 = (1 << 1),
- ePrefixGroup3 = (1 << 2),
- ePrefixGroup4 = (1 << 3)
- };
-
- int CountPrefixBytes(byteptr_t aBytes, const int aBytesIndex,
- unsigned char* aOutGroupBits)
- {
- unsigned char& groupBits = *aOutGroupBits;
- groupBits = eNoPrefixes;
- int index = aBytesIndex;
- while (true) {
- switch (aBytes[index]) {
- // Group 1
- case 0xF0: // LOCK
- case 0xF2: // REPNZ
- case 0xF3: // REP / REPZ
- if (groupBits & ePrefixGroup1) {
- return -1;
- }
- groupBits |= ePrefixGroup1;
- ++index;
- break;
-
- // Group 2
- case 0x2E: // CS override / branch not taken
- case 0x36: // SS override
- case 0x3E: // DS override / branch taken
- case 0x64: // FS override
- case 0x65: // GS override
- if (groupBits & ePrefixGroup2) {
- return -1;
- }
- groupBits |= ePrefixGroup2;
- ++index;
- break;
-
- // Group 3
- case 0x66: // operand size override
- if (groupBits & ePrefixGroup3) {
- return -1;
- }
- groupBits |= ePrefixGroup3;
- ++index;
- break;
-
- // Group 4
- case 0x67: // Address size override
- if (groupBits & ePrefixGroup4) {
- return -1;
- }
- groupBits |= ePrefixGroup4;
- ++index;
- break;
-
- default:
- return index - aBytesIndex;
- }
- }
- }
-
- // Return a ModR/M byte made from the 2 Mod bits, the register used for the
- // reg bits and the register used for the R/M bits.
- BYTE BuildModRmByte(BYTE aModBits, BYTE aReg, BYTE aRm)
- {
- MOZ_ASSERT((aRm & kMaskRm) == aRm);
- MOZ_ASSERT((aModBits & kMaskMod) == aModBits);
- MOZ_ASSERT(((aReg << kRegFieldShift) & kMaskReg) == (aReg << kRegFieldShift));
- return aModBits | (aReg << kRegFieldShift) | aRm;
- }
-
- void CreateTrampoline(void* aOrigFunction, intptr_t aDest, void** aOutTramp)
- {
- *aOutTramp = nullptr;
-
- AutoVirtualProtect protectHookPage(mHookPage, mMaxHooks * kHookSize,
- PAGE_EXECUTE_READWRITE);
- if (!protectHookPage.Protect()) {
- return;
- }
-
- byteptr_t tramp = FindTrampolineSpace();
- if (!tramp) {
- return;
- }
-
- // We keep the address of the original function in the first bytes of
- // the trampoline buffer
- *((void**)tramp) = EncodePointer(aOrigFunction);
- tramp += sizeof(void*);
-
- byteptr_t origBytes = (byteptr_t)aOrigFunction;
-
- // # of bytes of the original function that we can overwrite.
- int nOrigBytes = 0;
-
-#if defined(_M_IX86)
- int pJmp32 = -1;
- while (nOrigBytes < 5) {
- // Understand some simple instructions that might be found in a
- // prologue; we might need to extend this as necessary.
- //
- // Note! If we ever need to understand jump instructions, we'll
- // need to rewrite the displacement argument.
- unsigned char prefixGroups;
- int numPrefixBytes = CountPrefixBytes(origBytes, nOrigBytes, &prefixGroups);
- if (numPrefixBytes < 0 || (prefixGroups & (ePrefixGroup3 | ePrefixGroup4))) {
- // Either the prefix sequence was bad, or there are prefixes that
- // we don't currently support (groups 3 and 4)
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- nOrigBytes += numPrefixBytes;
- if (origBytes[nOrigBytes] >= 0x88 &&
- origBytes[nOrigBytes] <= 0x8B) {
- // various MOVs
- ++nOrigBytes;
- int len = CountModRmSib(origBytes + nOrigBytes);
- if (len < 0) {
- MOZ_ASSERT_UNREACHABLE("Unrecognized MOV opcode sequence");
- return;
- }
- nOrigBytes += len;
- } else if (origBytes[nOrigBytes] == 0x0f &&
- (origBytes[nOrigBytes + 1] == 0x10 ||
- origBytes[nOrigBytes + 1] == 0x11)) {
- // SSE: movups xmm, xmm/m128
- // movups xmm/m128, xmm
- nOrigBytes += 2;
- int len = CountModRmSib(origBytes + nOrigBytes);
- if (len < 0) {
- MOZ_ASSERT_UNREACHABLE("Unrecognized MOV opcode sequence");
- return;
- }
- nOrigBytes += len;
- } else if (origBytes[nOrigBytes] == 0xA1) {
- // MOV eax, [seg:offset]
- nOrigBytes += 5;
- } else if (origBytes[nOrigBytes] == 0xB8) {
- // MOV 0xB8: http://ref.x86asm.net/coder32.html#xB8
- nOrigBytes += 5;
- } else if (origBytes[nOrigBytes] == 0x33 &&
- (origBytes[nOrigBytes+1] & kMaskMod) == kModReg) {
- // XOR r32, r32
- nOrigBytes += 2;
- } else if ((origBytes[nOrigBytes] & 0xf8) == 0x40) {
- // INC r32
- nOrigBytes += 1;
- } else if (origBytes[nOrigBytes] == 0x83) {
- // ADD|ODR|ADC|SBB|AND|SUB|XOR|CMP r/m, imm8
- unsigned char b = origBytes[nOrigBytes + 1];
- if ((b & 0xc0) == 0xc0) {
- // ADD|ODR|ADC|SBB|AND|SUB|XOR|CMP r, imm8
- nOrigBytes += 3;
- } else {
- // bail
- MOZ_ASSERT_UNREACHABLE("Unrecognized bit opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x68) {
- // PUSH with 4-byte operand
- nOrigBytes += 5;
- } else if ((origBytes[nOrigBytes] & 0xf0) == 0x50) {
- // 1-byte PUSH/POP
- nOrigBytes++;
- } else if (origBytes[nOrigBytes] == 0x6A) {
- // PUSH imm8
- nOrigBytes += 2;
- } else if (origBytes[nOrigBytes] == 0xe9) {
- pJmp32 = nOrigBytes;
- // jmp 32bit offset
- nOrigBytes += 5;
- } else if (origBytes[nOrigBytes] == 0xff &&
- origBytes[nOrigBytes + 1] == 0x25) {
- // jmp [disp32]
- nOrigBytes += 6;
- } else if (origBytes[nOrigBytes] == 0xc2) {
- // ret imm16. We can't handle this but it happens. We don't ASSERT but we do fail to hook.
-#if defined(MOZILLA_INTERNAL_API)
- NS_WARNING("Cannot hook method -- RET opcode found");
-#endif
- return;
- } else {
- //printf ("Unknown x86 instruction byte 0x%02x, aborting trampoline\n", origBytes[nOrigBytes]);
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- }
-
- // The trampoline is a copy of the instructions that we just traced,
- // followed by a jump that we add below.
- memcpy(tramp, aOrigFunction, nOrigBytes);
-#elif defined(_M_X64)
- // The number of bytes used by the trampoline.
- int nTrampBytes = 0;
- bool foundJmp = false;
-
- while (nOrigBytes < 13) {
- // If we found JMP 32bit offset, we require that the next bytes must
- // be NOP or INT3. There is no reason to copy them.
- // TODO: This used to trigger for Je as well. Now that I allow
- // instructions after CALL and JE, I don't think I need that.
- // The only real value of this condition is that if code follows a JMP
- // then its _probably_ the target of a JMP somewhere else and we
- // will be overwriting it, which would be tragic. This seems
- // highly unlikely.
- if (foundJmp) {
- if (origBytes[nOrigBytes] == 0x90 || origBytes[nOrigBytes] == 0xcc) {
- nOrigBytes++;
- continue;
- }
- MOZ_ASSERT_UNREACHABLE("Opcode sequence includes commands after JMP");
- return;
- }
- if (origBytes[nOrigBytes] == 0x0f) {
- COPY_CODES(1);
- if (origBytes[nOrigBytes] == 0x1f) {
- // nop (multibyte)
- COPY_CODES(1);
- if ((origBytes[nOrigBytes] & 0xc0) == 0x40 &&
- (origBytes[nOrigBytes] & 0x7) == 0x04) {
- COPY_CODES(3);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x05) {
- // syscall
- COPY_CODES(1);
- } else if (origBytes[nOrigBytes] == 0x10 ||
- origBytes[nOrigBytes] == 0x11) {
- // SSE: movups xmm, xmm/m128
- // movups xmm/m128, xmm
- COPY_CODES(1);
- int nModRmSibBytes = CountModRmSib(&origBytes[nOrigBytes]);
- if (nModRmSibBytes < 0) {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- } else {
- COPY_CODES(nModRmSibBytes);
- }
- } else if (origBytes[nOrigBytes] == 0x84) {
- // je rel32
- JumpPatch jump(nTrampBytes - 1, // overwrite the 0x0f we copied above
- (intptr_t)(origBytes + nOrigBytes + 5 +
- *(reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 1))),
- JumpType::Je);
- nTrampBytes = jump.GenerateJump(tramp);
- nOrigBytes += 5;
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x40 ||
- origBytes[nOrigBytes] == 0x41) {
- // Plain REX or REX.B
- COPY_CODES(1);
- if ((origBytes[nOrigBytes] & 0xf0) == 0x50) {
- // push/pop with Rx register
- COPY_CODES(1);
- } else if (origBytes[nOrigBytes] >= 0xb8 && origBytes[nOrigBytes] <= 0xbf) {
- // mov r32, imm32
- COPY_CODES(5);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x44) {
- // REX.R
- COPY_CODES(1);
+ // Set aOrigFunc here, because after this point, aHookDest might be called,
+ // and aHookDest might use the aOrigFunc pointer.
+ *aOrigFunc = reinterpret_cast<void*>(writableFn.GetCurrentAddress() +
+ sizeof(uint16_t));
- // TODO: Combine with the "0x89" case below in the REX.W section
- if (origBytes[nOrigBytes] == 0x89) {
- // mov r/m32, r32
- COPY_CODES(1);
- int len = CountModRmSib(origBytes + nOrigBytes);
- if (len < 0) {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- COPY_CODES(len);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x45) {
- // REX.R & REX.B
- COPY_CODES(1);
-
- if (origBytes[nOrigBytes] == 0x33) {
- // xor r32, r32
- COPY_CODES(2);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if ((origBytes[nOrigBytes] & 0xfa) == 0x48) {
- // REX.W | REX.WR | REX.WRB | REX.WB
- COPY_CODES(1);
-
- if (origBytes[nOrigBytes] == 0x81 &&
- (origBytes[nOrigBytes + 1] & 0xf8) == 0xe8) {
- // sub r, dword
- COPY_CODES(6);
- } else if (origBytes[nOrigBytes] == 0x83 &&
- (origBytes[nOrigBytes + 1] & 0xf8) == 0xe8) {
- // sub r, byte
- COPY_CODES(3);
- } else if (origBytes[nOrigBytes] == 0x83 &&
- (origBytes[nOrigBytes + 1] & (kMaskMod|kMaskReg)) == kModReg) {
- // add r, byte
- COPY_CODES(3);
- } else if (origBytes[nOrigBytes] == 0x83 &&
- (origBytes[nOrigBytes + 1] & 0xf8) == 0x60) {
- // and [r+d], imm8
- COPY_CODES(5);
- } else if (origBytes[nOrigBytes] == 0x2b &&
- (origBytes[nOrigBytes + 1] & kMaskMod) == kModReg) {
- // sub r64, r64
- COPY_CODES(2);
- } else if (origBytes[nOrigBytes] == 0x85) {
- // 85 /r => TEST r/m32, r32
- if ((origBytes[nOrigBytes + 1] & 0xc0) == 0xc0) {
- COPY_CODES(2);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if ((origBytes[nOrigBytes] & 0xfd) == 0x89) {
- // MOV r/m64, r64 | MOV r64, r/m64
- BYTE reg;
- int len = CountModRmSib(origBytes + nOrigBytes + 1, ®);
- if (len < 0) {
- MOZ_ASSERT(len == kModOperand64);
- if (len != kModOperand64) {
- return;
- }
- nOrigBytes += 2; // skip the MOV and MOD R/M bytes
-
- // The instruction MOVs 64-bit data from a RIP-relative memory
- // address (determined with a 32-bit offset from RIP) into a
- // 64-bit register.
- int64_t* absAddr =
- reinterpret_cast<int64_t*>(origBytes + nOrigBytes + 4 +
- *reinterpret_cast<int32_t*>(origBytes + nOrigBytes));
- nOrigBytes += 4;
-
- if (reg == kRegAx) {
- // Destination is RAX. Encode instruction as MOVABS with a
- // 64-bit absolute address as its immediate operand.
- tramp[nTrampBytes] = 0xa1;
- ++nTrampBytes;
- int64_t** trampOperandPtr = reinterpret_cast<int64_t**>(tramp + nTrampBytes);
- *trampOperandPtr = absAddr;
- nTrampBytes += 8;
- } else {
- // The MOV must be done in two steps. First, we MOVABS the
- // absolute 64-bit address into our target register.
- // Then, we MOV from that address into the register
- // using register-indirect addressing.
- tramp[nTrampBytes] = 0xb8 + reg;
- ++nTrampBytes;
- int64_t** trampOperandPtr = reinterpret_cast<int64_t**>(tramp + nTrampBytes);
- *trampOperandPtr = absAddr;
- nTrampBytes += 8;
- tramp[nTrampBytes] = 0x48;
- tramp[nTrampBytes+1] = 0x8b;
- tramp[nTrampBytes+2] = BuildModRmByte(kModNoRegDisp, reg, reg);
- nTrampBytes += 3;
- }
- } else {
- COPY_CODES(len+1);
- }
- } else if (origBytes[nOrigBytes] == 0xc7) {
- // MOV r/m64, imm32
- if (origBytes[nOrigBytes + 1] == 0x44) {
- // MOV [r64+disp8], imm32
- // ModR/W + SIB + disp8 + imm32
- COPY_CODES(8);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0xff) {
- // JMP /4
- if ((origBytes[nOrigBytes + 1] & 0xc0) == 0x0 &&
- (origBytes[nOrigBytes + 1] & 0x07) == 0x5) {
- // [rip+disp32]
- // convert JMP 32bit offset to JMP 64bit direct
- JumpPatch jump(nTrampBytes - 1, // overwrite the REX.W/REX.WR we copied above
- *reinterpret_cast<intptr_t*>(origBytes + nOrigBytes + 6 +
- *reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 2)),
- JumpType::Jmp);
- nTrampBytes = jump.GenerateJump(tramp);
- nOrigBytes += 6;
- foundJmp = true;
- } else {
- // not support yet!
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x8d) {
- // LEA reg, addr
- if ((origBytes[nOrigBytes + 1] & kMaskMod) == 0x0 &&
- (origBytes[nOrigBytes + 1] & kMaskRm) == 0x5) {
- // [rip+disp32]
- // convert 32bit offset to 64bit direct and convert instruction
- // to a simple 64-bit mov
- BYTE reg = (origBytes[nOrigBytes + 1] & kMaskReg) >> kRegFieldShift;
- intptr_t absAddr =
- reinterpret_cast<intptr_t>(origBytes + nOrigBytes + 6 +
- *reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 2));
- nOrigBytes += 6;
- tramp[nTrampBytes] = 0xb8 + reg; // mov
- ++nTrampBytes;
- intptr_t* trampOperandPtr = reinterpret_cast<intptr_t*>(tramp + nTrampBytes);
- *trampOperandPtr = absAddr;
- nTrampBytes += 8;
- } else {
- // Above we dealt with RIP-relative instructions. Any other
- // operand form can simply be copied.
- int len = CountModRmSib(origBytes + nOrigBytes + 1);
- // We handled the kModOperand64 -- ie RIP-relative -- case above
- MOZ_ASSERT(len > 0);
- COPY_CODES(len + 1);
- }
- } else if (origBytes[nOrigBytes] == 0x63 &&
- (origBytes[nOrigBytes + 1] & kMaskMod) == kModReg) {
- // movsxd r64, r32 (move + sign extend)
- COPY_CODES(2);
- } else {
- // not support yet!
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x66) {
- // operand override prefix
- COPY_CODES(1);
- // This is the same as the x86 version
- if (origBytes[nOrigBytes] >= 0x88 && origBytes[nOrigBytes] <= 0x8B) {
- // various MOVs
- unsigned char b = origBytes[nOrigBytes + 1];
- if (((b & 0xc0) == 0xc0) ||
- (((b & 0xc0) == 0x00) &&
- ((b & 0x07) != 0x04) && ((b & 0x07) != 0x05))) {
- // REG=r, R/M=r or REG=r, R/M=[r]
- COPY_CODES(2);
- } else if ((b & 0xc0) == 0x40) {
- if ((b & 0x07) == 0x04) {
- // REG=r, R/M=[SIB + disp8]
- COPY_CODES(4);
- } else {
- // REG=r, R/M=[r + disp8]
- COPY_CODES(3);
- }
- } else {
- // complex MOV, bail
- MOZ_ASSERT_UNREACHABLE("Unrecognized MOV opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x44 &&
- origBytes[nOrigBytes+1] == 0x89) {
- // mov word ptr [reg+disp8], reg
- COPY_CODES(2);
- int len = CountModRmSib(origBytes + nOrigBytes);
- if (len < 0) {
- // no way to support this yet.
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- COPY_CODES(len);
- }
- } else if ((origBytes[nOrigBytes] & 0xf0) == 0x50) {
- // 1-byte push/pop
- COPY_CODES(1);
- } else if (origBytes[nOrigBytes] == 0x65) {
- // GS prefix
- //
- // The entry of GetKeyState on Windows 10 has the following code.
- // 65 48 8b 04 25 30 00 00 00 mov rax,qword ptr gs:[30h]
- // (GS prefix + REX + MOV (0x8b) ...)
- if (origBytes[nOrigBytes + 1] == 0x48 &&
- (origBytes[nOrigBytes + 2] >= 0x88 && origBytes[nOrigBytes + 2] <= 0x8b)) {
- COPY_CODES(3);
- int len = CountModRmSib(origBytes + nOrigBytes);
- if (len < 0) {
- // no way to support this yet.
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- COPY_CODES(len);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x80 &&
- origBytes[nOrigBytes + 1] == 0x3d) {
- // cmp byte ptr [rip-relative address], imm8
- // We'll compute the absolute address and do the cmp in r11
-
- // push r11 (to save the old value)
- tramp[nTrampBytes] = 0x49;
- ++nTrampBytes;
- tramp[nTrampBytes] = 0x53;
- ++nTrampBytes;
-
- byteptr_t absAddr =
- reinterpret_cast<byteptr_t>(origBytes + nOrigBytes + 7 +
- *reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 2));
- nOrigBytes += 6;
-
- // mov r11, absolute address
- tramp[nTrampBytes] = 0x49;
- ++nTrampBytes;
- tramp[nTrampBytes] = 0xbb;
- ++nTrampBytes;
-
- *reinterpret_cast<byteptr_t*>(tramp + nTrampBytes) = absAddr;
- nTrampBytes += 8;
-
- // cmp byte ptr [r11],...
- tramp[nTrampBytes] = 0x41;
- ++nTrampBytes;
- tramp[nTrampBytes] = 0x80;
- ++nTrampBytes;
- tramp[nTrampBytes] = 0x3b;
- ++nTrampBytes;
-
- // ...imm8
- COPY_CODES(1);
-
- // pop r11 (doesn't affect the flags from the cmp)
- tramp[nTrampBytes] = 0x49;
- ++nTrampBytes;
- tramp[nTrampBytes] = 0x5b;
- ++nTrampBytes;
- } else if (origBytes[nOrigBytes] == 0x90) {
- // nop
- COPY_CODES(1);
- } else if ((origBytes[nOrigBytes] & 0xf8) == 0xb8) {
- // MOV r32, imm32
- COPY_CODES(5);
- } else if (origBytes[nOrigBytes] == 0x33) {
- // xor r32, r/m32
- COPY_CODES(2);
- } else if (origBytes[nOrigBytes] == 0xf6) {
- // test r/m8, imm8 (used by ntdll on Windows 10 x64)
- // (no flags are affected by near jmp since there is no task switch,
- // so it is ok for a jmp to be written immediately after a test)
- BYTE subOpcode = 0;
- int nModRmSibBytes = CountModRmSib(&origBytes[nOrigBytes + 1], &subOpcode);
- if (nModRmSibBytes < 0 || subOpcode != 0) {
- // Unsupported
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- COPY_CODES(2 + nModRmSibBytes);
- } else if (origBytes[nOrigBytes] == 0x85) {
- // test r/m32, r32
- int nModRmSibBytes = CountModRmSib(&origBytes[nOrigBytes + 1]);
- if (nModRmSibBytes < 0) {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- COPY_CODES(1 + nModRmSibBytes);
- } else if (origBytes[nOrigBytes] == 0xd1 &&
- (origBytes[nOrigBytes+1] & kMaskMod) == kModReg) {
- // bit shifts/rotates : (SA|SH|RO|RC)(R|L) r32
- // (e.g. 0xd1 0xe0 is SAL, 0xd1 0xc8 is ROR)
- COPY_CODES(2);
- } else if (origBytes[nOrigBytes] == 0xc3) {
- // ret
- COPY_CODES(1);
- } else if (origBytes[nOrigBytes] == 0xcc) {
- // int 3
- COPY_CODES(1);
- } else if (origBytes[nOrigBytes] == 0xe8 ||
- origBytes[nOrigBytes] == 0xe9) {
- // CALL (0xe8) or JMP (0xe9) 32bit offset
- foundJmp = origBytes[nOrigBytes] == 0xe9;
- JumpPatch jump(nTrampBytes,
- (intptr_t)(origBytes + nOrigBytes + 5 +
- *(reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 1))),
- origBytes[nOrigBytes] == 0xe8 ? JumpType::Call : JumpType::Jmp);
- nTrampBytes = jump.GenerateJump(tramp);
- nOrigBytes += 5;
- } else if (origBytes[nOrigBytes] == 0x74 || // je rel8 (0x74)
- origBytes[nOrigBytes] == 0x75) { // jne rel8 (0x75)
- char offset = origBytes[nOrigBytes + 1];
- auto jumpType = JumpType::Je;
- if (origBytes[nOrigBytes] == 0x75)
- jumpType = JumpType::Jne;
- JumpPatch jump(nTrampBytes,
- (intptr_t)(origBytes + nOrigBytes + 2 + offset), jumpType);
- nTrampBytes = jump.GenerateJump(tramp);
- nOrigBytes += 2;
- } else if (origBytes[nOrigBytes] == 0xff) {
- if ((origBytes[nOrigBytes + 1] & (kMaskMod|kMaskReg)) == 0xf0) {
- // push r64
- COPY_CODES(2);
- } else if (origBytes[nOrigBytes + 1] == 0x25) {
- // jmp absolute indirect m32
- foundJmp = true;
- int32_t offset = *(reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 2));
- int64_t* ptrToJmpDest = reinterpret_cast<int64_t*>(origBytes + nOrigBytes + 6 + offset);
- intptr_t jmpDest = static_cast<intptr_t>(*ptrToJmpDest);
- JumpPatch jump(nTrampBytes, jmpDest, JumpType::Jmp);
- nTrampBytes = jump.GenerateJump(tramp);
- nOrigBytes += 6;
- } else if ((origBytes[nOrigBytes + 1] & (kMaskMod|kMaskReg)) == BuildModRmByte(kModReg, 2, 0)) {
- // CALL reg (ff nn)
- COPY_CODES(2);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x83 &&
- (origBytes[nOrigBytes + 1] & 0xf8) == 0x60) {
- // and [r+d], imm8
- COPY_CODES(5);
- } else if (origBytes[nOrigBytes] == 0xc6) {
- // mov [r+d], imm8
- int len = CountModRmSib(&origBytes[nOrigBytes + 1]);
- if (len < 0) {
- // RIP-relative not yet supported
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- COPY_CODES(len + 1);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- }
-#else
-#error "Unknown processor type"
-#endif
-
- if (nOrigBytes > 100) {
- //printf ("Too big!");
- return;
- }
-
- // target address of the final jmp instruction in the trampoline
- byteptr_t trampDest = origBytes + nOrigBytes;
-
-#if defined(_M_IX86)
- if (pJmp32 >= 0) {
- // Jump directly to the original target of the jump instead of jumping to the
- // original function.
- // Adjust jump target displacement to jump location in the trampoline.
- *((intptr_t*)(tramp + pJmp32 + 1)) += origBytes - tramp;
- } else {
- tramp[nOrigBytes] = 0xE9; // jmp
- *((intptr_t*)(tramp + nOrigBytes + 1)) =
- (intptr_t)trampDest - (intptr_t)(tramp + nOrigBytes + 5); // target displacement
- }
-#elif defined(_M_X64)
- // If the we found a Jmp, we don't need to add another instruction. However,
- // if we found a _conditional_ jump or a CALL (or no control operations
- // at all) then we still need to run the rest of aOriginalFunction.
- if (!foundJmp) {
- JumpPatch patch(nTrampBytes, reinterpret_cast<intptr_t>(trampDest));
- patch.GenerateJump(tramp);
- }
-#endif
-
- // The trampoline is now valid.
- *aOutTramp = tramp;
-
- // ensure we can modify the original code
- AutoVirtualProtect protect(aOrigFunction, nOrigBytes, PAGE_EXECUTE_READWRITE);
- if (!protect.Protect()) {
- return;
- }
-
-#if defined(_M_IX86)
- // now modify the original bytes
- origBytes[0] = 0xE9; // jmp
- *((intptr_t*)(origBytes + 1)) =
- aDest - (intptr_t)(origBytes + 5); // target displacement
-#elif defined(_M_X64)
- // mov r11, address
- origBytes[0] = 0x49;
- origBytes[1] = 0xbb;
-
- *((intptr_t*)(origBytes + 2)) = aDest;
-
- // jmp r11
- origBytes[10] = 0x41;
- origBytes[11] = 0xff;
- origBytes[12] = 0xe3;
-#endif
- }
-
- byteptr_t FindTrampolineSpace()
- {
- if (mCurHooks >= mMaxHooks) {
- return 0;
- }
-
- byteptr_t p = mHookPage + mCurHooks * kHookSize;
-
- mCurHooks++;
-
- return p;
- }
-
- static void* ResolveRedirectedAddress(const byteptr_t aOriginalFunction)
- {
- // If function entry is jmp rel8 stub to the internal implementation, we
- // resolve redirected address from the jump target.
- if (aOriginalFunction[0] == 0xeb) {
- int8_t offset = (int8_t)(aOriginalFunction[1]);
- if (offset <= 0) {
- // Bail out for negative offset: probably already patched by some
- // third-party code.
- return aOriginalFunction;
- }
-
- for (int8_t i = 0; i < offset; i++) {
- if (aOriginalFunction[2 + i] != 0x90) {
- // Bail out on insufficient nop space.
- return aOriginalFunction;
- }
- }
-
- return aOriginalFunction + 2 + offset;
- }
-
-#if defined(_M_IX86)
- // If function entry is jmp [disp32] such as used by kernel32,
- // we resolve redirected address from import table.
- if (aOriginalFunction[0] == 0xff && aOriginalFunction[1] == 0x25) {
- return (void*)(**((uint32_t**) (aOriginalFunction + 2)));
- }
-#elif defined(_M_X64)
- if (aOriginalFunction[0] == 0xe9) {
- // require for TestDllInterceptor with --disable-optimize
- int32_t offset = *((int32_t*)(aOriginalFunction + 1));
- return aOriginalFunction + 5 + offset;
- }
-#endif
-
- return aOriginalFunction;
+ // Short jump up into our long jump.
+ writableFn.WriteShort(0xF9EB); // jmp $-5
+ return writableFn.Commit();
}
};
-} // namespace internal
-
-class WindowsDllInterceptor
-{
- internal::WindowsDllNopSpacePatcher mNopSpacePatcher;
- internal::WindowsDllDetourPatcher mDetourPatcher;
-
- const char* mModuleName;
- int mNHooks;
-
-public:
- explicit WindowsDllInterceptor(const char* aModuleName = nullptr,
- int aNumHooks = 0)
- : mModuleName(nullptr)
- , mNHooks(0)
- {
- if (aModuleName) {
- Init(aModuleName, aNumHooks);
- }
- }
-
- void Init(const char* aModuleName, int aNumHooks = 0)
- {
- if (mModuleName) {
- return;
- }
-
- mModuleName = aModuleName;
- mNHooks = aNumHooks;
- mNopSpacePatcher.Init(aModuleName);
-
- // Lazily initialize mDetourPatcher, since it allocates memory and we might
- // not need it.
- }
-
- /**
- * Hook/detour the method aName from the DLL we set in Init so that it calls
- * aHookDest instead. Returns the original method pointer in aOrigFunc
- * and returns true if successful.
- *
- * IMPORTANT: If you use this method, please add your case to the
- * TestDllInterceptor in order to detect future failures. Even if this
- * succeeds now, updates to the hooked DLL could cause it to fail in
- * the future.
- */
- bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc)
- {
- // Use a nop space patch if possible, otherwise fall back to a detour.
- // This should be the preferred method for adding hooks.
-
- if (!mModuleName) {
- return false;
- }
-
- if (mNopSpacePatcher.AddHook(aName, aHookDest, aOrigFunc)) {
- return true;
- }
-
- return AddDetour(aName, aHookDest, aOrigFunc);
- }
-
- /**
- * Detour the method aName from the DLL we set in Init so that it calls
- * aHookDest instead. Returns the original method pointer in aOrigFunc
- * and returns true if successful.
- *
- * IMPORTANT: If you use this method, please add your case to the
- * TestDllInterceptor in order to detect future failures. Even if this
- * succeeds now, updates to the detoured DLL could cause it to fail in
- * the future.
- */
- bool AddDetour(const char* aName, intptr_t aHookDest, void** aOrigFunc)
- {
- // Generally, code should not call this method directly. Use AddHook unless
- // there is a specific need to avoid nop space patches.
-
- if (!mModuleName) {
- return false;
- }
-
- if (!mDetourPatcher.Initialized()) {
- mDetourPatcher.Init(mModuleName, mNHooks);
- }
-
- return mDetourPatcher.AddHook(aName, aHookDest, aOrigFunc);
- }
-};
-
+} // namespace interceptor
} // namespace mozilla
-#endif /* NS_WINDOWS_DLL_INTERCEPTOR_H_ */
+#endif // defined(_M_IX86)
+
+#endif // mozilla_interceptor_PatcherNopSpace_h
new file mode 100644
--- /dev/null
+++ b/mozglue/misc/interceptor/TargetFunction.h
@@ -0,0 +1,552 @@
+/* -*- 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_TargetFunction_h
+#define mozilla_interceptor_TargetFunction_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Types.h"
+
+#include <memory>
+
+namespace mozilla {
+namespace interceptor {
+
+template <typename MMPolicy>
+class MOZ_STACK_CLASS WritableTargetFunction final
+{
+public:
+ /**
+ * Used to initialize an invalid WritableTargetFunction, thus signalling an error.
+ */
+ explicit WritableTargetFunction(const MMPolicy& aMMPolicy)
+ : mMMPolicy(aMMPolicy)
+ , mFunc(0)
+ , mNumBytes(0)
+ , mOffset(0)
+ , mStartWriteOffset(0)
+ , mPrevProt(0)
+ , mAccumulatedStatus(false)
+ {
+ }
+
+ WritableTargetFunction(const MMPolicy& aMMPolicy, uintptr_t aFunc,
+ size_t aNumBytes)
+ : mMMPolicy(aMMPolicy)
+ , mFunc(aFunc)
+ , mNumBytes(aNumBytes)
+ , mOffset(0)
+ , mStartWriteOffset(0)
+ , mPrevProt(0)
+ , mAccumulatedStatus(true)
+ {
+ aMMPolicy.Protect(reinterpret_cast<void*>(aFunc), aNumBytes,
+ PAGE_EXECUTE_READWRITE, &mPrevProt);
+ }
+
+ WritableTargetFunction(WritableTargetFunction&& aOther)
+ : mMMPolicy(aOther.mMMPolicy)
+ , mFunc(aOther.mFunc)
+ , mNumBytes(aOther.mNumBytes)
+ , mOffset(aOther.mOffset)
+ , mStartWriteOffset(aOther.mStartWriteOffset)
+ , mPrevProt(aOther.mPrevProt)
+ , mLocalBytes(Move(aOther.mLocalBytes))
+ , mAccumulatedStatus(aOther.mAccumulatedStatus)
+ {
+ aOther.mPrevProt = 0;
+ aOther.mAccumulatedStatus = false;
+ }
+
+ ~WritableTargetFunction()
+ {
+ if (!mPrevProt) {
+ return;
+ }
+
+ MOZ_ASSERT(mLocalBytes.empty(), "Did you forget to call Commit?");
+
+ DebugOnly<bool> ok = mMMPolicy.Protect(reinterpret_cast<void*>(mFunc),
+ mNumBytes, mPrevProt, &mPrevProt);
+ MOZ_ASSERT(ok);
+ }
+
+ WritableTargetFunction(const WritableTargetFunction&) = delete;
+ WritableTargetFunction& operator=(const WritableTargetFunction&) = delete;
+ WritableTargetFunction& operator=(WritableTargetFunction&&) = delete;
+
+ /**
+ * @return true if data was successfully committed.
+ */
+ bool Commit()
+ {
+ if (!(*this)) {
+ return false;
+ }
+
+ if (mLocalBytes.empty()) {
+ // Nothing to commit, treat like success
+ return true;
+ }
+
+ bool ok = mMMPolicy.Write(reinterpret_cast<void*>(mFunc + mStartWriteOffset),
+ mLocalBytes.begin(), mLocalBytes.length());
+ if (!ok) {
+ return false;
+ }
+
+ mMMPolicy.FlushInstructionCache();
+ mLocalBytes.clear();
+ return true;
+ }
+
+ explicit operator bool() const
+ {
+ return mPrevProt && mAccumulatedStatus;
+ }
+
+ void WriteByte(const uint8_t& aValue)
+ {
+ if (!mLocalBytes.append(aValue)) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ mOffset += sizeof(uint8_t);
+ }
+
+ Maybe<uint8_t> ReadByte()
+ {
+ // Reading is only permitted prior to any writing
+ MOZ_ASSERT(mOffset == mStartWriteOffset);
+ if (mOffset > mStartWriteOffset) {
+ mAccumulatedStatus = false;
+ return Nothing();
+ }
+
+ uint8_t value;
+ if (!mMMPolicy.Read(&value, reinterpret_cast<const void*>(mFunc + mOffset),
+ sizeof(uint8_t))) {
+ mAccumulatedStatus = false;
+ return Nothing();
+ }
+
+ mOffset += sizeof(uint8_t);
+ mStartWriteOffset += sizeof(uint8_t);
+ return Some(value);
+ }
+
+ void WriteShort(const uint16_t& aValue)
+ {
+ if (!mLocalBytes.append(reinterpret_cast<const uint8_t*>(&aValue),
+ sizeof(uint16_t))) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ mOffset += sizeof(uint16_t);
+ }
+
+ void WriteDisp32(const uintptr_t aAbsTarget)
+ {
+ intptr_t diff = static_cast<intptr_t>(aAbsTarget) -
+ static_cast<intptr_t>(mFunc + mOffset + sizeof(int32_t));
+
+ CheckedInt<int32_t> checkedDisp(diff);
+ MOZ_ASSERT(checkedDisp.isValid());
+ if (!checkedDisp.isValid()) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ int32_t disp = checkedDisp.value();
+ if (!mLocalBytes.append(reinterpret_cast<uint8_t*>(&disp), sizeof(int32_t))) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ mOffset += sizeof(int32_t);
+ }
+
+ void WritePointer(const uintptr_t aAbsTarget)
+ {
+ if (!mLocalBytes.append(reinterpret_cast<const uint8_t*>(&aAbsTarget),
+ sizeof(uintptr_t))) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ mOffset += sizeof(uintptr_t);
+ }
+
+ /**
+ * @param aValues N-sized array of type T that specifies the set of values
+ * that are permissible in the first M bytes of the target
+ * function at aOffset.
+ * @return true if M values of type T in the function are members of the
+ * set specified by aValues.
+ */
+ template <typename T, size_t M, size_t N>
+ bool VerifyValuesAreOneOf(const T (&aValues)[N], const uint8_t aOffset = 0)
+ {
+ T buf[M];
+ if (!mMMPolicy.Read(buf, reinterpret_cast<const void*>(mFunc + mOffset + aOffset),
+ M * sizeof(T))) {
+ return false;
+ }
+
+ for (auto&& fnValue : buf) {
+ bool match = false;
+ for (auto&& testValue : aValues) {
+ match |= (fnValue == testValue);
+ }
+
+ if (!match) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ uintptr_t GetCurrentAddress() const
+ {
+ return mFunc + mOffset;
+ }
+
+private:
+ const MMPolicy& mMMPolicy;
+ const uintptr_t mFunc;
+ const size_t mNumBytes;
+ uint32_t mOffset;
+ uint32_t mStartWriteOffset;
+ uint32_t mPrevProt;
+#if defined(_M_IX86)
+ static const size_t kInlineStorage = 16;
+#elif defined(_M_X64)
+ static const size_t kInlineStorage = 32;
+#endif
+ Vector<uint8_t, kInlineStorage> mLocalBytes;
+ bool mAccumulatedStatus;
+};
+
+template <typename MMPolicy>
+class ReadOnlyTargetBytes;
+
+template <>
+class ReadOnlyTargetBytes<MMPolicyInProcess>
+{
+public:
+ ReadOnlyTargetBytes(const MMPolicyInProcess& aMMPolicy, const void* aBase)
+ : mMMPolicy(aMMPolicy)
+ , mBase(reinterpret_cast<const uint8_t*>(aBase))
+ {
+ }
+
+ ReadOnlyTargetBytes(ReadOnlyTargetBytes&& aOther)
+ : mMMPolicy(aOther.mMMPolicy)
+ , mBase(aOther.mBase)
+ {
+ }
+
+ ReadOnlyTargetBytes(const ReadOnlyTargetBytes& aOther,
+ const uint32_t aOffsetFromOther = 0)
+ : mMMPolicy(aOther.mMMPolicy)
+ , mBase(aOther.mBase + aOffsetFromOther)
+ {
+ }
+
+ void EnsureLimit(uint32_t aDesiredLimit)
+ {
+ // In the out-proc case we use this function to read the target function's
+ // bytes in the other process into a local buffer. We don't need that for
+ // the in-process case because we already have direct access to our target
+ // function's bytes.
+ }
+
+ bool IsValidAtOffset(const int8_t aOffset) const
+ {
+ if (!aOffset) {
+ return true;
+ }
+
+ uintptr_t base = reinterpret_cast<uintptr_t>(mBase);
+ uintptr_t adjusted = base + aOffset;
+ uint32_t pageSize = mMMPolicy.GetPageSize();
+
+ // If |adjusted| is within the same page as |mBase|, we're still valid
+ if ((base / pageSize) == (adjusted / pageSize)) {
+ return true;
+ }
+
+ // Otherwise, let's query |adjusted|
+ return mMMPolicy.IsPageAccessible(reinterpret_cast<void*>(adjusted));
+ }
+
+ const uint8_t* Get() const
+ {
+ return mBase;
+ }
+
+ const MMPolicyInProcess& GetMMPolicy() const
+ {
+ return mMMPolicy;
+ }
+
+ ReadOnlyTargetBytes& operator=(const ReadOnlyTargetBytes&) = delete;
+ ReadOnlyTargetBytes& operator=(ReadOnlyTargetBytes&&) = delete;
+
+private:
+ const MMPolicyInProcess& mMMPolicy;
+ uint8_t const * const mBase;
+};
+
+template <typename MMPolicy>
+class MOZ_STACK_CLASS ReadOnlyTargetFunction final
+{
+ template <typename TargetMMPolicy>
+ class TargetBytesPtr
+ {
+ public:
+ typedef TargetBytesPtr<TargetMMPolicy> Type;
+
+ static Type Make(const TargetMMPolicy& aMMPolicy, const void* aFunc)
+ {
+ return Move(TargetBytesPtr(aMMPolicy, aFunc));
+ }
+
+ static Type CopyFromOffset(const TargetBytesPtr& aOther,
+ const uint32_t aOffsetFromOther)
+ {
+ return Move(TargetBytesPtr(aOther, aOffsetFromOther));
+ }
+
+ ReadOnlyTargetBytes<TargetMMPolicy>* operator->()
+ {
+ return &mTargetBytes;
+ }
+
+ TargetBytesPtr(TargetBytesPtr&& aOther)
+ : mTargetBytes(Move(aOther.mTargetBytes))
+ {
+ }
+
+ TargetBytesPtr(const TargetBytesPtr& aOther)
+ : mTargetBytes(aOther.mTargetBytes)
+ {
+ }
+
+ TargetBytesPtr& operator=(const TargetBytesPtr&) = delete;
+ TargetBytesPtr& operator=(TargetBytesPtr&&) = delete;
+
+ private:
+ TargetBytesPtr(const TargetMMPolicy& aMMPolicy, const void* aFunc)
+ : mTargetBytes(aMMPolicy, aFunc)
+ {
+ }
+
+ TargetBytesPtr(const TargetBytesPtr& aOther,
+ const uint32_t aOffsetFromOther)
+ : mTargetBytes(aOther.mTargetBytes, aOffsetFromOther)
+ {
+ }
+
+ ReadOnlyTargetBytes<TargetMMPolicy> mTargetBytes;
+ };
+
+public:
+ ReadOnlyTargetFunction(const MMPolicy& aMMPolicy, const void* aFunc)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::Make(aMMPolicy, aFunc))
+ , mOffset(0)
+ {
+ }
+
+ ReadOnlyTargetFunction(const MMPolicy& aMMPolicy, uintptr_t aFunc)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::Make(aMMPolicy,
+ reinterpret_cast<const void*>(aFunc)))
+ , mOffset(0)
+ {
+ }
+
+ ReadOnlyTargetFunction(ReadOnlyTargetFunction&& aOther)
+ : mTargetBytes(Move(aOther.mTargetBytes))
+ , mOffset(aOther.mOffset)
+ {
+ }
+
+ ReadOnlyTargetFunction& operator=(const ReadOnlyTargetFunction&) = delete;
+ ReadOnlyTargetFunction& operator=(ReadOnlyTargetFunction&&) = delete;
+
+ ~ReadOnlyTargetFunction() = default;
+
+ ReadOnlyTargetFunction operator+(const uint32_t aOffset) const
+ {
+ return ReadOnlyTargetFunction(*this, mOffset + aOffset);
+ }
+
+ uintptr_t GetBaseAddress() const
+ {
+ return reinterpret_cast<uintptr_t>(mTargetBytes->Get());
+ }
+
+ uintptr_t GetAddress() const
+ {
+ return reinterpret_cast<uintptr_t>(mTargetBytes->Get() + mOffset);
+ }
+
+ uintptr_t AsEncodedPtr() const
+ {
+ return EncodePtr(const_cast<uint8_t*>(mTargetBytes->Get() + mOffset));
+ }
+
+ static uintptr_t EncodePtr(void* aPtr)
+ {
+ return reinterpret_cast<uintptr_t>(::EncodePointer(aPtr));
+ }
+
+ static uintptr_t DecodePtr(uintptr_t aEncodedPtr)
+ {
+ return reinterpret_cast<uintptr_t>(
+ ::DecodePointer(reinterpret_cast<PVOID>(aEncodedPtr)));
+ }
+
+ uint8_t const & operator*() const
+ {
+ mTargetBytes->EnsureLimit(mOffset);
+ return *(mTargetBytes->Get() + mOffset);
+ }
+
+ uint8_t const & operator[](uint32_t aIndex) const
+ {
+ mTargetBytes->EnsureLimit(mOffset + aIndex);
+ return *(mTargetBytes->Get() + mOffset + aIndex);
+ }
+
+ ReadOnlyTargetFunction& operator++()
+ {
+ ++mOffset;
+ return *this;
+ }
+
+ ReadOnlyTargetFunction& operator+=(uint32_t aDelta)
+ {
+ mOffset += aDelta;
+ return *this;
+ }
+
+ uint32_t GetOffset() const
+ {
+ return mOffset;
+ }
+
+ uintptr_t ReadDisp32AsAbsolute()
+ {
+ mTargetBytes->EnsureLimit(mOffset + sizeof(int32_t));
+ int32_t disp = *reinterpret_cast<const int32_t*>(mTargetBytes->Get() + mOffset);
+ uintptr_t result = reinterpret_cast<uintptr_t>(
+ mTargetBytes->Get() + mOffset + sizeof(int32_t) + disp);
+ mOffset += sizeof(int32_t);
+ return result;
+ }
+
+ uintptr_t OffsetToAbsolute(const uint8_t aOffset) const
+ {
+ return reinterpret_cast<uintptr_t>(mTargetBytes->Get() + mOffset + aOffset);
+ }
+
+ /**
+ * This method promotes the code referenced by this object to be writable.
+ *
+ * @param aLen The length of the function's code to make writable. If set
+ * to zero, this object's current offset is used as the length.
+ * @param aOffset The result's base address will be offset from this
+ * object's base address by |aOffset| bytes. This value may be
+ * negative.
+ */
+ WritableTargetFunction<MMPolicy> Promote(const uint32_t aLen = 0,
+ const int8_t aOffset = 0) const
+ {
+ const uint32_t effectiveLength = aLen ? aLen : mOffset;
+ MOZ_RELEASE_ASSERT(effectiveLength, "Cannot Promote a zero-length function");
+
+ if (!mTargetBytes->IsValidAtOffset(aOffset)) {
+ return WritableTargetFunction<MMPolicy>(mTargetBytes->GetMMPolicy());
+ }
+
+ WritableTargetFunction<MMPolicy> result(
+ mTargetBytes->GetMMPolicy(),
+ reinterpret_cast<uintptr_t>(mTargetBytes->Get() + aOffset),
+ effectiveLength);
+
+ return Move(result);
+ }
+
+private:
+ template <typename T>
+ struct ChasePointerHelper
+ {
+ template <typename MMPolicy>
+ static T Result(const MMPolicy&, T aValue)
+ {
+ return aValue;
+ }
+ };
+
+ template <typename T>
+ struct ChasePointerHelper<T*>
+ {
+ template <typename MMPolicy>
+ static auto Result(const MMPolicy& aPolicy, T* aValue)
+ {
+ ReadOnlyTargetFunction<MMPolicy> ptr(aPolicy, aValue);
+ return ptr.ChasePointer<T>();
+ }
+ };
+
+public:
+ // Keep chasing pointers until T is not a pointer type anymore
+ template <typename T>
+ auto ChasePointer()
+ {
+ mTargetBytes->EnsureLimit(mOffset + sizeof(T));
+ const typename RemoveCV<T>::Type result = *reinterpret_cast<const RemoveCV<T>::Type*>(mTargetBytes->Get() + mOffset);
+ return ChasePointerHelper<typename RemoveCV<T>::Type>::Result(mTargetBytes->GetMMPolicy(), result);
+ }
+
+ uintptr_t ChasePointerFromDisp()
+ {
+ uintptr_t ptrFromDisp = ReadDispAsAbsolute();
+ ReadOnlyTargetFunction<MMPolicy> ptr(mTargetBytes->GetMMPolicy(),
+ reinterpret_cast<const void*>(ptrFromDisp));
+ return ptr.template ChasePointer<uintptr_t>();
+ }
+
+private:
+ ReadOnlyTargetFunction(const ReadOnlyTargetFunction& aOther)
+ : mTargetBytes(aOther.mTargetBytes)
+ , mOffset(aOther.mOffset)
+ {
+ }
+
+ ReadOnlyTargetFunction(const ReadOnlyTargetFunction& aOther,
+ const uint32_t aOffsetFromOther)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::CopyFromOffset(aOther.mTargetBytes,
+ aOffsetFromOther))
+ , mOffset(0)
+ {
+ }
+
+private:
+ mutable typename TargetBytesPtr<MMPolicy>::Type mTargetBytes;
+ uint32_t mOffset;
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_TargetFunction_h
new file mode 100644
--- /dev/null
+++ b/mozglue/misc/interceptor/Trampoline.h
@@ -0,0 +1,369 @@
+/* -*- 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/Maybe.h"
+#include "mozilla/Types.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)
+ , mMaxOffset(aChunkSize)
+ , mAccumulatedStatus(true)
+ {
+ ::VirtualProtect(aLocalBase, aChunkSize, PAGE_EXECUTE_READWRITE,
+ &mPrevLocalProt);
+ }
+
+ Trampoline(Trampoline&& aOther)
+ : mMMPolicy(aOther.mMMPolicy)
+ , mPrevLocalProt(aOther.mPrevLocalProt)
+ , mLocalBase(aOther.mLocalBase)
+ , mRemoteBase(aOther.mRemoteBase)
+ , mOffset(aOther.mOffset)
+ , mExeOffset(aOther.mExeOffset)
+ , 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)
+ , mMaxOffset(0)
+ , mAccumulatedStatus(false)
+ {
+ }
+
+ Trampoline(const Trampoline&) = delete;
+ Trampoline& operator=(const Trampoline&) = delete;
+ Trampoline&& operator=(Trampoline&&) = delete;
+
+ ~Trampoline()
+ {
+ if (!mLocalBase || !mPrevLocalProt) {
+ return;
+ }
+
+ ::VirtualProtect(mLocalBase, mMaxOffset, mPrevLocalProt, &mPrevLocalProt);
+ }
+
+ explicit operator bool() const
+ {
+ return mLocalBase && mRemoteBase && mPrevLocalProt && mAccumulatedStatus;
+ }
+
+ void WriteByte(uint8_t aValue)
+ {
+ if (mOffset >= mMaxOffset) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ *(mLocalBase + mOffset) = aValue;
+ ++mOffset;
+ }
+
+ void WriteInteger(int32_t aValue)
+ {
+ if (mOffset + sizeof(int32_t) > mMaxOffset) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ *reinterpret_cast<int32_t*>(mLocalBase + mOffset) = aValue;
+ mOffset += sizeof(int32_t);
+ }
+
+ void WritePointer(uintptr_t aValue)
+ {
+ if (mOffset + sizeof(uintptr_t) > mMaxOffset) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ *reinterpret_cast<uintptr_t*>(mLocalBase + mOffset) = aValue;
+ mOffset += sizeof(uintptr_t);
+ }
+
+ 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 Move(result);
+ }
+
+ Maybe<uintptr_t> ReadEncodedPointer()
+ {
+ Maybe<uintptr_t> encoded(ReadPointer());
+ if (!encoded) {
+ return encoded;
+ }
+
+ return Some(ReadOnlyTargetFunction<MMPolicy>::DecodePtr(encoded.value()));
+ }
+
+ void WriteDisp32(uintptr_t aAbsTarget)
+ {
+ if (mOffset + sizeof(int32_t) > 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 + sizeof(int32_t));
+
+ 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 += sizeof(int32_t);
+ }
+
+#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 || mOffset + aNumBytes > mMaxOffset) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ if (!mMMPolicy->Read(mLocalBase + mOffset,
+ reinterpret_cast<void*>(aOrigBytes), aNumBytes)) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ mOffset += aNumBytes;
+ }
+
+ void Rewind()
+ {
+ mOffset = 0;
+ }
+
+ uintptr_t GetCurrentRemoteAddress() const
+ {
+ return mRemoteBase + mOffset;
+ }
+
+ void StartExecutableCode()
+ {
+ MOZ_ASSERT(!mExeOffset);
+ mExeOffset = mOffset;
+ }
+
+ void* EndExecutableCode() const
+ {
+ if (!mAccumulatedStatus) {
+ return nullptr;
+ }
+
+ // This must always return the start address the executable code
+ // *in the target process*
+ return reinterpret_cast<void*>(mRemoteBase + mExeOffset);
+ }
+
+ Trampoline<MMPolicy>& operator--()
+ {
+ MOZ_ASSERT(mOffset);
+ --mOffset;
+ return *this;
+ }
+
+private:
+ const MMPolicy* mMMPolicy;
+ DWORD mPrevLocalProt;
+ uint8_t* const mLocalBase;
+ const uintptr_t mRemoteBase;
+ uint32_t mOffset;
+ uint32_t mExeOffset;
+ const 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>(nullptr,
+ 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)
+ {
+ }
+
+ 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)
+ {
+ if (!aNumTramps) {
+ return;
+ }
+
+ DebugOnly<BOOL> ok = mMMPolicy.Protect(aLocalBase, aNumTramps * aTrampSize,
+ PAGE_EXECUTE_READWRITE, &mPrevProt);
+ MOZ_ASSERT(ok);
+ }
+
+ ~TrampolineCollection()
+ {
+ if (!mPrevProt) {
+ return;
+ }
+
+ mMMPolicy.Protect(mLocalBase, mNumTramps * mTrampSize,
+ mPrevProt, &mPrevProt);
+ }
+
+ 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)
+ {
+ aOther.mPrevProt = 0;
+ }
+
+private:
+ const MMPolicy& mMMPolicy;
+ uint8_t* const mLocalBase;
+ const uintptr_t mRemoteBase;
+ const uint32_t mTrampSize;
+ const uint32_t mNumTramps;
+ uint32_t mPrevProt;
+
+ friend class TrampolineIterator;
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_Trampoline_h
new file mode 100644
--- /dev/null
+++ b/mozglue/misc/interceptor/VMSharingPolicies.h
@@ -0,0 +1,90 @@
+/* -*- 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_VMSharingPolicies_h
+#define mozilla_interceptor_VMSharingPolicies_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Types.h"
+
+namespace mozilla {
+namespace interceptor {
+
+template <typename MMPolicy, uint32_t kChunkSize>
+class VMSharingPolicyUnique : public MMPolicy
+{
+public:
+ template <typename... Args>
+ explicit VMSharingPolicyUnique(Args... aArgs)
+ : MMPolicy(mozilla::Forward<Args>(aArgs)...)
+ , mNextChunkIndex(0)
+ {
+ }
+
+ bool Reserve(uint32_t aCount)
+ {
+ MOZ_ASSERT(aCount);
+ uint32_t bytesReserved = MMPolicy::Reserve(aCount * kChunkSize);
+ return !!bytesReserved;
+ }
+
+ Trampoline<MMPolicy> GetNextTrampoline()
+ {
+ uint32_t offset = mNextChunkIndex * kChunkSize;
+ if (!MaybeCommitNextPage(offset, kChunkSize)) {
+ return nullptr;
+ }
+
+
+ Trampoline<MMPolicy> result(this, GetLocalView() + offset,
+ GetRemoteView() + offset, kChunkSize);
+ if (!!result) {
+ ++mNextChunkIndex;
+ }
+
+ return Move(result);
+ }
+
+ TrampolineCollection<MMPolicy> Items() const
+ {
+ return TrampolineCollection<MMPolicy>(*this, GetLocalView(), GetRemoteView(),
+ kChunkSize, mNextChunkIndex);
+ }
+
+ void Clear()
+ {
+ mNextChunkIndex = 0;
+ }
+
+ ~VMSharingPolicyUnique() = default;
+
+ VMSharingPolicyUnique(const VMSharingPolicyUnique&) = delete;
+ VMSharingPolicyUnique& operator=(const VMSharingPolicyUnique&) = delete;
+
+ VMSharingPolicyUnique(VMSharingPolicyUnique&& aOther)
+ : MMPolicy(Move(aOther))
+ , mNextChunkIndex(aOther.mNextChunkIndex)
+ {
+ aOther.mNextChunkIndex = 0;
+ }
+
+ VMSharingPolicyUnique& operator=(VMSharingPolicyUnique&& aOther)
+ {
+ static_cast<MMPolicy&>(*this) = Move(aOther);
+ mNextChunkIndex = aOther.mNextChunkIndex;
+ aOther.mNextChunkIndex = 0;
+ return *this;
+ }
+
+private:
+ uint32_t mNextChunkIndex;
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_VMSharingPolicies_h
+
--- a/mozglue/misc/moz.build
+++ b/mozglue/misc/moz.build
@@ -28,16 +28,28 @@ SOURCES += [
'TimeStamp.cpp',
]
OS_LIBS += CONFIG['REALTIME_LIBS']
DEFINES['IMPL_MFBT'] = True
if CONFIG['OS_ARCH'] == 'WINNT':
+ EXPORTS += [
+ 'nsWindowsDllInterceptor.h',
+ ]
+ EXPORTS.mozilla.interceptor += [
+ 'interceptor/MMPolicies.h',
+ 'interceptor/PatcherBase.h',
+ 'interceptor/PatcherDetour.h',
+ 'interceptor/PatcherNopSpace.h',
+ 'interceptor/TargetFunction.h',
+ 'interceptor/Trampoline.h',
+ 'interceptor/VMSharingPolicies.h',
+ ]
SOURCES += [
'TimeStamp_windows.cpp',
]
OS_LIBS += ['dbghelp']
elif CONFIG['HAVE_CLOCK_MONOTONIC']:
SOURCES += [
'TimeStamp_posix.cpp',
]
copy from xpcom/build/nsWindowsDllInterceptor.h
copy to mozglue/misc/nsWindowsDllInterceptor.h
--- a/xpcom/build/nsWindowsDllInterceptor.h
+++ b/mozglue/misc/nsWindowsDllInterceptor.h
@@ -4,23 +4,34 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef NS_WINDOWS_DLL_INTERCEPTOR_H_
#define NS_WINDOWS_DLL_INTERCEPTOR_H_
#include "mozilla/Assertions.h"
#include "mozilla/ArrayUtils.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/TypeTraits.h"
+#include "mozilla/Types.h"
#include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
#include "nsWindowsHelpers.h"
#include <wchar.h>
#include <windows.h>
#include <winternl.h>
+#include "mozilla/interceptor/MMPolicies.h"
+#include "mozilla/interceptor/PatcherDetour.h"
+#include "mozilla/interceptor/PatcherNopSpace.h"
+#include "mozilla/interceptor/VMSharingPolicies.h"
+
/*
* Simple function interception.
*
* We have two separate mechanisms for intercepting a function: We can use the
* built-in nop space, if it exists, or we can create a detour.
*
* Using the built-in nop space works as follows: On x86-32, DLL functions
* begin with a two-byte nop (mov edi, edi) and are preceeded by five bytes of
@@ -63,1409 +74,127 @@
* patched again to jump directly to the trampoline instead of going through
* the hook function. As such, re-intercepting the same function won't work, as
* jump instructions are not supported.
*
* Note that this is not thread-safe. Sad day.
*
*/
-#include <stdint.h>
-
-#define COPY_CODES(NBYTES) do { \
- memcpy(&tramp[nTrampBytes], &origBytes[nOrigBytes], NBYTES); \
- nOrigBytes += NBYTES; \
- nTrampBytes += NBYTES; \
-} while (0)
-
namespace mozilla {
-namespace internal {
-
-class AutoVirtualProtect
-{
-public:
- AutoVirtualProtect(void* aFunc, size_t aSize, DWORD aProtect)
- : mFunc(aFunc), mSize(aSize), mNewProtect(aProtect), mOldProtect(0),
- mSuccess(false)
- {}
+namespace interceptor {
- ~AutoVirtualProtect()
- {
- if (mSuccess) {
- VirtualProtectEx(GetCurrentProcess(), mFunc, mSize, mOldProtect,
- &mOldProtect);
- }
- }
-
- bool Protect()
- {
- mSuccess = !!VirtualProtectEx(GetCurrentProcess(), mFunc, mSize,
- mNewProtect, &mOldProtect);
- if (!mSuccess) {
- // printf("VirtualProtectEx failed! %d\n", GetLastError());
- }
- return mSuccess;
- }
-
-private:
- void* const mFunc;
- size_t const mSize;
- DWORD const mNewProtect;
- DWORD mOldProtect;
- bool mSuccess;
+enum
+{
+ kDefaultTrampolineSize = 128
};
-class WindowsDllNopSpacePatcher
+template <typename VMPolicy =
+ mozilla::interceptor::VMSharingPolicyUnique<
+ mozilla::interceptor::MMPolicyInProcess, kDefaultTrampolineSize>>
+class WindowsDllInterceptor final
{
- typedef uint8_t* byteptr_t;
- HMODULE mModule;
+ interceptor::WindowsDllDetourPatcher<VMPolicy> mDetourPatcher;
+#if defined(_M_IX86)
+ interceptor::WindowsDllNopSpacePatcher<typename VMPolicy::MMPolicyT> mNopSpacePatcher;
+#endif // defined(_M_IX86)
- // Dumb array for remembering the addresses of functions we've patched.
- // (This should be nsTArray, but non-XPCOM code uses this class.)
- static const size_t maxPatchedFns = 16;
- byteptr_t mPatchedFns[maxPatchedFns];
- size_t mPatchedFnsLen;
+ HMODULE mModule;
+ int mNHooks;
public:
- WindowsDllNopSpacePatcher()
- : mModule(0)
- , mPatchedFnsLen(0)
- {}
-
+ template <typename... Args>
+ explicit WindowsDllInterceptor(Args... aArgs)
+ : mDetourPatcher(mozilla::Forward<Args>(aArgs)...)
#if defined(_M_IX86)
- ~WindowsDllNopSpacePatcher()
- {
- // Restore the mov edi, edi to the beginning of each function we patched.
-
- for (size_t i = 0; i < mPatchedFnsLen; i++) {
- byteptr_t fn = mPatchedFns[i];
-
- // Ensure we can write to the code.
- AutoVirtualProtect protect(fn, 2, PAGE_EXECUTE_READWRITE);
- if (!protect.Protect()) {
- continue;
- }
-
- // mov edi, edi
- *((uint16_t*)fn) = 0xff8b;
-
- // I don't think this is actually necessary, but it can't hurt.
- FlushInstructionCache(GetCurrentProcess(),
- /* ignored */ nullptr,
- /* ignored */ 0);
- }
- }
-
- void Init(const char* aModuleName)
- {
- if (!IsCompatible()) {
-#if defined(MOZILLA_INTERNAL_API)
- NS_WARNING("NOP space patching is unavailable for compatibility reasons");
-#endif
- return;
- }
-
- mModule = LoadLibraryExA(aModuleName, nullptr, 0);
- if (!mModule) {
- //printf("LoadLibraryEx for '%s' failed\n", aModuleName);
- return;
- }
- }
-
- /**
- * NVIDIA Optimus drivers utilize Microsoft Detours 2.x to patch functions
- * in our address space. There is a bug in Detours 2.x that causes it to
- * patch at the wrong address when attempting to detour code that is already
- * NOP space patched. This function is an effort to detect the presence of
- * this NVIDIA code in our address space and disable NOP space patching if it
- * is. We also check AppInit_DLLs since this is the mechanism that the Optimus
- * drivers use to inject into our process.
- */
- static bool IsCompatible()
- {
- // These DLLs are known to have bad interactions with this style of patching
- const wchar_t* kIncompatibleDLLs[] = {
- L"detoured.dll",
- L"_etoured.dll",
- L"nvd3d9wrap.dll",
- L"nvdxgiwrap.dll"
- };
- // See if the infringing DLLs are already loaded
- for (unsigned int i = 0; i < mozilla::ArrayLength(kIncompatibleDLLs); ++i) {
- if (GetModuleHandleW(kIncompatibleDLLs[i])) {
- return false;
- }
- }
- if (GetModuleHandleW(L"user32.dll")) {
- // user32 is loaded but the infringing DLLs are not, assume we're safe to
- // proceed.
- return true;
- }
- // If user32 has not loaded yet, check AppInit_DLLs to ensure that Optimus
- // won't be loaded once user32 is initialized.
- HKEY hkey = NULL;
- if (!RegOpenKeyExW(HKEY_LOCAL_MACHINE,
- L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows",
- 0, KEY_QUERY_VALUE, &hkey)) {
- nsAutoRegKey key(hkey);
- DWORD numBytes = 0;
- const wchar_t kAppInitDLLs[] = L"AppInit_DLLs";
- // Query for required buffer size
- LONG status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr,
- nullptr, nullptr, &numBytes);
- mozilla::UniquePtr<wchar_t[]> data;
- if (!status) {
- // Allocate the buffer and query for the actual data
- data = mozilla::MakeUnique<wchar_t[]>(numBytes / sizeof(wchar_t));
- status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr,
- nullptr, (LPBYTE)data.get(), &numBytes);
- }
- if (!status) {
- // For each token, split up the filename components and then check the
- // name of the file.
- const wchar_t kDelimiters[] = L", ";
- wchar_t* tokenContext = nullptr;
- wchar_t* token = wcstok_s(data.get(), kDelimiters, &tokenContext);
- while (token) {
- wchar_t fname[_MAX_FNAME] = {0};
- if (!_wsplitpath_s(token, nullptr, 0, nullptr, 0,
- fname, mozilla::ArrayLength(fname),
- nullptr, 0)) {
- // nvinit.dll is responsible for bootstrapping the DLL injection, so
- // that is the library that we check for here
- const wchar_t kNvInitName[] = L"nvinit";
- if (!_wcsnicmp(fname, kNvInitName,
- mozilla::ArrayLength(kNvInitName))) {
- return false;
- }
- }
- token = wcstok_s(nullptr, kDelimiters, &tokenContext);
- }
- }
- }
- return true;
- }
-
- bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc)
- {
- if (!mModule) {
- return false;
- }
-
- if (!IsCompatible()) {
-#if defined(MOZILLA_INTERNAL_API)
- NS_WARNING("NOP space patching is unavailable for compatibility reasons");
-#endif
- return false;
- }
-
- MOZ_RELEASE_ASSERT(mPatchedFnsLen < maxPatchedFns, "No room for the hook");
-
- byteptr_t fn = reinterpret_cast<byteptr_t>(GetProcAddress(mModule, aName));
- if (!fn) {
- //printf ("GetProcAddress failed\n");
- return false;
- }
-
- fn = ResolveRedirectedAddress(fn);
-
- // Ensure we can read and write starting at fn - 5 (for the long jmp we're
- // going to write) and ending at fn + 2 (for the short jmp up to the long
- // jmp). These bytes may span two pages with different protection.
- AutoVirtualProtect protectBefore(fn - 5, 5, PAGE_EXECUTE_READWRITE);
- AutoVirtualProtect protectAfter(fn, 2, PAGE_EXECUTE_READWRITE);
- if (!protectBefore.Protect() || !protectAfter.Protect()) {
- return false;
- }
-
- bool rv = WriteHook(fn, aHookDest, aOrigFunc);
-
- if (rv) {
- mPatchedFns[mPatchedFnsLen] = fn;
- mPatchedFnsLen++;
- }
-
- return rv;
- }
-
- bool WriteHook(byteptr_t aFn, intptr_t aHookDest, void** aOrigFunc)
- {
- // Check that the 5 bytes before aFn are NOP's or INT 3's,
- // and that the 2 bytes after aFn are mov(edi, edi).
- //
- // It's safe to read aFn[-5] because we set it to PAGE_EXECUTE_READWRITE
- // before calling WriteHook.
-
- for (int i = -5; i <= -1; i++) {
- if (aFn[i] != 0x90 && aFn[i] != 0xcc) { // nop or int 3
- return false;
- }
- }
-
- // mov edi, edi. Yes, there are two ways to encode the same thing:
- //
- // 0x89ff == mov r/m, r
- // 0x8bff == mov r, r/m
- //
- // where "r" is register and "r/m" is register or memory. Windows seems to
- // use 8bff; I include 89ff out of paranoia.
- if ((aFn[0] != 0x8b && aFn[0] != 0x89) || aFn[1] != 0xff) {
- return false;
- }
-
- // Write a long jump into the space above the function.
- aFn[-5] = 0xe9; // jmp
- *((intptr_t*)(aFn - 4)) = aHookDest - (uintptr_t)(aFn); // target displacement
-
- // Set aOrigFunc here, because after this point, aHookDest might be called,
- // and aHookDest might use the aOrigFunc pointer.
- *aOrigFunc = aFn + 2;
-
- // Short jump up into our long jump.
- *((uint16_t*)(aFn)) = 0xf9eb; // jmp $-5
-
- // I think this routine is safe without this, but it can't hurt.
- FlushInstructionCache(GetCurrentProcess(),
- /* ignored */ nullptr,
- /* ignored */ 0);
-
- return true;
- }
-
-private:
- static byteptr_t ResolveRedirectedAddress(const byteptr_t aOriginalFunction)
- {
- // If function entry is jmp rel8 stub to the internal implementation, we
- // resolve redirected address from the jump target.
- if (aOriginalFunction[0] == 0xeb) {
- int8_t offset = (int8_t)(aOriginalFunction[1]);
- if (offset <= 0) {
- // Bail out for negative offset: probably already patched by some
- // third-party code.
- return aOriginalFunction;
- }
-
- for (int8_t i = 0; i < offset; i++) {
- if (aOriginalFunction[2 + i] != 0x90) {
- // Bail out on insufficient nop space.
- return aOriginalFunction;
- }
- }
-
- return aOriginalFunction + 2 + offset;
- }
-
- // If function entry is jmp [disp32] such as used by kernel32,
- // we resolve redirected address from import table.
- if (aOriginalFunction[0] == 0xff && aOriginalFunction[1] == 0x25) {
- return (byteptr_t)(**((uint32_t**) (aOriginalFunction + 2)));
- }
-
- return aOriginalFunction;
- }
-#else
- void Init(const char* aModuleName)
- {
- // Not implemented except on x86-32.
- }
-
- bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc)
- {
- // Not implemented except on x86-32.
- return false;
- }
-#endif
-};
-
-class WindowsDllDetourPatcher
-{
- typedef unsigned char* byteptr_t;
-public:
- WindowsDllDetourPatcher()
- : mModule(0), mHookPage(0), mMaxHooks(0), mCurHooks(0)
+ , mNopSpacePatcher(mozilla::Forward<Args>(aArgs)...)
+#endif // defined(_M_IX86)
+ , mModule(nullptr)
+ , mNHooks(0)
{
}
- ~WindowsDllDetourPatcher()
- {
- int i;
- byteptr_t p;
- for (i = 0, p = mHookPage; i < mCurHooks; i++, p += kHookSize) {
-#if defined(_M_IX86)
- size_t nBytes = 1 + sizeof(intptr_t);
-#elif defined(_M_X64)
- size_t nBytes = 2 + sizeof(intptr_t);
-#else
-#error "Unknown processor type"
-#endif
- byteptr_t origBytes = (byteptr_t)DecodePointer(*((byteptr_t*)p));
+ WindowsDllInterceptor(const WindowsDllInterceptor&) = delete;
+ WindowsDllInterceptor(WindowsDllInterceptor&&) = delete;
+ WindowsDllInterceptor& operator=(const WindowsDllInterceptor&) = delete;
+ WindowsDllInterceptor& operator=(WindowsDllInterceptor&&) = delete;
- // ensure we can modify the original code
- AutoVirtualProtect protect(origBytes, nBytes, PAGE_EXECUTE_READWRITE);
- if (!protect.Protect()) {
- continue;
- }
-
- // Remove the hook by making the original function jump directly
- // in the trampoline.
- intptr_t dest = (intptr_t)(p + sizeof(void*));
-#if defined(_M_IX86)
- // Ensure the JMP from CreateTrampoline is where we expect it to be.
- if (origBytes[0] != 0xE9)
- continue;
- *((intptr_t*)(origBytes + 1)) =
- dest - (intptr_t)(origBytes + 5); // target displacement
-#elif defined(_M_X64)
- // Ensure the MOV R11 from CreateTrampoline is where we expect it to be.
- if (origBytes[0] != 0x49 || origBytes[1] != 0xBB)
- continue;
- *((intptr_t*)(origBytes + 2)) = dest;
-#else
-#error "Unknown processor type"
-#endif
- }
+ ~WindowsDllInterceptor()
+ {
+ Clear();
}
- void Init(const char* aModuleName, int aNumHooks = 0)
+ template <size_t N>
+ void Init(const char (&aModuleName)[N], int aNumHooks = 0)
+ {
+ wchar_t moduleName[N];
+
+ for (size_t i = 0; i < N; ++i) {
+ MOZ_ASSERT(!(aModuleName[i] & 0x80),
+ "Use wide-character overload for non-ASCII module names");
+ moduleName[i] = aModuleName[i];
+ }
+
+ Init(moduleName, aNumHooks);
+ }
+
+ void Init(const wchar_t* aModuleName, int aNumHooks = 0)
{
if (mModule) {
return;
}
- mModule = LoadLibraryExA(aModuleName, nullptr, 0);
- if (!mModule) {
- //printf("LoadLibraryEx for '%s' failed\n", aModuleName);
- return;
- }
-
- int hooksPerPage = 4096 / kHookSize;
- if (aNumHooks == 0) {
- aNumHooks = hooksPerPage;
- }
-
- mMaxHooks = aNumHooks + (hooksPerPage % aNumHooks);
-
- mHookPage = (byteptr_t)VirtualAllocEx(GetCurrentProcess(), nullptr,
- mMaxHooks * kHookSize,
- MEM_COMMIT | MEM_RESERVE,
- PAGE_EXECUTE_READ);
- if (!mHookPage) {
- mModule = 0;
- return;
- }
- }
-
- bool Initialized() { return !!mModule; }
-
- bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc)
- {
- if (!mModule) {
- return false;
- }
-
- void* pAddr = (void*)GetProcAddress(mModule, aName);
- if (!pAddr) {
- //printf ("GetProcAddress failed\n");
- return false;
- }
-
- pAddr = ResolveRedirectedAddress((byteptr_t)pAddr);
-
- CreateTrampoline(pAddr, aHookDest, aOrigFunc);
- if (!*aOrigFunc) {
- //printf ("CreateTrampoline failed\n");
- return false;
- }
-
- return true;
- }
-
-protected:
- const static int kPageSize = 4096;
- const static int kHookSize = 128;
-
- HMODULE mModule;
- byteptr_t mHookPage;
- int mMaxHooks;
- int mCurHooks;
-
- // rex bits
- static const BYTE kMaskHighNibble = 0xF0;
- static const BYTE kRexOpcode = 0x40;
- static const BYTE kMaskRexW = 0x08;
- static const BYTE kMaskRexR = 0x04;
- static const BYTE kMaskRexX = 0x02;
- static const BYTE kMaskRexB = 0x01;
-
- // mod r/m bits
- static const BYTE kRegFieldShift = 3;
- static const BYTE kMaskMod = 0xC0;
- static const BYTE kMaskReg = 0x38;
- static const BYTE kMaskRm = 0x07;
- static const BYTE kRmNeedSib = 0x04;
- static const BYTE kModReg = 0xC0;
- static const BYTE kModDisp32 = 0x80;
- static const BYTE kModDisp8 = 0x40;
- static const BYTE kModNoRegDisp = 0x00;
- static const BYTE kRmNoRegDispDisp32 = 0x05;
-
- // sib bits
- static const BYTE kMaskSibScale = 0xC0;
- static const BYTE kMaskSibIndex = 0x38;
- static const BYTE kMaskSibBase = 0x07;
- static const BYTE kSibBaseEbp = 0x05;
-
- // Register bit IDs.
- static const BYTE kRegAx = 0x0;
- static const BYTE kRegCx = 0x1;
- static const BYTE kRegDx = 0x2;
- static const BYTE kRegBx = 0x3;
- static const BYTE kRegSp = 0x4;
- static const BYTE kRegBp = 0x5;
- static const BYTE kRegSi = 0x6;
- static const BYTE kRegDi = 0x7;
-
- // Special ModR/M codes. These indicate operands that cannot be simply
- // memcpy-ed.
- // Operand is a 64-bit RIP-relative address.
- static const int kModOperand64 = -2;
- // Operand is not yet handled by our trampoline.
- static const int kModUnknown = -1;
-
- /**
- * Returns the number of bytes taken by the ModR/M byte, SIB (if present)
- * and the instruction's operand. In special cases, the special MODRM codes
- * above are returned.
- * aModRm points to the ModR/M byte of the instruction.
- * On return, aSubOpcode (if present) is filled with the subopcode/register
- * code found in the ModR/M byte.
- */
- int CountModRmSib(const BYTE *aModRm, BYTE* aSubOpcode = nullptr)
- {
- if (!aModRm) {
- MOZ_ASSERT(aModRm, "Missing ModRM byte");
- return kModUnknown;
- }
- int numBytes = 1; // Start with 1 for mod r/m byte itself
- switch (*aModRm & kMaskMod) {
- case kModReg:
- return numBytes;
- case kModDisp8:
- numBytes += 1;
- break;
- case kModDisp32:
- numBytes += 4;
- break;
- case kModNoRegDisp:
- if ((*aModRm & kMaskRm) == kRmNoRegDispDisp32) {
-#if defined(_M_X64)
- if (aSubOpcode) {
- *aSubOpcode = (*aModRm & kMaskReg) >> kRegFieldShift;
- }
- return kModOperand64;
-#else
- // On IA-32, all ModR/M instruction modes address memory relative to 0
- numBytes += 4;
-#endif
- } else if (((*aModRm & kMaskRm) == kRmNeedSib &&
- (*(aModRm + 1) & kMaskSibBase) == kSibBaseEbp)) {
- numBytes += 4;
- }
- break;
- default:
- // This should not be reachable
- MOZ_ASSERT_UNREACHABLE("Impossible value for modr/m byte mod bits");
- return kModUnknown;
- }
- if ((*aModRm & kMaskRm) == kRmNeedSib) {
- // SIB byte
- numBytes += 1;
- }
- if (aSubOpcode) {
- *aSubOpcode = (*aModRm & kMaskReg) >> kRegFieldShift;
- }
- return numBytes;
- }
-
-#if defined(_M_X64)
- // To patch for JMP and JE
-
- enum JumpType {
- Je,
- Jne,
- Jmp,
- Call
- };
-
- struct JumpPatch {
- JumpPatch()
- : mHookOffset(0), mJumpAddress(0), mType(JumpType::Jmp)
- {
- }
-
- JumpPatch(size_t aOffset, intptr_t aAddress, JumpType aType = JumpType::Jmp)
- : mHookOffset(aOffset), mJumpAddress(aAddress), mType(aType)
- {
- }
-
- size_t GenerateJump(uint8_t* aCode)
- {
- size_t offset = mHookOffset;
- if (mType == JumpType::Je) {
- // JNE RIP+14
- aCode[offset] = 0x75;
- aCode[offset + 1] = 14;
- offset += 2;
- } else if (mType == JumpType::Jne) {
- // JE RIP+14
- aCode[offset] = 0x74;
- aCode[offset + 1] = 14;
- offset += 2;
- }
-
- // Near call/jmp, absolute indirect, address given in r/m32
- if (mType == JumpType::Call) {
- // CALL [RIP+0]
- aCode[offset] = 0xff;
- aCode[offset + 1] = 0x15;
- // The offset to jump destination -- ie it is placed 2 bytes after the offset.
- *reinterpret_cast<int32_t*>(aCode + offset + 2) = 2;
- aCode[offset + 2 + 4] = 0xeb; // JMP +8 (jump over mJumpAddress)
- aCode[offset + 2 + 4 + 1] = 8;
- *reinterpret_cast<int64_t*>(aCode + offset + 2 + 4 + 2) = mJumpAddress;
- return offset + 2 + 4 + 2 + 8;
- } else {
- // JMP [RIP+0]
- aCode[offset] = 0xff;
- aCode[offset + 1] = 0x25;
- // The offset to jump destination is 0
- *reinterpret_cast<int32_t*>(aCode + offset + 2) = 0;
- *reinterpret_cast<int64_t*>(aCode + offset + 2 + 4) = mJumpAddress;
- return offset + 2 + 4 + 8;
- }
- }
-
- size_t mHookOffset;
- intptr_t mJumpAddress;
- JumpType mType;
- };
-
-#endif
-
- enum ePrefixGroupBits
- {
- eNoPrefixes = 0,
- ePrefixGroup1 = (1 << 0),
- ePrefixGroup2 = (1 << 1),
- ePrefixGroup3 = (1 << 2),
- ePrefixGroup4 = (1 << 3)
- };
-
- int CountPrefixBytes(byteptr_t aBytes, const int aBytesIndex,
- unsigned char* aOutGroupBits)
- {
- unsigned char& groupBits = *aOutGroupBits;
- groupBits = eNoPrefixes;
- int index = aBytesIndex;
- while (true) {
- switch (aBytes[index]) {
- // Group 1
- case 0xF0: // LOCK
- case 0xF2: // REPNZ
- case 0xF3: // REP / REPZ
- if (groupBits & ePrefixGroup1) {
- return -1;
- }
- groupBits |= ePrefixGroup1;
- ++index;
- break;
-
- // Group 2
- case 0x2E: // CS override / branch not taken
- case 0x36: // SS override
- case 0x3E: // DS override / branch taken
- case 0x64: // FS override
- case 0x65: // GS override
- if (groupBits & ePrefixGroup2) {
- return -1;
- }
- groupBits |= ePrefixGroup2;
- ++index;
- break;
-
- // Group 3
- case 0x66: // operand size override
- if (groupBits & ePrefixGroup3) {
- return -1;
- }
- groupBits |= ePrefixGroup3;
- ++index;
- break;
-
- // Group 4
- case 0x67: // Address size override
- if (groupBits & ePrefixGroup4) {
- return -1;
- }
- groupBits |= ePrefixGroup4;
- ++index;
- break;
-
- default:
- return index - aBytesIndex;
- }
- }
- }
-
- // Return a ModR/M byte made from the 2 Mod bits, the register used for the
- // reg bits and the register used for the R/M bits.
- BYTE BuildModRmByte(BYTE aModBits, BYTE aReg, BYTE aRm)
- {
- MOZ_ASSERT((aRm & kMaskRm) == aRm);
- MOZ_ASSERT((aModBits & kMaskMod) == aModBits);
- MOZ_ASSERT(((aReg << kRegFieldShift) & kMaskReg) == (aReg << kRegFieldShift));
- return aModBits | (aReg << kRegFieldShift) | aRm;
+ mModule = ::LoadLibraryW(aModuleName);
+ mNHooks = aNumHooks;
}
- void CreateTrampoline(void* aOrigFunction, intptr_t aDest, void** aOutTramp)
+ void Clear()
{
- *aOutTramp = nullptr;
-
- AutoVirtualProtect protectHookPage(mHookPage, mMaxHooks * kHookSize,
- PAGE_EXECUTE_READWRITE);
- if (!protectHookPage.Protect()) {
- return;
- }
-
- byteptr_t tramp = FindTrampolineSpace();
- if (!tramp) {
- return;
- }
-
- // We keep the address of the original function in the first bytes of
- // the trampoline buffer
- *((void**)tramp) = EncodePointer(aOrigFunction);
- tramp += sizeof(void*);
-
- byteptr_t origBytes = (byteptr_t)aOrigFunction;
-
- // # of bytes of the original function that we can overwrite.
- int nOrigBytes = 0;
-
-#if defined(_M_IX86)
- int pJmp32 = -1;
- while (nOrigBytes < 5) {
- // Understand some simple instructions that might be found in a
- // prologue; we might need to extend this as necessary.
- //
- // Note! If we ever need to understand jump instructions, we'll
- // need to rewrite the displacement argument.
- unsigned char prefixGroups;
- int numPrefixBytes = CountPrefixBytes(origBytes, nOrigBytes, &prefixGroups);
- if (numPrefixBytes < 0 || (prefixGroups & (ePrefixGroup3 | ePrefixGroup4))) {
- // Either the prefix sequence was bad, or there are prefixes that
- // we don't currently support (groups 3 and 4)
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- nOrigBytes += numPrefixBytes;
- if (origBytes[nOrigBytes] >= 0x88 &&
- origBytes[nOrigBytes] <= 0x8B) {
- // various MOVs
- ++nOrigBytes;
- int len = CountModRmSib(origBytes + nOrigBytes);
- if (len < 0) {
- MOZ_ASSERT_UNREACHABLE("Unrecognized MOV opcode sequence");
- return;
- }
- nOrigBytes += len;
- } else if (origBytes[nOrigBytes] == 0x0f &&
- (origBytes[nOrigBytes + 1] == 0x10 ||
- origBytes[nOrigBytes + 1] == 0x11)) {
- // SSE: movups xmm, xmm/m128
- // movups xmm/m128, xmm
- nOrigBytes += 2;
- int len = CountModRmSib(origBytes + nOrigBytes);
- if (len < 0) {
- MOZ_ASSERT_UNREACHABLE("Unrecognized MOV opcode sequence");
- return;
- }
- nOrigBytes += len;
- } else if (origBytes[nOrigBytes] == 0xA1) {
- // MOV eax, [seg:offset]
- nOrigBytes += 5;
- } else if (origBytes[nOrigBytes] == 0xB8) {
- // MOV 0xB8: http://ref.x86asm.net/coder32.html#xB8
- nOrigBytes += 5;
- } else if (origBytes[nOrigBytes] == 0x33 &&
- (origBytes[nOrigBytes+1] & kMaskMod) == kModReg) {
- // XOR r32, r32
- nOrigBytes += 2;
- } else if ((origBytes[nOrigBytes] & 0xf8) == 0x40) {
- // INC r32
- nOrigBytes += 1;
- } else if (origBytes[nOrigBytes] == 0x83) {
- // ADD|ODR|ADC|SBB|AND|SUB|XOR|CMP r/m, imm8
- unsigned char b = origBytes[nOrigBytes + 1];
- if ((b & 0xc0) == 0xc0) {
- // ADD|ODR|ADC|SBB|AND|SUB|XOR|CMP r, imm8
- nOrigBytes += 3;
- } else {
- // bail
- MOZ_ASSERT_UNREACHABLE("Unrecognized bit opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x68) {
- // PUSH with 4-byte operand
- nOrigBytes += 5;
- } else if ((origBytes[nOrigBytes] & 0xf0) == 0x50) {
- // 1-byte PUSH/POP
- nOrigBytes++;
- } else if (origBytes[nOrigBytes] == 0x6A) {
- // PUSH imm8
- nOrigBytes += 2;
- } else if (origBytes[nOrigBytes] == 0xe9) {
- pJmp32 = nOrigBytes;
- // jmp 32bit offset
- nOrigBytes += 5;
- } else if (origBytes[nOrigBytes] == 0xff &&
- origBytes[nOrigBytes + 1] == 0x25) {
- // jmp [disp32]
- nOrigBytes += 6;
- } else if (origBytes[nOrigBytes] == 0xc2) {
- // ret imm16. We can't handle this but it happens. We don't ASSERT but we do fail to hook.
-#if defined(MOZILLA_INTERNAL_API)
- NS_WARNING("Cannot hook method -- RET opcode found");
-#endif
- return;
- } else {
- //printf ("Unknown x86 instruction byte 0x%02x, aborting trampoline\n", origBytes[nOrigBytes]);
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- }
-
- // The trampoline is a copy of the instructions that we just traced,
- // followed by a jump that we add below.
- memcpy(tramp, aOrigFunction, nOrigBytes);
-#elif defined(_M_X64)
- // The number of bytes used by the trampoline.
- int nTrampBytes = 0;
- bool foundJmp = false;
-
- while (nOrigBytes < 13) {
- // If we found JMP 32bit offset, we require that the next bytes must
- // be NOP or INT3. There is no reason to copy them.
- // TODO: This used to trigger for Je as well. Now that I allow
- // instructions after CALL and JE, I don't think I need that.
- // The only real value of this condition is that if code follows a JMP
- // then its _probably_ the target of a JMP somewhere else and we
- // will be overwriting it, which would be tragic. This seems
- // highly unlikely.
- if (foundJmp) {
- if (origBytes[nOrigBytes] == 0x90 || origBytes[nOrigBytes] == 0xcc) {
- nOrigBytes++;
- continue;
- }
- MOZ_ASSERT_UNREACHABLE("Opcode sequence includes commands after JMP");
- return;
- }
- if (origBytes[nOrigBytes] == 0x0f) {
- COPY_CODES(1);
- if (origBytes[nOrigBytes] == 0x1f) {
- // nop (multibyte)
- COPY_CODES(1);
- if ((origBytes[nOrigBytes] & 0xc0) == 0x40 &&
- (origBytes[nOrigBytes] & 0x7) == 0x04) {
- COPY_CODES(3);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x05) {
- // syscall
- COPY_CODES(1);
- } else if (origBytes[nOrigBytes] == 0x10 ||
- origBytes[nOrigBytes] == 0x11) {
- // SSE: movups xmm, xmm/m128
- // movups xmm/m128, xmm
- COPY_CODES(1);
- int nModRmSibBytes = CountModRmSib(&origBytes[nOrigBytes]);
- if (nModRmSibBytes < 0) {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- } else {
- COPY_CODES(nModRmSibBytes);
- }
- } else if (origBytes[nOrigBytes] == 0x84) {
- // je rel32
- JumpPatch jump(nTrampBytes - 1, // overwrite the 0x0f we copied above
- (intptr_t)(origBytes + nOrigBytes + 5 +
- *(reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 1))),
- JumpType::Je);
- nTrampBytes = jump.GenerateJump(tramp);
- nOrigBytes += 5;
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x40 ||
- origBytes[nOrigBytes] == 0x41) {
- // Plain REX or REX.B
- COPY_CODES(1);
- if ((origBytes[nOrigBytes] & 0xf0) == 0x50) {
- // push/pop with Rx register
- COPY_CODES(1);
- } else if (origBytes[nOrigBytes] >= 0xb8 && origBytes[nOrigBytes] <= 0xbf) {
- // mov r32, imm32
- COPY_CODES(5);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x44) {
- // REX.R
- COPY_CODES(1);
-
- // TODO: Combine with the "0x89" case below in the REX.W section
- if (origBytes[nOrigBytes] == 0x89) {
- // mov r/m32, r32
- COPY_CODES(1);
- int len = CountModRmSib(origBytes + nOrigBytes);
- if (len < 0) {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- COPY_CODES(len);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x45) {
- // REX.R & REX.B
- COPY_CODES(1);
-
- if (origBytes[nOrigBytes] == 0x33) {
- // xor r32, r32
- COPY_CODES(2);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if ((origBytes[nOrigBytes] & 0xfa) == 0x48) {
- // REX.W | REX.WR | REX.WRB | REX.WB
- COPY_CODES(1);
-
- if (origBytes[nOrigBytes] == 0x81 &&
- (origBytes[nOrigBytes + 1] & 0xf8) == 0xe8) {
- // sub r, dword
- COPY_CODES(6);
- } else if (origBytes[nOrigBytes] == 0x83 &&
- (origBytes[nOrigBytes + 1] & 0xf8) == 0xe8) {
- // sub r, byte
- COPY_CODES(3);
- } else if (origBytes[nOrigBytes] == 0x83 &&
- (origBytes[nOrigBytes + 1] & (kMaskMod|kMaskReg)) == kModReg) {
- // add r, byte
- COPY_CODES(3);
- } else if (origBytes[nOrigBytes] == 0x83 &&
- (origBytes[nOrigBytes + 1] & 0xf8) == 0x60) {
- // and [r+d], imm8
- COPY_CODES(5);
- } else if (origBytes[nOrigBytes] == 0x2b &&
- (origBytes[nOrigBytes + 1] & kMaskMod) == kModReg) {
- // sub r64, r64
- COPY_CODES(2);
- } else if (origBytes[nOrigBytes] == 0x85) {
- // 85 /r => TEST r/m32, r32
- if ((origBytes[nOrigBytes + 1] & 0xc0) == 0xc0) {
- COPY_CODES(2);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if ((origBytes[nOrigBytes] & 0xfd) == 0x89) {
- // MOV r/m64, r64 | MOV r64, r/m64
- BYTE reg;
- int len = CountModRmSib(origBytes + nOrigBytes + 1, ®);
- if (len < 0) {
- MOZ_ASSERT(len == kModOperand64);
- if (len != kModOperand64) {
- return;
- }
- nOrigBytes += 2; // skip the MOV and MOD R/M bytes
-
- // The instruction MOVs 64-bit data from a RIP-relative memory
- // address (determined with a 32-bit offset from RIP) into a
- // 64-bit register.
- int64_t* absAddr =
- reinterpret_cast<int64_t*>(origBytes + nOrigBytes + 4 +
- *reinterpret_cast<int32_t*>(origBytes + nOrigBytes));
- nOrigBytes += 4;
-
- if (reg == kRegAx) {
- // Destination is RAX. Encode instruction as MOVABS with a
- // 64-bit absolute address as its immediate operand.
- tramp[nTrampBytes] = 0xa1;
- ++nTrampBytes;
- int64_t** trampOperandPtr = reinterpret_cast<int64_t**>(tramp + nTrampBytes);
- *trampOperandPtr = absAddr;
- nTrampBytes += 8;
- } else {
- // The MOV must be done in two steps. First, we MOVABS the
- // absolute 64-bit address into our target register.
- // Then, we MOV from that address into the register
- // using register-indirect addressing.
- tramp[nTrampBytes] = 0xb8 + reg;
- ++nTrampBytes;
- int64_t** trampOperandPtr = reinterpret_cast<int64_t**>(tramp + nTrampBytes);
- *trampOperandPtr = absAddr;
- nTrampBytes += 8;
- tramp[nTrampBytes] = 0x48;
- tramp[nTrampBytes+1] = 0x8b;
- tramp[nTrampBytes+2] = BuildModRmByte(kModNoRegDisp, reg, reg);
- nTrampBytes += 3;
- }
- } else {
- COPY_CODES(len+1);
- }
- } else if (origBytes[nOrigBytes] == 0xc7) {
- // MOV r/m64, imm32
- if (origBytes[nOrigBytes + 1] == 0x44) {
- // MOV [r64+disp8], imm32
- // ModR/W + SIB + disp8 + imm32
- COPY_CODES(8);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0xff) {
- // JMP /4
- if ((origBytes[nOrigBytes + 1] & 0xc0) == 0x0 &&
- (origBytes[nOrigBytes + 1] & 0x07) == 0x5) {
- // [rip+disp32]
- // convert JMP 32bit offset to JMP 64bit direct
- JumpPatch jump(nTrampBytes - 1, // overwrite the REX.W/REX.WR we copied above
- *reinterpret_cast<intptr_t*>(origBytes + nOrigBytes + 6 +
- *reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 2)),
- JumpType::Jmp);
- nTrampBytes = jump.GenerateJump(tramp);
- nOrigBytes += 6;
- foundJmp = true;
- } else {
- // not support yet!
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x8d) {
- // LEA reg, addr
- if ((origBytes[nOrigBytes + 1] & kMaskMod) == 0x0 &&
- (origBytes[nOrigBytes + 1] & kMaskRm) == 0x5) {
- // [rip+disp32]
- // convert 32bit offset to 64bit direct and convert instruction
- // to a simple 64-bit mov
- BYTE reg = (origBytes[nOrigBytes + 1] & kMaskReg) >> kRegFieldShift;
- intptr_t absAddr =
- reinterpret_cast<intptr_t>(origBytes + nOrigBytes + 6 +
- *reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 2));
- nOrigBytes += 6;
- tramp[nTrampBytes] = 0xb8 + reg; // mov
- ++nTrampBytes;
- intptr_t* trampOperandPtr = reinterpret_cast<intptr_t*>(tramp + nTrampBytes);
- *trampOperandPtr = absAddr;
- nTrampBytes += 8;
- } else {
- // Above we dealt with RIP-relative instructions. Any other
- // operand form can simply be copied.
- int len = CountModRmSib(origBytes + nOrigBytes + 1);
- // We handled the kModOperand64 -- ie RIP-relative -- case above
- MOZ_ASSERT(len > 0);
- COPY_CODES(len + 1);
- }
- } else if (origBytes[nOrigBytes] == 0x63 &&
- (origBytes[nOrigBytes + 1] & kMaskMod) == kModReg) {
- // movsxd r64, r32 (move + sign extend)
- COPY_CODES(2);
- } else {
- // not support yet!
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x66) {
- // operand override prefix
- COPY_CODES(1);
- // This is the same as the x86 version
- if (origBytes[nOrigBytes] >= 0x88 && origBytes[nOrigBytes] <= 0x8B) {
- // various MOVs
- unsigned char b = origBytes[nOrigBytes + 1];
- if (((b & 0xc0) == 0xc0) ||
- (((b & 0xc0) == 0x00) &&
- ((b & 0x07) != 0x04) && ((b & 0x07) != 0x05))) {
- // REG=r, R/M=r or REG=r, R/M=[r]
- COPY_CODES(2);
- } else if ((b & 0xc0) == 0x40) {
- if ((b & 0x07) == 0x04) {
- // REG=r, R/M=[SIB + disp8]
- COPY_CODES(4);
- } else {
- // REG=r, R/M=[r + disp8]
- COPY_CODES(3);
- }
- } else {
- // complex MOV, bail
- MOZ_ASSERT_UNREACHABLE("Unrecognized MOV opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x44 &&
- origBytes[nOrigBytes+1] == 0x89) {
- // mov word ptr [reg+disp8], reg
- COPY_CODES(2);
- int len = CountModRmSib(origBytes + nOrigBytes);
- if (len < 0) {
- // no way to support this yet.
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- COPY_CODES(len);
- }
- } else if ((origBytes[nOrigBytes] & 0xf0) == 0x50) {
- // 1-byte push/pop
- COPY_CODES(1);
- } else if (origBytes[nOrigBytes] == 0x65) {
- // GS prefix
- //
- // The entry of GetKeyState on Windows 10 has the following code.
- // 65 48 8b 04 25 30 00 00 00 mov rax,qword ptr gs:[30h]
- // (GS prefix + REX + MOV (0x8b) ...)
- if (origBytes[nOrigBytes + 1] == 0x48 &&
- (origBytes[nOrigBytes + 2] >= 0x88 && origBytes[nOrigBytes + 2] <= 0x8b)) {
- COPY_CODES(3);
- int len = CountModRmSib(origBytes + nOrigBytes);
- if (len < 0) {
- // no way to support this yet.
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- COPY_CODES(len);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x80 &&
- origBytes[nOrigBytes + 1] == 0x3d) {
- // cmp byte ptr [rip-relative address], imm8
- // We'll compute the absolute address and do the cmp in r11
-
- // push r11 (to save the old value)
- tramp[nTrampBytes] = 0x49;
- ++nTrampBytes;
- tramp[nTrampBytes] = 0x53;
- ++nTrampBytes;
-
- byteptr_t absAddr =
- reinterpret_cast<byteptr_t>(origBytes + nOrigBytes + 7 +
- *reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 2));
- nOrigBytes += 6;
-
- // mov r11, absolute address
- tramp[nTrampBytes] = 0x49;
- ++nTrampBytes;
- tramp[nTrampBytes] = 0xbb;
- ++nTrampBytes;
-
- *reinterpret_cast<byteptr_t*>(tramp + nTrampBytes) = absAddr;
- nTrampBytes += 8;
-
- // cmp byte ptr [r11],...
- tramp[nTrampBytes] = 0x41;
- ++nTrampBytes;
- tramp[nTrampBytes] = 0x80;
- ++nTrampBytes;
- tramp[nTrampBytes] = 0x3b;
- ++nTrampBytes;
-
- // ...imm8
- COPY_CODES(1);
-
- // pop r11 (doesn't affect the flags from the cmp)
- tramp[nTrampBytes] = 0x49;
- ++nTrampBytes;
- tramp[nTrampBytes] = 0x5b;
- ++nTrampBytes;
- } else if (origBytes[nOrigBytes] == 0x90) {
- // nop
- COPY_CODES(1);
- } else if ((origBytes[nOrigBytes] & 0xf8) == 0xb8) {
- // MOV r32, imm32
- COPY_CODES(5);
- } else if (origBytes[nOrigBytes] == 0x33) {
- // xor r32, r/m32
- COPY_CODES(2);
- } else if (origBytes[nOrigBytes] == 0xf6) {
- // test r/m8, imm8 (used by ntdll on Windows 10 x64)
- // (no flags are affected by near jmp since there is no task switch,
- // so it is ok for a jmp to be written immediately after a test)
- BYTE subOpcode = 0;
- int nModRmSibBytes = CountModRmSib(&origBytes[nOrigBytes + 1], &subOpcode);
- if (nModRmSibBytes < 0 || subOpcode != 0) {
- // Unsupported
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- COPY_CODES(2 + nModRmSibBytes);
- } else if (origBytes[nOrigBytes] == 0x85) {
- // test r/m32, r32
- int nModRmSibBytes = CountModRmSib(&origBytes[nOrigBytes + 1]);
- if (nModRmSibBytes < 0) {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- COPY_CODES(1 + nModRmSibBytes);
- } else if (origBytes[nOrigBytes] == 0xd1 &&
- (origBytes[nOrigBytes+1] & kMaskMod) == kModReg) {
- // bit shifts/rotates : (SA|SH|RO|RC)(R|L) r32
- // (e.g. 0xd1 0xe0 is SAL, 0xd1 0xc8 is ROR)
- COPY_CODES(2);
- } else if (origBytes[nOrigBytes] == 0xc3) {
- // ret
- COPY_CODES(1);
- } else if (origBytes[nOrigBytes] == 0xcc) {
- // int 3
- COPY_CODES(1);
- } else if (origBytes[nOrigBytes] == 0xe8 ||
- origBytes[nOrigBytes] == 0xe9) {
- // CALL (0xe8) or JMP (0xe9) 32bit offset
- foundJmp = origBytes[nOrigBytes] == 0xe9;
- JumpPatch jump(nTrampBytes,
- (intptr_t)(origBytes + nOrigBytes + 5 +
- *(reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 1))),
- origBytes[nOrigBytes] == 0xe8 ? JumpType::Call : JumpType::Jmp);
- nTrampBytes = jump.GenerateJump(tramp);
- nOrigBytes += 5;
- } else if (origBytes[nOrigBytes] == 0x74 || // je rel8 (0x74)
- origBytes[nOrigBytes] == 0x75) { // jne rel8 (0x75)
- char offset = origBytes[nOrigBytes + 1];
- auto jumpType = JumpType::Je;
- if (origBytes[nOrigBytes] == 0x75)
- jumpType = JumpType::Jne;
- JumpPatch jump(nTrampBytes,
- (intptr_t)(origBytes + nOrigBytes + 2 + offset), jumpType);
- nTrampBytes = jump.GenerateJump(tramp);
- nOrigBytes += 2;
- } else if (origBytes[nOrigBytes] == 0xff) {
- if ((origBytes[nOrigBytes + 1] & (kMaskMod|kMaskReg)) == 0xf0) {
- // push r64
- COPY_CODES(2);
- } else if (origBytes[nOrigBytes + 1] == 0x25) {
- // jmp absolute indirect m32
- foundJmp = true;
- int32_t offset = *(reinterpret_cast<int32_t*>(origBytes + nOrigBytes + 2));
- int64_t* ptrToJmpDest = reinterpret_cast<int64_t*>(origBytes + nOrigBytes + 6 + offset);
- intptr_t jmpDest = static_cast<intptr_t>(*ptrToJmpDest);
- JumpPatch jump(nTrampBytes, jmpDest, JumpType::Jmp);
- nTrampBytes = jump.GenerateJump(tramp);
- nOrigBytes += 6;
- } else if ((origBytes[nOrigBytes + 1] & (kMaskMod|kMaskReg)) == BuildModRmByte(kModReg, 2, 0)) {
- // CALL reg (ff nn)
- COPY_CODES(2);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- } else if (origBytes[nOrigBytes] == 0x83 &&
- (origBytes[nOrigBytes + 1] & 0xf8) == 0x60) {
- // and [r+d], imm8
- COPY_CODES(5);
- } else if (origBytes[nOrigBytes] == 0xc6) {
- // mov [r+d], imm8
- int len = CountModRmSib(&origBytes[nOrigBytes + 1]);
- if (len < 0) {
- // RIP-relative not yet supported
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- COPY_CODES(len + 1);
- } else {
- MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
- return;
- }
- }
-#else
-#error "Unknown processor type"
-#endif
-
- if (nOrigBytes > 100) {
- //printf ("Too big!");
- return;
- }
-
- // target address of the final jmp instruction in the trampoline
- byteptr_t trampDest = origBytes + nOrigBytes;
-
-#if defined(_M_IX86)
- if (pJmp32 >= 0) {
- // Jump directly to the original target of the jump instead of jumping to the
- // original function.
- // Adjust jump target displacement to jump location in the trampoline.
- *((intptr_t*)(tramp + pJmp32 + 1)) += origBytes - tramp;
- } else {
- tramp[nOrigBytes] = 0xE9; // jmp
- *((intptr_t*)(tramp + nOrigBytes + 1)) =
- (intptr_t)trampDest - (intptr_t)(tramp + nOrigBytes + 5); // target displacement
- }
-#elif defined(_M_X64)
- // If the we found a Jmp, we don't need to add another instruction. However,
- // if we found a _conditional_ jump or a CALL (or no control operations
- // at all) then we still need to run the rest of aOriginalFunction.
- if (!foundJmp) {
- JumpPatch patch(nTrampBytes, reinterpret_cast<intptr_t>(trampDest));
- patch.GenerateJump(tramp);
- }
-#endif
-
- // The trampoline is now valid.
- *aOutTramp = tramp;
-
- // ensure we can modify the original code
- AutoVirtualProtect protect(aOrigFunction, nOrigBytes, PAGE_EXECUTE_READWRITE);
- if (!protect.Protect()) {
+ if (!mModule) {
return;
}
#if defined(_M_IX86)
- // now modify the original bytes
- origBytes[0] = 0xE9; // jmp
- *((intptr_t*)(origBytes + 1)) =
- aDest - (intptr_t)(origBytes + 5); // target displacement
-#elif defined(_M_X64)
- // mov r11, address
- origBytes[0] = 0x49;
- origBytes[1] = 0xbb;
-
- *((intptr_t*)(origBytes + 2)) = aDest;
-
- // jmp r11
- origBytes[10] = 0x41;
- origBytes[11] = 0xff;
- origBytes[12] = 0xe3;
-#endif
- }
-
- byteptr_t FindTrampolineSpace()
- {
- if (mCurHooks >= mMaxHooks) {
- return 0;
- }
-
- byteptr_t p = mHookPage + mCurHooks * kHookSize;
-
- mCurHooks++;
-
- return p;
- }
-
- static void* ResolveRedirectedAddress(const byteptr_t aOriginalFunction)
- {
- // If function entry is jmp rel8 stub to the internal implementation, we
- // resolve redirected address from the jump target.
- if (aOriginalFunction[0] == 0xeb) {
- int8_t offset = (int8_t)(aOriginalFunction[1]);
- if (offset <= 0) {
- // Bail out for negative offset: probably already patched by some
- // third-party code.
- return aOriginalFunction;
- }
-
- for (int8_t i = 0; i < offset; i++) {
- if (aOriginalFunction[2 + i] != 0x90) {
- // Bail out on insufficient nop space.
- return aOriginalFunction;
- }
- }
+ mNopSpacePatcher.Clear();
+#endif // defined(_M_IX86)
+ mDetourPatcher.Clear();
- return aOriginalFunction + 2 + offset;
- }
-
-#if defined(_M_IX86)
- // If function entry is jmp [disp32] such as used by kernel32,
- // we resolve redirected address from import table.
- if (aOriginalFunction[0] == 0xff && aOriginalFunction[1] == 0x25) {
- return (void*)(**((uint32_t**) (aOriginalFunction + 2)));
- }
-#elif defined(_M_X64)
- if (aOriginalFunction[0] == 0xe9) {
- // require for TestDllInterceptor with --disable-optimize
- int32_t offset = *((int32_t*)(aOriginalFunction + 1));
- return aOriginalFunction + 5 + offset;
- }
-#endif
-
- return aOriginalFunction;
- }
-};
-
-} // namespace internal
-
-class WindowsDllInterceptor
-{
- internal::WindowsDllNopSpacePatcher mNopSpacePatcher;
- internal::WindowsDllDetourPatcher mDetourPatcher;
-
- const char* mModuleName;
- int mNHooks;
-
-public:
- explicit WindowsDllInterceptor(const char* aModuleName = nullptr,
- int aNumHooks = 0)
- : mModuleName(nullptr)
- , mNHooks(0)
- {
- if (aModuleName) {
- Init(aModuleName, aNumHooks);
- }
- }
-
- void Init(const char* aModuleName, int aNumHooks = 0)
- {
- if (mModuleName) {
- return;
- }
-
- mModuleName = aModuleName;
- mNHooks = aNumHooks;
- mNopSpacePatcher.Init(aModuleName);
-
- // Lazily initialize mDetourPatcher, since it allocates memory and we might
- // not need it.
+ // NB: We intentionally leak mModule
}
/**
* Hook/detour the method aName from the DLL we set in Init so that it calls
* aHookDest instead. Returns the original method pointer in aOrigFunc
* and returns true if successful.
*
* IMPORTANT: If you use this method, please add your case to the
* TestDllInterceptor in order to detect future failures. Even if this
* succeeds now, updates to the hooked DLL could cause it to fail in
* the future.
*/
bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc)
{
// Use a nop space patch if possible, otherwise fall back to a detour.
// This should be the preferred method for adding hooks.
-
- if (!mModuleName) {
+ if (!mModule) {
return false;
}
- if (mNopSpacePatcher.AddHook(aName, aHookDest, aOrigFunc)) {
+ FARPROC proc = ::GetProcAddress(mModule, aName);
+ if (!proc) {
+ return false;
+ }
+
+#if defined(_M_IX86)
+ if (mNopSpacePatcher.AddHook(proc, aHookDest, aOrigFunc)) {
return true;
}
+#endif // defined(_M_IX86)
- return AddDetour(aName, aHookDest, aOrigFunc);
+ return AddDetour(proc, aHookDest, aOrigFunc);
}
/**
* Detour the method aName from the DLL we set in Init so that it calls
* aHookDest instead. Returns the original method pointer in aOrigFunc
* and returns true if successful.
*
* IMPORTANT: If you use this method, please add your case to the
@@ -1473,23 +202,40 @@ public:
* succeeds now, updates to the detoured DLL could cause it to fail in
* the future.
*/
bool AddDetour(const char* aName, intptr_t aHookDest, void** aOrigFunc)
{
// Generally, code should not call this method directly. Use AddHook unless
// there is a specific need to avoid nop space patches.
- if (!mModuleName) {
+ if (!mModule) {
+ return false;
+ }
+
+ FARPROC proc = ::GetProcAddress(mModule, aName);
+ if (!proc) {
return false;
}
+ return AddDetour(proc, aHookDest, aOrigFunc);
+ }
+
+private:
+ bool AddDetour(FARPROC aProc, intptr_t aHookDest, void** aOrigFunc)
+ {
+ MOZ_ASSERT(mModule && aProc);
+
if (!mDetourPatcher.Initialized()) {
- mDetourPatcher.Init(mModuleName, mNHooks);
+ mDetourPatcher.Init(mNHooks);
}
- return mDetourPatcher.AddHook(aName, aHookDest, aOrigFunc);
+ return mDetourPatcher.AddHook(aProc, aHookDest, aOrigFunc);
}
};
+} // namespace interceptor
+
+using WindowsDllInterceptor = interceptor::WindowsDllInterceptor<>;
+
} // namespace mozilla
#endif /* NS_WINDOWS_DLL_INTERCEPTOR_H_ */
rename from toolkit/xre/test/win/TestDllInterceptor.cpp
rename to mozglue/tests/interceptor/TestDllInterceptor.cpp
--- a/toolkit/xre/test/win/TestDllInterceptor.cpp
+++ b/mozglue/tests/interceptor/TestDllInterceptor.cpp
@@ -56,17 +56,18 @@ bool CheckHook(HookTestFunc aHookTestFun
"Executed hooked function %s from %s\n", aFuncName, aDllName);
return true;
}
printf("TEST-FAILED | WindowsDllInterceptor | "
"Failed to execute hooked function %s from %s\n", aFuncName, aDllName);
return false;
}
-bool TestHook(HookTestFunc funcTester, const char *dll, const char *func)
+template <size_t N>
+bool TestHook(HookTestFunc funcTester, const char (&dll)[N], const char *func)
{
void *orig_func;
bool successful = false;
{
WindowsDllInterceptor TestIntercept;
TestIntercept.Init(dll);
successful = TestIntercept.AddHook(func, 0, &orig_func);
}
@@ -75,17 +76,18 @@ bool TestHook(HookTestFunc funcTester, c
printf("TEST-PASS | WindowsDllInterceptor | Could hook %s from %s\n", func, dll);
return CheckHook(funcTester, orig_func, dll, func);
} else {
printf("TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Failed to hook %s from %s\n", func, dll);
return false;
}
}
-bool TestDetour(const char *dll, const char *func)
+template <size_t N>
+bool TestDetour(const char (&dll)[N], const char *func)
{
void *orig_func;
bool successful = false;
{
WindowsDllInterceptor TestIntercept;
TestIntercept.Init(dll);
successful = TestIntercept.AddDetour(func, 0, &orig_func);
}
@@ -94,17 +96,18 @@ bool TestDetour(const char *dll, const c
printf("TEST-PASS | WindowsDllInterceptor | Could detour %s from %s\n", func, dll);
return true;
} else {
printf("TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Failed to detour %s from %s\n", func, dll);
return false;
}
}
-bool MaybeTestHook(const bool cond, HookTestFunc funcTester, const char* dll, const char* func)
+template <size_t N>
+bool MaybeTestHook(const bool cond, HookTestFunc funcTester, const char (&dll)[N], const char* func)
{
if (!cond) {
printf("TEST-SKIPPED | WindowsDllInterceptor | Skipped hook test for %s from %s\n", func, dll);
return true;
}
return TestHook(funcTester, dll, func);
}
@@ -590,16 +593,19 @@ bool TestFreeCredentialsHandle(void* aFu
{
auto patchedFreeCredentialsHandle =
reinterpret_cast<decltype(&FreeCredentialsHandle)>(aFunc);
return patchedFreeCredentialsHandle(&sCredHandle) == S_OK;
}
int main()
{
+ LARGE_INTEGER start;
+ QueryPerformanceCounter(&start);
+
// We disable this part of the test because the code coverage instrumentation
// injects code in rotatePayload in a way that WindowsDllInterceptor doesn't
// understand.
#ifndef MOZ_CODE_COVERAGE
payload initial = { 0x12345678, 0xfc4e9d31, 0x87654321 };
payload p0, p1;
ZeroMemory(&p0, sizeof(p0));
ZeroMemory(&p1, sizeof(p1));
@@ -728,13 +734,26 @@ int main()
TestHook(TestAcquireCredentialsHandleA, "sspicli.dll", "AcquireCredentialsHandleA") &&
TestHook(TestQueryCredentialsAttributesA, "sspicli.dll", "QueryCredentialsAttributesA") &&
TestHook(TestFreeCredentialsHandle, "sspicli.dll", "FreeCredentialsHandle") &&
TestDetour("kernel32.dll", "BaseThreadInitThunk") &&
TestDetour("ntdll.dll", "LdrLoadDll")) {
printf("TEST-PASS | WindowsDllInterceptor | all checks passed\n");
+
+ LARGE_INTEGER end, freq;
+ QueryPerformanceCounter(&end);
+
+ QueryPerformanceFrequency(&freq);
+
+ LARGE_INTEGER result;
+ result.QuadPart = end.QuadPart - start.QuadPart;
+ result.QuadPart *= 1000000;
+ result.QuadPart /= freq.QuadPart;
+
+ printf("Elapsed time: %lld microseconds\n", result.QuadPart);
+
return 0;
}
return 1;
}
new file mode 100644
--- /dev/null
+++ b/mozglue/tests/interceptor/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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 http://mozilla.org/MPL/2.0/.
+
+CppUnitTests([
+ 'TestDllInterceptor',
+])
+
+DEFINES['NS_NO_XPCOM'] = True
+
+DisableStlWrapping()
+
+OS_LIBS += [
+ 'ole32',
+]
+
+USE_LIBS += [
+ 'mfbt',
+]
--- a/mozglue/tests/moz.build
+++ b/mozglue/tests/moz.build
@@ -8,8 +8,13 @@ DisableStlWrapping()
GeckoCppUnitTests([
'ShowSSEConfig',
], linkage=None)
CppUnitTests([
'TestPrintf',
])
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ TEST_DIRS += [
+ 'interceptor',
+ ]
--- a/toolkit/xre/test/win/moz.build
+++ b/toolkit/xre/test/win/moz.build
@@ -3,28 +3,23 @@
# 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 http://mozilla.org/MPL/2.0/.
SimplePrograms([
'TestXREMakeCommandLineWin',
])
-CppUnitTests([
- 'TestDllInterceptor',
-])
-
DEFINES['NS_NO_XPCOM'] = True
LOCAL_INCLUDES += [
'/config',
'/toolkit/xre',
]
DisableStlWrapping()
USE_STATIC_LIBS = True
OS_LIBS += [
'comctl32',
- 'ole32',
'shell32',
'ws2_32',
]
--- a/xpcom/build/PoisonIOInterposerWin.cpp
+++ b/xpcom/build/PoisonIOInterposerWin.cpp
@@ -480,13 +480,13 @@ InitPoisonIOInterposer()
void
ClearPoisonIOInterposer()
{
MOZ_ASSERT(false);
if (sIOPoisoned) {
// Destroy the DLL interceptor
sIOPoisoned = false;
- sNtDllInterceptor = WindowsDllInterceptor();
+ sNtDllInterceptor.Clear();
}
}
} // namespace mozilla
--- a/xpcom/build/moz.build
+++ b/xpcom/build/moz.build
@@ -21,18 +21,19 @@ EXPORTS.mozilla += [
'LateWriteChecks.h',
'Omnijar.h',
'PoisonIOInterposer.h',
'XPCOM.h',
'XREAppData.h',
]
if CONFIG['OS_ARCH'] == 'WINNT':
- EXPORTS += ['nsWindowsDllInterceptor.h']
- EXPORTS.mozilla += ['perfprobe.h']
+ EXPORTS.mozilla += [
+ 'perfprobe.h',
+ ]
SOURCES += ['perfprobe.cpp']
if CONFIG['CC_TYPE'] != 'gcc':
SOURCES += [
'PoisonIOInterposerBase.cpp',
'PoisonIOInterposerWin.cpp',
]
else:
SOURCES += ['PoisonIOInterposerStub.cpp']