summaryrefslogtreecommitdiffstats
path: root/mozglue/misc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--mozglue/misc/AutoProfilerLabel.cpp109
-rw-r--r--mozglue/misc/AutoProfilerLabel.h70
-rw-r--r--mozglue/misc/ConditionVariable_posix.cpp161
-rw-r--r--mozglue/misc/ConditionVariable_windows.cpp98
-rw-r--r--mozglue/misc/Debug.h63
-rw-r--r--mozglue/misc/DynamicallyLinkedFunctionPtr.h137
-rw-r--r--mozglue/misc/ImportDir.h94
-rw-r--r--mozglue/misc/MmapFaultHandler.cpp131
-rw-r--r--mozglue/misc/MmapFaultHandler.h105
-rw-r--r--mozglue/misc/MutexPlatformData_posix.h18
-rw-r--r--mozglue/misc/MutexPlatformData_windows.h18
-rw-r--r--mozglue/misc/Mutex_posix.cpp133
-rw-r--r--mozglue/misc/Mutex_windows.cpp40
-rw-r--r--mozglue/misc/NativeNt.h1680
-rw-r--r--mozglue/misc/PlatformConditionVariable.h71
-rw-r--r--mozglue/misc/PlatformMutex.h66
-rw-r--r--mozglue/misc/PreXULSkeletonUI.cpp2222
-rw-r--r--mozglue/misc/PreXULSkeletonUI.h82
-rw-r--r--mozglue/misc/Printf.cpp952
-rw-r--r--mozglue/misc/Printf.h264
-rw-r--r--mozglue/misc/StackWalk.cpp929
-rw-r--r--mozglue/misc/StackWalk.h177
-rw-r--r--mozglue/misc/StackWalk_windows.h34
-rw-r--r--mozglue/misc/TimeStamp.cpp154
-rw-r--r--mozglue/misc/TimeStamp.h615
-rw-r--r--mozglue/misc/TimeStamp_darwin.cpp191
-rw-r--r--mozglue/misc/TimeStamp_posix.cpp336
-rw-r--r--mozglue/misc/TimeStamp_windows.cpp535
-rw-r--r--mozglue/misc/TimeStamp_windows.h102
-rw-r--r--mozglue/misc/Uptime.cpp150
-rw-r--r--mozglue/misc/Uptime.h26
-rw-r--r--mozglue/misc/WinUtils.h140
-rw-r--r--mozglue/misc/WindowsDpiAwareness.h41
-rw-r--r--mozglue/misc/WindowsMapRemoteView.cpp124
-rw-r--r--mozglue/misc/WindowsMapRemoteView.h25
-rw-r--r--mozglue/misc/WindowsProcessMitigations.cpp77
-rw-r--r--mozglue/misc/WindowsProcessMitigations.h20
-rw-r--r--mozglue/misc/WindowsUnicode.cpp59
-rw-r--r--mozglue/misc/WindowsUnicode.h35
-rw-r--r--mozglue/misc/decimal/Decimal.cpp1063
-rw-r--r--mozglue/misc/decimal/Decimal.h221
-rw-r--r--mozglue/misc/decimal/DoubleConversion.h27
-rw-r--r--mozglue/misc/decimal/UPSTREAM-GIT-SHA1
-rw-r--r--mozglue/misc/decimal/add-doubleconversion-impl.patch42
-rw-r--r--mozglue/misc/decimal/comparison-with-nan.patch67
-rw-r--r--mozglue/misc/decimal/fix-wshadow-warnings.patch171
-rw-r--r--mozglue/misc/decimal/mfbt-abi-markers.patch150
-rw-r--r--mozglue/misc/decimal/moz-decimal-utils.h111
-rw-r--r--mozglue/misc/decimal/to-moz-dependencies.patch224
-rwxr-xr-xmozglue/misc/decimal/update.sh60
-rw-r--r--mozglue/misc/decimal/zero-serialization.patch22
-rw-r--r--mozglue/misc/interceptor/Arm64.cpp89
-rw-r--r--mozglue/misc/interceptor/Arm64.h221
-rw-r--r--mozglue/misc/interceptor/MMPolicies.h981
-rw-r--r--mozglue/misc/interceptor/PatcherBase.h141
-rw-r--r--mozglue/misc/interceptor/PatcherDetour.h1715
-rw-r--r--mozglue/misc/interceptor/PatcherNopSpace.h205
-rw-r--r--mozglue/misc/interceptor/RangeMap.h142
-rw-r--r--mozglue/misc/interceptor/TargetFunction.h1000
-rw-r--r--mozglue/misc/interceptor/Trampoline.h517
-rw-r--r--mozglue/misc/interceptor/VMSharingPolicies.h285
-rw-r--r--mozglue/misc/interceptor/moz.build24
-rw-r--r--mozglue/misc/moz.build107
-rw-r--r--mozglue/misc/nsWindowsDllInterceptor.h819
64 files changed, 18689 insertions, 0 deletions
diff --git a/mozglue/misc/AutoProfilerLabel.cpp b/mozglue/misc/AutoProfilerLabel.cpp
new file mode 100644
index 0000000000..9bfdc19ef7
--- /dev/null
+++ b/mozglue/misc/AutoProfilerLabel.cpp
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#include "mozilla/AutoProfilerLabel.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/PlatformMutex.h"
+
+namespace mozilla {
+
+// RAII class that encapsulates all shared static data, and enforces locking
+// when accessing this data.
+class MOZ_RAII AutoProfilerLabelData {
+ public:
+ AutoProfilerLabelData() { sAPLMutex.Lock(); }
+
+ ~AutoProfilerLabelData() { sAPLMutex.Unlock(); }
+
+ AutoProfilerLabelData(const AutoProfilerLabelData&) = delete;
+ void operator=(const AutoProfilerLabelData&) = delete;
+
+ const ProfilerLabelEnter& EnterCRef() const { return sEnter; }
+ ProfilerLabelEnter& EnterRef() { return sEnter; }
+
+ const ProfilerLabelExit& ExitCRef() const { return sExit; }
+ ProfilerLabelExit& ExitRef() { return sExit; }
+
+ const uint32_t& GenerationCRef() const { return sGeneration; }
+ uint32_t& GenerationRef() { return sGeneration; }
+
+ static bool RacyIsProfilerPresent() { return !!sGeneration; }
+
+ private:
+ // Thin shell around mozglue PlatformMutex, for local internal use.
+ // Does not preserve behavior in JS record/replay.
+ class Mutex : private mozilla::detail::MutexImpl {
+ public:
+ Mutex() : mozilla::detail::MutexImpl() {}
+ void Lock() { mozilla::detail::MutexImpl::lock(); }
+ void Unlock() { mozilla::detail::MutexImpl::unlock(); }
+ };
+
+ // Mutex protecting access to the following static members.
+ static Mutex sAPLMutex;
+
+ static ProfilerLabelEnter sEnter;
+ static ProfilerLabelExit sExit;
+
+ // Current "generation" of RegisterProfilerLabelEnterExit calls.
+ static uint32_t sGeneration;
+};
+
+/* static */ AutoProfilerLabelData::Mutex AutoProfilerLabelData::sAPLMutex;
+/* static */ ProfilerLabelEnter AutoProfilerLabelData::sEnter = nullptr;
+/* static */ ProfilerLabelExit AutoProfilerLabelData::sExit = nullptr;
+/* static */ uint32_t AutoProfilerLabelData::sGeneration = 0;
+
+void RegisterProfilerLabelEnterExit(ProfilerLabelEnter aEnter,
+ ProfilerLabelExit aExit) {
+ MOZ_ASSERT(!aEnter == !aExit, "Must provide both null or both non-null");
+
+ AutoProfilerLabelData data;
+ MOZ_ASSERT(!aEnter != !data.EnterRef(),
+ "Must go from null to non-null, or from non-null to null");
+ data.EnterRef() = aEnter;
+ data.ExitRef() = aExit;
+ ++data.GenerationRef();
+}
+
+bool IsProfilerPresent() {
+ return AutoProfilerLabelData::RacyIsProfilerPresent();
+}
+
+ProfilerLabel ProfilerLabelBegin(const char* aLabelName,
+ const char* aDynamicString, void* aSp) {
+ const AutoProfilerLabelData data;
+ void* entryContext = (data.EnterCRef())
+ ? data.EnterCRef()(aLabelName, aDynamicString, aSp)
+ : nullptr;
+ uint32_t generation = data.GenerationCRef();
+
+ return MakeTuple(entryContext, generation);
+}
+
+void ProfilerLabelEnd(const ProfilerLabel& aLabel) {
+ if (!IsValidProfilerLabel(aLabel)) {
+ return;
+ }
+
+ const AutoProfilerLabelData data;
+ if (data.ExitCRef() && (Get<1>(aLabel) == data.GenerationCRef())) {
+ data.ExitCRef()(Get<0>(aLabel));
+ }
+}
+
+AutoProfilerLabel::AutoProfilerLabel(const char* aLabel,
+ const char* aDynamicString) {
+ Tie(mEntryContext, mGeneration) =
+ ProfilerLabelBegin(aLabel, aDynamicString, this);
+}
+
+AutoProfilerLabel::~AutoProfilerLabel() {
+ ProfilerLabelEnd(MakeTuple(mEntryContext, mGeneration));
+}
+
+} // namespace mozilla
diff --git a/mozglue/misc/AutoProfilerLabel.h b/mozglue/misc/AutoProfilerLabel.h
new file mode 100644
index 0000000000..2cbd8252c9
--- /dev/null
+++ b/mozglue/misc/AutoProfilerLabel.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 2; 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 mozilla_AutoProfilerLabel_h
+#define mozilla_AutoProfilerLabel_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Tuple.h"
+#include "mozilla/Types.h"
+
+// The Gecko Profiler defines AutoProfilerLabel, an RAII class for
+// pushing/popping frames to/from the ProfilingStack.
+//
+// This file defines a class of the same name that does much the same thing,
+// but which can be used in (and only in) mozglue. A different class is
+// necessary because mozglue cannot directly access sProfilingStack.
+//
+// Note that this class is slightly slower than the other AutoProfilerLabel,
+// and it lacks the macro wrappers. It also is effectively hardwired to use
+// JS::ProfilingCategory::OTHER as the category pair, because that's what
+// the callbacks provided by the profiler use. (Specifying the categories in
+// this file would require #including ProfilingCategory.h in mozglue, which we
+// don't want to do.)
+
+namespace mozilla {
+
+// Enter should return a pointer that will be given to Exit.
+typedef void* (*ProfilerLabelEnter)(const char* aLabel,
+ const char* aDynamicString, void* aSp);
+typedef void (*ProfilerLabelExit)(void* EntryContext);
+
+// Register callbacks that do the entry/exit work involving sProfilingStack.
+MFBT_API void RegisterProfilerLabelEnterExit(ProfilerLabelEnter aEnter,
+ ProfilerLabelExit aExit);
+
+// This #ifdef prevents this AutoProfilerLabel from being defined in libxul,
+// which would conflict with the one in the profiler.
+#ifdef IMPL_MFBT
+
+class MOZ_RAII AutoProfilerLabel {
+ public:
+ AutoProfilerLabel(const char* aLabel, const char* aDynamicString);
+ ~AutoProfilerLabel();
+
+ private:
+ void* mEntryContext;
+ // Number of RegisterProfilerLabelEnterExit calls, to avoid giving an entry
+ // context from one generation to the next.
+ uint32_t mGeneration;
+};
+
+using ProfilerLabel = Tuple<void*, uint32_t>;
+
+bool IsProfilerPresent();
+ProfilerLabel ProfilerLabelBegin(const char* aLabelName,
+ const char* aDynamicString, void* aSp);
+void ProfilerLabelEnd(const ProfilerLabel& aLabel);
+
+inline bool IsValidProfilerLabel(const ProfilerLabel& aLabel) {
+ return !!Get<0>(aLabel);
+}
+
+#endif
+
+} // namespace mozilla
+
+#endif // mozilla_AutoProfilerLabel_h
diff --git a/mozglue/misc/ConditionVariable_posix.cpp b/mozglue/misc/ConditionVariable_posix.cpp
new file mode 100644
index 0000000000..d15c87f4e8
--- /dev/null
+++ b/mozglue/misc/ConditionVariable_posix.cpp
@@ -0,0 +1,161 @@
+/* -*- 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/. */
+
+#include "mozilla/Assertions.h"
+#include "mozilla/CheckedInt.h"
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "mozilla/PlatformConditionVariable.h"
+#include "mozilla/PlatformMutex.h"
+#include "MutexPlatformData_posix.h"
+
+using mozilla::CheckedInt;
+using mozilla::TimeDuration;
+
+static const long NanoSecPerSec = 1000000000;
+
+// Android 32-bit & macOS 10.12 has the clock functions, but not
+// pthread_condattr_setclock.
+#if defined(HAVE_CLOCK_MONOTONIC) && \
+ !(defined(__ANDROID__) && !defined(__LP64__)) && !defined(__APPLE__)
+# define CV_USE_CLOCK_API
+#endif
+
+#ifdef CV_USE_CLOCK_API
+// The C++ specification defines std::condition_variable::wait_for in terms of
+// std::chrono::steady_clock, which is closest to CLOCK_MONOTONIC.
+static const clockid_t WhichClock = CLOCK_MONOTONIC;
+
+// While timevaladd is widely available to work with timevals, the newer
+// timespec structure is largely lacking such conveniences. Thankfully, the
+// utilities available in MFBT make implementing our own quite easy.
+static void moz_timespecadd(struct timespec* lhs, struct timespec* rhs,
+ struct timespec* result) {
+ // Add nanoseconds. This may wrap, but not above 2 billion.
+ MOZ_RELEASE_ASSERT(lhs->tv_nsec < NanoSecPerSec);
+ MOZ_RELEASE_ASSERT(rhs->tv_nsec < NanoSecPerSec);
+ result->tv_nsec = lhs->tv_nsec + rhs->tv_nsec;
+
+ // Add seconds, checking for overflow in the platform specific time_t type.
+ CheckedInt<time_t> sec = CheckedInt<time_t>(lhs->tv_sec) + rhs->tv_sec;
+
+ // If nanoseconds overflowed, carry the result over into seconds.
+ if (result->tv_nsec >= NanoSecPerSec) {
+ MOZ_RELEASE_ASSERT(result->tv_nsec < 2 * NanoSecPerSec);
+ result->tv_nsec -= NanoSecPerSec;
+ sec += 1;
+ }
+
+ // Extracting the value asserts that there was no overflow.
+ MOZ_RELEASE_ASSERT(sec.isValid());
+ result->tv_sec = sec.value();
+}
+#endif
+
+struct mozilla::detail::ConditionVariableImpl::PlatformData {
+ pthread_cond_t ptCond;
+};
+
+mozilla::detail::ConditionVariableImpl::ConditionVariableImpl() {
+ pthread_cond_t* ptCond = &platformData()->ptCond;
+
+#ifdef CV_USE_CLOCK_API
+ pthread_condattr_t attr;
+ int r0 = pthread_condattr_init(&attr);
+ MOZ_RELEASE_ASSERT(!r0);
+
+ int r1 = pthread_condattr_setclock(&attr, WhichClock);
+ MOZ_RELEASE_ASSERT(!r1);
+
+ int r2 = pthread_cond_init(ptCond, &attr);
+ MOZ_RELEASE_ASSERT(!r2);
+
+ int r3 = pthread_condattr_destroy(&attr);
+ MOZ_RELEASE_ASSERT(!r3);
+#else
+ int r = pthread_cond_init(ptCond, NULL);
+ MOZ_RELEASE_ASSERT(!r);
+#endif
+}
+
+mozilla::detail::ConditionVariableImpl::~ConditionVariableImpl() {
+ int r = pthread_cond_destroy(&platformData()->ptCond);
+ MOZ_RELEASE_ASSERT(r == 0);
+}
+
+void mozilla::detail::ConditionVariableImpl::notify_one() {
+ int r = pthread_cond_signal(&platformData()->ptCond);
+ MOZ_RELEASE_ASSERT(r == 0);
+}
+
+void mozilla::detail::ConditionVariableImpl::notify_all() {
+ int r = pthread_cond_broadcast(&platformData()->ptCond);
+ MOZ_RELEASE_ASSERT(r == 0);
+}
+
+void mozilla::detail::ConditionVariableImpl::wait(MutexImpl& lock) {
+ pthread_cond_t* ptCond = &platformData()->ptCond;
+ pthread_mutex_t* ptMutex = &lock.platformData()->ptMutex;
+
+ int r = pthread_cond_wait(ptCond, ptMutex);
+ MOZ_RELEASE_ASSERT(r == 0);
+}
+
+mozilla::CVStatus mozilla::detail::ConditionVariableImpl::wait_for(
+ MutexImpl& lock, const TimeDuration& a_rel_time) {
+ if (a_rel_time == TimeDuration::Forever()) {
+ wait(lock);
+ return CVStatus::NoTimeout;
+ }
+
+ pthread_cond_t* ptCond = &platformData()->ptCond;
+ pthread_mutex_t* ptMutex = &lock.platformData()->ptMutex;
+ int r;
+
+ // Clamp to 0, as time_t is unsigned.
+ TimeDuration rel_time = a_rel_time < TimeDuration::FromSeconds(0)
+ ? TimeDuration::FromSeconds(0)
+ : a_rel_time;
+
+ // Convert the duration to a timespec.
+ struct timespec rel_ts;
+ rel_ts.tv_sec = static_cast<time_t>(rel_time.ToSeconds());
+ rel_ts.tv_nsec =
+ static_cast<uint64_t>(rel_time.ToMicroseconds() * 1000.0) % NanoSecPerSec;
+
+#ifdef CV_USE_CLOCK_API
+ struct timespec now_ts;
+ r = clock_gettime(WhichClock, &now_ts);
+ MOZ_RELEASE_ASSERT(!r);
+
+ struct timespec abs_ts;
+ moz_timespecadd(&now_ts, &rel_ts, &abs_ts);
+
+ r = pthread_cond_timedwait(ptCond, ptMutex, &abs_ts);
+#else
+ // Our non-clock-supporting platforms, OS X and Android, do support waiting
+ // on a condition variable with a relative timeout.
+ r = pthread_cond_timedwait_relative_np(ptCond, ptMutex, &rel_ts);
+#endif
+
+ if (r == 0) {
+ return CVStatus::NoTimeout;
+ }
+ MOZ_RELEASE_ASSERT(r == ETIMEDOUT);
+ return CVStatus::Timeout;
+}
+
+mozilla::detail::ConditionVariableImpl::PlatformData*
+mozilla::detail::ConditionVariableImpl::platformData() {
+ static_assert(sizeof platformData_ >= sizeof(PlatformData),
+ "platformData_ is too small");
+ return reinterpret_cast<PlatformData*>(platformData_);
+}
diff --git a/mozglue/misc/ConditionVariable_windows.cpp b/mozglue/misc/ConditionVariable_windows.cpp
new file mode 100644
index 0000000000..0c0151f1d3
--- /dev/null
+++ b/mozglue/misc/ConditionVariable_windows.cpp
@@ -0,0 +1,98 @@
+/* -*- 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/. */
+
+#include "mozilla/Assertions.h"
+
+#include <float.h>
+#include <intrin.h>
+#include <stdlib.h>
+#include <windows.h>
+
+#include "mozilla/PlatformConditionVariable.h"
+#include "mozilla/PlatformMutex.h"
+#include "MutexPlatformData_windows.h"
+
+// Some versions of the Windows SDK have a bug where some interlocked functions
+// are not redefined as compiler intrinsics. Fix that for the interlocked
+// functions that are used in this file.
+#if defined(_MSC_VER) && !defined(InterlockedExchangeAdd)
+# define InterlockedExchangeAdd(addend, value) \
+ _InterlockedExchangeAdd((volatile long*)(addend), (long)(value))
+#endif
+
+#if defined(_MSC_VER) && !defined(InterlockedIncrement)
+# define InterlockedIncrement(addend) \
+ _InterlockedIncrement((volatile long*)(addend))
+#endif
+
+// Wrapper for native condition variable APIs.
+struct mozilla::detail::ConditionVariableImpl::PlatformData {
+ CONDITION_VARIABLE cv_;
+};
+
+mozilla::detail::ConditionVariableImpl::ConditionVariableImpl() {
+ InitializeConditionVariable(&platformData()->cv_);
+}
+
+void mozilla::detail::ConditionVariableImpl::notify_one() {
+ WakeConditionVariable(&platformData()->cv_);
+}
+
+void mozilla::detail::ConditionVariableImpl::notify_all() {
+ WakeAllConditionVariable(&platformData()->cv_);
+}
+
+void mozilla::detail::ConditionVariableImpl::wait(MutexImpl& lock) {
+ SRWLOCK* srwlock = &lock.platformData()->lock;
+ bool r =
+ SleepConditionVariableSRW(&platformData()->cv_, srwlock, INFINITE, 0);
+ MOZ_RELEASE_ASSERT(r);
+}
+
+mozilla::CVStatus mozilla::detail::ConditionVariableImpl::wait_for(
+ MutexImpl& lock, const mozilla::TimeDuration& rel_time) {
+ if (rel_time == mozilla::TimeDuration::Forever()) {
+ wait(lock);
+ return CVStatus::NoTimeout;
+ }
+
+ SRWLOCK* srwlock = &lock.platformData()->lock;
+
+ // Note that DWORD is unsigned, so we have to be careful to clamp at 0. If
+ // rel_time is Forever, then ToMilliseconds is +inf, which evaluates as
+ // greater than UINT32_MAX, resulting in the correct INFINITE wait. We also
+ // don't want to round sub-millisecond waits to 0, as that wastes energy (see
+ // bug 1437167 comment 6), so we instead round submillisecond waits to 1ms.
+ double msecd = rel_time.ToMilliseconds();
+ DWORD msec;
+ if (msecd < 0.0) {
+ msec = 0;
+ } else if (msecd > UINT32_MAX) {
+ msec = INFINITE;
+ } else {
+ msec = static_cast<DWORD>(msecd);
+ // Round submillisecond waits to 1ms.
+ if (msec == 0 && !rel_time.IsZero()) {
+ msec = 1;
+ }
+ }
+
+ BOOL r = SleepConditionVariableSRW(&platformData()->cv_, srwlock, msec, 0);
+ if (r) return CVStatus::NoTimeout;
+ MOZ_RELEASE_ASSERT(GetLastError() == ERROR_TIMEOUT);
+ return CVStatus::Timeout;
+}
+
+mozilla::detail::ConditionVariableImpl::~ConditionVariableImpl() {
+ // Native condition variables don't require cleanup.
+}
+
+inline mozilla::detail::ConditionVariableImpl::PlatformData*
+mozilla::detail::ConditionVariableImpl::platformData() {
+ static_assert(sizeof platformData_ >= sizeof(PlatformData),
+ "platformData_ is too small");
+ return reinterpret_cast<PlatformData*>(platformData_);
+}
diff --git a/mozglue/misc/Debug.h b/mozglue/misc/Debug.h
new file mode 100644
index 0000000000..adc8d02c7b
--- /dev/null
+++ b/mozglue/misc/Debug.h
@@ -0,0 +1,63 @@
+/* -*- 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_glue_Debug_h
+#define mozilla_glue_Debug_h
+
+/* This header file intends to supply debugging utilities for use in code
+ * that cannot use XPCOM debugging facilities like nsDebug.h.
+ * e.g. mozglue, browser/app
+ *
+ * NB: printf_stderr() is in the global namespace, so include this file with
+ * care; avoid including from header files.
+ */
+
+#include <io.h>
+#if defined(XP_WIN)
+# include <windows.h>
+#endif // defined(XP_WIN)
+#include "mozilla/Attributes.h"
+#include "mozilla/Sprintf.h"
+
+#if defined(MOZILLA_INTERNAL_API)
+# error Do not include this file from XUL sources.
+#endif
+
+// Though this is a separate implementation than nsDebug's, we want to make the
+// declarations compatible to avoid confusing the linker if both headers are
+// included.
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+inline void printf_stderr(const char* fmt, ...) MOZ_FORMAT_PRINTF(1, 2) {
+#if defined(XP_WIN)
+ if (IsDebuggerPresent()) {
+ char buf[2048];
+ va_list args;
+ va_start(args, fmt);
+ VsprintfLiteral(buf, fmt, args);
+ va_end(args);
+ OutputDebugStringA(buf);
+ }
+#endif // defined(XP_WIN)
+
+ FILE* fp = _fdopen(_dup(2), "a");
+ if (!fp) return;
+
+ va_list args;
+ va_start(args, fmt);
+ vfprintf(fp, fmt, args);
+ va_end(args);
+
+ fclose(fp);
+}
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+#endif // mozilla_glue_Debug_h
diff --git a/mozglue/misc/DynamicallyLinkedFunctionPtr.h b/mozglue/misc/DynamicallyLinkedFunctionPtr.h
new file mode 100644
index 0000000000..4313974ec5
--- /dev/null
+++ b/mozglue/misc/DynamicallyLinkedFunctionPtr.h
@@ -0,0 +1,137 @@
+/* -*- 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 mozilla_DynamicallyLinkedFunctionPtr_h
+#define mozilla_DynamicallyLinkedFunctionPtr_h
+
+#include <windows.h>
+
+#include <utility>
+
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace detail {
+
+template <typename T>
+struct FunctionPtrCracker;
+
+template <typename R, typename... Args>
+struct FunctionPtrCracker<R (*)(Args...)> {
+ using ReturnT = R;
+ using FunctionPtrT = R (*)(Args...);
+};
+
+#if defined(_M_IX86)
+template <typename R, typename... Args>
+struct FunctionPtrCracker<R(__stdcall*)(Args...)> {
+ using ReturnT = R;
+ using FunctionPtrT = R(__stdcall*)(Args...);
+};
+
+template <typename R, typename... Args>
+struct FunctionPtrCracker<R(__fastcall*)(Args...)> {
+ using ReturnT = R;
+ using FunctionPtrT = R(__fastcall*)(Args...);
+};
+#endif // defined(_M_IX86)
+
+template <typename T>
+class DynamicallyLinkedFunctionPtrBase {
+ public:
+ using ReturnT = typename FunctionPtrCracker<T>::ReturnT;
+ using FunctionPtrT = typename FunctionPtrCracker<T>::FunctionPtrT;
+
+ DynamicallyLinkedFunctionPtrBase(const wchar_t* aLibName,
+ const char* aFuncName)
+ : mModule(::LoadLibraryW(aLibName)), mFunction(nullptr) {
+ if (!mModule) {
+ return;
+ }
+
+ mFunction =
+ reinterpret_cast<FunctionPtrT>(::GetProcAddress(mModule, aFuncName));
+
+ if (!mFunction) {
+ // Since the function doesn't exist, there is no point in holding a
+ // reference to mModule anymore.
+ ::FreeLibrary(mModule);
+ mModule = nullptr;
+ }
+ }
+
+ DynamicallyLinkedFunctionPtrBase(const DynamicallyLinkedFunctionPtrBase&) =
+ delete;
+ DynamicallyLinkedFunctionPtrBase& operator=(
+ const DynamicallyLinkedFunctionPtrBase&) = delete;
+
+ DynamicallyLinkedFunctionPtrBase(DynamicallyLinkedFunctionPtrBase&&) = delete;
+ DynamicallyLinkedFunctionPtrBase& operator=(
+ DynamicallyLinkedFunctionPtrBase&&) = delete;
+
+ template <typename... Args>
+ ReturnT operator()(Args&&... args) const {
+ return mFunction(std::forward<Args>(args)...);
+ }
+
+ explicit operator bool() const { return !!mFunction; }
+
+ protected:
+ HMODULE mModule;
+ FunctionPtrT mFunction;
+};
+
+} // namespace detail
+
+/**
+ * In most cases, this class is the one that you want to use for resolving a
+ * dynamically-linked function pointer. It should be instantiated as a static
+ * local variable.
+ *
+ * NB: It has a trivial destructor, so the DLL that is loaded is never freed.
+ * Assuming that this function is called fairly often, this is the most
+ * sensible option. OTOH, if the function you are calling is a one-off, or the
+ * static local requirement is too restrictive, use DynamicallyLinkedFunctionPtr
+ * instead.
+ */
+template <typename T>
+class MOZ_STATIC_LOCAL_CLASS StaticDynamicallyLinkedFunctionPtr final
+ : public detail::DynamicallyLinkedFunctionPtrBase<T> {
+ public:
+ StaticDynamicallyLinkedFunctionPtr(const wchar_t* aLibName,
+ const char* aFuncName)
+ : detail::DynamicallyLinkedFunctionPtrBase<T>(aLibName, aFuncName) {}
+
+ /**
+ * We only offer this operator for the static local case, as it is not
+ * possible for this object to be destroyed while the returned pointer is
+ * being held.
+ */
+ operator typename detail::DynamicallyLinkedFunctionPtrBase<T>::FunctionPtrT()
+ const {
+ return this->mFunction;
+ }
+};
+
+template <typename T>
+class MOZ_NON_PARAM MOZ_NON_TEMPORARY_CLASS DynamicallyLinkedFunctionPtr final
+ : public detail::DynamicallyLinkedFunctionPtrBase<T> {
+ public:
+ DynamicallyLinkedFunctionPtr(const wchar_t* aLibName, const char* aFuncName)
+ : detail::DynamicallyLinkedFunctionPtrBase<T>(aLibName, aFuncName) {}
+
+ ~DynamicallyLinkedFunctionPtr() {
+ if (!this->mModule) {
+ return;
+ }
+
+ ::FreeLibrary(this->mModule);
+ }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_DynamicallyLinkedFunctionPtr_h
diff --git a/mozglue/misc/ImportDir.h b/mozglue/misc/ImportDir.h
new file mode 100644
index 0000000000..6f7721d966
--- /dev/null
+++ b/mozglue/misc/ImportDir.h
@@ -0,0 +1,94 @@
+/* -*- 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/. */
+
+#include "mozilla/NativeNt.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+
+namespace mozilla {
+namespace detail {
+
+inline LauncherResult<nt::DataDirectoryEntry> GetImageDirectoryViaFileIo(
+ const nsAutoHandle& aImageFile, const uint32_t aOurImportDirectoryRva) {
+ OVERLAPPED ov = {};
+ ov.Offset = aOurImportDirectoryRva;
+
+ DWORD bytesRead;
+ nt::DataDirectoryEntry result;
+ if (!::ReadFile(aImageFile, &result, sizeof(result), &bytesRead, &ov) ||
+ bytesRead != sizeof(result)) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+
+ return result;
+}
+
+} // namespace detail
+
+/**
+ * This function ensures that the import directory of a loaded binary image
+ * matches the version that is found in the original file on disk. We do this
+ * to prevent tampering by third-party code.
+ *
+ * Yes, this function may perform file I/O on the critical path during
+ * startup. A mitigating factor here is that this function must be called
+ * immediately after creating a process using the image specified by
+ * |aFullImagePath|; by this point, the system has already paid the price of
+ * pulling the image file's contents into the page cache.
+ *
+ * @param aFullImagePath Wide-character string containing the absolute path
+ * to the binary whose import directory we are touching.
+ * @param aTransferMgr Encapsulating the transfer from the current process to
+ * the child process whose import table we are touching.
+ */
+inline LauncherVoidResult RestoreImportDirectory(
+ const wchar_t* aFullImagePath, nt::CrossExecTransferManager& aTransferMgr) {
+ uint32_t importDirEntryRva;
+ PIMAGE_DATA_DIRECTORY importDirEntry =
+ aTransferMgr.LocalPEHeaders().GetImageDirectoryEntryPtr(
+ IMAGE_DIRECTORY_ENTRY_IMPORT, &importDirEntryRva);
+ if (!importDirEntry) {
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
+ }
+
+ nsAutoHandle file(::CreateFileW(aFullImagePath, GENERIC_READ, FILE_SHARE_READ,
+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
+ nullptr));
+ if (file.get() == INVALID_HANDLE_VALUE) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+
+ // Why do we use file I/O here instead of a memory mapping? The simple reason
+ // is that we do not want any kernel-mode drivers to start tampering with file
+ // contents under the belief that the file is being mapped for execution.
+ // Windows 8 supports creation of file mappings using the SEC_IMAGE_NO_EXECUTE
+ // flag, which may help to mitigate this, but we might as well just support
+ // a single implementation that works everywhere.
+ LauncherResult<nt::DataDirectoryEntry> realImportDirectory =
+ detail::GetImageDirectoryViaFileIo(file, importDirEntryRva);
+ if (realImportDirectory.isErr()) {
+ return realImportDirectory.propagateErr();
+ }
+
+ nt::DataDirectoryEntry toWrite = realImportDirectory.unwrap();
+
+ { // Scope for prot
+ AutoVirtualProtect prot = aTransferMgr.Protect(
+ importDirEntry, sizeof(IMAGE_DATA_DIRECTORY), PAGE_READWRITE);
+ if (!prot) {
+ return LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(prot.GetError());
+ }
+
+ LauncherVoidResult writeResult = aTransferMgr.Transfer(
+ importDirEntry, &toWrite, sizeof(IMAGE_DATA_DIRECTORY));
+ if (writeResult.isErr()) {
+ return writeResult.propagateErr();
+ }
+ }
+
+ return Ok();
+}
+
+} // namespace mozilla
diff --git a/mozglue/misc/MmapFaultHandler.cpp b/mozglue/misc/MmapFaultHandler.cpp
new file mode 100644
index 0000000000..1702a34243
--- /dev/null
+++ b/mozglue/misc/MmapFaultHandler.cpp
@@ -0,0 +1,131 @@
+/* -*- 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/. */
+
+#include "MmapFaultHandler.h"
+
+#if defined(XP_UNIX) && !defined(XP_DARWIN)
+
+# include "PlatformMutex.h"
+# include "mozilla/Atomics.h"
+# include "mozilla/MemoryChecking.h"
+# include "mozilla/ThreadLocal.h"
+# include <signal.h>
+# include <cstring>
+
+static MOZ_THREAD_LOCAL(MmapAccessScope*) sMmapAccessScope;
+
+static struct sigaction sPrevSIGBUSHandler;
+
+static void MmapSIGBUSHandler(int signum, siginfo_t* info, void* context) {
+ MOZ_RELEASE_ASSERT(signum == SIGBUS);
+
+ MmapAccessScope* mas = sMmapAccessScope.get();
+
+ if (mas && mas->IsInsideBuffer(info->si_addr)) {
+ // Temporarily instead of handling the signal, we crash intentionally and
+ // send some diagnostic information to find out why the signal is received.
+ mas->CrashWithInfo(info->si_addr);
+
+ // The address is inside the buffer, handle the failure.
+ siglongjmp(mas->mJmpBuf, signum);
+ }
+
+ // This signal is not caused by accessing region protected by MmapAccessScope.
+ // Forward the signal to the next handler.
+ if (sPrevSIGBUSHandler.sa_flags & SA_SIGINFO) {
+ sPrevSIGBUSHandler.sa_sigaction(signum, info, context);
+ } else if (sPrevSIGBUSHandler.sa_handler == SIG_DFL ||
+ sPrevSIGBUSHandler.sa_handler == SIG_IGN) {
+ // There is no next handler. Uninstalling our handler and returning will
+ // cause a crash.
+ sigaction(signum, &sPrevSIGBUSHandler, nullptr);
+ } else {
+ sPrevSIGBUSHandler.sa_handler(signum);
+ }
+}
+
+mozilla::Atomic<bool> gSIGBUSHandlerInstalled(false);
+mozilla::Atomic<bool> gSIGBUSHandlerInstalling(false);
+
+void InstallMmapFaultHandler() {
+ // This function is called from MmapAccessScope's constructor because there is
+ // no single point where we could install the handler during startup. This
+ // means that it's called quite often, so to minimize using of the mutex we
+ // first check the atomic variable outside the lock.
+ if (gSIGBUSHandlerInstalled) {
+ return;
+ }
+
+ if (gSIGBUSHandlerInstalling.compareExchange(false, true)) {
+ sMmapAccessScope.infallibleInit();
+
+ struct sigaction busHandler;
+ busHandler.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK;
+ busHandler.sa_sigaction = MmapSIGBUSHandler;
+ sigemptyset(&busHandler.sa_mask);
+ if (sigaction(SIGBUS, &busHandler, &sPrevSIGBUSHandler)) {
+ MOZ_CRASH("Unable to install SIGBUS handler");
+ }
+
+ MOZ_ASSERT(!gSIGBUSHandlerInstalled);
+ gSIGBUSHandlerInstalled = true;
+ } else {
+ // Just spin lock here. It should not take a substantial amount
+ // of time, so a mutex would likely be a spin lock anyway, and
+ // this avoids the need to new up a static mutex from within
+ // mozglue/misc, which complicates things with
+ // check_vanilla_allocations.py
+ while (!gSIGBUSHandlerInstalled) {
+ }
+ }
+}
+
+MmapAccessScope::MmapAccessScope(void* aBuf, uint32_t aBufLen,
+ const char* aFilename) {
+ // Install signal handler if it wasn't installed yet.
+ InstallMmapFaultHandler();
+
+ // We'll handle the signal only if the crashing address is inside this buffer.
+ mBuf = aBuf;
+ mBufLen = aBufLen;
+ mFilename = aFilename;
+
+ SetThreadLocalScope();
+}
+
+MmapAccessScope::~MmapAccessScope() {
+ MOZ_RELEASE_ASSERT(sMmapAccessScope.get() == this);
+ sMmapAccessScope.set(mPreviousScope);
+}
+
+void MmapAccessScope::SetThreadLocalScope() {
+ // mJmpBuf is set outside of this classs for reasons mentioned in the header
+ // file, but we need to initialize the member here too to make Coverity happy.
+ memset(mJmpBuf, 0, sizeof(sigjmp_buf));
+
+ // If MmapAccessScopes are nested, save the previous one and restore it in
+ // the destructor.
+ mPreviousScope = sMmapAccessScope.get();
+
+ // MmapAccessScope is now set up (except mJmpBuf for reasons mentioned in the
+ // header file). Store the pointer in a thread-local variable sMmapAccessScope
+ // so we can use it in the handler if the signal is triggered.
+ sMmapAccessScope.set(this);
+}
+
+bool MmapAccessScope::IsInsideBuffer(void* aPtr) {
+ return aPtr >= mBuf && aPtr < (void*)((char*)mBuf + mBufLen);
+}
+
+void MmapAccessScope::CrashWithInfo(void* aPtr) {
+ // All we have is the buffer and the crashing address.
+ MOZ_CRASH_UNSAFE_PRINTF(
+ "SIGBUS received when accessing mmaped file [buffer=%p, "
+ "buflen=%u, address=%p, filename=%s]",
+ mBuf, mBufLen, aPtr, mFilename);
+}
+
+#endif
diff --git a/mozglue/misc/MmapFaultHandler.h b/mozglue/misc/MmapFaultHandler.h
new file mode 100644
index 0000000000..5fb6cdb142
--- /dev/null
+++ b/mozglue/misc/MmapFaultHandler.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 MmapFaultHandler_h_
+#define MmapFaultHandler_h_
+
+#if defined(XP_WIN)
+// Windows
+
+# ifdef HAVE_SEH_EXCEPTIONS
+# define MMAP_FAULT_HANDLER_BEGIN_HANDLE(fd) __try {
+# define MMAP_FAULT_HANDLER_BEGIN_BUFFER(buf, bufLen) __try {
+# define MMAP_FAULT_HANDLER_CATCH(retval) \
+ } \
+ __except (GetExceptionCode() == EXCEPTION_IN_PAGE_ERROR \
+ ? EXCEPTION_EXECUTE_HANDLER \
+ : EXCEPTION_CONTINUE_SEARCH) { \
+ NS_WARNING("unexpected EXCEPTION_IN_PAGE_ERROR"); \
+ return retval; \
+ }
+# else
+# define MMAP_FAULT_HANDLER_BEGIN_HANDLE(fd) {
+# define MMAP_FAULT_HANDLER_BEGIN_BUFFER(buf, bufLen) {
+# define MMAP_FAULT_HANDLER_CATCH(retval) }
+# endif
+
+#elif defined(XP_DARWIN)
+// MacOS
+
+# define MMAP_FAULT_HANDLER_BEGIN_HANDLE(fd) {
+# define MMAP_FAULT_HANDLER_BEGIN_BUFFER(buf, bufLen) {
+# define MMAP_FAULT_HANDLER_CATCH(retval) }
+
+#else
+// Linux
+
+# include "mozilla/Attributes.h"
+# include "mozilla/Types.h"
+# include <stdint.h>
+# include <setjmp.h>
+
+class MOZ_RAII MmapAccessScope {
+ public:
+ MFBT_API MmapAccessScope(void* aBuf, uint32_t aBufLen,
+ const char* aFilename = nullptr);
+ MFBT_API ~MmapAccessScope();
+
+ MmapAccessScope(const MmapAccessScope&) = delete;
+ MmapAccessScope& operator=(const MmapAccessScope&) = delete;
+
+ void SetThreadLocalScope();
+ bool IsInsideBuffer(void* aPtr);
+ void CrashWithInfo(void* aPtr);
+
+ // sigsetjmp cannot be called from a method that returns before calling
+ // siglongjmp, so the macro must call sigsetjmp directly and mJmpBuf must be
+ // public.
+ sigjmp_buf mJmpBuf;
+
+ private:
+ void* mBuf;
+ const char* mFilename;
+ uint32_t mBufLen;
+ MmapAccessScope* mPreviousScope;
+};
+
+// Gets around warnings for null-checking in a macro.
+template <typename T>
+inline bool ValidFD(T fd) {
+ return !!fd;
+}
+
+# define MMAP_FAULT_HANDLER_BEGIN_HANDLE(fd) \
+ { \
+ void* mmapScopeBuf = nullptr; \
+ nsCString mmapScopeFilename; \
+ uint32_t mmapScopeBufLen = 0; \
+ if (ValidFD(fd) && fd->mMap) { \
+ mmapScopeBuf = (void*)fd->mFileStart; \
+ mmapScopeBufLen = fd->mTotalLen; \
+ } \
+ if (ValidFD(fd) && fd->mFile) { \
+ nsCOMPtr<nsIFile> file = fd->mFile.GetBaseFile(); \
+ file->GetNativeLeafName(mmapScopeFilename); \
+ } \
+ MmapAccessScope mmapScope(mmapScopeBuf, mmapScopeBufLen, \
+ mmapScopeFilename.get()); \
+ if (sigsetjmp(mmapScope.mJmpBuf, 0) == 0) {
+# define MMAP_FAULT_HANDLER_BEGIN_BUFFER(buf, bufLen) \
+ { \
+ MmapAccessScope mmapScope((void*)(buf), (bufLen)); \
+ if (sigsetjmp(mmapScope.mJmpBuf, 0) == 0) {
+# define MMAP_FAULT_HANDLER_CATCH(retval) \
+ } \
+ else { \
+ NS_WARNING("SIGBUS received when accessing mmapped file"); \
+ return retval; \
+ } \
+ }
+
+#endif
+
+#endif
diff --git a/mozglue/misc/MutexPlatformData_posix.h b/mozglue/misc/MutexPlatformData_posix.h
new file mode 100644
index 0000000000..d1659d8d7b
--- /dev/null
+++ b/mozglue/misc/MutexPlatformData_posix.h
@@ -0,0 +1,18 @@
+/* -*- 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 MutexPlatformData_posix_h
+#define MutexPlatformData_posix_h
+
+#include <pthread.h>
+
+#include "mozilla/PlatformMutex.h"
+
+struct mozilla::detail::MutexImpl::PlatformData {
+ pthread_mutex_t ptMutex;
+};
+
+#endif // MutexPlatformData_posix_h
diff --git a/mozglue/misc/MutexPlatformData_windows.h b/mozglue/misc/MutexPlatformData_windows.h
new file mode 100644
index 0000000000..489f921115
--- /dev/null
+++ b/mozglue/misc/MutexPlatformData_windows.h
@@ -0,0 +1,18 @@
+/* -*- 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 MutexPlatformData_windows_h
+#define MutexPlatformData_windows_h
+
+#include <windows.h>
+
+#include "mozilla/PlatformMutex.h"
+
+struct mozilla::detail::MutexImpl::PlatformData {
+ SRWLOCK lock;
+};
+
+#endif // MutexPlatformData_windows_h
diff --git a/mozglue/misc/Mutex_posix.cpp b/mozglue/misc/Mutex_posix.cpp
new file mode 100644
index 0000000000..7378a544f2
--- /dev/null
+++ b/mozglue/misc/Mutex_posix.cpp
@@ -0,0 +1,133 @@
+/* -*- 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/. */
+
+#include "mozilla/Assertions.h"
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdio.h>
+
+#if defined(XP_DARWIN)
+# include <pthread_spis.h>
+#endif
+
+#include "mozilla/PlatformMutex.h"
+#include "MutexPlatformData_posix.h"
+
+#define REPORT_PTHREADS_ERROR(result, msg) \
+ { \
+ errno = result; \
+ perror(msg); \
+ MOZ_CRASH(msg); \
+ }
+
+#define TRY_CALL_PTHREADS(call, msg) \
+ { \
+ int result = (call); \
+ if (result != 0) { \
+ REPORT_PTHREADS_ERROR(result, msg); \
+ } \
+ }
+
+mozilla::detail::MutexImpl::MutexImpl() {
+ pthread_mutexattr_t* attrp = nullptr;
+
+#if defined(DEBUG)
+# define MUTEX_KIND PTHREAD_MUTEX_ERRORCHECK
+// Linux with glibc, FreeBSD and macOS 10.14+ support adaptive mutexes that
+// spin for a short number of tries before sleeping. NSPR's locks did this,
+// too, and it seems like a reasonable thing to do.
+#elif (defined(__linux__) && defined(__GLIBC__)) || defined(__FreeBSD__)
+# define MUTEX_KIND PTHREAD_MUTEX_ADAPTIVE_NP
+#elif defined(XP_DARWIN)
+# if defined(PTHREAD_MUTEX_POLICY_FIRSTFIT_NP)
+# define POLICY_KIND PTHREAD_MUTEX_POLICY_FIRSTFIT_NP
+# else
+# define POLICY_KIND (3) // The definition is missing in old SDKs
+# endif
+#endif
+
+#if defined(MUTEX_KIND) || defined(POLICY_KIND)
+# define ATTR_REQUIRED
+#endif
+
+#if defined(ATTR_REQUIRED)
+ pthread_mutexattr_t attr;
+
+ TRY_CALL_PTHREADS(
+ pthread_mutexattr_init(&attr),
+ "mozilla::detail::MutexImpl::MutexImpl: pthread_mutexattr_init failed");
+
+# if defined(MUTEX_KIND)
+ TRY_CALL_PTHREADS(pthread_mutexattr_settype(&attr, MUTEX_KIND),
+ "mozilla::detail::MutexImpl::MutexImpl: "
+ "pthread_mutexattr_settype failed");
+# elif defined(POLICY_KIND)
+ if (__builtin_available(macOS 10.14, *)) {
+ TRY_CALL_PTHREADS(pthread_mutexattr_setpolicy_np(&attr, POLICY_KIND),
+ "mozilla::detail::MutexImpl::MutexImpl: "
+ "pthread_mutexattr_setpolicy_np failed");
+ }
+# endif
+ attrp = &attr;
+#endif
+
+ TRY_CALL_PTHREADS(
+ pthread_mutex_init(&platformData()->ptMutex, attrp),
+ "mozilla::detail::MutexImpl::MutexImpl: pthread_mutex_init failed");
+
+#if defined(ATTR_REQUIRED)
+ TRY_CALL_PTHREADS(pthread_mutexattr_destroy(&attr),
+ "mozilla::detail::MutexImpl::MutexImpl: "
+ "pthread_mutexattr_destroy failed");
+#endif
+}
+
+mozilla::detail::MutexImpl::~MutexImpl() {
+ TRY_CALL_PTHREADS(
+ pthread_mutex_destroy(&platformData()->ptMutex),
+ "mozilla::detail::MutexImpl::~MutexImpl: pthread_mutex_destroy failed");
+}
+
+inline void mozilla::detail::MutexImpl::mutexLock() {
+ TRY_CALL_PTHREADS(
+ pthread_mutex_lock(&platformData()->ptMutex),
+ "mozilla::detail::MutexImpl::mutexLock: pthread_mutex_lock failed");
+}
+
+bool mozilla::detail::MutexImpl::tryLock() { return mutexTryLock(); }
+
+bool mozilla::detail::MutexImpl::mutexTryLock() {
+ int result = pthread_mutex_trylock(&platformData()->ptMutex);
+ if (result == 0) {
+ return true;
+ }
+
+ if (result == EBUSY) {
+ return false;
+ }
+
+ REPORT_PTHREADS_ERROR(
+ result,
+ "mozilla::detail::MutexImpl::mutexTryLock: pthread_mutex_trylock failed");
+}
+
+void mozilla::detail::MutexImpl::lock() { mutexLock(); }
+
+void mozilla::detail::MutexImpl::unlock() {
+ TRY_CALL_PTHREADS(
+ pthread_mutex_unlock(&platformData()->ptMutex),
+ "mozilla::detail::MutexImpl::unlock: pthread_mutex_unlock failed");
+}
+
+#undef TRY_CALL_PTHREADS
+
+mozilla::detail::MutexImpl::PlatformData*
+mozilla::detail::MutexImpl::platformData() {
+ static_assert(sizeof(platformData_) >= sizeof(PlatformData),
+ "platformData_ is too small");
+ return reinterpret_cast<PlatformData*>(platformData_);
+}
diff --git a/mozglue/misc/Mutex_windows.cpp b/mozglue/misc/Mutex_windows.cpp
new file mode 100644
index 0000000000..c4c78fd4f6
--- /dev/null
+++ b/mozglue/misc/Mutex_windows.cpp
@@ -0,0 +1,40 @@
+/* -*- 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/. */
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/PlatformMutex.h"
+
+#include <windows.h>
+
+#include "MutexPlatformData_windows.h"
+
+mozilla::detail::MutexImpl::MutexImpl() {
+ InitializeSRWLock(&platformData()->lock);
+}
+
+mozilla::detail::MutexImpl::~MutexImpl() {}
+
+void mozilla::detail::MutexImpl::lock() {
+ AcquireSRWLockExclusive(&platformData()->lock);
+}
+
+bool mozilla::detail::MutexImpl::tryLock() { return mutexTryLock(); }
+
+bool mozilla::detail::MutexImpl::mutexTryLock() {
+ return !!TryAcquireSRWLockExclusive(&platformData()->lock);
+}
+
+void mozilla::detail::MutexImpl::unlock() {
+ ReleaseSRWLockExclusive(&platformData()->lock);
+}
+
+mozilla::detail::MutexImpl::PlatformData*
+mozilla::detail::MutexImpl::platformData() {
+ static_assert(sizeof(platformData_) >= sizeof(PlatformData),
+ "platformData_ is too small");
+ return reinterpret_cast<PlatformData*>(platformData_);
+}
diff --git a/mozglue/misc/NativeNt.h b/mozglue/misc/NativeNt.h
new file mode 100644
index 0000000000..ca37fc7bec
--- /dev/null
+++ b/mozglue/misc/NativeNt.h
@@ -0,0 +1,1680 @@
+/* -*- 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_NativeNt_h
+#define mozilla_NativeNt_h
+
+#include <stdint.h>
+#include <windows.h>
+#include <winnt.h>
+#include <winternl.h>
+
+#include <algorithm>
+#include <utility>
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Range.h"
+#include "mozilla/Span.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+#include "mozilla/interceptor/MMPolicies.h"
+#include "mozilla/interceptor/TargetFunction.h"
+
+#if defined(MOZILLA_INTERNAL_API)
+# include "nsHashKeys.h"
+# include "nsString.h"
+# include "nsTHashtable.h"
+#endif // defined(MOZILLA_INTERNAL_API)
+
+// The declarations within this #if block are intended to be used for initial
+// process initialization ONLY. You probably don't want to be using these in
+// normal Gecko code!
+#if !defined(MOZILLA_INTERNAL_API)
+
+extern "C" {
+
+# if !defined(STATUS_ACCESS_DENIED)
+# define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L)
+# endif // !defined(STATUS_ACCESS_DENIED)
+
+# if !defined(STATUS_DLL_NOT_FOUND)
+# define STATUS_DLL_NOT_FOUND ((NTSTATUS)0xC0000135L)
+# endif // !defined(STATUS_DLL_NOT_FOUND)
+
+# if !defined(STATUS_UNSUCCESSFUL)
+# define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L)
+# endif // !defined(STATUS_UNSUCCESSFUL)
+
+# if !defined(STATUS_INFO_LENGTH_MISMATCH)
+# define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
+# endif
+
+enum SECTION_INHERIT { ViewShare = 1, ViewUnmap = 2 };
+
+NTSTATUS NTAPI NtMapViewOfSection(
+ HANDLE aSection, HANDLE aProcess, PVOID* aBaseAddress, ULONG_PTR aZeroBits,
+ SIZE_T aCommitSize, PLARGE_INTEGER aSectionOffset, PSIZE_T aViewSize,
+ SECTION_INHERIT aInheritDisposition, ULONG aAllocationType,
+ ULONG aProtectionFlags);
+
+NTSTATUS NTAPI NtUnmapViewOfSection(HANDLE aProcess, PVOID aBaseAddress);
+
+enum MEMORY_INFORMATION_CLASS {
+ MemoryBasicInformation = 0,
+ MemorySectionName = 2
+};
+
+// NB: When allocating, space for the buffer must also be included
+typedef struct _MEMORY_SECTION_NAME {
+ UNICODE_STRING mSectionFileName;
+} MEMORY_SECTION_NAME, *PMEMORY_SECTION_NAME;
+
+NTSTATUS NTAPI NtQueryVirtualMemory(HANDLE aProcess, PVOID aBaseAddress,
+ MEMORY_INFORMATION_CLASS aMemInfoClass,
+ PVOID aMemInfo, SIZE_T aMemInfoLen,
+ PSIZE_T aReturnLen);
+
+LONG NTAPI RtlCompareUnicodeString(PCUNICODE_STRING aStr1,
+ PCUNICODE_STRING aStr2,
+ BOOLEAN aCaseInsensitive);
+
+BOOLEAN NTAPI RtlEqualUnicodeString(PCUNICODE_STRING aStr1,
+ PCUNICODE_STRING aStr2,
+ BOOLEAN aCaseInsensitive);
+
+NTSTATUS NTAPI RtlGetVersion(PRTL_OSVERSIONINFOW aOutVersionInformation);
+
+VOID NTAPI RtlAcquireSRWLockExclusive(PSRWLOCK aLock);
+VOID NTAPI RtlAcquireSRWLockShared(PSRWLOCK aLock);
+
+VOID NTAPI RtlReleaseSRWLockExclusive(PSRWLOCK aLock);
+VOID NTAPI RtlReleaseSRWLockShared(PSRWLOCK aLock);
+
+ULONG NTAPI RtlNtStatusToDosError(NTSTATUS aStatus);
+VOID NTAPI RtlSetLastWin32Error(DWORD aError);
+DWORD NTAPI RtlGetLastWin32Error();
+
+VOID NTAPI RtlRunOnceInitialize(PRTL_RUN_ONCE aRunOnce);
+
+NTSTATUS NTAPI NtReadVirtualMemory(HANDLE aProcessHandle, PVOID aBaseAddress,
+ PVOID aBuffer, SIZE_T aNumBytesToRead,
+ PSIZE_T aNumBytesRead);
+
+NTSTATUS NTAPI LdrLoadDll(PWCHAR aDllPath, PULONG aFlags,
+ PUNICODE_STRING aDllName, PHANDLE aOutHandle);
+
+typedef ULONG(NTAPI* PRTL_RUN_ONCE_INIT_FN)(PRTL_RUN_ONCE, PVOID, PVOID*);
+NTSTATUS NTAPI RtlRunOnceExecuteOnce(PRTL_RUN_ONCE aRunOnce,
+ PRTL_RUN_ONCE_INIT_FN aInitFn,
+ PVOID aContext, PVOID* aParameter);
+
+} // extern "C"
+
+#endif // !defined(MOZILLA_INTERNAL_API)
+
+extern "C" {
+PVOID NTAPI RtlAllocateHeap(PVOID aHeapHandle, ULONG aFlags, SIZE_T aSize);
+
+PVOID NTAPI RtlReAllocateHeap(PVOID aHeapHandle, ULONG aFlags, LPVOID aMem,
+ SIZE_T aNewSize);
+
+BOOLEAN NTAPI RtlFreeHeap(PVOID aHeapHandle, ULONG aFlags, PVOID aHeapBase);
+
+BOOLEAN NTAPI RtlQueryPerformanceCounter(LARGE_INTEGER* aPerfCount);
+
+#define RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE 1
+#define RTL_DUPLICATE_UNICODE_STRING_ALLOCATE_NULL_STRING 2
+NTSTATUS NTAPI RtlDuplicateUnicodeString(ULONG aFlags, PCUNICODE_STRING aSrc,
+ PUNICODE_STRING aDest);
+
+VOID NTAPI RtlFreeUnicodeString(PUNICODE_STRING aUnicodeString);
+} // extern "C"
+
+namespace mozilla {
+namespace nt {
+
+/**
+ * This class encapsulates a UNICODE_STRING that owns its own buffer. The
+ * buffer is always NULL terminated, thus allowing us to cast to a wide C-string
+ * without requiring any mutation.
+ *
+ * We only allow creation of this owned buffer from outside XUL.
+ */
+class AllocatedUnicodeString final {
+ public:
+ AllocatedUnicodeString() : mUnicodeString() {}
+
+#if defined(MOZILLA_INTERNAL_API)
+ AllocatedUnicodeString(const AllocatedUnicodeString& aOther) = delete;
+
+ AllocatedUnicodeString& operator=(const AllocatedUnicodeString& aOther) =
+ delete;
+#else
+ explicit AllocatedUnicodeString(PCUNICODE_STRING aSrc) {
+ if (!aSrc) {
+ mUnicodeString = {};
+ return;
+ }
+
+ Duplicate(aSrc);
+ }
+
+ explicit AllocatedUnicodeString(const char* aSrc) {
+ if (!aSrc) {
+ mUnicodeString = {};
+ return;
+ }
+
+ Duplicate(aSrc);
+ }
+
+ AllocatedUnicodeString(const AllocatedUnicodeString& aOther) {
+ Duplicate(&aOther.mUnicodeString);
+ }
+
+ AllocatedUnicodeString& operator=(const AllocatedUnicodeString& aOther) {
+ Clear();
+ Duplicate(&aOther.mUnicodeString);
+ return *this;
+ }
+
+ AllocatedUnicodeString& operator=(PCUNICODE_STRING aSrc) {
+ Clear();
+ Duplicate(aSrc);
+ return *this;
+ }
+#endif // defined(MOZILLA_INTERNAL_API)
+
+ AllocatedUnicodeString(AllocatedUnicodeString&& aOther)
+ : mUnicodeString(aOther.mUnicodeString) {
+ aOther.mUnicodeString = {};
+ }
+
+ AllocatedUnicodeString& operator=(AllocatedUnicodeString&& aOther) {
+ Clear();
+ mUnicodeString = aOther.mUnicodeString;
+ aOther.mUnicodeString = {};
+ return *this;
+ }
+
+ ~AllocatedUnicodeString() { Clear(); }
+
+ bool IsEmpty() const {
+ return !mUnicodeString.Buffer || !mUnicodeString.Length;
+ }
+
+ operator PCUNICODE_STRING() const { return &mUnicodeString; }
+
+ operator const WCHAR*() const { return mUnicodeString.Buffer; }
+
+ USHORT CharLen() const { return mUnicodeString.Length / sizeof(WCHAR); }
+
+#if defined(MOZILLA_INTERNAL_API)
+ nsDependentString AsString() const {
+ if (!mUnicodeString.Buffer) {
+ return nsDependentString();
+ }
+
+ // We can use nsDependentString here as we guaranteed null termination
+ // when we allocated the string.
+ return nsDependentString(mUnicodeString.Buffer, CharLen());
+ }
+#endif // defined(MOZILLA_INTERNAL_API)
+
+ private:
+#if !defined(MOZILLA_INTERNAL_API)
+ void Duplicate(PCUNICODE_STRING aSrc) {
+ MOZ_ASSERT(aSrc);
+
+ // We duplicate with null termination so that this string may be used
+ // as a wide C-string without any further manipulation.
+ NTSTATUS ntStatus = ::RtlDuplicateUnicodeString(
+ RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE, aSrc, &mUnicodeString);
+ MOZ_ASSERT(NT_SUCCESS(ntStatus));
+ if (!NT_SUCCESS(ntStatus)) {
+ // Make sure that mUnicodeString does not contain bogus data
+ // (since not all callers zero it out before invoking)
+ mUnicodeString = {};
+ }
+ }
+
+ void Duplicate(const char* aSrc) {
+ MOZ_ASSERT(aSrc);
+
+ ANSI_STRING ansiStr;
+ RtlInitAnsiString(&ansiStr, aSrc);
+ NTSTATUS ntStatus =
+ ::RtlAnsiStringToUnicodeString(&mUnicodeString, &ansiStr, TRUE);
+ MOZ_ASSERT(NT_SUCCESS(ntStatus));
+ if (!NT_SUCCESS(ntStatus)) {
+ mUnicodeString = {};
+ }
+ }
+#endif // !defined(MOZILLA_INTERNAL_API)
+
+ void Clear() {
+ if (!mUnicodeString.Buffer) {
+ return;
+ }
+
+ ::RtlFreeUnicodeString(&mUnicodeString);
+ mUnicodeString = {};
+ }
+
+ UNICODE_STRING mUnicodeString;
+};
+
+#if !defined(MOZILLA_INTERNAL_API)
+
+struct MemorySectionNameBuf : public _MEMORY_SECTION_NAME {
+ MemorySectionNameBuf() {
+ mSectionFileName.Length = 0;
+ mSectionFileName.MaximumLength = sizeof(mBuf);
+ mSectionFileName.Buffer = mBuf;
+ }
+
+ MemorySectionNameBuf(const MemorySectionNameBuf& aOther) { *this = aOther; }
+
+ MemorySectionNameBuf(MemorySectionNameBuf&& aOther) {
+ *this = std::move(aOther);
+ }
+
+ // We cannot use default copy here because mSectionFileName.Buffer needs to
+ // be updated to point to |this->mBuf|, not |aOther.mBuf|.
+ MemorySectionNameBuf& operator=(const MemorySectionNameBuf& aOther) {
+ mSectionFileName.Length = aOther.mSectionFileName.Length;
+ mSectionFileName.MaximumLength = sizeof(mBuf);
+ MOZ_ASSERT(mSectionFileName.Length <= mSectionFileName.MaximumLength);
+ mSectionFileName.Buffer = mBuf;
+ memcpy(mBuf, aOther.mBuf, aOther.mSectionFileName.Length);
+ return *this;
+ }
+
+ MemorySectionNameBuf& operator=(MemorySectionNameBuf&& aOther) {
+ mSectionFileName.Length = aOther.mSectionFileName.Length;
+ aOther.mSectionFileName.Length = 0;
+ mSectionFileName.MaximumLength = sizeof(mBuf);
+ MOZ_ASSERT(mSectionFileName.Length <= mSectionFileName.MaximumLength);
+ aOther.mSectionFileName.MaximumLength = sizeof(aOther.mBuf);
+ mSectionFileName.Buffer = mBuf;
+ memmove(mBuf, aOther.mBuf, mSectionFileName.Length);
+ return *this;
+ }
+
+ // Native NT paths, so we can't assume MAX_PATH. Use a larger buffer.
+ WCHAR mBuf[2 * MAX_PATH];
+
+ bool IsEmpty() const {
+ return !mSectionFileName.Buffer || !mSectionFileName.Length;
+ }
+
+ operator PCUNICODE_STRING() const { return &mSectionFileName; }
+};
+
+class MemorySectionNameOnHeap {
+ UniquePtr<uint8_t[]> mBuffer;
+
+ MemorySectionNameOnHeap() = default;
+ explicit MemorySectionNameOnHeap(size_t aBufferLen)
+ : mBuffer(MakeUnique<uint8_t[]>(aBufferLen)) {}
+
+ public:
+ static MemorySectionNameOnHeap GetBackingFilePath(HANDLE aProcess,
+ void* aSectionAddr) {
+ SIZE_T bufferLen = MAX_PATH * 2;
+ do {
+ MemorySectionNameOnHeap sectionName(bufferLen);
+
+ SIZE_T requiredBytes;
+ NTSTATUS ntStatus = ::NtQueryVirtualMemory(
+ aProcess, aSectionAddr, MemorySectionName, sectionName.mBuffer.get(),
+ bufferLen, &requiredBytes);
+ if (NT_SUCCESS(ntStatus)) {
+ return sectionName;
+ }
+
+ if (ntStatus != STATUS_INFO_LENGTH_MISMATCH ||
+ bufferLen >= requiredBytes) {
+ break;
+ }
+
+ bufferLen = requiredBytes;
+ } while (1);
+
+ return MemorySectionNameOnHeap();
+ }
+
+ // Allow move & Disallow copy
+ MemorySectionNameOnHeap(MemorySectionNameOnHeap&&) = default;
+ MemorySectionNameOnHeap& operator=(MemorySectionNameOnHeap&&) = default;
+ MemorySectionNameOnHeap(const MemorySectionNameOnHeap&) = delete;
+ MemorySectionNameOnHeap& operator=(const MemorySectionNameOnHeap&) = delete;
+
+ PCUNICODE_STRING AsUnicodeString() const {
+ return reinterpret_cast<PCUNICODE_STRING>(mBuffer.get());
+ }
+};
+
+inline bool FindCharInUnicodeString(const UNICODE_STRING& aStr, WCHAR aChar,
+ uint16_t& aPos, uint16_t aStartIndex = 0) {
+ const uint16_t aMaxIndex = aStr.Length / sizeof(WCHAR);
+
+ for (uint16_t curIndex = aStartIndex; curIndex < aMaxIndex; ++curIndex) {
+ if (aStr.Buffer[curIndex] == aChar) {
+ aPos = curIndex;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+inline bool IsHexDigit(WCHAR aChar) {
+ return (aChar >= L'0' && aChar <= L'9') || (aChar >= L'A' && aChar <= L'F') ||
+ (aChar >= L'a' && aChar <= L'f');
+}
+
+inline bool MatchUnicodeString(const UNICODE_STRING& aStr,
+ bool (*aPredicate)(WCHAR)) {
+ WCHAR* cur = aStr.Buffer;
+ WCHAR* end = &aStr.Buffer[aStr.Length / sizeof(WCHAR)];
+ while (cur < end) {
+ if (!aPredicate(*cur)) {
+ return false;
+ }
+
+ ++cur;
+ }
+
+ return true;
+}
+
+inline bool Contains12DigitHexString(const UNICODE_STRING& aLeafName) {
+ // Quick check: If the string is too short, don't bother
+ // (We need at least 12 hex digits, one char for '.', and 3 for extension)
+ const USHORT kMinLen = (12 + 1 + 3) * sizeof(wchar_t);
+ if (aLeafName.Length < kMinLen) {
+ return false;
+ }
+
+ uint16_t start, end;
+ if (!FindCharInUnicodeString(aLeafName, L'.', start)) {
+ return false;
+ }
+
+ ++start;
+ if (!FindCharInUnicodeString(aLeafName, L'.', end, start)) {
+ return false;
+ }
+
+ if (end - start != 12) {
+ return false;
+ }
+
+ UNICODE_STRING test;
+ test.Buffer = &aLeafName.Buffer[start];
+ test.Length = (end - start) * sizeof(WCHAR);
+ test.MaximumLength = test.Length;
+
+ return MatchUnicodeString(test, &IsHexDigit);
+}
+
+inline bool IsFileNameAtLeast16HexDigits(const UNICODE_STRING& aLeafName) {
+ // Quick check: If the string is too short, don't bother
+ // (We need 16 hex digits, one char for '.', and 3 for extension)
+ const USHORT kMinLen = (16 + 1 + 3) * sizeof(wchar_t);
+ if (aLeafName.Length < kMinLen) {
+ return false;
+ }
+
+ uint16_t dotIndex;
+ if (!FindCharInUnicodeString(aLeafName, L'.', dotIndex)) {
+ return false;
+ }
+
+ if (dotIndex < 16) {
+ return false;
+ }
+
+ UNICODE_STRING test;
+ test.Buffer = aLeafName.Buffer;
+ test.Length = dotIndex * sizeof(WCHAR);
+ test.MaximumLength = aLeafName.MaximumLength;
+
+ return MatchUnicodeString(test, &IsHexDigit);
+}
+
+inline void GetLeafName(PUNICODE_STRING aDestString,
+ PCUNICODE_STRING aSrcString) {
+ WCHAR* buf = aSrcString->Buffer;
+ WCHAR* end = &aSrcString->Buffer[(aSrcString->Length / sizeof(WCHAR)) - 1];
+ WCHAR* cur = end;
+ while (cur >= buf) {
+ if (*cur == L'\\') {
+ break;
+ }
+
+ --cur;
+ }
+
+ // At this point, either cur points to the final backslash, or it points to
+ // buf - 1. Either way, we're interested in cur + 1 as the desired buffer.
+ aDestString->Buffer = cur + 1;
+ aDestString->Length = (end - aDestString->Buffer + 1) * sizeof(WCHAR);
+ aDestString->MaximumLength = aDestString->Length;
+}
+
+#endif // !defined(MOZILLA_INTERNAL_API)
+
+#if defined(MOZILLA_INTERNAL_API)
+
+inline const nsDependentSubstring GetLeafName(const nsString& aString) {
+ int32_t lastBackslashPos = aString.RFindChar(L'\\');
+ int32_t leafStartPos =
+ (lastBackslashPos == kNotFound) ? 0 : (lastBackslashPos + 1);
+ return Substring(aString, leafStartPos);
+}
+
+#endif // defined(MOZILLA_INTERNAL_API)
+
+inline char EnsureLowerCaseASCII(char aChar) {
+ if (aChar >= 'A' && aChar <= 'Z') {
+ aChar -= 'A' - 'a';
+ }
+
+ return aChar;
+}
+
+inline int StricmpASCII(const char* aLeft, const char* aRight) {
+ char curLeft, curRight;
+
+ do {
+ curLeft = EnsureLowerCaseASCII(*(aLeft++));
+ curRight = EnsureLowerCaseASCII(*(aRight++));
+ } while (curLeft && curLeft == curRight);
+
+ return curLeft - curRight;
+}
+
+inline int StrcmpASCII(const char* aLeft, const char* aRight) {
+ char curLeft, curRight;
+
+ do {
+ curLeft = *(aLeft++);
+ curRight = *(aRight++);
+ } while (curLeft && curLeft == curRight);
+
+ return curLeft - curRight;
+}
+
+inline size_t StrlenASCII(const char* aStr) {
+ size_t len = 0;
+
+ while (*(aStr++)) {
+ ++len;
+ }
+
+ return len;
+}
+
+class MOZ_RAII PEHeaders final {
+ /**
+ * This structure is documented on MSDN as VS_VERSIONINFO, but is not present
+ * in SDK headers because it cannot be specified as a C struct. The following
+ * structure contains the fixed-length fields at the beginning of
+ * VS_VERSIONINFO.
+ */
+ struct VS_VERSIONINFO_HEADER {
+ WORD wLength;
+ WORD wValueLength;
+ WORD wType;
+ WCHAR szKey[16]; // ArrayLength(L"VS_VERSION_INFO")
+ // Additional data goes here, aligned on a 4-byte boundary
+ };
+
+ public:
+ // The lowest two bits of an HMODULE are used as flags. Stripping those bits
+ // from the HMODULE yields the base address of the binary's memory mapping.
+ // (See LoadLibraryEx docs on MSDN)
+ template <typename T>
+ static T HModuleToBaseAddr(HMODULE aModule) {
+ return reinterpret_cast<T>(reinterpret_cast<uintptr_t>(aModule) &
+ ~uintptr_t(3));
+ }
+
+ explicit PEHeaders(void* aBaseAddress)
+ : PEHeaders(reinterpret_cast<PIMAGE_DOS_HEADER>(aBaseAddress)) {}
+
+ explicit PEHeaders(HMODULE aModule)
+ : PEHeaders(HModuleToBaseAddr<PIMAGE_DOS_HEADER>(aModule)) {}
+
+ explicit PEHeaders(PIMAGE_DOS_HEADER aMzHeader)
+ : mMzHeader(aMzHeader),
+ mPeHeader(nullptr),
+ mImageLimit(nullptr),
+ mIsImportDirectoryTampered(false) {
+ if (!mMzHeader || mMzHeader->e_magic != IMAGE_DOS_SIGNATURE) {
+ return;
+ }
+
+ mPeHeader = RVAToPtrUnchecked<PIMAGE_NT_HEADERS>(mMzHeader->e_lfanew);
+ if (!mPeHeader || mPeHeader->Signature != IMAGE_NT_SIGNATURE) {
+ return;
+ }
+
+ if (mPeHeader->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC) {
+ return;
+ }
+
+ DWORD imageSize = mPeHeader->OptionalHeader.SizeOfImage;
+ // This is a coarse-grained check to ensure that the image size is
+ // reasonable. It we aren't big enough to contain headers, we have a
+ // problem!
+ if (imageSize < sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS)) {
+ return;
+ }
+
+ mImageLimit = RVAToPtrUnchecked<void*>(imageSize - 1UL);
+
+ PIMAGE_DATA_DIRECTORY importDirEntry =
+ GetImageDirectoryEntryPtr(IMAGE_DIRECTORY_ENTRY_IMPORT);
+ if (!importDirEntry) {
+ return;
+ }
+
+ mIsImportDirectoryTampered = (importDirEntry->VirtualAddress >= imageSize);
+ }
+
+ explicit operator bool() const { return !!mImageLimit; }
+
+ /**
+ * This overload computes absolute virtual addresses relative to the base
+ * address of the binary.
+ */
+ template <typename T, typename R>
+ T RVAToPtr(R aRva) const {
+ return RVAToPtr<T>(mMzHeader, aRva);
+ }
+
+ /**
+ * This overload computes a result by adding aRva to aBase, but also ensures
+ * that the resulting pointer falls within the bounds of this binary's memory
+ * mapping.
+ */
+ template <typename T, typename R>
+ T RVAToPtr(void* aBase, R aRva) const {
+ if (!mImageLimit) {
+ return nullptr;
+ }
+
+ char* absAddress = reinterpret_cast<char*>(aBase) + aRva;
+ if (absAddress < reinterpret_cast<char*>(mMzHeader) ||
+ absAddress > reinterpret_cast<char*>(mImageLimit)) {
+ return nullptr;
+ }
+
+ return reinterpret_cast<T>(absAddress);
+ }
+
+ Maybe<Range<const uint8_t>> GetBounds() const {
+ if (!mImageLimit) {
+ return Nothing();
+ }
+
+ auto base = reinterpret_cast<const uint8_t*>(mMzHeader);
+ DWORD imageSize = mPeHeader->OptionalHeader.SizeOfImage;
+ return Some(Range(base, imageSize));
+ }
+
+ bool IsWithinImage(const void* aAddress) const {
+ uintptr_t addr = reinterpret_cast<uintptr_t>(aAddress);
+ uintptr_t imageBase = reinterpret_cast<uintptr_t>(mMzHeader);
+ uintptr_t imageLimit = reinterpret_cast<uintptr_t>(mImageLimit);
+ return addr >= imageBase && addr <= imageLimit;
+ }
+
+ PIMAGE_IMPORT_DESCRIPTOR GetImportDirectory() const {
+ // If the import directory is already tampered, we skip bounds check
+ // because it could be located outside the mapped image.
+ return mIsImportDirectoryTampered
+ ? GetImageDirectoryEntry<PIMAGE_IMPORT_DESCRIPTOR,
+ BoundsCheckPolicy::Skip>(
+ IMAGE_DIRECTORY_ENTRY_IMPORT)
+ : GetImageDirectoryEntry<PIMAGE_IMPORT_DESCRIPTOR>(
+ IMAGE_DIRECTORY_ENTRY_IMPORT);
+ }
+
+ PIMAGE_RESOURCE_DIRECTORY GetResourceTable() const {
+ return GetImageDirectoryEntry<PIMAGE_RESOURCE_DIRECTORY>(
+ IMAGE_DIRECTORY_ENTRY_RESOURCE);
+ }
+
+ PIMAGE_DATA_DIRECTORY GetImageDirectoryEntryPtr(
+ const uint32_t aDirectoryIndex, uint32_t* aOutRva = nullptr) const {
+ if (aOutRva) {
+ *aOutRva = 0;
+ }
+
+ IMAGE_OPTIONAL_HEADER& optionalHeader = mPeHeader->OptionalHeader;
+
+ const uint32_t maxIndex = std::min(optionalHeader.NumberOfRvaAndSizes,
+ DWORD(IMAGE_NUMBEROF_DIRECTORY_ENTRIES));
+ if (aDirectoryIndex >= maxIndex) {
+ return nullptr;
+ }
+
+ PIMAGE_DATA_DIRECTORY dirEntry =
+ &optionalHeader.DataDirectory[aDirectoryIndex];
+ if (aOutRva) {
+ *aOutRva = reinterpret_cast<char*>(dirEntry) -
+ reinterpret_cast<char*>(mMzHeader);
+ MOZ_ASSERT(*aOutRva);
+ }
+
+ return dirEntry;
+ }
+
+ bool GetVersionInfo(uint64_t& aOutVersion) const {
+ // RT_VERSION == 16
+ // Version resources require an id of 1
+ auto root = FindResourceLeaf<VS_VERSIONINFO_HEADER*>(16, 1);
+ if (!root) {
+ return false;
+ }
+
+ VS_FIXEDFILEINFO* fixedInfo = GetFixedFileInfo(root);
+ if (!fixedInfo) {
+ return false;
+ }
+
+ aOutVersion = ((static_cast<uint64_t>(fixedInfo->dwFileVersionMS) << 32) |
+ static_cast<uint64_t>(fixedInfo->dwFileVersionLS));
+ return true;
+ }
+
+ bool GetTimeStamp(DWORD& aResult) const {
+ if (!(*this)) {
+ return false;
+ }
+
+ aResult = mPeHeader->FileHeader.TimeDateStamp;
+ return true;
+ }
+
+ PIMAGE_IMPORT_DESCRIPTOR
+ GetImportDescriptor(const char* aModuleNameASCII) const {
+ for (PIMAGE_IMPORT_DESCRIPTOR curImpDesc = GetImportDirectory();
+ IsValid(curImpDesc); ++curImpDesc) {
+ auto curName = mIsImportDirectoryTampered
+ ? RVAToPtrUnchecked<const char*>(curImpDesc->Name)
+ : RVAToPtr<const char*>(curImpDesc->Name);
+ if (!curName) {
+ return nullptr;
+ }
+
+ if (StricmpASCII(aModuleNameASCII, curName)) {
+ continue;
+ }
+
+ // curImpDesc now points to the IAT for the module we're interested in
+ return curImpDesc;
+ }
+
+ return nullptr;
+ }
+
+ template <typename CallbackT>
+ void EnumImportChunks(const CallbackT& aCallback) const {
+ for (PIMAGE_IMPORT_DESCRIPTOR curImpDesc = GetImportDirectory();
+ IsValid(curImpDesc); ++curImpDesc) {
+ auto curName = mIsImportDirectoryTampered
+ ? RVAToPtrUnchecked<const char*>(curImpDesc->Name)
+ : RVAToPtr<const char*>(curImpDesc->Name);
+ if (!curName) {
+ continue;
+ }
+
+ aCallback(curName);
+ }
+ }
+
+#if defined(MOZILLA_INTERNAL_API)
+ nsTHashtable<nsStringCaseInsensitiveHashKey> GenerateDependentModuleSet()
+ const {
+ nsTHashtable<nsStringCaseInsensitiveHashKey> dependentModuleSet;
+ EnumImportChunks([&dependentModuleSet](const char* aModule) {
+ dependentModuleSet.PutEntry(GetLeafName(NS_ConvertASCIItoUTF16(aModule)));
+ });
+ return dependentModuleSet;
+ }
+#endif // defined(MOZILLA_INTERNAL_API)
+
+ /**
+ * If |aBoundaries| is given, this method checks whether each IAT entry is
+ * within the given range, and if any entry is out of the range, we return
+ * Nothing().
+ */
+ Maybe<Span<IMAGE_THUNK_DATA>> GetIATThunksForModule(
+ const char* aModuleNameASCII,
+ const Range<const uint8_t>* aBoundaries = nullptr) const {
+ PIMAGE_IMPORT_DESCRIPTOR impDesc = GetImportDescriptor(aModuleNameASCII);
+ if (!impDesc) {
+ return Nothing();
+ }
+
+ auto firstIatThunk =
+ this->template RVAToPtr<PIMAGE_THUNK_DATA>(impDesc->FirstThunk);
+ if (!firstIatThunk) {
+ return Nothing();
+ }
+
+ // Find the length by iterating through the table until we find a null entry
+ PIMAGE_THUNK_DATA curIatThunk = firstIatThunk;
+ while (IsValid(curIatThunk)) {
+ if (aBoundaries) {
+ auto iatEntry =
+ reinterpret_cast<const uint8_t*>(curIatThunk->u1.Function);
+ if (iatEntry < aBoundaries->begin().get() ||
+ iatEntry >= aBoundaries->end().get()) {
+ return Nothing();
+ }
+ }
+
+ ++curIatThunk;
+ }
+
+ return Some(Span(firstIatThunk, curIatThunk));
+ }
+
+ /**
+ * Resources are stored in a three-level tree. To locate a particular entry,
+ * you must supply a resource type, the resource id, and then the language id.
+ * If aLangId == 0, we just resolve the first entry regardless of language.
+ */
+ template <typename T>
+ T FindResourceLeaf(WORD aType, WORD aResId, WORD aLangId = 0) const {
+ PIMAGE_RESOURCE_DIRECTORY topLevel = GetResourceTable();
+ if (!topLevel) {
+ return nullptr;
+ }
+
+ PIMAGE_RESOURCE_DIRECTORY_ENTRY typeEntry =
+ FindResourceEntry(topLevel, aType);
+ if (!typeEntry || !typeEntry->DataIsDirectory) {
+ return nullptr;
+ }
+
+ auto idDir = RVAToPtr<PIMAGE_RESOURCE_DIRECTORY>(
+ topLevel, typeEntry->OffsetToDirectory);
+ PIMAGE_RESOURCE_DIRECTORY_ENTRY idEntry = FindResourceEntry(idDir, aResId);
+ if (!idEntry || !idEntry->DataIsDirectory) {
+ return nullptr;
+ }
+
+ auto langDir = RVAToPtr<PIMAGE_RESOURCE_DIRECTORY>(
+ topLevel, idEntry->OffsetToDirectory);
+ PIMAGE_RESOURCE_DIRECTORY_ENTRY langEntry;
+ if (aLangId) {
+ langEntry = FindResourceEntry(langDir, aLangId);
+ } else {
+ langEntry = FindFirstResourceEntry(langDir);
+ }
+
+ if (!langEntry || langEntry->DataIsDirectory) {
+ return nullptr;
+ }
+
+ auto dataEntry =
+ RVAToPtr<PIMAGE_RESOURCE_DATA_ENTRY>(topLevel, langEntry->OffsetToData);
+ return RVAToPtr<T>(dataEntry->OffsetToData);
+ }
+
+ template <size_t N>
+ Maybe<Span<const uint8_t>> FindSection(const char (&aSecName)[N],
+ DWORD aCharacteristicsMask) const {
+ static_assert((N - 1) <= IMAGE_SIZEOF_SHORT_NAME,
+ "Section names must be at most 8 characters excluding null "
+ "terminator");
+
+ if (!(*this)) {
+ return Nothing();
+ }
+
+ Span<IMAGE_SECTION_HEADER> sectionTable = GetSectionTable();
+ for (auto&& sectionHeader : sectionTable) {
+ if (strncmp(reinterpret_cast<const char*>(sectionHeader.Name), aSecName,
+ IMAGE_SIZEOF_SHORT_NAME)) {
+ continue;
+ }
+
+ if (!(sectionHeader.Characteristics & aCharacteristicsMask)) {
+ // We found the section but it does not have the expected
+ // characteristics
+ return Nothing();
+ }
+
+ DWORD rva = sectionHeader.VirtualAddress;
+ if (!rva) {
+ return Nothing();
+ }
+
+ DWORD size = sectionHeader.Misc.VirtualSize;
+ if (!size) {
+ return Nothing();
+ }
+
+ auto base = RVAToPtr<const uint8_t*>(rva);
+ return Some(Span(base, size));
+ }
+
+ return Nothing();
+ }
+
+ // There may be other code sections in the binary besides .text
+ Maybe<Span<const uint8_t>> GetTextSectionInfo() const {
+ return FindSection(".text", IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE |
+ IMAGE_SCN_MEM_READ);
+ }
+
+ static bool IsValid(PIMAGE_IMPORT_DESCRIPTOR aImpDesc) {
+ return aImpDesc && aImpDesc->OriginalFirstThunk != 0;
+ }
+
+ static bool IsValid(PIMAGE_THUNK_DATA aImgThunk) {
+ return aImgThunk && aImgThunk->u1.Ordinal != 0;
+ }
+
+ bool IsImportDirectoryTampered() const { return mIsImportDirectoryTampered; }
+
+ FARPROC GetEntryPoint() const {
+ // Use the unchecked version because the entrypoint may be tampered.
+ return RVAToPtrUnchecked<FARPROC>(
+ mPeHeader->OptionalHeader.AddressOfEntryPoint);
+ }
+
+ private:
+ enum class BoundsCheckPolicy { Default, Skip };
+
+ template <typename T, BoundsCheckPolicy Policy = BoundsCheckPolicy::Default>
+ T GetImageDirectoryEntry(const uint32_t aDirectoryIndex) const {
+ PIMAGE_DATA_DIRECTORY dirEntry = GetImageDirectoryEntryPtr(aDirectoryIndex);
+ if (!dirEntry) {
+ return nullptr;
+ }
+
+ return Policy == BoundsCheckPolicy::Skip
+ ? RVAToPtrUnchecked<T>(dirEntry->VirtualAddress)
+ : RVAToPtr<T>(dirEntry->VirtualAddress);
+ }
+
+ // This private variant does not have bounds checks, because we need to be
+ // able to resolve the bounds themselves.
+ template <typename T, typename R>
+ T RVAToPtrUnchecked(R aRva) const {
+ return reinterpret_cast<T>(reinterpret_cast<char*>(mMzHeader) + aRva);
+ }
+
+ Span<IMAGE_SECTION_HEADER> GetSectionTable() const {
+ MOZ_ASSERT(*this);
+ auto base = RVAToPtr<PIMAGE_SECTION_HEADER>(
+ &mPeHeader->OptionalHeader, mPeHeader->FileHeader.SizeOfOptionalHeader);
+ // The Windows loader has an internal limit of 96 sections (per PE spec)
+ auto numSections =
+ std::min(mPeHeader->FileHeader.NumberOfSections, WORD(96));
+ return Span{base, numSections};
+ }
+
+ PIMAGE_RESOURCE_DIRECTORY_ENTRY
+ FindResourceEntry(PIMAGE_RESOURCE_DIRECTORY aCurLevel, WORD aId) const {
+ // Immediately after the IMAGE_RESOURCE_DIRECTORY structure is an array
+ // of IMAGE_RESOURCE_DIRECTORY_ENTRY structures. Since this function
+ // searches by ID, we need to skip past any named entries before iterating.
+ auto dirEnt =
+ reinterpret_cast<PIMAGE_RESOURCE_DIRECTORY_ENTRY>(aCurLevel + 1) +
+ aCurLevel->NumberOfNamedEntries;
+ for (WORD i = 0; i < aCurLevel->NumberOfIdEntries; ++i) {
+ if (dirEnt[i].Id == aId) {
+ return &dirEnt[i];
+ }
+ }
+
+ return nullptr;
+ }
+
+ PIMAGE_RESOURCE_DIRECTORY_ENTRY
+ FindFirstResourceEntry(PIMAGE_RESOURCE_DIRECTORY aCurLevel) const {
+ // Immediately after the IMAGE_RESOURCE_DIRECTORY structure is an array
+ // of IMAGE_RESOURCE_DIRECTORY_ENTRY structures. We just return the first
+ // entry, regardless of whether it is indexed by name or by id.
+ auto dirEnt =
+ reinterpret_cast<PIMAGE_RESOURCE_DIRECTORY_ENTRY>(aCurLevel + 1);
+ WORD numEntries =
+ aCurLevel->NumberOfNamedEntries + aCurLevel->NumberOfIdEntries;
+ if (!numEntries) {
+ return nullptr;
+ }
+
+ return dirEnt;
+ }
+
+ VS_FIXEDFILEINFO* GetFixedFileInfo(VS_VERSIONINFO_HEADER* aVerInfo) const {
+ WORD length = aVerInfo->wLength;
+ if (length < sizeof(VS_VERSIONINFO_HEADER)) {
+ return nullptr;
+ }
+
+ const wchar_t kVersionInfoKey[] = L"VS_VERSION_INFO";
+ if (::RtlCompareMemory(aVerInfo->szKey, kVersionInfoKey,
+ ArrayLength(kVersionInfoKey)) !=
+ ArrayLength(kVersionInfoKey)) {
+ return nullptr;
+ }
+
+ if (aVerInfo->wValueLength != sizeof(VS_FIXEDFILEINFO)) {
+ // Fixed file info does not exist
+ return nullptr;
+ }
+
+ WORD offset = sizeof(VS_VERSIONINFO_HEADER);
+
+ uintptr_t base = reinterpret_cast<uintptr_t>(aVerInfo);
+ // Align up to 4-byte boundary
+#pragma warning(suppress : 4146)
+ offset += (-(base + offset) & 3);
+
+ if (offset >= length) {
+ return nullptr;
+ }
+
+ auto result = reinterpret_cast<VS_FIXEDFILEINFO*>(base + offset);
+ if (result->dwSignature != 0xFEEF04BD) {
+ return nullptr;
+ }
+
+ return result;
+ }
+
+ private:
+ PIMAGE_DOS_HEADER mMzHeader;
+ PIMAGE_NT_HEADERS mPeHeader;
+ void* mImageLimit;
+ bool mIsImportDirectoryTampered;
+};
+
+// This class represents an export section of a local/remote process.
+template <typename MMPolicy>
+class MOZ_RAII PEExportSection {
+ const MMPolicy& mMMPolicy;
+ uintptr_t mImageBase;
+ DWORD mOrdinalBase;
+ DWORD mRvaDirStart;
+ DWORD mRvaDirEnd;
+ mozilla::interceptor::TargetObjectArray<MMPolicy, DWORD> mExportAddressTable;
+ mozilla::interceptor::TargetObjectArray<MMPolicy, DWORD> mExportNameTable;
+ mozilla::interceptor::TargetObjectArray<MMPolicy, WORD> mExportOrdinalTable;
+
+ explicit PEExportSection(const MMPolicy& aMMPolicy)
+ : mMMPolicy(aMMPolicy),
+ mImageBase(0),
+ mOrdinalBase(0),
+ mRvaDirStart(0),
+ mRvaDirEnd(0),
+ mExportAddressTable(mMMPolicy),
+ mExportNameTable(mMMPolicy),
+ mExportOrdinalTable(mMMPolicy) {}
+
+ PEExportSection(const MMPolicy& aMMPolicy, uintptr_t aImageBase,
+ DWORD aRvaDirStart, DWORD aRvaDirEnd,
+ const IMAGE_EXPORT_DIRECTORY& exportDir)
+ : mMMPolicy(aMMPolicy),
+ mImageBase(aImageBase),
+ mOrdinalBase(exportDir.Base),
+ mRvaDirStart(aRvaDirStart),
+ mRvaDirEnd(aRvaDirEnd),
+ mExportAddressTable(mMMPolicy,
+ mImageBase + exportDir.AddressOfFunctions,
+ exportDir.NumberOfFunctions),
+ mExportNameTable(mMMPolicy, mImageBase + exportDir.AddressOfNames,
+ exportDir.NumberOfNames),
+ mExportOrdinalTable(mMMPolicy,
+ mImageBase + exportDir.AddressOfNameOrdinals,
+ exportDir.NumberOfNames) {}
+
+ static const PEExportSection Get(uintptr_t aImageBase,
+ const MMPolicy& aMMPolicy) {
+ mozilla::interceptor::TargetObject<MMPolicy, IMAGE_DOS_HEADER> mzHeader(
+ aMMPolicy, aImageBase);
+ if (!mzHeader || mzHeader->e_magic != IMAGE_DOS_SIGNATURE) {
+ return PEExportSection(aMMPolicy);
+ }
+
+ mozilla::interceptor::TargetObject<MMPolicy, IMAGE_NT_HEADERS> peHeader(
+ aMMPolicy, aImageBase + mzHeader->e_lfanew);
+ if (!peHeader || peHeader->Signature != IMAGE_NT_SIGNATURE) {
+ return PEExportSection(aMMPolicy);
+ }
+
+ if (peHeader->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC) {
+ return PEExportSection(aMMPolicy);
+ }
+
+ const IMAGE_OPTIONAL_HEADER& optionalHeader = peHeader->OptionalHeader;
+
+ DWORD imageSize = optionalHeader.SizeOfImage;
+ // This is a coarse-grained check to ensure that the image size is
+ // reasonable. It we aren't big enough to contain headers, we have a
+ // problem!
+ if (imageSize < sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS)) {
+ return PEExportSection(aMMPolicy);
+ }
+
+ if (optionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_EXPORT) {
+ return PEExportSection(aMMPolicy);
+ }
+
+ const IMAGE_DATA_DIRECTORY& exportDirectoryEntry =
+ optionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
+ if (!exportDirectoryEntry.VirtualAddress || !exportDirectoryEntry.Size) {
+ return PEExportSection(aMMPolicy);
+ }
+
+ mozilla::interceptor::TargetObject<MMPolicy, IMAGE_EXPORT_DIRECTORY>
+ exportDirectory(aMMPolicy,
+ aImageBase + exportDirectoryEntry.VirtualAddress);
+ if (!exportDirectory || !exportDirectory->NumberOfFunctions) {
+ return PEExportSection(aMMPolicy);
+ }
+
+ return PEExportSection(
+ aMMPolicy, aImageBase, exportDirectoryEntry.VirtualAddress,
+ exportDirectoryEntry.VirtualAddress + exportDirectoryEntry.Size,
+ *exportDirectory.GetLocalBase());
+ }
+
+ FARPROC GetProcAddressByOrdinal(WORD aOrdinal) const {
+ if (aOrdinal < mOrdinalBase) {
+ return nullptr;
+ }
+
+ auto rvaToFunction = mExportAddressTable[aOrdinal - mOrdinalBase];
+ if (!rvaToFunction) {
+ return nullptr;
+ }
+ return reinterpret_cast<FARPROC>(mImageBase + *rvaToFunction);
+ }
+
+ public:
+ static const PEExportSection Get(HMODULE aModule, const MMPolicy& aMMPolicy) {
+ return Get(PEHeaders::HModuleToBaseAddr<uintptr_t>(aModule), aMMPolicy);
+ }
+
+ explicit operator bool() const {
+ // Because PEExportSection doesn't use MMPolicy::Reserve(), a boolified
+ // mMMPolicy is expected to be false. We don't check mMMPolicy here.
+ return mImageBase && mRvaDirStart && mRvaDirEnd && mExportAddressTable &&
+ mExportNameTable && mExportOrdinalTable;
+ }
+
+ template <typename T>
+ T RVAToPtr(uint32_t aRva) const {
+ return reinterpret_cast<T>(mImageBase + aRva);
+ }
+
+ PIMAGE_EXPORT_DIRECTORY GetExportDirectory() const {
+ if (!*this) {
+ return nullptr;
+ }
+
+ return RVAToPtr<PIMAGE_EXPORT_DIRECTORY>(mRvaDirStart);
+ }
+
+ /**
+ * This functions searches the export table for a given string as
+ * GetProcAddress does, but this returns a matched entry of the Export
+ * Address Table i.e. a pointer to an RVA of a matched function instead
+ * of a function address. If the entry is forwarded, this function
+ * returns nullptr.
+ */
+ const DWORD* FindExportAddressTableEntry(
+ const char* aFunctionNameASCII) const {
+ if (!*this || !aFunctionNameASCII) {
+ return nullptr;
+ }
+
+ struct NameTableComparator {
+ NameTableComparator(const PEExportSection<MMPolicy>& aExportSection,
+ const char* aTarget)
+ : mExportSection(aExportSection),
+ mTargetName(aTarget),
+ mTargetNamelength(StrlenASCII(aTarget)) {}
+
+ int operator()(DWORD aRVAToString) const {
+ mozilla::interceptor::TargetObjectArray<MMPolicy, char> itemString(
+ mExportSection.mMMPolicy, mExportSection.mImageBase + aRVAToString,
+ mTargetNamelength + 1);
+ return StrcmpASCII(mTargetName, itemString[0]);
+ }
+
+ const PEExportSection<MMPolicy>& mExportSection;
+ const char* mTargetName;
+ size_t mTargetNamelength;
+ };
+
+ const NameTableComparator comp(*this, aFunctionNameASCII);
+
+ size_t match;
+ if (!mExportNameTable.BinarySearchIf(comp, &match)) {
+ return nullptr;
+ }
+
+ const WORD* index = mExportOrdinalTable[match];
+ if (!index) {
+ return nullptr;
+ }
+
+ const DWORD* rvaToFunction = mExportAddressTable[*index];
+ if (!rvaToFunction) {
+ return nullptr;
+ }
+
+ if (*rvaToFunction >= mRvaDirStart && *rvaToFunction < mRvaDirEnd) {
+ // If an entry points to an address within the export section, the
+ // field is a forwarder RVA. We return nullptr because the entry is
+ // not a function address but a null-terminated string used for export
+ // forwarding.
+ return nullptr;
+ }
+
+ return rvaToFunction;
+ }
+
+ /**
+ * This functions behaves the same as the native ::GetProcAddress except
+ * the following cases:
+ * - Returns nullptr if a target entry is forwarded to another dll.
+ */
+ FARPROC GetProcAddress(const char* aFunctionNameASCII) const {
+ uintptr_t maybeOdrinal = reinterpret_cast<uintptr_t>(aFunctionNameASCII);
+ // When the high-order word of |aFunctionNameASCII| is zero, it's not
+ // a string but an ordinal value.
+ if (maybeOdrinal < 0x10000) {
+ return GetProcAddressByOrdinal(static_cast<WORD>(maybeOdrinal));
+ }
+
+ auto rvaToFunction = FindExportAddressTableEntry(aFunctionNameASCII);
+ if (!rvaToFunction) {
+ return nullptr;
+ }
+ return reinterpret_cast<FARPROC>(mImageBase + *rvaToFunction);
+ }
+};
+
+inline HANDLE RtlGetProcessHeap() {
+ PTEB teb = ::NtCurrentTeb();
+ PPEB peb = teb->ProcessEnvironmentBlock;
+ return peb->Reserved4[1];
+}
+
+inline PVOID RtlGetThreadLocalStoragePointer() {
+ return ::NtCurrentTeb()->Reserved1[11];
+}
+
+inline void RtlSetThreadLocalStoragePointerForTestingOnly(PVOID aNewValue) {
+ ::NtCurrentTeb()->Reserved1[11] = aNewValue;
+}
+
+inline DWORD RtlGetCurrentThreadId() {
+ PTEB teb = ::NtCurrentTeb();
+ CLIENT_ID* cid = reinterpret_cast<CLIENT_ID*>(&teb->Reserved1[8]);
+ return static_cast<DWORD>(reinterpret_cast<uintptr_t>(cid->UniqueThread) &
+ 0xFFFFFFFFUL);
+}
+
+const HANDLE kCurrentProcess = reinterpret_cast<HANDLE>(-1);
+
+inline LauncherResult<DWORD> GetParentProcessId() {
+ struct PROCESS_BASIC_INFORMATION {
+ NTSTATUS ExitStatus;
+ PPEB PebBaseAddress;
+ ULONG_PTR AffinityMask;
+ LONG BasePriority;
+ ULONG_PTR UniqueProcessId;
+ ULONG_PTR InheritedFromUniqueProcessId;
+ };
+
+ ULONG returnLength;
+ PROCESS_BASIC_INFORMATION pbi = {};
+ NTSTATUS status =
+ ::NtQueryInformationProcess(kCurrentProcess, ProcessBasicInformation,
+ &pbi, sizeof(pbi), &returnLength);
+ if (!NT_SUCCESS(status)) {
+ return LAUNCHER_ERROR_FROM_NTSTATUS(status);
+ }
+
+ return static_cast<DWORD>(pbi.InheritedFromUniqueProcessId & 0xFFFFFFFF);
+}
+
+inline SIZE_T WINAPI VirtualQueryEx(HANDLE aProcess, LPCVOID aAddress,
+ PMEMORY_BASIC_INFORMATION aMemInfo,
+ SIZE_T aMemInfoLen) {
+#if defined(MOZILLA_INTERNAL_API)
+ return ::VirtualQueryEx(aProcess, aAddress, aMemInfo, aMemInfoLen);
+#else
+ SIZE_T returnedLength;
+ NTSTATUS status = ::NtQueryVirtualMemory(
+ aProcess, const_cast<PVOID>(aAddress), MemoryBasicInformation, aMemInfo,
+ aMemInfoLen, &returnedLength);
+ if (!NT_SUCCESS(status)) {
+ ::RtlSetLastWin32Error(::RtlNtStatusToDosError(status));
+ returnedLength = 0;
+ }
+ return returnedLength;
+#endif // defined(MOZILLA_INTERNAL_API)
+}
+
+inline SIZE_T WINAPI VirtualQuery(LPCVOID aAddress,
+ PMEMORY_BASIC_INFORMATION aMemInfo,
+ SIZE_T aMemInfoLen) {
+ return nt::VirtualQueryEx(kCurrentProcess, aAddress, aMemInfo, aMemInfoLen);
+}
+
+struct DataDirectoryEntry : public _IMAGE_DATA_DIRECTORY {
+ DataDirectoryEntry() : _IMAGE_DATA_DIRECTORY() {}
+
+ MOZ_IMPLICIT DataDirectoryEntry(const _IMAGE_DATA_DIRECTORY& aOther)
+ : _IMAGE_DATA_DIRECTORY(aOther) {}
+
+ DataDirectoryEntry(const DataDirectoryEntry& aOther) = default;
+
+ bool operator==(const DataDirectoryEntry& aOther) const {
+ return VirtualAddress == aOther.VirtualAddress && Size == aOther.Size;
+ }
+
+ bool operator!=(const DataDirectoryEntry& aOther) const {
+ return !(*this == aOther);
+ }
+};
+
+inline LauncherResult<void*> GetProcessPebPtr(HANDLE aProcess) {
+ ULONG returnLength;
+ PROCESS_BASIC_INFORMATION pbi;
+ NTSTATUS status = ::NtQueryInformationProcess(
+ aProcess, ProcessBasicInformation, &pbi, sizeof(pbi), &returnLength);
+ if (!NT_SUCCESS(status)) {
+ return LAUNCHER_ERROR_FROM_NTSTATUS(status);
+ }
+
+ return pbi.PebBaseAddress;
+}
+
+/**
+ * This function relies on a specific offset into the mostly-undocumented PEB
+ * structure. The risk is reduced thanks to the fact that the Chromium sandbox
+ * relies on the location of this field. It is unlikely to change at this point.
+ * To further reduce the risk, we also check for the magic 'MZ' signature that
+ * should indicate the beginning of a PE image.
+ */
+inline LauncherResult<HMODULE> GetProcessExeModule(HANDLE aProcess) {
+ LauncherResult<void*> ppeb = GetProcessPebPtr(aProcess);
+ if (ppeb.isErr()) {
+ return ppeb.propagateErr();
+ }
+
+ PEB peb;
+ SIZE_T bytesRead;
+
+#if defined(MOZILLA_INTERNAL_API)
+ if (!::ReadProcessMemory(aProcess, ppeb.unwrap(), &peb, sizeof(peb),
+ &bytesRead) ||
+ bytesRead != sizeof(peb)) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+#else
+ NTSTATUS ntStatus = ::NtReadVirtualMemory(aProcess, ppeb.unwrap(), &peb,
+ sizeof(peb), &bytesRead);
+ if (!NT_SUCCESS(ntStatus) || bytesRead != sizeof(peb)) {
+ return LAUNCHER_ERROR_FROM_NTSTATUS(ntStatus);
+ }
+#endif
+
+ // peb.ImageBaseAddress
+ void* baseAddress = peb.Reserved3[1];
+
+ char mzMagic[2];
+#if defined(MOZILLA_INTERNAL_API)
+ if (!::ReadProcessMemory(aProcess, baseAddress, mzMagic, sizeof(mzMagic),
+ &bytesRead) ||
+ bytesRead != sizeof(mzMagic)) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+#else
+ ntStatus = ::NtReadVirtualMemory(aProcess, baseAddress, mzMagic,
+ sizeof(mzMagic), &bytesRead);
+ if (!NT_SUCCESS(ntStatus) || bytesRead != sizeof(mzMagic)) {
+ return LAUNCHER_ERROR_FROM_NTSTATUS(ntStatus);
+ }
+#endif
+
+ MOZ_ASSERT(mzMagic[0] == 'M' && mzMagic[1] == 'Z');
+ if (mzMagic[0] != 'M' || mzMagic[1] != 'Z') {
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
+ }
+
+ return static_cast<HMODULE>(baseAddress);
+}
+
+#if defined(_MSC_VER)
+extern "C" IMAGE_DOS_HEADER __ImageBase;
+#endif
+
+// This class manages data transfer from the local process's executable
+// to another process's executable via WriteProcessMemory.
+// Bug 1662560 told us the same executable may be mapped onto a different
+// address in a different process. This means when we transfer data within
+// the mapped executable such as a global variable or IAT from the current
+// process to another process, we need to shift its address by the difference
+// between two executable's mapped imagebase.
+class CrossExecTransferManager final {
+ HANDLE mRemoteProcess;
+ uint8_t* mLocalImagebase;
+ PEHeaders mLocalExec;
+ uint8_t* mRemoteImagebase;
+
+ static HMODULE GetLocalExecModule() {
+#if defined(_MSC_VER)
+ return reinterpret_cast<HMODULE>(&__ImageBase);
+#else
+ return ::GetModuleHandleW(nullptr);
+#endif
+ }
+
+ LauncherVoidResult EnsureRemoteImagebase() {
+ if (!mRemoteImagebase) {
+ LauncherResult<HMODULE> remoteImageBaseResult =
+ GetProcessExeModule(mRemoteProcess);
+ if (remoteImageBaseResult.isErr()) {
+ return remoteImageBaseResult.propagateErr();
+ }
+
+ mRemoteImagebase =
+ reinterpret_cast<uint8_t*>(remoteImageBaseResult.unwrap());
+ }
+ return Ok();
+ }
+
+ template <typename T>
+ T* LocalExecToRemoteExec(T* aLocalAddress) const {
+ MOZ_ASSERT(mRemoteImagebase);
+ MOZ_ASSERT(mLocalExec.IsWithinImage(aLocalAddress));
+
+ if (!mRemoteImagebase || !mLocalExec.IsWithinImage(aLocalAddress)) {
+ return aLocalAddress;
+ }
+
+ uintptr_t offset = reinterpret_cast<uintptr_t>(aLocalAddress) -
+ reinterpret_cast<uintptr_t>(mLocalImagebase);
+ return reinterpret_cast<T*>(mRemoteImagebase + offset);
+ }
+
+ public:
+ explicit CrossExecTransferManager(HANDLE aRemoteProcess)
+ : mRemoteProcess(aRemoteProcess),
+ mLocalImagebase(
+ PEHeaders::HModuleToBaseAddr<uint8_t*>(GetLocalExecModule())),
+ mLocalExec(mLocalImagebase),
+ mRemoteImagebase(nullptr) {}
+
+ CrossExecTransferManager(HANDLE aRemoteProcess, HMODULE aLocalImagebase)
+ : mRemoteProcess(aRemoteProcess),
+ mLocalImagebase(
+ PEHeaders::HModuleToBaseAddr<uint8_t*>(aLocalImagebase)),
+ mLocalExec(mLocalImagebase),
+ mRemoteImagebase(nullptr) {}
+
+ explicit operator bool() const { return !!mLocalExec; }
+ HANDLE RemoteProcess() const { return mRemoteProcess; }
+ const PEHeaders& LocalPEHeaders() const { return mLocalExec; }
+
+ AutoVirtualProtect Protect(void* aLocalAddress, size_t aLength,
+ DWORD aProtFlags) {
+ // If EnsureRemoteImagebase() fails, a subsequent operaion will fail.
+ Unused << EnsureRemoteImagebase();
+ return AutoVirtualProtect(LocalExecToRemoteExec(aLocalAddress), aLength,
+ aProtFlags, mRemoteProcess);
+ }
+
+ LauncherVoidResult Transfer(LPVOID aDestinationAddress,
+ LPCVOID aBufferToWrite, SIZE_T aBufferSize) {
+ LauncherVoidResult result = EnsureRemoteImagebase();
+ if (result.isErr()) {
+ return result.propagateErr();
+ }
+
+ if (!::WriteProcessMemory(mRemoteProcess,
+ LocalExecToRemoteExec(aDestinationAddress),
+ aBufferToWrite, aBufferSize, nullptr)) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+
+ return Ok();
+ }
+};
+
+#if !defined(MOZILLA_INTERNAL_API)
+
+inline LauncherResult<HMODULE> GetModuleHandleFromLeafName(
+ const UNICODE_STRING& aTarget) {
+ auto maybePeb = nt::GetProcessPebPtr(kCurrentProcess);
+ if (maybePeb.isErr()) {
+ return maybePeb.propagateErr();
+ }
+
+ const PPEB peb = reinterpret_cast<PPEB>(maybePeb.unwrap());
+ if (!peb->Ldr) {
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
+ }
+
+ auto firstItem = &peb->Ldr->InMemoryOrderModuleList;
+ for (auto p = firstItem->Flink; p != firstItem; p = p->Flink) {
+ const auto currentTableEntry =
+ CONTAINING_RECORD(p, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
+
+ UNICODE_STRING leafName;
+ nt::GetLeafName(&leafName, &currentTableEntry->FullDllName);
+
+ if (::RtlCompareUnicodeString(&leafName, &aTarget, TRUE) == 0) {
+ return reinterpret_cast<HMODULE>(currentTableEntry->DllBase);
+ }
+ }
+
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_MOD_NOT_FOUND);
+}
+
+class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS SRWLock final {
+ public:
+ constexpr SRWLock() : mLock(SRWLOCK_INIT) {}
+
+ void LockShared() { ::RtlAcquireSRWLockShared(&mLock); }
+
+ void LockExclusive() { ::RtlAcquireSRWLockExclusive(&mLock); }
+
+ void UnlockShared() { ::RtlReleaseSRWLockShared(&mLock); }
+
+ void UnlockExclusive() { ::RtlReleaseSRWLockExclusive(&mLock); }
+
+ SRWLock(const SRWLock&) = delete;
+ SRWLock(SRWLock&&) = delete;
+ SRWLock& operator=(const SRWLock&) = delete;
+ SRWLock& operator=(SRWLock&&) = delete;
+
+ SRWLOCK* operator&() { return &mLock; }
+
+ private:
+ SRWLOCK mLock;
+};
+
+class MOZ_RAII AutoExclusiveLock final {
+ public:
+ explicit AutoExclusiveLock(SRWLock& aLock) : mLock(aLock) {
+ aLock.LockExclusive();
+ }
+
+ ~AutoExclusiveLock() { mLock.UnlockExclusive(); }
+
+ AutoExclusiveLock(const AutoExclusiveLock&) = delete;
+ AutoExclusiveLock(AutoExclusiveLock&&) = delete;
+ AutoExclusiveLock& operator=(const AutoExclusiveLock&) = delete;
+ AutoExclusiveLock& operator=(AutoExclusiveLock&&) = delete;
+
+ private:
+ SRWLock& mLock;
+};
+
+class MOZ_RAII AutoSharedLock final {
+ public:
+ explicit AutoSharedLock(SRWLock& aLock) : mLock(aLock) { aLock.LockShared(); }
+
+ ~AutoSharedLock() { mLock.UnlockShared(); }
+
+ AutoSharedLock(const AutoSharedLock&) = delete;
+ AutoSharedLock(AutoSharedLock&&) = delete;
+ AutoSharedLock& operator=(const AutoSharedLock&) = delete;
+ AutoSharedLock& operator=(AutoSharedLock&&) = delete;
+
+ private:
+ SRWLock& mLock;
+};
+
+#endif // !defined(MOZILLA_INTERNAL_API)
+
+class RtlAllocPolicy {
+ public:
+ template <typename T>
+ T* maybe_pod_malloc(size_t aNumElems) {
+ if (aNumElems & mozilla::tl::MulOverflowMask<sizeof(T)>::value) {
+ return nullptr;
+ }
+
+ return static_cast<T*>(
+ ::RtlAllocateHeap(RtlGetProcessHeap(), 0, aNumElems * sizeof(T)));
+ }
+
+ template <typename T>
+ T* maybe_pod_calloc(size_t aNumElems) {
+ if (aNumElems & mozilla::tl::MulOverflowMask<sizeof(T)>::value) {
+ return nullptr;
+ }
+
+ return static_cast<T*>(::RtlAllocateHeap(
+ RtlGetProcessHeap(), HEAP_ZERO_MEMORY, aNumElems * sizeof(T)));
+ }
+
+ template <typename T>
+ T* maybe_pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize) {
+ if (aNewSize & mozilla::tl::MulOverflowMask<sizeof(T)>::value) {
+ return nullptr;
+ }
+
+ return static_cast<T*>(::RtlReAllocateHeap(RtlGetProcessHeap(), 0, aPtr,
+ aNewSize * sizeof(T)));
+ }
+
+ template <typename T>
+ T* pod_malloc(size_t aNumElems) {
+ return maybe_pod_malloc<T>(aNumElems);
+ }
+
+ template <typename T>
+ T* pod_calloc(size_t aNumElems) {
+ return maybe_pod_calloc<T>(aNumElems);
+ }
+
+ template <typename T>
+ T* pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize) {
+ return maybe_pod_realloc<T>(aPtr, aOldSize, aNewSize);
+ }
+
+ template <typename T>
+ void free_(T* aPtr, size_t aNumElems = 0) {
+ ::RtlFreeHeap(RtlGetProcessHeap(), 0, aPtr);
+ }
+
+ void reportAllocOverflow() const {}
+
+ [[nodiscard]] bool checkSimulatedOOM() const { return true; }
+};
+
+class AutoMappedView final {
+ void* mView;
+
+ void Unmap() {
+ if (!mView) {
+ return;
+ }
+
+#if defined(MOZILLA_INTERNAL_API)
+ ::UnmapViewOfFile(mView);
+#else
+ NTSTATUS status = ::NtUnmapViewOfSection(nt::kCurrentProcess, mView);
+ if (!NT_SUCCESS(status)) {
+ ::RtlSetLastWin32Error(::RtlNtStatusToDosError(status));
+ }
+#endif
+ mView = nullptr;
+ }
+
+ public:
+ explicit AutoMappedView(void* aView) : mView(aView) {}
+
+ AutoMappedView(HANDLE aSection, ULONG aProtectionFlags) : mView(nullptr) {
+#if defined(MOZILLA_INTERNAL_API)
+ mView = ::MapViewOfFile(aSection, aProtectionFlags, 0, 0, 0);
+#else
+ SIZE_T viewSize = 0;
+ NTSTATUS status = ::NtMapViewOfSection(aSection, nt::kCurrentProcess,
+ &mView, 0, 0, nullptr, &viewSize,
+ ViewUnmap, 0, aProtectionFlags);
+ if (!NT_SUCCESS(status)) {
+ ::RtlSetLastWin32Error(::RtlNtStatusToDosError(status));
+ }
+#endif
+ }
+ ~AutoMappedView() { Unmap(); }
+
+ // Allow move & Disallow copy
+ AutoMappedView(AutoMappedView&& aOther) : mView(aOther.mView) {
+ aOther.mView = nullptr;
+ }
+ AutoMappedView& operator=(AutoMappedView&& aOther) {
+ if (this != &aOther) {
+ Unmap();
+ mView = aOther.mView;
+ aOther.mView = nullptr;
+ }
+ return *this;
+ }
+ AutoMappedView(const AutoMappedView&) = delete;
+ AutoMappedView& operator=(const AutoMappedView&) = delete;
+
+ explicit operator bool() const { return !!mView; }
+ template <typename T>
+ T* as() {
+ return reinterpret_cast<T*>(mView);
+ }
+
+ void* release() {
+ void* p = mView;
+ mView = nullptr;
+ return p;
+ }
+};
+
+} // namespace nt
+} // namespace mozilla
+
+#endif // mozilla_NativeNt_h
diff --git a/mozglue/misc/PlatformConditionVariable.h b/mozglue/misc/PlatformConditionVariable.h
new file mode 100644
index 0000000000..2b006918cb
--- /dev/null
+++ b/mozglue/misc/PlatformConditionVariable.h
@@ -0,0 +1,71 @@
+/* -*- 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 mozilla_ConditionVariable_h
+#define mozilla_ConditionVariable_h
+
+#include <stdint.h>
+
+#include <utility>
+
+#include "mozilla/Attributes.h"
+#include "mozilla/PlatformMutex.h"
+#include "mozilla/TimeStamp.h"
+#ifndef XP_WIN
+# include <pthread.h>
+#endif
+
+namespace mozilla {
+
+enum class CVStatus { NoTimeout, Timeout };
+
+namespace detail {
+
+class ConditionVariableImpl {
+ public:
+ struct PlatformData;
+
+ MFBT_API ConditionVariableImpl();
+ MFBT_API ~ConditionVariableImpl();
+
+ // Wake one thread that is waiting on this condition.
+ MFBT_API void notify_one();
+
+ // Wake all threads that are waiting on this condition.
+ MFBT_API void notify_all();
+
+ // Atomically release |lock| and sleep the current thread of execution on
+ // this condition variable.
+ // |lock| will be re-acquired before this function returns.
+ // The thread may be woken from sleep from another thread via notify_one()
+ // or notify_all(), but may also wake spuriously. The caller should recheck
+ // its predicate after this function returns, typically in a while loop.
+ MFBT_API void wait(MutexImpl& lock);
+
+ MFBT_API CVStatus wait_for(MutexImpl& lock,
+ const mozilla::TimeDuration& rel_time);
+
+ private:
+ ConditionVariableImpl(const ConditionVariableImpl&) = delete;
+ ConditionVariableImpl& operator=(const ConditionVariableImpl&) = delete;
+
+ PlatformData* platformData();
+
+#ifndef XP_WIN
+ void* platformData_[sizeof(pthread_cond_t) / sizeof(void*)];
+ static_assert(sizeof(pthread_cond_t) / sizeof(void*) != 0 &&
+ sizeof(pthread_cond_t) % sizeof(void*) == 0,
+ "pthread_cond_t must have pointer alignment");
+#else
+ void* platformData_[4];
+#endif
+};
+
+} // namespace detail
+
+} // namespace mozilla
+
+#endif // mozilla_ConditionVariable_h
diff --git a/mozglue/misc/PlatformMutex.h b/mozglue/misc/PlatformMutex.h
new file mode 100644
index 0000000000..704bf60e41
--- /dev/null
+++ b/mozglue/misc/PlatformMutex.h
@@ -0,0 +1,66 @@
+/* -*- 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 mozilla_PlatformMutex_h
+#define mozilla_PlatformMutex_h
+
+#include <utility>
+
+#include "mozilla/Attributes.h"
+
+#if !defined(XP_WIN)
+# include <pthread.h>
+#endif
+
+namespace mozilla {
+
+namespace detail {
+
+class ConditionVariableImpl;
+
+class MutexImpl {
+ public:
+ struct PlatformData;
+
+ explicit MFBT_API MutexImpl();
+ MFBT_API ~MutexImpl();
+
+ protected:
+ MFBT_API void lock();
+ MFBT_API void unlock();
+ // We have a separate, forwarding API so internal uses don't have to go
+ // through the PLT.
+ MFBT_API bool tryLock();
+
+ private:
+ MutexImpl(const MutexImpl&) = delete;
+ void operator=(const MutexImpl&) = delete;
+ MutexImpl(MutexImpl&&) = delete;
+ void operator=(MutexImpl&&) = delete;
+ bool operator==(const MutexImpl& rhs) = delete;
+
+ void mutexLock();
+ bool mutexTryLock();
+
+ PlatformData* platformData();
+
+#if !defined(XP_WIN)
+ void* platformData_[sizeof(pthread_mutex_t) / sizeof(void*)];
+ static_assert(sizeof(pthread_mutex_t) / sizeof(void*) != 0 &&
+ sizeof(pthread_mutex_t) % sizeof(void*) == 0,
+ "pthread_mutex_t must have pointer alignment");
+#else
+ void* platformData_[6];
+#endif
+
+ friend class mozilla::detail::ConditionVariableImpl;
+};
+
+} // namespace detail
+
+} // namespace mozilla
+
+#endif // mozilla_PlatformMutex_h
diff --git a/mozglue/misc/PreXULSkeletonUI.cpp b/mozglue/misc/PreXULSkeletonUI.cpp
new file mode 100644
index 0000000000..22abee4456
--- /dev/null
+++ b/mozglue/misc/PreXULSkeletonUI.cpp
@@ -0,0 +1,2222 @@
+/* -*- 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/. */
+
+#include "PreXULSkeletonUI.h"
+
+#include <algorithm>
+#include <math.h>
+#include <limits.h>
+#include <cmath>
+#include <locale>
+#include <string>
+#include <objbase.h>
+#include <shlobj.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/BaseProfilerMarkers.h"
+#include "mozilla/FStream.h"
+#include "mozilla/HelperMacros.h"
+#include "mozilla/glue/Debug.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/Unused.h"
+#include "mozilla/WindowsDpiAwareness.h"
+#include "mozilla/WindowsVersion.h"
+
+namespace mozilla {
+
+// ColorRect defines an optionally-rounded, optionally-bordered rectangle of a
+// particular color that we will draw.
+struct ColorRect {
+ uint32_t color;
+ uint32_t borderColor;
+ int x;
+ int y;
+ int width;
+ int height;
+ int borderWidth;
+ int borderRadius;
+ bool flipIfRTL;
+};
+
+// DrawRect is mostly the same as ColorRect, but exists as an implementation
+// detail to simplify drawing borders. We draw borders as a strokeOnly rect
+// underneath an inner rect of a particular color. We also need to keep
+// track of the backgroundColor for rounding rects, in order to correctly
+// anti-alias.
+struct DrawRect {
+ uint32_t color;
+ uint32_t backgroundColor;
+ int x;
+ int y;
+ int width;
+ int height;
+ int borderRadius;
+ int borderWidth;
+ bool strokeOnly;
+};
+
+struct NormalizedRGB {
+ double r;
+ double g;
+ double b;
+};
+
+NormalizedRGB UintToRGB(uint32_t color) {
+ double r = static_cast<double>(color >> 16 & 0xff) / 255.0;
+ double g = static_cast<double>(color >> 8 & 0xff) / 255.0;
+ double b = static_cast<double>(color >> 0 & 0xff) / 255.0;
+ return NormalizedRGB{r, g, b};
+}
+
+uint32_t RGBToUint(const NormalizedRGB& rgb) {
+ return (static_cast<uint32_t>(rgb.r * 255.0) << 16) |
+ (static_cast<uint32_t>(rgb.g * 255.0) << 8) |
+ (static_cast<uint32_t>(rgb.b * 255.0) << 0);
+}
+
+double Lerp(double a, double b, double x) { return a + x * (b - a); }
+
+NormalizedRGB Lerp(const NormalizedRGB& a, const NormalizedRGB& b, double x) {
+ return NormalizedRGB{Lerp(a.r, b.r, x), Lerp(a.g, b.g, x), Lerp(a.b, b.b, x)};
+}
+
+// Produces a smooth curve in [0,1] based on a linear input in [0,1]
+double SmoothStep3(double x) { return x * x * (3.0 - 2.0 * x); }
+
+static const wchar_t kPreXULSkeletonUIKeyPath[] =
+ L"SOFTWARE"
+ L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\PreXULSkeletonUISettings";
+
+static bool sPreXULSkeletonUIEnabled = false;
+// sPreXULSkeletonUIDisallowed means that we don't even have the capacity to
+// enable the skeleton UI, whether because we're on a platform that doesn't
+// support it or because we launched with command line arguments that we don't
+// support. Some of these situations are transient, so we want to make sure we
+// don't mess with registry values in these scenarios that we may use in
+// other scenarios in which the skeleton UI is actually enabled.
+static bool sPreXULSkeletonUIDisallowed = false;
+static HWND sPreXULSkeletonUIWindow;
+static LPWSTR const gStockApplicationIcon = MAKEINTRESOURCEW(32512);
+static LPWSTR const gIDCWait = MAKEINTRESOURCEW(32514);
+static HANDLE sPreXULSKeletonUIAnimationThread;
+
+static uint32_t* sPixelBuffer = nullptr;
+static Vector<ColorRect>* sAnimatedRects = nullptr;
+static int sTotalChromeHeight = 0;
+static volatile LONG sAnimationControlFlag = 0;
+static bool sMaximized = false;
+static int sNonClientVerticalMargins = 0;
+static int sNonClientHorizontalMargins = 0;
+static uint32_t sDpi = 0;
+
+// Color values needed by the animation loop
+static uint32_t sAnimationColor;
+static uint32_t sToolbarForegroundColor;
+
+static ThemeMode sTheme = ThemeMode::Invalid;
+
+typedef BOOL(WINAPI* EnableNonClientDpiScalingProc)(HWND);
+static EnableNonClientDpiScalingProc sEnableNonClientDpiScaling = NULL;
+typedef int(WINAPI* GetSystemMetricsForDpiProc)(int, UINT);
+GetSystemMetricsForDpiProc sGetSystemMetricsForDpi = NULL;
+typedef UINT(WINAPI* GetDpiForWindowProc)(HWND);
+GetDpiForWindowProc sGetDpiForWindow = NULL;
+typedef ATOM(WINAPI* RegisterClassWProc)(const WNDCLASSW*);
+RegisterClassWProc sRegisterClassW = NULL;
+typedef HICON(WINAPI* LoadIconWProc)(HINSTANCE, LPCWSTR);
+LoadIconWProc sLoadIconW = NULL;
+typedef HICON(WINAPI* LoadCursorWProc)(HINSTANCE, LPCWSTR);
+LoadCursorWProc sLoadCursorW = NULL;
+typedef HWND(WINAPI* CreateWindowExWProc)(DWORD, LPCWSTR, LPCWSTR, DWORD, int,
+ int, int, int, HWND, HMENU, HINSTANCE,
+ LPVOID);
+CreateWindowExWProc sCreateWindowExW = NULL;
+typedef BOOL(WINAPI* ShowWindowProc)(HWND, int);
+ShowWindowProc sShowWindow = NULL;
+typedef BOOL(WINAPI* SetWindowPosProc)(HWND, HWND, int, int, int, int, UINT);
+SetWindowPosProc sSetWindowPos = NULL;
+typedef HDC(WINAPI* GetWindowDCProc)(HWND);
+GetWindowDCProc sGetWindowDC = NULL;
+typedef int(WINAPI* FillRectProc)(HDC, const RECT*, HBRUSH);
+FillRectProc sFillRect = NULL;
+typedef BOOL(WINAPI* DeleteObjectProc)(HGDIOBJ);
+DeleteObjectProc sDeleteObject = NULL;
+typedef int(WINAPI* ReleaseDCProc)(HWND, HDC);
+ReleaseDCProc sReleaseDC = NULL;
+typedef HMONITOR(WINAPI* MonitorFromWindowProc)(HWND, DWORD);
+MonitorFromWindowProc sMonitorFromWindow = NULL;
+typedef BOOL(WINAPI* GetMonitorInfoWProc)(HMONITOR, LPMONITORINFO);
+GetMonitorInfoWProc sGetMonitorInfoW = NULL;
+typedef LONG_PTR(WINAPI* SetWindowLongPtrWProc)(HWND, int, LONG_PTR);
+SetWindowLongPtrWProc sSetWindowLongPtrW = NULL;
+typedef int(WINAPI* StretchDIBitsProc)(HDC, int, int, int, int, int, int, int,
+ int, const VOID*, const BITMAPINFO*,
+ UINT, DWORD);
+StretchDIBitsProc sStretchDIBits = NULL;
+typedef HBRUSH(WINAPI* CreateSolidBrushProc)(COLORREF);
+CreateSolidBrushProc sCreateSolidBrush = NULL;
+
+static int sWindowWidth;
+static int sWindowHeight;
+static double sCSSToDevPixelScaling;
+
+static const int kAnimationCSSPixelsPerFrame = 21;
+static const int kAnimationCSSExtraWindowSize = 300;
+
+// NOTE: these values were pulled out of thin air as round numbers that are
+// likely to be too big to be seen in practice. If we legitimately see windows
+// this big, we probably don't want to be drawing them on the CPU anyway.
+static const uint32_t kMaxWindowWidth = 1 << 16;
+static const uint32_t kMaxWindowHeight = 1 << 16;
+
+static const wchar_t* sEnabledRegSuffix = L"|Enabled";
+static const wchar_t* sScreenXRegSuffix = L"|ScreenX";
+static const wchar_t* sScreenYRegSuffix = L"|ScreenY";
+static const wchar_t* sWidthRegSuffix = L"|Width";
+static const wchar_t* sHeightRegSuffix = L"|Height";
+static const wchar_t* sMaximizedRegSuffix = L"|Maximized";
+static const wchar_t* sUrlbarCSSRegSuffix = L"|UrlbarCSSSpan";
+static const wchar_t* sCssToDevPixelScalingRegSuffix = L"|CssToDevPixelScaling";
+static const wchar_t* sSearchbarRegSuffix = L"|SearchbarCSSSpan";
+static const wchar_t* sSpringsCSSRegSuffix = L"|SpringsCSSSpan";
+static const wchar_t* sThemeRegSuffix = L"|Theme";
+static const wchar_t* sFlagsRegSuffix = L"|Flags";
+
+struct LoadedCoTaskMemFreeDeleter {
+ void operator()(void* ptr) {
+ static decltype(CoTaskMemFree)* coTaskMemFree = nullptr;
+ if (!coTaskMemFree) {
+ // Just let this get cleaned up when the process is terminated, because
+ // we're going to load it anyway elsewhere.
+ HMODULE ole32Dll = ::LoadLibraryW(L"ole32");
+ if (!ole32Dll) {
+ printf_stderr(
+ "Could not load ole32 - will not free with CoTaskMemFree");
+ return;
+ }
+ coTaskMemFree = reinterpret_cast<decltype(coTaskMemFree)>(
+ ::GetProcAddress(ole32Dll, "CoTaskMemFree"));
+ if (!coTaskMemFree) {
+ printf_stderr("Could not find CoTaskMemFree");
+ return;
+ }
+ }
+ coTaskMemFree(ptr);
+ }
+};
+
+std::wstring GetRegValueName(const wchar_t* prefix, const wchar_t* suffix) {
+ std::wstring result(prefix);
+ result.append(suffix);
+ return result;
+}
+
+// This is paraphrased from WinHeaderOnlyUtils.h. The fact that this file is
+// included in standalone SpiderMonkey builds prohibits us from including that
+// file directly, and it hardly warrants its own header. Bug 1674920 tracks
+// only including this file for gecko-related builds.
+UniquePtr<wchar_t[]> GetBinaryPath() {
+ DWORD bufLen = MAX_PATH;
+ UniquePtr<wchar_t[]> buf;
+ while (true) {
+ buf = MakeUnique<wchar_t[]>(bufLen);
+ DWORD retLen = ::GetModuleFileNameW(nullptr, buf.get(), bufLen);
+ if (!retLen) {
+ return nullptr;
+ }
+
+ if (retLen == bufLen && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+ bufLen *= 2;
+ continue;
+ }
+
+ break;
+ }
+
+ return buf;
+}
+
+static UniquePtr<wchar_t, LoadedCoTaskMemFreeDeleter> GetKnownFolderPath(
+ REFKNOWNFOLDERID folderId) {
+ static decltype(SHGetKnownFolderPath)* shGetKnownFolderPath = nullptr;
+ if (!shGetKnownFolderPath) {
+ // We could go out of our way to `FreeLibrary` on this, decrementing its
+ // ref count and potentially unloading it. However doing so would be either
+ // effectively a no-op, or counterproductive. Just let it get cleaned up
+ // when the process is terminated, because we're going to load it anyway
+ // elsewhere.
+ HMODULE shell32Dll = ::LoadLibraryW(L"shell32");
+ if (!shell32Dll) {
+ return nullptr;
+ }
+ shGetKnownFolderPath = reinterpret_cast<decltype(shGetKnownFolderPath)>(
+ ::GetProcAddress(shell32Dll, "SHGetKnownFolderPath"));
+ if (!shGetKnownFolderPath) {
+ return nullptr;
+ }
+ }
+ PWSTR path = nullptr;
+ shGetKnownFolderPath(folderId, 0, nullptr, &path);
+ return UniquePtr<wchar_t, LoadedCoTaskMemFreeDeleter>(path);
+}
+
+// Note: this is specifically *not* a robust, multi-locale lowercasing
+// operation. It is not intended to be such. It is simply intended to match the
+// way in which we look for other instances of firefox to remote into.
+// See
+// https://searchfox.org/mozilla-central/rev/71621bfa47a371f2b1ccfd33c704913124afb933/toolkit/components/remote/nsRemoteService.cpp#56
+static void MutateStringToLowercase(wchar_t* ptr) {
+ while (*ptr) {
+ wchar_t ch = *ptr;
+ if (ch >= L'A' && ch <= L'Z') {
+ *ptr = ch + (L'a' - L'A');
+ }
+ ++ptr;
+ }
+}
+
+static bool TryGetSkeletonUILock() {
+ auto localAppDataPath = GetKnownFolderPath(FOLDERID_LocalAppData);
+ if (!localAppDataPath) {
+ return false;
+ }
+
+ // Note: because we're in mozglue, we cannot easily access things from
+ // toolkit, like `GetInstallHash`. We could move `GetInstallHash` into
+ // mozglue, and rip out all of its usage of types defined in toolkit headers.
+ // However, it seems cleaner to just hash the bin path ourselves. We don't
+ // get quite the same robustness that `GetInstallHash` might provide, but
+ // we already don't have that with how we key our registry values, so it
+ // probably makes sense to just match those.
+ UniquePtr<wchar_t[]> binPath = GetBinaryPath();
+ if (!binPath) {
+ return false;
+ }
+
+ // Lowercase the binpath to match how we look for remote instances.
+ MutateStringToLowercase(binPath.get());
+
+ // The number of bytes * 2 characters per byte + 1 for the null terminator
+ uint32_t hexHashSize = sizeof(uint32_t) * 2 + 1;
+ UniquePtr<wchar_t[]> installHash = MakeUnique<wchar_t[]>(hexHashSize);
+ // This isn't perfect - it's a 32-bit hash of the path to our executable. It
+ // could reasonably collide, or casing could potentially affect things, but
+ // the theory is that that should be uncommon enough and the failure case
+ // mild enough that this is fine.
+ uint32_t binPathHash = HashString(binPath.get());
+ swprintf(installHash.get(), hexHashSize, L"%08x", binPathHash);
+
+ std::wstring lockFilePath;
+ lockFilePath.append(localAppDataPath.get());
+ lockFilePath.append(
+ L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\SkeletonUILock-");
+ lockFilePath.append(installHash.get());
+
+ // We intentionally leak this file - that is okay, and (kind of) the point.
+ // We want to hold onto this handle until the application exits, and hold
+ // onto it with exclusive rights. If this check fails, then we assume that
+ // another instance of the executable is holding it, and thus return false.
+ HANDLE lockFile =
+ ::CreateFileW(lockFilePath.c_str(), GENERIC_READ | GENERIC_WRITE,
+ 0, // No sharing - this is how the lock works
+ nullptr, CREATE_ALWAYS,
+ FILE_FLAG_DELETE_ON_CLOSE, // Don't leave this lying around
+ nullptr);
+
+ return lockFile != INVALID_HANDLE_VALUE;
+}
+
+const char kGeneralSection[] = "[General]";
+const char kStartWithLastProfile[] = "StartWithLastProfile=";
+
+static bool ProfileDbHasStartWithLastProfile(IFStream& iniContents) {
+ bool inGeneral = false;
+ std::string line;
+ while (std::getline(iniContents, line)) {
+ int whitespace = 0;
+ while (line.length() > whitespace &&
+ (line[whitespace] == ' ' || line[whitespace] == '\t')) {
+ whitespace++;
+ }
+ line.erase(0, whitespace);
+
+ if (line.compare(kGeneralSection) == 0) {
+ inGeneral = true;
+ } else if (inGeneral) {
+ if (line[0] == '[') {
+ inGeneral = false;
+ } else {
+ if (line.find(kStartWithLastProfile) == 0) {
+ char val = line.c_str()[sizeof(kStartWithLastProfile) - 1];
+ if (val == '0') {
+ return false;
+ } else if (val == '1') {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ // If we don't find it in the .ini file, we interpret that as true
+ return true;
+}
+
+static bool CheckForStartWithLastProfile() {
+ auto roamingAppData = GetKnownFolderPath(FOLDERID_RoamingAppData);
+ if (!roamingAppData) {
+ return false;
+ }
+ std::wstring profileDbPath(roamingAppData.get());
+ profileDbPath.append(
+ L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\profiles.ini");
+ IFStream profileDb(profileDbPath.c_str());
+ if (profileDb.fail()) {
+ return false;
+ }
+
+ return ProfileDbHasStartWithLastProfile(profileDb);
+}
+
+// We could use nsAutoRegKey, but including nsWindowsHelpers.h causes build
+// failures in random places because we're in mozglue. Overall it should be
+// simpler and cleaner to just step around that issue with this class:
+class MOZ_RAII AutoCloseRegKey {
+ public:
+ explicit AutoCloseRegKey(HKEY key) : mKey(key) {}
+ ~AutoCloseRegKey() { ::RegCloseKey(mKey); }
+
+ private:
+ HKEY mKey;
+};
+
+int CSSToDevPixels(double cssPixels, double scaling) {
+ return floor(cssPixels * scaling + 0.5);
+}
+
+int CSSToDevPixels(int cssPixels, double scaling) {
+ return CSSToDevPixels((double)cssPixels, scaling);
+}
+
+int CSSToDevPixelsFloor(double cssPixels, double scaling) {
+ return floor(cssPixels * scaling);
+}
+
+// Some things appear to floor to device pixels rather than rounding. A good
+// example of this is border widths.
+int CSSToDevPixelsFloor(int cssPixels, double scaling) {
+ return CSSToDevPixelsFloor((double)cssPixels, scaling);
+}
+
+double SignedDistanceToCircle(double x, double y, double radius) {
+ return sqrt(x * x + y * y) - radius;
+}
+
+// For more details, see
+// https://searchfox.org/mozilla-central/rev/a5d9abfda1e26b1207db9549549ab0bdd73f735d/gfx/wr/webrender/res/shared.glsl#141-187
+// which was a reference for this function.
+double DistanceAntiAlias(double signedDistance) {
+ // Distance assumed to be in device pixels. We use an aa range of 0.5 for
+ // reasons detailed in the linked code above.
+ const double aaRange = 0.5;
+ double dist = 0.5 * signedDistance / aaRange;
+ if (dist <= -0.5 + std::numeric_limits<double>::epsilon()) return 1.0;
+ if (dist >= 0.5 - std::numeric_limits<double>::epsilon()) return 0.0;
+ return 0.5 + dist * (0.8431027 * dist * dist - 1.14453603);
+}
+
+void RasterizeRoundedRectTopAndBottom(const DrawRect& rect) {
+ if (rect.height <= 2 * rect.borderRadius) {
+ MOZ_ASSERT(false, "Skeleton UI rect height too small for border radius.");
+ return;
+ }
+ if (rect.width <= 2 * rect.borderRadius) {
+ MOZ_ASSERT(false, "Skeleton UI rect width too small for border radius.");
+ return;
+ }
+
+ NormalizedRGB rgbBase = UintToRGB(rect.backgroundColor);
+ NormalizedRGB rgbBlend = UintToRGB(rect.color);
+
+ for (int rowIndex = 0; rowIndex < rect.borderRadius; ++rowIndex) {
+ int yTop = rect.y + rect.borderRadius - 1 - rowIndex;
+ int yBottom = rect.y + rect.height - rect.borderRadius + rowIndex;
+
+ uint32_t* lineStartTop = &sPixelBuffer[yTop * sWindowWidth];
+ uint32_t* innermostPixelTopLeft =
+ lineStartTop + rect.x + rect.borderRadius - 1;
+ uint32_t* innermostPixelTopRight =
+ lineStartTop + rect.x + rect.width - rect.borderRadius;
+ uint32_t* lineStartBottom = &sPixelBuffer[yBottom * sWindowWidth];
+ uint32_t* innermostPixelBottomLeft =
+ lineStartBottom + rect.x + rect.borderRadius - 1;
+ uint32_t* innermostPixelBottomRight =
+ lineStartBottom + rect.x + rect.width - rect.borderRadius;
+
+ // Add 0.5 to x and y to get the pixel center.
+ double pixelY = (double)rowIndex + 0.5;
+ for (int columnIndex = 0; columnIndex < rect.borderRadius; ++columnIndex) {
+ double pixelX = (double)columnIndex + 0.5;
+ double distance =
+ SignedDistanceToCircle(pixelX, pixelY, (double)rect.borderRadius);
+ double alpha = DistanceAntiAlias(distance);
+ NormalizedRGB rgb = Lerp(rgbBase, rgbBlend, alpha);
+ uint32_t color = RGBToUint(rgb);
+
+ innermostPixelTopLeft[-columnIndex] = color;
+ innermostPixelTopRight[columnIndex] = color;
+ innermostPixelBottomLeft[-columnIndex] = color;
+ innermostPixelBottomRight[columnIndex] = color;
+ }
+
+ std::fill(innermostPixelTopLeft + 1, innermostPixelTopRight, rect.color);
+ std::fill(innermostPixelBottomLeft + 1, innermostPixelBottomRight,
+ rect.color);
+ }
+}
+
+void RasterizeAnimatedRoundedRectTopAndBottom(
+ const ColorRect& colorRect, const uint32_t* animationLookup,
+ int priorUpdateAreaMin, int priorUpdateAreaMax, int currentUpdateAreaMin,
+ int currentUpdateAreaMax, int animationMin) {
+ // We iterate through logical pixel rows here, from inside to outside, which
+ // for the top of the rounded rect means from bottom to top, and for the
+ // bottom of the rect means top to bottom. We paint pixels from left to
+ // right on the top and bottom rows at the same time for the entire animation
+ // window. (If the animation window does not overlap any rounded corners,
+ // however, we won't be called at all)
+ for (int rowIndex = 0; rowIndex < colorRect.borderRadius; ++rowIndex) {
+ int yTop = colorRect.y + colorRect.borderRadius - 1 - rowIndex;
+ int yBottom =
+ colorRect.y + colorRect.height - colorRect.borderRadius + rowIndex;
+
+ uint32_t* lineStartTop = &sPixelBuffer[yTop * sWindowWidth];
+ uint32_t* lineStartBottom = &sPixelBuffer[yBottom * sWindowWidth];
+
+ // Add 0.5 to x and y to get the pixel center.
+ double pixelY = (double)rowIndex + 0.5;
+ for (int x = priorUpdateAreaMin; x < currentUpdateAreaMax; ++x) {
+ // The column index is the distance from the innermost pixel, which
+ // is different depending on whether we're on the left or right
+ // side of the rect. It will always be the max here, and if it's
+ // negative that just means we're outside the rounded area.
+ int columnIndex =
+ std::max((int)colorRect.x + (int)colorRect.borderRadius - x - 1,
+ x - ((int)colorRect.x + (int)colorRect.width -
+ (int)colorRect.borderRadius));
+
+ double alpha = 1.0;
+ if (columnIndex >= 0) {
+ double pixelX = (double)columnIndex + 0.5;
+ double distance = SignedDistanceToCircle(
+ pixelX, pixelY, (double)colorRect.borderRadius);
+ alpha = DistanceAntiAlias(distance);
+ }
+ // We don't do alpha blending for the antialiased pixels at the
+ // shape's border. It is not noticeable in the animation.
+ if (alpha > 1.0 - std::numeric_limits<double>::epsilon()) {
+ // Overwrite the tail end of last frame's animation with the
+ // rect's normal, unanimated color.
+ uint32_t color = x < priorUpdateAreaMax
+ ? colorRect.color
+ : animationLookup[x - animationMin];
+ lineStartTop[x] = color;
+ lineStartBottom[x] = color;
+ }
+ }
+ }
+}
+
+void RasterizeColorRect(const ColorRect& colorRect) {
+ // We sometimes split our rect into two, to simplify drawing borders. If we
+ // have a border, we draw a stroke-only rect first, and then draw the smaller
+ // inner rect on top of it.
+ Vector<DrawRect, 2> drawRects;
+ Unused << drawRects.reserve(2);
+ if (colorRect.borderWidth == 0) {
+ DrawRect rect = {};
+ rect.color = colorRect.color;
+ rect.backgroundColor =
+ sPixelBuffer[colorRect.y * sWindowWidth + colorRect.x];
+ rect.x = colorRect.x;
+ rect.y = colorRect.y;
+ rect.width = colorRect.width;
+ rect.height = colorRect.height;
+ rect.borderRadius = colorRect.borderRadius;
+ rect.strokeOnly = false;
+ drawRects.infallibleAppend(rect);
+ } else {
+ DrawRect borderRect = {};
+ borderRect.color = colorRect.borderColor;
+ borderRect.backgroundColor =
+ sPixelBuffer[colorRect.y * sWindowWidth + colorRect.x];
+ borderRect.x = colorRect.x;
+ borderRect.y = colorRect.y;
+ borderRect.width = colorRect.width;
+ borderRect.height = colorRect.height;
+ borderRect.borderRadius = colorRect.borderRadius;
+ borderRect.borderWidth = colorRect.borderWidth;
+ borderRect.strokeOnly = true;
+ drawRects.infallibleAppend(borderRect);
+
+ DrawRect baseRect = {};
+ baseRect.color = colorRect.color;
+ baseRect.backgroundColor = borderRect.color;
+ baseRect.x = colorRect.x + colorRect.borderWidth;
+ baseRect.y = colorRect.y + colorRect.borderWidth;
+ baseRect.width = colorRect.width - 2 * colorRect.borderWidth;
+ baseRect.height = colorRect.height - 2 * colorRect.borderWidth;
+ baseRect.borderRadius =
+ std::max(0, (int)colorRect.borderRadius - (int)colorRect.borderWidth);
+ baseRect.borderWidth = 0;
+ baseRect.strokeOnly = false;
+ drawRects.infallibleAppend(baseRect);
+ }
+
+ for (const DrawRect& rect : drawRects) {
+ if (rect.height <= 0 || rect.width <= 0) {
+ continue;
+ }
+
+ // For rounded rectangles, the first thing we do is draw the top and
+ // bottom of the rectangle, with the more complicated logic below. After
+ // that we can just draw the vertically centered part of the rect like
+ // normal.
+ RasterizeRoundedRectTopAndBottom(rect);
+
+ // We then draw the flat, central portion of the rect (which in the case of
+ // non-rounded rects, is just the entire thing.)
+ int solidRectStartY =
+ std::clamp(rect.y + rect.borderRadius, 0, sTotalChromeHeight);
+ int solidRectEndY = std::clamp(rect.y + rect.height - rect.borderRadius, 0,
+ sTotalChromeHeight);
+ for (int y = solidRectStartY; y < solidRectEndY; ++y) {
+ // For strokeOnly rects (used to draw borders), we just draw the left
+ // and right side here. Looping down a column of pixels is not the most
+ // cache-friendly thing, but it shouldn't be a big deal given the height
+ // of the urlbar.
+ // Also, if borderRadius is less than borderWidth, we need to ensure
+ // that we fully draw the top and bottom lines, so we make sure to check
+ // that we're inside the middle range range before excluding pixels.
+ if (rect.strokeOnly && y - rect.y > rect.borderWidth &&
+ rect.y + rect.height - y > rect.borderWidth) {
+ int startXLeft = std::clamp(rect.x, 0, sWindowWidth);
+ int endXLeft = std::clamp(rect.x + rect.borderWidth, 0, sWindowWidth);
+ int startXRight =
+ std::clamp(rect.x + rect.width - rect.borderWidth, 0, sWindowWidth);
+ int endXRight = std::clamp(rect.x + rect.width, 0, sWindowWidth);
+
+ uint32_t* lineStart = &sPixelBuffer[y * sWindowWidth];
+ uint32_t* dataStartLeft = lineStart + startXLeft;
+ uint32_t* dataEndLeft = lineStart + endXLeft;
+ uint32_t* dataStartRight = lineStart + startXRight;
+ uint32_t* dataEndRight = lineStart + endXRight;
+ std::fill(dataStartLeft, dataEndLeft, rect.color);
+ std::fill(dataStartRight, dataEndRight, rect.color);
+ } else {
+ int startX = std::clamp(rect.x, 0, sWindowWidth);
+ int endX = std::clamp(rect.x + rect.width, 0, sWindowWidth);
+ uint32_t* lineStart = &sPixelBuffer[y * sWindowWidth];
+ uint32_t* dataStart = lineStart + startX;
+ uint32_t* dataEnd = lineStart + endX;
+ std::fill(dataStart, dataEnd, rect.color);
+ }
+ }
+ }
+}
+
+// Paints the pixels to sPixelBuffer for the skeleton UI animation (a light
+// gradient which moves from left to right across the grey placeholder rects).
+// Takes in the rect to draw, together with a lookup table for the gradient,
+// and the bounds of the previous and current frame of the animation.
+bool RasterizeAnimatedRect(const ColorRect& colorRect,
+ const uint32_t* animationLookup,
+ int priorAnimationMin, int animationMin,
+ int animationMax) {
+ int rectMin = colorRect.x;
+ int rectMax = colorRect.x + colorRect.width;
+ bool animationWindowOverlaps =
+ rectMax >= priorAnimationMin && rectMin < animationMax;
+
+ int priorUpdateAreaMin = std::max(rectMin, priorAnimationMin);
+ int priorUpdateAreaMax = std::min(rectMax, animationMin);
+ int currentUpdateAreaMin = std::max(rectMin, animationMin);
+ int currentUpdateAreaMax = std::min(rectMax, animationMax);
+
+ if (!animationWindowOverlaps) {
+ return false;
+ }
+
+ bool animationWindowOverlapsBorderRadius =
+ rectMin + colorRect.borderRadius > priorAnimationMin ||
+ rectMax - colorRect.borderRadius <= animationMax;
+
+ // If we don't overlap the left or right side of the rounded rectangle,
+ // just pretend it's not rounded. This is a small optimization but
+ // there's no point in doing all of this rounded rectangle checking if
+ // we aren't even overlapping
+ int borderRadius =
+ animationWindowOverlapsBorderRadius ? colorRect.borderRadius : 0;
+
+ if (borderRadius > 0) {
+ // Similarly to how we draw the rounded rects in DrawSkeletonUI, we
+ // first draw the rounded top and bottom, and then we draw the center
+ // rect.
+ RasterizeAnimatedRoundedRectTopAndBottom(
+ colorRect, animationLookup, priorUpdateAreaMin, priorUpdateAreaMax,
+ currentUpdateAreaMin, currentUpdateAreaMax, animationMin);
+ }
+
+ for (int y = colorRect.y + borderRadius;
+ y < colorRect.y + colorRect.height - borderRadius; ++y) {
+ uint32_t* lineStart = &sPixelBuffer[y * sWindowWidth];
+ // Overwrite the tail end of last frame's animation with the rect's
+ // normal, unanimated color.
+ for (int x = priorUpdateAreaMin; x < priorUpdateAreaMax; ++x) {
+ lineStart[x] = colorRect.color;
+ }
+ // Then apply the animated color
+ for (int x = currentUpdateAreaMin; x < currentUpdateAreaMax; ++x) {
+ lineStart[x] = animationLookup[x - animationMin];
+ }
+ }
+
+ return true;
+}
+
+void DrawSkeletonUI(HWND hWnd, CSSPixelSpan urlbarCSSSpan,
+ CSSPixelSpan searchbarCSSSpan,
+ Vector<CSSPixelSpan>& springs,
+ const ThemeColors& currentTheme,
+ const EnumSet<SkeletonUIFlag, uint32_t>& flags) {
+ // NOTE: we opt here to paint a pixel buffer for the application chrome by
+ // hand, without using native UI library methods. Why do we do this?
+ //
+ // 1) It gives us a little bit more control, especially if we want to animate
+ // any of this.
+ // 2) It's actually more portable. We can do this on any platform where we
+ // can blit a pixel buffer to the screen, and it only has to change
+ // insofar as the UI is different on those platforms (and thus would have
+ // to change anyway.)
+ //
+ // The performance impact of this ought to be negligible. As far as has been
+ // observed, on slow reference hardware this might take up to a millisecond,
+ // for a startup which otherwise takes 30 seconds.
+ //
+ // The readability and maintainability are a greater concern. When the
+ // silhouette of Firefox's core UI changes, this code will likely need to
+ // change. However, for the foreseeable future, our skeleton UI will be mostly
+ // axis-aligned geometric shapes, and the thought is that any code which is
+ // manipulating raw pixels should not be *too* hard to maintain and
+ // understand so long as it is only painting such simple shapes.
+
+ sAnimationColor = currentTheme.animationColor;
+ sToolbarForegroundColor = currentTheme.toolbarForegroundColor;
+
+ bool menubarShown = flags.contains(SkeletonUIFlag::MenubarShown);
+ bool bookmarksToolbarShown =
+ flags.contains(SkeletonUIFlag::BookmarksToolbarShown);
+ bool rtlEnabled = flags.contains(SkeletonUIFlag::RtlEnabled);
+
+ int chromeHorMargin = CSSToDevPixels(2, sCSSToDevPixelScaling);
+ int verticalOffset = sMaximized ? sNonClientVerticalMargins : 0;
+ int horizontalOffset =
+ sNonClientHorizontalMargins - (sMaximized ? 0 : chromeHorMargin);
+
+ // found in browser-aero.css, ":root[sizemode=normal][tabsintitlebar]"
+ int topBorderHeight =
+ sMaximized ? 0 : CSSToDevPixels(1, sCSSToDevPixelScaling);
+ // found in tabs.inc.css, "--tab-min-height" - depends on uidensity variable
+ int tabBarHeight = CSSToDevPixels(33, sCSSToDevPixelScaling) + verticalOffset;
+ // found in tabs.inc.css, ".titlebar-spacer"
+ int titlebarSpacerWidth = horizontalOffset;
+ if (!sMaximized && !menubarShown) {
+ titlebarSpacerWidth += CSSToDevPixels(40, sCSSToDevPixelScaling);
+ }
+ // found in tabs.inc.css, ".tab-line"
+ int tabLineHeight = CSSToDevPixels(2, sCSSToDevPixelScaling) + verticalOffset;
+ int selectedTabWidth = CSSToDevPixels(224, sCSSToDevPixelScaling);
+ int toolbarHeight = CSSToDevPixels(39, sCSSToDevPixelScaling);
+ // found in browser.css, "#PersonalToolbar"
+ int bookmarkToolbarHeight = CSSToDevPixels(28, sCSSToDevPixelScaling);
+ if (bookmarksToolbarShown) {
+ toolbarHeight += bookmarkToolbarHeight;
+ }
+ // found in urlbar-searchbar.inc.css, "#urlbar[breakout]"
+ int urlbarTopOffset = CSSToDevPixels(5, sCSSToDevPixelScaling);
+ int urlbarHeight = CSSToDevPixels(30, sCSSToDevPixelScaling);
+ // found in browser-aero.css, "#navigator-toolbox::after" border-bottom
+ int chromeContentDividerHeight = CSSToDevPixels(1, sCSSToDevPixelScaling);
+
+ int tabPlaceholderBarMarginTop = CSSToDevPixels(13, sCSSToDevPixelScaling);
+ int tabPlaceholderBarMarginLeft = CSSToDevPixels(10, sCSSToDevPixelScaling);
+ int tabPlaceholderBarHeight = CSSToDevPixels(8, sCSSToDevPixelScaling);
+ int tabPlaceholderBarWidth = CSSToDevPixels(120, sCSSToDevPixelScaling);
+
+ int toolbarPlaceholderHeight = CSSToDevPixels(10, sCSSToDevPixelScaling);
+ int toolbarPlaceholderMarginRight =
+ rtlEnabled ? CSSToDevPixels(11, sCSSToDevPixelScaling)
+ : CSSToDevPixels(9, sCSSToDevPixelScaling);
+ int toolbarPlaceholderMarginLeft =
+ rtlEnabled ? CSSToDevPixels(9, sCSSToDevPixelScaling)
+ : CSSToDevPixels(11, sCSSToDevPixelScaling);
+ int placeholderMargin = CSSToDevPixels(8, sCSSToDevPixelScaling);
+
+ int menubarHeightDevPixels =
+ menubarShown ? CSSToDevPixels(28, sCSSToDevPixelScaling) : 0;
+
+ // controlled by css variable urlbarMarginInline in urlbar-searchbar.inc.css
+ int urlbarMargin =
+ CSSToDevPixels(5, sCSSToDevPixelScaling) + horizontalOffset;
+
+ int urlbarTextPlaceholderMarginTop =
+ CSSToDevPixels(10, sCSSToDevPixelScaling);
+ int urlbarTextPlaceholderMarginLeft =
+ CSSToDevPixels(10, sCSSToDevPixelScaling);
+ int urlbarTextPlaceHolderWidth = CSSToDevPixels(
+ std::clamp(urlbarCSSSpan.end - urlbarCSSSpan.start - 10.0, 0.0, 260.0),
+ sCSSToDevPixelScaling);
+ int urlbarTextPlaceholderHeight = CSSToDevPixels(10, sCSSToDevPixelScaling);
+
+ int searchbarTextPlaceholderWidth = CSSToDevPixels(62, sCSSToDevPixelScaling);
+
+ auto scopeExit = MakeScopeExit([&] {
+ delete sAnimatedRects;
+ sAnimatedRects = nullptr;
+ return;
+ });
+
+ Vector<ColorRect> rects;
+
+ ColorRect topBorder = {};
+ topBorder.color = 0x00000000;
+ topBorder.x = 0;
+ topBorder.y = 0;
+ topBorder.width = sWindowWidth;
+ topBorder.height = topBorderHeight;
+ topBorder.flipIfRTL = false;
+ if (!rects.append(topBorder)) {
+ return;
+ }
+
+ ColorRect menubar = {};
+ menubar.color = currentTheme.tabBarColor;
+ menubar.x = 0;
+ menubar.y = topBorder.height;
+ menubar.width = sWindowWidth;
+ menubar.height = menubarHeightDevPixels;
+ menubar.flipIfRTL = false;
+ if (!rects.append(menubar)) {
+ return;
+ }
+
+ int placeholderBorderRadius = CSSToDevPixels(2, sCSSToDevPixelScaling);
+ // found in browser.css "--toolbarbutton-border-radius"
+ int urlbarBorderRadius = CSSToDevPixels(2, sCSSToDevPixelScaling);
+ // found in urlbar-searchbar.inc.css "#urlbar-background"
+ int urlbarBorderWidth = CSSToDevPixelsFloor(1, sCSSToDevPixelScaling);
+ int urlbarBorderColor = currentTheme.urlbarBorderColor;
+
+ // The (traditionally dark blue on Windows) background of the tab bar.
+ ColorRect tabBar = {};
+ tabBar.color = currentTheme.tabBarColor;
+ tabBar.x = 0;
+ tabBar.y = menubar.height + topBorder.height;
+ tabBar.width = sWindowWidth;
+ tabBar.height = tabBarHeight;
+ tabBar.flipIfRTL = false;
+ if (!rects.append(tabBar)) {
+ return;
+ }
+
+ // The blue highlight at the top of the initial selected tab
+ ColorRect tabLine = {};
+ tabLine.color = currentTheme.tabLineColor;
+ tabLine.x = titlebarSpacerWidth;
+ tabLine.y = menubar.height + topBorder.height;
+ tabLine.width = selectedTabWidth;
+ tabLine.height = tabLineHeight;
+ tabLine.flipIfRTL = true;
+ if (!rects.append(tabLine)) {
+ return;
+ }
+
+ // The initial selected tab
+ ColorRect selectedTab = {};
+ selectedTab.color = currentTheme.backgroundColor;
+ selectedTab.x = titlebarSpacerWidth;
+ selectedTab.y = tabLine.y + tabLineHeight;
+ selectedTab.width = selectedTabWidth;
+ selectedTab.height = tabBar.y + tabBar.height - selectedTab.y;
+ selectedTab.flipIfRTL = true;
+ if (!rects.append(selectedTab)) {
+ return;
+ }
+
+ // A placeholder rect representing text that will fill the selected tab title
+ ColorRect tabTextPlaceholder = {};
+ tabTextPlaceholder.color = sToolbarForegroundColor;
+ tabTextPlaceholder.x = selectedTab.x + tabPlaceholderBarMarginLeft;
+ tabTextPlaceholder.y = selectedTab.y + tabPlaceholderBarMarginTop;
+ tabTextPlaceholder.width = tabPlaceholderBarWidth;
+ tabTextPlaceholder.height = tabPlaceholderBarHeight;
+ tabTextPlaceholder.borderRadius = placeholderBorderRadius;
+ tabTextPlaceholder.flipIfRTL = true;
+ if (!rects.append(tabTextPlaceholder)) {
+ return;
+ }
+
+ // The toolbar background
+ ColorRect toolbar = {};
+ toolbar.color = currentTheme.backgroundColor;
+ toolbar.x = 0;
+ toolbar.y = tabBar.y + tabBarHeight;
+ toolbar.width = sWindowWidth;
+ toolbar.height = toolbarHeight;
+ toolbar.flipIfRTL = false;
+ if (!rects.append(toolbar)) {
+ return;
+ }
+
+ // The single-pixel divider line below the toolbar
+ ColorRect chromeContentDivider = {};
+ chromeContentDivider.color = currentTheme.chromeContentDividerColor;
+ chromeContentDivider.x = 0;
+ chromeContentDivider.y = toolbar.y + toolbar.height;
+ chromeContentDivider.width = sWindowWidth;
+ chromeContentDivider.height = chromeContentDividerHeight;
+ chromeContentDivider.flipIfRTL = false;
+ if (!rects.append(chromeContentDivider)) {
+ return;
+ }
+
+ // The urlbar
+ ColorRect urlbar = {};
+ urlbar.color = currentTheme.urlbarColor;
+ urlbar.x = CSSToDevPixels(urlbarCSSSpan.start, sCSSToDevPixelScaling) +
+ horizontalOffset;
+ urlbar.y = tabBar.y + tabBarHeight + urlbarTopOffset;
+ urlbar.width = CSSToDevPixels((urlbarCSSSpan.end - urlbarCSSSpan.start),
+ sCSSToDevPixelScaling);
+ urlbar.height = urlbarHeight;
+ urlbar.borderRadius = urlbarBorderRadius;
+ urlbar.borderWidth = urlbarBorderWidth;
+ urlbar.borderColor = urlbarBorderColor;
+ urlbar.flipIfRTL = false;
+ if (!rects.append(urlbar)) {
+ return;
+ }
+
+ // The urlbar placeholder rect representating text that will fill the urlbar
+ // If rtl is enabled, it is flipped relative to the the urlbar rectangle, not
+ // sWindowWidth.
+ ColorRect urlbarTextPlaceholder = {};
+ urlbarTextPlaceholder.color = sToolbarForegroundColor;
+ urlbarTextPlaceholder.x =
+ rtlEnabled
+ ? ((urlbar.x + urlbar.width) - urlbarTextPlaceholderMarginLeft -
+ urlbarTextPlaceHolderWidth)
+ : (urlbar.x + urlbarTextPlaceholderMarginLeft);
+ urlbarTextPlaceholder.y = urlbar.y + urlbarTextPlaceholderMarginTop;
+ urlbarTextPlaceholder.width = urlbarTextPlaceHolderWidth;
+ urlbarTextPlaceholder.height = urlbarTextPlaceholderHeight;
+ urlbarTextPlaceholder.borderRadius = placeholderBorderRadius;
+ urlbarTextPlaceholder.flipIfRTL = false;
+ if (!rects.append(urlbarTextPlaceholder)) {
+ return;
+ }
+
+ // The searchbar and placeholder text, if present
+ // This is y-aligned with the urlbar
+ bool hasSearchbar = searchbarCSSSpan.start != 0 && searchbarCSSSpan.end != 0;
+ ColorRect searchbarRect = {};
+ if (hasSearchbar == true) {
+ searchbarRect.color = currentTheme.urlbarColor;
+ searchbarRect.x =
+ CSSToDevPixels(searchbarCSSSpan.start, sCSSToDevPixelScaling) +
+ horizontalOffset;
+ searchbarRect.y = urlbar.y;
+ searchbarRect.width = CSSToDevPixels(
+ searchbarCSSSpan.end - searchbarCSSSpan.start, sCSSToDevPixelScaling);
+ searchbarRect.height = urlbarHeight;
+ searchbarRect.borderRadius = urlbarBorderRadius;
+ searchbarRect.borderWidth = urlbarBorderWidth;
+ searchbarRect.borderColor = urlbarBorderColor;
+ searchbarRect.flipIfRTL = false;
+ if (!rects.append(searchbarRect)) {
+ return;
+ }
+
+ // The placeholder rect representating text that will fill the searchbar
+ // This uses the same margins as the urlbarTextPlaceholder
+ // If rtl is enabled, it is flipped relative to the the searchbar rectangle,
+ // not sWindowWidth.
+ ColorRect searchbarTextPlaceholder = {};
+ searchbarTextPlaceholder.color = sToolbarForegroundColor;
+ searchbarTextPlaceholder.x =
+ rtlEnabled
+ ? ((searchbarRect.x + searchbarRect.width) -
+ urlbarTextPlaceholderMarginLeft - searchbarTextPlaceholderWidth)
+ : (searchbarRect.x + urlbarTextPlaceholderMarginLeft);
+ searchbarTextPlaceholder.y =
+ searchbarRect.y + urlbarTextPlaceholderMarginTop;
+ searchbarTextPlaceholder.width = searchbarTextPlaceholderWidth;
+ searchbarTextPlaceholder.height = urlbarTextPlaceholderHeight;
+ searchbarTextPlaceholder.flipIfRTL = false;
+ if (!rects.append(searchbarTextPlaceholder) ||
+ !sAnimatedRects->append(searchbarTextPlaceholder)) {
+ return;
+ }
+ }
+
+ // Determine where the placeholder rectangles should not go. This is
+ // anywhere occupied by a spring, urlbar, or searchbar
+ Vector<DevPixelSpan> noPlaceholderSpans;
+
+ DevPixelSpan urlbarSpan;
+ urlbarSpan.start = urlbar.x - urlbarMargin;
+ urlbarSpan.end = urlbar.width + urlbar.x + urlbarMargin;
+
+ DevPixelSpan searchbarSpan;
+ if (hasSearchbar) {
+ searchbarSpan.start = searchbarRect.x - urlbarMargin;
+ searchbarSpan.end = searchbarRect.width + searchbarRect.x + urlbarMargin;
+ }
+
+ DevPixelSpan marginLeftPlaceholder;
+ marginLeftPlaceholder.start = toolbarPlaceholderMarginLeft;
+ marginLeftPlaceholder.end = toolbarPlaceholderMarginLeft;
+ if (!noPlaceholderSpans.append(marginLeftPlaceholder)) {
+ return;
+ }
+
+ if (rtlEnabled) {
+ // If we're RTL, then the springs as ordered in the DOM will be from right
+ // to left, which will break our comparison logic below
+ springs.reverse();
+ }
+
+ for (auto spring : springs) {
+ DevPixelSpan springDevPixels;
+ springDevPixels.start =
+ CSSToDevPixels(spring.start, sCSSToDevPixelScaling) + horizontalOffset;
+ springDevPixels.end =
+ CSSToDevPixels(spring.end, sCSSToDevPixelScaling) + horizontalOffset;
+ if (!noPlaceholderSpans.append(springDevPixels)) {
+ return;
+ }
+ }
+
+ DevPixelSpan marginRightPlaceholder;
+ marginRightPlaceholder.start = sWindowWidth - toolbarPlaceholderMarginRight;
+ marginRightPlaceholder.end = sWindowWidth - toolbarPlaceholderMarginRight;
+ if (!noPlaceholderSpans.append(marginRightPlaceholder)) {
+ return;
+ }
+
+ Vector<DevPixelSpan, 2> spansToAdd;
+ Unused << spansToAdd.reserve(2);
+ spansToAdd.infallibleAppend(urlbarSpan);
+ if (hasSearchbar) {
+ spansToAdd.infallibleAppend(searchbarSpan);
+ }
+
+ for (auto& toAdd : spansToAdd) {
+ for (auto& span : noPlaceholderSpans) {
+ if (span.start > toAdd.start) {
+ if (!noPlaceholderSpans.insert(&span, toAdd)) {
+ return;
+ }
+ break;
+ }
+ }
+ }
+
+ for (int i = 1; i < noPlaceholderSpans.length(); i++) {
+ int start = noPlaceholderSpans[i - 1].end + placeholderMargin;
+ int end = noPlaceholderSpans[i].start - placeholderMargin;
+ if (start + 2 * placeholderBorderRadius >= end) {
+ continue;
+ }
+
+ // The placeholder rects should all be y-aligned.
+ ColorRect placeholderRect = {};
+ placeholderRect.color = sToolbarForegroundColor;
+ placeholderRect.x = start;
+ placeholderRect.y = urlbarTextPlaceholder.y;
+ placeholderRect.width = end - start;
+ placeholderRect.height = toolbarPlaceholderHeight;
+ placeholderRect.borderRadius = placeholderBorderRadius;
+ placeholderRect.flipIfRTL = false;
+ if (!rects.append(placeholderRect) ||
+ !sAnimatedRects->append(placeholderRect)) {
+ return;
+ }
+ }
+
+ sTotalChromeHeight = chromeContentDivider.y + chromeContentDivider.height;
+ if (sTotalChromeHeight > sWindowHeight) {
+ printf_stderr("Exiting drawing skeleton UI because window is too small.\n");
+ return;
+ }
+
+ if (!sAnimatedRects->append(tabTextPlaceholder) ||
+ !sAnimatedRects->append(urlbarTextPlaceholder)) {
+ return;
+ }
+
+ sPixelBuffer =
+ (uint32_t*)calloc(sWindowWidth * sTotalChromeHeight, sizeof(uint32_t));
+
+ for (auto& rect : *sAnimatedRects) {
+ if (rtlEnabled && rect.flipIfRTL) {
+ rect.x = sWindowWidth - rect.x - rect.width;
+ }
+ rect.x = std::clamp(rect.x, 0, sWindowWidth);
+ rect.width = std::clamp(rect.width, 0, sWindowWidth - rect.x);
+ rect.y = std::clamp(rect.y, 0, sTotalChromeHeight);
+ rect.height = std::clamp(rect.height, 0, sTotalChromeHeight - rect.y);
+ }
+
+ for (auto& rect : rects) {
+ if (rtlEnabled && rect.flipIfRTL) {
+ rect.x = sWindowWidth - rect.x - rect.width;
+ }
+ rect.x = std::clamp(rect.x, 0, sWindowWidth);
+ rect.width = std::clamp(rect.width, 0, sWindowWidth - rect.x);
+ rect.y = std::clamp(rect.y, 0, sTotalChromeHeight);
+ rect.height = std::clamp(rect.height, 0, sTotalChromeHeight - rect.y);
+ RasterizeColorRect(rect);
+ }
+
+ HDC hdc = sGetWindowDC(hWnd);
+
+ BITMAPINFO chromeBMI = {};
+ chromeBMI.bmiHeader.biSize = sizeof(chromeBMI.bmiHeader);
+ chromeBMI.bmiHeader.biWidth = sWindowWidth;
+ chromeBMI.bmiHeader.biHeight = -sTotalChromeHeight;
+ chromeBMI.bmiHeader.biPlanes = 1;
+ chromeBMI.bmiHeader.biBitCount = 32;
+ chromeBMI.bmiHeader.biCompression = BI_RGB;
+
+ // First, we just paint the chrome area with our pixel buffer
+ sStretchDIBits(hdc, 0, 0, sWindowWidth, sTotalChromeHeight, 0, 0,
+ sWindowWidth, sTotalChromeHeight, sPixelBuffer, &chromeBMI,
+ DIB_RGB_COLORS, SRCCOPY);
+
+ // Then, we just fill the rest with FillRect
+ RECT rect = {0, sTotalChromeHeight, sWindowWidth, sWindowHeight};
+ HBRUSH brush = sCreateSolidBrush(currentTheme.backgroundColor);
+ sFillRect(hdc, &rect, brush);
+
+ scopeExit.release();
+ sReleaseDC(hWnd, hdc);
+ sDeleteObject(brush);
+}
+
+DWORD WINAPI AnimateSkeletonUI(void* aUnused) {
+ if (!sPixelBuffer || sAnimatedRects->empty()) {
+ return 0;
+ }
+
+ // On each of the animated rects (which happen to all be placeholder UI
+ // rects sharing the same color), we want to animate a gradient moving across
+ // the screen from left to right. The gradient starts as the rect's color on,
+ // the left side, changes to the background color of the window by the middle
+ // of the gradient, and then goes back down to the rect's color. To make this
+ // faster than interpolating between the two colors for each pixel for each
+ // frame, we simply create a lookup buffer in which we can look up the color
+ // for a particular offset into the gradient.
+ //
+ // To do this we just interpolate between the two values, and to give the
+ // gradient a smoother transition between colors, we transform the linear
+ // blend amount via the cubic smooth step function (SmoothStep3) to produce
+ // a smooth start and stop for the gradient. We do this for the first half
+ // of the gradient, and then simply copy that backwards for the second half.
+ //
+ // The CSS width of 80 chosen here is effectively is just to match the size
+ // of the animation provided in the design mockup. We define it in CSS pixels
+ // simply because the rest of our UI is based off of CSS scalings.
+ int animationWidth = CSSToDevPixels(80, sCSSToDevPixelScaling);
+ UniquePtr<uint32_t[]> animationLookup =
+ MakeUnique<uint32_t[]>(animationWidth);
+ uint32_t animationColor = sAnimationColor;
+ NormalizedRGB rgbBlend = UintToRGB(animationColor);
+
+ // Build the first half of the lookup table
+ for (int i = 0; i < animationWidth / 2; ++i) {
+ uint32_t baseColor = sToolbarForegroundColor;
+ double blendAmountLinear =
+ static_cast<double>(i) / (static_cast<double>(animationWidth / 2));
+ double blendAmount = SmoothStep3(blendAmountLinear);
+
+ NormalizedRGB rgbBase = UintToRGB(baseColor);
+ NormalizedRGB rgb = Lerp(rgbBase, rgbBlend, blendAmount);
+ animationLookup[i] = RGBToUint(rgb);
+ }
+
+ // Copy the first half of the lookup table into the second half backwards
+ for (int i = animationWidth / 2; i < animationWidth; ++i) {
+ int j = animationWidth - 1 - i;
+ if (j == animationWidth / 2) {
+ // If animationWidth is odd, we'll be left with one pixel at the center.
+ // Just color that as the animation color.
+ animationLookup[i] = animationColor;
+ } else {
+ animationLookup[i] = animationLookup[j];
+ }
+ }
+
+ // The bitmap info remains unchanged throughout the animation - this just
+ // effectively describes the contents of sPixelBuffer
+ BITMAPINFO chromeBMI = {};
+ chromeBMI.bmiHeader.biSize = sizeof(chromeBMI.bmiHeader);
+ chromeBMI.bmiHeader.biWidth = sWindowWidth;
+ chromeBMI.bmiHeader.biHeight = -sTotalChromeHeight;
+ chromeBMI.bmiHeader.biPlanes = 1;
+ chromeBMI.bmiHeader.biBitCount = 32;
+ chromeBMI.bmiHeader.biCompression = BI_RGB;
+
+ uint32_t animationIteration = 0;
+
+ int devPixelsPerFrame =
+ CSSToDevPixels(kAnimationCSSPixelsPerFrame, sCSSToDevPixelScaling);
+ int devPixelsExtraWindowSize =
+ CSSToDevPixels(kAnimationCSSExtraWindowSize, sCSSToDevPixelScaling);
+
+ if (::InterlockedCompareExchange(&sAnimationControlFlag, 0, 0)) {
+ // The window got consumed before we were able to draw anything.
+ return 0;
+ }
+
+ while (true) {
+ // The gradient will move across the screen at devPixelsPerFrame at
+ // 60fps, and then loop back to the beginning. However, we add a buffer of
+ // devPixelsExtraWindowSize around the edges so it doesn't immediately
+ // jump back, giving it a more pulsing feel.
+ int animationMin = ((animationIteration * devPixelsPerFrame) %
+ (sWindowWidth + devPixelsExtraWindowSize)) -
+ devPixelsExtraWindowSize / 2;
+ int animationMax = animationMin + animationWidth;
+ // The priorAnimationMin is the beginning of the previous frame's animation.
+ // Since we only want to draw the bits of the image that we updated, we need
+ // to overwrite the left bit of the animation we drew last frame with the
+ // default color.
+ int priorAnimationMin = animationMin - devPixelsPerFrame;
+ animationMin = std::max(0, animationMin);
+ priorAnimationMin = std::max(0, priorAnimationMin);
+ animationMax = std::min((int)sWindowWidth, animationMax);
+
+ // The gradient only affects the specific rects that we put into
+ // sAnimatedRects. So we simply update those rects, and maintain a flag
+ // to avoid drawing when we don't need to.
+ bool updatedAnything = false;
+ for (ColorRect rect : *sAnimatedRects) {
+ bool hadUpdates =
+ RasterizeAnimatedRect(rect, animationLookup.get(), priorAnimationMin,
+ animationMin, animationMax);
+ updatedAnything = updatedAnything || hadUpdates;
+ }
+
+ if (updatedAnything) {
+ HDC hdc = sGetWindowDC(sPreXULSkeletonUIWindow);
+
+ sStretchDIBits(hdc, priorAnimationMin, 0,
+ animationMax - priorAnimationMin, sTotalChromeHeight,
+ priorAnimationMin, 0, animationMax - priorAnimationMin,
+ sTotalChromeHeight, sPixelBuffer, &chromeBMI,
+ DIB_RGB_COLORS, SRCCOPY);
+
+ sReleaseDC(sPreXULSkeletonUIWindow, hdc);
+ }
+
+ animationIteration++;
+
+ // We coordinate around our sleep here to ensure that the main thread does
+ // not wait on us if we're sleeping. If we don't get 1 here, it means the
+ // window has been consumed and we don't need to sleep. If in
+ // ConsumePreXULSkeletonUIHandle we get a value other than 1 after
+ // incrementing, it means we're sleeping, and that function can assume that
+ // we will safely exit after the sleep because of the observed value of
+ // sAnimationControlFlag.
+ if (InterlockedIncrement(&sAnimationControlFlag) != 1) {
+ return 0;
+ }
+
+ // Note: Sleep does not guarantee an exact time interval. If the system is
+ // busy, for instance, we could easily end up taking several frames longer,
+ // and really we could be left unscheduled for an arbitrarily long time.
+ // This is fine, and we don't really care. We could track how much time this
+ // actually took and jump the animation forward the appropriate amount, but
+ // its not even clear that that's a better user experience. So we leave this
+ // as simple as we can.
+ ::Sleep(16);
+
+ // Here we bring sAnimationControlFlag back down - again, if we don't get a
+ // 0 here it means we consumed the skeleton UI window in the mean time, so
+ // we can simply exit.
+ if (InterlockedDecrement(&sAnimationControlFlag) != 0) {
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+LRESULT WINAPI PreXULSkeletonUIProc(HWND hWnd, UINT msg, WPARAM wParam,
+ LPARAM lParam) {
+ // NOTE: this block was copied from WinUtils.cpp, and needs to be kept in
+ // sync.
+ if (msg == WM_NCCREATE && sEnableNonClientDpiScaling) {
+ sEnableNonClientDpiScaling(hWnd);
+ }
+
+ // NOTE: this block was paraphrased from the WM_NCCALCSIZE handler in
+ // nsWindow.cpp, and will need to be kept in sync.
+ if (msg == WM_NCCALCSIZE) {
+ RECT* clientRect =
+ wParam ? &(reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam))->rgrc[0]
+ : (reinterpret_cast<RECT*>(lParam));
+
+ // These match the margins set in browser-tabsintitlebar.js with
+ // default prefs on Windows. Bug 1673092 tracks lining this up with
+ // that more correctly instead of hard-coding it.
+ int horizontalOffset =
+ sNonClientHorizontalMargins -
+ (sMaximized ? 0 : CSSToDevPixels(2, sCSSToDevPixelScaling));
+ int verticalOffset =
+ sNonClientHorizontalMargins -
+ (sMaximized ? 0 : CSSToDevPixels(2, sCSSToDevPixelScaling));
+ clientRect->top = clientRect->top;
+ clientRect->left += horizontalOffset;
+ clientRect->right -= horizontalOffset;
+ clientRect->bottom -= verticalOffset;
+ return 0;
+ }
+
+ return ::DefWindowProcW(hWnd, msg, wParam, lParam);
+}
+
+bool IsSystemDarkThemeEnabled() {
+ DWORD result;
+ HKEY themeKey;
+ DWORD dataLen = sizeof(uint32_t);
+ LPCWSTR keyName =
+ L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
+
+ result = ::RegOpenKeyExW(HKEY_CURRENT_USER, keyName, 0, KEY_READ, &themeKey);
+ if (result != ERROR_SUCCESS) {
+ return false;
+ }
+ AutoCloseRegKey closeKey(themeKey);
+
+ uint32_t lightThemeEnabled;
+ result = ::RegGetValueW(
+ themeKey, nullptr, L"AppsUseLightTheme", RRF_RT_REG_DWORD, nullptr,
+ reinterpret_cast<PBYTE>(&lightThemeEnabled), &dataLen);
+ if (result != ERROR_SUCCESS) {
+ return false;
+ }
+ return !lightThemeEnabled;
+}
+
+ThemeColors GetTheme(ThemeMode themeId) {
+ ThemeColors theme = {};
+ switch (themeId) {
+ case ThemeMode::Dark:
+ // Dark theme or default theme when in dark mode
+
+ // controlled by css variable --toolbar-bgcolor
+ theme.backgroundColor = 0x323234;
+ theme.toolbarForegroundColor = 0x6a6a6b;
+ // controlled by css variable --lwt-accent-color
+ theme.tabBarColor = 0x0c0c0d;
+ // controlled by --toolbar-non-lwt-textcolor in browser.css
+ theme.chromeContentDividerColor = 0x0c0c0d;
+ // controlled by css variable --tab-line-color
+ theme.tabLineColor = 0x0a84ff;
+ // controlled by css variable --lwt-toolbar-field-background-color
+ theme.urlbarColor = 0x474749;
+ // controlled by css variable --lwt-toolbar-field-border-color
+ theme.urlbarBorderColor = 0x5a5a5c;
+ theme.animationColor = theme.urlbarColor;
+ return theme;
+ case ThemeMode::Light:
+ // Light theme
+
+ // controlled by --toolbar-bgcolor
+ theme.backgroundColor = 0xf5f6f7;
+ theme.toolbarForegroundColor = 0xd9dadb;
+ // controlled by css variable --lwt-accent-color
+ theme.tabBarColor = 0xe3e4e6;
+ // --chrome-content-separator-color in browser.css
+ theme.chromeContentDividerColor = 0xcccccc;
+ // controlled by css variable --tab-line-color
+ theme.tabLineColor = 0x0a84ff;
+ // by css variable --lwt-toolbar-field-background-color
+ theme.urlbarColor = 0xffffff;
+ // controlled by css variable --lwt-toolbar-field-border-color
+ theme.urlbarBorderColor = 0xcccccc;
+ theme.animationColor = theme.backgroundColor;
+ return theme;
+ case ThemeMode::Default:
+ default:
+ // Default theme when not in dark mode
+ MOZ_ASSERT(themeId == ThemeMode::Default);
+
+ // --toolbar-non-lwt-bgcolor in browser.css
+ theme.backgroundColor = 0xf9f9fa;
+ theme.toolbarForegroundColor = 0xe5e5e5;
+ // found in browser-aero.css ":root[tabsintitlebar]:not(:-moz-lwtheme)"
+ // (set to "hsl(235,33%,19%)")
+ theme.tabBarColor = 0x202340;
+ // --chrome-content-separator-color in browser.css
+ theme.chromeContentDividerColor = 0xe2e1e3;
+ // controlled by css variable --tab-line-color
+ theme.tabLineColor = 0x0a84ff;
+ // controlled by css variable --toolbar-color
+ theme.urlbarColor = 0xffffff;
+ // controlled by css variable --lwt-toolbar-field-border-color
+ theme.urlbarBorderColor = 0xbebebe;
+ theme.animationColor = theme.backgroundColor;
+ return theme;
+ }
+}
+
+bool OpenPreXULSkeletonUIRegKey(HKEY& key) {
+ DWORD disposition;
+ LSTATUS result =
+ ::RegCreateKeyExW(HKEY_CURRENT_USER, kPreXULSkeletonUIKeyPath, 0, nullptr,
+ 0, KEY_ALL_ACCESS, nullptr, &key, &disposition);
+
+ if (result != ERROR_SUCCESS) {
+ return false;
+ }
+
+ if (disposition == REG_CREATED_NEW_KEY) {
+ return false;
+ }
+
+ if (disposition == REG_OPENED_EXISTING_KEY) {
+ return true;
+ }
+
+ ::RegCloseKey(key);
+ return false;
+}
+
+bool LoadGdi32AndUser32Procedures() {
+ HMODULE user32Dll = ::LoadLibraryW(L"user32");
+ HMODULE gdi32Dll = ::LoadLibraryW(L"gdi32");
+
+ if (!user32Dll || !gdi32Dll) {
+ return false;
+ }
+
+ auto getThreadDpiAwarenessContext =
+ (decltype(GetThreadDpiAwarenessContext)*)::GetProcAddress(
+ user32Dll, "GetThreadDpiAwarenessContext");
+ auto areDpiAwarenessContextsEqual =
+ (decltype(AreDpiAwarenessContextsEqual)*)::GetProcAddress(
+ user32Dll, "AreDpiAwarenessContextsEqual");
+ if (getThreadDpiAwarenessContext && areDpiAwarenessContextsEqual &&
+ areDpiAwarenessContextsEqual(getThreadDpiAwarenessContext(),
+ DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) {
+ // EnableNonClientDpiScaling is optional - we can handle not having it.
+ sEnableNonClientDpiScaling =
+ (EnableNonClientDpiScalingProc)::GetProcAddress(
+ user32Dll, "EnableNonClientDpiScaling");
+ }
+
+ sGetSystemMetricsForDpi = (GetSystemMetricsForDpiProc)::GetProcAddress(
+ user32Dll, "GetSystemMetricsForDpi");
+ if (!sGetSystemMetricsForDpi) {
+ return false;
+ }
+ sGetDpiForWindow =
+ (GetDpiForWindowProc)::GetProcAddress(user32Dll, "GetDpiForWindow");
+ if (!sGetDpiForWindow) {
+ return false;
+ }
+ sRegisterClassW =
+ (RegisterClassWProc)::GetProcAddress(user32Dll, "RegisterClassW");
+ if (!sRegisterClassW) {
+ return false;
+ }
+ sCreateWindowExW =
+ (CreateWindowExWProc)::GetProcAddress(user32Dll, "CreateWindowExW");
+ if (!sCreateWindowExW) {
+ return false;
+ }
+ sShowWindow = (ShowWindowProc)::GetProcAddress(user32Dll, "ShowWindow");
+ if (!sShowWindow) {
+ return false;
+ }
+ sSetWindowPos = (SetWindowPosProc)::GetProcAddress(user32Dll, "SetWindowPos");
+ if (!sSetWindowPos) {
+ return false;
+ }
+ sGetWindowDC = (GetWindowDCProc)::GetProcAddress(user32Dll, "GetWindowDC");
+ if (!sGetWindowDC) {
+ return false;
+ }
+ sFillRect = (FillRectProc)::GetProcAddress(user32Dll, "FillRect");
+ if (!sFillRect) {
+ return false;
+ }
+ sReleaseDC = (ReleaseDCProc)::GetProcAddress(user32Dll, "ReleaseDC");
+ if (!sReleaseDC) {
+ return false;
+ }
+ sLoadIconW = (LoadIconWProc)::GetProcAddress(user32Dll, "LoadIconW");
+ if (!sLoadIconW) {
+ return false;
+ }
+ sLoadCursorW = (LoadCursorWProc)::GetProcAddress(user32Dll, "LoadCursorW");
+ if (!sLoadCursorW) {
+ return false;
+ }
+ sMonitorFromWindow =
+ (MonitorFromWindowProc)::GetProcAddress(user32Dll, "MonitorFromWindow");
+ if (!sMonitorFromWindow) {
+ return false;
+ }
+ sGetMonitorInfoW =
+ (GetMonitorInfoWProc)::GetProcAddress(user32Dll, "GetMonitorInfoW");
+ if (!sGetMonitorInfoW) {
+ return false;
+ }
+ sSetWindowLongPtrW =
+ (SetWindowLongPtrWProc)::GetProcAddress(user32Dll, "SetWindowLongPtrW");
+ if (!sSetWindowLongPtrW) {
+ return false;
+ }
+ sStretchDIBits =
+ (StretchDIBitsProc)::GetProcAddress(gdi32Dll, "StretchDIBits");
+ if (!sStretchDIBits) {
+ return false;
+ }
+ sCreateSolidBrush =
+ (CreateSolidBrushProc)::GetProcAddress(gdi32Dll, "CreateSolidBrush");
+ if (!sCreateSolidBrush) {
+ return false;
+ }
+ sDeleteObject = (DeleteObjectProc)::GetProcAddress(gdi32Dll, "DeleteObject");
+ if (!sDeleteObject) {
+ return false;
+ }
+
+ return true;
+}
+
+// Strips "--", "-", and "/" from the front of the arg if one of those exists,
+// returning `arg + 2`, `arg + 1`, and `arg + 1` respectively. If none of these
+// prefixes are found, the argument is not a flag, and nullptr is returned.
+const char* NormalizeFlag(const char* arg) {
+ if (strstr(arg, "--") == arg) {
+ return arg + 2;
+ }
+
+ if (arg[0] == '-') {
+ return arg + 1;
+ }
+
+ if (arg[0] == '/') {
+ return arg + 1;
+ }
+
+ return nullptr;
+}
+
+static bool EnvHasValue(const char* name) {
+ const char* val = getenv(name);
+ return (val && *val);
+}
+
+// Ensures that we only see arguments in the command line which are acceptable.
+// This is based on manual inspection of the list of arguments listed in the MDN
+// page for Gecko/Firefox commandline options:
+// https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options
+// Broadly speaking, we want to reject any argument which causes us to show
+// something other than the default window at its normal size. Here is a non-
+// exhaustive list of command line options we want to *exclude*:
+//
+// -ProfileManager : This will display the profile manager window, which does
+// not match the skeleton UI at all.
+//
+// -CreateProfile : This will display a firefox window with the default
+// screen position and size, and not the position and size
+// which we have recorded in the registry.
+//
+// -P <profile> : This could cause us to display firefox with a position
+// and size of a different profile than that in which we
+// were previously running.
+//
+// -width, -height : This will cause the width and height values in the
+// registry to be incorrect.
+//
+// -kiosk : See above.
+//
+// -headless : This one should be rather obvious.
+//
+// -migration : This will start with the import wizard, which of course
+// does not match the skeleton UI.
+//
+// -private-window : This is tricky, but the colors of the main content area
+// make this not feel great with the white content of the
+// default skeleton UI.
+//
+// NOTE: we generally want to skew towards erroneous rejections of the command
+// line rather than erroneous approvals. The consequence of a bad rejection
+// is that we don't show the skeleton UI, which is business as usual. The
+// consequence of a bad approval is that we show it when we're not supposed to,
+// which is visually jarring and can also be unpredictable - there's no
+// guarantee that the code which handles the non-default window is set up to
+// properly handle the transition from the skeleton UI window.
+bool AreAllCmdlineArgumentsApproved(int argc, char** argv,
+ bool* explicitProfile) {
+ const char* approvedArgumentsArray[] = {
+ // These won't cause the browser to be visualy different in any way
+ "new-instance", "no-remote", "browser", "foreground", "setDefaultBrowser",
+ "attach-console", "wait-for-browser", "osint",
+
+ // These will cause the chrome to be a bit different or extra windows to
+ // be created, but overall the skeleton UI should still be broadly
+ // correct enough.
+ "new-tab", "new-window",
+
+ // To the extent possible, we want to ensure that existing tests cover
+ // the skeleton UI, so we need to allow marionette
+ "marionette",
+
+ // These will cause the content area to appear different, but won't
+ // meaningfully affect the chrome
+ "preferences", "search", "url",
+
+#ifndef MOZILLA_OFFICIAL
+ // On local builds, we want to allow -profile, because it's how `mach run`
+ // operates, and excluding that would create an unnecessary blind spot for
+ // Firefox devs.
+ "profile"
+#endif
+
+ // There are other arguments which are likely okay. However, they are
+ // not included here because this list is not intended to be
+ // exhaustive - it only intends to green-light some somewhat commonly
+ // used arguments. We want to err on the side of an unnecessary
+ // rejection of the command line.
+ };
+
+ int approvedArgumentsArraySize =
+ sizeof(approvedArgumentsArray) / sizeof(approvedArgumentsArray[0]);
+ Vector<const char*> approvedArguments;
+ if (!approvedArguments.reserve(approvedArgumentsArraySize)) {
+ return false;
+ }
+
+ for (int i = 0; i < approvedArgumentsArraySize; ++i) {
+ approvedArguments.infallibleAppend(approvedArgumentsArray[i]);
+ }
+
+#ifdef MOZILLA_OFFICIAL
+ int profileArgIndex = -1;
+ // If we're running mochitests or direct marionette tests, those specify a
+ // temporary profile, and we want to ensure that we get the added coverage
+ // from those.
+ for (int i = 1; i < argc; ++i) {
+ const char* flag = NormalizeFlag(argv[i]);
+ if (flag && !strcmp(flag, "marionette")) {
+ if (!approvedArguments.append("profile")) {
+ return false;
+ }
+ profileArgIndex = approvedArguments.length() - 1;
+
+ break;
+ }
+ }
+#else
+ int profileArgIndex = approvedArguments.length() - 1;
+#endif
+
+ for (int i = 1; i < argc; ++i) {
+ const char* flag = NormalizeFlag(argv[i]);
+ if (!flag) {
+ // If this is not a flag, then we interpret it as a URL, similar to
+ // BrowserContentHandler.jsm. Some command line options take additional
+ // arguments, which may or may not be URLs. We don't need to know this,
+ // because we don't need to parse them out; we just rely on the
+ // assumption that if arg X is actually a parameter for the preceding
+ // arg Y, then X must not look like a flag (starting with "--", "-",
+ // or "/").
+ //
+ // The most important thing here is the assumption that if something is
+ // going to meaningfully alter the appearance of the window itself, it
+ // must be a flag.
+ continue;
+ }
+
+ bool approved = false;
+ for (const char* approvedArg : approvedArguments) {
+ // We do a case-insensitive compare here with _stricmp. Even though some
+ // of these arguments are *not* read as case-insensitive, others *are*.
+ // Similar to the flag logic above, we don't really care about this
+ // distinction, because we don't need to parse the arguments - we just
+ // rely on the assumption that none of the listed flags in our
+ // approvedArguments are overloaded in such a way that a different
+ // casing would visually alter the firefox window.
+ if (!_stricmp(flag, approvedArg)) {
+ approved = true;
+
+ if (i == profileArgIndex) {
+ *explicitProfile = true;
+ }
+ break;
+ }
+ }
+
+ if (!approved) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool VerifyWindowDimensions(uint32_t windowWidth,
+ uint32_t windowHeight) {
+ return windowWidth <= kMaxWindowWidth && windowHeight <= kMaxWindowHeight;
+}
+
+void CreateAndStorePreXULSkeletonUI(HINSTANCE hInstance, int argc,
+ char** argv) {
+#ifdef MOZ_GECKO_PROFILER
+ const TimeStamp skeletonStart = TimeStamp::NowUnfuzzed();
+#endif
+
+ bool explicitProfile = false;
+ if (!AreAllCmdlineArgumentsApproved(argc, argv, &explicitProfile) ||
+ EnvHasValue("MOZ_SAFE_MODE_RESTART") || EnvHasValue("XRE_PROFILE_PATH") ||
+ EnvHasValue("MOZ_RESET_PROFILE_RESTART") || EnvHasValue("MOZ_HEADLESS")) {
+ sPreXULSkeletonUIDisallowed = true;
+ return;
+ }
+
+ HKEY regKey;
+ if (!IsWin10OrLater() || !OpenPreXULSkeletonUIRegKey(regKey)) {
+ return;
+ }
+ AutoCloseRegKey closeKey(regKey);
+
+ UniquePtr<wchar_t[]> binPath = GetBinaryPath();
+
+ DWORD dataLen = sizeof(uint32_t);
+ uint32_t enabled;
+ LSTATUS result = ::RegGetValueW(
+ regKey, nullptr,
+ GetRegValueName(binPath.get(), sEnabledRegSuffix).c_str(),
+ RRF_RT_REG_DWORD, nullptr, reinterpret_cast<PBYTE>(&enabled), &dataLen);
+ if (result != ERROR_SUCCESS || enabled == 0) {
+ return;
+ }
+ sPreXULSkeletonUIEnabled = true;
+
+ MOZ_ASSERT(!sAnimatedRects);
+ sAnimatedRects = new Vector<ColorRect>();
+
+ if (!LoadGdi32AndUser32Procedures()) {
+ return;
+ }
+
+ if (!TryGetSkeletonUILock()) {
+ printf_stderr("Error trying to get skeleton UI lock %lu\n", GetLastError());
+ return;
+ }
+
+ if (!explicitProfile && !CheckForStartWithLastProfile()) {
+ return;
+ }
+
+ WNDCLASSW wc;
+ wc.style = CS_DBLCLKS;
+ wc.lpfnWndProc = PreXULSkeletonUIProc;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hInstance = hInstance;
+ wc.hIcon = sLoadIconW(::GetModuleHandleW(nullptr), gStockApplicationIcon);
+ wc.hCursor = sLoadCursorW(hInstance, gIDCWait);
+ wc.hbrBackground = nullptr;
+ wc.lpszMenuName = nullptr;
+
+ // TODO: just ensure we disable this if we've overridden the window class
+ wc.lpszClassName = L"MozillaWindowClass";
+
+ if (!sRegisterClassW(&wc)) {
+ printf_stderr("RegisterClassW error %lu\n", GetLastError());
+ return;
+ }
+
+ uint32_t screenX;
+ result = ::RegGetValueW(
+ regKey, nullptr,
+ GetRegValueName(binPath.get(), sScreenXRegSuffix).c_str(),
+ RRF_RT_REG_DWORD, nullptr, reinterpret_cast<PBYTE>(&screenX), &dataLen);
+ if (result != ERROR_SUCCESS) {
+ printf_stderr("Error reading screenX %lu\n", GetLastError());
+ return;
+ }
+
+ uint32_t screenY;
+ result = ::RegGetValueW(
+ regKey, nullptr,
+ GetRegValueName(binPath.get(), sScreenYRegSuffix).c_str(),
+ RRF_RT_REG_DWORD, nullptr, reinterpret_cast<PBYTE>(&screenY), &dataLen);
+ if (result != ERROR_SUCCESS) {
+ printf_stderr("Error reading screenY %lu\n", GetLastError());
+ return;
+ }
+
+ uint32_t windowWidth;
+ result = ::RegGetValueW(
+ regKey, nullptr, GetRegValueName(binPath.get(), sWidthRegSuffix).c_str(),
+ RRF_RT_REG_DWORD, nullptr, reinterpret_cast<PBYTE>(&windowWidth),
+ &dataLen);
+ if (result != ERROR_SUCCESS) {
+ printf_stderr("Error reading width %lu\n", GetLastError());
+ return;
+ }
+
+ uint32_t windowHeight;
+ result = ::RegGetValueW(
+ regKey, nullptr, GetRegValueName(binPath.get(), sHeightRegSuffix).c_str(),
+ RRF_RT_REG_DWORD, nullptr, reinterpret_cast<PBYTE>(&windowHeight),
+ &dataLen);
+ if (result != ERROR_SUCCESS) {
+ printf_stderr("Error reading height %lu\n", GetLastError());
+ return;
+ }
+
+ uint32_t maximized;
+ result = ::RegGetValueW(
+ regKey, nullptr,
+ GetRegValueName(binPath.get(), sMaximizedRegSuffix).c_str(),
+ RRF_RT_REG_DWORD, nullptr, reinterpret_cast<PBYTE>(&maximized), &dataLen);
+ if (result != ERROR_SUCCESS) {
+ printf_stderr("Error reading maximized %lu\n", GetLastError());
+ return;
+ }
+ sMaximized = maximized != 0;
+
+ EnumSet<SkeletonUIFlag, uint32_t> flags;
+ uint32_t flagsUint;
+ result = ::RegGetValueW(
+ regKey, nullptr, GetRegValueName(binPath.get(), sFlagsRegSuffix).c_str(),
+ RRF_RT_REG_DWORD, nullptr, reinterpret_cast<PBYTE>(&flagsUint), &dataLen);
+ if (result != ERROR_SUCCESS) {
+ printf_stderr("Error reading flags %lu\n", GetLastError());
+ return;
+ }
+ flags.deserialize(flagsUint);
+
+ dataLen = sizeof(double);
+ result = ::RegGetValueW(
+ regKey, nullptr,
+ GetRegValueName(binPath.get(), sCssToDevPixelScalingRegSuffix).c_str(),
+ RRF_RT_REG_BINARY, nullptr,
+ reinterpret_cast<PBYTE>(&sCSSToDevPixelScaling), &dataLen);
+ if (result != ERROR_SUCCESS || dataLen != sizeof(double)) {
+ printf_stderr("Error reading cssToDevPixelScaling %lu\n", GetLastError());
+ return;
+ }
+
+ int showCmd = SW_SHOWNORMAL;
+ DWORD windowStyle = kPreXULSkeletonUIWindowStyle;
+ if (sMaximized) {
+ showCmd = SW_SHOWMAXIMIZED;
+ windowStyle |= WS_MAXIMIZE;
+ }
+
+ dataLen = 2 * sizeof(double);
+ auto buffer = MakeUniqueFallible<wchar_t[]>(2 * sizeof(double));
+ if (!buffer) {
+ return;
+ }
+ result = ::RegGetValueW(
+ regKey, nullptr,
+ GetRegValueName(binPath.get(), sUrlbarCSSRegSuffix).c_str(),
+ RRF_RT_REG_BINARY, nullptr, reinterpret_cast<PBYTE>(buffer.get()),
+ &dataLen);
+ if (result != ERROR_SUCCESS || dataLen % (2 * sizeof(double)) != 0) {
+ printf_stderr("Error reading urlbar %lu\n", GetLastError());
+ return;
+ }
+
+ double* asDoubles = reinterpret_cast<double*>(buffer.get());
+ CSSPixelSpan urlbar;
+ urlbar.start = *(asDoubles++);
+ urlbar.end = *(asDoubles++);
+
+ result = ::RegGetValueW(
+ regKey, nullptr,
+ GetRegValueName(binPath.get(), sSearchbarRegSuffix).c_str(),
+ RRF_RT_REG_BINARY, nullptr, reinterpret_cast<PBYTE>(buffer.get()),
+ &dataLen);
+ if (result != ERROR_SUCCESS || dataLen % (2 * sizeof(double)) != 0) {
+ printf_stderr("Error reading searchbar %lu\n", GetLastError());
+ return;
+ }
+
+ asDoubles = reinterpret_cast<double*>(buffer.get());
+ CSSPixelSpan searchbar;
+ searchbar.start = *(asDoubles++);
+ searchbar.end = *(asDoubles++);
+
+ result = ::RegQueryValueExW(
+ regKey, GetRegValueName(binPath.get(), sSpringsCSSRegSuffix).c_str(),
+ nullptr, nullptr, nullptr, &dataLen);
+ if (result != ERROR_SUCCESS || dataLen % (2 * sizeof(double)) != 0) {
+ printf_stderr("Error reading springsCSS %lu\n", GetLastError());
+ return;
+ }
+
+ buffer = MakeUniqueFallible<wchar_t[]>(dataLen);
+ if (!buffer) {
+ return;
+ }
+ result = ::RegGetValueW(
+ regKey, nullptr,
+ GetRegValueName(binPath.get(), sSpringsCSSRegSuffix).c_str(),
+ RRF_RT_REG_BINARY, nullptr, reinterpret_cast<PBYTE>(buffer.get()),
+ &dataLen);
+ if (result != ERROR_SUCCESS) {
+ printf_stderr("Error reading springsCSS %lu\n", GetLastError());
+ return;
+ }
+
+ Vector<CSSPixelSpan> springs;
+ asDoubles = reinterpret_cast<double*>(buffer.get());
+ for (int i = 0; i < dataLen / (2 * sizeof(double)); i++) {
+ CSSPixelSpan spring;
+ spring.start = *(asDoubles++);
+ spring.end = *(asDoubles++);
+ if (!springs.append(spring)) {
+ return;
+ }
+ }
+
+ dataLen = sizeof(uint32_t);
+ uint32_t theme;
+ result = ::RegGetValueW(
+ regKey, nullptr, GetRegValueName(binPath.get(), sThemeRegSuffix).c_str(),
+ RRF_RT_REG_DWORD, nullptr, reinterpret_cast<PBYTE>(&theme), &dataLen);
+ if (result != ERROR_SUCCESS) {
+ printf_stderr("Error reading theme %lu\n", GetLastError());
+ return;
+ }
+ ThemeMode themeMode = static_cast<ThemeMode>(theme);
+ if (themeMode == ThemeMode::Default) {
+ if (IsSystemDarkThemeEnabled() == true) {
+ themeMode = ThemeMode::Dark;
+ }
+ }
+ ThemeColors currentTheme = GetTheme(themeMode);
+
+ if (!VerifyWindowDimensions(windowWidth, windowHeight)) {
+ printf_stderr("Bad window dimensions for skeleton UI.");
+ return;
+ }
+
+ sPreXULSkeletonUIWindow =
+ sCreateWindowExW(kPreXULSkeletonUIWindowStyleEx, L"MozillaWindowClass",
+ L"", windowStyle, screenX, screenY, windowWidth,
+ windowHeight, nullptr, nullptr, hInstance, nullptr);
+ sShowWindow(sPreXULSkeletonUIWindow, showCmd);
+
+ sDpi = sGetDpiForWindow(sPreXULSkeletonUIWindow);
+ sNonClientHorizontalMargins =
+ sGetSystemMetricsForDpi(SM_CXFRAME, sDpi) +
+ sGetSystemMetricsForDpi(SM_CXPADDEDBORDER, sDpi);
+ sNonClientVerticalMargins = sGetSystemMetricsForDpi(SM_CYFRAME, sDpi) +
+ sGetSystemMetricsForDpi(SM_CXPADDEDBORDER, sDpi);
+
+ if (sMaximized) {
+ HMONITOR monitor =
+ sMonitorFromWindow(sPreXULSkeletonUIWindow, MONITOR_DEFAULTTONULL);
+ if (!monitor) {
+ // NOTE: we specifically don't clean up the window here. If we're unable
+ // to finish setting up the window how we want it, we still need to keep
+ // it around and consume it with the first real toplevel window we
+ // create, to avoid flickering.
+ return;
+ }
+ MONITORINFO mi = {sizeof(MONITORINFO)};
+ if (!sGetMonitorInfoW(monitor, &mi)) {
+ return;
+ }
+
+ sWindowWidth =
+ mi.rcWork.right - mi.rcWork.left + sNonClientHorizontalMargins * 2;
+ sWindowHeight =
+ mi.rcWork.bottom - mi.rcWork.top + sNonClientVerticalMargins * 2;
+ } else {
+ sWindowWidth = static_cast<int>(windowWidth);
+ sWindowHeight = static_cast<int>(windowHeight);
+ }
+
+ sSetWindowPos(sPreXULSkeletonUIWindow, 0, 0, 0, 0, 0,
+ SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE |
+ SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER);
+ DrawSkeletonUI(sPreXULSkeletonUIWindow, urlbar, searchbar, springs,
+ currentTheme, flags);
+ if (sAnimatedRects) {
+ sPreXULSKeletonUIAnimationThread = ::CreateThread(
+ nullptr, 256 * 1024, AnimateSkeletonUI, nullptr, 0, nullptr);
+ }
+
+ BASE_PROFILER_MARKER_UNTYPED(
+ "CreatePreXULSkeletonUI", OTHER,
+ MarkerTiming::IntervalUntilNowFrom(skeletonStart));
+}
+
+bool WasPreXULSkeletonUIMaximized() { return sMaximized; }
+
+HWND ConsumePreXULSkeletonUIHandle() {
+ // NOTE: we need to make sure that everything that runs here is a no-op if
+ // it failed to be set, which is a possibility. If anything fails to be set
+ // we don't want to clean everything up right away, because if we have a
+ // blank window up, we want that to stick around and get consumed by nsWindow
+ // as normal, otherwise the window will flicker in and out, which we imagine
+ // is unpleasant.
+
+ // If we don't get 1 here, it means the thread is actually just sleeping, so
+ // we don't need to worry about giving out ownership of the window, because
+ // the thread will simply exit after its sleep. However, if it is 1, we need
+ // to wait for the thread to exit to be safe, as it could be doing anything.
+ if (InterlockedIncrement(&sAnimationControlFlag) == 1) {
+ ::WaitForSingleObject(sPreXULSKeletonUIAnimationThread, INFINITE);
+ }
+ ::CloseHandle(sPreXULSKeletonUIAnimationThread);
+ sPreXULSKeletonUIAnimationThread = nullptr;
+ HWND result = sPreXULSkeletonUIWindow;
+ sPreXULSkeletonUIWindow = nullptr;
+ free(sPixelBuffer);
+ sPixelBuffer = nullptr;
+ delete sAnimatedRects;
+ sAnimatedRects = nullptr;
+ return result;
+}
+
+void PersistPreXULSkeletonUIValues(const SkeletonUISettings& settings) {
+ if (!sPreXULSkeletonUIEnabled) {
+ return;
+ }
+
+ HKEY regKey;
+ if (!OpenPreXULSkeletonUIRegKey(regKey)) {
+ return;
+ }
+ AutoCloseRegKey closeKey(regKey);
+
+ UniquePtr<wchar_t[]> binPath = GetBinaryPath();
+
+ LSTATUS result;
+ result = ::RegSetValueExW(
+ regKey, GetRegValueName(binPath.get(), sScreenXRegSuffix).c_str(), 0,
+ REG_DWORD, reinterpret_cast<const BYTE*>(&settings.screenX),
+ sizeof(settings.screenX));
+ if (result != ERROR_SUCCESS) {
+ printf_stderr("Failed persisting screenX to Windows registry\n");
+ return;
+ }
+
+ result = ::RegSetValueExW(
+ regKey, GetRegValueName(binPath.get(), sScreenYRegSuffix).c_str(), 0,
+ REG_DWORD, reinterpret_cast<const BYTE*>(&settings.screenY),
+ sizeof(settings.screenY));
+ if (result != ERROR_SUCCESS) {
+ printf_stderr("Failed persisting screenY to Windows registry\n");
+ return;
+ }
+
+ result = ::RegSetValueExW(
+ regKey, GetRegValueName(binPath.get(), sWidthRegSuffix).c_str(), 0,
+ REG_DWORD, reinterpret_cast<const BYTE*>(&settings.width),
+ sizeof(settings.width));
+ if (result != ERROR_SUCCESS) {
+ printf_stderr("Failed persisting width to Windows registry\n");
+ return;
+ }
+
+ result = ::RegSetValueExW(
+ regKey, GetRegValueName(binPath.get(), sHeightRegSuffix).c_str(), 0,
+ REG_DWORD, reinterpret_cast<const BYTE*>(&settings.height),
+ sizeof(settings.height));
+ if (result != ERROR_SUCCESS) {
+ printf_stderr("Failed persisting height to Windows registry\n");
+ return;
+ }
+
+ DWORD maximizedDword = settings.maximized ? 1 : 0;
+ result = ::RegSetValueExW(
+ regKey, GetRegValueName(binPath.get(), sMaximizedRegSuffix).c_str(), 0,
+ REG_DWORD, reinterpret_cast<const BYTE*>(&maximizedDword),
+ sizeof(maximizedDword));
+ if (result != ERROR_SUCCESS) {
+ printf_stderr("Failed persisting maximized to Windows registry\n");
+ }
+
+ EnumSet<SkeletonUIFlag, uint32_t> flags;
+ if (settings.menubarShown) {
+ flags += SkeletonUIFlag::MenubarShown;
+ }
+ if (settings.bookmarksToolbarShown) {
+ flags += SkeletonUIFlag::BookmarksToolbarShown;
+ }
+ if (settings.rtlEnabled) {
+ flags += SkeletonUIFlag::RtlEnabled;
+ }
+ uint32_t flagsUint = flags.serialize();
+ result = ::RegSetValueExW(
+ regKey, GetRegValueName(binPath.get(), sFlagsRegSuffix).c_str(), 0,
+ REG_DWORD, reinterpret_cast<const BYTE*>(&flagsUint), sizeof(flagsUint));
+ if (result != ERROR_SUCCESS) {
+ printf_stderr("Failed persisting flags to Windows registry\n");
+ return;
+ }
+
+ result = ::RegSetValueExW(
+ regKey,
+ GetRegValueName(binPath.get(), sCssToDevPixelScalingRegSuffix).c_str(), 0,
+ REG_BINARY, reinterpret_cast<const BYTE*>(&settings.cssToDevPixelScaling),
+ sizeof(settings.cssToDevPixelScaling));
+ if (result != ERROR_SUCCESS) {
+ printf_stderr(
+ "Failed persisting cssToDevPixelScaling to Windows registry\n");
+ return;
+ }
+
+ double urlbar[2];
+ urlbar[0] = settings.urlbarSpan.start;
+ urlbar[1] = settings.urlbarSpan.end;
+ result = ::RegSetValueExW(
+ regKey, GetRegValueName(binPath.get(), sUrlbarCSSRegSuffix).c_str(), 0,
+ REG_BINARY, reinterpret_cast<const BYTE*>(urlbar), sizeof(urlbar));
+ if (result != ERROR_SUCCESS) {
+ printf_stderr("Failed persisting urlbar to Windows registry\n");
+ return;
+ }
+
+ double searchbar[2];
+ searchbar[0] = settings.searchbarSpan.start;
+ searchbar[1] = settings.searchbarSpan.end;
+ result = ::RegSetValueExW(
+ regKey, GetRegValueName(binPath.get(), sSearchbarRegSuffix).c_str(), 0,
+ REG_BINARY, reinterpret_cast<const BYTE*>(searchbar), sizeof(searchbar));
+ if (result != ERROR_SUCCESS) {
+ printf_stderr("Failed persisting searchbar to Windows registry\n");
+ return;
+ }
+
+ Vector<double> springValues;
+ if (!springValues.reserve(settings.springs.length() * 2)) {
+ return;
+ }
+
+ for (auto spring : settings.springs) {
+ springValues.infallibleAppend(spring.start);
+ springValues.infallibleAppend(spring.end);
+ }
+
+ result = ::RegSetValueExW(
+ regKey, GetRegValueName(binPath.get(), sSpringsCSSRegSuffix).c_str(), 0,
+ REG_BINARY, reinterpret_cast<const BYTE*>(springValues.begin()),
+ springValues.length() * sizeof(double));
+ if (result != ERROR_SUCCESS) {
+ printf_stderr("Failed persisting springsCSS to Windows registry\n");
+ return;
+ }
+}
+
+MFBT_API bool GetPreXULSkeletonUIEnabled() { return sPreXULSkeletonUIEnabled; }
+
+MFBT_API void SetPreXULSkeletonUIEnabledIfAllowed(bool value) {
+ // If the pre-XUL skeleton UI was disallowed for some reason, we just want to
+ // ignore changes to the registry. An example of how things could be bad if
+ // we didn't: someone running firefox with the -profile argument could
+ // turn the skeleton UI on or off for the default profile. Turning it off
+ // maybe isn't so bad (though it's likely still incorrect), but turning it
+ // on could be bad if the user had specifically disabled it for a profile for
+ // some reason. Ultimately there's no correct decision here, and the
+ // messiness of this is just a consequence of sharing the registry values
+ // across profiles. However, whatever ill effects we observe should be
+ // correct themselves after one session.
+ if (sPreXULSkeletonUIDisallowed) {
+ return;
+ }
+
+ HKEY regKey;
+ if (!OpenPreXULSkeletonUIRegKey(regKey)) {
+ return;
+ }
+ AutoCloseRegKey closeKey(regKey);
+
+ UniquePtr<wchar_t[]> binPath = GetBinaryPath();
+ DWORD enabled = value;
+ LSTATUS result = ::RegSetValueExW(
+ regKey, GetRegValueName(binPath.get(), sEnabledRegSuffix).c_str(), 0,
+ REG_DWORD, reinterpret_cast<PBYTE>(&enabled), sizeof(enabled));
+ if (result != ERROR_SUCCESS) {
+ printf_stderr("Failed persisting enabled to Windows registry\n");
+ return;
+ }
+
+ if (!sPreXULSkeletonUIEnabled && value) {
+ // We specifically don't care if we fail to get this lock. We just want to
+ // do our best effort to lock it so that future instances don't create
+ // skeleton UIs while we're still running, since they will immediately exit
+ // and tell us to open a new window.
+ Unused << TryGetSkeletonUILock();
+ }
+
+ sPreXULSkeletonUIEnabled = value;
+}
+
+MFBT_API void SetPreXULSkeletonUIThemeId(ThemeMode theme) {
+ if (theme == sTheme) {
+ return;
+ }
+
+ HKEY regKey;
+ if (!OpenPreXULSkeletonUIRegKey(regKey)) {
+ return;
+ }
+ AutoCloseRegKey closeKey(regKey);
+
+ UniquePtr<wchar_t[]> binPath = GetBinaryPath();
+ uint32_t themeId = (uint32_t)theme;
+ LSTATUS result;
+ result = ::RegSetValueExW(
+ regKey, GetRegValueName(binPath.get(), sThemeRegSuffix).c_str(), 0,
+ REG_DWORD, reinterpret_cast<PBYTE>(&themeId), sizeof(themeId));
+ if (result != ERROR_SUCCESS) {
+ printf_stderr("Failed persisting theme to Windows registry\n");
+ sTheme = ThemeMode::Invalid;
+ return;
+ }
+ sTheme = static_cast<ThemeMode>(themeId);
+}
+
+MFBT_API void PollPreXULSkeletonUIEvents() {
+ if (sPreXULSkeletonUIEnabled && sPreXULSkeletonUIWindow) {
+ MSG outMsg = {};
+ PeekMessageW(&outMsg, sPreXULSkeletonUIWindow, 0, 0, 0);
+ }
+}
+
+} // namespace mozilla
diff --git a/mozglue/misc/PreXULSkeletonUI.h b/mozglue/misc/PreXULSkeletonUI.h
new file mode 100644
index 0000000000..6076dff48b
--- /dev/null
+++ b/mozglue/misc/PreXULSkeletonUI.h
@@ -0,0 +1,82 @@
+/* -*- 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 PreXULSkeletonUI_h_
+#define PreXULSkeletonUI_h_
+
+#include <windows.h>
+#include "mozilla/EnumSet.h"
+#include "mozilla/Types.h"
+#include "mozilla/Vector.h"
+
+namespace mozilla {
+
+// These unfortunately need to be kept in sync with the window style and
+// extended window style computations in nsWindow. Luckily those styles seem
+// to not vary based off of any user settings for the initial toplevel window,
+// so we're safe here for now.
+static const DWORD kPreXULSkeletonUIWindowStyle =
+ WS_CLIPCHILDREN | WS_DLGFRAME | WS_BORDER | WS_MAXIMIZEBOX |
+ WS_MINIMIZEBOX | WS_SIZEBOX | WS_SYSMENU;
+static const DWORD kPreXULSkeletonUIWindowStyleEx = WS_EX_WINDOWEDGE;
+
+struct CSSPixelSpan {
+ double start;
+ double end;
+};
+
+struct DevPixelSpan {
+ int start;
+ int end;
+};
+
+struct SkeletonUISettings {
+ uint32_t screenX;
+ uint32_t screenY;
+ uint32_t width;
+ uint32_t height;
+ CSSPixelSpan urlbarSpan;
+ CSSPixelSpan searchbarSpan;
+ double cssToDevPixelScaling;
+ Vector<CSSPixelSpan> springs;
+ bool maximized;
+ bool menubarShown;
+ bool bookmarksToolbarShown;
+ bool rtlEnabled;
+};
+
+enum class ThemeMode : uint32_t { Invalid, Default, Dark, Light };
+
+enum class SkeletonUIFlag : uint8_t {
+ MenubarShown,
+ BookmarksToolbarShown,
+ RtlEnabled,
+};
+
+struct ThemeColors {
+ uint32_t backgroundColor;
+ uint32_t toolbarForegroundColor;
+ uint32_t tabBarColor;
+ uint32_t chromeContentDividerColor;
+ uint32_t tabLineColor;
+ uint32_t urlbarColor;
+ uint32_t urlbarBorderColor;
+ uint32_t animationColor;
+};
+
+MFBT_API void CreateAndStorePreXULSkeletonUI(HINSTANCE hInstance, int argc,
+ char** argv);
+MFBT_API HWND ConsumePreXULSkeletonUIHandle();
+MFBT_API bool WasPreXULSkeletonUIMaximized();
+MFBT_API void PersistPreXULSkeletonUIValues(const SkeletonUISettings& settings);
+MFBT_API bool GetPreXULSkeletonUIEnabled();
+MFBT_API void SetPreXULSkeletonUIEnabledIfAllowed(bool value);
+MFBT_API void PollPreXULSkeletonUIEvents();
+MFBT_API void SetPreXULSkeletonUIThemeId(ThemeMode theme);
+
+} // namespace mozilla
+
+#endif
diff --git a/mozglue/misc/Printf.cpp b/mozglue/misc/Printf.cpp
new file mode 100644
index 0000000000..4d3306e513
--- /dev/null
+++ b/mozglue/misc/Printf.cpp
@@ -0,0 +1,952 @@
+/* -*- 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/. */
+
+/*
+ * Portable safe sprintf code.
+ *
+ * Author: Kipp E.B. Hickman
+ */
+
+#include "mozilla/AllocPolicy.h"
+#include "mozilla/Likely.h"
+#include "mozilla/Printf.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/Vector.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if defined(XP_WIN)
+# include <windows.h>
+#endif
+
+/*
+ * Note: on some platforms va_list is defined as an array,
+ * and requires array notation.
+ */
+#ifdef HAVE_VA_COPY
+# define VARARGS_ASSIGN(foo, bar) VA_COPY(foo, bar)
+#elif defined(HAVE_VA_LIST_AS_ARRAY)
+# define VARARGS_ASSIGN(foo, bar) foo[0] = bar[0]
+#else
+# define VARARGS_ASSIGN(foo, bar) (foo) = (bar)
+#endif
+
+/*
+ * Numbered Argument State
+ */
+struct NumArgState {
+ int type; // type of the current ap
+ va_list ap; // point to the corresponding position on ap
+};
+
+typedef mozilla::Vector<NumArgState, 20, mozilla::MallocAllocPolicy>
+ NumArgStateVector;
+
+#define TYPE_SHORT 0
+#define TYPE_USHORT 1
+#define TYPE_INTN 2
+#define TYPE_UINTN 3
+#define TYPE_LONG 4
+#define TYPE_ULONG 5
+#define TYPE_LONGLONG 6
+#define TYPE_ULONGLONG 7
+#define TYPE_STRING 8
+#define TYPE_DOUBLE 9
+#define TYPE_INTSTR 10
+#define TYPE_POINTER 11
+#if defined(XP_WIN)
+# define TYPE_WSTRING 12
+#endif
+#define TYPE_UNKNOWN 20
+
+#define FLAG_LEFT 0x1
+#define FLAG_SIGNED 0x2
+#define FLAG_SPACED 0x4
+#define FLAG_ZEROS 0x8
+#define FLAG_NEG 0x10
+
+static const char hex[] = "0123456789abcdef";
+static const char HEX[] = "0123456789ABCDEF";
+
+// Fill into the buffer using the data in src
+bool mozilla::PrintfTarget::fill2(const char* src, int srclen, int width,
+ int flags) {
+ char space = ' ';
+
+ width -= srclen;
+ if (width > 0 && (flags & FLAG_LEFT) == 0) { // Right adjusting
+ if (flags & FLAG_ZEROS) space = '0';
+ while (--width >= 0) {
+ if (!emit(&space, 1)) return false;
+ }
+ }
+
+ // Copy out the source data
+ if (!emit(src, srclen)) return false;
+
+ if (width > 0 && (flags & FLAG_LEFT) != 0) { // Left adjusting
+ while (--width >= 0) {
+ if (!emit(&space, 1)) return false;
+ }
+ }
+ return true;
+}
+
+/*
+ * Fill a number. The order is: optional-sign zero-filling conversion-digits
+ */
+bool mozilla::PrintfTarget::fill_n(const char* src, int srclen, int width,
+ int prec, int type, int flags) {
+ int zerowidth = 0;
+ int precwidth = 0;
+ int signwidth = 0;
+ int leftspaces = 0;
+ int rightspaces = 0;
+ int cvtwidth;
+ char sign;
+
+ if ((type & 1) == 0) {
+ if (flags & FLAG_NEG) {
+ sign = '-';
+ signwidth = 1;
+ } else if (flags & FLAG_SIGNED) {
+ sign = '+';
+ signwidth = 1;
+ } else if (flags & FLAG_SPACED) {
+ sign = ' ';
+ signwidth = 1;
+ }
+ }
+ cvtwidth = signwidth + srclen;
+
+ if (prec > 0) {
+ if (prec > srclen) {
+ precwidth = prec - srclen; // Need zero filling
+ cvtwidth += precwidth;
+ }
+ }
+
+ if ((flags & FLAG_ZEROS) && (prec < 0)) {
+ if (width > cvtwidth) {
+ zerowidth = width - cvtwidth; // Zero filling
+ cvtwidth += zerowidth;
+ }
+ }
+
+ if (flags & FLAG_LEFT) {
+ if (width > cvtwidth) {
+ // Space filling on the right (i.e. left adjusting)
+ rightspaces = width - cvtwidth;
+ }
+ } else {
+ if (width > cvtwidth) {
+ // Space filling on the left (i.e. right adjusting)
+ leftspaces = width - cvtwidth;
+ }
+ }
+ while (--leftspaces >= 0) {
+ if (!emit(" ", 1)) return false;
+ }
+ if (signwidth) {
+ if (!emit(&sign, 1)) return false;
+ }
+ while (--precwidth >= 0) {
+ if (!emit("0", 1)) return false;
+ }
+ while (--zerowidth >= 0) {
+ if (!emit("0", 1)) return false;
+ }
+ if (!emit(src, uint32_t(srclen))) return false;
+ while (--rightspaces >= 0) {
+ if (!emit(" ", 1)) return false;
+ }
+ return true;
+}
+
+// All that the cvt_* functions care about as far as the TYPE_* constants is
+// that the low bit is set to indicate unsigned, or unset to indicate signed.
+// So we don't try to hard to ensure that the passed TYPE_* constant lines
+// up with the actual size of the number being printed here. The main printf
+// code, below, does have to care so that the correct bits are extracted from
+// the varargs list.
+bool mozilla::PrintfTarget::appendIntDec(int32_t num) {
+ int flags = 0;
+ long n = num;
+ if (n < 0) {
+ n = -n;
+ flags |= FLAG_NEG;
+ }
+ return cvt_l(n, -1, -1, 10, TYPE_INTN, flags, hex);
+}
+
+bool mozilla::PrintfTarget::appendIntDec(uint32_t num) {
+ return cvt_l(num, -1, -1, 10, TYPE_UINTN, 0, hex);
+}
+
+bool mozilla::PrintfTarget::appendIntOct(uint32_t num) {
+ return cvt_l(num, -1, -1, 8, TYPE_UINTN, 0, hex);
+}
+
+bool mozilla::PrintfTarget::appendIntHex(uint32_t num) {
+ return cvt_l(num, -1, -1, 16, TYPE_UINTN, 0, hex);
+}
+
+bool mozilla::PrintfTarget::appendIntDec(int64_t num) {
+ int flags = 0;
+ if (num < 0) {
+ num = -num;
+ flags |= FLAG_NEG;
+ }
+ return cvt_ll(num, -1, -1, 10, TYPE_INTN, flags, hex);
+}
+
+bool mozilla::PrintfTarget::appendIntDec(uint64_t num) {
+ return cvt_ll(num, -1, -1, 10, TYPE_UINTN, 0, hex);
+}
+
+bool mozilla::PrintfTarget::appendIntOct(uint64_t num) {
+ return cvt_ll(num, -1, -1, 8, TYPE_UINTN, 0, hex);
+}
+
+bool mozilla::PrintfTarget::appendIntHex(uint64_t num) {
+ return cvt_ll(num, -1, -1, 16, TYPE_UINTN, 0, hex);
+}
+
+/* Convert a long into its printable form. */
+bool mozilla::PrintfTarget::cvt_l(long num, int width, int prec, int radix,
+ int type, int flags, const char* hexp) {
+ char cvtbuf[100];
+ char* cvt;
+ int digits;
+
+ // according to the man page this needs to happen
+ if ((prec == 0) && (num == 0)) return true;
+
+ // Converting decimal is a little tricky. In the unsigned case we
+ // need to stop when we hit 10 digits. In the signed case, we can
+ // stop when the number is zero.
+ cvt = cvtbuf + sizeof(cvtbuf);
+ digits = 0;
+ while (num) {
+ int digit = (((unsigned long)num) % radix) & 0xF;
+ *--cvt = hexp[digit];
+ digits++;
+ num = (long)(((unsigned long)num) / radix);
+ }
+ if (digits == 0) {
+ *--cvt = '0';
+ digits++;
+ }
+
+ // Now that we have the number converted without its sign, deal with
+ // the sign and zero padding.
+ return fill_n(cvt, digits, width, prec, type, flags);
+}
+
+/* Convert a 64-bit integer into its printable form. */
+bool mozilla::PrintfTarget::cvt_ll(int64_t num, int width, int prec, int radix,
+ int type, int flags, const char* hexp) {
+ // According to the man page, this needs to happen.
+ if (prec == 0 && num == 0) return true;
+
+ // Converting decimal is a little tricky. In the unsigned case we
+ // need to stop when we hit 10 digits. In the signed case, we can
+ // stop when the number is zero.
+ int64_t rad = int64_t(radix);
+ char cvtbuf[100];
+ char* cvt = cvtbuf + sizeof(cvtbuf);
+ int digits = 0;
+ while (num != 0) {
+ int64_t quot = uint64_t(num) / rad;
+ int64_t rem = uint64_t(num) % rad;
+ int32_t digit = int32_t(rem);
+ *--cvt = hexp[digit & 0xf];
+ digits++;
+ num = quot;
+ }
+ if (digits == 0) {
+ *--cvt = '0';
+ digits++;
+ }
+
+ // Now that we have the number converted without its sign, deal with
+ // the sign and zero padding.
+ return fill_n(cvt, digits, width, prec, type, flags);
+}
+
+/*
+ * Convert a double precision floating point number into its printable
+ * form.
+ */
+bool mozilla::PrintfTarget::cvt_f(double d, const char* fmt0,
+ const char* fmt1) {
+ char fin[20];
+ // The size is chosen such that we can print DBL_MAX. See bug#1350097.
+ char fout[320];
+ int amount = fmt1 - fmt0;
+
+ MOZ_ASSERT((amount > 0) && (amount < (int)sizeof(fin)));
+ if (amount >= (int)sizeof(fin)) {
+ // Totally bogus % command to sprintf. Just ignore it
+ return true;
+ }
+ memcpy(fin, fmt0, (size_t)amount);
+ fin[amount] = 0;
+
+ // Convert floating point using the native snprintf code
+#ifdef DEBUG
+ {
+ const char* p = fin;
+ while (*p) {
+ MOZ_ASSERT(*p != 'L');
+ p++;
+ }
+ }
+#endif
+ size_t len = SprintfLiteral(fout, fin, d);
+ // Note that SprintfLiteral will always write a \0 at the end, so a
+ // "<=" check here would be incorrect -- the buffer size passed to
+ // snprintf includes the trailing \0, but the returned length does
+ // not.
+ if (MOZ_LIKELY(len < sizeof(fout))) {
+ return emit(fout, len);
+ }
+
+ // Maybe the user used "%500.500f" or something like that.
+ size_t buf_size = len + 1;
+ UniqueFreePtr<char> buf((char*)malloc(buf_size));
+ if (!buf) {
+ return false;
+ }
+ len = snprintf(buf.get(), buf_size, fin, d);
+ // If this assert fails, then SprintfLiteral has a bug -- and in
+ // this case we would like to learn of it, which is why there is a
+ // release assert.
+ MOZ_RELEASE_ASSERT(len < buf_size);
+
+ return emit(buf.get(), len);
+}
+
+/*
+ * Convert a string into its printable form. "width" is the output
+ * width. "prec" is the maximum number of characters of "s" to output,
+ * where -1 means until NUL.
+ */
+bool mozilla::PrintfTarget::cvt_s(const char* s, int width, int prec,
+ int flags) {
+ if (prec == 0) return true;
+ if (!s) s = "(null)";
+
+ // Limit string length by precision value
+ int slen = int(strlen(s));
+ if (0 < prec && prec < slen) slen = prec;
+
+ // and away we go
+ return fill2(s, slen, width, flags);
+}
+
+/*
+ * BuildArgArray stands for Numbered Argument list Sprintf
+ * for example,
+ * fmp = "%4$i, %2$d, %3s, %1d";
+ * the number must start from 1, and no gap among them
+ */
+static bool BuildArgArray(const char* fmt, va_list ap, NumArgStateVector& nas) {
+ size_t number = 0, cn = 0, i;
+ const char* p;
+ char c;
+
+ // First pass:
+ // Detemine how many legal % I have got, then allocate space.
+
+ p = fmt;
+ i = 0;
+ while ((c = *p++) != 0) {
+ if (c != '%') continue;
+ if ((c = *p++) == '%') // skip %% case
+ continue;
+
+ while (c != 0) {
+ if (c > '9' || c < '0') {
+ if (c == '$') { // numbered argument case
+ if (i > 0) MOZ_CRASH("Bad format string");
+ number++;
+ } else { // non-numbered argument case
+ if (number > 0) MOZ_CRASH("Bad format string");
+ i = 1;
+ }
+ break;
+ }
+
+ c = *p++;
+ }
+ }
+
+ if (number == 0) return true;
+
+ // Only allow a limited number of arguments.
+ MOZ_RELEASE_ASSERT(number <= 20);
+
+ if (!nas.growByUninitialized(number)) return false;
+
+ for (i = 0; i < number; i++) nas[i].type = TYPE_UNKNOWN;
+
+ // Second pass:
+ // Set nas[].type.
+
+ p = fmt;
+ while ((c = *p++) != 0) {
+ if (c != '%') continue;
+ c = *p++;
+ if (c == '%') continue;
+
+ cn = 0;
+ while (c && c != '$') { // should improve error check later
+ cn = cn * 10 + c - '0';
+ c = *p++;
+ }
+
+ if (!c || cn < 1 || cn > number) MOZ_CRASH("Bad format string");
+
+ // nas[cn] starts from 0, and make sure nas[cn].type is not assigned.
+ cn--;
+ if (nas[cn].type != TYPE_UNKNOWN) continue;
+
+ c = *p++;
+
+ // flags
+ while ((c == '-') || (c == '+') || (c == ' ') || (c == '0')) {
+ c = *p++;
+ }
+
+ // width
+ if (c == '*') {
+ // not supported feature, for the argument is not numbered
+ MOZ_CRASH("Bad format string");
+ }
+
+ while ((c >= '0') && (c <= '9')) {
+ c = *p++;
+ }
+
+ // precision
+ if (c == '.') {
+ c = *p++;
+ if (c == '*') {
+ // not supported feature, for the argument is not numbered
+ MOZ_CRASH("Bad format string");
+ }
+
+ while ((c >= '0') && (c <= '9')) {
+ c = *p++;
+ }
+ }
+
+ // size
+ nas[cn].type = TYPE_INTN;
+ if (c == 'h') {
+ nas[cn].type = TYPE_SHORT;
+ c = *p++;
+ } else if (c == 'L') {
+ nas[cn].type = TYPE_LONGLONG;
+ c = *p++;
+ } else if (c == 'l') {
+ nas[cn].type = TYPE_LONG;
+ c = *p++;
+ if (c == 'l') {
+ nas[cn].type = TYPE_LONGLONG;
+ c = *p++;
+ }
+ } else if (c == 'z' || c == 'I') {
+ static_assert(sizeof(size_t) == sizeof(int) ||
+ sizeof(size_t) == sizeof(long) ||
+ sizeof(size_t) == sizeof(long long),
+ "size_t is not one of the expected sizes");
+ nas[cn].type = sizeof(size_t) == sizeof(int) ? TYPE_INTN
+ : sizeof(size_t) == sizeof(long) ? TYPE_LONG
+ : TYPE_LONGLONG;
+ c = *p++;
+ }
+
+ // format
+ switch (c) {
+ case 'd':
+ case 'c':
+ case 'i':
+ break;
+
+ case 'o':
+ case 'u':
+ case 'x':
+ case 'X':
+ // Mark as unsigned type.
+ nas[cn].type |= 1;
+ break;
+
+ case 'e':
+ case 'f':
+ case 'g':
+ nas[cn].type = TYPE_DOUBLE;
+ break;
+
+ case 'p':
+ nas[cn].type = TYPE_POINTER;
+ break;
+
+ case 'S':
+#if defined(XP_WIN)
+ nas[cn].type = TYPE_WSTRING;
+#else
+ MOZ_ASSERT(0);
+ nas[cn].type = TYPE_UNKNOWN;
+#endif
+ break;
+
+ case 's':
+#if defined(XP_WIN)
+ if (nas[cn].type == TYPE_LONG) {
+ nas[cn].type = TYPE_WSTRING;
+ break;
+ }
+#endif
+ // Other type sizes are not supported here.
+ MOZ_ASSERT(nas[cn].type == TYPE_INTN);
+ nas[cn].type = TYPE_STRING;
+ break;
+
+ case 'n':
+ nas[cn].type = TYPE_INTSTR;
+ break;
+
+ default:
+ MOZ_ASSERT(0);
+ nas[cn].type = TYPE_UNKNOWN;
+ break;
+ }
+
+ // get a legal para.
+ if (nas[cn].type == TYPE_UNKNOWN) MOZ_CRASH("Bad format string");
+ }
+
+ // Third pass:
+ // Fill nas[].ap.
+
+ cn = 0;
+ while (cn < number) {
+ // A TYPE_UNKNOWN here means that the format asked for a
+ // positional argument without specifying the meaning of some
+ // earlier argument.
+ MOZ_ASSERT(nas[cn].type != TYPE_UNKNOWN);
+
+ VARARGS_ASSIGN(nas[cn].ap, ap);
+
+ switch (nas[cn].type) {
+ case TYPE_SHORT:
+ case TYPE_USHORT:
+ case TYPE_INTN:
+ case TYPE_UINTN:
+ (void)va_arg(ap, int);
+ break;
+ case TYPE_LONG:
+ (void)va_arg(ap, long);
+ break;
+ case TYPE_ULONG:
+ (void)va_arg(ap, unsigned long);
+ break;
+ case TYPE_LONGLONG:
+ (void)va_arg(ap, long long);
+ break;
+ case TYPE_ULONGLONG:
+ (void)va_arg(ap, unsigned long long);
+ break;
+ case TYPE_STRING:
+ (void)va_arg(ap, char*);
+ break;
+ case TYPE_INTSTR:
+ (void)va_arg(ap, int*);
+ break;
+ case TYPE_DOUBLE:
+ (void)va_arg(ap, double);
+ break;
+ case TYPE_POINTER:
+ (void)va_arg(ap, void*);
+ break;
+#if defined(XP_WIN)
+ case TYPE_WSTRING:
+ (void)va_arg(ap, wchar_t*);
+ break;
+#endif
+
+ default:
+ MOZ_CRASH();
+ }
+
+ cn++;
+ }
+
+ return true;
+}
+
+mozilla::PrintfTarget::PrintfTarget() : mEmitted(0) {}
+
+bool mozilla::PrintfTarget::vprint(const char* fmt, va_list ap) {
+ char c;
+ int flags, width, prec, radix, type;
+ union {
+ char ch;
+ int i;
+ long l;
+ long long ll;
+ double d;
+ const char* s;
+ int* ip;
+ void* p;
+#if defined(XP_WIN)
+ const wchar_t* ws;
+#endif
+ } u;
+ const char* fmt0;
+ const char* hexp;
+ int i;
+ char pattern[20];
+ const char* dolPt = nullptr; // in "%4$.2f", dolPt will point to '.'
+
+ // Build an argument array, IF the fmt is numbered argument
+ // list style, to contain the Numbered Argument list pointers.
+
+ NumArgStateVector nas;
+ if (!BuildArgArray(fmt, ap, nas)) {
+ // the fmt contains error Numbered Argument format, jliu@netscape.com
+ MOZ_CRASH("Bad format string");
+ }
+
+ while ((c = *fmt++) != 0) {
+ if (c != '%') {
+ if (!emit(fmt - 1, 1)) return false;
+
+ continue;
+ }
+ fmt0 = fmt - 1;
+
+ // Gobble up the % format string. Hopefully we have handled all
+ // of the strange cases!
+ flags = 0;
+ c = *fmt++;
+ if (c == '%') {
+ // quoting a % with %%
+ if (!emit(fmt - 1, 1)) return false;
+
+ continue;
+ }
+
+ if (!nas.empty()) {
+ // the fmt contains the Numbered Arguments feature
+ i = 0;
+ while (c && c != '$') { // should improve error check later
+ i = (i * 10) + (c - '0');
+ c = *fmt++;
+ }
+
+ if (nas[i - 1].type == TYPE_UNKNOWN) MOZ_CRASH("Bad format string");
+
+ ap = nas[i - 1].ap;
+ dolPt = fmt;
+ c = *fmt++;
+ }
+
+ // Examine optional flags. Note that we do not implement the
+ // '#' flag of sprintf(). The ANSI C spec. of the '#' flag is
+ // somewhat ambiguous and not ideal, which is perhaps why
+ // the various sprintf() implementations are inconsistent
+ // on this feature.
+ while ((c == '-') || (c == '+') || (c == ' ') || (c == '0')) {
+ if (c == '-') flags |= FLAG_LEFT;
+ if (c == '+') flags |= FLAG_SIGNED;
+ if (c == ' ') flags |= FLAG_SPACED;
+ if (c == '0') flags |= FLAG_ZEROS;
+ c = *fmt++;
+ }
+ if (flags & FLAG_SIGNED) flags &= ~FLAG_SPACED;
+ if (flags & FLAG_LEFT) flags &= ~FLAG_ZEROS;
+
+ // width
+ if (c == '*') {
+ c = *fmt++;
+ width = va_arg(ap, int);
+ if (width < 0) {
+ width = -width;
+ flags |= FLAG_LEFT;
+ flags &= ~FLAG_ZEROS;
+ }
+ } else {
+ width = 0;
+ while ((c >= '0') && (c <= '9')) {
+ width = (width * 10) + (c - '0');
+ c = *fmt++;
+ }
+ }
+
+ // precision
+ prec = -1;
+ if (c == '.') {
+ c = *fmt++;
+ if (c == '*') {
+ c = *fmt++;
+ prec = va_arg(ap, int);
+ } else {
+ prec = 0;
+ while ((c >= '0') && (c <= '9')) {
+ prec = (prec * 10) + (c - '0');
+ c = *fmt++;
+ }
+ }
+ }
+
+ // size
+ type = TYPE_INTN;
+ if (c == 'h') {
+ type = TYPE_SHORT;
+ c = *fmt++;
+ } else if (c == 'L') {
+ type = TYPE_LONGLONG;
+ c = *fmt++;
+ } else if (c == 'l') {
+ type = TYPE_LONG;
+ c = *fmt++;
+ if (c == 'l') {
+ type = TYPE_LONGLONG;
+ c = *fmt++;
+ }
+ } else if (c == 'z' || c == 'I') {
+ static_assert(sizeof(size_t) == sizeof(int) ||
+ sizeof(size_t) == sizeof(long) ||
+ sizeof(size_t) == sizeof(long long),
+ "size_t is not one of the expected sizes");
+ type = sizeof(size_t) == sizeof(int) ? TYPE_INTN
+ : sizeof(size_t) == sizeof(long) ? TYPE_LONG
+ : TYPE_LONGLONG;
+ c = *fmt++;
+ }
+
+ // format
+ hexp = hex;
+ switch (c) {
+ case 'd':
+ case 'i': // decimal/integer
+ radix = 10;
+ goto fetch_and_convert;
+
+ case 'o': // octal
+ radix = 8;
+ type |= 1;
+ goto fetch_and_convert;
+
+ case 'u': // unsigned decimal
+ radix = 10;
+ type |= 1;
+ goto fetch_and_convert;
+
+ case 'x': // unsigned hex
+ radix = 16;
+ type |= 1;
+ goto fetch_and_convert;
+
+ case 'X': // unsigned HEX
+ radix = 16;
+ hexp = HEX;
+ type |= 1;
+ goto fetch_and_convert;
+
+ fetch_and_convert:
+ switch (type) {
+ case TYPE_SHORT:
+ u.l = va_arg(ap, int);
+ if (u.l < 0) {
+ u.l = -u.l;
+ flags |= FLAG_NEG;
+ }
+ goto do_long;
+ case TYPE_USHORT:
+ u.l = (unsigned short)va_arg(ap, unsigned int);
+ goto do_long;
+ case TYPE_INTN:
+ u.l = va_arg(ap, int);
+ if (u.l < 0) {
+ u.l = -u.l;
+ flags |= FLAG_NEG;
+ }
+ goto do_long;
+ case TYPE_UINTN:
+ u.l = (long)va_arg(ap, unsigned int);
+ goto do_long;
+
+ case TYPE_LONG:
+ u.l = va_arg(ap, long);
+ if (u.l < 0) {
+ u.l = -u.l;
+ flags |= FLAG_NEG;
+ }
+ goto do_long;
+ case TYPE_ULONG:
+ u.l = (long)va_arg(ap, unsigned long);
+ do_long:
+ if (!cvt_l(u.l, width, prec, radix, type, flags, hexp))
+ return false;
+
+ break;
+
+ case TYPE_LONGLONG:
+ u.ll = va_arg(ap, long long);
+ if (u.ll < 0) {
+ u.ll = -u.ll;
+ flags |= FLAG_NEG;
+ }
+ goto do_longlong;
+ case TYPE_POINTER:
+ u.ll = (uintptr_t)va_arg(ap, void*);
+ goto do_longlong;
+ case TYPE_ULONGLONG:
+ u.ll = va_arg(ap, unsigned long long);
+ do_longlong:
+ if (!cvt_ll(u.ll, width, prec, radix, type, flags, hexp))
+ return false;
+
+ break;
+ }
+ break;
+
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'g':
+ u.d = va_arg(ap, double);
+ if (!nas.empty()) {
+ i = fmt - dolPt;
+ if (i < int(sizeof(pattern))) {
+ pattern[0] = '%';
+ memcpy(&pattern[1], dolPt, size_t(i));
+ if (!cvt_f(u.d, pattern, &pattern[i + 1])) return false;
+ }
+ } else {
+ if (!cvt_f(u.d, fmt0, fmt)) return false;
+ }
+
+ break;
+
+ case 'c':
+ if ((flags & FLAG_LEFT) == 0) {
+ while (width-- > 1) {
+ if (!emit(" ", 1)) return false;
+ }
+ }
+ switch (type) {
+ case TYPE_SHORT:
+ case TYPE_INTN:
+ u.ch = va_arg(ap, int);
+ if (!emit(&u.ch, 1)) return false;
+ break;
+ }
+ if (flags & FLAG_LEFT) {
+ while (width-- > 1) {
+ if (!emit(" ", 1)) return false;
+ }
+ }
+ break;
+
+ case 'p':
+ type = TYPE_POINTER;
+ radix = 16;
+ goto fetch_and_convert;
+
+ case 's':
+ if (type == TYPE_INTN) {
+ u.s = va_arg(ap, const char*);
+ if (!cvt_s(u.s, width, prec, flags)) return false;
+ break;
+ }
+ MOZ_ASSERT(type == TYPE_LONG);
+ [[fallthrough]];
+ case 'S':
+#if defined(XP_WIN)
+ {
+ u.ws = va_arg(ap, const wchar_t*);
+
+ int rv = WideCharToMultiByte(CP_ACP, 0, u.ws, -1, NULL, 0, NULL, NULL);
+ if (rv == 0 && GetLastError() == ERROR_NO_UNICODE_TRANSLATION) {
+ if (!cvt_s("<unicode errors in string>", width, prec, flags)) {
+ return false;
+ }
+ } else {
+ if (rv == 0) {
+ rv = 1;
+ }
+ UniqueFreePtr<char[]> buf((char*)malloc(rv));
+ WideCharToMultiByte(CP_ACP, 0, u.ws, -1, buf.get(), rv, NULL, NULL);
+ buf[rv - 1] = '\0';
+
+ if (!cvt_s(buf.get(), width, prec, flags)) {
+ return false;
+ }
+ }
+ }
+#else
+ // Not supported here.
+ MOZ_ASSERT(0);
+#endif
+ break;
+
+ case 'n':
+ u.ip = va_arg(ap, int*);
+ if (u.ip) {
+ *u.ip = mEmitted;
+ }
+ break;
+
+ default:
+ // Not a % token after all... skip it
+ if (!emit("%", 1)) return false;
+ if (!emit(fmt - 1, 1)) return false;
+ }
+ }
+
+ return true;
+}
+
+/************************************************************************/
+
+bool mozilla::PrintfTarget::print(const char* format, ...) {
+ va_list ap;
+
+ va_start(ap, format);
+ bool result = vprint(format, ap);
+ va_end(ap);
+ return result;
+}
+
+#undef TYPE_SHORT
+#undef TYPE_USHORT
+#undef TYPE_INTN
+#undef TYPE_UINTN
+#undef TYPE_LONG
+#undef TYPE_ULONG
+#undef TYPE_LONGLONG
+#undef TYPE_ULONGLONG
+#undef TYPE_STRING
+#undef TYPE_DOUBLE
+#undef TYPE_INTSTR
+#undef TYPE_POINTER
+#undef TYPE_WSTRING
+#undef TYPE_UNKNOWN
+
+#undef FLAG_LEFT
+#undef FLAG_SIGNED
+#undef FLAG_SPACED
+#undef FLAG_ZEROS
+#undef FLAG_NEG
diff --git a/mozglue/misc/Printf.h b/mozglue/misc/Printf.h
new file mode 100644
index 0000000000..886511b3cf
--- /dev/null
+++ b/mozglue/misc/Printf.h
@@ -0,0 +1,264 @@
+/* -*- 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/. */
+
+/* Printf-like functions, with canned variants that malloc their result. */
+
+#ifndef mozilla_Printf_h
+#define mozilla_Printf_h
+
+/*
+** API for PR printf like routines.
+**
+** These exist partly for historical reasons -- initially they were in
+** NSPR, then forked in tree and modified in js/ -- but now the prime
+** motivation is both closer control over the exact formatting (with
+** one exception, see below) and also the ability to control where
+** exactly the generated results are sent.
+**
+** It might seem that this could all be dispensed with in favor of a
+** wrapper around |vsnprintf| -- except that this implementation
+** guarantees that the %s format will accept a NULL pointer, whereas
+** with standard functions this is undefined.
+**
+** This supports the following formats. It implements a subset of the
+** standard formats; due to the use of MOZ_FORMAT_PRINTF, it is not
+** permissible to extend the standard, aside from relaxing undefined
+** behavior.
+**
+** %d - decimal
+** %u - unsigned decimal
+** %x - unsigned hex
+** %X - unsigned uppercase hex
+** %o - unsigned octal
+** %hd, %hu, %hx, %hX, %ho - "short" versions of above
+** %ld, %lu, %lx, %lX, %lo - "long" versions of above
+** %lld, %llu, %llx, %llX, %llo - "long long" versions of above
+** %zd, %zo, %zu, %zx, %zX - size_t versions of above
+** %Id, %Io, %Iu, %Ix, %IX - size_t versions of above (for Windows compat).
+** Note that MSVC 2015 and newer supports the z length modifier so
+** users should prefer using %z instead of %I. We are supporting %I in
+** addition to %z in case third-party code that uses %I gets routed to
+** use this printf implementation.
+** %s - string
+** %S, %ls - wide string, that is wchar_t*
+** %c - character
+** %p - pointer (deals with machine dependent pointer size)
+** %f - float; note that this is actually formatted using the
+** system's native printf, and so the results may vary
+** %g - float; note that this is actually formatted using the
+** system's native printf, and so the results may vary
+*/
+
+#include "mozilla/AllocPolicy.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Types.h"
+#include "mozilla/UniquePtr.h"
+
+#include <stdarg.h>
+#include <string.h>
+
+namespace mozilla {
+
+/*
+ * This class may be subclassed to provide a way to get the output of
+ * a printf-like call, as the output is generated.
+ */
+class PrintfTarget {
+ public:
+ /* The Printf-like interface. */
+ bool MFBT_API print(const char* format, ...) MOZ_FORMAT_PRINTF(2, 3);
+
+ /* The Vprintf-like interface. */
+ bool MFBT_API vprint(const char* format, va_list) MOZ_FORMAT_PRINTF(2, 0);
+
+ /* Fast paths for formatting integers as though by %d, %o, %u, or %x.
+ Since octal and hex formatting always treat numbers as unsigned, there
+ are no signed overloads for AppendInt{Oct,Hex}. */
+ bool MFBT_API appendIntDec(int32_t);
+ bool MFBT_API appendIntDec(uint32_t);
+ bool MFBT_API appendIntOct(uint32_t);
+ bool MFBT_API appendIntHex(uint32_t);
+ bool MFBT_API appendIntDec(int64_t);
+ bool MFBT_API appendIntDec(uint64_t);
+ bool MFBT_API appendIntOct(uint64_t);
+ bool MFBT_API appendIntHex(uint64_t);
+
+ protected:
+ MFBT_API PrintfTarget();
+ virtual ~PrintfTarget() = default;
+
+ /* Subclasses override this. It is called when more output is
+ available. It may be called with len==0. This should return
+ true on success, or false on failure. */
+ virtual bool append(const char* sp, size_t len) = 0;
+
+ private:
+ /* Number of bytes emitted so far. */
+ size_t mEmitted;
+
+ /* The implementation calls this to emit bytes and update
+ mEmitted. */
+ bool emit(const char* sp, size_t len) {
+ mEmitted += len;
+ return append(sp, len);
+ }
+
+ bool fill2(const char* src, int srclen, int width, int flags);
+ bool fill_n(const char* src, int srclen, int width, int prec, int type,
+ int flags);
+ bool cvt_l(long num, int width, int prec, int radix, int type, int flags,
+ const char* hxp);
+ bool cvt_ll(int64_t num, int width, int prec, int radix, int type, int flags,
+ const char* hexp);
+ bool cvt_f(double d, const char* fmt0, const char* fmt1);
+ bool cvt_s(const char* s, int width, int prec, int flags);
+};
+
+namespace detail {
+
+template <typename AllocPolicy = mozilla::MallocAllocPolicy>
+struct AllocPolicyBasedFreePolicy {
+ void operator()(const void* ptr) {
+ AllocPolicy policy;
+ policy.free_(const_cast<void*>(ptr));
+ }
+};
+
+} // namespace detail
+
+// The type returned by Smprintf and friends.
+template <typename AllocPolicy>
+using SmprintfPolicyPointer =
+ mozilla::UniquePtr<char, detail::AllocPolicyBasedFreePolicy<AllocPolicy>>;
+
+// The default type if no alloc policy is specified.
+typedef SmprintfPolicyPointer<mozilla::MallocAllocPolicy> SmprintfPointer;
+
+// Used in the implementation of Smprintf et al.
+template <typename AllocPolicy>
+class MOZ_STACK_CLASS SprintfState final : private mozilla::PrintfTarget,
+ private AllocPolicy {
+ public:
+ explicit SprintfState(char* base)
+ : mMaxlen(base ? strlen(base) : 0),
+ mBase(base),
+ mCur(base ? base + mMaxlen : 0) {}
+
+ ~SprintfState() { this->free_(mBase); }
+
+ bool vprint(const char* format, va_list ap_list) MOZ_FORMAT_PRINTF(2, 0) {
+ // The "" here has a single \0 character, which is what we're
+ // trying to append.
+ return mozilla::PrintfTarget::vprint(format, ap_list) && append("", 1);
+ }
+
+ SmprintfPolicyPointer<AllocPolicy> release() {
+ SmprintfPolicyPointer<AllocPolicy> result(mBase);
+ mBase = nullptr;
+ return result;
+ }
+
+ protected:
+ bool append(const char* sp, size_t len) override {
+ ptrdiff_t off;
+ char* newbase;
+ size_t newlen;
+
+ off = mCur - mBase;
+ if (off + len >= mMaxlen) {
+ /* Grow the buffer */
+ newlen = mMaxlen + ((len > 32) ? len : 32);
+ newbase = this->template maybe_pod_malloc<char>(newlen);
+ if (!newbase) {
+ /* Ran out of memory */
+ return false;
+ }
+ memcpy(newbase, mBase, mMaxlen);
+ this->free_(mBase);
+ mBase = newbase;
+ mMaxlen = newlen;
+ mCur = mBase + off;
+ }
+
+ /* Copy data */
+ memcpy(mCur, sp, len);
+ mCur += len;
+ MOZ_ASSERT(size_t(mCur - mBase) <= mMaxlen);
+ return true;
+ }
+
+ private:
+ size_t mMaxlen;
+ char* mBase;
+ char* mCur;
+};
+
+/*
+** sprintf into a malloc'd buffer. Return a pointer to the malloc'd
+** buffer on success, nullptr on failure. Call AllocPolicy::free_ to release
+** the memory returned.
+*/
+template <typename AllocPolicy = mozilla::MallocAllocPolicy>
+MOZ_FORMAT_PRINTF(1, 2)
+SmprintfPolicyPointer<AllocPolicy> Smprintf(const char* fmt, ...) {
+ SprintfState<AllocPolicy> ss(nullptr);
+ va_list ap;
+ va_start(ap, fmt);
+ bool r = ss.vprint(fmt, ap);
+ va_end(ap);
+ if (!r) {
+ return nullptr;
+ }
+ return ss.release();
+}
+
+/*
+** "append" sprintf into a malloc'd buffer. "last" is the last value of
+** the malloc'd buffer. sprintf will append data to the end of last,
+** growing it as necessary using realloc. If last is nullptr, SmprintfAppend
+** will allocate the initial string. The return value is the new value of
+** last for subsequent calls, or nullptr if there is a malloc failure.
+*/
+template <typename AllocPolicy = mozilla::MallocAllocPolicy>
+MOZ_FORMAT_PRINTF(2, 3)
+SmprintfPolicyPointer<AllocPolicy> SmprintfAppend(
+ SmprintfPolicyPointer<AllocPolicy>&& last, const char* fmt, ...) {
+ SprintfState<AllocPolicy> ss(last.release());
+ va_list ap;
+ va_start(ap, fmt);
+ bool r = ss.vprint(fmt, ap);
+ va_end(ap);
+ if (!r) {
+ return nullptr;
+ }
+ return ss.release();
+}
+
+/*
+** va_list forms of the above.
+*/
+template <typename AllocPolicy = mozilla::MallocAllocPolicy>
+MOZ_FORMAT_PRINTF(1, 0)
+SmprintfPolicyPointer<AllocPolicy> Vsmprintf(const char* fmt, va_list ap) {
+ SprintfState<AllocPolicy> ss(nullptr);
+ if (!ss.vprint(fmt, ap)) return nullptr;
+ return ss.release();
+}
+
+template <typename AllocPolicy = mozilla::MallocAllocPolicy>
+MOZ_FORMAT_PRINTF(2, 0)
+SmprintfPolicyPointer<AllocPolicy> VsmprintfAppend(
+ SmprintfPolicyPointer<AllocPolicy>&& last, const char* fmt, va_list ap) {
+ SprintfState<AllocPolicy> ss(last.release());
+ if (!ss.vprint(fmt, ap)) return nullptr;
+ return ss.release();
+}
+
+} // namespace mozilla
+
+#endif /* mozilla_Printf_h */
diff --git a/mozglue/misc/StackWalk.cpp b/mozglue/misc/StackWalk.cpp
new file mode 100644
index 0000000000..24868ae5de
--- /dev/null
+++ b/mozglue/misc/StackWalk.cpp
@@ -0,0 +1,929 @@
+/* -*- 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/. */
+
+/* API for getting a stack trace of the C/C++ stack on the current thread */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/StackWalk.h"
+
+#include <string.h>
+
+#if defined(ANDROID) && defined(MOZ_LINKER)
+# include "Linker.h"
+# include <android/log.h>
+#endif
+
+using namespace mozilla;
+
+// for _Unwind_Backtrace from libcxxrt or libunwind
+// cxxabi.h from libcxxrt implicitly includes unwind.h first
+#if defined(HAVE__UNWIND_BACKTRACE) && !defined(_GNU_SOURCE)
+# define _GNU_SOURCE
+#endif
+
+#if defined(HAVE_DLOPEN) || defined(XP_DARWIN)
+# include <dlfcn.h>
+#endif
+
+#if (defined(XP_DARWIN) && \
+ (defined(__i386) || defined(__ppc__) || defined(HAVE__UNWIND_BACKTRACE)))
+# define MOZ_STACKWALK_SUPPORTS_MACOSX 1
+#else
+# define MOZ_STACKWALK_SUPPORTS_MACOSX 0
+#endif
+
+#if (defined(linux) && \
+ ((defined(__GNUC__) && (defined(__i386) || defined(PPC))) || \
+ defined(HAVE__UNWIND_BACKTRACE)))
+# define MOZ_STACKWALK_SUPPORTS_LINUX 1
+#else
+# define MOZ_STACKWALK_SUPPORTS_LINUX 0
+#endif
+
+#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 1)
+# define HAVE___LIBC_STACK_END 1
+#else
+# define HAVE___LIBC_STACK_END 0
+#endif
+
+#if HAVE___LIBC_STACK_END
+extern MOZ_EXPORT void* __libc_stack_end; // from ld-linux.so
+#endif
+
+#ifdef ANDROID
+# include <algorithm>
+# include <unistd.h>
+# include <pthread.h>
+#endif
+
+#if MOZ_STACKWALK_SUPPORTS_WINDOWS
+
+# include <windows.h>
+# include <process.h>
+# include <stdio.h>
+# include <malloc.h>
+# include "mozilla/ArrayUtils.h"
+# include "mozilla/Atomics.h"
+# include "mozilla/StackWalk_windows.h"
+# include "mozilla/WindowsVersion.h"
+
+# include <imagehlp.h>
+// We need a way to know if we are building for WXP (or later), as if we are, we
+// need to use the newer 64-bit APIs. API_VERSION_NUMBER seems to fit the bill.
+// A value of 9 indicates we want to use the new APIs.
+# if API_VERSION_NUMBER < 9
+# error Too old imagehlp.h
+# endif
+
+struct WalkStackData {
+ // Are we walking the stack of the calling thread? Note that we need to avoid
+ // calling fprintf and friends if this is false, in order to avoid deadlocks.
+ bool walkCallingThread;
+ uint32_t skipFrames;
+ HANDLE thread;
+ HANDLE process;
+ HANDLE eventStart;
+ HANDLE eventEnd;
+ void** pcs;
+ uint32_t pc_size;
+ uint32_t pc_count;
+ uint32_t pc_max;
+ void** sps;
+ uint32_t sp_size;
+ uint32_t sp_count;
+ CONTEXT* context;
+};
+
+CRITICAL_SECTION gDbgHelpCS;
+
+# if defined(_M_AMD64) || defined(_M_ARM64)
+// Because various Win64 APIs acquire function-table locks, we need a way of
+// preventing stack walking while those APIs are being called. Otherwise, the
+// stack walker may suspend a thread holding such a lock, and deadlock when the
+// stack unwind code attempts to wait for that lock.
+//
+// We're using an atomic counter rather than a critical section because we
+// don't require mutual exclusion with the stack walker. If the stack walker
+// determines that it's safe to start unwinding the suspended thread (i.e.
+// there are no suppressions when the unwind begins), then it's safe to
+// continue unwinding that thread even if other threads request suppressions
+// in the meantime, because we can't deadlock with those other threads.
+//
+// XXX: This global variable is a larger-than-necessary hammer. A more scoped
+// solution would be to maintain a counter per thread, but then it would be
+// more difficult for WalkStackMain64 to read the suspended thread's counter.
+static Atomic<size_t> sStackWalkSuppressions;
+
+void SuppressStackWalking() { ++sStackWalkSuppressions; }
+
+void DesuppressStackWalking() { --sStackWalkSuppressions; }
+
+MFBT_API
+AutoSuppressStackWalking::AutoSuppressStackWalking() { SuppressStackWalking(); }
+
+MFBT_API
+AutoSuppressStackWalking::~AutoSuppressStackWalking() {
+ DesuppressStackWalking();
+}
+
+static uint8_t* sJitCodeRegionStart;
+static size_t sJitCodeRegionSize;
+uint8_t* sMsMpegJitCodeRegionStart;
+size_t sMsMpegJitCodeRegionSize;
+
+MFBT_API void RegisterJitCodeRegion(uint8_t* aStart, size_t aSize) {
+ // Currently we can only handle one JIT code region at a time
+ MOZ_RELEASE_ASSERT(!sJitCodeRegionStart);
+
+ sJitCodeRegionStart = aStart;
+ sJitCodeRegionSize = aSize;
+}
+
+MFBT_API void UnregisterJitCodeRegion(uint8_t* aStart, size_t aSize) {
+ // Currently we can only handle one JIT code region at a time
+ MOZ_RELEASE_ASSERT(sJitCodeRegionStart && sJitCodeRegionStart == aStart &&
+ sJitCodeRegionSize == aSize);
+
+ sJitCodeRegionStart = nullptr;
+ sJitCodeRegionSize = 0;
+}
+
+# endif // _M_AMD64 || _M_ARM64
+
+// Routine to print an error message to standard error.
+static void PrintError(const char* aPrefix) {
+ LPSTR lpMsgBuf;
+ DWORD lastErr = GetLastError();
+ FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ nullptr, lastErr,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
+ (LPSTR)&lpMsgBuf, 0, nullptr);
+ fprintf(stderr, "### ERROR: %s: %s", aPrefix,
+ lpMsgBuf ? lpMsgBuf : "(null)\n");
+ fflush(stderr);
+ LocalFree(lpMsgBuf);
+}
+
+static void InitializeDbgHelpCriticalSection() {
+ static bool initialized = false;
+ if (initialized) {
+ return;
+ }
+ ::InitializeCriticalSection(&gDbgHelpCS);
+ initialized = true;
+}
+
+static void WalkStackMain64(struct WalkStackData* aData) {
+ // Get a context for the specified thread.
+ CONTEXT context_buf;
+ CONTEXT* context;
+ if (!aData->context) {
+ context = &context_buf;
+ memset(context, 0, sizeof(CONTEXT));
+ context->ContextFlags = CONTEXT_FULL;
+ if (aData->walkCallingThread) {
+ ::RtlCaptureContext(context);
+ } else if (!GetThreadContext(aData->thread, context)) {
+ return;
+ }
+ } else {
+ context = aData->context;
+ }
+
+# if defined(_M_IX86) || defined(_M_IA64)
+ // Setup initial stack frame to walk from.
+ STACKFRAME64 frame64;
+ memset(&frame64, 0, sizeof(frame64));
+# ifdef _M_IX86
+ frame64.AddrPC.Offset = context->Eip;
+ frame64.AddrStack.Offset = context->Esp;
+ frame64.AddrFrame.Offset = context->Ebp;
+# elif defined _M_IA64
+ frame64.AddrPC.Offset = context->StIIP;
+ frame64.AddrStack.Offset = context->SP;
+ frame64.AddrFrame.Offset = context->RsBSP;
+# endif
+ frame64.AddrPC.Mode = AddrModeFlat;
+ frame64.AddrStack.Mode = AddrModeFlat;
+ frame64.AddrFrame.Mode = AddrModeFlat;
+ frame64.AddrReturn.Mode = AddrModeFlat;
+# endif
+
+# if defined(_M_AMD64) || defined(_M_ARM64)
+ // If there are any active suppressions, then at least one thread (we don't
+ // know which) is holding a lock that can deadlock RtlVirtualUnwind. Since
+ // that thread may be the one that we're trying to unwind, we can't proceed.
+ //
+ // But if there are no suppressions, then our target thread can't be holding
+ // a lock, and it's safe to proceed. By virtue of being suspended, the target
+ // thread can't acquire any new locks during the unwind process, so we only
+ // need to do this check once. After that, sStackWalkSuppressions can be
+ // changed by other threads while we're unwinding, and that's fine because
+ // we can't deadlock with those threads.
+ if (sStackWalkSuppressions) {
+ return;
+ }
+# endif
+
+# if defined(_M_AMD64) || defined(_M_ARM64)
+ bool firstFrame = true;
+# endif
+
+ // Skip our own stack walking frames.
+ int skip = (aData->walkCallingThread ? 3 : 0) + aData->skipFrames;
+
+ // Now walk the stack.
+ while (true) {
+ DWORD64 addr;
+ DWORD64 spaddr;
+
+# if defined(_M_IX86) || defined(_M_IA64)
+ // 32-bit frame unwinding.
+ // Debug routines are not threadsafe, so grab the lock.
+ EnterCriticalSection(&gDbgHelpCS);
+ BOOL ok = StackWalk64(
+# if defined _M_IA64
+ IMAGE_FILE_MACHINE_IA64,
+# elif defined _M_IX86
+ IMAGE_FILE_MACHINE_I386,
+# endif
+ aData->process, aData->thread, &frame64, context, nullptr,
+ SymFunctionTableAccess64, // function table access routine
+ SymGetModuleBase64, // module base routine
+ 0);
+ LeaveCriticalSection(&gDbgHelpCS);
+
+ if (ok) {
+ addr = frame64.AddrPC.Offset;
+ spaddr = frame64.AddrStack.Offset;
+ } else {
+ addr = 0;
+ spaddr = 0;
+ if (aData->walkCallingThread) {
+ PrintError("WalkStack64");
+ }
+ }
+
+ if (!ok) {
+ break;
+ }
+
+# elif defined(_M_AMD64) || defined(_M_ARM64)
+
+# if defined(_M_AMD64)
+ auto currentInstr = context->Rip;
+# elif defined(_M_ARM64)
+ auto currentInstr = context->Pc;
+# endif
+
+ // If we reach a frame in JIT code, we don't have enough information to
+ // unwind, so we have to give up.
+ if (sJitCodeRegionStart && (uint8_t*)currentInstr >= sJitCodeRegionStart &&
+ (uint8_t*)currentInstr < sJitCodeRegionStart + sJitCodeRegionSize) {
+ break;
+ }
+
+ // We must also avoid msmpeg2vdec.dll's JIT region: they don't generate
+ // unwind data, so their JIT unwind callback just throws up its hands and
+ // terminates the process.
+ if (sMsMpegJitCodeRegionStart &&
+ (uint8_t*)currentInstr >= sMsMpegJitCodeRegionStart &&
+ (uint8_t*)currentInstr <
+ sMsMpegJitCodeRegionStart + sMsMpegJitCodeRegionSize) {
+ break;
+ }
+
+ // 64-bit frame unwinding.
+ // Try to look up unwind metadata for the current function.
+ ULONG64 imageBase;
+ PRUNTIME_FUNCTION runtimeFunction =
+ RtlLookupFunctionEntry(currentInstr, &imageBase, NULL);
+
+ if (runtimeFunction) {
+ PVOID dummyHandlerData;
+ ULONG64 dummyEstablisherFrame;
+ RtlVirtualUnwind(UNW_FLAG_NHANDLER, imageBase, currentInstr,
+ runtimeFunction, context, &dummyHandlerData,
+ &dummyEstablisherFrame, nullptr);
+ } else if (firstFrame) {
+ // Leaf functions can be unwound by hand.
+# if defined(_M_AMD64)
+ context->Rip = *reinterpret_cast<DWORD64*>(context->Rsp);
+ context->Rsp += sizeof(void*);
+# elif defined(_M_ARM64)
+ context->Pc = *reinterpret_cast<DWORD64*>(context->Sp);
+ context->Sp += sizeof(void*);
+# endif
+ } else {
+ // Something went wrong.
+ break;
+ }
+
+# if defined(_M_AMD64)
+ addr = context->Rip;
+ spaddr = context->Rsp;
+# elif defined(_M_ARM64)
+ addr = context->Pc;
+ spaddr = context->Sp;
+# endif
+ firstFrame = false;
+# else
+# error "unknown platform"
+# endif
+
+ if (addr == 0) {
+ break;
+ }
+
+ if (skip-- > 0) {
+ continue;
+ }
+
+ if (aData->pc_count < aData->pc_size) {
+ aData->pcs[aData->pc_count] = (void*)addr;
+ }
+ ++aData->pc_count;
+
+ if (aData->sp_count < aData->sp_size) {
+ aData->sps[aData->sp_count] = (void*)spaddr;
+ }
+ ++aData->sp_count;
+
+ if (aData->pc_max != 0 && aData->pc_count == aData->pc_max) {
+ break;
+ }
+
+# if defined(_M_IX86) || defined(_M_IA64)
+ if (frame64.AddrReturn.Offset == 0) {
+ break;
+ }
+# endif
+ }
+}
+
+/**
+ * Walk the stack, translating PC's found into strings and recording the
+ * chain in aBuffer. For this to work properly, the DLLs must be rebased
+ * so that the address in the file agrees with the address in memory.
+ * Otherwise StackWalk will return FALSE when it hits a frame in a DLL
+ * whose in memory address doesn't match its in-file address.
+ */
+
+MFBT_API void MozStackWalkThread(MozWalkStackCallback aCallback,
+ uint32_t aSkipFrames, uint32_t aMaxFrames,
+ void* aClosure, HANDLE aThread,
+ CONTEXT* aContext) {
+ struct WalkStackData data;
+
+ InitializeDbgHelpCriticalSection();
+
+ HANDLE targetThread = aThread;
+ if (!aThread) {
+ targetThread = ::GetCurrentThread();
+ data.walkCallingThread = true;
+ } else {
+ DWORD threadId = ::GetThreadId(aThread);
+ DWORD currentThreadId = ::GetCurrentThreadId();
+ data.walkCallingThread = (threadId == currentThreadId);
+ }
+
+ data.skipFrames = aSkipFrames;
+ data.thread = targetThread;
+ data.process = ::GetCurrentProcess();
+ void* local_pcs[1024];
+ data.pcs = local_pcs;
+ data.pc_count = 0;
+ data.pc_size = ArrayLength(local_pcs);
+ data.pc_max = aMaxFrames;
+ void* local_sps[1024];
+ data.sps = local_sps;
+ data.sp_count = 0;
+ data.sp_size = ArrayLength(local_sps);
+ data.context = aContext;
+
+ WalkStackMain64(&data);
+
+ if (data.pc_count > data.pc_size) {
+ data.pcs = (void**)_alloca(data.pc_count * sizeof(void*));
+ data.pc_size = data.pc_count;
+ data.pc_count = 0;
+ data.sps = (void**)_alloca(data.sp_count * sizeof(void*));
+ data.sp_size = data.sp_count;
+ data.sp_count = 0;
+ WalkStackMain64(&data);
+ }
+
+ for (uint32_t i = 0; i < data.pc_count; ++i) {
+ (*aCallback)(i + 1, data.pcs[i], data.sps[i], aClosure);
+ }
+}
+
+MFBT_API void MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames,
+ uint32_t aMaxFrames, void* aClosure) {
+ MozStackWalkThread(aCallback, aSkipFrames, aMaxFrames, aClosure, nullptr,
+ nullptr);
+}
+
+static BOOL CALLBACK callbackEspecial64(PCSTR aModuleName, DWORD64 aModuleBase,
+ ULONG aModuleSize, PVOID aUserContext) {
+ BOOL retval = TRUE;
+ DWORD64 addr = *(DWORD64*)aUserContext;
+
+ /*
+ * You'll want to control this if we are running on an
+ * architecture where the addresses go the other direction.
+ * Not sure this is even a realistic consideration.
+ */
+ const BOOL addressIncreases = TRUE;
+
+ /*
+ * If it falls in side the known range, load the symbols.
+ */
+ if (addressIncreases
+ ? (addr >= aModuleBase && addr <= (aModuleBase + aModuleSize))
+ : (addr <= aModuleBase && addr >= (aModuleBase - aModuleSize))) {
+ retval = !!SymLoadModule64(GetCurrentProcess(), nullptr, (PSTR)aModuleName,
+ nullptr, aModuleBase, aModuleSize);
+ if (!retval) {
+ PrintError("SymLoadModule64");
+ }
+ }
+
+ return retval;
+}
+
+/*
+ * SymGetModuleInfoEspecial
+ *
+ * Attempt to determine the module information.
+ * Bug 112196 says this DLL may not have been loaded at the time
+ * SymInitialize was called, and thus the module information
+ * and symbol information is not available.
+ * This code rectifies that problem.
+ */
+
+// New members were added to IMAGEHLP_MODULE64 (that show up in the
+// Platform SDK that ships with VC8, but not the Platform SDK that ships
+// with VC7.1, i.e., between DbgHelp 6.0 and 6.1), but we don't need to
+// use them, and it's useful to be able to function correctly with the
+// older library. (Stock Windows XP SP2 seems to ship with dbghelp.dll
+// version 5.1.) Since Platform SDK version need not correspond to
+// compiler version, and the version number in debughlp.h was NOT bumped
+// when these changes were made, ifdef based on a constant that was
+// added between these versions.
+# ifdef SSRVOPT_SETCONTEXT
+# define NS_IMAGEHLP_MODULE64_SIZE \
+ (((offsetof(IMAGEHLP_MODULE64, LoadedPdbName) + sizeof(DWORD64) - 1) / \
+ sizeof(DWORD64)) * \
+ sizeof(DWORD64))
+# else
+# define NS_IMAGEHLP_MODULE64_SIZE sizeof(IMAGEHLP_MODULE64)
+# endif
+
+BOOL SymGetModuleInfoEspecial64(HANDLE aProcess, DWORD64 aAddr,
+ PIMAGEHLP_MODULE64 aModuleInfo,
+ PIMAGEHLP_LINE64 aLineInfo) {
+ BOOL retval = FALSE;
+
+ /*
+ * Init the vars if we have em.
+ */
+ aModuleInfo->SizeOfStruct = NS_IMAGEHLP_MODULE64_SIZE;
+ if (aLineInfo) {
+ aLineInfo->SizeOfStruct = sizeof(IMAGEHLP_LINE64);
+ }
+
+ /*
+ * Give it a go.
+ * It may already be loaded.
+ */
+ retval = SymGetModuleInfo64(aProcess, aAddr, aModuleInfo);
+ if (retval == FALSE) {
+ /*
+ * Not loaded, here's the magic.
+ * Go through all the modules.
+ */
+ // Need to cast to PENUMLOADED_MODULES_CALLBACK64 because the
+ // constness of the first parameter of
+ // PENUMLOADED_MODULES_CALLBACK64 varies over SDK versions (from
+ // non-const to const over time). See bug 391848 and bug
+ // 415426.
+ BOOL enumRes = EnumerateLoadedModules64(
+ aProcess, (PENUMLOADED_MODULES_CALLBACK64)callbackEspecial64,
+ (PVOID)&aAddr);
+ if (enumRes != FALSE) {
+ /*
+ * One final go.
+ * If it fails, then well, we have other problems.
+ */
+ retval = SymGetModuleInfo64(aProcess, aAddr, aModuleInfo);
+ }
+ }
+
+ /*
+ * If we got module info, we may attempt line info as well.
+ * We will not report failure if this does not work.
+ */
+ if (retval != FALSE && aLineInfo) {
+ DWORD displacement = 0;
+ BOOL lineRes = FALSE;
+ lineRes = SymGetLineFromAddr64(aProcess, aAddr, &displacement, aLineInfo);
+ if (!lineRes) {
+ // Clear out aLineInfo to indicate that it's not valid
+ memset(aLineInfo, 0, sizeof(*aLineInfo));
+ }
+ }
+
+ return retval;
+}
+
+static bool EnsureSymInitialized() {
+ static bool gInitialized = false;
+ bool retStat;
+
+ if (gInitialized) {
+ return gInitialized;
+ }
+
+ InitializeDbgHelpCriticalSection();
+
+ SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME);
+ retStat = SymInitialize(GetCurrentProcess(), nullptr, TRUE);
+ if (!retStat) {
+ PrintError("SymInitialize");
+ }
+
+ gInitialized = retStat;
+ /* XXX At some point we need to arrange to call SymCleanup */
+
+ return retStat;
+}
+
+MFBT_API bool MozDescribeCodeAddress(void* aPC,
+ MozCodeAddressDetails* aDetails) {
+ aDetails->library[0] = '\0';
+ aDetails->loffset = 0;
+ aDetails->filename[0] = '\0';
+ aDetails->lineno = 0;
+ aDetails->function[0] = '\0';
+ aDetails->foffset = 0;
+
+ if (!EnsureSymInitialized()) {
+ return false;
+ }
+
+ HANDLE myProcess = ::GetCurrentProcess();
+ BOOL ok;
+
+ // debug routines are not threadsafe, so grab the lock.
+ EnterCriticalSection(&gDbgHelpCS);
+
+ //
+ // Attempt to load module info before we attempt to resolve the symbol.
+ // This just makes sure we get good info if available.
+ //
+
+ DWORD64 addr = (DWORD64)aPC;
+ IMAGEHLP_MODULE64 modInfo;
+ IMAGEHLP_LINE64 lineInfo;
+ BOOL modInfoRes;
+ modInfoRes = SymGetModuleInfoEspecial64(myProcess, addr, &modInfo, &lineInfo);
+
+ if (modInfoRes) {
+ strncpy(aDetails->library, modInfo.LoadedImageName,
+ sizeof(aDetails->library));
+ aDetails->library[mozilla::ArrayLength(aDetails->library) - 1] = '\0';
+ aDetails->loffset = (char*)aPC - (char*)modInfo.BaseOfImage;
+
+ if (lineInfo.FileName) {
+ strncpy(aDetails->filename, lineInfo.FileName,
+ sizeof(aDetails->filename));
+ aDetails->filename[mozilla::ArrayLength(aDetails->filename) - 1] = '\0';
+ aDetails->lineno = lineInfo.LineNumber;
+ }
+ }
+
+ ULONG64 buffer[(sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR) +
+ sizeof(ULONG64) - 1) /
+ sizeof(ULONG64)];
+ PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;
+ pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
+ pSymbol->MaxNameLen = MAX_SYM_NAME;
+
+ DWORD64 displacement;
+ ok = SymFromAddr(myProcess, addr, &displacement, pSymbol);
+
+ if (ok) {
+ strncpy(aDetails->function, pSymbol->Name, sizeof(aDetails->function));
+ aDetails->function[mozilla::ArrayLength(aDetails->function) - 1] = '\0';
+ aDetails->foffset = static_cast<ptrdiff_t>(displacement);
+ }
+
+ LeaveCriticalSection(&gDbgHelpCS); // release our lock
+ return true;
+}
+
+// i386 or PPC Linux stackwalking code
+#elif HAVE_DLADDR && \
+ (HAVE__UNWIND_BACKTRACE || MOZ_STACKWALK_SUPPORTS_LINUX || \
+ MOZ_STACKWALK_SUPPORTS_MACOSX)
+
+# include <stdlib.h>
+# include <string.h>
+# include <stdio.h>
+
+// On glibc 2.1, the Dl_info api defined in <dlfcn.h> is only exposed
+// if __USE_GNU is defined. I suppose its some kind of standards
+// adherence thing.
+//
+# if (__GLIBC_MINOR__ >= 1) && !defined(__USE_GNU)
+# define __USE_GNU
+# endif
+
+// This thing is exported by libstdc++
+// Yes, this is a gcc only hack
+# if defined(MOZ_DEMANGLE_SYMBOLS)
+# include <cxxabi.h>
+# endif // MOZ_DEMANGLE_SYMBOLS
+
+namespace mozilla {
+
+void DemangleSymbol(const char* aSymbol, char* aBuffer, int aBufLen) {
+ aBuffer[0] = '\0';
+
+# if defined(MOZ_DEMANGLE_SYMBOLS)
+ /* See demangle.h in the gcc source for the voodoo */
+ char* demangled = abi::__cxa_demangle(aSymbol, 0, 0, 0);
+
+ if (demangled) {
+ strncpy(aBuffer, demangled, aBufLen);
+ aBuffer[aBufLen - 1] = '\0';
+ free(demangled);
+ }
+# endif // MOZ_DEMANGLE_SYMBOLS
+}
+
+} // namespace mozilla
+
+// {x86, ppc} x {Linux, Mac} stackwalking code.
+# if ((defined(__i386) || defined(PPC) || defined(__ppc__)) && \
+ (MOZ_STACKWALK_SUPPORTS_MACOSX || MOZ_STACKWALK_SUPPORTS_LINUX))
+
+MFBT_API void MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames,
+ uint32_t aMaxFrames, void* aClosure) {
+ // Get the frame pointer
+ void** bp = (void**)__builtin_frame_address(0);
+
+ void* stackEnd;
+# if HAVE___LIBC_STACK_END
+ stackEnd = __libc_stack_end;
+# elif defined(XP_DARWIN)
+ stackEnd = pthread_get_stackaddr_np(pthread_self());
+# elif defined(ANDROID)
+ pthread_attr_t sattr;
+ pthread_attr_init(&sattr);
+ pthread_getattr_np(pthread_self(), &sattr);
+ void* stackBase = stackEnd = nullptr;
+ size_t stackSize = 0;
+ if (gettid() != getpid()) {
+ // bionic's pthread_attr_getstack doesn't tell the truth for the main
+ // thread (see bug 846670). So don't use it for the main thread.
+ if (!pthread_attr_getstack(&sattr, &stackBase, &stackSize)) {
+ stackEnd = static_cast<char*>(stackBase) + stackSize;
+ } else {
+ stackEnd = nullptr;
+ }
+ }
+ if (!stackEnd) {
+ // So consider the current frame pointer + an arbitrary size of 8MB
+ // (modulo overflow ; not really arbitrary as it's the default stack
+ // size for the main thread) if pthread_attr_getstack failed for
+ // some reason (or was skipped).
+ static const uintptr_t kMaxStackSize = 8 * 1024 * 1024;
+ uintptr_t maxStackStart = uintptr_t(-1) - kMaxStackSize;
+ uintptr_t stackStart = std::max(maxStackStart, uintptr_t(bp));
+ stackEnd = reinterpret_cast<void*>(stackStart + kMaxStackSize);
+ }
+# else
+# error Unsupported configuration
+# endif
+ FramePointerStackWalk(aCallback, aSkipFrames, aMaxFrames, aClosure, bp,
+ stackEnd);
+}
+
+# elif defined(HAVE__UNWIND_BACKTRACE)
+
+// libgcc_s.so symbols _Unwind_Backtrace@@GCC_3.3 and _Unwind_GetIP@@GCC_3.0
+# include <unwind.h>
+
+struct unwind_info {
+ MozWalkStackCallback callback;
+ int skip;
+ int maxFrames;
+ int numFrames;
+ void* closure;
+};
+
+static _Unwind_Reason_Code unwind_callback(struct _Unwind_Context* context,
+ void* closure) {
+ unwind_info* info = static_cast<unwind_info*>(closure);
+ void* pc = reinterpret_cast<void*>(_Unwind_GetIP(context));
+ // TODO Use something like '_Unwind_GetGR()' to get the stack pointer.
+ if (--info->skip < 0) {
+ info->numFrames++;
+ (*info->callback)(info->numFrames, pc, nullptr, info->closure);
+ if (info->maxFrames != 0 && info->numFrames == info->maxFrames) {
+ // Again, any error code that stops the walk will do.
+ return _URC_FOREIGN_EXCEPTION_CAUGHT;
+ }
+ }
+ return _URC_NO_REASON;
+}
+
+MFBT_API void MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames,
+ uint32_t aMaxFrames, void* aClosure) {
+ unwind_info info;
+ info.callback = aCallback;
+ info.skip = aSkipFrames + 1;
+ info.maxFrames = aMaxFrames;
+ info.numFrames = 0;
+ info.closure = aClosure;
+
+ // We ignore the return value from _Unwind_Backtrace. There are three main
+ // reasons for this.
+ // - On ARM/Android bionic's _Unwind_Backtrace usually (always?) returns
+ // _URC_FAILURE. See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=717853#c110.
+ // - If aMaxFrames != 0, we want to stop early, and the only way to do that
+ // is to make unwind_callback return something other than _URC_NO_REASON,
+ // which causes _Unwind_Backtrace to return a non-success code.
+ // - MozStackWalk doesn't have a return value anyway.
+ (void)_Unwind_Backtrace(unwind_callback, &info);
+}
+
+# endif
+
+bool MFBT_API MozDescribeCodeAddress(void* aPC,
+ MozCodeAddressDetails* aDetails) {
+ aDetails->library[0] = '\0';
+ aDetails->loffset = 0;
+ aDetails->filename[0] = '\0';
+ aDetails->lineno = 0;
+ aDetails->function[0] = '\0';
+ aDetails->foffset = 0;
+
+ Dl_info info;
+
+# if defined(ANDROID) && defined(MOZ_LINKER)
+ int ok = __wrap_dladdr(aPC, &info);
+# else
+ int ok = dladdr(aPC, &info);
+# endif
+
+ if (!ok) {
+ return true;
+ }
+
+ strncpy(aDetails->library, info.dli_fname, sizeof(aDetails->library));
+ aDetails->library[mozilla::ArrayLength(aDetails->library) - 1] = '\0';
+ aDetails->loffset = (char*)aPC - (char*)info.dli_fbase;
+
+# if !defined(XP_FREEBSD)
+ // On FreeBSD, dli_sname is unusably bad, it often returns things like
+ // 'gtk_xtbin_new' or 'XRE_GetBootstrap' instead of long C++ symbols. Just let
+ // GetFunction do the lookup directly in the ELF image.
+
+ const char* symbol = info.dli_sname;
+ if (!symbol || symbol[0] == '\0') {
+ return true;
+ }
+
+ DemangleSymbol(symbol, aDetails->function, sizeof(aDetails->function));
+
+ if (aDetails->function[0] == '\0') {
+ // Just use the mangled symbol if demangling failed.
+ strncpy(aDetails->function, symbol, sizeof(aDetails->function));
+ aDetails->function[mozilla::ArrayLength(aDetails->function) - 1] = '\0';
+ }
+
+ aDetails->foffset = (char*)aPC - (char*)info.dli_saddr;
+# endif
+
+ return true;
+}
+
+#else // unsupported platform.
+
+MFBT_API void MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames,
+ uint32_t aMaxFrames, void* aClosure) {}
+
+MFBT_API bool MozDescribeCodeAddress(void* aPC,
+ MozCodeAddressDetails* aDetails) {
+ aDetails->library[0] = '\0';
+ aDetails->loffset = 0;
+ aDetails->filename[0] = '\0';
+ aDetails->lineno = 0;
+ aDetails->function[0] = '\0';
+ aDetails->foffset = 0;
+ return false;
+}
+
+#endif
+
+#if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_LINUX)
+namespace mozilla {
+MOZ_ASAN_BLACKLIST
+void FramePointerStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames,
+ uint32_t aMaxFrames, void* aClosure, void** aBp,
+ void* aStackEnd) {
+ // Stack walking code courtesy Kipp's "leaky".
+
+ int32_t skip = aSkipFrames;
+ uint32_t numFrames = 0;
+ while (aBp) {
+ void** next = (void**)*aBp;
+ // aBp may not be a frame pointer on i386 if code was compiled with
+ // -fomit-frame-pointer, so do some sanity checks.
+ // (aBp should be a frame pointer on ppc(64) but checking anyway may help
+ // a little if the stack has been corrupted.)
+ // We don't need to check against the begining of the stack because
+ // we can assume that aBp > sp
+ if (next <= aBp || next >= aStackEnd || (uintptr_t(next) & 3)) {
+ break;
+ }
+# if (defined(__ppc__) && defined(XP_MACOSX)) || defined(__powerpc64__)
+ // ppc mac or powerpc64 linux
+ void* pc = *(aBp + 2);
+ aBp += 3;
+# else // i386 or powerpc32 linux
+ void* pc = *(aBp + 1);
+ aBp += 2;
+# endif
+ if (--skip < 0) {
+ // Assume that the SP points to the BP of the function
+ // it called. We can't know the exact location of the SP
+ // but this should be sufficient for our use the SP
+ // to order elements on the stack.
+ numFrames++;
+ (*aCallback)(numFrames, pc, aBp, aClosure);
+ if (aMaxFrames != 0 && numFrames == aMaxFrames) {
+ break;
+ }
+ }
+ aBp = next;
+ }
+}
+} // namespace mozilla
+
+#else
+
+namespace mozilla {
+MFBT_API void FramePointerStackWalk(MozWalkStackCallback aCallback,
+ uint32_t aSkipFrames, uint32_t aMaxFrames,
+ void* aClosure, void** aBp,
+ void* aStackEnd) {}
+} // namespace mozilla
+
+#endif
+
+MFBT_API void MozFormatCodeAddressDetails(
+ char* aBuffer, uint32_t aBufferSize, uint32_t aFrameNumber, void* aPC,
+ const MozCodeAddressDetails* aDetails) {
+ MozFormatCodeAddress(aBuffer, aBufferSize, aFrameNumber, aPC,
+ aDetails->function, aDetails->library, aDetails->loffset,
+ aDetails->filename, aDetails->lineno);
+}
+
+MFBT_API void MozFormatCodeAddress(char* aBuffer, uint32_t aBufferSize,
+ uint32_t aFrameNumber, const void* aPC,
+ const char* aFunction, const char* aLibrary,
+ ptrdiff_t aLOffset, const char* aFileName,
+ uint32_t aLineNo) {
+ const char* function = aFunction && aFunction[0] ? aFunction : "???";
+ if (aFileName && aFileName[0]) {
+ // We have a filename and (presumably) a line number. Use them.
+ snprintf(aBuffer, aBufferSize, "#%02u: %s (%s:%u)", aFrameNumber, function,
+ aFileName, aLineNo);
+ } else if (aLibrary && aLibrary[0]) {
+ // We have no filename, but we do have a library name. Use it and the
+ // library offset, and print them in a way that `fix_stacks.py` can
+ // post-process.
+ snprintf(aBuffer, aBufferSize, "#%02u: %s[%s +0x%" PRIxPTR "]",
+ aFrameNumber, function, aLibrary,
+ static_cast<uintptr_t>(aLOffset));
+ } else {
+ // We have nothing useful to go on. (The format string is split because
+ // '??)' is a trigraph and causes a warning, sigh.)
+ snprintf(aBuffer, aBufferSize,
+ "#%02u: ??? (???:???"
+ ")",
+ aFrameNumber);
+ }
+}
diff --git a/mozglue/misc/StackWalk.h b/mozglue/misc/StackWalk.h
new file mode 100644
index 0000000000..3ea9de8091
--- /dev/null
+++ b/mozglue/misc/StackWalk.h
@@ -0,0 +1,177 @@
+/* -*- 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/. */
+
+/* APIs for getting a stack trace of the current thread */
+
+#ifndef mozilla_StackWalk_h
+#define mozilla_StackWalk_h
+
+#include "mozilla/Types.h"
+#include <stdint.h>
+
+/**
+ * The callback for MozStackWalk and MozStackWalkThread.
+ *
+ * @param aFrameNumber The frame number (starts at 1, not 0).
+ * @param aPC The program counter value.
+ * @param aSP The best approximation possible of what the stack
+ * pointer will be pointing to when the execution returns
+ * to executing that at aPC. If no approximation can
+ * be made it will be nullptr.
+ * @param aClosure Extra data passed in from MozStackWalk() or
+ * MozStackWalkThread().
+ */
+typedef void (*MozWalkStackCallback)(uint32_t aFrameNumber, void* aPC,
+ void* aSP, void* aClosure);
+
+/**
+ * Call aCallback for each stack frame on the current thread, from
+ * the caller of MozStackWalk to main (or above).
+ *
+ * @param aCallback Callback function, called once per frame.
+ * @param aSkipFrames Number of initial frames to skip. 0 means that
+ * the first callback will be for the caller of
+ * MozStackWalk.
+ * @param aMaxFrames Maximum number of frames to trace. 0 means no limit.
+ * @param aClosure Caller-supplied data passed through to aCallback.
+ *
+ * May skip some stack frames due to compiler optimizations or code
+ * generation.
+ */
+MFBT_API void MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames,
+ uint32_t aMaxFrames, void* aClosure);
+
+#if defined(_WIN32) && (defined(_M_IX86) || defined(_M_AMD64) || \
+ defined(_M_IA64) || defined(_M_ARM64))
+
+# include <windows.h>
+
+# define MOZ_STACKWALK_SUPPORTS_WINDOWS 1
+
+/**
+ * Like MozStackWalk, but walks the stack for another thread.
+ * Call aCallback for each stack frame on the current thread, from
+ * the caller of MozStackWalk to main (or above).
+ *
+ * @param aCallback Same as for MozStackWalk().
+ * @param aSkipFrames Same as for MozStackWalk().
+ * @param aMaxFrames Same as for MozStackWalk().
+ * @param aClosure Same as for MozStackWalk().
+ * @param aThread The handle of the thread whose stack is to be walked.
+ * If 0, walks the current thread.
+ * @param aContext A CONTEXT, presumably obtained with GetThreadContext()
+ * after suspending the thread with SuspendThread(). If
+ * null, the CONTEXT will be re-obtained.
+ */
+MFBT_API void MozStackWalkThread(MozWalkStackCallback aCallback,
+ uint32_t aSkipFrames, uint32_t aMaxFrames,
+ void* aClosure, HANDLE aThread,
+ CONTEXT* aContext);
+
+#else
+
+# define MOZ_STACKWALK_SUPPORTS_WINDOWS 0
+
+#endif
+
+typedef struct {
+ /*
+ * The name of the shared library or executable containing an
+ * address and the address's offset within that library, or empty
+ * string and zero if unknown.
+ */
+ char library[256];
+ ptrdiff_t loffset;
+ /*
+ * The name of the file name and line number of the code
+ * corresponding to the address, or empty string and zero if
+ * unknown.
+ */
+ char filename[256];
+ unsigned long lineno;
+ /*
+ * The name of the function containing an address and the address's
+ * offset within that function, or empty string and zero if unknown.
+ */
+ char function[256];
+ ptrdiff_t foffset;
+} MozCodeAddressDetails;
+
+/**
+ * For a given pointer to code, fill in the pieces of information used
+ * when printing a stack trace.
+ *
+ * @param aPC The code address.
+ * @param aDetails A structure to be filled in with the result.
+ */
+MFBT_API bool MozDescribeCodeAddress(void* aPC,
+ MozCodeAddressDetails* aDetails);
+
+/**
+ * Format the information about a code address in a format suitable for
+ * stack traces on the current platform. When available, this string
+ * should contain the function name, source file, and line number. When
+ * these are not available, library and offset should be reported, if
+ * possible.
+ *
+ * Note that this output is parsed by several scripts including the fix*.py and
+ * make-tree.pl scripts in tools/rb/. It should only be change with care, and
+ * in conjunction with those scripts.
+ *
+ * @param aBuffer A string to be filled in with the description.
+ * The string will always be null-terminated.
+ * @param aBufferSize The size, in bytes, of aBuffer, including
+ * room for the terminating null. If the information
+ * to be printed would be larger than aBuffer, it
+ * will be truncated so that aBuffer[aBufferSize-1]
+ * is the terminating null.
+ * @param aFrameNumber The frame number.
+ * @param aPC The code address.
+ * @param aFunction The function name. Possibly null or the empty string.
+ * @param aLibrary The library name. Possibly null or the empty string.
+ * @param aLOffset The library offset.
+ * @param aFileName The filename. Possibly null or the empty string.
+ * @param aLineNo The line number. Possibly zero.
+ */
+MFBT_API void MozFormatCodeAddress(char* aBuffer, uint32_t aBufferSize,
+ uint32_t aFrameNumber, const void* aPC,
+ const char* aFunction, const char* aLibrary,
+ ptrdiff_t aLOffset, const char* aFileName,
+ uint32_t aLineNo);
+
+/**
+ * Format the information about a code address in the same fashion as
+ * MozFormatCodeAddress.
+ *
+ * @param aBuffer A string to be filled in with the description.
+ * The string will always be null-terminated.
+ * @param aBufferSize The size, in bytes, of aBuffer, including
+ * room for the terminating null. If the information
+ * to be printed would be larger than aBuffer, it
+ * will be truncated so that aBuffer[aBufferSize-1]
+ * is the terminating null.
+ * @param aFrameNumber The frame number.
+ * @param aPC The code address.
+ * @param aDetails The value filled in by MozDescribeCodeAddress(aPC).
+ */
+MFBT_API void MozFormatCodeAddressDetails(
+ char* aBuffer, uint32_t aBufferSize, uint32_t aFrameNumber, void* aPC,
+ const MozCodeAddressDetails* aDetails);
+
+namespace mozilla {
+
+MFBT_API void FramePointerStackWalk(MozWalkStackCallback aCallback,
+ uint32_t aSkipFrames, uint32_t aMaxFrames,
+ void* aClosure, void** aBp,
+ void* aStackEnd);
+
+#if defined(XP_LINUX) || defined(XP_FREEBSD)
+MFBT_API void DemangleSymbol(const char* aSymbol, char* aBuffer, int aBufLen);
+#endif
+
+} // namespace mozilla
+
+#endif
diff --git a/mozglue/misc/StackWalk_windows.h b/mozglue/misc/StackWalk_windows.h
new file mode 100644
index 0000000000..dca4d41064
--- /dev/null
+++ b/mozglue/misc/StackWalk_windows.h
@@ -0,0 +1,34 @@
+/* -*- 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 mozilla_StackWalk_windows_h
+#define mozilla_StackWalk_windows_h
+
+#include "mozilla/Types.h"
+
+#if defined(_M_AMD64) || defined(_M_ARM64)
+/**
+ * Allow stack walkers to work around the egregious win64 dynamic lookup table
+ * list API by locking around SuspendThread to avoid deadlock.
+ *
+ * See comment in StackWalk.cpp
+ */
+struct MOZ_RAII AutoSuppressStackWalking {
+ MFBT_API AutoSuppressStackWalking();
+ MFBT_API ~AutoSuppressStackWalking();
+};
+
+# if defined(IMPL_MFBT)
+void SuppressStackWalking();
+void DesuppressStackWalking();
+# endif // defined(IMPL_MFBT)
+
+MFBT_API void RegisterJitCodeRegion(uint8_t* aStart, size_t size);
+
+MFBT_API void UnregisterJitCodeRegion(uint8_t* aStart, size_t size);
+#endif // _M_AMD64
+
+#endif // mozilla_StackWalk_windows_h
diff --git a/mozglue/misc/TimeStamp.cpp b/mozglue/misc/TimeStamp.cpp
new file mode 100644
index 0000000000..0c02413e98
--- /dev/null
+++ b/mozglue/misc/TimeStamp.cpp
@@ -0,0 +1,154 @@
+/* -*- 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/. */
+
+/*
+ * Implementation of the OS-independent methods of the TimeStamp class
+ */
+
+#include "mozilla/Atomics.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Uptime.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+namespace mozilla {
+
+/**
+ * Wrapper class used to initialize static data used by the TimeStamp class
+ */
+struct TimeStampInitialization {
+ /**
+ * First timestamp taken when the class static initializers are run. This
+ * timestamp is used to sanitize timestamps coming from different sources.
+ */
+ TimeStamp mFirstTimeStamp;
+
+ /**
+ * Timestamp representing the time when the process was created. This field
+ * is populated lazily the first time this information is required and is
+ * replaced every time the process is restarted.
+ */
+ TimeStamp mProcessCreation;
+
+ TimeStampInitialization() {
+ TimeStamp::Startup();
+ mFirstTimeStamp = TimeStamp::Now();
+ // On Windows < 10, initializing the uptime requires `mFirstTimeStamp` to be
+ // valid.
+ mozilla::InitializeUptime();
+ };
+
+ ~TimeStampInitialization() { TimeStamp::Shutdown(); };
+};
+
+static bool sFuzzyfoxEnabled;
+
+/* static */
+bool TimeStamp::GetFuzzyfoxEnabled() { return sFuzzyfoxEnabled; }
+
+/* static */
+void TimeStamp::SetFuzzyfoxEnabled(bool aValue) { sFuzzyfoxEnabled = aValue; }
+
+// These variables store the frozen time (as a TimeStamp) for FuzzyFox that
+// will be reported if FuzzyFox is enabled.
+// We overload the top bit of sCanonicalNow and sCanonicalGTC to
+// indicate if a Timestamp is a fuzzed timestamp (bit set) or not
+// (bit unset).
+#ifdef XP_WIN
+static Atomic<uint64_t> sCanonicalGTC;
+static Atomic<uint64_t> sCanonicalQPC;
+static Atomic<bool> sCanonicalHasQPC;
+#else
+static Atomic<uint64_t> sCanonicalNowTimeStamp;
+#endif
+static Atomic<int64_t> sCanonicalNowTime;
+// This variable stores the frozen time (as ms since the epoch) for FuzzyFox
+// to report if FuzzyFox is enabled.
+static TimeStampInitialization sInitOnce;
+
+MFBT_API TimeStamp TimeStamp::ProcessCreation(bool* aIsInconsistent) {
+ if (aIsInconsistent) {
+ *aIsInconsistent = false;
+ }
+
+ if (sInitOnce.mProcessCreation.IsNull()) {
+ char* mozAppRestart = getenv("MOZ_APP_RESTART");
+ TimeStamp ts;
+
+ /* When calling PR_SetEnv() with an empty value the existing variable may
+ * be unset or set to the empty string depending on the underlying platform
+ * thus we have to check if the variable is present and not empty. */
+ if (mozAppRestart && (strcmp(mozAppRestart, "") != 0)) {
+ /* Firefox was restarted, use the first time-stamp we've taken as the new
+ * process startup time. */
+ ts = sInitOnce.mFirstTimeStamp;
+ } else {
+ TimeStamp now = Now();
+ uint64_t uptime = ComputeProcessUptime();
+
+ ts = now - TimeDuration::FromMicroseconds(uptime);
+
+ if ((ts > sInitOnce.mFirstTimeStamp) || (uptime == 0)) {
+ /* If the process creation timestamp was inconsistent replace it with
+ * the first one instead and notify that a telemetry error was
+ * detected. */
+ if (aIsInconsistent) {
+ *aIsInconsistent = true;
+ }
+ ts = sInitOnce.mFirstTimeStamp;
+ }
+ }
+
+ sInitOnce.mProcessCreation = ts;
+ }
+
+ return sInitOnce.mProcessCreation;
+}
+
+void TimeStamp::RecordProcessRestart() {
+ sInitOnce.mProcessCreation = TimeStamp();
+}
+
+MFBT_API TimeStamp TimeStamp::NowFuzzy(TimeStampValue aValue) {
+#ifdef XP_WIN
+ TimeStampValue canonicalNow =
+ TimeStampValue(sCanonicalGTC, sCanonicalQPC, sCanonicalHasQPC, true);
+#else
+ TimeStampValue canonicalNow = TimeStampValue(sCanonicalNowTimeStamp);
+#endif
+
+ if (TimeStamp::GetFuzzyfoxEnabled()) {
+ if (MOZ_LIKELY(!canonicalNow.IsNull())) {
+ return TimeStamp(canonicalNow);
+ }
+ }
+ // When we disable Fuzzyfox, time may goes backwards, so we need to make sure
+ // we don't do that.
+ else if (MOZ_UNLIKELY(canonicalNow > aValue)) {
+ return TimeStamp(canonicalNow);
+ }
+
+ return TimeStamp(aValue);
+}
+
+MFBT_API void TimeStamp::UpdateFuzzyTimeStamp(TimeStamp aValue) {
+#ifdef XP_WIN
+ sCanonicalGTC = aValue.mValue.mGTC;
+ sCanonicalQPC = aValue.mValue.mQPC;
+ sCanonicalHasQPC = aValue.mValue.mHasQPC;
+#else
+ sCanonicalNowTimeStamp = aValue.mValue.mTimeStamp;
+#endif
+}
+
+MFBT_API int64_t TimeStamp::NowFuzzyTime() { return sCanonicalNowTime; }
+
+MFBT_API void TimeStamp::UpdateFuzzyTime(int64_t aValue) {
+ sCanonicalNowTime = aValue;
+}
+
+} // namespace mozilla
diff --git a/mozglue/misc/TimeStamp.h b/mozglue/misc/TimeStamp.h
new file mode 100644
index 0000000000..9b2521955f
--- /dev/null
+++ b/mozglue/misc/TimeStamp.h
@@ -0,0 +1,615 @@
+/* -*- 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 mozilla_TimeStamp_h
+#define mozilla_TimeStamp_h
+
+#include <stdint.h>
+#include <algorithm> // for std::min, std::max
+#include <ostream>
+#include <type_traits>
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Types.h"
+
+namespace IPC {
+template <typename T>
+struct ParamTraits;
+} // namespace IPC
+
+#ifdef XP_WIN
+// defines TimeStampValue as a complex value keeping both
+// GetTickCount and QueryPerformanceCounter values
+# include "TimeStamp_windows.h"
+#endif
+
+namespace mozilla {
+
+#ifndef XP_WIN
+struct TimeStamp63Bit {
+ uint64_t mUsedCanonicalNow : 1;
+ uint64_t mTimeStamp : 63;
+
+ constexpr TimeStamp63Bit() : mUsedCanonicalNow(0), mTimeStamp(0) {}
+
+ MOZ_IMPLICIT constexpr TimeStamp63Bit(const uint64_t aValue)
+ : mUsedCanonicalNow(0), mTimeStamp(aValue) {}
+
+ constexpr TimeStamp63Bit(const bool aUsedCanonicalNow,
+ const int64_t aTimeStamp)
+ : mUsedCanonicalNow(aUsedCanonicalNow ? 1 : 0), mTimeStamp(aTimeStamp) {}
+
+ bool operator==(const TimeStamp63Bit aOther) const {
+ uint64_t here, there;
+ memcpy(&here, this, sizeof(TimeStamp63Bit));
+ memcpy(&there, &aOther, sizeof(TimeStamp63Bit));
+ return here == there;
+ }
+
+ operator uint64_t() const { return mTimeStamp; }
+
+ bool IsNull() const { return mTimeStamp == 0; }
+
+ bool UsedCanonicalNow() const { return mUsedCanonicalNow; }
+
+ void SetCanonicalNow() { mUsedCanonicalNow = 1; }
+};
+
+typedef TimeStamp63Bit TimeStampValue;
+#endif
+
+class TimeStamp;
+
+/**
+ * Platform-specific implementation details of BaseTimeDuration.
+ */
+class BaseTimeDurationPlatformUtils {
+ public:
+ static MFBT_API double ToSeconds(int64_t aTicks);
+ static MFBT_API double ToSecondsSigDigits(int64_t aTicks);
+ static MFBT_API int64_t TicksFromMilliseconds(double aMilliseconds);
+ static MFBT_API int64_t ResolutionInTicks();
+};
+
+/**
+ * Instances of this class represent the length of an interval of time.
+ * Negative durations are allowed, meaning the end is before the start.
+ *
+ * Internally the duration is stored as a int64_t in units of
+ * PR_TicksPerSecond() when building with NSPR interval timers, or a
+ * system-dependent unit when building with system clocks. The
+ * system-dependent unit must be constant, otherwise the semantics of
+ * this class would be broken.
+ *
+ * The ValueCalculator template parameter determines how arithmetic
+ * operations are performed on the integer count of ticks (mValue).
+ */
+template <typename ValueCalculator>
+class BaseTimeDuration {
+ public:
+ // The default duration is 0.
+ constexpr BaseTimeDuration() : mValue(0) {}
+ // Allow construction using '0' as the initial value, for readability,
+ // but no other numbers (so we don't have any implicit unit conversions).
+ struct _SomethingVeryRandomHere;
+ MOZ_IMPLICIT BaseTimeDuration(_SomethingVeryRandomHere* aZero) : mValue(0) {
+ MOZ_ASSERT(!aZero, "Who's playing funny games here?");
+ }
+ // Default copy-constructor and assignment are OK
+
+ // Converting copy-constructor and assignment operator
+ template <typename E>
+ explicit BaseTimeDuration(const BaseTimeDuration<E>& aOther)
+ : mValue(aOther.mValue) {}
+
+ template <typename E>
+ BaseTimeDuration& operator=(const BaseTimeDuration<E>& aOther) {
+ mValue = aOther.mValue;
+ return *this;
+ }
+
+ double ToSeconds() const {
+ if (mValue == INT64_MAX) {
+ return PositiveInfinity<double>();
+ }
+ if (mValue == INT64_MIN) {
+ return NegativeInfinity<double>();
+ }
+ return BaseTimeDurationPlatformUtils::ToSeconds(mValue);
+ }
+ // Return a duration value that includes digits of time we think to
+ // be significant. This method should be used when displaying a
+ // time to humans.
+ double ToSecondsSigDigits() const {
+ if (mValue == INT64_MAX) {
+ return PositiveInfinity<double>();
+ }
+ if (mValue == INT64_MIN) {
+ return NegativeInfinity<double>();
+ }
+ return BaseTimeDurationPlatformUtils::ToSecondsSigDigits(mValue);
+ }
+ double ToMilliseconds() const { return ToSeconds() * 1000.0; }
+ double ToMicroseconds() const { return ToMilliseconds() * 1000.0; }
+
+ // Using a double here is safe enough; with 53 bits we can represent
+ // durations up to over 280,000 years exactly. If the units of
+ // mValue do not allow us to represent durations of that length,
+ // long durations are clamped to the max/min representable value
+ // instead of overflowing.
+ static inline BaseTimeDuration FromSeconds(double aSeconds) {
+ return FromMilliseconds(aSeconds * 1000.0);
+ }
+ static BaseTimeDuration FromMilliseconds(double aMilliseconds) {
+ if (aMilliseconds == PositiveInfinity<double>()) {
+ return Forever();
+ }
+ if (aMilliseconds == NegativeInfinity<double>()) {
+ return FromTicks(INT64_MIN);
+ }
+ return FromTicks(
+ BaseTimeDurationPlatformUtils::TicksFromMilliseconds(aMilliseconds));
+ }
+ static inline BaseTimeDuration FromMicroseconds(double aMicroseconds) {
+ return FromMilliseconds(aMicroseconds / 1000.0);
+ }
+
+ static constexpr BaseTimeDuration Forever() { return FromTicks(INT64_MAX); }
+
+ BaseTimeDuration operator+(const BaseTimeDuration& aOther) const {
+ return FromTicks(ValueCalculator::Add(mValue, aOther.mValue));
+ }
+ BaseTimeDuration operator-(const BaseTimeDuration& aOther) const {
+ return FromTicks(ValueCalculator::Subtract(mValue, aOther.mValue));
+ }
+ BaseTimeDuration& operator+=(const BaseTimeDuration& aOther) {
+ mValue = ValueCalculator::Add(mValue, aOther.mValue);
+ return *this;
+ }
+ BaseTimeDuration& operator-=(const BaseTimeDuration& aOther) {
+ mValue = ValueCalculator::Subtract(mValue, aOther.mValue);
+ return *this;
+ }
+ BaseTimeDuration operator-() const {
+ // We don't just use FromTicks(ValueCalculator::Subtract(0, mValue))
+ // since that won't give the correct result for -TimeDuration::Forever().
+ int64_t ticks;
+ if (mValue == INT64_MAX) {
+ ticks = INT64_MIN;
+ } else if (mValue == INT64_MIN) {
+ ticks = INT64_MAX;
+ } else {
+ ticks = -mValue;
+ }
+
+ return FromTicks(ticks);
+ }
+
+ static BaseTimeDuration Max(const BaseTimeDuration& aA,
+ const BaseTimeDuration& aB) {
+ return FromTicks(std::max(aA.mValue, aB.mValue));
+ }
+ static BaseTimeDuration Min(const BaseTimeDuration& aA,
+ const BaseTimeDuration& aB) {
+ return FromTicks(std::min(aA.mValue, aB.mValue));
+ }
+
+ private:
+ // Block double multiplier (slower, imprecise if long duration) - Bug 853398.
+ // If required, use MultDouble explicitly and with care.
+ BaseTimeDuration operator*(const double aMultiplier) const = delete;
+
+ // Block double divisor (for the same reason, and because dividing by
+ // fractional values would otherwise invoke the int64_t variant, and rounding
+ // the passed argument can then cause divide-by-zero) - Bug 1147491.
+ BaseTimeDuration operator/(const double aDivisor) const = delete;
+
+ public:
+ BaseTimeDuration MultDouble(double aMultiplier) const {
+ return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier));
+ }
+ BaseTimeDuration operator*(const int32_t aMultiplier) const {
+ return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier));
+ }
+ BaseTimeDuration operator*(const uint32_t aMultiplier) const {
+ return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier));
+ }
+ BaseTimeDuration operator*(const int64_t aMultiplier) const {
+ return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier));
+ }
+ BaseTimeDuration operator*(const uint64_t aMultiplier) const {
+ if (aMultiplier > INT64_MAX) {
+ return Forever();
+ }
+ return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier));
+ }
+ BaseTimeDuration operator/(const int64_t aDivisor) const {
+ MOZ_ASSERT(aDivisor != 0, "Division by zero");
+ return FromTicks(ValueCalculator::Divide(mValue, aDivisor));
+ }
+ double operator/(const BaseTimeDuration& aOther) const {
+ MOZ_ASSERT(aOther.mValue != 0, "Division by zero");
+ return ValueCalculator::DivideDouble(mValue, aOther.mValue);
+ }
+ BaseTimeDuration operator%(const BaseTimeDuration& aOther) const {
+ MOZ_ASSERT(aOther.mValue != 0, "Division by zero");
+ return FromTicks(ValueCalculator::Modulo(mValue, aOther.mValue));
+ }
+
+ template <typename E>
+ bool operator<(const BaseTimeDuration<E>& aOther) const {
+ return mValue < aOther.mValue;
+ }
+ template <typename E>
+ bool operator<=(const BaseTimeDuration<E>& aOther) const {
+ return mValue <= aOther.mValue;
+ }
+ template <typename E>
+ bool operator>=(const BaseTimeDuration<E>& aOther) const {
+ return mValue >= aOther.mValue;
+ }
+ template <typename E>
+ bool operator>(const BaseTimeDuration<E>& aOther) const {
+ return mValue > aOther.mValue;
+ }
+ template <typename E>
+ bool operator==(const BaseTimeDuration<E>& aOther) const {
+ return mValue == aOther.mValue;
+ }
+ template <typename E>
+ bool operator!=(const BaseTimeDuration<E>& aOther) const {
+ return mValue != aOther.mValue;
+ }
+ bool IsZero() const { return mValue == 0; }
+ explicit operator bool() const { return mValue != 0; }
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const BaseTimeDuration& aDuration) {
+ return aStream << aDuration.ToMilliseconds() << " ms";
+ }
+
+ // Return a best guess at the system's current timing resolution,
+ // which might be variable. BaseTimeDurations below this order of
+ // magnitude are meaningless, and those at the same order of
+ // magnitude or just above are suspect.
+ static BaseTimeDuration Resolution() {
+ return FromTicks(BaseTimeDurationPlatformUtils::ResolutionInTicks());
+ }
+
+ // We could define additional operators here:
+ // -- convert to/from other time units
+ // -- scale duration by a float
+ // but let's do that on demand.
+ // Comparing durations for equality will only lead to bugs on
+ // platforms with high-resolution timers.
+
+ private:
+ friend class TimeStamp;
+ friend struct IPC::ParamTraits<mozilla::BaseTimeDuration<ValueCalculator>>;
+ template <typename>
+ friend class BaseTimeDuration;
+
+ static BaseTimeDuration FromTicks(int64_t aTicks) {
+ BaseTimeDuration t;
+ t.mValue = aTicks;
+ return t;
+ }
+
+ static BaseTimeDuration FromTicks(double aTicks) {
+ // NOTE: this MUST be a >= test, because int64_t(double(INT64_MAX))
+ // overflows and gives INT64_MIN.
+ if (aTicks >= double(INT64_MAX)) {
+ return FromTicks(INT64_MAX);
+ }
+
+ // This MUST be a <= test.
+ if (aTicks <= double(INT64_MIN)) {
+ return FromTicks(INT64_MIN);
+ }
+
+ return FromTicks(int64_t(aTicks));
+ }
+
+ // Duration, result is implementation-specific difference of two TimeStamps
+ int64_t mValue;
+};
+
+/**
+ * Perform arithmetic operations on the value of a BaseTimeDuration without
+ * doing strict checks on the range of values.
+ */
+class TimeDurationValueCalculator {
+ public:
+ static int64_t Add(int64_t aA, int64_t aB) { return aA + aB; }
+ static int64_t Subtract(int64_t aA, int64_t aB) { return aA - aB; }
+
+ template <typename T>
+ static int64_t Multiply(int64_t aA, T aB) {
+ static_assert(std::is_integral_v<T>,
+ "Using integer multiplication routine with non-integer type."
+ " Further specialization required");
+ return aA * static_cast<int64_t>(aB);
+ }
+
+ static int64_t Divide(int64_t aA, int64_t aB) { return aA / aB; }
+ static double DivideDouble(int64_t aA, int64_t aB) {
+ return static_cast<double>(aA) / aB;
+ }
+ static int64_t Modulo(int64_t aA, int64_t aB) { return aA % aB; }
+};
+
+template <>
+inline int64_t TimeDurationValueCalculator::Multiply<double>(int64_t aA,
+ double aB) {
+ return static_cast<int64_t>(aA * aB);
+}
+
+/**
+ * Specialization of BaseTimeDuration that uses TimeDurationValueCalculator for
+ * arithmetic on the mValue member.
+ *
+ * Use this class for time durations that are *not* expected to hold values of
+ * Forever (or the negative equivalent) or when such time duration are *not*
+ * expected to be used in arithmetic operations.
+ */
+typedef BaseTimeDuration<TimeDurationValueCalculator> TimeDuration;
+
+/**
+ * Instances of this class represent moments in time, or a special
+ * "null" moment. We do not use the non-monotonic system clock or
+ * local time, since they can be reset, causing apparent backward
+ * travel in time, which can confuse algorithms. Instead we measure
+ * elapsed time according to the system. This time can never go
+ * backwards (i.e. it never wraps around, at least not in less than
+ * five million years of system elapsed time). It might not advance
+ * while the system is sleeping. If TimeStamp::SetNow() is not called
+ * at all for hours or days, we might not notice the passage of some
+ * of that time.
+ *
+ * We deliberately do not expose a way to convert TimeStamps to some
+ * particular unit. All you can do is compute a difference between two
+ * TimeStamps to get a TimeDuration. You can also add a TimeDuration
+ * to a TimeStamp to get a new TimeStamp. You can't do something
+ * meaningless like add two TimeStamps.
+ *
+ * Internally this is implemented as either a wrapper around
+ * - high-resolution, monotonic, system clocks if they exist on this
+ * platform
+ * - PRIntervalTime otherwise. We detect wraparounds of
+ * PRIntervalTime and work around them.
+ *
+ * This class is similar to C++11's time_point, however it is
+ * explicitly nullable and provides an IsNull() method. time_point
+ * is initialized to the clock's epoch and provides a
+ * time_since_epoch() method that functions similiarly. i.e.
+ * t.IsNull() is equivalent to t.time_since_epoch() ==
+ * decltype(t)::duration::zero();
+ *
+ * Note that, since TimeStamp objects are small, prefer to pass them by value
+ * unless there is a specific reason not to do so.
+ */
+class TimeStamp {
+ public:
+ /**
+ * Initialize to the "null" moment
+ */
+ constexpr TimeStamp() : mValue() {}
+ // Default copy-constructor and assignment are OK
+
+ /**
+ * The system timestamps are the same as the TimeStamp
+ * retrieved by mozilla::TimeStamp. Since we need this for
+ * vsync timestamps, we enable the creation of mozilla::TimeStamps
+ * on platforms that support vsync aligned refresh drivers / compositors
+ * Verified true as of Jan 31, 2015: B2G and OS X
+ * False on Windows 7
+ * Android's event time uses CLOCK_MONOTONIC via SystemClock.uptimeMilles.
+ * So it is same value of TimeStamp posix implementation.
+ * Wayland/GTK event time also uses CLOCK_MONOTONIC on Weston/Mutter
+ * compositors.
+ * UNTESTED ON OTHER PLATFORMS
+ */
+#if defined(XP_DARWIN) || defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GTK)
+ static TimeStamp FromSystemTime(int64_t aSystemTime) {
+ static_assert(sizeof(aSystemTime) == sizeof(TimeStampValue),
+ "System timestamp should be same units as TimeStampValue");
+ return TimeStamp(TimeStampValue(false, aSystemTime));
+ }
+#endif
+
+ /**
+ * Return true if this is the "null" moment
+ */
+ bool IsNull() const { return mValue.IsNull(); }
+
+ /**
+ * Return true if this is not the "null" moment, may be used in tests, e.g.:
+ * |if (timestamp) { ... }|
+ */
+ explicit operator bool() const { return !IsNull(); }
+
+ bool UsedCanonicalNow() const { return mValue.UsedCanonicalNow(); }
+ static MFBT_API bool GetFuzzyfoxEnabled();
+ static MFBT_API void SetFuzzyfoxEnabled(bool aValue);
+
+ /**
+ * Return a timestamp reflecting the current elapsed system time. This
+ * is monotonically increasing (i.e., does not decrease) over the
+ * lifetime of this process' XPCOM session.
+ *
+ * Now() is trying to ensure the best possible precision on each platform,
+ * at least one millisecond.
+ *
+ * NowLoRes() has been introduced to workaround performance problems of
+ * QueryPerformanceCounter on the Windows platform. NowLoRes() is giving
+ * lower precision, usually 15.6 ms, but with very good performance benefit.
+ * Use it for measurements of longer times, like >200ms timeouts.
+ */
+ static TimeStamp Now() { return Now(true); }
+ static TimeStamp NowLoRes() { return Now(false); }
+ static TimeStamp NowUnfuzzed() { return NowUnfuzzed(true); }
+
+ static MFBT_API int64_t NowFuzzyTime();
+ /**
+ * Return a timestamp representing the time when the current process was
+ * created which will be comparable with other timestamps taken with this
+ * class. If the actual process creation time is detected to be inconsistent
+ * the @a aIsInconsistent parameter will be set to true, the returned
+ * timestamp however will still be valid though inaccurate.
+ *
+ * @param aIsInconsistent If non-null, set to true if an inconsistency was
+ * detected in the process creation time
+ * @returns A timestamp representing the time when the process was created,
+ * this timestamp is always valid even when errors are reported
+ */
+ static MFBT_API TimeStamp ProcessCreation(bool* aIsInconsistent = nullptr);
+
+ /**
+ * Records a process restart. After this call ProcessCreation() will return
+ * the time when the browser was restarted instead of the actual time when
+ * the process was created.
+ */
+ static MFBT_API void RecordProcessRestart();
+
+ /**
+ * Compute the difference between two timestamps. Both must be non-null.
+ */
+ TimeDuration operator-(const TimeStamp& aOther) const {
+ MOZ_ASSERT(!IsNull(), "Cannot compute with a null value");
+ MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value");
+ static_assert(-INT64_MAX > INT64_MIN, "int64_t sanity check");
+ int64_t ticks = int64_t(mValue - aOther.mValue);
+ // Check for overflow.
+ if (mValue > aOther.mValue) {
+ if (ticks < 0) {
+ ticks = INT64_MAX;
+ }
+ } else {
+ if (ticks > 0) {
+ ticks = INT64_MIN;
+ }
+ }
+ return TimeDuration::FromTicks(ticks);
+ }
+
+ TimeStamp operator+(const TimeDuration& aOther) const {
+ TimeStamp result = *this;
+ result += aOther;
+ return result;
+ }
+ TimeStamp operator-(const TimeDuration& aOther) const {
+ TimeStamp result = *this;
+ result -= aOther;
+ return result;
+ }
+ TimeStamp& operator+=(const TimeDuration& aOther) {
+ MOZ_ASSERT(!IsNull(), "Cannot compute with a null value");
+ TimeStampValue value = mValue + aOther.mValue;
+ // Check for underflow.
+ // (We don't check for overflow because it's not obvious what the error
+ // behavior should be in that case.)
+ if (aOther.mValue < 0 && value > mValue) {
+ value = TimeStampValue();
+ }
+ if (mValue.UsedCanonicalNow()) {
+ value.SetCanonicalNow();
+ }
+ mValue = value;
+ return *this;
+ }
+ TimeStamp& operator-=(const TimeDuration& aOther) {
+ MOZ_ASSERT(!IsNull(), "Cannot compute with a null value");
+ TimeStampValue value = mValue - aOther.mValue;
+ // Check for underflow.
+ // (We don't check for overflow because it's not obvious what the error
+ // behavior should be in that case.)
+ if (aOther.mValue > 0 && value > mValue) {
+ value = TimeStampValue();
+ }
+ if (mValue.UsedCanonicalNow()) {
+ value.SetCanonicalNow();
+ }
+ mValue = value;
+ return *this;
+ }
+
+ bool operator<(const TimeStamp& aOther) const {
+ MOZ_ASSERT(!IsNull(), "Cannot compute with a null value");
+ MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value");
+ return mValue < aOther.mValue;
+ }
+ bool operator<=(const TimeStamp& aOther) const {
+ MOZ_ASSERT(!IsNull(), "Cannot compute with a null value");
+ MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value");
+ return mValue <= aOther.mValue;
+ }
+ bool operator>=(const TimeStamp& aOther) const {
+ MOZ_ASSERT(!IsNull(), "Cannot compute with a null value");
+ MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value");
+ return mValue >= aOther.mValue;
+ }
+ bool operator>(const TimeStamp& aOther) const {
+ MOZ_ASSERT(!IsNull(), "Cannot compute with a null value");
+ MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value");
+ return mValue > aOther.mValue;
+ }
+ bool operator==(const TimeStamp& aOther) const {
+ return IsNull() ? aOther.IsNull()
+ : !aOther.IsNull() && mValue == aOther.mValue;
+ }
+ bool operator!=(const TimeStamp& aOther) const { return !(*this == aOther); }
+
+ // Comparing TimeStamps for equality should be discouraged. Adding
+ // two TimeStamps, or scaling TimeStamps, is nonsense and must never
+ // be allowed.
+
+ static MFBT_API void Startup();
+ static MFBT_API void Shutdown();
+
+ private:
+ friend struct IPC::ParamTraits<mozilla::TimeStamp>;
+
+ MOZ_IMPLICIT TimeStamp(TimeStampValue aValue) : mValue(aValue) {}
+
+ static MFBT_API TimeStamp Now(bool aHighResolution);
+ static MFBT_API TimeStamp NowUnfuzzed(bool aHighResolution);
+ static MFBT_API TimeStamp NowFuzzy(TimeStampValue aValue);
+
+ static MFBT_API void UpdateFuzzyTime(int64_t aValue);
+ static MFBT_API void UpdateFuzzyTimeStamp(TimeStamp aValue);
+
+ /**
+ * Computes the uptime of the current process in microseconds. The result
+ * is platform-dependent and needs to be checked against existing timestamps
+ * for consistency.
+ *
+ * @returns The number of microseconds since the calling process was started
+ * or 0 if an error was encountered while computing the uptime
+ */
+ static MFBT_API uint64_t ComputeProcessUptime();
+
+ /**
+ * When built with PRIntervalTime, a value of 0 means this instance
+ * is "null". Otherwise, the low 32 bits represent a PRIntervalTime,
+ * and the high 32 bits represent a counter of the number of
+ * rollovers of PRIntervalTime that we've seen. This counter starts
+ * at 1 to avoid a real time colliding with the "null" value.
+ *
+ * PR_INTERVAL_MAX is set at 100,000 ticks per second. So the minimum
+ * time to wrap around is about 2^64/100000 seconds, i.e. about
+ * 5,849,424 years.
+ *
+ * When using a system clock, a value is system dependent.
+ */
+ TimeStampValue mValue;
+
+ friend class Fuzzyfox;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_TimeStamp_h */
diff --git a/mozglue/misc/TimeStamp_darwin.cpp b/mozglue/misc/TimeStamp_darwin.cpp
new file mode 100644
index 0000000000..a0f0fb0681
--- /dev/null
+++ b/mozglue/misc/TimeStamp_darwin.cpp
@@ -0,0 +1,191 @@
+/* -*- 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/. */
+
+//
+// Implement TimeStamp::Now() with mach_absolute_time
+//
+// The "tick" unit for mach_absolute_time is defined using mach_timebase_info()
+// which gives a conversion ratio to nanoseconds. For more information see
+// Apple's QA1398.
+//
+// This code is inspired by Chromium's time_mac.cc. The biggest
+// differences are that we explicitly initialize using
+// TimeStamp::Initialize() instead of lazily in Now() and that
+// we store the time value in ticks and convert when needed instead
+// of storing the time value in nanoseconds.
+
+#include <mach/mach_time.h>
+#include <sys/time.h>
+#include <sys/sysctl.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Uptime.h"
+
+// Estimate of the smallest duration of time we can measure.
+static uint64_t sResolution;
+static uint64_t sResolutionSigDigs;
+
+static const uint64_t kNsPerMs = 1000000;
+static const uint64_t kUsPerSec = 1000000;
+static const double kNsPerMsd = 1000000.0;
+static const double kNsPerSecd = 1000000000.0;
+
+static bool gInitialized = false;
+static double sNsPerTick;
+
+static uint64_t ClockTime() {
+ // mach_absolute_time is it when it comes to ticks on the Mac. Other calls
+ // with less precision (such as TickCount) just call through to
+ // mach_absolute_time.
+ //
+ // At the time of writing mach_absolute_time returns the number of nanoseconds
+ // since boot. This won't overflow 64bits for 500+ years so we aren't going
+ // to worry about that possiblity
+ return mach_absolute_time();
+}
+
+static uint64_t ClockResolutionNs() {
+ uint64_t start = ClockTime();
+ uint64_t end = ClockTime();
+ uint64_t minres = (end - start);
+
+ // 10 total trials is arbitrary: what we're trying to avoid by
+ // looping is getting unlucky and being interrupted by a context
+ // switch or signal, or being bitten by paging/cache effects
+ for (int i = 0; i < 9; ++i) {
+ start = ClockTime();
+ end = ClockTime();
+
+ uint64_t candidate = (start - end);
+ if (candidate < minres) {
+ minres = candidate;
+ }
+ }
+
+ if (0 == minres) {
+ // measurable resolution is either incredibly low, ~1ns, or very
+ // high. fall back on NSPR's resolution assumption
+ minres = 1 * kNsPerMs;
+ }
+
+ return minres;
+}
+
+namespace mozilla {
+
+double BaseTimeDurationPlatformUtils::ToSeconds(int64_t aTicks) {
+ MOZ_ASSERT(gInitialized, "calling TimeDuration too early");
+ return (aTicks * sNsPerTick) / kNsPerSecd;
+}
+
+double BaseTimeDurationPlatformUtils::ToSecondsSigDigits(int64_t aTicks) {
+ MOZ_ASSERT(gInitialized, "calling TimeDuration too early");
+ // don't report a value < mResolution ...
+ int64_t valueSigDigs = sResolution * (aTicks / sResolution);
+ // and chop off insignificant digits
+ valueSigDigs = sResolutionSigDigs * (valueSigDigs / sResolutionSigDigs);
+ return (valueSigDigs * sNsPerTick) / kNsPerSecd;
+}
+
+int64_t BaseTimeDurationPlatformUtils::TicksFromMilliseconds(
+ double aMilliseconds) {
+ MOZ_ASSERT(gInitialized, "calling TimeDuration too early");
+ double result = (aMilliseconds * kNsPerMsd) / sNsPerTick;
+ if (result > double(INT64_MAX)) {
+ return INT64_MAX;
+ } else if (result < double(INT64_MIN)) {
+ return INT64_MIN;
+ }
+
+ return result;
+}
+
+int64_t BaseTimeDurationPlatformUtils::ResolutionInTicks() {
+ MOZ_ASSERT(gInitialized, "calling TimeDuration too early");
+ return static_cast<int64_t>(sResolution);
+}
+
+void TimeStamp::Startup() {
+ if (gInitialized) {
+ return;
+ }
+
+ mach_timebase_info_data_t timebaseInfo;
+ // Apple's QA1398 suggests that the output from mach_timebase_info
+ // will not change while a program is running, so it should be safe
+ // to cache the result.
+ kern_return_t kr = mach_timebase_info(&timebaseInfo);
+ if (kr != KERN_SUCCESS) {
+ MOZ_RELEASE_ASSERT(false, "mach_timebase_info failed");
+ }
+
+ sNsPerTick = double(timebaseInfo.numer) / timebaseInfo.denom;
+
+ sResolution = ClockResolutionNs();
+
+ // find the number of significant digits in sResolution, for the
+ // sake of ToSecondsSigDigits()
+ for (sResolutionSigDigs = 1; !(sResolutionSigDigs == sResolution ||
+ 10 * sResolutionSigDigs > sResolution);
+ sResolutionSigDigs *= 10)
+ ;
+
+ gInitialized = true;
+
+ return;
+}
+
+void TimeStamp::Shutdown() {}
+
+TimeStamp TimeStamp::Now(bool aHighResolution) {
+ return TimeStamp::NowFuzzy(TimeStampValue(false, ClockTime()));
+}
+
+TimeStamp TimeStamp::NowUnfuzzed(bool aHighResolution) {
+ return TimeStamp(TimeStampValue(false, ClockTime()));
+}
+
+// Computes and returns the process uptime in microseconds.
+// Returns 0 if an error was encountered.
+uint64_t TimeStamp::ComputeProcessUptime() {
+ struct timeval tv;
+ int rv = gettimeofday(&tv, nullptr);
+
+ if (rv == -1) {
+ return 0;
+ }
+
+ int mib[] = {
+ CTL_KERN,
+ KERN_PROC,
+ KERN_PROC_PID,
+ getpid(),
+ };
+ u_int mibLen = sizeof(mib) / sizeof(mib[0]);
+
+ struct kinfo_proc proc;
+ size_t bufferSize = sizeof(proc);
+ rv = sysctl(mib, mibLen, &proc, &bufferSize, nullptr, 0);
+
+ if (rv == -1) {
+ return 0;
+ }
+
+ uint64_t startTime =
+ ((uint64_t)proc.kp_proc.p_un.__p_starttime.tv_sec * kUsPerSec) +
+ proc.kp_proc.p_un.__p_starttime.tv_usec;
+ uint64_t now = (tv.tv_sec * kUsPerSec) + tv.tv_usec;
+
+ if (startTime > now) {
+ return 0;
+ }
+
+ return now - startTime;
+}
+
+} // namespace mozilla
diff --git a/mozglue/misc/TimeStamp_posix.cpp b/mozglue/misc/TimeStamp_posix.cpp
new file mode 100644
index 0000000000..b07f955505
--- /dev/null
+++ b/mozglue/misc/TimeStamp_posix.cpp
@@ -0,0 +1,336 @@
+/* -*- 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/. */
+
+//
+// Implement TimeStamp::Now() with POSIX clocks.
+//
+// The "tick" unit for POSIX clocks is simply a nanosecond, as this is
+// the smallest unit of time representable by struct timespec. That
+// doesn't mean that a nanosecond is the resolution of TimeDurations
+// obtained with this API; see TimeDuration::Resolution;
+//
+
+#include <sys/syscall.h>
+#include <time.h>
+#include <unistd.h>
+#include <string.h>
+
+#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || \
+ defined(__OpenBSD__)
+# include <sys/param.h>
+# include <sys/sysctl.h>
+#endif
+
+#if defined(__DragonFly__) || defined(__FreeBSD__)
+# include <sys/user.h>
+#endif
+
+#if defined(__NetBSD__)
+# undef KERN_PROC
+# define KERN_PROC KERN_PROC2
+# define KINFO_PROC struct kinfo_proc2
+#else
+# define KINFO_PROC struct kinfo_proc
+#endif
+
+#if defined(__DragonFly__)
+# define KP_START_SEC kp_start.tv_sec
+# define KP_START_USEC kp_start.tv_usec
+#elif defined(__FreeBSD__)
+# define KP_START_SEC ki_start.tv_sec
+# define KP_START_USEC ki_start.tv_usec
+#else
+# define KP_START_SEC p_ustart_sec
+# define KP_START_USEC p_ustart_usec
+#endif
+
+#include "mozilla/Sprintf.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Uptime.h"
+#include <pthread.h>
+
+// Estimate of the smallest duration of time we can measure.
+static uint64_t sResolution;
+static uint64_t sResolutionSigDigs;
+
+static const uint16_t kNsPerUs = 1000;
+static const uint64_t kNsPerMs = 1000000;
+static const uint64_t kNsPerSec = 1000000000;
+static const double kNsPerMsd = 1000000.0;
+static const double kNsPerSecd = 1000000000.0;
+
+static uint64_t TimespecToNs(const struct timespec& aTs) {
+ uint64_t baseNs = uint64_t(aTs.tv_sec) * kNsPerSec;
+ return baseNs + uint64_t(aTs.tv_nsec);
+}
+
+static uint64_t ClockTimeNs() {
+ struct timespec ts;
+ // this can't fail: we know &ts is valid, and TimeStamp::Startup()
+ // checks that CLOCK_MONOTONIC is supported (and aborts if not)
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+
+ // tv_sec is defined to be relative to an arbitrary point in time,
+ // but it would be madness for that point in time to be earlier than
+ // the Epoch. So we can safely assume that even if time_t is 32
+ // bits, tv_sec won't overflow while the browser is open. Revisit
+ // this argument if we're still building with 32-bit time_t around
+ // the year 2037.
+ return TimespecToNs(ts);
+}
+
+static uint64_t ClockResolutionNs() {
+ // NB: why not rely on clock_getres()? Two reasons: (i) it might
+ // lie, and (ii) it might return an "ideal" resolution that while
+ // theoretically true, could never be measured in practice. Since
+ // clock_gettime() likely involves a system call on your platform,
+ // the "actual" timing resolution shouldn't be lower than syscall
+ // overhead.
+
+ uint64_t start = ClockTimeNs();
+ uint64_t end = ClockTimeNs();
+ uint64_t minres = (end - start);
+
+ // 10 total trials is arbitrary: what we're trying to avoid by
+ // looping is getting unlucky and being interrupted by a context
+ // switch or signal, or being bitten by paging/cache effects
+ for (int i = 0; i < 9; ++i) {
+ start = ClockTimeNs();
+ end = ClockTimeNs();
+
+ uint64_t candidate = (start - end);
+ if (candidate < minres) {
+ minres = candidate;
+ }
+ }
+
+ if (0 == minres) {
+ // measurable resolution is either incredibly low, ~1ns, or very
+ // high. fall back on clock_getres()
+ struct timespec ts;
+ if (0 == clock_getres(CLOCK_MONOTONIC, &ts)) {
+ minres = TimespecToNs(ts);
+ }
+ }
+
+ if (0 == minres) {
+ // clock_getres probably failed. fall back on NSPR's resolution
+ // assumption
+ minres = 1 * kNsPerMs;
+ }
+
+ return minres;
+}
+
+namespace mozilla {
+
+double BaseTimeDurationPlatformUtils::ToSeconds(int64_t aTicks) {
+ return double(aTicks) / kNsPerSecd;
+}
+
+double BaseTimeDurationPlatformUtils::ToSecondsSigDigits(int64_t aTicks) {
+ // don't report a value < mResolution ...
+ int64_t valueSigDigs = sResolution * (aTicks / sResolution);
+ // and chop off insignificant digits
+ valueSigDigs = sResolutionSigDigs * (valueSigDigs / sResolutionSigDigs);
+ return double(valueSigDigs) / kNsPerSecd;
+}
+
+int64_t BaseTimeDurationPlatformUtils::TicksFromMilliseconds(
+ double aMilliseconds) {
+ double result = aMilliseconds * kNsPerMsd;
+ if (result > double(INT64_MAX)) {
+ return INT64_MAX;
+ }
+ if (result < INT64_MIN) {
+ return INT64_MIN;
+ }
+
+ return result;
+}
+
+int64_t BaseTimeDurationPlatformUtils::ResolutionInTicks() {
+ return static_cast<int64_t>(sResolution);
+}
+
+static bool gInitialized = false;
+
+void TimeStamp::Startup() {
+ if (gInitialized) {
+ return;
+ }
+
+ struct timespec dummy;
+ if (clock_gettime(CLOCK_MONOTONIC, &dummy) != 0) {
+ MOZ_CRASH("CLOCK_MONOTONIC is absent!");
+ }
+
+ sResolution = ClockResolutionNs();
+
+ // find the number of significant digits in sResolution, for the
+ // sake of ToSecondsSigDigits()
+ for (sResolutionSigDigs = 1; !(sResolutionSigDigs == sResolution ||
+ 10 * sResolutionSigDigs > sResolution);
+ sResolutionSigDigs *= 10)
+ ;
+
+ gInitialized = true;
+}
+
+void TimeStamp::Shutdown() {}
+
+TimeStamp TimeStamp::Now(bool aHighResolution) {
+ return TimeStamp::NowFuzzy(TimeStampValue(false, ClockTimeNs()));
+}
+
+TimeStamp TimeStamp::NowUnfuzzed(bool aHighResolution) {
+ return TimeStamp(TimeStampValue(false, ClockTimeNs()));
+}
+
+#if defined(XP_LINUX) || defined(ANDROID)
+
+// Calculates the amount of jiffies that have elapsed since boot and up to the
+// starttime value of a specific process as found in its /proc/*/stat file.
+// Returns 0 if an error occurred.
+
+static uint64_t JiffiesSinceBoot(const char* aFile) {
+ char stat[512];
+
+ FILE* f = fopen(aFile, "r");
+ if (!f) {
+ return 0;
+ }
+
+ int n = fread(&stat, 1, sizeof(stat) - 1, f);
+
+ fclose(f);
+
+ if (n <= 0) {
+ return 0;
+ }
+
+ stat[n] = 0;
+
+ long long unsigned startTime = 0; // instead of uint64_t to keep GCC quiet
+ char* s = strrchr(stat, ')');
+
+ if (!s) {
+ return 0;
+ }
+
+ int rv = sscanf(s + 2,
+ "%*c %*d %*d %*d %*d %*d %*u %*u %*u %*u "
+ "%*u %*u %*u %*d %*d %*d %*d %*d %*d %llu",
+ &startTime);
+
+ if (rv != 1 || !startTime) {
+ return 0;
+ }
+
+ return startTime;
+}
+
+// Computes the interval that has elapsed between the thread creation and the
+// process creation by comparing the starttime fields in the respective
+// /proc/*/stat files. The resulting value will be a good approximation of the
+// process uptime. This value will be stored at the address pointed by aTime;
+// if an error occurred 0 will be stored instead.
+
+static void* ComputeProcessUptimeThread(void* aTime) {
+ uint64_t* uptime = static_cast<uint64_t*>(aTime);
+ long hz = sysconf(_SC_CLK_TCK);
+
+ *uptime = 0;
+
+ if (!hz) {
+ return nullptr;
+ }
+
+ char threadStat[40];
+ SprintfLiteral(threadStat, "/proc/self/task/%d/stat",
+ (pid_t)syscall(__NR_gettid));
+
+ uint64_t threadJiffies = JiffiesSinceBoot(threadStat);
+ uint64_t selfJiffies = JiffiesSinceBoot("/proc/self/stat");
+
+ if (!threadJiffies || !selfJiffies) {
+ return nullptr;
+ }
+
+ *uptime = ((threadJiffies - selfJiffies) * kNsPerSec) / hz;
+ return nullptr;
+}
+
+// Computes and returns the process uptime in us on Linux & its derivatives.
+// Returns 0 if an error was encountered.
+
+uint64_t TimeStamp::ComputeProcessUptime() {
+ uint64_t uptime = 0;
+ pthread_t uptime_pthread;
+
+ if (pthread_create(&uptime_pthread, nullptr, ComputeProcessUptimeThread,
+ &uptime)) {
+ MOZ_CRASH("Failed to create process uptime thread.");
+ return 0;
+ }
+
+ pthread_join(uptime_pthread, NULL);
+
+ return uptime / kNsPerUs;
+}
+
+#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || \
+ defined(__OpenBSD__)
+
+// Computes and returns the process uptime in us on various BSD flavors.
+// Returns 0 if an error was encountered.
+
+uint64_t TimeStamp::ComputeProcessUptime() {
+ struct timespec ts;
+ int rv = clock_gettime(CLOCK_REALTIME, &ts);
+
+ if (rv == -1) {
+ return 0;
+ }
+
+ int mib[] = {
+ CTL_KERN,
+ KERN_PROC,
+ KERN_PROC_PID,
+ getpid(),
+# if defined(__NetBSD__) || defined(__OpenBSD__)
+ sizeof(KINFO_PROC),
+ 1,
+# endif
+ };
+ u_int mibLen = sizeof(mib) / sizeof(mib[0]);
+
+ KINFO_PROC proc;
+ size_t bufferSize = sizeof(proc);
+ rv = sysctl(mib, mibLen, &proc, &bufferSize, nullptr, 0);
+
+ if (rv == -1) {
+ return 0;
+ }
+
+ uint64_t startTime = ((uint64_t)proc.KP_START_SEC * kNsPerSec) +
+ (proc.KP_START_USEC * kNsPerUs);
+ uint64_t now = ((uint64_t)ts.tv_sec * kNsPerSec) + ts.tv_nsec;
+
+ if (startTime > now) {
+ return 0;
+ }
+
+ return (now - startTime) / kNsPerUs;
+}
+
+#else
+
+uint64_t TimeStamp::ComputeProcessUptime() { return 0; }
+
+#endif
+
+} // namespace mozilla
diff --git a/mozglue/misc/TimeStamp_windows.cpp b/mozglue/misc/TimeStamp_windows.cpp
new file mode 100644
index 0000000000..e4739ced29
--- /dev/null
+++ b/mozglue/misc/TimeStamp_windows.cpp
@@ -0,0 +1,535 @@
+/* -*- 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/. */
+
+// Implement TimeStamp::Now() with QueryPerformanceCounter() controlled with
+// values of GetTickCount64().
+
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Uptime.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <intrin.h>
+#include <windows.h>
+
+// To enable logging define to your favorite logging API
+#define LOG(x)
+
+class AutoCriticalSection {
+ public:
+ explicit AutoCriticalSection(LPCRITICAL_SECTION aSection)
+ : mSection(aSection) {
+ ::EnterCriticalSection(mSection);
+ }
+ ~AutoCriticalSection() { ::LeaveCriticalSection(mSection); }
+
+ private:
+ LPCRITICAL_SECTION mSection;
+};
+
+// Estimate of the smallest duration of time we can measure.
+static volatile ULONGLONG sResolution;
+static volatile ULONGLONG sResolutionSigDigs;
+static const double kNsPerSecd = 1000000000.0;
+static const LONGLONG kNsPerMillisec = 1000000;
+
+// ----------------------------------------------------------------------------
+// Global constants
+// ----------------------------------------------------------------------------
+
+// Tolerance to failures settings.
+//
+// What is the interval we want to have failure free.
+// in [ms]
+static const uint32_t kFailureFreeInterval = 5000;
+// How many failures we are willing to tolerate in the interval.
+static const uint32_t kMaxFailuresPerInterval = 4;
+// What is the threshold to treat fluctuations as actual failures.
+// in [ms]
+static const uint32_t kFailureThreshold = 50;
+
+// If we are not able to get the value of GTC time increment, use this value
+// which is the most usual increment.
+static const DWORD kDefaultTimeIncrement = 156001;
+
+// ----------------------------------------------------------------------------
+// Global variables, not changing at runtime
+// ----------------------------------------------------------------------------
+
+// Result of QueryPerformanceFrequency
+// We use default of 1 for the case we can't use QueryPerformanceCounter
+// to make mt/ms conversions work despite that.
+static uint64_t sFrequencyPerSec = 1;
+
+namespace mozilla {
+
+MFBT_API uint64_t GetQueryPerformanceFrequencyPerSec() {
+ return sFrequencyPerSec;
+}
+
+} // namespace mozilla
+
+// How much we are tolerant to GTC occasional loose of resoltion.
+// This number says how many multiples of the minimal GTC resolution
+// detected on the system are acceptable. This number is empirical.
+static const LONGLONG kGTCTickLeapTolerance = 4;
+
+// Base tolerance (more: "inability of detection" range) threshold is calculated
+// dynamically, and kept in sGTCResolutionThreshold.
+//
+// Schematically, QPC worked "100%" correctly if ((GTC_now - GTC_epoch) -
+// (QPC_now - QPC_epoch)) was in [-sGTCResolutionThreshold,
+// sGTCResolutionThreshold] interval every time we'd compared two time stamps.
+// If not, then we check the overflow behind this basic threshold
+// is in kFailureThreshold. If not, we condider it as a QPC failure. If too
+// many failures in short time are detected, QPC is considered faulty and
+// disabled.
+//
+// Kept in [mt]
+static LONGLONG sGTCResolutionThreshold;
+
+// If QPC is found faulty for two stamps in this interval, we engage
+// the fault detection algorithm. For duration larger then this limit
+// we bypass using durations calculated from QPC when jitter is detected,
+// but don't touch the sUseQPC flag.
+//
+// Value is in [ms].
+static const uint32_t kHardFailureLimit = 2000;
+// Conversion to [mt]
+static LONGLONG sHardFailureLimit;
+
+// Conversion of kFailureFreeInterval and kFailureThreshold to [mt]
+static LONGLONG sFailureFreeInterval;
+static LONGLONG sFailureThreshold;
+
+// ----------------------------------------------------------------------------
+// Systemm status flags
+// ----------------------------------------------------------------------------
+
+// Flag for stable TSC that indicates platform where QPC is stable.
+static bool sHasStableTSC = false;
+
+// ----------------------------------------------------------------------------
+// Global state variables, changing at runtime
+// ----------------------------------------------------------------------------
+
+// Initially true, set to false when QPC is found unstable and never
+// returns back to true since that time.
+static bool volatile sUseQPC = true;
+
+// ----------------------------------------------------------------------------
+// Global lock
+// ----------------------------------------------------------------------------
+
+// Thread spin count before entering the full wait state for sTimeStampLock.
+// Inspired by Rob Arnold's work on PRMJ_Now().
+static const DWORD kLockSpinCount = 4096;
+
+// Common mutex (thanks the relative complexity of the logic, this is better
+// then using CMPXCHG8B.)
+// It is protecting the globals bellow.
+static CRITICAL_SECTION sTimeStampLock;
+
+// ----------------------------------------------------------------------------
+// Global lock protected variables
+// ----------------------------------------------------------------------------
+
+// Timestamp in future until QPC must behave correctly.
+// Set to now + kFailureFreeInterval on first QPC failure detection.
+// Set to now + E * kFailureFreeInterval on following errors,
+// where E is number of errors detected during last kFailureFreeInterval
+// milliseconds, calculated simply as:
+// E = (sFaultIntoleranceCheckpoint - now) / kFailureFreeInterval + 1.
+// When E > kMaxFailuresPerInterval -> disable QPC.
+//
+// Kept in [mt]
+static ULONGLONG sFaultIntoleranceCheckpoint = 0;
+
+namespace mozilla {
+
+// Result is in [mt]
+static inline ULONGLONG PerformanceCounter() {
+ LARGE_INTEGER pc;
+ ::QueryPerformanceCounter(&pc);
+
+ // QueryPerformanceCounter may slightly jitter (not be 100% monotonic.)
+ // This is a simple go-backward protection for such a faulty hardware.
+ AutoCriticalSection lock(&sTimeStampLock);
+
+ static decltype(LARGE_INTEGER::QuadPart) last;
+ if (last > pc.QuadPart) {
+ return last * 1000ULL;
+ }
+ last = pc.QuadPart;
+ return pc.QuadPart * 1000ULL;
+}
+
+static void InitThresholds() {
+ DWORD timeAdjustment = 0, timeIncrement = 0;
+ BOOL timeAdjustmentDisabled;
+ GetSystemTimeAdjustment(&timeAdjustment, &timeIncrement,
+ &timeAdjustmentDisabled);
+
+ LOG(("TimeStamp: timeIncrement=%d [100ns]", timeIncrement));
+
+ if (!timeIncrement) {
+ timeIncrement = kDefaultTimeIncrement;
+ }
+
+ // Ceiling to a millisecond
+ // Example values: 156001, 210000
+ DWORD timeIncrementCeil = timeIncrement;
+ // Don't want to round up if already rounded, values will be: 156000, 209999
+ timeIncrementCeil -= 1;
+ // Convert to ms, values will be: 15, 20
+ timeIncrementCeil /= 10000;
+ // Round up, values will be: 16, 21
+ timeIncrementCeil += 1;
+ // Convert back to 100ns, values will be: 160000, 210000
+ timeIncrementCeil *= 10000;
+
+ // How many milli-ticks has the interval rounded up
+ LONGLONG ticksPerGetTickCountResolutionCeiling =
+ (int64_t(timeIncrementCeil) * sFrequencyPerSec) / 10000LL;
+
+ // GTC may jump by 32 (2*16) ms in two steps, therefor use the ceiling value.
+ sGTCResolutionThreshold =
+ LONGLONG(kGTCTickLeapTolerance * ticksPerGetTickCountResolutionCeiling);
+
+ sHardFailureLimit = ms2mt(kHardFailureLimit);
+ sFailureFreeInterval = ms2mt(kFailureFreeInterval);
+ sFailureThreshold = ms2mt(kFailureThreshold);
+}
+
+static void InitResolution() {
+ // 10 total trials is arbitrary: what we're trying to avoid by
+ // looping is getting unlucky and being interrupted by a context
+ // switch or signal, or being bitten by paging/cache effects
+
+ ULONGLONG minres = ~0ULL;
+ if (sUseQPC) {
+ int loops = 10;
+ do {
+ ULONGLONG start = PerformanceCounter();
+ ULONGLONG end = PerformanceCounter();
+
+ ULONGLONG candidate = (end - start);
+ if (candidate < minres) {
+ minres = candidate;
+ }
+ } while (--loops && minres);
+
+ if (0 == minres) {
+ minres = 1;
+ }
+ } else {
+ // GetTickCount has only ~16ms known resolution
+ minres = ms2mt(16);
+ }
+
+ // Converting minres that is in [mt] to nanosecods, multiplicating
+ // the argument to preserve resolution.
+ ULONGLONG result = mt2ms(minres * kNsPerMillisec);
+ if (0 == result) {
+ result = 1;
+ }
+
+ sResolution = result;
+
+ // find the number of significant digits in mResolution, for the
+ // sake of ToSecondsSigDigits()
+ ULONGLONG sigDigs;
+ for (sigDigs = 1; !(sigDigs == result || 10 * sigDigs > result);
+ sigDigs *= 10)
+ ;
+
+ sResolutionSigDigs = sigDigs;
+}
+
+// ----------------------------------------------------------------------------
+// TimeStampValue implementation
+// ----------------------------------------------------------------------------
+MFBT_API
+TimeStampValue::TimeStampValue(ULONGLONG aGTC, ULONGLONG aQPC, bool aHasQPC,
+ bool aUsedCanonicalNow)
+ : mGTC(aGTC),
+ mQPC(aQPC),
+ mUsedCanonicalNow(aUsedCanonicalNow),
+ mHasQPC(aHasQPC) {
+ mIsNull = aGTC == 0 && aQPC == 0;
+}
+
+MFBT_API TimeStampValue& TimeStampValue::operator+=(const int64_t aOther) {
+ mGTC += aOther;
+ mQPC += aOther;
+ return *this;
+}
+
+MFBT_API TimeStampValue& TimeStampValue::operator-=(const int64_t aOther) {
+ mGTC -= aOther;
+ mQPC -= aOther;
+ return *this;
+}
+
+// If the duration is less then two seconds, perform check of QPC stability
+// by comparing both GTC and QPC calculated durations of this and aOther.
+MFBT_API uint64_t TimeStampValue::CheckQPC(const TimeStampValue& aOther) const {
+ uint64_t deltaGTC = mGTC - aOther.mGTC;
+
+ if (!mHasQPC || !aOther.mHasQPC) { // Both not holding QPC
+ return deltaGTC;
+ }
+
+ uint64_t deltaQPC = mQPC - aOther.mQPC;
+
+ if (sHasStableTSC) { // For stable TSC there is no need to check
+ return deltaQPC;
+ }
+
+ // Check QPC is sane before using it.
+ int64_t diff = DeprecatedAbs(int64_t(deltaQPC) - int64_t(deltaGTC));
+ if (diff <= sGTCResolutionThreshold) {
+ return deltaQPC;
+ }
+
+ // Treat absolutely for calibration purposes
+ int64_t duration = DeprecatedAbs(int64_t(deltaGTC));
+ int64_t overflow = diff - sGTCResolutionThreshold;
+
+ LOG(("TimeStamp: QPC check after %llums with overflow %1.4fms",
+ mt2ms(duration), mt2ms_f(overflow)));
+
+ if (overflow <= sFailureThreshold) { // We are in the limit, let go.
+ return deltaQPC;
+ }
+
+ // QPC deviates, don't use it, since now this method may only return deltaGTC.
+
+ if (!sUseQPC) { // QPC already disabled, no need to run the fault tolerance
+ // algorithm.
+ return deltaGTC;
+ }
+
+ LOG(("TimeStamp: QPC jittered over failure threshold"));
+
+ if (duration < sHardFailureLimit) {
+ // Interval between the two time stamps is very short, consider
+ // QPC as unstable and record a failure.
+ uint64_t now = ms2mt(GetTickCount64());
+
+ AutoCriticalSection lock(&sTimeStampLock);
+
+ if (sFaultIntoleranceCheckpoint && sFaultIntoleranceCheckpoint > now) {
+ // There's already been an error in the last fault intollerant interval.
+ // Time since now to the checkpoint actually holds information on how many
+ // failures there were in the failure free interval we have defined.
+ uint64_t failureCount =
+ (sFaultIntoleranceCheckpoint - now + sFailureFreeInterval - 1) /
+ sFailureFreeInterval;
+ if (failureCount > kMaxFailuresPerInterval) {
+ sUseQPC = false;
+ LOG(("TimeStamp: QPC disabled"));
+ } else {
+ // Move the fault intolerance checkpoint more to the future, prolong it
+ // to reflect the number of detected failures.
+ ++failureCount;
+ sFaultIntoleranceCheckpoint = now + failureCount * sFailureFreeInterval;
+ LOG(("TimeStamp: recording %dth QPC failure", failureCount));
+ }
+ } else {
+ // Setup fault intolerance checkpoint in the future for first detected
+ // error.
+ sFaultIntoleranceCheckpoint = now + sFailureFreeInterval;
+ LOG(("TimeStamp: recording 1st QPC failure"));
+ }
+ }
+
+ return deltaGTC;
+}
+
+MFBT_API uint64_t
+TimeStampValue::operator-(const TimeStampValue& aOther) const {
+ if (IsNull() && aOther.IsNull()) {
+ return uint64_t(0);
+ }
+
+ return CheckQPC(aOther);
+}
+
+// ----------------------------------------------------------------------------
+// TimeDuration and TimeStamp implementation
+// ----------------------------------------------------------------------------
+
+MFBT_API double BaseTimeDurationPlatformUtils::ToSeconds(int64_t aTicks) {
+ // Converting before arithmetic avoids blocked store forward
+ return double(aTicks) / (double(sFrequencyPerSec) * 1000.0);
+}
+
+MFBT_API double BaseTimeDurationPlatformUtils::ToSecondsSigDigits(
+ int64_t aTicks) {
+ // don't report a value < mResolution ...
+ LONGLONG resolution = sResolution;
+ LONGLONG resolutionSigDigs = sResolutionSigDigs;
+ LONGLONG valueSigDigs = resolution * (aTicks / resolution);
+ // and chop off insignificant digits
+ valueSigDigs = resolutionSigDigs * (valueSigDigs / resolutionSigDigs);
+ return double(valueSigDigs) / kNsPerSecd;
+}
+
+MFBT_API int64_t
+BaseTimeDurationPlatformUtils::TicksFromMilliseconds(double aMilliseconds) {
+ double result = ms2mt(aMilliseconds);
+ if (result > double(INT64_MAX)) {
+ return INT64_MAX;
+ } else if (result < double(INT64_MIN)) {
+ return INT64_MIN;
+ }
+
+ return result;
+}
+
+MFBT_API int64_t BaseTimeDurationPlatformUtils::ResolutionInTicks() {
+ return static_cast<int64_t>(sResolution);
+}
+
+static bool HasStableTSC() {
+#if defined(_M_ARM64)
+ // AArch64 defines that its system counter run at a constant rate
+ // regardless of the current clock frequency of the system. See "The
+ // Generic Timer", section D7, in the ARMARM for ARMv8.
+ return true;
+#else
+ union {
+ int regs[4];
+ struct {
+ int nIds;
+ char cpuString[12];
+ };
+ } cpuInfo;
+
+ __cpuid(cpuInfo.regs, 0);
+ // Only allow Intel or AMD CPUs for now.
+ // The order of the registers is reg[1], reg[3], reg[2]. We just adjust the
+ // string so that we can compare in one go.
+ if (_strnicmp(cpuInfo.cpuString, "GenuntelineI", sizeof(cpuInfo.cpuString)) &&
+ _strnicmp(cpuInfo.cpuString, "AuthcAMDenti", sizeof(cpuInfo.cpuString))) {
+ return false;
+ }
+
+ int regs[4];
+
+ // detect if the Advanced Power Management feature is supported
+ __cpuid(regs, 0x80000000);
+ if ((unsigned int)regs[0] < 0x80000007) {
+ // XXX should we return true here? If there is no APM there may be
+ // no way how TSC can run out of sync among cores.
+ return false;
+ }
+
+ __cpuid(regs, 0x80000007);
+ // if bit 8 is set than TSC will run at a constant rate
+ // in all ACPI P-states, C-states and T-states
+ return regs[3] & (1 << 8);
+#endif
+}
+
+static bool gInitialized = false;
+
+MFBT_API void TimeStamp::Startup() {
+ if (gInitialized) {
+ return;
+ }
+
+ gInitialized = true;
+
+ // Decide which implementation to use for the high-performance timer.
+
+ InitializeCriticalSectionAndSpinCount(&sTimeStampLock, kLockSpinCount);
+
+ bool forceGTC = false;
+ bool forceQPC = false;
+
+ char* modevar = getenv("MOZ_TIMESTAMP_MODE");
+ if (modevar) {
+ if (!strcmp(modevar, "QPC")) {
+ forceQPC = true;
+ } else if (!strcmp(modevar, "GTC")) {
+ forceGTC = true;
+ }
+ }
+
+ LARGE_INTEGER freq;
+ sUseQPC = !forceGTC && ::QueryPerformanceFrequency(&freq);
+ if (!sUseQPC) {
+ // No Performance Counter. Fall back to use GetTickCount64.
+ InitResolution();
+
+ LOG(("TimeStamp: using GetTickCount64"));
+ return;
+ }
+
+ sHasStableTSC = forceQPC || HasStableTSC();
+ LOG(("TimeStamp: HasStableTSC=%d", sHasStableTSC));
+
+ sFrequencyPerSec = freq.QuadPart;
+ LOG(("TimeStamp: QPC frequency=%llu", sFrequencyPerSec));
+
+ InitThresholds();
+ InitResolution();
+
+ return;
+}
+
+MFBT_API void TimeStamp::Shutdown() { DeleteCriticalSection(&sTimeStampLock); }
+
+TimeStampValue NowInternal(bool aHighResolution) {
+ // sUseQPC is volatile
+ bool useQPC = (aHighResolution && sUseQPC);
+
+ // Both values are in [mt] units.
+ ULONGLONG QPC = useQPC ? PerformanceCounter() : uint64_t(0);
+ ULONGLONG GTC = ms2mt(GetTickCount64());
+ return TimeStampValue(GTC, QPC, useQPC, false);
+}
+
+MFBT_API TimeStamp TimeStamp::Now(bool aHighResolution) {
+ return TimeStamp::NowFuzzy(NowInternal(aHighResolution));
+}
+
+MFBT_API TimeStamp TimeStamp::NowUnfuzzed(bool aHighResolution) {
+ return TimeStamp(NowInternal(aHighResolution));
+}
+
+// Computes and returns the process uptime in microseconds.
+// Returns 0 if an error was encountered.
+
+MFBT_API uint64_t TimeStamp::ComputeProcessUptime() {
+ FILETIME start, foo, bar, baz;
+ bool success = GetProcessTimes(GetCurrentProcess(), &start, &foo, &bar, &baz);
+ if (!success) {
+ return 0;
+ }
+
+ static const StaticDynamicallyLinkedFunctionPtr<void(WINAPI*)(LPFILETIME)>
+ pGetSystemTimePreciseAsFileTime(L"kernel32.dll",
+ "GetSystemTimePreciseAsFileTime");
+
+ FILETIME now;
+ if (pGetSystemTimePreciseAsFileTime) {
+ pGetSystemTimePreciseAsFileTime(&now);
+ } else {
+ GetSystemTimeAsFileTime(&now);
+ }
+
+ ULARGE_INTEGER startUsec = {{start.dwLowDateTime, start.dwHighDateTime}};
+ ULARGE_INTEGER nowUsec = {{now.dwLowDateTime, now.dwHighDateTime}};
+
+ return (nowUsec.QuadPart - startUsec.QuadPart) / 10ULL;
+}
+
+} // namespace mozilla
diff --git a/mozglue/misc/TimeStamp_windows.h b/mozglue/misc/TimeStamp_windows.h
new file mode 100644
index 0000000000..1953aca62b
--- /dev/null
+++ b/mozglue/misc/TimeStamp_windows.h
@@ -0,0 +1,102 @@
+/* -*- 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 mozilla_TimeStamp_windows_h
+#define mozilla_TimeStamp_windows_h
+
+#include "mozilla/Types.h"
+
+namespace mozilla {
+
+/**
+ * The [mt] unit:
+ *
+ * Many values are kept in ticks of the Performance Counter x 1000,
+ * further just referred as [mt], meaning milli-ticks.
+ *
+ * This is needed to preserve maximum precision of the performance frequency
+ * representation. GetTickCount64 values in milliseconds are multiplied with
+ * frequency per second. Therefore we need to multiply QPC value by 1000 to
+ * have the same units to allow simple arithmentic with both QPC and GTC.
+ */
+#define ms2mt(x) ((x)*mozilla::GetQueryPerformanceFrequencyPerSec())
+#define mt2ms(x) ((x) / mozilla::GetQueryPerformanceFrequencyPerSec())
+#define mt2ms_f(x) (double(x) / mozilla::GetQueryPerformanceFrequencyPerSec())
+
+MFBT_API uint64_t GetQueryPerformanceFrequencyPerSec();
+
+class TimeStamp;
+class TimeStampValue;
+
+TimeStampValue NowInternal(bool aHighResolution);
+
+class TimeStampValue {
+ friend TimeStampValue NowInternal(bool);
+ friend bool IsCanonicalTimeStamp(TimeStampValue);
+ friend struct IPC::ParamTraits<mozilla::TimeStampValue>;
+ friend class TimeStamp;
+ friend class Fuzzyfox;
+
+ // Both QPC and GTC are kept in [mt] units.
+ uint64_t mGTC;
+ uint64_t mQPC;
+
+ bool mUsedCanonicalNow;
+ bool mIsNull;
+ bool mHasQPC;
+
+ MFBT_API TimeStampValue(uint64_t aGTC, uint64_t aQPC, bool aHasQPC,
+ bool aUsedCanonicalNow);
+
+ MFBT_API uint64_t CheckQPC(const TimeStampValue& aOther) const;
+
+ constexpr MOZ_IMPLICIT TimeStampValue()
+ : mGTC(0),
+ mQPC(0),
+ mUsedCanonicalNow(false),
+ mIsNull(true),
+ mHasQPC(false) {}
+
+ public:
+ MFBT_API uint64_t operator-(const TimeStampValue& aOther) const;
+
+ TimeStampValue operator+(const int64_t aOther) const {
+ return TimeStampValue(mGTC + aOther, mQPC + aOther, mHasQPC,
+ mUsedCanonicalNow);
+ }
+ TimeStampValue operator-(const int64_t aOther) const {
+ return TimeStampValue(mGTC - aOther, mQPC - aOther, mHasQPC,
+ mUsedCanonicalNow);
+ }
+ MFBT_API TimeStampValue& operator+=(const int64_t aOther);
+ MFBT_API TimeStampValue& operator-=(const int64_t aOther);
+
+ bool operator<(const TimeStampValue& aOther) const {
+ return int64_t(*this - aOther) < 0;
+ }
+ bool operator>(const TimeStampValue& aOther) const {
+ return int64_t(*this - aOther) > 0;
+ }
+ bool operator<=(const TimeStampValue& aOther) const {
+ return int64_t(*this - aOther) <= 0;
+ }
+ bool operator>=(const TimeStampValue& aOther) const {
+ return int64_t(*this - aOther) >= 0;
+ }
+ bool operator==(const TimeStampValue& aOther) const {
+ return int64_t(*this - aOther) == 0;
+ }
+ bool operator!=(const TimeStampValue& aOther) const {
+ return int64_t(*this - aOther) != 0;
+ }
+ bool UsedCanonicalNow() const { return mUsedCanonicalNow; }
+ void SetCanonicalNow() { mUsedCanonicalNow = true; }
+ bool IsNull() const { return mIsNull; }
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_TimeStamp_h */
diff --git a/mozglue/misc/Uptime.cpp b/mozglue/misc/Uptime.cpp
new file mode 100644
index 0000000000..bded4017ec
--- /dev/null
+++ b/mozglue/misc/Uptime.cpp
@@ -0,0 +1,150 @@
+/* -*- 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/. */
+
+#include "Uptime.h"
+
+#ifdef XP_WIN
+# include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#endif // XP_WIN
+
+#include <stdint.h>
+#include <inttypes.h>
+
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Assertions.h"
+
+using namespace mozilla;
+
+namespace {
+
+Maybe<uint64_t> NowIncludingSuspendMs();
+Maybe<uint64_t> NowExcludingSuspendMs();
+static Maybe<uint64_t> mStartExcludingSuspendMs;
+static Maybe<uint64_t> mStartIncludingSuspendMs;
+
+// Apple things
+#if defined(__APPLE__) && defined(__MACH__)
+# include <time.h>
+# include <sys/time.h>
+# include <sys/types.h>
+# include <mach/mach_time.h>
+
+const uint64_t kNSperMS = 1000000;
+
+Maybe<uint64_t> NowExcludingSuspendMs() {
+ return Some(clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / kNSperMS);
+}
+
+Maybe<uint64_t> NowIncludingSuspendMs() {
+ return Some(clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW) / kNSperMS);
+}
+
+#endif // macOS
+
+#if defined(XP_WIN)
+
+// Number of hundreds of nanoseconds in a millisecond
+static constexpr uint64_t kHNSperMS = 10000;
+
+Maybe<uint64_t> NowExcludingSuspendMs() {
+ ULONGLONG interrupt_time;
+ if (!QueryUnbiasedInterruptTime(&interrupt_time)) {
+ return Nothing();
+ }
+ return Some(interrupt_time / kHNSperMS);
+}
+
+Maybe<uint64_t> NowIncludingSuspendMs() {
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<void(WINAPI*)(
+ PULONGLONG)>
+ pQueryInterruptTime(L"KernelBase.dll", "QueryInterruptTime");
+ if (!pQueryInterruptTime) {
+ // On Windows, this does include the time the computer was suspended so it's
+ // an adequate fallback.
+ TimeStamp processCreation = TimeStamp::ProcessCreation();
+ TimeStamp now = TimeStamp::Now();
+ if (!processCreation.IsNull() && !now.IsNull()) {
+ return Some(uint64_t((now - processCreation).ToMilliseconds()));
+ } else {
+ return Nothing();
+ }
+ }
+ ULONGLONG interrupt_time;
+ pQueryInterruptTime(&interrupt_time);
+ return Some(interrupt_time / kHNSperMS);
+}
+#endif // XP_WIN
+
+#if defined(XP_LINUX) // including Android
+# include <time.h>
+
+// Number of nanoseconds in a millisecond.
+static constexpr uint64_t kNSperMS = 1000000;
+
+uint64_t TimespecToMilliseconds(struct timespec aTs) {
+ return aTs.tv_sec * 1000 + aTs.tv_nsec / kNSperMS;
+}
+
+Maybe<uint64_t> NowExcludingSuspendMs() {
+ struct timespec ts = {0};
+
+ if (clock_gettime(CLOCK_MONOTONIC, &ts)) {
+ return Nothing();
+ }
+ return Some(TimespecToMilliseconds(ts));
+}
+
+Maybe<uint64_t> NowIncludingSuspendMs() {
+# ifndef CLOCK_BOOTTIME
+ return Nothing();
+# else
+ struct timespec ts = {0};
+
+ if (clock_gettime(CLOCK_BOOTTIME, &ts)) {
+ return Nothing();
+ }
+ return Some(TimespecToMilliseconds(ts));
+# endif
+}
+
+#endif // XP_LINUX
+
+}; // anonymous namespace
+
+namespace mozilla {
+
+void InitializeUptime() {
+ MOZ_RELEASE_ASSERT(mStartIncludingSuspendMs.isNothing() &&
+ mStartExcludingSuspendMs.isNothing(),
+ "Must not be called more than once");
+ mStartIncludingSuspendMs = NowIncludingSuspendMs();
+ mStartExcludingSuspendMs = NowExcludingSuspendMs();
+}
+
+Maybe<uint64_t> ProcessUptimeMs() {
+ if (!mStartIncludingSuspendMs) {
+ return Nothing();
+ }
+ Maybe<uint64_t> maybeNow = NowIncludingSuspendMs();
+ if (!maybeNow) {
+ return Nothing();
+ }
+ return Some(maybeNow.value() - mStartIncludingSuspendMs.value());
+}
+
+Maybe<uint64_t> ProcessUptimeExcludingSuspendMs() {
+ if (!mStartExcludingSuspendMs) {
+ return Nothing();
+ }
+ Maybe<uint64_t> maybeNow = NowExcludingSuspendMs();
+ if (!maybeNow) {
+ return Nothing();
+ }
+ return Some(maybeNow.value() - mStartExcludingSuspendMs.value());
+}
+
+}; // namespace mozilla
diff --git a/mozglue/misc/Uptime.h b/mozglue/misc/Uptime.h
new file mode 100644
index 0000000000..4438e0d6d1
--- /dev/null
+++ b/mozglue/misc/Uptime.h
@@ -0,0 +1,26 @@
+/* -*- 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 mozilla_Uptime_h
+#define mozilla_Uptime_h
+
+#include <stdint.h>
+
+#include "mozilla/Maybe.h"
+
+namespace mozilla {
+
+// Called at the beginning of the process from TimeStamp::Startup.
+MFBT_API void InitializeUptime();
+// Returns the number of milliseconds the calling process has lived for.
+MFBT_API Maybe<uint64_t> ProcessUptimeMs();
+// Returns the number of milliseconds the calling process has lived for,
+// excluding the time period the system was suspended.
+MFBT_API Maybe<uint64_t> ProcessUptimeExcludingSuspendMs();
+
+}; // namespace mozilla
+
+#endif // mozilla_Uptime_h
diff --git a/mozglue/misc/WinUtils.h b/mozglue/misc/WinUtils.h
new file mode 100644
index 0000000000..2291a352a5
--- /dev/null
+++ b/mozglue/misc/WinUtils.h
@@ -0,0 +1,140 @@
+/* -*- 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 mozilla_glue_MozglueUtils_h
+#define mozilla_glue_MozglueUtils_h
+
+#include <windows.h>
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace glue {
+
+#ifdef DEBUG
+
+class MOZ_STATIC_CLASS Win32SRWLock final {
+ public:
+ // Microsoft guarantees that '0' is never a valid thread id
+ // https://docs.microsoft.com/en-ca/windows/desktop/ProcThread/thread-handles-and-identifiers
+ static const DWORD kInvalidThreadId = 0;
+
+ constexpr Win32SRWLock()
+ : mExclusiveThreadId(kInvalidThreadId), mLock(SRWLOCK_INIT) {}
+
+ ~Win32SRWLock() { MOZ_ASSERT(mExclusiveThreadId == kInvalidThreadId); }
+
+ void LockShared() {
+ MOZ_ASSERT(
+ mExclusiveThreadId != GetCurrentThreadId(),
+ "Deadlock detected - A thread attempted to acquire a shared lock on "
+ "a SRWLOCK when it already owns the exclusive lock on it.");
+
+ ::AcquireSRWLockShared(&mLock);
+ }
+
+ void UnlockShared() { ::ReleaseSRWLockShared(&mLock); }
+
+ void LockExclusive() {
+ MOZ_ASSERT(
+ mExclusiveThreadId != GetCurrentThreadId(),
+ "Deadlock detected - A thread attempted to acquire an exclusive lock "
+ "on a SRWLOCK when it already owns the exclusive lock on it.");
+
+ ::AcquireSRWLockExclusive(&mLock);
+ mExclusiveThreadId = GetCurrentThreadId();
+ }
+
+ void UnlockExclusive() {
+ MOZ_ASSERT(mExclusiveThreadId == GetCurrentThreadId());
+
+ mExclusiveThreadId = kInvalidThreadId;
+ ::ReleaseSRWLockExclusive(&mLock);
+ }
+
+ Win32SRWLock(const Win32SRWLock&) = delete;
+ Win32SRWLock(Win32SRWLock&&) = delete;
+ Win32SRWLock& operator=(const Win32SRWLock&) = delete;
+ Win32SRWLock& operator=(Win32SRWLock&&) = delete;
+
+ private:
+ // "Relaxed" memory ordering is fine. Threads will see other thread IDs
+ // appear here in some non-deterministic ordering (or not at all) and simply
+ // ignore them.
+ //
+ // But a thread will only read its own ID if it previously wrote it, and a
+ // single thread doesn't need a memory barrier to read its own write.
+
+ Atomic<DWORD, Relaxed> mExclusiveThreadId;
+ SRWLOCK mLock;
+};
+
+#else // DEBUG
+
+class MOZ_STATIC_CLASS Win32SRWLock final {
+ public:
+ constexpr Win32SRWLock() : mLock(SRWLOCK_INIT) {}
+
+ void LockShared() { ::AcquireSRWLockShared(&mLock); }
+
+ void UnlockShared() { ::ReleaseSRWLockShared(&mLock); }
+
+ void LockExclusive() { ::AcquireSRWLockExclusive(&mLock); }
+
+ void UnlockExclusive() { ::ReleaseSRWLockExclusive(&mLock); }
+
+ ~Win32SRWLock() = default;
+
+ Win32SRWLock(const Win32SRWLock&) = delete;
+ Win32SRWLock(Win32SRWLock&&) = delete;
+ Win32SRWLock& operator=(const Win32SRWLock&) = delete;
+ Win32SRWLock& operator=(Win32SRWLock&&) = delete;
+
+ private:
+ SRWLOCK mLock;
+};
+
+#endif
+
+class MOZ_RAII AutoSharedLock final {
+ public:
+ explicit AutoSharedLock(Win32SRWLock& aLock) : mLock(aLock) {
+ mLock.LockShared();
+ }
+
+ ~AutoSharedLock() { mLock.UnlockShared(); }
+
+ AutoSharedLock(const AutoSharedLock&) = delete;
+ AutoSharedLock(AutoSharedLock&&) = delete;
+ AutoSharedLock& operator=(const AutoSharedLock&) = delete;
+ AutoSharedLock& operator=(AutoSharedLock&&) = delete;
+
+ private:
+ Win32SRWLock& mLock;
+};
+
+class MOZ_RAII AutoExclusiveLock final {
+ public:
+ explicit AutoExclusiveLock(Win32SRWLock& aLock) : mLock(aLock) {
+ mLock.LockExclusive();
+ }
+
+ ~AutoExclusiveLock() { mLock.UnlockExclusive(); }
+
+ AutoExclusiveLock(const AutoExclusiveLock&) = delete;
+ AutoExclusiveLock(AutoExclusiveLock&&) = delete;
+ AutoExclusiveLock& operator=(const AutoExclusiveLock&) = delete;
+ AutoExclusiveLock& operator=(AutoExclusiveLock&&) = delete;
+
+ private:
+ Win32SRWLock& mLock;
+};
+
+} // namespace glue
+} // namespace mozilla
+
+#endif // mozilla_glue_MozglueUtils_h
diff --git a/mozglue/misc/WindowsDpiAwareness.h b/mozglue/misc/WindowsDpiAwareness.h
new file mode 100644
index 0000000000..104b135536
--- /dev/null
+++ b/mozglue/misc/WindowsDpiAwareness.h
@@ -0,0 +1,41 @@
+/* -*- 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 WindowsDpiAwareness_h_
+#define WindowsDpiAwareness_h_
+
+#include <windows.h>
+
+#if !defined(DPI_AWARENESS_CONTEXT_DECLARED) && \
+ !defined(DPI_AWARENESS_CONTEXT_UNAWARE)
+
+DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
+
+typedef enum DPI_AWARENESS {
+ DPI_AWARENESS_INVALID = -1,
+ DPI_AWARENESS_UNAWARE = 0,
+ DPI_AWARENESS_SYSTEM_AWARE = 1,
+ DPI_AWARENESS_PER_MONITOR_AWARE = 2
+} DPI_AWARENESS;
+
+# define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT)-1)
+# define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2)
+# define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3)
+
+# define DPI_AWARENESS_CONTEXT_DECLARED
+#endif // (DPI_AWARENESS_CONTEXT_DECLARED)
+
+#if WINVER < 0x0605
+WINUSERAPI DPI_AWARENESS_CONTEXT WINAPI GetThreadDpiAwarenessContext();
+WINUSERAPI BOOL WINAPI AreDpiAwarenessContextsEqual(DPI_AWARENESS_CONTEXT,
+ DPI_AWARENESS_CONTEXT);
+#endif /* WINVER < 0x0605 */
+typedef DPI_AWARENESS_CONTEXT(WINAPI* SetThreadDpiAwarenessContextProc)(
+ DPI_AWARENESS_CONTEXT);
+typedef BOOL(WINAPI* EnableNonClientDpiScalingProc)(HWND);
+typedef int(WINAPI* GetSystemMetricsForDpiProc)(int, UINT);
+
+#endif
diff --git a/mozglue/misc/WindowsMapRemoteView.cpp b/mozglue/misc/WindowsMapRemoteView.cpp
new file mode 100644
index 0000000000..4cd60ba7f1
--- /dev/null
+++ b/mozglue/misc/WindowsMapRemoteView.cpp
@@ -0,0 +1,124 @@
+/* -*- 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/. */
+
+#include "mozilla/WindowsMapRemoteView.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+
+#include <winternl.h>
+
+#if (NTDDI_VERSION < NTDDI_WIN10_RS2)
+
+// MapViewOfFile2 is just an inline function that calls MapViewOfFileNuma2 with
+// its preferred node set to NUMA_NO_PREFERRED_NODE
+WINBASEAPI PVOID WINAPI MapViewOfFileNuma2(HANDLE aFileMapping, HANDLE aProcess,
+ ULONG64 aOffset, PVOID aBaseAddress,
+ SIZE_T aViewSize,
+ ULONG aAllocationType,
+ ULONG aPageProtection,
+ ULONG aPreferredNode);
+
+WINBASEAPI BOOL WINAPI UnmapViewOfFile2(HANDLE aProcess, PVOID aBaseAddress,
+ ULONG aUnmapFlags);
+
+#endif // (NTDDI_VERSION < NTDDI_WIN10_RS2)
+
+enum SECTION_INHERIT { ViewShare = 1, ViewUnmap = 2 };
+
+NTSTATUS NTAPI NtMapViewOfSection(
+ HANDLE aSection, HANDLE aProcess, PVOID* aBaseAddress, ULONG_PTR aZeroBits,
+ SIZE_T aCommitSize, PLARGE_INTEGER aSectionOffset, PSIZE_T aViewSize,
+ SECTION_INHERIT aInheritDisposition, ULONG aAllocationType,
+ ULONG aProtectionFlags);
+
+NTSTATUS NTAPI NtUnmapViewOfSection(HANDLE aProcess, PVOID aBaseAddress);
+
+static DWORD GetWin32ErrorCode(NTSTATUS aNtStatus) {
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<decltype(
+ &RtlNtStatusToDosError)>
+ pRtlNtStatusToDosError(L"ntdll.dll", "RtlNtStatusToDosError");
+
+ MOZ_ASSERT(!!pRtlNtStatusToDosError);
+ if (!pRtlNtStatusToDosError) {
+ return ERROR_GEN_FAILURE;
+ }
+
+ return pRtlNtStatusToDosError(aNtStatus);
+}
+
+namespace mozilla {
+
+MFBT_API void* MapRemoteViewOfFile(HANDLE aFileMapping, HANDLE aProcess,
+ ULONG64 aOffset, PVOID aBaseAddress,
+ SIZE_T aViewSize, ULONG aAllocationType,
+ ULONG aProtectionFlags) {
+ static const StaticDynamicallyLinkedFunctionPtr<decltype(&MapViewOfFileNuma2)>
+ pMapViewOfFileNuma2(L"Api-ms-win-core-memory-l1-1-5.dll",
+ "MapViewOfFileNuma2");
+
+ if (!!pMapViewOfFileNuma2) {
+ return pMapViewOfFileNuma2(aFileMapping, aProcess, aOffset, aBaseAddress,
+ aViewSize, aAllocationType, aProtectionFlags,
+ NUMA_NO_PREFERRED_NODE);
+ }
+
+ static const StaticDynamicallyLinkedFunctionPtr<decltype(&NtMapViewOfSection)>
+ pNtMapViewOfSection(L"ntdll.dll", "NtMapViewOfSection");
+
+ MOZ_ASSERT(!!pNtMapViewOfSection);
+ if (!pNtMapViewOfSection) {
+ return nullptr;
+ }
+
+ // For the sake of consistency, we only permit the same flags that
+ // MapViewOfFileNuma2 allows
+ if (aAllocationType != 0 && aAllocationType != MEM_RESERVE &&
+ aAllocationType != MEM_LARGE_PAGES) {
+ ::SetLastError(ERROR_INVALID_PARAMETER);
+ return nullptr;
+ }
+
+ NTSTATUS ntStatus;
+
+ LARGE_INTEGER offset;
+ offset.QuadPart = aOffset;
+
+ ntStatus = pNtMapViewOfSection(aFileMapping, aProcess, &aBaseAddress, 0, 0,
+ &offset, &aViewSize, ViewUnmap,
+ aAllocationType, aProtectionFlags);
+ if (NT_SUCCESS(ntStatus)) {
+ ::SetLastError(ERROR_SUCCESS);
+ return aBaseAddress;
+ }
+
+ ::SetLastError(GetWin32ErrorCode(ntStatus));
+ return nullptr;
+}
+
+MFBT_API bool UnmapRemoteViewOfFile(HANDLE aProcess, PVOID aBaseAddress) {
+ static const StaticDynamicallyLinkedFunctionPtr<decltype(&UnmapViewOfFile2)>
+ pUnmapViewOfFile2(L"kernel32.dll", "UnmapViewOfFile2");
+
+ if (!!pUnmapViewOfFile2) {
+ return !!pUnmapViewOfFile2(aProcess, aBaseAddress, 0);
+ }
+
+ static const StaticDynamicallyLinkedFunctionPtr<decltype(
+ &NtUnmapViewOfSection)>
+ pNtUnmapViewOfSection(L"ntdll.dll", "NtUnmapViewOfSection");
+
+ MOZ_ASSERT(!!pNtUnmapViewOfSection);
+ if (!pNtUnmapViewOfSection) {
+ return false;
+ }
+
+ NTSTATUS ntStatus = pNtUnmapViewOfSection(aProcess, aBaseAddress);
+ ::SetLastError(GetWin32ErrorCode(ntStatus));
+ return NT_SUCCESS(ntStatus);
+}
+
+} // namespace mozilla
diff --git a/mozglue/misc/WindowsMapRemoteView.h b/mozglue/misc/WindowsMapRemoteView.h
new file mode 100644
index 0000000000..6ab88074b5
--- /dev/null
+++ b/mozglue/misc/WindowsMapRemoteView.h
@@ -0,0 +1,25 @@
+/* -*- 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_WindowsMapRemoteView_h
+#define mozilla_WindowsMapRemoteView_h
+
+#include "mozilla/Types.h"
+
+#include <windows.h>
+
+namespace mozilla {
+
+MFBT_API PVOID MapRemoteViewOfFile(HANDLE aFileMapping, HANDLE aProcess,
+ ULONG64 aOffset, PVOID aBaseAddress,
+ SIZE_T aViewSize, ULONG aAllocationType,
+ ULONG aProtectionFlags);
+
+MFBT_API bool UnmapRemoteViewOfFile(HANDLE aProcess, PVOID aBaseAddress);
+
+} // namespace mozilla
+
+#endif // mozilla_WindowsMapRemoteView_h
diff --git a/mozglue/misc/WindowsProcessMitigations.cpp b/mozglue/misc/WindowsProcessMitigations.cpp
new file mode 100644
index 0000000000..647def7217
--- /dev/null
+++ b/mozglue/misc/WindowsProcessMitigations.cpp
@@ -0,0 +1,77 @@
+/* -*- 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/. */
+
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "mozilla/WindowsProcessMitigations.h"
+
+#include <processthreadsapi.h>
+
+#if (_WIN32_WINNT < 0x0602)
+BOOL WINAPI GetProcessMitigationPolicy(
+ HANDLE hProcess, PROCESS_MITIGATION_POLICY MitigationPolicy, PVOID lpBuffer,
+ SIZE_T dwLength);
+#endif // (_WIN32_WINNT < 0x0602)
+
+namespace mozilla {
+
+static decltype(&::GetProcessMitigationPolicy)
+FetchGetProcessMitigationPolicyFunc() {
+ static const StaticDynamicallyLinkedFunctionPtr<decltype(
+ &::GetProcessMitigationPolicy)>
+ pGetProcessMitigationPolicy(L"kernel32.dll",
+ "GetProcessMitigationPolicy");
+ return pGetProcessMitigationPolicy;
+}
+
+MFBT_API bool IsWin32kLockedDown() {
+ auto pGetProcessMitigationPolicy = FetchGetProcessMitigationPolicyFunc();
+ if (!pGetProcessMitigationPolicy) {
+ return false;
+ }
+
+ PROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY polInfo;
+ if (!pGetProcessMitigationPolicy(::GetCurrentProcess(),
+ ProcessSystemCallDisablePolicy, &polInfo,
+ sizeof(polInfo))) {
+ return false;
+ }
+
+ return polInfo.DisallowWin32kSystemCalls;
+}
+
+MFBT_API bool IsDynamicCodeDisabled() {
+ auto pGetProcessMitigationPolicy = FetchGetProcessMitigationPolicyFunc();
+ if (!pGetProcessMitigationPolicy) {
+ return false;
+ }
+
+ PROCESS_MITIGATION_DYNAMIC_CODE_POLICY polInfo;
+ if (!pGetProcessMitigationPolicy(::GetCurrentProcess(),
+ ProcessDynamicCodePolicy, &polInfo,
+ sizeof(polInfo))) {
+ return false;
+ }
+
+ return polInfo.ProhibitDynamicCode;
+}
+
+MFBT_API bool IsEafPlusEnabled() {
+ auto pGetProcessMitigationPolicy = FetchGetProcessMitigationPolicyFunc();
+ if (!pGetProcessMitigationPolicy) {
+ return false;
+ }
+
+ PROCESS_MITIGATION_PAYLOAD_RESTRICTION_POLICY polInfo;
+ if (!pGetProcessMitigationPolicy(::GetCurrentProcess(),
+ ProcessPayloadRestrictionPolicy, &polInfo,
+ sizeof(polInfo))) {
+ return false;
+ }
+
+ return polInfo.EnableExportAddressFilterPlus;
+}
+
+} // namespace mozilla
diff --git a/mozglue/misc/WindowsProcessMitigations.h b/mozglue/misc/WindowsProcessMitigations.h
new file mode 100644
index 0000000000..31a93f9b69
--- /dev/null
+++ b/mozglue/misc/WindowsProcessMitigations.h
@@ -0,0 +1,20 @@
+/* -*- 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 mozilla_WindowsProcessMitigations_h
+#define mozilla_WindowsProcessMitigations_h
+
+#include "mozilla/Types.h"
+
+namespace mozilla {
+
+MFBT_API bool IsWin32kLockedDown();
+MFBT_API bool IsDynamicCodeDisabled();
+MFBT_API bool IsEafPlusEnabled();
+
+} // namespace mozilla
+
+#endif // mozilla_WindowsProcessMitigations_h
diff --git a/mozglue/misc/WindowsUnicode.cpp b/mozglue/misc/WindowsUnicode.cpp
new file mode 100644
index 0000000000..464380b6da
--- /dev/null
+++ b/mozglue/misc/WindowsUnicode.cpp
@@ -0,0 +1,59 @@
+/* -*- 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/. */
+
+#include "WindowsUnicode.h"
+
+#include <windows.h>
+// For UNICODE_STRING
+#include <winternl.h>
+
+#include <string.h>
+
+namespace mozilla {
+namespace glue {
+
+mozilla::UniquePtr<char[]> WideToUTF8(const wchar_t* aStr,
+ const size_t aStrLenExclNul) {
+ int numConv = ::WideCharToMultiByte(CP_UTF8, 0, aStr, aStrLenExclNul, nullptr,
+ 0, nullptr, nullptr);
+ if (!numConv) {
+ return nullptr;
+ }
+
+ // Include room for the null terminator by adding one
+ auto buf = mozilla::MakeUnique<char[]>(numConv + 1);
+
+ numConv = ::WideCharToMultiByte(CP_UTF8, 0, aStr, aStrLenExclNul, buf.get(),
+ numConv, nullptr, nullptr);
+ if (!numConv) {
+ return nullptr;
+ }
+
+ // Add null termination. numConv does not include the terminator, so we don't
+ // subtract 1 when indexing into buf.
+ buf[numConv] = 0;
+
+ return buf;
+}
+
+mozilla::UniquePtr<char[]> WideToUTF8(const wchar_t* aStr) {
+ return WideToUTF8(aStr, wcslen(aStr));
+}
+
+mozilla::UniquePtr<char[]> WideToUTF8(const std::wstring& aStr) {
+ return WideToUTF8(aStr.data(), aStr.length());
+}
+
+mozilla::UniquePtr<char[]> WideToUTF8(PCUNICODE_STRING aStr) {
+ if (!aStr) {
+ return nullptr;
+ }
+
+ return WideToUTF8(aStr->Buffer, aStr->Length / sizeof(WCHAR));
+}
+
+} // namespace glue
+} // namespace mozilla
diff --git a/mozglue/misc/WindowsUnicode.h b/mozglue/misc/WindowsUnicode.h
new file mode 100644
index 0000000000..77fc376b92
--- /dev/null
+++ b/mozglue/misc/WindowsUnicode.h
@@ -0,0 +1,35 @@
+/* -*- 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_glue_WindowsUnicode_h
+#define mozilla_glue_WindowsUnicode_h
+
+#include "mozilla/UniquePtr.h"
+
+#include <string>
+
+struct _UNICODE_STRING;
+
+namespace mozilla {
+namespace glue {
+
+mozilla::UniquePtr<char[]> WideToUTF8(const wchar_t* aStr,
+ const size_t aStrLenExclNul);
+
+mozilla::UniquePtr<char[]> WideToUTF8(const wchar_t* aStr);
+mozilla::UniquePtr<char[]> WideToUTF8(const std::wstring& aStr);
+mozilla::UniquePtr<char[]> WideToUTF8(const _UNICODE_STRING* aStr);
+
+#if defined(bstr_t)
+inline mozilla::UniquePtr<char[]> WideToUTF8(const _bstr_t& aStr) {
+ return WideToUTF8(static_cast<const wchar_t*>(aStr), aStr.length());
+}
+#endif // defined(bstr_t)
+
+} // namespace glue
+} // namespace mozilla
+
+#endif // mozilla_glue_WindowsUnicode_h
diff --git a/mozglue/misc/decimal/Decimal.cpp b/mozglue/misc/decimal/Decimal.cpp
new file mode 100644
index 0000000000..cc828e2843
--- /dev/null
+++ b/mozglue/misc/decimal/Decimal.cpp
@@ -0,0 +1,1063 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Decimal.h"
+#include "moz-decimal-utils.h"
+#include "DoubleConversion.h"
+
+using namespace moz_decimal_utils;
+
+#include <algorithm>
+#include <float.h>
+
+namespace blink {
+
+namespace DecimalPrivate {
+
+static int const ExponentMax = 1023;
+static int const ExponentMin = -1023;
+static int const Precision = 18;
+
+static const uint64_t MaxCoefficient = UINT64_C(0xDE0B6B3A763FFFF); // 999999999999999999 == 18 9's
+
+// This class handles Decimal special values.
+class SpecialValueHandler {
+ STACK_ALLOCATED();
+ WTF_MAKE_NONCOPYABLE(SpecialValueHandler);
+public:
+ enum HandleResult {
+ BothFinite,
+ BothInfinity,
+ EitherNaN,
+ LHSIsInfinity,
+ RHSIsInfinity,
+ };
+
+ SpecialValueHandler(const Decimal& lhs, const Decimal& rhs);
+ HandleResult handle();
+ Decimal value() const;
+
+private:
+ enum Result {
+ ResultIsLHS,
+ ResultIsRHS,
+ ResultIsUnknown,
+ };
+
+ const Decimal& m_lhs;
+ const Decimal& m_rhs;
+ Result m_result;
+};
+
+SpecialValueHandler::SpecialValueHandler(const Decimal& lhs, const Decimal& rhs)
+ : m_lhs(lhs), m_rhs(rhs), m_result(ResultIsUnknown)
+{
+}
+
+SpecialValueHandler::HandleResult SpecialValueHandler::handle()
+{
+ if (m_lhs.isFinite() && m_rhs.isFinite())
+ return BothFinite;
+
+ const Decimal::EncodedData::FormatClass lhsClass = m_lhs.value().formatClass();
+ const Decimal::EncodedData::FormatClass rhsClass = m_rhs.value().formatClass();
+ if (lhsClass == Decimal::EncodedData::ClassNaN) {
+ m_result = ResultIsLHS;
+ return EitherNaN;
+ }
+
+ if (rhsClass == Decimal::EncodedData::ClassNaN) {
+ m_result = ResultIsRHS;
+ return EitherNaN;
+ }
+
+ if (lhsClass == Decimal::EncodedData::ClassInfinity)
+ return rhsClass == Decimal::EncodedData::ClassInfinity ? BothInfinity : LHSIsInfinity;
+
+ if (rhsClass == Decimal::EncodedData::ClassInfinity)
+ return RHSIsInfinity;
+
+ ASSERT_NOT_REACHED();
+ return BothFinite;
+}
+
+Decimal SpecialValueHandler::value() const
+{
+ switch (m_result) {
+ case ResultIsLHS:
+ return m_lhs;
+ case ResultIsRHS:
+ return m_rhs;
+ case ResultIsUnknown:
+ default:
+ ASSERT_NOT_REACHED();
+ return m_lhs;
+ }
+}
+
+// This class is used for 128 bit unsigned integer arithmetic.
+class UInt128 {
+public:
+ UInt128(uint64_t low, uint64_t high)
+ : m_high(high), m_low(low)
+ {
+ }
+
+ UInt128& operator/=(uint32_t);
+
+ uint64_t high() const { return m_high; }
+ uint64_t low() const { return m_low; }
+
+ static UInt128 multiply(uint64_t u, uint64_t v) { return UInt128(u * v, multiplyHigh(u, v)); }
+
+private:
+ static uint32_t highUInt32(uint64_t x) { return static_cast<uint32_t>(x >> 32); }
+ static uint32_t lowUInt32(uint64_t x) { return static_cast<uint32_t>(x & ((static_cast<uint64_t>(1) << 32) - 1)); }
+ static uint64_t makeUInt64(uint32_t low, uint32_t high) { return low | (static_cast<uint64_t>(high) << 32); }
+
+ static uint64_t multiplyHigh(uint64_t, uint64_t);
+
+ uint64_t m_high;
+ uint64_t m_low;
+};
+
+UInt128& UInt128::operator/=(const uint32_t divisor)
+{
+ ASSERT(divisor);
+
+ if (!m_high) {
+ m_low /= divisor;
+ return *this;
+ }
+
+ uint32_t dividend[4];
+ dividend[0] = lowUInt32(m_low);
+ dividend[1] = highUInt32(m_low);
+ dividend[2] = lowUInt32(m_high);
+ dividend[3] = highUInt32(m_high);
+
+ uint32_t quotient[4];
+ uint32_t remainder = 0;
+ for (int i = 3; i >= 0; --i) {
+ const uint64_t work = makeUInt64(dividend[i], remainder);
+ remainder = static_cast<uint32_t>(work % divisor);
+ quotient[i] = static_cast<uint32_t>(work / divisor);
+ }
+ m_low = makeUInt64(quotient[0], quotient[1]);
+ m_high = makeUInt64(quotient[2], quotient[3]);
+ return *this;
+}
+
+// Returns high 64bit of 128bit product.
+uint64_t UInt128::multiplyHigh(uint64_t u, uint64_t v)
+{
+ const uint64_t uLow = lowUInt32(u);
+ const uint64_t uHigh = highUInt32(u);
+ const uint64_t vLow = lowUInt32(v);
+ const uint64_t vHigh = highUInt32(v);
+ const uint64_t partialProduct = uHigh * vLow + highUInt32(uLow * vLow);
+ return uHigh * vHigh + highUInt32(partialProduct) + highUInt32(uLow * vHigh + lowUInt32(partialProduct));
+}
+
+static int countDigits(uint64_t x)
+{
+ int numberOfDigits = 0;
+ for (uint64_t powerOfTen = 1; x >= powerOfTen; powerOfTen *= 10) {
+ ++numberOfDigits;
+ if (powerOfTen >= std::numeric_limits<uint64_t>::max() / 10)
+ break;
+ }
+ return numberOfDigits;
+}
+
+static uint64_t scaleDown(uint64_t x, int n)
+{
+ ASSERT(n >= 0);
+ while (n > 0 && x) {
+ x /= 10;
+ --n;
+ }
+ return x;
+}
+
+static uint64_t scaleUp(uint64_t x, int n)
+{
+ ASSERT(n >= 0);
+ ASSERT(n <= Precision);
+
+ uint64_t y = 1;
+ uint64_t z = 10;
+ for (;;) {
+ if (n & 1)
+ y = y * z;
+
+ n >>= 1;
+ if (!n)
+ return x * y;
+
+ z = z * z;
+ }
+}
+
+} // namespace DecimalPrivate
+
+using namespace DecimalPrivate;
+
+Decimal::EncodedData::EncodedData(Sign sign, FormatClass formatClass)
+ : m_coefficient(0)
+ , m_exponent(0)
+ , m_formatClass(formatClass)
+ , m_sign(sign)
+{
+}
+
+Decimal::EncodedData::EncodedData(Sign sign, int exponent, uint64_t coefficient)
+ : m_formatClass(coefficient ? ClassNormal : ClassZero)
+ , m_sign(sign)
+{
+ if (exponent >= ExponentMin && exponent <= ExponentMax) {
+ while (coefficient > MaxCoefficient) {
+ coefficient /= 10;
+ ++exponent;
+ }
+ }
+
+ if (exponent > ExponentMax) {
+ m_coefficient = 0;
+ m_exponent = 0;
+ m_formatClass = ClassInfinity;
+ return;
+ }
+
+ if (exponent < ExponentMin) {
+ m_coefficient = 0;
+ m_exponent = 0;
+ m_formatClass = ClassZero;
+ return;
+ }
+
+ m_coefficient = coefficient;
+ m_exponent = static_cast<int16_t>(exponent);
+}
+
+bool Decimal::EncodedData::operator==(const EncodedData& another) const
+{
+ return m_sign == another.m_sign
+ && m_formatClass == another.m_formatClass
+ && m_exponent == another.m_exponent
+ && m_coefficient == another.m_coefficient;
+}
+
+Decimal::Decimal(int32_t i32)
+ : m_data(i32 < 0 ? Negative : Positive, 0, i32 < 0 ? static_cast<uint64_t>(-static_cast<int64_t>(i32)) : static_cast<uint64_t>(i32))
+{
+}
+
+Decimal::Decimal(Sign sign, int exponent, uint64_t coefficient)
+ : m_data(sign, coefficient ? exponent : 0, coefficient)
+{
+}
+
+Decimal::Decimal(const EncodedData& data)
+ : m_data(data)
+{
+}
+
+Decimal::Decimal(const Decimal& other)
+ : m_data(other.m_data)
+{
+}
+
+Decimal& Decimal::operator=(const Decimal& other)
+{
+ m_data = other.m_data;
+ return *this;
+}
+
+Decimal& Decimal::operator+=(const Decimal& other)
+{
+ m_data = (*this + other).m_data;
+ return *this;
+}
+
+Decimal& Decimal::operator-=(const Decimal& other)
+{
+ m_data = (*this - other).m_data;
+ return *this;
+}
+
+Decimal& Decimal::operator*=(const Decimal& other)
+{
+ m_data = (*this * other).m_data;
+ return *this;
+}
+
+Decimal& Decimal::operator/=(const Decimal& other)
+{
+ m_data = (*this / other).m_data;
+ return *this;
+}
+
+Decimal Decimal::operator-() const
+{
+ if (isNaN())
+ return *this;
+
+ Decimal result(*this);
+ result.m_data.setSign(invertSign(m_data.sign()));
+ return result;
+}
+
+Decimal Decimal::operator+(const Decimal& rhs) const
+{
+ const Decimal& lhs = *this;
+ const Sign lhsSign = lhs.sign();
+ const Sign rhsSign = rhs.sign();
+
+ SpecialValueHandler handler(lhs, rhs);
+ switch (handler.handle()) {
+ case SpecialValueHandler::BothFinite:
+ break;
+
+ case SpecialValueHandler::BothInfinity:
+ return lhsSign == rhsSign ? lhs : nan();
+
+ case SpecialValueHandler::EitherNaN:
+ return handler.value();
+
+ case SpecialValueHandler::LHSIsInfinity:
+ return lhs;
+
+ case SpecialValueHandler::RHSIsInfinity:
+ return rhs;
+ }
+
+ const AlignedOperands alignedOperands = alignOperands(lhs, rhs);
+
+ const uint64_t result = lhsSign == rhsSign
+ ? alignedOperands.lhsCoefficient + alignedOperands.rhsCoefficient
+ : alignedOperands.lhsCoefficient - alignedOperands.rhsCoefficient;
+
+ if (lhsSign == Negative && rhsSign == Positive && !result)
+ return Decimal(Positive, alignedOperands.exponent, 0);
+
+ return static_cast<int64_t>(result) >= 0
+ ? Decimal(lhsSign, alignedOperands.exponent, result)
+ : Decimal(invertSign(lhsSign), alignedOperands.exponent, -static_cast<int64_t>(result));
+}
+
+Decimal Decimal::operator-(const Decimal& rhs) const
+{
+ const Decimal& lhs = *this;
+ const Sign lhsSign = lhs.sign();
+ const Sign rhsSign = rhs.sign();
+
+ SpecialValueHandler handler(lhs, rhs);
+ switch (handler.handle()) {
+ case SpecialValueHandler::BothFinite:
+ break;
+
+ case SpecialValueHandler::BothInfinity:
+ return lhsSign == rhsSign ? nan() : lhs;
+
+ case SpecialValueHandler::EitherNaN:
+ return handler.value();
+
+ case SpecialValueHandler::LHSIsInfinity:
+ return lhs;
+
+ case SpecialValueHandler::RHSIsInfinity:
+ return infinity(invertSign(rhsSign));
+ }
+
+ const AlignedOperands alignedOperands = alignOperands(lhs, rhs);
+
+ const uint64_t result = lhsSign == rhsSign
+ ? alignedOperands.lhsCoefficient - alignedOperands.rhsCoefficient
+ : alignedOperands.lhsCoefficient + alignedOperands.rhsCoefficient;
+
+ if (lhsSign == Negative && rhsSign == Negative && !result)
+ return Decimal(Positive, alignedOperands.exponent, 0);
+
+ return static_cast<int64_t>(result) >= 0
+ ? Decimal(lhsSign, alignedOperands.exponent, result)
+ : Decimal(invertSign(lhsSign), alignedOperands.exponent, -static_cast<int64_t>(result));
+}
+
+Decimal Decimal::operator*(const Decimal& rhs) const
+{
+ const Decimal& lhs = *this;
+ const Sign lhsSign = lhs.sign();
+ const Sign rhsSign = rhs.sign();
+ const Sign resultSign = lhsSign == rhsSign ? Positive : Negative;
+
+ SpecialValueHandler handler(lhs, rhs);
+ switch (handler.handle()) {
+ case SpecialValueHandler::BothFinite: {
+ const uint64_t lhsCoefficient = lhs.m_data.coefficient();
+ const uint64_t rhsCoefficient = rhs.m_data.coefficient();
+ int resultExponent = lhs.exponent() + rhs.exponent();
+ UInt128 work(UInt128::multiply(lhsCoefficient, rhsCoefficient));
+ while (work.high()) {
+ work /= 10;
+ ++resultExponent;
+ }
+ return Decimal(resultSign, resultExponent, work.low());
+ }
+
+ case SpecialValueHandler::BothInfinity:
+ return infinity(resultSign);
+
+ case SpecialValueHandler::EitherNaN:
+ return handler.value();
+
+ case SpecialValueHandler::LHSIsInfinity:
+ return rhs.isZero() ? nan() : infinity(resultSign);
+
+ case SpecialValueHandler::RHSIsInfinity:
+ return lhs.isZero() ? nan() : infinity(resultSign);
+ }
+
+ ASSERT_NOT_REACHED();
+ return nan();
+}
+
+Decimal Decimal::operator/(const Decimal& rhs) const
+{
+ const Decimal& lhs = *this;
+ const Sign lhsSign = lhs.sign();
+ const Sign rhsSign = rhs.sign();
+ const Sign resultSign = lhsSign == rhsSign ? Positive : Negative;
+
+ SpecialValueHandler handler(lhs, rhs);
+ switch (handler.handle()) {
+ case SpecialValueHandler::BothFinite:
+ break;
+
+ case SpecialValueHandler::BothInfinity:
+ return nan();
+
+ case SpecialValueHandler::EitherNaN:
+ return handler.value();
+
+ case SpecialValueHandler::LHSIsInfinity:
+ return infinity(resultSign);
+
+ case SpecialValueHandler::RHSIsInfinity:
+ return zero(resultSign);
+ }
+
+ ASSERT(lhs.isFinite());
+ ASSERT(rhs.isFinite());
+
+ if (rhs.isZero())
+ return lhs.isZero() ? nan() : infinity(resultSign);
+
+ int resultExponent = lhs.exponent() - rhs.exponent();
+
+ if (lhs.isZero())
+ return Decimal(resultSign, resultExponent, 0);
+
+ uint64_t remainder = lhs.m_data.coefficient();
+ const uint64_t divisor = rhs.m_data.coefficient();
+ uint64_t result = 0;
+ for (;;) {
+ while (remainder < divisor && result < MaxCoefficient / 10) {
+ remainder *= 10;
+ result *= 10;
+ --resultExponent;
+ }
+ if (remainder < divisor)
+ break;
+ uint64_t quotient = remainder / divisor;
+ if (result > MaxCoefficient - quotient)
+ break;
+ result += quotient;
+ remainder %= divisor;
+ if (!remainder)
+ break;
+ }
+
+ if (remainder > divisor / 2)
+ ++result;
+
+ return Decimal(resultSign, resultExponent, result);
+}
+
+bool Decimal::operator==(const Decimal& rhs) const
+{
+ if (isNaN() || rhs.isNaN())
+ return false;
+ return m_data == rhs.m_data || compareTo(rhs).isZero();
+}
+
+bool Decimal::operator!=(const Decimal& rhs) const
+{
+ if (isNaN() || rhs.isNaN())
+ return true;
+ if (m_data == rhs.m_data)
+ return false;
+ const Decimal result = compareTo(rhs);
+ if (result.isNaN())
+ return false;
+ return !result.isZero();
+}
+
+bool Decimal::operator<(const Decimal& rhs) const
+{
+ const Decimal result = compareTo(rhs);
+ if (result.isNaN())
+ return false;
+ return !result.isZero() && result.isNegative();
+}
+
+bool Decimal::operator<=(const Decimal& rhs) const
+{
+ if (isNaN() || rhs.isNaN())
+ return false;
+ if (m_data == rhs.m_data)
+ return true;
+ const Decimal result = compareTo(rhs);
+ if (result.isNaN())
+ return false;
+ return result.isZero() || result.isNegative();
+}
+
+bool Decimal::operator>(const Decimal& rhs) const
+{
+ const Decimal result = compareTo(rhs);
+ if (result.isNaN())
+ return false;
+ return !result.isZero() && result.isPositive();
+}
+
+bool Decimal::operator>=(const Decimal& rhs) const
+{
+ if (isNaN() || rhs.isNaN())
+ return false;
+ if (m_data == rhs.m_data)
+ return true;
+ const Decimal result = compareTo(rhs);
+ if (result.isNaN())
+ return false;
+ return result.isZero() || !result.isNegative();
+}
+
+Decimal Decimal::abs() const
+{
+ Decimal result(*this);
+ result.m_data.setSign(Positive);
+ return result;
+}
+
+Decimal::AlignedOperands Decimal::alignOperands(const Decimal& lhs, const Decimal& rhs)
+{
+ ASSERT(lhs.isFinite());
+ ASSERT(rhs.isFinite());
+
+ const int lhsExponent = lhs.exponent();
+ const int rhsExponent = rhs.exponent();
+ int exponent = std::min(lhsExponent, rhsExponent);
+ uint64_t lhsCoefficient = lhs.m_data.coefficient();
+ uint64_t rhsCoefficient = rhs.m_data.coefficient();
+
+ if (lhsExponent > rhsExponent) {
+ const int numberOfLHSDigits = countDigits(lhsCoefficient);
+ if (numberOfLHSDigits) {
+ const int lhsShiftAmount = lhsExponent - rhsExponent;
+ const int overflow = numberOfLHSDigits + lhsShiftAmount - Precision;
+ if (overflow <= 0) {
+ lhsCoefficient = scaleUp(lhsCoefficient, lhsShiftAmount);
+ } else {
+ lhsCoefficient = scaleUp(lhsCoefficient, lhsShiftAmount - overflow);
+ rhsCoefficient = scaleDown(rhsCoefficient, overflow);
+ exponent += overflow;
+ }
+ }
+
+ } else if (lhsExponent < rhsExponent) {
+ const int numberOfRHSDigits = countDigits(rhsCoefficient);
+ if (numberOfRHSDigits) {
+ const int rhsShiftAmount = rhsExponent - lhsExponent;
+ const int overflow = numberOfRHSDigits + rhsShiftAmount - Precision;
+ if (overflow <= 0) {
+ rhsCoefficient = scaleUp(rhsCoefficient, rhsShiftAmount);
+ } else {
+ rhsCoefficient = scaleUp(rhsCoefficient, rhsShiftAmount - overflow);
+ lhsCoefficient = scaleDown(lhsCoefficient, overflow);
+ exponent += overflow;
+ }
+ }
+ }
+
+ AlignedOperands alignedOperands;
+ alignedOperands.exponent = exponent;
+ alignedOperands.lhsCoefficient = lhsCoefficient;
+ alignedOperands.rhsCoefficient = rhsCoefficient;
+ return alignedOperands;
+}
+
+static bool isMultiplePowersOfTen(uint64_t coefficient, int n)
+{
+ return !coefficient || !(coefficient % scaleUp(1, n));
+}
+
+// Round toward positive infinity.
+Decimal Decimal::ceil() const
+{
+ if (isSpecial())
+ return *this;
+
+ if (exponent() >= 0)
+ return *this;
+
+ uint64_t result = m_data.coefficient();
+ const int numberOfDigits = countDigits(result);
+ const int numberOfDropDigits = -exponent();
+ if (numberOfDigits <= numberOfDropDigits)
+ return isPositive() ? Decimal(1) : zero(Positive);
+
+ result = scaleDown(result, numberOfDropDigits);
+ if (isPositive() && !isMultiplePowersOfTen(m_data.coefficient(), numberOfDropDigits))
+ ++result;
+ return Decimal(sign(), 0, result);
+}
+
+Decimal Decimal::compareTo(const Decimal& rhs) const
+{
+ const Decimal result(*this - rhs);
+ switch (result.m_data.formatClass()) {
+ case EncodedData::ClassInfinity:
+ return result.isNegative() ? Decimal(-1) : Decimal(1);
+
+ case EncodedData::ClassNaN:
+ case EncodedData::ClassNormal:
+ return result;
+
+ case EncodedData::ClassZero:
+ return zero(Positive);
+
+ default:
+ ASSERT_NOT_REACHED();
+ return nan();
+ }
+}
+
+// Round toward negative infinity.
+Decimal Decimal::floor() const
+{
+ if (isSpecial())
+ return *this;
+
+ if (exponent() >= 0)
+ return *this;
+
+ uint64_t result = m_data.coefficient();
+ const int numberOfDigits = countDigits(result);
+ const int numberOfDropDigits = -exponent();
+ if (numberOfDigits < numberOfDropDigits)
+ return isPositive() ? zero(Positive) : Decimal(-1);
+
+ result = scaleDown(result, numberOfDropDigits);
+ if (isNegative() && !isMultiplePowersOfTen(m_data.coefficient(), numberOfDropDigits))
+ ++result;
+ return Decimal(sign(), 0, result);
+}
+
+Decimal Decimal::fromDouble(double doubleValue)
+{
+ if (std::isfinite(doubleValue))
+ return fromString(mozToString(doubleValue));
+
+ if (std::isinf(doubleValue))
+ return infinity(doubleValue < 0 ? Negative : Positive);
+
+ return nan();
+}
+
+Decimal Decimal::fromString(const String& str)
+{
+ int exponent = 0;
+ Sign exponentSign = Positive;
+ int numberOfDigits = 0;
+ int numberOfDigitsAfterDot = 0;
+ int numberOfExtraDigits = 0;
+ Sign sign = Positive;
+
+ enum {
+ StateDigit,
+ StateDot,
+ StateDotDigit,
+ StateE,
+ StateEDigit,
+ StateESign,
+ StateSign,
+ StateStart,
+ StateZero,
+ } state = StateStart;
+
+#define HandleCharAndBreak(expected, nextState) \
+ if (ch == expected) { \
+ state = nextState; \
+ break; \
+ }
+
+#define HandleTwoCharsAndBreak(expected1, expected2, nextState) \
+ if (ch == expected1 || ch == expected2) { \
+ state = nextState; \
+ break; \
+ }
+
+ uint64_t accumulator = 0;
+ for (unsigned index = 0; index < str.length(); ++index) {
+ const int ch = str[index];
+ switch (state) {
+ case StateDigit:
+ if (ch >= '0' && ch <= '9') {
+ if (numberOfDigits < Precision) {
+ ++numberOfDigits;
+ accumulator *= 10;
+ accumulator += ch - '0';
+ } else {
+ ++numberOfExtraDigits;
+ }
+ break;
+ }
+
+ HandleCharAndBreak('.', StateDot);
+ HandleTwoCharsAndBreak('E', 'e', StateE);
+ return nan();
+
+ case StateDot:
+ case StateDotDigit:
+ if (ch >= '0' && ch <= '9') {
+ if (numberOfDigits < Precision) {
+ ++numberOfDigits;
+ ++numberOfDigitsAfterDot;
+ accumulator *= 10;
+ accumulator += ch - '0';
+ }
+ state = StateDotDigit;
+ break;
+ }
+
+ HandleTwoCharsAndBreak('E', 'e', StateE);
+ return nan();
+
+ case StateE:
+ if (ch == '+') {
+ exponentSign = Positive;
+ state = StateESign;
+ break;
+ }
+
+ if (ch == '-') {
+ exponentSign = Negative;
+ state = StateESign;
+ break;
+ }
+
+ if (ch >= '0' && ch <= '9') {
+ exponent = ch - '0';
+ state = StateEDigit;
+ break;
+ }
+
+ return nan();
+
+ case StateEDigit:
+ if (ch >= '0' && ch <= '9') {
+ exponent *= 10;
+ exponent += ch - '0';
+ if (exponent > ExponentMax + Precision) {
+ if (accumulator)
+ return exponentSign == Negative ? zero(Positive) : infinity(sign);
+ return zero(sign);
+ }
+ state = StateEDigit;
+ break;
+ }
+
+ return nan();
+
+ case StateESign:
+ if (ch >= '0' && ch <= '9') {
+ exponent = ch - '0';
+ state = StateEDigit;
+ break;
+ }
+
+ return nan();
+
+ case StateSign:
+ if (ch >= '1' && ch <= '9') {
+ accumulator = ch - '0';
+ numberOfDigits = 1;
+ state = StateDigit;
+ break;
+ }
+
+ HandleCharAndBreak('0', StateZero);
+ return nan();
+
+ case StateStart:
+ if (ch >= '1' && ch <= '9') {
+ accumulator = ch - '0';
+ numberOfDigits = 1;
+ state = StateDigit;
+ break;
+ }
+
+ if (ch == '-') {
+ sign = Negative;
+ state = StateSign;
+ break;
+ }
+
+ if (ch == '+') {
+ sign = Positive;
+ state = StateSign;
+ break;
+ }
+
+ HandleCharAndBreak('0', StateZero);
+ HandleCharAndBreak('.', StateDot);
+ return nan();
+
+ case StateZero:
+ if (ch == '0')
+ break;
+
+ if (ch >= '1' && ch <= '9') {
+ accumulator = ch - '0';
+ numberOfDigits = 1;
+ state = StateDigit;
+ break;
+ }
+
+ HandleCharAndBreak('.', StateDot);
+ HandleTwoCharsAndBreak('E', 'e', StateE);
+ return nan();
+
+ default:
+ ASSERT_NOT_REACHED();
+ return nan();
+ }
+ }
+
+ if (state == StateZero)
+ return zero(sign);
+
+ if (state == StateDigit || state == StateEDigit || state == StateDotDigit) {
+ int resultExponent = exponent * (exponentSign == Negative ? -1 : 1) - numberOfDigitsAfterDot + numberOfExtraDigits;
+ if (resultExponent < ExponentMin)
+ return zero(Positive);
+
+ const int overflow = resultExponent - ExponentMax + 1;
+ if (overflow > 0) {
+ if (overflow + numberOfDigits - numberOfDigitsAfterDot > Precision)
+ return infinity(sign);
+ accumulator = scaleUp(accumulator, overflow);
+ resultExponent -= overflow;
+ }
+
+ return Decimal(sign, resultExponent, accumulator);
+ }
+
+ return nan();
+}
+
+Decimal Decimal::infinity(const Sign sign)
+{
+ return Decimal(EncodedData(sign, EncodedData::ClassInfinity));
+}
+
+Decimal Decimal::nan()
+{
+ return Decimal(EncodedData(Positive, EncodedData::ClassNaN));
+}
+
+Decimal Decimal::remainder(const Decimal& rhs) const
+{
+ const Decimal quotient = *this / rhs;
+ return quotient.isSpecial() ? quotient : *this - (quotient.isNegative() ? quotient.ceil() : quotient.floor()) * rhs;
+}
+
+Decimal Decimal::round() const
+{
+ if (isSpecial())
+ return *this;
+
+ if (exponent() >= 0)
+ return *this;
+
+ uint64_t result = m_data.coefficient();
+ const int numberOfDigits = countDigits(result);
+ const int numberOfDropDigits = -exponent();
+ if (numberOfDigits < numberOfDropDigits)
+ return zero(Positive);
+
+ result = scaleDown(result, numberOfDropDigits - 1);
+ if (result % 10 >= 5)
+ result += 10;
+ result /= 10;
+ return Decimal(sign(), 0, result);
+}
+
+double Decimal::toDouble() const
+{
+ if (isFinite()) {
+ bool valid;
+ const double doubleValue = mozToDouble(toString(), &valid);
+ return valid ? doubleValue : std::numeric_limits<double>::quiet_NaN();
+ }
+
+ if (isInfinity())
+ return isNegative() ? -std::numeric_limits<double>::infinity() : std::numeric_limits<double>::infinity();
+
+ return std::numeric_limits<double>::quiet_NaN();
+}
+
+String Decimal::toString() const
+{
+ switch (m_data.formatClass()) {
+ case EncodedData::ClassInfinity:
+ return sign() ? "-Infinity" : "Infinity";
+
+ case EncodedData::ClassNaN:
+ return "NaN";
+
+ case EncodedData::ClassNormal:
+ case EncodedData::ClassZero:
+ break;
+
+ default:
+ ASSERT_NOT_REACHED();
+ return "";
+ }
+
+ StringBuilder builder;
+ if (sign())
+ builder.append('-');
+
+ int originalExponent = exponent();
+ uint64_t coefficient = m_data.coefficient();
+
+ if (originalExponent < 0) {
+ const int maxDigits = DBL_DIG;
+ uint64_t lastDigit = 0;
+ while (countDigits(coefficient) > maxDigits) {
+ lastDigit = coefficient % 10;
+ coefficient /= 10;
+ ++originalExponent;
+ }
+
+ if (lastDigit >= 5)
+ ++coefficient;
+
+ while (originalExponent < 0 && coefficient && !(coefficient % 10)) {
+ coefficient /= 10;
+ ++originalExponent;
+ }
+ }
+
+ const String digits = mozToString(coefficient);
+ int coefficientLength = static_cast<int>(digits.length());
+ const int adjustedExponent = originalExponent + coefficientLength - 1;
+ if (originalExponent <= 0 && adjustedExponent >= -6) {
+ if (!originalExponent) {
+ builder.append(digits);
+ return builder.toString();
+ }
+
+ if (adjustedExponent >= 0) {
+ for (int i = 0; i < coefficientLength; ++i) {
+ builder.append(digits[i]);
+ if (i == adjustedExponent)
+ builder.append('.');
+ }
+ return builder.toString();
+ }
+
+ builder.appendLiteral("0.");
+ for (int i = adjustedExponent + 1; i < 0; ++i)
+ builder.append('0');
+
+ builder.append(digits);
+
+ } else {
+ builder.append(digits[0]);
+ while (coefficientLength >= 2 && digits[coefficientLength - 1] == '0')
+ --coefficientLength;
+ if (coefficientLength >= 2) {
+ builder.append('.');
+ for (int i = 1; i < coefficientLength; ++i)
+ builder.append(digits[i]);
+ }
+
+ if (adjustedExponent) {
+ builder.append(adjustedExponent < 0 ? "e" : "e+");
+ builder.appendNumber(adjustedExponent);
+ }
+ }
+ return builder.toString();
+}
+
+bool Decimal::toString(char* strBuf, size_t bufLength) const
+{
+ ASSERT(bufLength > 0);
+ String str = toString();
+ size_t length = str.copy(strBuf, bufLength);
+ if (length < bufLength) {
+ strBuf[length] = '\0';
+ return true;
+ }
+ strBuf[bufLength - 1] = '\0';
+ return false;
+}
+
+Decimal Decimal::zero(Sign sign)
+{
+ return Decimal(EncodedData(sign, EncodedData::ClassZero));
+}
+
+} // namespace blink
+
+// Implementation of DoubleConversion.h:
+
+namespace mozilla {
+
+Maybe<double> StringToDouble(Span<const char> aStringSpan) {
+ bool valid = false;
+ double result = mozToDouble(aStringSpan, &valid);
+ return valid ? Some(result) : Nothing();
+}
+
+}
diff --git a/mozglue/misc/decimal/Decimal.h b/mozglue/misc/decimal/Decimal.h
new file mode 100644
index 0000000000..10d0e2c7ce
--- /dev/null
+++ b/mozglue/misc/decimal/Decimal.h
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * Imported from:
+ * https://chromium.googlesource.com/chromium/src.git/+/master/third_party/WebKit/Source/platform/Decimal.h
+ * Check UPSTREAM-GIT-SHA for the commit ID of the last update from Blink core.
+ */
+
+#ifndef Decimal_h
+#define Decimal_h
+
+#include "mozilla/Assertions.h"
+#include <stdint.h>
+#include "mozilla/Types.h"
+
+#include <string>
+
+#ifndef ASSERT
+#define DEFINED_ASSERT_FOR_DECIMAL_H 1
+#define ASSERT MOZ_ASSERT
+#endif
+
+#define PLATFORM_EXPORT
+
+// To use USING_FAST_MALLOC we'd need:
+// https://chromium.googlesource.com/chromium/src.git/+/master/third_party/WebKit/Source/wtf/Allocator.h
+// Since we don't allocate Decimal objects, no need.
+#define USING_FAST_MALLOC(type) \
+ void ignore_this_dummy_method() = delete
+
+#define DISALLOW_NEW() \
+ private: \
+ void* operator new(size_t) = delete; \
+ void* operator new(size_t, void*) = delete; \
+ public:
+
+namespace blink {
+
+namespace DecimalPrivate {
+class SpecialValueHandler;
+}
+
+// This class represents decimal base floating point number.
+//
+// FIXME: Once all C++ compiler support decimal type, we should replace this
+// class to compiler supported one. See below URI for current status of decimal
+// type for C++: // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n1977.html
+class PLATFORM_EXPORT Decimal {
+ USING_FAST_MALLOC(Decimal);
+public:
+ enum Sign {
+ Positive,
+ Negative,
+ };
+
+ // You should not use EncodedData other than unit testing.
+ class EncodedData {
+ DISALLOW_NEW();
+ // For accessing FormatClass.
+ friend class Decimal;
+ friend class DecimalPrivate::SpecialValueHandler;
+ public:
+ EncodedData(Sign, int exponent, uint64_t coefficient);
+
+ bool operator==(const EncodedData&) const;
+ bool operator!=(const EncodedData& another) const { return !operator==(another); }
+
+ uint64_t coefficient() const { return m_coefficient; }
+ int countDigits() const;
+ int exponent() const { return m_exponent; }
+ bool isFinite() const { return !isSpecial(); }
+ bool isInfinity() const { return m_formatClass == ClassInfinity; }
+ bool isNaN() const { return m_formatClass == ClassNaN; }
+ bool isSpecial() const { return m_formatClass == ClassInfinity || m_formatClass == ClassNaN; }
+ bool isZero() const { return m_formatClass == ClassZero; }
+ Sign sign() const { return m_sign; }
+ void setSign(Sign sign) { m_sign = sign; }
+
+ private:
+ enum FormatClass {
+ ClassInfinity,
+ ClassNormal,
+ ClassNaN,
+ ClassZero,
+ };
+
+ EncodedData(Sign, FormatClass);
+ FormatClass formatClass() const { return m_formatClass; }
+
+ uint64_t m_coefficient;
+ int16_t m_exponent;
+ FormatClass m_formatClass;
+ Sign m_sign;
+ };
+
+ MFBT_API explicit Decimal(int32_t = 0);
+ MFBT_API Decimal(Sign, int exponent, uint64_t coefficient);
+ MFBT_API Decimal(const Decimal&);
+
+ MFBT_API Decimal& operator=(const Decimal&);
+ MFBT_API Decimal& operator+=(const Decimal&);
+ MFBT_API Decimal& operator-=(const Decimal&);
+ MFBT_API Decimal& operator*=(const Decimal&);
+ MFBT_API Decimal& operator/=(const Decimal&);
+
+ MFBT_API Decimal operator-() const;
+
+ MFBT_API bool operator==(const Decimal&) const;
+ MFBT_API bool operator!=(const Decimal&) const;
+ MFBT_API bool operator<(const Decimal&) const;
+ MFBT_API bool operator<=(const Decimal&) const;
+ MFBT_API bool operator>(const Decimal&) const;
+ MFBT_API bool operator>=(const Decimal&) const;
+
+ MFBT_API Decimal operator+(const Decimal&) const;
+ MFBT_API Decimal operator-(const Decimal&) const;
+ MFBT_API Decimal operator*(const Decimal&) const;
+ MFBT_API Decimal operator/(const Decimal&) const;
+
+ int exponent() const
+ {
+ ASSERT(isFinite());
+ return m_data.exponent();
+ }
+
+ bool isFinite() const { return m_data.isFinite(); }
+ bool isInfinity() const { return m_data.isInfinity(); }
+ bool isNaN() const { return m_data.isNaN(); }
+ bool isNegative() const { return sign() == Negative; }
+ bool isPositive() const { return sign() == Positive; }
+ bool isSpecial() const { return m_data.isSpecial(); }
+ bool isZero() const { return m_data.isZero(); }
+
+ MFBT_API Decimal abs() const;
+ MFBT_API Decimal ceil() const;
+ MFBT_API Decimal floor() const;
+ MFBT_API Decimal remainder(const Decimal&) const;
+ MFBT_API Decimal round() const;
+
+ MFBT_API double toDouble() const;
+ // Note: toString method supports infinity and nan but fromString not.
+ MFBT_API std::string toString() const;
+ MFBT_API bool toString(char* strBuf, size_t bufLength) const;
+
+ static MFBT_API Decimal fromDouble(double);
+ // fromString supports following syntax EBNF:
+ // number ::= sign? digit+ ('.' digit*) (exponent-marker sign? digit+)?
+ // | sign? '.' digit+ (exponent-marker sign? digit+)?
+ // sign ::= '+' | '-'
+ // exponent-marker ::= 'e' | 'E'
+ // digit ::= '0' | '1' | ... | '9'
+ // Note: fromString doesn't support "infinity" and "nan".
+ static MFBT_API Decimal fromString(const std::string& aValue);
+ static MFBT_API Decimal infinity(Sign);
+ static MFBT_API Decimal nan();
+ static MFBT_API Decimal zero(Sign);
+
+ // You should not use below methods. We expose them for unit testing.
+ MFBT_API explicit Decimal(const EncodedData&);
+ const EncodedData& value() const { return m_data; }
+
+private:
+ struct AlignedOperands {
+ uint64_t lhsCoefficient;
+ uint64_t rhsCoefficient;
+ int exponent;
+ };
+
+ MFBT_API explicit Decimal(double);
+ MFBT_API Decimal compareTo(const Decimal&) const;
+
+ static MFBT_API AlignedOperands alignOperands(const Decimal& lhs, const Decimal& rhs);
+ static inline Sign invertSign(Sign sign) { return sign == Negative ? Positive : Negative; }
+
+ Sign sign() const { return m_data.sign(); }
+
+ EncodedData m_data;
+};
+
+} // namespace blink
+
+namespace mozilla {
+typedef blink::Decimal Decimal;
+} // namespace mozilla
+
+#undef USING_FAST_MALLOC
+
+#ifdef DEFINED_ASSERT_FOR_DECIMAL_H
+#undef DEFINED_ASSERT_FOR_DECIMAL_H
+#undef ASSERT
+#endif
+
+#endif // Decimal_h
diff --git a/mozglue/misc/decimal/DoubleConversion.h b/mozglue/misc/decimal/DoubleConversion.h
new file mode 100644
index 0000000000..14c19e2540
--- /dev/null
+++ b/mozglue/misc/decimal/DoubleConversion.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/* A utility function that converts a string to a double independent of OS locale. */
+
+#ifndef MOZILLA_DOUBLECONVERSION_H
+#define MOZILLA_DOUBLECONVERSION_H
+
+#include "mozilla/Maybe.h"
+#include "mozilla/Span.h"
+
+#include <string>
+
+namespace mozilla {
+
+// Parses aStringSpan into a double floating point value. Always treats . as the
+// decimal separator, regardless of OS locale. Consumes the entire string;
+// trailing garbage is invalid. Returns Nothing() for invalid input.
+// The implementation uses double_conversion::StringToDoubleConverter with
+// NO_FLAGS, see double-conversion/string-to-double.h for more documentation.
+Maybe<double> StringToDouble(Span<const char> aStringSpan);
+
+}
+
+#endif // MOZILLA_DOUBLECONVERSION_H
diff --git a/mozglue/misc/decimal/UPSTREAM-GIT-SHA b/mozglue/misc/decimal/UPSTREAM-GIT-SHA
new file mode 100644
index 0000000000..ed86150b28
--- /dev/null
+++ b/mozglue/misc/decimal/UPSTREAM-GIT-SHA
@@ -0,0 +1 @@
+cad4c9e3b3c9e80bb189059373db528272bca96f
diff --git a/mozglue/misc/decimal/add-doubleconversion-impl.patch b/mozglue/misc/decimal/add-doubleconversion-impl.patch
new file mode 100644
index 0000000000..1cf0fb6ff1
--- /dev/null
+++ b/mozglue/misc/decimal/add-doubleconversion-impl.patch
@@ -0,0 +1,42 @@
+diff --git a/mozglue/misc/decimal/Decimal.cpp b/mozglue/misc/decimal/Decimal.cpp
+--- a/mozglue/misc/decimal/Decimal.cpp
++++ b/mozglue/misc/decimal/Decimal.cpp
+@@ -25,16 +25,17 @@
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ #include "Decimal.h"
+ #include "moz-decimal-utils.h"
++#include "DoubleConversion.h"
+
+ using namespace moz_decimal_utils;
+
+ #include <algorithm>
+ #include <float.h>
+
+ namespace blink {
+
+@@ -1043,8 +1044,20 @@ bool Decimal::toString(char* strBuf, siz
+ }
+
+ Decimal Decimal::zero(Sign sign)
+ {
+ return Decimal(EncodedData(sign, EncodedData::ClassZero));
+ }
+
+ } // namespace blink
++
++// Implementation of DoubleConversion.h:
++
++namespace mozilla {
++
++Maybe<double> StringToDouble(Span<const char> aStringSpan) {
++ bool valid = false;
++ double result = mozToDouble(aStringSpan, &valid);
++ return valid ? Some(result) : Nothing();
++}
++
++}
diff --git a/mozglue/misc/decimal/comparison-with-nan.patch b/mozglue/misc/decimal/comparison-with-nan.patch
new file mode 100644
index 0000000000..0e274ce033
--- /dev/null
+++ b/mozglue/misc/decimal/comparison-with-nan.patch
@@ -0,0 +1,67 @@
+diff --git a/mozglue/misc/decimal/Decimal.cpp b/mozglue/misc/decimal/Decimal.cpp
+--- a/mozglue/misc/decimal/Decimal.cpp
++++ b/mozglue/misc/decimal/Decimal.cpp
+@@ -509,21 +509,25 @@ Decimal Decimal::operator/(const Decimal
+ if (remainder > divisor / 2)
+ ++result;
+
+ return Decimal(resultSign, resultExponent, result);
+ }
+
+ bool Decimal::operator==(const Decimal& rhs) const
+ {
++ if (isNaN() || rhs.isNaN())
++ return false;
+ return m_data == rhs.m_data || compareTo(rhs).isZero();
+ }
+
+ bool Decimal::operator!=(const Decimal& rhs) const
+ {
++ if (isNaN() || rhs.isNaN())
++ return true;
+ if (m_data == rhs.m_data)
+ return false;
+ const Decimal result = compareTo(rhs);
+ if (result.isNaN())
+ return false;
+ return !result.isZero();
+ }
+
+@@ -532,16 +536,18 @@ bool Decimal::operator<(const Decimal& r
+ const Decimal result = compareTo(rhs);
+ if (result.isNaN())
+ return false;
+ return !result.isZero() && result.isNegative();
+ }
+
+ bool Decimal::operator<=(const Decimal& rhs) const
+ {
++ if (isNaN() || rhs.isNaN())
++ return false;
+ if (m_data == rhs.m_data)
+ return true;
+ const Decimal result = compareTo(rhs);
+ if (result.isNaN())
+ return false;
+ return result.isZero() || result.isNegative();
+ }
+
+@@ -550,16 +556,18 @@ bool Decimal::operator>(const Decimal& r
+ const Decimal result = compareTo(rhs);
+ if (result.isNaN())
+ return false;
+ return !result.isZero() && result.isPositive();
+ }
+
+ bool Decimal::operator>=(const Decimal& rhs) const
+ {
++ if (isNaN() || rhs.isNaN())
++ return false;
+ if (m_data == rhs.m_data)
+ return true;
+ const Decimal result = compareTo(rhs);
+ if (result.isNaN())
+ return false;
+ return result.isZero() || !result.isNegative();
+ }
+
diff --git a/mozglue/misc/decimal/fix-wshadow-warnings.patch b/mozglue/misc/decimal/fix-wshadow-warnings.patch
new file mode 100644
index 0000000000..465c61a223
--- /dev/null
+++ b/mozglue/misc/decimal/fix-wshadow-warnings.patch
@@ -0,0 +1,171 @@
+diff --git a/mozglue/misc/decimal/Decimal.cpp b/mozglue/misc/decimal/Decimal.cpp
+--- a/mozglue/misc/decimal/Decimal.cpp
++++ b/mozglue/misc/decimal/Decimal.cpp
+@@ -118,18 +118,18 @@ Decimal SpecialValueHandler::value() con
+ ASSERT_NOT_REACHED();
+ return m_lhs;
+ }
+ }
+
+ // This class is used for 128 bit unsigned integer arithmetic.
+ class UInt128 {
+ public:
+- UInt128(uint64_t low, uint64_t high)
+- : m_high(high), m_low(low)
++ UInt128(uint64_t aLow, uint64_t aHigh)
++ : m_high(aHigh), m_low(aLow)
+ {
+ }
+
+ UInt128& operator/=(uint32_t);
+
+ uint64_t high() const { return m_high; }
+ uint64_t low() const { return m_low; }
+
+@@ -224,68 +224,68 @@ static uint64_t scaleUp(uint64_t x, int
+ z = z * z;
+ }
+ }
+
+ } // namespace DecimalPrivate
+
+ using namespace DecimalPrivate;
+
+-Decimal::EncodedData::EncodedData(Sign sign, FormatClass formatClass)
++Decimal::EncodedData::EncodedData(Sign aSign, FormatClass aFormatClass)
+ : m_coefficient(0)
+ , m_exponent(0)
+- , m_formatClass(formatClass)
+- , m_sign(sign)
++ , m_formatClass(aFormatClass)
++ , m_sign(aSign)
+ {
+ }
+
+-Decimal::EncodedData::EncodedData(Sign sign, int exponent, uint64_t coefficient)
+- : m_formatClass(coefficient ? ClassNormal : ClassZero)
+- , m_sign(sign)
++Decimal::EncodedData::EncodedData(Sign aSign, int aExponent, uint64_t aCoefficient)
++ : m_formatClass(aCoefficient ? ClassNormal : ClassZero)
++ , m_sign(aSign)
+ {
+- if (exponent >= ExponentMin && exponent <= ExponentMax) {
+- while (coefficient > MaxCoefficient) {
+- coefficient /= 10;
+- ++exponent;
++ if (aExponent >= ExponentMin && aExponent <= ExponentMax) {
++ while (aCoefficient > MaxCoefficient) {
++ aCoefficient /= 10;
++ ++aExponent;
+ }
+ }
+
+- if (exponent > ExponentMax) {
++ if (aExponent > ExponentMax) {
+ m_coefficient = 0;
+ m_exponent = 0;
+ m_formatClass = ClassInfinity;
+ return;
+ }
+
+- if (exponent < ExponentMin) {
++ if (aExponent < ExponentMin) {
+ m_coefficient = 0;
+ m_exponent = 0;
+ m_formatClass = ClassZero;
+ return;
+ }
+
+- m_coefficient = coefficient;
+- m_exponent = static_cast<int16_t>(exponent);
++ m_coefficient = aCoefficient;
++ m_exponent = static_cast<int16_t>(aExponent);
+ }
+
+ bool Decimal::EncodedData::operator==(const EncodedData& another) const
+ {
+ return m_sign == another.m_sign
+ && m_formatClass == another.m_formatClass
+ && m_exponent == another.m_exponent
+ && m_coefficient == another.m_coefficient;
+ }
+
+ Decimal::Decimal(int32_t i32)
+ : m_data(i32 < 0 ? Negative : Positive, 0, i32 < 0 ? static_cast<uint64_t>(-static_cast<int64_t>(i32)) : static_cast<uint64_t>(i32))
+ {
+ }
+
+-Decimal::Decimal(Sign sign, int exponent, uint64_t coefficient)
+- : m_data(sign, coefficient ? exponent : 0, coefficient)
++Decimal::Decimal(Sign aSign, int aExponent, uint64_t aCoefficient)
++ : m_data(aSign, aCoefficient ? aExponent : 0, aCoefficient)
+ {
+ }
+
+ Decimal::Decimal(const EncodedData& data)
+ : m_data(data)
+ {
+ }
+
+@@ -479,32 +479,32 @@ Decimal Decimal::operator/(const Decimal
+ if (rhs.isZero())
+ return lhs.isZero() ? nan() : infinity(resultSign);
+
+ int resultExponent = lhs.exponent() - rhs.exponent();
+
+ if (lhs.isZero())
+ return Decimal(resultSign, resultExponent, 0);
+
+- uint64_t remainder = lhs.m_data.coefficient();
++ uint64_t lhsRemainder = lhs.m_data.coefficient();
+ const uint64_t divisor = rhs.m_data.coefficient();
+ uint64_t result = 0;
+ while (result < MaxCoefficient / 100) {
+- while (remainder < divisor) {
+- remainder *= 10;
++ while (lhsRemainder < divisor) {
++ lhsRemainder *= 10;
+ result *= 10;
+ --resultExponent;
+ }
+- result += remainder / divisor;
+- remainder %= divisor;
+- if (!remainder)
++ result += lhsRemainder / divisor;
++ lhsRemainder %= divisor;
++ if (!lhsRemainder)
+ break;
+ }
+
+- if (remainder > divisor / 2)
++ if (lhsRemainder > divisor / 2)
+ ++result;
+
+ return Decimal(resultSign, resultExponent, result);
+ }
+
+ bool Decimal::operator==(const Decimal& rhs) const
+ {
+ if (isNaN() || rhs.isNaN())
+diff --git a/mozglue/misc/decimal/Decimal.h b/mozglue/misc/decimal/Decimal.h
+--- a/mozglue/misc/decimal/Decimal.h
++++ b/mozglue/misc/decimal/Decimal.h
+@@ -88,17 +88,17 @@ public:
+ int countDigits() const;
+ int exponent() const { return m_exponent; }
+ bool isFinite() const { return !isSpecial(); }
+ bool isInfinity() const { return m_formatClass == ClassInfinity; }
+ bool isNaN() const { return m_formatClass == ClassNaN; }
+ bool isSpecial() const { return m_formatClass == ClassInfinity || m_formatClass == ClassNaN; }
+ bool isZero() const { return m_formatClass == ClassZero; }
+ Sign sign() const { return m_sign; }
+- void setSign(Sign sign) { m_sign = sign; }
++ void setSign(Sign aSign) { m_sign = aSign; }
+
+ private:
+ enum FormatClass {
+ ClassInfinity,
+ ClassNormal,
+ ClassNaN,
+ ClassZero,
+ };
diff --git a/mozglue/misc/decimal/mfbt-abi-markers.patch b/mozglue/misc/decimal/mfbt-abi-markers.patch
new file mode 100644
index 0000000000..1d50d3d643
--- /dev/null
+++ b/mozglue/misc/decimal/mfbt-abi-markers.patch
@@ -0,0 +1,150 @@
+diff --git a/mozglue/misc/decimal/Decimal.h b/mozglue/misc/decimal/Decimal.h
+--- a/mozglue/misc/decimal/Decimal.h
++++ b/mozglue/misc/decimal/Decimal.h
+@@ -26,16 +26,18 @@
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ #ifndef Decimal_h
+ #define Decimal_h
+
++#include "mozilla/Types.h"
++
+ #include "platform/PlatformExport.h"
+ #include "wtf/Allocator.h"
+ #include "wtf/Assertions.h"
+ #include "wtf/text/WTFString.h"
+ #include <stdint.h>
+
+ namespace blink {
+
+@@ -91,92 +93,92 @@ public:
+ FormatClass formatClass() const { return m_formatClass; }
+
+ uint64_t m_coefficient;
+ int16_t m_exponent;
+ FormatClass m_formatClass;
+ Sign m_sign;
+ };
+
+- Decimal(int32_t = 0);
+- Decimal(Sign, int exponent, uint64_t coefficient);
+- Decimal(const Decimal&);
++ MFBT_API explicit Decimal(int32_t = 0);
++ MFBT_API Decimal(Sign, int exponent, uint64_t coefficient);
++ MFBT_API Decimal(const Decimal&);
+
+- Decimal& operator=(const Decimal&);
+- Decimal& operator+=(const Decimal&);
+- Decimal& operator-=(const Decimal&);
+- Decimal& operator*=(const Decimal&);
+- Decimal& operator/=(const Decimal&);
++ MFBT_API Decimal& operator=(const Decimal&);
++ MFBT_API Decimal& operator+=(const Decimal&);
++ MFBT_API Decimal& operator-=(const Decimal&);
++ MFBT_API Decimal& operator*=(const Decimal&);
++ MFBT_API Decimal& operator/=(const Decimal&);
+
+- Decimal operator-() const;
++ MFBT_API Decimal operator-() const;
+
+- bool operator==(const Decimal&) const;
+- bool operator!=(const Decimal&) const;
+- bool operator<(const Decimal&) const;
+- bool operator<=(const Decimal&) const;
+- bool operator>(const Decimal&) const;
+- bool operator>=(const Decimal&) const;
++ MFBT_API bool operator==(const Decimal&) const;
++ MFBT_API bool operator!=(const Decimal&) const;
++ MFBT_API bool operator<(const Decimal&) const;
++ MFBT_API bool operator<=(const Decimal&) const;
++ MFBT_API bool operator>(const Decimal&) const;
++ MFBT_API bool operator>=(const Decimal&) const;
+
+- Decimal operator+(const Decimal&) const;
+- Decimal operator-(const Decimal&) const;
+- Decimal operator*(const Decimal&) const;
+- Decimal operator/(const Decimal&) const;
++ MFBT_API Decimal operator+(const Decimal&) const;
++ MFBT_API Decimal operator-(const Decimal&) const;
++ MFBT_API Decimal operator*(const Decimal&) const;
++ MFBT_API Decimal operator/(const Decimal&) const;
+
+ int exponent() const
+ {
+ ASSERT(isFinite());
+ return m_data.exponent();
+ }
+
+ bool isFinite() const { return m_data.isFinite(); }
+ bool isInfinity() const { return m_data.isInfinity(); }
+ bool isNaN() const { return m_data.isNaN(); }
+ bool isNegative() const { return sign() == Negative; }
+ bool isPositive() const { return sign() == Positive; }
+ bool isSpecial() const { return m_data.isSpecial(); }
+ bool isZero() const { return m_data.isZero(); }
+
+- Decimal abs() const;
+- Decimal ceil() const;
+- Decimal floor() const;
+- Decimal remainder(const Decimal&) const;
+- Decimal round() const;
++ MFBT_API Decimal abs() const;
++ MFBT_API Decimal ceil() const;
++ MFBT_API Decimal floor() const;
++ MFBT_API Decimal remainder(const Decimal&) const;
++ MFBT_API Decimal round() const;
+
+- double toDouble() const;
++ MFBT_API double toDouble() const;
+ // Note: toString method supports infinity and nan but fromString not.
+- String toString() const;
++ MFBT_API String toString() const;
+
+- static Decimal fromDouble(double);
++ static MFBT_API Decimal fromDouble(double);
+ // fromString supports following syntax EBNF:
+ // number ::= sign? digit+ ('.' digit*) (exponent-marker sign? digit+)?
+ // | sign? '.' digit+ (exponent-marker sign? digit+)?
+ // sign ::= '+' | '-'
+ // exponent-marker ::= 'e' | 'E'
+ // digit ::= '0' | '1' | ... | '9'
+ // Note: fromString doesn't support "infinity" and "nan".
+- static Decimal fromString(const String&);
+- static Decimal infinity(Sign);
+- static Decimal nan();
+- static Decimal zero(Sign);
++ static MFBT_API Decimal fromString(const String&);
++ static MFBT_API Decimal infinity(Sign);
++ static MFBT_API Decimal nan();
++ static MFBT_API Decimal zero(Sign);
+
+ // You should not use below methods. We expose them for unit testing.
+- explicit Decimal(const EncodedData&);
++ MFBT_API explicit Decimal(const EncodedData&);
+ const EncodedData& value() const { return m_data; }
+
+ private:
+ struct AlignedOperands {
+ uint64_t lhsCoefficient;
+ uint64_t rhsCoefficient;
+ int exponent;
+ };
+
+- Decimal(double);
+- Decimal compareTo(const Decimal&) const;
++ MFBT_API explicit Decimal(double);
++ MFBT_API Decimal compareTo(const Decimal&) const;
+
+- static AlignedOperands alignOperands(const Decimal& lhs, const Decimal& rhs);
++ static MFBT_API AlignedOperands alignOperands(const Decimal& lhs, const Decimal& rhs);
+ static inline Sign invertSign(Sign sign) { return sign == Negative ? Positive : Negative; }
+
+ Sign sign() const { return m_data.sign(); }
+
+ EncodedData m_data;
+ };
+
+ } // namespace blink
diff --git a/mozglue/misc/decimal/moz-decimal-utils.h b/mozglue/misc/decimal/moz-decimal-utils.h
new file mode 100644
index 0000000000..390bdaf02d
--- /dev/null
+++ b/mozglue/misc/decimal/moz-decimal-utils.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 MOZ_DECIMAL_UTILS_H
+#define MOZ_DECIMAL_UTILS_H
+
+// This file contains extra includes, defines and typedefs to allow compilation
+// of Decimal.cpp under the Mozilla source without blink core dependencies. Do
+// not include it into any file other than Decimal.cpp.
+
+#include "double-conversion/double-conversion.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Casting.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Span.h"
+
+#include <cmath>
+#include <cstring>
+#include <iomanip>
+#include <limits>
+#include <sstream>
+
+#ifndef UINT64_C
+// For Android toolchain
+#define UINT64_C(c) (c ## ULL)
+#endif
+
+#ifdef ASSERT
+#undef ASSERT
+#endif
+#define ASSERT MOZ_ASSERT
+
+#define ASSERT_NOT_REACHED() MOZ_ASSERT_UNREACHABLE("moz-decimal-utils.h")
+
+#define STACK_ALLOCATED() DISALLOW_NEW()
+
+#define WTF_MAKE_NONCOPYABLE(ClassName) \
+ private: \
+ ClassName(const ClassName&) = delete; \
+ void operator=(const ClassName&) = delete;
+
+typedef std::string String;
+
+double mozToDouble(mozilla::Span<const char> aStr, bool *valid) {
+ double_conversion::StringToDoubleConverter converter(
+ double_conversion::StringToDoubleConverter::NO_FLAGS,
+ mozilla::UnspecifiedNaN<double>(), mozilla::UnspecifiedNaN<double>(), nullptr, nullptr);
+ const char* str = aStr.Elements();
+ int length = mozilla::AssertedCast<int>(aStr.Length());
+ int processed_char_count; // unused - NO_FLAGS requires the whole string to parse
+ double result = converter.StringToDouble(str, length, &processed_char_count);
+ *valid = mozilla::IsFinite(result);
+ return result;
+}
+
+double mozToDouble(const String &aStr, bool *valid) {
+ return mozToDouble(mozilla::MakeStringSpan(aStr.c_str()), valid);
+}
+
+String mozToString(double aNum) {
+ char buffer[64];
+ int buffer_length = mozilla::ArrayLength(buffer);
+ const double_conversion::DoubleToStringConverter& converter =
+ double_conversion::DoubleToStringConverter::EcmaScriptConverter();
+ double_conversion::StringBuilder builder(buffer, buffer_length);
+ converter.ToShortest(aNum, &builder);
+ return String(builder.Finalize());
+}
+
+String mozToString(int64_t aNum) {
+ std::ostringstream o;
+ o << std::setprecision(std::numeric_limits<int64_t>::digits10) << aNum;
+ return o.str();
+}
+
+String mozToString(uint64_t aNum) {
+ std::ostringstream o;
+ o << std::setprecision(std::numeric_limits<uint64_t>::digits10) << aNum;
+ return o.str();
+}
+
+namespace moz_decimal_utils {
+
+class StringBuilder
+{
+public:
+ void append(char c) {
+ mStr += c;
+ }
+ void appendLiteral(const char *aStr) {
+ mStr += aStr;
+ }
+ void appendNumber(int aNum) {
+ mStr += mozToString(int64_t(aNum));
+ }
+ void append(const String& aStr) {
+ mStr += aStr;
+ }
+ std::string toString() const {
+ return mStr;
+ }
+private:
+ std::string mStr;
+};
+
+} // namespace moz_decimal_utils
+
+#endif
+
diff --git a/mozglue/misc/decimal/to-moz-dependencies.patch b/mozglue/misc/decimal/to-moz-dependencies.patch
new file mode 100644
index 0000000000..bf19a6da96
--- /dev/null
+++ b/mozglue/misc/decimal/to-moz-dependencies.patch
@@ -0,0 +1,224 @@
+diff --git a/mozglue/misc/decimal/Decimal.cpp b/mozglue/misc/decimal/Decimal.cpp
+--- a/mozglue/misc/decimal/Decimal.cpp
++++ b/mozglue/misc/decimal/Decimal.cpp
+@@ -23,22 +23,20 @@
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+-#include "platform/Decimal.h"
++#include "Decimal.h"
++#include "moz-decimal-utils.h"
+
+-#include "wtf/Allocator.h"
+-#include "wtf/MathExtras.h"
+-#include "wtf/Noncopyable.h"
+-#include "wtf/text/StringBuilder.h"
++using namespace moz_decimal_utils;
+
+ #include <algorithm>
+ #include <float.h>
+
+ namespace blink {
+
+ namespace DecimalPrivate {
+
+@@ -690,17 +688,17 @@ Decimal Decimal::floor() const
+ if (isNegative() && !isMultiplePowersOfTen(m_data.coefficient(), numberOfDropDigits))
+ ++result;
+ return Decimal(sign(), 0, result);
+ }
+
+ Decimal Decimal::fromDouble(double doubleValue)
+ {
+ if (std::isfinite(doubleValue))
+- return fromString(String::numberToStringECMAScript(doubleValue));
++ return fromString(mozToString(doubleValue));
+
+ if (std::isinf(doubleValue))
+ return infinity(doubleValue < 0 ? Negative : Positive);
+
+ return nan();
+ }
+
+ Decimal Decimal::fromString(const String& str)
+@@ -931,17 +929,17 @@ Decimal Decimal::round() const
+ result /= 10;
+ return Decimal(sign(), 0, result);
+ }
+
+ double Decimal::toDouble() const
+ {
+ if (isFinite()) {
+ bool valid;
+- const double doubleValue = toString().toDouble(&valid);
++ const double doubleValue = mozToDouble(toString(), &valid);
+ return valid ? doubleValue : std::numeric_limits<double>::quiet_NaN();
+ }
+
+ if (isInfinity())
+ return isNegative() ? -std::numeric_limits<double>::infinity() : std::numeric_limits<double>::infinity();
+
+ return std::numeric_limits<double>::quiet_NaN();
+ }
+@@ -984,17 +982,17 @@ String Decimal::toString() const
+ ++coefficient;
+
+ while (originalExponent < 0 && coefficient && !(coefficient % 10)) {
+ coefficient /= 10;
+ ++originalExponent;
+ }
+ }
+
+- const String digits = String::number(coefficient);
++ const String digits = mozToString(coefficient);
+ int coefficientLength = static_cast<int>(digits.length());
+ const int adjustedExponent = originalExponent + coefficientLength - 1;
+ if (originalExponent <= 0 && adjustedExponent >= -6) {
+ if (!originalExponent) {
+ builder.append(digits);
+ return builder.toString();
+ }
+
+@@ -1026,14 +1024,27 @@ String Decimal::toString() const
+ if (adjustedExponent) {
+ builder.append(adjustedExponent < 0 ? "e" : "e+");
+ builder.appendNumber(adjustedExponent);
+ }
+ }
+ return builder.toString();
+ }
+
++bool Decimal::toString(char* strBuf, size_t bufLength) const
++{
++ ASSERT(bufLength > 0);
++ String str = toString();
++ size_t length = str.copy(strBuf, bufLength);
++ if (length < bufLength) {
++ strBuf[length] = '\0';
++ return true;
++ }
++ strBuf[bufLength - 1] = '\0';
++ return false;
++}
++
+ Decimal Decimal::zero(Sign sign)
+ {
+ return Decimal(EncodedData(sign, EncodedData::ClassZero));
+ }
+
+ } // namespace blink
+diff --git a/mozglue/misc/decimal/Decimal.h b/mozglue/misc/decimal/Decimal.h
+--- a/mozglue/misc/decimal/Decimal.h
++++ b/mozglue/misc/decimal/Decimal.h
+@@ -23,26 +23,49 @@
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
++/**
++ * Imported from:
++ * https://chromium.googlesource.com/chromium/src.git/+/master/third_party/WebKit/Source/platform/Decimal.h
++ * Check UPSTREAM-GIT-SHA for the commit ID of the last update from Blink core.
++ */
++
+ #ifndef Decimal_h
+ #define Decimal_h
+
++#include "mozilla/Assertions.h"
++#include <stdint.h>
+ #include "mozilla/Types.h"
+
+-#include "platform/PlatformExport.h"
+-#include "wtf/Allocator.h"
+-#include "wtf/Assertions.h"
+-#include "wtf/text/WTFString.h"
+-#include <stdint.h>
++#include <string>
++
++#ifndef ASSERT
++#define DEFINED_ASSERT_FOR_DECIMAL_H 1
++#define ASSERT MOZ_ASSERT
++#endif
++
++#define PLATFORM_EXPORT
++
++// To use USING_FAST_MALLOC we'd need:
++// https://chromium.googlesource.com/chromium/src.git/+/master/third_party/WebKit/Source/wtf/Allocator.h
++// Since we don't allocate Decimal objects, no need.
++#define USING_FAST_MALLOC(type) \
++ void ignore_this_dummy_method() = delete
++
++#define DISALLOW_NEW() \
++ private: \
++ void* operator new(size_t) = delete; \
++ void* operator new(size_t, void*) = delete; \
++ public:
+
+ namespace blink {
+
+ namespace DecimalPrivate {
+ class SpecialValueHandler;
+ }
+
+ // This class represents decimal base floating point number.
+@@ -139,27 +162,28 @@ public:
+ MFBT_API Decimal abs() const;
+ MFBT_API Decimal ceil() const;
+ MFBT_API Decimal floor() const;
+ MFBT_API Decimal remainder(const Decimal&) const;
+ MFBT_API Decimal round() const;
+
+ MFBT_API double toDouble() const;
+ // Note: toString method supports infinity and nan but fromString not.
+- MFBT_API String toString() const;
++ MFBT_API std::string toString() const;
++ MFBT_API bool toString(char* strBuf, size_t bufLength) const;
+
+ static MFBT_API Decimal fromDouble(double);
+ // fromString supports following syntax EBNF:
+ // number ::= sign? digit+ ('.' digit*) (exponent-marker sign? digit+)?
+ // | sign? '.' digit+ (exponent-marker sign? digit+)?
+ // sign ::= '+' | '-'
+ // exponent-marker ::= 'e' | 'E'
+ // digit ::= '0' | '1' | ... | '9'
+ // Note: fromString doesn't support "infinity" and "nan".
+- static MFBT_API Decimal fromString(const String&);
++ static MFBT_API Decimal fromString(const std::string& aValue);
+ static MFBT_API Decimal infinity(Sign);
+ static MFBT_API Decimal nan();
+ static MFBT_API Decimal zero(Sign);
+
+ // You should not use below methods. We expose them for unit testing.
+ MFBT_API explicit Decimal(const EncodedData&);
+ const EncodedData& value() const { return m_data; }
+
+@@ -178,9 +202,20 @@ private:
+
+ Sign sign() const { return m_data.sign(); }
+
+ EncodedData m_data;
+ };
+
+ } // namespace blink
+
++namespace mozilla {
++typedef blink::Decimal Decimal;
++} // namespace mozilla
++
++#undef USING_FAST_MALLOC
++
++#ifdef DEFINED_ASSERT_FOR_DECIMAL_H
++#undef DEFINED_ASSERT_FOR_DECIMAL_H
++#undef ASSERT
++#endif
++
+ #endif // Decimal_h
diff --git a/mozglue/misc/decimal/update.sh b/mozglue/misc/decimal/update.sh
new file mode 100755
index 0000000000..23748ebe2c
--- /dev/null
+++ b/mozglue/misc/decimal/update.sh
@@ -0,0 +1,60 @@
+# Usage: ./update.sh [blink-core-source-directory]
+#
+# Copies the needed files from a directory containing the original
+# Decimal.h and Decimal.cpp source that we need.
+# If [blink-core-source-directory] is not specified, this script will
+# attempt to download the latest versions using git.
+
+set -e
+
+FILES=(
+ "Decimal.h"
+ "Decimal.cpp"
+)
+
+OWN_NAME=`basename $0`
+
+if [ $# -gt 1 ]; then
+ echo "$OWN_NAME: Too many arguments">&2
+ exit 1
+fi
+
+if [ $# -eq 1 ]; then
+ BLINK_CORE_DIR="$1"
+ for F in "${FILES[@]}"
+ do
+ P="$BLINK_CORE_DIR/$F"
+ if [ ! -f "$P" ]; then
+ echo "$OWN_NAME: Couldn't find file: $P">&2
+ exit 1
+ fi
+ done
+ for F in "${FILES[@]}"
+ do
+ P="$BLINK_CORE_DIR/$F"
+ cp "$P" .
+ done
+else
+ #LATEST_SHA=$(cat UPSTREAM-GIT-SHA)
+ LATEST_SHA=$(git ls-remote https://chromium.googlesource.com/chromium/src.git/ | awk "/refs\/heads\/master/ {print \$1}")
+ REPO_PATH="https://chromium.googlesource.com/chromium/src.git/+/$LATEST_SHA/third_party/WebKit/Source/platform"
+ #REPO_PATH="https://github.com/WebKit/webkit/tree/master/Source/WebCore/platform"
+ for F in "${FILES[@]}"
+ do
+ printf "Downloading `basename $F`..."
+ curl "$REPO_PATH/${F}?format=TEXT" | base64 -D > "$F"
+ echo done.
+ done
+ echo $LATEST_SHA > UPSTREAM-GIT-SHA
+fi
+
+# Apply patches:
+
+patch -p4 < zero-serialization.patch
+patch -p4 < comparison-with-nan.patch
+patch -p4 < mfbt-abi-markers.patch
+patch -p4 < to-moz-dependencies.patch
+patch -p4 < add-doubleconversion-impl.patch
+# The following is disabled. See
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1208357#c7
+#patch -p4 < fix-wshadow-warnings.patch
diff --git a/mozglue/misc/decimal/zero-serialization.patch b/mozglue/misc/decimal/zero-serialization.patch
new file mode 100644
index 0000000000..b8de9241bd
--- /dev/null
+++ b/mozglue/misc/decimal/zero-serialization.patch
@@ -0,0 +1,22 @@
+diff --git a/mozglue/misc/decimal/Decimal.cpp b/mozglue/misc/decimal/Decimal.cpp
+--- a/mozglue/misc/decimal/Decimal.cpp
++++ b/mozglue/misc/decimal/Decimal.cpp
+@@ -277,17 +277,17 @@ bool Decimal::EncodedData::operator==(co
+ }
+
+ Decimal::Decimal(int32_t i32)
+ : m_data(i32 < 0 ? Negative : Positive, 0, i32 < 0 ? static_cast<uint64_t>(-static_cast<int64_t>(i32)) : static_cast<uint64_t>(i32))
+ {
+ }
+
+ Decimal::Decimal(Sign sign, int exponent, uint64_t coefficient)
+- : m_data(sign, exponent, coefficient)
++ : m_data(sign, coefficient ? exponent : 0, coefficient)
+ {
+ }
+
+ Decimal::Decimal(const EncodedData& data)
+ : m_data(data)
+ {
+ }
+
diff --git a/mozglue/misc/interceptor/Arm64.cpp b/mozglue/misc/interceptor/Arm64.cpp
new file mode 100644
index 0000000000..81d8e6d09b
--- /dev/null
+++ b/mozglue/misc/interceptor/Arm64.cpp
@@ -0,0 +1,89 @@
+/* -*- 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/. */
+
+#include "Arm64.h"
+
+#include "mozilla/ResultVariant.h"
+
+namespace mozilla {
+namespace interceptor {
+namespace arm64 {
+
+struct PCRelativeLoadTest {
+ // Bitmask to be ANDed with the instruction to isolate the bits that this
+ // instance is interested in
+ uint32_t mTestMask;
+ // The desired bits that we want to see after masking
+ uint32_t mMatchBits;
+ // If we match, mDecodeFn provide the code to decode the instruction.
+ LoadOrBranch (*mDecodeFn)(const uintptr_t aPC, const uint32_t aInst);
+};
+
+static LoadOrBranch ADRPDecode(const uintptr_t aPC, const uint32_t aInst) {
+ // Keep in mind that on Windows aarch64, uint32_t is little-endian
+ const uint32_t kMaskDataProcImmPcRelativeImmLo = 0x60000000;
+ const uint32_t kMaskDataProcImmPcRelativeImmHi = 0x00FFFFE0;
+
+ uintptr_t base = aPC;
+ intptr_t offset = SignExtend<intptr_t>(
+ ((aInst & kMaskDataProcImmPcRelativeImmHi) >> 3) |
+ ((aInst & kMaskDataProcImmPcRelativeImmLo) >> 29),
+ 21);
+
+ base &= ~0xFFFULL;
+ offset <<= 12;
+
+ uint8_t reg = aInst & 0x1F;
+
+ return LoadOrBranch(base + offset, reg);
+}
+
+MFBT_API LoadOrBranch BUncondImmDecode(const uintptr_t aPC,
+ const uint32_t aInst) {
+ int32_t offset = SignExtend<int32_t>(aInst & 0x03FFFFFFU, 26);
+ return LoadOrBranch(aPC + offset);
+}
+
+// Order is important here; more specific encoding tests must be placed before
+// less specific encoding tests.
+static const PCRelativeLoadTest gPCRelTests[] = {
+ {0x9FC00000, 0x10000000, nullptr}, // ADR
+ {0x9FC00000, 0x90000000, &ADRPDecode}, // ADRP
+ {0xFF000000, 0x58000000, nullptr}, // LDR (literal) 64-bit GPR
+ {0x3B000000, 0x18000000, nullptr}, // LDR (literal) (remaining forms)
+ {0x7C000000, 0x14000000, nullptr}, // B (unconditional immediate)
+ {0xFE000000, 0x54000000, nullptr}, // B.Cond
+ {0x7E000000, 0x34000000, nullptr}, // Compare and branch (imm)
+ {0x7E000000, 0x36000000, nullptr}, // Test and branch (imm)
+ {0xFE000000, 0xD6000000, nullptr} // Unconditional branch (reg)
+};
+
+/**
+ * In this function we interate through each entry in |gPCRelTests|, AND
+ * |aInst| with |test.mTestMask| to isolate the bits that we're interested in,
+ * then compare that result against |test.mMatchBits|. If we have a match,
+ * then that particular entry is applicable to |aInst|. If |test.mDecodeFn| is
+ * present, then we call it to decode the instruction. If it is not present,
+ * then we assume that this particular instruction is unsupported.
+ */
+MFBT_API Result<LoadOrBranch, PCRelCheckError> CheckForPCRel(
+ const uintptr_t aPC, const uint32_t aInst) {
+ for (auto&& test : gPCRelTests) {
+ if ((aInst & test.mTestMask) == test.mMatchBits) {
+ if (!test.mDecodeFn) {
+ return Err(PCRelCheckError::NoDecoderAvailable);
+ }
+
+ return test.mDecodeFn(aPC, aInst);
+ }
+ }
+
+ return Err(PCRelCheckError::InstructionNotPCRel);
+}
+
+} // namespace arm64
+} // namespace interceptor
+} // namespace mozilla
diff --git a/mozglue/misc/interceptor/Arm64.h b/mozglue/misc/interceptor/Arm64.h
new file mode 100644
index 0000000000..ebb30ecd6b
--- /dev/null
+++ b/mozglue/misc/interceptor/Arm64.h
@@ -0,0 +1,221 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_interceptor_Arm64_h
+#define mozilla_interceptor_Arm64_h
+
+#include <type_traits>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Result.h"
+#include "mozilla/Saturate.h"
+#include "mozilla/Types.h"
+
+namespace mozilla {
+namespace interceptor {
+namespace arm64 {
+
+// clang-format off
+enum class IntegerConditionCode : uint8_t {
+ // From the ARMv8 Architectural Reference Manual, Section C1.2.4
+ // Description Condition Flags
+ EQ = 0b0000, // == Z == 1
+ NE = 0b0001, // != Z == 0
+ CS = 0b0010, // carry set C == 1
+ HS = 0b0010, // carry set (alias) C == 1
+ CC = 0b0011, // carry clear C == 0
+ LO = 0b0011, // carry clear (alias) C == 0
+ MI = 0b0100, // < 0 N == 1
+ PL = 0b0101, // >= 0 N == 0
+ VS = 0b0110, // overflow V == 1
+ VC = 0b0111, // no overflow V == 0
+ HI = 0b1000, // unsigned > C == 1 && Z == 0
+ LS = 0b1001, // unsigned <= !(C == 1 && Z == 0)
+ GE = 0b1010, // signed >= N == V
+ LT = 0b1011, // signed < N != V
+ GT = 0b1100, // signed > Z == 0 && N == V
+ LE = 0b1101, // signed <= !(Z == 0 && N == V)
+ AL = 0b1110, // unconditional <Any>
+ NV = 0b1111 // unconditional (but AL is the preferred encoding)
+};
+// clang-format on
+
+struct LoadOrBranch {
+ enum class Type {
+ Load,
+ Branch,
+ };
+
+ // Load constructor
+ LoadOrBranch(const uintptr_t aAbsAddress, const uint8_t aDestReg)
+ : mType(Type::Load), mAbsAddress(aAbsAddress), mDestReg(aDestReg) {
+ MOZ_ASSERT(aDestReg < 32);
+ }
+
+ // Unconditional branch constructor
+ explicit LoadOrBranch(const uintptr_t aAbsAddress)
+ : mType(Type::Branch),
+ mAbsAddress(aAbsAddress),
+ mCond(IntegerConditionCode::AL) {}
+
+ // Conditional branch constructor
+ LoadOrBranch(const uintptr_t aAbsAddress, const IntegerConditionCode aCond)
+ : mType(Type::Branch), mAbsAddress(aAbsAddress), mCond(aCond) {}
+
+ Type mType;
+
+ // The absolute address to be loaded into a register, or branched to
+ uintptr_t mAbsAddress;
+
+ union {
+ // The destination register for the load
+ uint8_t mDestReg;
+
+ // The condition code for the branch
+ IntegerConditionCode mCond;
+ };
+};
+
+enum class PCRelCheckError {
+ InstructionNotPCRel,
+ NoDecoderAvailable,
+};
+
+MFBT_API Result<LoadOrBranch, PCRelCheckError> CheckForPCRel(
+ const uintptr_t aPC, const uint32_t aInst);
+
+/**
+ * Casts |aValue| to a |ResultT| via sign extension.
+ *
+ * This function should be used when extracting signed immediate values from
+ * an instruction.
+ *
+ * @param aValue The value to be sign extended. This value should already be
+ * isolated from the remainder of the instruction's bits and
+ * shifted all the way to the right.
+ * @param aNumValidBits The number of bits in |aValue| that contain the
+ * immediate signed value, including the sign bit.
+ */
+template <typename ResultT>
+inline ResultT SignExtend(const uint32_t aValue, const uint8_t aNumValidBits) {
+ static_assert(std::is_integral_v<ResultT> && std::is_signed_v<ResultT>,
+ "ResultT must be a signed integral type");
+ MOZ_ASSERT(aNumValidBits < 32U && aNumValidBits > 1);
+
+ using UnsignedResultT = std::decay_t<std::make_unsigned_t<ResultT>>;
+
+ const uint8_t kResultWidthBits = sizeof(ResultT) * 8;
+
+ // Shift left unsigned
+ const uint8_t shiftAmt = kResultWidthBits - aNumValidBits;
+ UnsignedResultT shiftedLeft = static_cast<UnsignedResultT>(aValue)
+ << shiftAmt;
+
+ // Now shift right signed
+ auto result = static_cast<ResultT>(shiftedLeft);
+ result >>= shiftAmt;
+
+ return result;
+}
+
+inline static uint32_t BuildUnconditionalBranchToRegister(const uint32_t aReg) {
+ MOZ_ASSERT(aReg < 32);
+ // BR aReg
+ return 0xD61F0000 | (aReg << 5);
+}
+
+MFBT_API LoadOrBranch BUncondImmDecode(const uintptr_t aPC,
+ const uint32_t aInst);
+
+/**
+ * If |aTarget| is more than 128MB away from |aPC|, we need to use a veneer.
+ */
+inline static bool IsVeneerRequired(const uintptr_t aPC,
+ const uintptr_t aTarget) {
+ detail::Saturate<intptr_t> saturated(aTarget);
+ saturated -= aPC;
+
+ uintptr_t absDiff = Abs(saturated.value());
+
+ return absDiff >= 0x08000000U;
+}
+
+inline static bool IsUnconditionalBranchImm(const uint32_t aInst) {
+ return (aInst & 0xFC000000U) == 0x14000000U;
+}
+
+inline static Maybe<uint32_t> BuildUnconditionalBranchImm(
+ const uintptr_t aPC, const uintptr_t aTarget) {
+ detail::Saturate<intptr_t> saturated(aTarget);
+ saturated -= aPC;
+
+ CheckedInt<int32_t> offset(saturated.value());
+ if (!offset.isValid()) {
+ return Nothing();
+ }
+
+ // offset should be a multiple of 4
+ MOZ_ASSERT(offset.value() % 4 == 0);
+ if (offset.value() % 4) {
+ return Nothing();
+ }
+
+ offset /= 4;
+ if (!offset.isValid()) {
+ return Nothing();
+ }
+
+ int32_t signbits = offset.value() & 0xFE000000;
+ // Ensure that offset is small enough to fit into the 26 bit region.
+ // We check that the sign bits are either all ones or all zeros.
+ MOZ_ASSERT(signbits == 0xFE000000 || !signbits);
+ if (signbits && signbits != 0xFE000000) {
+ return Nothing();
+ }
+
+ int32_t masked = offset.value() & 0x03FFFFFF;
+
+ // B imm26
+ return Some(0x14000000U | masked);
+}
+
+/**
+ * Allocate and construct a veneer that provides an absolute 64-bit branch to
+ * the hook function.
+ */
+template <typename TrampPoolT>
+inline static uintptr_t MakeVeneer(TrampPoolT& aTrampPool, void* aPrimaryTramp,
+ const uintptr_t aDestAddress) {
+ auto maybeVeneer = aTrampPool.GetNextTrampoline();
+ if (!maybeVeneer) {
+ return 0;
+ }
+
+ Trampoline<typename TrampPoolT::MMPolicyT> veneer(
+ std::move(maybeVeneer.ref()));
+
+ // Write the same header information that is used for trampolines
+ veneer.WriteEncodedPointer(nullptr);
+ veneer.WriteEncodedPointer(aPrimaryTramp);
+
+ veneer.StartExecutableCode();
+
+ // Register 16 is explicitly intended for veneers in ARM64, so we use that
+ // register without fear of clobbering anything important.
+ veneer.WriteLoadLiteral(aDestAddress, 16);
+ veneer.WriteInstruction(BuildUnconditionalBranchToRegister(16));
+
+ return reinterpret_cast<uintptr_t>(veneer.EndExecutableCode());
+}
+
+} // namespace arm64
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_Arm64_h
diff --git a/mozglue/misc/interceptor/MMPolicies.h b/mozglue/misc/interceptor/MMPolicies.h
new file mode 100644
index 0000000000..9eacc1896f
--- /dev/null
+++ b/mozglue/misc/interceptor/MMPolicies.h
@@ -0,0 +1,981 @@
+/* -*- 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/CheckedInt.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Span.h"
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/Types.h"
+#include "mozilla/WindowsMapRemoteView.h"
+
+#include <windows.h>
+
+#if (NTDDI_VERSION < NTDDI_WIN10_RS4) || defined(__MINGW32__)
+PVOID WINAPI VirtualAlloc2(HANDLE Process, PVOID BaseAddress, SIZE_T Size,
+ ULONG AllocationType, ULONG PageProtection,
+ MEM_EXTENDED_PARAMETER* ExtendedParameters,
+ ULONG ParameterCount);
+PVOID WINAPI MapViewOfFile3(HANDLE FileMapping, HANDLE Process,
+ PVOID BaseAddress, ULONG64 Offset, SIZE_T ViewSize,
+ ULONG AllocationType, ULONG PageProtection,
+ MEM_EXTENDED_PARAMETER* ExtendedParameters,
+ ULONG ParameterCount);
+#endif // (NTDDI_VERSION < NTDDI_WIN10_RS4) || defined(__MINGW32__)
+
+// _CRT_RAND_S is not defined everywhere, but we need it.
+#if !defined(_CRT_RAND_S)
+extern "C" errno_t rand_s(unsigned int* randomValue);
+#endif // !defined(_CRT_RAND_S)
+
+// Declaring only the functions we need in NativeNt.h. To include the entire
+// NativeNt.h causes circular dependency.
+namespace mozilla {
+namespace nt {
+SIZE_T WINAPI VirtualQueryEx(HANDLE aProcess, LPCVOID aAddress,
+ PMEMORY_BASIC_INFORMATION aMemInfo,
+ SIZE_T aMemInfoLen);
+
+SIZE_T WINAPI VirtualQuery(LPCVOID aAddress, PMEMORY_BASIC_INFORMATION aMemInfo,
+ SIZE_T aMemInfoLen);
+} // namespace nt
+} // namespace mozilla
+
+namespace mozilla {
+namespace interceptor {
+
+// This class implements memory operations not involving any kernel32's
+// functions, so that derived classes can use them.
+class MOZ_TRIVIAL_CTOR_DTOR MMPolicyInProcessPrimitive {
+ protected:
+ bool ProtectInternal(decltype(&::VirtualProtect) aVirtualProtect,
+ void* aVAddress, size_t aSize, uint32_t aProtFlags,
+ uint32_t* aPrevProtFlags) const {
+ MOZ_ASSERT(aPrevProtFlags);
+ BOOL ok = aVirtualProtect(aVAddress, aSize, aProtFlags,
+ reinterpret_cast<PDWORD>(aPrevProtFlags));
+ if (!ok && aPrevProtFlags) {
+ // VirtualProtect can fail but still set valid protection flags.
+ // Let's clear those upon failure.
+ *aPrevProtFlags = 0;
+ }
+
+ return !!ok;
+ }
+
+ public:
+ 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;
+ }
+
+ /**
+ * @return true if the page that hosts aVAddress is accessible.
+ */
+ bool IsPageAccessible(uintptr_t aVAddress) const {
+ MEMORY_BASIC_INFORMATION mbi;
+ SIZE_T result = nt::VirtualQuery(reinterpret_cast<LPCVOID>(aVAddress), &mbi,
+ sizeof(mbi));
+
+ return result && mbi.AllocationProtect && mbi.State == MEM_COMMIT &&
+ mbi.Protect != PAGE_NOACCESS;
+ }
+};
+
+class MOZ_TRIVIAL_CTOR_DTOR MMPolicyBase {
+ protected:
+ static uintptr_t AlignDown(const uintptr_t aUnaligned,
+ const uintptr_t aAlignTo) {
+ MOZ_ASSERT(IsPowerOfTwo(aAlignTo));
+#pragma warning(suppress : 4146)
+ return aUnaligned & (-aAlignTo);
+ }
+
+ static uintptr_t AlignUp(const uintptr_t aUnaligned,
+ const uintptr_t aAlignTo) {
+ MOZ_ASSERT(IsPowerOfTwo(aAlignTo));
+#pragma warning(suppress : 4146)
+ return aUnaligned + ((-aUnaligned) & (aAlignTo - 1));
+ }
+
+ static PVOID AlignUpToRegion(PVOID aUnaligned, uintptr_t aAlignTo,
+ size_t aLen, size_t aDesiredLen) {
+ uintptr_t unaligned = reinterpret_cast<uintptr_t>(aUnaligned);
+ uintptr_t aligned = AlignUp(unaligned, aAlignTo);
+ MOZ_ASSERT(aligned >= unaligned);
+
+ if (aLen < aligned - unaligned) {
+ return nullptr;
+ }
+
+ aLen -= (aligned - unaligned);
+ return reinterpret_cast<PVOID>((aLen >= aDesiredLen) ? aligned : 0);
+ }
+
+ public:
+#if defined(NIGHTLY_BUILD)
+ Maybe<DetourError> mLastError;
+ const Maybe<DetourError>& GetLastDetourError() const { return mLastError; }
+ template <typename... Args>
+ void SetLastDetourError(Args&&... aArgs) {
+ mLastError = Some(DetourError(std::forward<Args>(aArgs)...));
+ }
+#else
+ template <typename... Args>
+ void SetLastDetourError(Args&&... aArgs) {}
+#endif // defined(NIGHTLY_BUILD)
+
+ DWORD ComputeAllocationSize(const uint32_t aRequestedSize) const {
+ MOZ_ASSERT(aRequestedSize);
+ DWORD result = aRequestedSize;
+
+ const uint32_t granularity = GetAllocGranularity();
+
+ uint32_t mod = aRequestedSize % granularity;
+ if (mod) {
+ result += (granularity - mod);
+ }
+
+ return result;
+ }
+
+ DWORD GetAllocGranularity() const {
+ static const DWORD kAllocGranularity = []() -> DWORD {
+ SYSTEM_INFO sysInfo;
+ ::GetSystemInfo(&sysInfo);
+ return sysInfo.dwAllocationGranularity;
+ }();
+
+ return kAllocGranularity;
+ }
+
+ DWORD GetPageSize() const {
+ static const DWORD kPageSize = []() -> DWORD {
+ SYSTEM_INFO sysInfo;
+ ::GetSystemInfo(&sysInfo);
+ return sysInfo.dwPageSize;
+ }();
+
+ return kPageSize;
+ }
+
+ uintptr_t GetMaxUserModeAddress() const {
+ static const uintptr_t kMaxUserModeAddr = []() -> uintptr_t {
+ SYSTEM_INFO sysInfo;
+ ::GetSystemInfo(&sysInfo);
+ return reinterpret_cast<uintptr_t>(sysInfo.lpMaximumApplicationAddress);
+ }();
+
+ return kMaxUserModeAddr;
+ }
+
+ static const uint8_t* GetLowerBound(const Span<const uint8_t>& aBounds) {
+ return &(*aBounds.cbegin());
+ }
+
+ static const uint8_t* GetUpperBoundIncl(const Span<const uint8_t>& aBounds) {
+ // We return an upper bound that is inclusive.
+ return &(*(aBounds.cend() - 1));
+ }
+
+ static const uint8_t* GetUpperBoundExcl(const Span<const uint8_t>& aBounds) {
+ // We return an upper bound that is exclusive by adding 1 to the inclusive
+ // upper bound.
+ return GetUpperBoundIncl(aBounds) + 1;
+ }
+
+ /**
+ * It is convenient for us to provide address range information based on a
+ * "pivot" and a distance from that pivot, as branch instructions operate
+ * within a range of the program counter. OTOH, to actually manage the
+ * regions of memory, it is easier to think about them in terms of their
+ * lower and upper bounds. This function converts from the former format to
+ * the latter format.
+ */
+ Maybe<Span<const uint8_t>> SpanFromPivotAndDistance(
+ const uint32_t aSize, const uintptr_t aPivotAddr,
+ const uint32_t aMaxDistanceFromPivot) const {
+ if (!aPivotAddr || !aMaxDistanceFromPivot) {
+ return Nothing();
+ }
+
+ // We don't allow regions below 1MB so that we're not allocating near any
+ // sensitive areas in our address space.
+ const uintptr_t kMinAllowableAddress = 0x100000;
+
+ const uintptr_t kGranularity(GetAllocGranularity());
+
+ // We subtract the max distance from the pivot to determine our lower bound.
+ CheckedInt<uintptr_t> lowerBound(aPivotAddr);
+ lowerBound -= aMaxDistanceFromPivot;
+ if (lowerBound.isValid()) {
+ // In this case, the subtraction has not underflowed, but we still want
+ // the lower bound to be at least kMinAllowableAddress.
+ lowerBound = std::max(lowerBound.value(), kMinAllowableAddress);
+ } else {
+ // In this case, we underflowed. Forcibly set the lower bound to
+ // kMinAllowableAddress.
+ lowerBound = CheckedInt<uintptr_t>(kMinAllowableAddress);
+ }
+
+ // Align up to the next unit of allocation granularity when necessary.
+ lowerBound = AlignUp(lowerBound.value(), kGranularity);
+ MOZ_ASSERT(lowerBound.isValid());
+ if (!lowerBound.isValid()) {
+ return Nothing();
+ }
+
+ // We must ensure that our region is below the maximum allowable user-mode
+ // address, or our reservation will fail.
+ const uintptr_t kMaxUserModeAddr = GetMaxUserModeAddress();
+
+ // We add the max distance from the pivot to determine our upper bound.
+ CheckedInt<uintptr_t> upperBound(aPivotAddr);
+ upperBound += aMaxDistanceFromPivot;
+ if (upperBound.isValid()) {
+ // In this case, the addition has not overflowed, but we still want
+ // the upper bound to be at most kMaxUserModeAddr.
+ upperBound = std::min(upperBound.value(), kMaxUserModeAddr);
+ } else {
+ // In this case, we overflowed. Forcibly set the upper bound to
+ // kMaxUserModeAddr.
+ upperBound = CheckedInt<uintptr_t>(kMaxUserModeAddr);
+ }
+
+ // Subtract the desired allocation size so that any chunk allocated in the
+ // region will be reachable.
+ upperBound -= aSize;
+ if (!upperBound.isValid()) {
+ return Nothing();
+ }
+
+ // Align down to the next unit of allocation granularity when necessary.
+ upperBound = AlignDown(upperBound.value(), kGranularity);
+ if (!upperBound.isValid()) {
+ return Nothing();
+ }
+
+ MOZ_ASSERT(lowerBound.value() < upperBound.value());
+ if (lowerBound.value() >= upperBound.value()) {
+ return Nothing();
+ }
+
+ // Return the result as a Span
+ return Some(Span(reinterpret_cast<const uint8_t*>(lowerBound.value()),
+ upperBound.value() - lowerBound.value()));
+ }
+
+ /**
+ * This function locates a virtual memory region of |aDesiredBytesLen| that
+ * resides in the interval [aRangeMin, aRangeMax). We do this by scanning the
+ * virtual memory space for a block of unallocated memory that is sufficiently
+ * large.
+ */
+ PVOID FindRegion(HANDLE aProcess, const size_t aDesiredBytesLen,
+ const uint8_t* aRangeMin, const uint8_t* aRangeMax) {
+ // Convert the given pointers to uintptr_t because we should not
+ // compare two pointers unless they are from the same array or object.
+ uintptr_t rangeMin = reinterpret_cast<uintptr_t>(aRangeMin);
+ uintptr_t rangeMax = reinterpret_cast<uintptr_t>(aRangeMax);
+
+ const DWORD kGranularity = GetAllocGranularity();
+ if (!aDesiredBytesLen) {
+ SetLastDetourError(MMPOLICY_RESERVE_FINDREGION_INVALIDLEN);
+ return nullptr;
+ }
+
+ MOZ_ASSERT(rangeMin < rangeMax);
+ if (rangeMin >= rangeMax) {
+ SetLastDetourError(MMPOLICY_RESERVE_FINDREGION_INVALIDRANGE);
+ return nullptr;
+ }
+
+ // Generate a randomized base address that falls within the interval
+ // [aRangeMin, aRangeMax - aDesiredBytesLen]
+ unsigned int rnd = 0;
+ rand_s(&rnd);
+
+ // Reduce rnd to a value that falls within the acceptable range
+ uintptr_t maxOffset =
+ (rangeMax - rangeMin - aDesiredBytesLen) / kGranularity;
+ // Divide by maxOffset + 1 because maxOffset * kGranularity is acceptable.
+ uintptr_t offset = (uintptr_t(rnd) % (maxOffset + 1)) * kGranularity;
+
+ // Start searching at this address
+ const uintptr_t searchStart = rangeMin + offset;
+ // The max address needs to incorporate the desired length
+ const uintptr_t kMaxPtr = rangeMax - aDesiredBytesLen;
+
+ MOZ_DIAGNOSTIC_ASSERT(searchStart <= kMaxPtr);
+
+ MEMORY_BASIC_INFORMATION mbi;
+ SIZE_T len = sizeof(mbi);
+
+ // Scan the range for a free chunk that is at least as large as
+ // aDesiredBytesLen
+ // Scan [searchStart, kMaxPtr]
+ for (uintptr_t address = searchStart; address <= kMaxPtr;) {
+ if (nt::VirtualQueryEx(aProcess, reinterpret_cast<uint8_t*>(address),
+ &mbi, len) != len) {
+ SetLastDetourError(MMPOLICY_RESERVE_FINDREGION_VIRTUALQUERY_ERROR,
+ ::GetLastError());
+ return nullptr;
+ }
+
+ if (mbi.State == MEM_FREE) {
+ // |mbi.BaseAddress| is aligned with the page granularity, but may not
+ // be aligned with the allocation granularity. VirtualAlloc does not
+ // accept such a non-aligned address unless the corresponding allocation
+ // region is free. So we get the next boundary's start address.
+ PVOID regionStart = AlignUpToRegion(mbi.BaseAddress, kGranularity,
+ mbi.RegionSize, aDesiredBytesLen);
+ if (regionStart) {
+ return regionStart;
+ }
+ }
+
+ address = reinterpret_cast<uintptr_t>(mbi.BaseAddress) + mbi.RegionSize;
+ }
+
+ // Scan [aRangeMin, searchStart)
+ for (uintptr_t address = rangeMin; address < searchStart;) {
+ if (nt::VirtualQueryEx(aProcess, reinterpret_cast<uint8_t*>(address),
+ &mbi, len) != len) {
+ SetLastDetourError(MMPOLICY_RESERVE_FINDREGION_VIRTUALQUERY_ERROR,
+ ::GetLastError());
+ return nullptr;
+ }
+
+ if (mbi.State == MEM_FREE) {
+ PVOID regionStart = AlignUpToRegion(mbi.BaseAddress, kGranularity,
+ mbi.RegionSize, aDesiredBytesLen);
+ if (regionStart) {
+ return regionStart;
+ }
+ }
+
+ address = reinterpret_cast<uintptr_t>(mbi.BaseAddress) + mbi.RegionSize;
+ }
+
+ SetLastDetourError(MMPOLICY_RESERVE_FINDREGION_NO_FREE_REGION,
+ ::GetLastError());
+ return nullptr;
+ }
+
+ /**
+ * This function reserves a |aSize| block of virtual memory.
+ *
+ * When |aBounds| is Nothing, it just calls |aReserveFn| and lets Windows
+ * choose the base address.
+ *
+ * Otherwise, it tries to call |aReserveRangeFn| to reserve the memory within
+ * the bounds provided by |aBounds|. It is advantageous to use this function
+ * because the OS's VM manager has better information as to which base
+ * addresses are the best to use.
+ *
+ * If |aReserveRangeFn| retuns Nothing, this means that the platform support
+ * is not available. In that case, we fall back to manually computing a region
+ * to use for reserving the memory by calling |FindRegion|.
+ */
+ template <typename ReserveFnT, typename ReserveRangeFnT>
+ PVOID Reserve(HANDLE aProcess, const uint32_t aSize,
+ const ReserveFnT& aReserveFn,
+ const ReserveRangeFnT& aReserveRangeFn,
+ const Maybe<Span<const uint8_t>>& aBounds) {
+ if (!aBounds) {
+ // No restrictions, let the OS choose the base address
+ PVOID ret = aReserveFn(aProcess, nullptr, aSize);
+ if (!ret) {
+ SetLastDetourError(MMPOLICY_RESERVE_NOBOUND_RESERVE_ERROR,
+ ::GetLastError());
+ }
+ return ret;
+ }
+
+ const uint8_t* lowerBound = GetLowerBound(aBounds.ref());
+ const uint8_t* upperBoundExcl = GetUpperBoundExcl(aBounds.ref());
+
+ Maybe<PVOID> result =
+ aReserveRangeFn(aProcess, aSize, lowerBound, upperBoundExcl);
+ if (result) {
+ return result.value();
+ }
+
+ // aReserveRangeFn is not available on this machine. We'll do a manual
+ // search.
+
+ size_t curAttempt = 0;
+ const size_t kMaxAttempts = 8;
+
+ // We loop here because |FindRegion| may return a base address that
+ // is reserved elsewhere before we have had a chance to reserve it
+ // ourselves.
+ while (curAttempt < kMaxAttempts) {
+ PVOID base = FindRegion(aProcess, aSize, lowerBound, upperBoundExcl);
+ if (!base) {
+ return nullptr;
+ }
+
+ result = Some(aReserveFn(aProcess, base, aSize));
+ if (result.value()) {
+ return result.value();
+ }
+
+ ++curAttempt;
+ }
+
+ // If we run out of attempts, we fall through to the default case where
+ // the system chooses any base address it wants. In that case, the hook
+ // will be set on a best-effort basis.
+ PVOID ret = aReserveFn(aProcess, nullptr, aSize);
+ if (!ret) {
+ SetLastDetourError(MMPOLICY_RESERVE_FINAL_RESERVE_ERROR,
+ ::GetLastError());
+ }
+ return ret;
+ }
+};
+
+class MOZ_TRIVIAL_CTOR_DTOR MMPolicyInProcess
+ : public MMPolicyInProcessPrimitive,
+ public MMPolicyBase {
+ public:
+ typedef MMPolicyInProcess MMPolicyT;
+
+ constexpr 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 = std::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;
+ }
+
+ explicit operator bool() const { return !!mBase; }
+
+ /**
+ * Should we unhook everything upon destruction?
+ */
+ bool ShouldUnhookUponDestruction() const { return true; }
+
+#if defined(_M_IX86)
+ bool WriteAtomic(void* aDestPtr, const uint16_t aValue) const {
+ *static_cast<uint16_t*>(aDestPtr) = aValue;
+ return true;
+ }
+#endif // defined(_M_IX86)
+
+ bool Protect(void* aVAddress, size_t aSize, uint32_t aProtFlags,
+ uint32_t* aPrevProtFlags) const {
+ return ProtectInternal(::VirtualProtect, aVAddress, aSize, aProtFlags,
+ aPrevProtFlags);
+ }
+
+ bool FlushInstructionCache() const {
+ return !!::FlushInstructionCache(::GetCurrentProcess(), nullptr, 0);
+ }
+
+ static DWORD GetTrampWriteProtFlags() { return PAGE_EXECUTE_READWRITE; }
+
+#if defined(_M_X64)
+ bool IsTrampolineSpaceInLowest2GB() const {
+ return (mBase + mReservationSize) <=
+ reinterpret_cast<uint8_t*>(0x0000000080000000ULL);
+ }
+#endif // defined(_M_X64)
+
+ 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,
+ const Maybe<Span<const uint8_t>>& aBounds) {
+ if (!aSize) {
+ return 0;
+ }
+
+ if (mBase) {
+ MOZ_ASSERT(mReservationSize >= aSize);
+ return mReservationSize;
+ }
+
+ mReservationSize = ComputeAllocationSize(aSize);
+
+ auto reserveFn = [](HANDLE aProcess, PVOID aBase, uint32_t aSize) -> PVOID {
+ return ::VirtualAlloc(aBase, aSize, MEM_RESERVE, PAGE_NOACCESS);
+ };
+
+ auto reserveWithinRangeFn =
+ [](HANDLE aProcess, uint32_t aSize, const uint8_t* aRangeMin,
+ const uint8_t* aRangeMaxExcl) -> Maybe<PVOID> {
+ static const StaticDynamicallyLinkedFunctionPtr<decltype(
+ &::VirtualAlloc2)>
+ pVirtualAlloc2(L"kernelbase.dll", "VirtualAlloc2");
+ if (!pVirtualAlloc2) {
+ return Nothing();
+ }
+
+ // NB: MEM_ADDRESS_REQUIREMENTS::HighestEndingAddress is *inclusive*
+ MEM_ADDRESS_REQUIREMENTS memReq = {
+ const_cast<uint8_t*>(aRangeMin),
+ const_cast<uint8_t*>(aRangeMaxExcl - 1)};
+
+ MEM_EXTENDED_PARAMETER memParam = {};
+ memParam.Type = MemExtendedParameterAddressRequirements;
+ memParam.Pointer = &memReq;
+
+ return Some(pVirtualAlloc2(aProcess, nullptr, aSize, MEM_RESERVE,
+ PAGE_NOACCESS, &memParam, 1));
+ };
+
+ mBase = static_cast<uint8_t*>(
+ MMPolicyBase::Reserve(::GetCurrentProcess(), mReservationSize,
+ reserveFn, reserveWithinRangeFn, aBounds));
+
+ 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;
+};
+
+// This class manages in-process memory access without using functions
+// imported from kernel32.dll. Instead, it uses functions in its own
+// function table that are provided from outside.
+class MMPolicyInProcessEarlyStage : public MMPolicyInProcessPrimitive {
+ public:
+ struct Kernel32Exports {
+ decltype(&::FlushInstructionCache) mFlushInstructionCache;
+ decltype(&::GetModuleHandleW) mGetModuleHandleW;
+ decltype(&::GetSystemInfo) mGetSystemInfo;
+ decltype(&::VirtualProtect) mVirtualProtect;
+ };
+
+ private:
+ static DWORD GetPageSize(const Kernel32Exports& aK32Exports) {
+ SYSTEM_INFO sysInfo;
+ aK32Exports.mGetSystemInfo(&sysInfo);
+ return sysInfo.dwPageSize;
+ }
+
+ const Kernel32Exports& mK32Exports;
+ const DWORD mPageSize;
+
+ public:
+ explicit MMPolicyInProcessEarlyStage(const Kernel32Exports& aK32Exports)
+ : mK32Exports(aK32Exports), mPageSize(GetPageSize(mK32Exports)) {}
+
+ // The pattern of constructing a local static variable with a lambda,
+ // which can be seen in MMPolicyBase, is compiled into code with the
+ // critical section APIs like EnterCriticalSection imported from kernel32.dll.
+ // Because this class needs to be able to run in a process's early stage
+ // when IAT is not yet resolved, we cannot use that patten, thus simply
+ // caching a value as a local member in the class.
+ DWORD GetPageSize() const { return mPageSize; }
+
+ bool Protect(void* aVAddress, size_t aSize, uint32_t aProtFlags,
+ uint32_t* aPrevProtFlags) const {
+ return ProtectInternal(mK32Exports.mVirtualProtect, aVAddress, aSize,
+ aProtFlags, aPrevProtFlags);
+ }
+
+ bool FlushInstructionCache() const {
+ const HANDLE kCurrentProcess = reinterpret_cast<HANDLE>(-1);
+ return !!mK32Exports.mFlushInstructionCache(kCurrentProcess, nullptr, 0);
+ }
+};
+
+class MMPolicyOutOfProcess : public MMPolicyBase {
+ public:
+ typedef MMPolicyOutOfProcess MMPolicyT;
+
+ explicit MMPolicyOutOfProcess(HANDLE aProcess)
+ : mProcess(nullptr),
+ mMapping(nullptr),
+ mLocalView(nullptr),
+ mRemoteView(nullptr),
+ mReservationSize(0),
+ mCommitOffset(0) {
+ MOZ_ASSERT(aProcess);
+ ::DuplicateHandle(::GetCurrentProcess(), aProcess, ::GetCurrentProcess(),
+ &mProcess, kAccessFlags, FALSE, 0);
+ MOZ_ASSERT(mProcess);
+ }
+
+ explicit MMPolicyOutOfProcess(DWORD aPid)
+ : mProcess(::OpenProcess(kAccessFlags, FALSE, aPid)),
+ mMapping(nullptr),
+ mLocalView(nullptr),
+ mRemoteView(nullptr),
+ mReservationSize(0),
+ mCommitOffset(0) {
+ MOZ_ASSERT(mProcess);
+ }
+
+ ~MMPolicyOutOfProcess() { Destroy(); }
+
+ MMPolicyOutOfProcess(MMPolicyOutOfProcess&& aOther)
+ : mProcess(nullptr),
+ mMapping(nullptr),
+ mLocalView(nullptr),
+ mRemoteView(nullptr),
+ mReservationSize(0),
+ mCommitOffset(0) {
+ *this = std::move(aOther);
+ }
+
+ MMPolicyOutOfProcess(const MMPolicyOutOfProcess& aOther) = delete;
+ MMPolicyOutOfProcess& operator=(const MMPolicyOutOfProcess&) = delete;
+
+ MMPolicyOutOfProcess& operator=(MMPolicyOutOfProcess&& aOther) {
+ Destroy();
+
+ mProcess = aOther.mProcess;
+ aOther.mProcess = nullptr;
+
+ mMapping = aOther.mMapping;
+ aOther.mMapping = nullptr;
+
+ mLocalView = aOther.mLocalView;
+ aOther.mLocalView = nullptr;
+
+ mRemoteView = aOther.mRemoteView;
+ aOther.mRemoteView = nullptr;
+
+ mReservationSize = aOther.mReservationSize;
+ aOther.mReservationSize = 0;
+
+ mCommitOffset = aOther.mCommitOffset;
+ aOther.mCommitOffset = 0;
+
+ return *this;
+ }
+
+ explicit operator bool() const {
+ return mProcess && mMapping && mLocalView && mRemoteView;
+ }
+
+ bool ShouldUnhookUponDestruction() const {
+ // We don't clean up hooks for remote processes; they are expected to
+ // outlive our process.
+ return false;
+ }
+
+ // This function reads as many bytes as |aLen| from the target process and
+ // succeeds only when the entire area to be read is accessible.
+ bool Read(void* aToPtr, const void* aFromPtr, size_t aLen) const {
+ MOZ_ASSERT(mProcess);
+ if (!mProcess) {
+ return false;
+ }
+
+ SIZE_T numBytes = 0;
+ BOOL ok = ::ReadProcessMemory(mProcess, aFromPtr, aToPtr, aLen, &numBytes);
+ return ok && numBytes == aLen;
+ }
+
+ // This function reads as many bytes as possible from the target process up
+ // to |aLen| bytes and returns the number of bytes which was actually read.
+ size_t TryRead(void* aToPtr, const void* aFromPtr, size_t aLen) const {
+ MOZ_ASSERT(mProcess);
+ if (!mProcess) {
+ return 0;
+ }
+
+ uint32_t pageSize = GetPageSize();
+ uintptr_t pageMask = pageSize - 1;
+
+ auto rangeStart = reinterpret_cast<uintptr_t>(aFromPtr);
+ auto rangeEnd = rangeStart + aLen;
+
+ while (rangeStart < rangeEnd) {
+ SIZE_T numBytes = 0;
+ BOOL ok = ::ReadProcessMemory(mProcess, aFromPtr, aToPtr,
+ rangeEnd - rangeStart, &numBytes);
+ if (ok) {
+ return numBytes;
+ }
+
+ // If ReadProcessMemory fails, try to read up to each page boundary from
+ // the end of the requested area one by one.
+ if (rangeEnd & pageMask) {
+ rangeEnd &= ~pageMask;
+ } else {
+ rangeEnd -= pageSize;
+ }
+ }
+
+ return 0;
+ }
+
+ bool Write(void* aToPtr, const void* aFromPtr, size_t aLen) const {
+ MOZ_ASSERT(mProcess);
+ if (!mProcess) {
+ return false;
+ }
+
+ SIZE_T numBytes = 0;
+ BOOL ok = ::WriteProcessMemory(mProcess, aToPtr, aFromPtr, aLen, &numBytes);
+ return ok && numBytes == aLen;
+ }
+
+ bool Protect(void* aVAddress, size_t aSize, uint32_t aProtFlags,
+ uint32_t* aPrevProtFlags) const {
+ MOZ_ASSERT(mProcess);
+ if (!mProcess) {
+ return false;
+ }
+
+ MOZ_ASSERT(aPrevProtFlags);
+ BOOL ok = ::VirtualProtectEx(mProcess, aVAddress, aSize, aProtFlags,
+ reinterpret_cast<PDWORD>(aPrevProtFlags));
+ if (!ok && aPrevProtFlags) {
+ // VirtualProtectEx can fail but still set valid protection flags.
+ // Let's clear those upon failure.
+ *aPrevProtFlags = 0;
+ }
+
+ return !!ok;
+ }
+
+ /**
+ * @return true if the page that hosts aVAddress is accessible.
+ */
+ bool IsPageAccessible(uintptr_t aVAddress) const {
+ MEMORY_BASIC_INFORMATION mbi;
+ SIZE_T result = nt::VirtualQueryEx(
+ mProcess, reinterpret_cast<LPCVOID>(aVAddress), &mbi, sizeof(mbi));
+
+ return result && mbi.AllocationProtect && mbi.State == MEM_COMMIT &&
+ mbi.Protect != PAGE_NOACCESS;
+ }
+
+ bool FlushInstructionCache() const {
+ return !!::FlushInstructionCache(mProcess, nullptr, 0);
+ }
+
+ static DWORD GetTrampWriteProtFlags() { return PAGE_READWRITE; }
+
+#if defined(_M_X64)
+ bool IsTrampolineSpaceInLowest2GB() const {
+ return (GetRemoteView() + mReservationSize) <= 0x0000000080000000ULL;
+ }
+#endif // defined(_M_X64)
+
+ protected:
+ uint8_t* GetLocalView() const { return mLocalView; }
+
+ uintptr_t GetRemoteView() const {
+ return reinterpret_cast<uintptr_t>(mRemoteView);
+ }
+
+ /**
+ * @return the effective number of bytes reserved, or 0 on failure
+ */
+ uint32_t Reserve(const uint32_t aSize,
+ const Maybe<Span<const uint8_t>>& aBounds) {
+ if (!aSize || !mProcess) {
+ SetLastDetourError(MMPOLICY_RESERVE_INVALIDARG);
+ return 0;
+ }
+
+ if (mRemoteView) {
+ MOZ_ASSERT(mReservationSize >= aSize);
+ SetLastDetourError(MMPOLICY_RESERVE_ZERO_RESERVATIONSIZE);
+ return mReservationSize;
+ }
+
+ mReservationSize = ComputeAllocationSize(aSize);
+
+ mMapping = ::CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr,
+ PAGE_EXECUTE_READWRITE | SEC_RESERVE, 0,
+ mReservationSize, nullptr);
+ if (!mMapping) {
+ SetLastDetourError(MMPOLICY_RESERVE_CREATEFILEMAPPING, ::GetLastError());
+ return 0;
+ }
+
+ mLocalView = static_cast<uint8_t*>(
+ ::MapViewOfFile(mMapping, FILE_MAP_WRITE, 0, 0, 0));
+ if (!mLocalView) {
+ SetLastDetourError(MMPOLICY_RESERVE_MAPVIEWOFFILE, ::GetLastError());
+ return 0;
+ }
+
+ auto reserveFn = [mapping = mMapping](HANDLE aProcess, PVOID aBase,
+ uint32_t aSize) -> PVOID {
+ return mozilla::MapRemoteViewOfFile(mapping, aProcess, 0ULL, aBase, 0, 0,
+ PAGE_EXECUTE_READ);
+ };
+
+ auto reserveWithinRangeFn =
+ [mapping = mMapping](HANDLE aProcess, uint32_t aSize,
+ const uint8_t* aRangeMin,
+ const uint8_t* aRangeMaxExcl) -> Maybe<PVOID> {
+ static const StaticDynamicallyLinkedFunctionPtr<decltype(
+ &::MapViewOfFile3)>
+ pMapViewOfFile3(L"kernelbase.dll", "MapViewOfFile3");
+ if (!pMapViewOfFile3) {
+ return Nothing();
+ }
+
+ // NB: MEM_ADDRESS_REQUIREMENTS::HighestEndingAddress is *inclusive*
+ MEM_ADDRESS_REQUIREMENTS memReq = {
+ const_cast<uint8_t*>(aRangeMin),
+ const_cast<uint8_t*>(aRangeMaxExcl - 1)};
+
+ MEM_EXTENDED_PARAMETER memParam = {};
+ memParam.Type = MemExtendedParameterAddressRequirements;
+ memParam.Pointer = &memReq;
+
+ return Some(pMapViewOfFile3(mapping, aProcess, nullptr, 0, aSize, 0,
+ PAGE_EXECUTE_READ, &memParam, 1));
+ };
+
+ mRemoteView = MMPolicyBase::Reserve(mProcess, mReservationSize, reserveFn,
+ reserveWithinRangeFn, aBounds);
+ if (!mRemoteView) {
+ 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(mLocalView + mCommitOffset, GetPageSize(),
+ MEM_COMMIT, PAGE_READWRITE);
+ if (!local) {
+ return false;
+ }
+
+ PVOID remote = ::VirtualAllocEx(
+ mProcess, static_cast<uint8_t*>(mRemoteView) + mCommitOffset,
+ GetPageSize(), MEM_COMMIT, PAGE_EXECUTE_READ);
+ if (!remote) {
+ return false;
+ }
+
+ mCommitOffset += GetPageSize();
+ return true;
+ }
+
+ private:
+ void Destroy() {
+ // We always leak the remote view
+ if (mLocalView) {
+ ::UnmapViewOfFile(mLocalView);
+ mLocalView = nullptr;
+ }
+
+ if (mMapping) {
+ ::CloseHandle(mMapping);
+ mMapping = nullptr;
+ }
+
+ if (mProcess) {
+ ::CloseHandle(mProcess);
+ mProcess = nullptr;
+ }
+ }
+
+ private:
+ HANDLE mProcess;
+ HANDLE mMapping;
+ uint8_t* mLocalView;
+ PVOID mRemoteView;
+ uint32_t mReservationSize;
+ uint32_t mCommitOffset;
+
+ static const DWORD kAccessFlags = PROCESS_QUERY_INFORMATION |
+ PROCESS_VM_OPERATION | PROCESS_VM_READ |
+ PROCESS_VM_WRITE;
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_MMPolicies_h
diff --git a/mozglue/misc/interceptor/PatcherBase.h b/mozglue/misc/interceptor/PatcherBase.h
new file mode 100644
index 0000000000..e39a38fafd
--- /dev/null
+++ b/mozglue/misc/interceptor/PatcherBase.h
@@ -0,0 +1,141 @@
+/* -*- 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_PatcherBase_h
+#define mozilla_interceptor_PatcherBase_h
+
+#include "mozilla/interceptor/TargetFunction.h"
+
+namespace mozilla {
+namespace interceptor {
+
+template <typename MMPolicy>
+struct GetProcAddressSelector;
+
+template <>
+struct GetProcAddressSelector<MMPolicyOutOfProcess> {
+ FARPROC operator()(HMODULE aModule, const char* aName,
+ const MMPolicyOutOfProcess& aMMPolicy) const {
+ auto exportSection =
+ mozilla::nt::PEExportSection<MMPolicyOutOfProcess>::Get(aModule,
+ aMMPolicy);
+ return exportSection.GetProcAddress(aName);
+ }
+};
+
+template <>
+struct GetProcAddressSelector<MMPolicyInProcess> {
+ FARPROC operator()(HMODULE aModule, const char* aName,
+ const MMPolicyInProcess&) const {
+ // PEExportSection works for MMPolicyInProcess, too, but the native
+ // GetProcAddress is still better because PEExportSection does not
+ // solve a forwarded entry.
+ return ::GetProcAddress(aModule, aName);
+ }
+};
+
+template <typename VMPolicy>
+class WindowsDllPatcherBase {
+ protected:
+ typedef typename VMPolicy::MMPolicyT MMPolicyT;
+
+ template <typename... Args>
+ explicit WindowsDllPatcherBase(Args&&... aArgs)
+ : mVMPolicy(std::forward<Args>(aArgs)...) {}
+
+ ReadOnlyTargetFunction<MMPolicyT> ResolveRedirectedAddress(
+ FARPROC aOriginalFunction) {
+ uintptr_t currAddr = reinterpret_cast<uintptr_t>(aOriginalFunction);
+
+#if defined(_M_IX86) || defined(_M_X64)
+ uintptr_t prevAddr = 0;
+ while (prevAddr != currAddr) {
+ ReadOnlyTargetFunction<MMPolicyT> currFunc(mVMPolicy, currAddr);
+ prevAddr = currAddr;
+
+ // If function entry is jmp rel8 stub to the internal implementation, we
+ // resolve redirected address from the jump target.
+ uintptr_t nextAddr = 0;
+ if (currFunc.IsRelativeShortJump(&nextAddr)) {
+ int8_t offset = nextAddr - currFunc.GetAddress() - 2;
+
+# if defined(_M_X64)
+ // We redirect to the target of a short jump backwards if the target
+ // is another jump (only 32-bit displacement is currently supported).
+ // This case is used by GetFileAttributesW in Win7 x64.
+ if ((offset < 0) && (currFunc.IsValidAtOffset(2 + offset))) {
+ ReadOnlyTargetFunction<MMPolicyT> redirectFn(mVMPolicy, nextAddr);
+ if (redirectFn.IsIndirectNearJump(&nextAddr)) {
+ return redirectFn;
+ }
+ }
+# endif
+
+ // We check the downstream has enough nop-space only when the offset is
+ // positive. Otherwise we stop chasing redirects and let the caller
+ // fail to hook.
+ if (offset > 0) {
+ bool isNopSpace = true;
+ for (int8_t i = 0; i < offset; i++) {
+ if (currFunc[2 + i] != 0x90) {
+ isNopSpace = false;
+ break;
+ }
+ }
+
+ if (isNopSpace) {
+ currAddr = nextAddr;
+ }
+ }
+# if defined(_M_X64)
+ } else if (currFunc.IsIndirectNearJump(&nextAddr) ||
+ currFunc.IsRelativeNearJump(&nextAddr)) {
+# else
+ } else if (currFunc.IsIndirectNearJump(&nextAddr)) {
+# endif
+ // If function entry is jmp [disp32] such as used by kernel32, we
+ // resolve redirected address from import table. For x64, we resolve
+ // a relative near jump for TestDllInterceptor with --disable-optimize.
+ currAddr = nextAddr;
+ }
+ }
+#endif // defined(_M_IX86) || defined(_M_X64)
+
+ if (currAddr != reinterpret_cast<uintptr_t>(aOriginalFunction) &&
+ !mVMPolicy.IsPageAccessible(currAddr)) {
+ currAddr = reinterpret_cast<uintptr_t>(aOriginalFunction);
+ }
+ return ReadOnlyTargetFunction<MMPolicyT>(mVMPolicy, currAddr);
+ }
+
+ public:
+ FARPROC GetProcAddress(HMODULE aModule, const char* aName) const {
+ GetProcAddressSelector<MMPolicyT> selector;
+ return selector(aModule, aName, mVMPolicy);
+ }
+
+ bool IsPageAccessible(uintptr_t aAddress) const {
+ return mVMPolicy.IsPageAccessible(aAddress);
+ }
+
+#if defined(NIGHTLY_BUILD)
+ const Maybe<DetourError>& GetLastDetourError() const {
+ return mVMPolicy.GetLastDetourError();
+ }
+#endif // defined(NIGHTLY_BUILD)
+ template <typename... Args>
+ void SetLastDetourError(Args&&... aArgs) {
+ mVMPolicy.SetLastDetourError(std::forward<Args>(aArgs)...);
+ }
+
+ protected:
+ VMPolicy mVMPolicy;
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_PatcherBase_h
diff --git a/mozglue/misc/interceptor/PatcherDetour.h b/mozglue/misc/interceptor/PatcherDetour.h
new file mode 100644
index 0000000000..7b04a20c2b
--- /dev/null
+++ b/mozglue/misc/interceptor/PatcherDetour.h
@@ -0,0 +1,1715 @@
+/* -*- 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_PatcherDetour_h
+#define mozilla_interceptor_PatcherDetour_h
+
+#if defined(_M_ARM64)
+# include "mozilla/interceptor/Arm64.h"
+#endif // defined(_M_ARM64)
+#include <utility>
+
+#include "mozilla/Maybe.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/Types.h"
+#include "mozilla/Unused.h"
+#include "mozilla/interceptor/PatcherBase.h"
+#include "mozilla/interceptor/Trampoline.h"
+#include "mozilla/interceptor/VMSharingPolicies.h"
+
+#define COPY_CODES(NBYTES) \
+ do { \
+ tramp.CopyFrom(origBytes.GetAddress(), NBYTES); \
+ origBytes += NBYTES; \
+ } while (0)
+
+namespace mozilla {
+namespace interceptor {
+
+enum class DetourFlags : uint32_t {
+ eDefault = 0,
+ eEnable10BytePatch = 1, // Allow 10-byte patches when conditions allow
+ eTestOnlyForceShortPatch =
+ 2, // Force short patches at all times (x86-64 and arm64 testing only)
+ eDontResolveRedirection =
+ 4, // Don't resolve the redirection of JMP (e.g. kernel32 -> kernelbase)
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(DetourFlags)
+
+// This class is responsible to do tasks which depend on MMPolicy, decoupled
+// from VMPolicy. We already have WindowsDllPatcherBase, but it needs to
+// depend on VMPolicy to hold an instance of VMPolicy as a member.
+template <typename MMPolicyT>
+class WindowsDllDetourPatcherPrimitive {
+ protected:
+#if defined(_M_ARM64)
+ // LDR x16, .+8
+ static const uint32_t kLdrX16Plus8 = 0x58000050U;
+#endif // defined(_M_ARM64)
+
+ static void ApplyDefaultPatch(WritableTargetFunction<MMPolicyT>& target,
+ intptr_t aDest) {
+#if defined(_M_IX86)
+ target.WriteByte(0xe9); // jmp
+ target.WriteDisp32(aDest); // hook displacement
+#elif defined(_M_X64)
+ // mov r11, address
+ target.WriteByte(0x49);
+ target.WriteByte(0xbb);
+ target.WritePointer(aDest);
+
+ // jmp r11
+ target.WriteByte(0x41);
+ target.WriteByte(0xff);
+ target.WriteByte(0xe3);
+#elif defined(_M_ARM64)
+ // The default patch requires 16 bytes
+ // LDR x16, .+8
+ target.WriteLong(kLdrX16Plus8);
+ // BR x16
+ target.WriteLong(arm64::BuildUnconditionalBranchToRegister(16));
+ target.WritePointer(aDest);
+#else
+# error "Unsupported processor architecture"
+#endif
+ }
+
+ public:
+ constexpr static uint32_t GetWorstCaseRequiredBytesToPatch() {
+#if defined(_M_IX86)
+ return 5;
+#elif defined(_M_X64)
+ return 13;
+#elif defined(_M_ARM64)
+ return 16;
+#else
+# error "Unsupported processor architecture"
+#endif
+ }
+
+ WindowsDllDetourPatcherPrimitive() = default;
+
+ WindowsDllDetourPatcherPrimitive(const WindowsDllDetourPatcherPrimitive&) =
+ delete;
+ WindowsDllDetourPatcherPrimitive(WindowsDllDetourPatcherPrimitive&&) = delete;
+ WindowsDllDetourPatcherPrimitive& operator=(
+ const WindowsDllDetourPatcherPrimitive&) = delete;
+ WindowsDllDetourPatcherPrimitive& operator=(
+ WindowsDllDetourPatcherPrimitive&&) = delete;
+
+ bool AddIrreversibleHook(const MMPolicyT& aMMPolicy, FARPROC aTargetFn,
+ intptr_t aHookDest) {
+ ReadOnlyTargetFunction<MMPolicyT> targetReadOnly(aMMPolicy, aTargetFn);
+
+ WritableTargetFunction<MMPolicyT> targetWritable(
+ targetReadOnly.Promote(GetWorstCaseRequiredBytesToPatch()));
+ if (!targetWritable) {
+ return false;
+ }
+
+ ApplyDefaultPatch(targetWritable, aHookDest);
+
+ return targetWritable.Commit();
+ }
+};
+
+template <typename VMPolicy>
+class WindowsDllDetourPatcher final
+ : public WindowsDllDetourPatcherPrimitive<typename VMPolicy::MMPolicyT>,
+ public WindowsDllPatcherBase<VMPolicy> {
+ using MMPolicyT = typename VMPolicy::MMPolicyT;
+ using TrampPoolT = typename VMPolicy::PoolType;
+ using PrimitiveT = WindowsDllDetourPatcherPrimitive<MMPolicyT>;
+ Maybe<DetourFlags> mFlags;
+
+ public:
+ template <typename... Args>
+ explicit WindowsDllDetourPatcher(Args&&... aArgs)
+ : WindowsDllPatcherBase<VMPolicy>(std::forward<Args>(aArgs)...) {}
+
+ ~WindowsDllDetourPatcher() { Clear(); }
+
+ WindowsDllDetourPatcher(const WindowsDllDetourPatcher&) = delete;
+ WindowsDllDetourPatcher(WindowsDllDetourPatcher&&) = delete;
+ WindowsDllDetourPatcher& operator=(const WindowsDllDetourPatcher&) = delete;
+ WindowsDllDetourPatcher& operator=(WindowsDllDetourPatcher&&) = delete;
+
+ void Clear() {
+ if (!this->mVMPolicy.ShouldUnhookUponDestruction()) {
+ return;
+ }
+
+#if defined(_M_IX86)
+ size_t nBytes = 1 + sizeof(intptr_t);
+#elif defined(_M_X64)
+ size_t nBytes = 2 + sizeof(intptr_t);
+#elif defined(_M_ARM64)
+ size_t nBytes = 2 * sizeof(uint32_t) + sizeof(uintptr_t);
+#else
+# error "Unknown processor type"
+#endif
+
+ const auto& tramps = this->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(
+ this->mVMPolicy, interceptedFn.value(), nBytes);
+ if (!origBytes) {
+ continue;
+ }
+
+#if defined(_M_IX86) || defined(_M_X64)
+
+ 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.
+ MOZ_ASSERT(opcode1 == 0xE9);
+ if (opcode1 != 0xE9) {
+ continue;
+ }
+
+ intptr_t startOfTrampInstructions =
+ static_cast<intptr_t>(tramp.GetCurrentRemoteAddress());
+
+ origBytes.WriteDisp32(startOfTrampInstructions);
+ if (!origBytes) {
+ continue;
+ }
+
+ origBytes.Commit();
+# elif defined(_M_X64)
+ if (opcode1 == 0x49) {
+ if (!Clear13BytePatch(origBytes, tramp.GetCurrentRemoteAddress())) {
+ continue;
+ }
+ } else if (opcode1 == 0xB8) {
+ if (!Clear10BytePatch(origBytes)) {
+ continue;
+ }
+ } else if (opcode1 == 0x48) {
+ // The original function was just a different trampoline
+ if (!ClearTrampolinePatch(origBytes, tramp.GetCurrentRemoteAddress())) {
+ continue;
+ }
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized patch!");
+ continue;
+ }
+# endif
+
+#elif defined(_M_ARM64)
+
+ // Ensure that we see the instruction that we expect
+ Maybe<uint32_t> inst1 = origBytes.ReadLong();
+ if (!inst1) {
+ continue;
+ }
+
+ if (inst1.value() == this->kLdrX16Plus8) {
+ if (!Clear16BytePatch(origBytes, tramp.GetCurrentRemoteAddress())) {
+ continue;
+ }
+ } else if (arm64::IsUnconditionalBranchImm(inst1.value())) {
+ if (!Clear4BytePatch(inst1.value(), origBytes)) {
+ continue;
+ }
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized patch!");
+ continue;
+ }
+
+#else
+# error "Unknown processor type"
+#endif
+ }
+
+ this->mVMPolicy.Clear();
+ }
+
+#if defined(_M_X64)
+ bool Clear13BytePatch(WritableTargetFunction<MMPolicyT>& aOrigBytes,
+ const uintptr_t aResetToAddress) {
+ Maybe<uint8_t> maybeOpcode2 = aOrigBytes.ReadByte();
+ if (!maybeOpcode2) {
+ return false;
+ }
+
+ uint8_t opcode2 = maybeOpcode2.value();
+ if (opcode2 != 0xBB) {
+ return false;
+ }
+
+ aOrigBytes.WritePointer(aResetToAddress);
+ if (!aOrigBytes) {
+ return false;
+ }
+
+ return aOrigBytes.Commit();
+ }
+
+ bool ClearTrampolinePatch(WritableTargetFunction<MMPolicyT>& aOrigBytes,
+ const uintptr_t aPtrToResetToAddress) {
+ // The target of the trampoline we replaced is stored at
+ // aPtrToResetToAddress. We simply put it back where we got it from.
+ Maybe<uint8_t> maybeOpcode2 = aOrigBytes.ReadByte();
+ if (!maybeOpcode2) {
+ return false;
+ }
+
+ uint8_t opcode2 = maybeOpcode2.value();
+ if (opcode2 != 0xB8) {
+ return false;
+ }
+
+ auto oldPtr = *(reinterpret_cast<const uintptr_t*>(aPtrToResetToAddress));
+
+ aOrigBytes.WritePointer(oldPtr);
+ if (!aOrigBytes) {
+ return false;
+ }
+
+ return aOrigBytes.Commit();
+ }
+
+ bool Clear10BytePatch(WritableTargetFunction<MMPolicyT>& aOrigBytes) {
+ Maybe<uint32_t> maybePtr32 = aOrigBytes.ReadLong();
+ if (!maybePtr32) {
+ return false;
+ }
+
+ uint32_t ptr32 = maybePtr32.value();
+ // We expect the high bit to be clear
+ if (ptr32 & 0x80000000) {
+ return false;
+ }
+
+ uintptr_t trampPtr = ptr32;
+
+ // trampPtr points to an intermediate trampoline that contains a 13-byte
+ // patch. We back up by sizeof(uintptr_t) so that we can access the pointer
+ // to the stub trampoline.
+ WritableTargetFunction<MMPolicyT> writableIntermediate(
+ this->mVMPolicy, trampPtr - sizeof(uintptr_t), 13 + sizeof(uintptr_t));
+ if (!writableIntermediate) {
+ return false;
+ }
+
+ Maybe<uintptr_t> stubTramp = writableIntermediate.ReadEncodedPtr();
+ if (!stubTramp || !stubTramp.value()) {
+ return false;
+ }
+
+ Maybe<uint8_t> maybeOpcode1 = writableIntermediate.ReadByte();
+ if (!maybeOpcode1) {
+ return false;
+ }
+
+ // We expect this opcode to be the beginning of our normal mov r11, ptr
+ // patch sequence.
+ uint8_t opcode1 = maybeOpcode1.value();
+ if (opcode1 != 0x49) {
+ return false;
+ }
+
+ // Now we can just delegate the rest to our normal 13-byte patch clearing.
+ return Clear13BytePatch(writableIntermediate, stubTramp.value());
+ }
+#endif // defined(_M_X64)
+
+#if defined(_M_ARM64)
+ bool Clear4BytePatch(const uint32_t aBranchImm,
+ WritableTargetFunction<MMPolicyT>& aOrigBytes) {
+ MOZ_ASSERT(arm64::IsUnconditionalBranchImm(aBranchImm));
+
+ arm64::LoadOrBranch decoded = arm64::BUncondImmDecode(
+ aOrigBytes.GetCurrentAddress() - sizeof(uint32_t), aBranchImm);
+
+ uintptr_t trampPtr = decoded.mAbsAddress;
+
+ // trampPtr points to an intermediate trampoline that contains a veneer.
+ // We back up by sizeof(uintptr_t) so that we can access the pointer to the
+ // stub trampoline.
+
+ // We want trampLen to be the size of the veneer, plus one pointer (since
+ // we are backing up trampPtr by one pointer)
+ size_t trampLen = 16 + sizeof(uintptr_t);
+
+ WritableTargetFunction<MMPolicyT> writableIntermediate(
+ this->mVMPolicy, trampPtr - sizeof(uintptr_t), trampLen);
+ if (!writableIntermediate) {
+ return false;
+ }
+
+ Maybe<uintptr_t> stubTramp = writableIntermediate.ReadEncodedPtr();
+ if (!stubTramp || !stubTramp.value()) {
+ return false;
+ }
+
+ Maybe<uint32_t> inst1 = writableIntermediate.ReadLong();
+ if (!inst1 || inst1.value() != this->kLdrX16Plus8) {
+ return false;
+ }
+
+ return Clear16BytePatch(writableIntermediate, stubTramp.value());
+ }
+
+ bool Clear16BytePatch(WritableTargetFunction<MMPolicyT>& aOrigBytes,
+ const uintptr_t aResetToAddress) {
+ Maybe<uint32_t> inst2 = aOrigBytes.ReadLong();
+ if (!inst2) {
+ return false;
+ }
+
+ if (inst2.value() != arm64::BuildUnconditionalBranchToRegister(16)) {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized patch!");
+ return false;
+ }
+
+ // Clobber the pointer to our hook function with a pointer to the
+ // start of the trampoline.
+ aOrigBytes.WritePointer(aResetToAddress);
+ aOrigBytes.Commit();
+
+ return true;
+ }
+#endif // defined(_M_ARM64)
+
+ void Init(DetourFlags aFlags = DetourFlags::eDefault) {
+ if (Initialized()) {
+ return;
+ }
+
+#if defined(_M_X64)
+ if (aFlags & DetourFlags::eTestOnlyForceShortPatch) {
+ aFlags |= DetourFlags::eEnable10BytePatch;
+ }
+#endif // defined(_M_X64)
+
+ mFlags = Some(aFlags);
+ }
+
+ bool Initialized() const { return mFlags.isSome(); }
+
+ bool AddHook(FARPROC aTargetFn, intptr_t aHookDest, void** aOrigFunc) {
+ ReadOnlyTargetFunction<MMPolicyT> target(
+ (mFlags.value() & DetourFlags::eDontResolveRedirection)
+ ? ReadOnlyTargetFunction<MMPolicyT>(
+ this->mVMPolicy, reinterpret_cast<uintptr_t>(aTargetFn))
+ : this->ResolveRedirectedAddress(aTargetFn));
+
+ TrampPoolT* trampPool = nullptr;
+
+#if defined(_M_ARM64)
+ // ARM64 uses two passes to build its trampoline. The first pass uses a
+ // null tramp to determine how many bytes are needed. Once that is known,
+ // CreateTrampoline calls itself recursively with a "real" tramp.
+ Trampoline<MMPolicyT> tramp(nullptr);
+#else
+ Maybe<TrampPoolT> maybeTrampPool = DoReserve();
+ MOZ_ASSERT(maybeTrampPool);
+ if (!maybeTrampPool) {
+ return false;
+ }
+
+ trampPool = maybeTrampPool.ptr();
+
+ Maybe<Trampoline<MMPolicyT>> maybeTramp(trampPool->GetNextTrampoline());
+ if (!maybeTramp) {
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_NEXT_TRAMPOLINE_ERROR);
+ return false;
+ }
+
+ Trampoline<MMPolicyT> tramp(std::move(maybeTramp.ref()));
+#endif
+
+ CreateTrampoline(target, trampPool, tramp, aHookDest, aOrigFunc);
+ if (!*aOrigFunc) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private:
+ /**
+ * This function returns a maximum distance that can be reached by a single
+ * unconditional jump instruction. This is dependent on the processor ISA.
+ * Note that this distance is *exclusive* when added to the pivot, so the
+ * distance returned by this function is actually
+ * (maximum_absolute_offset + 1).
+ */
+ static uint32_t GetDefaultPivotDistance() {
+#if defined(_M_ARM64)
+ // Immediate unconditional branch allows for +/- 128MB
+ return 0x08000000U;
+#elif defined(_M_IX86) || defined(_M_X64)
+ // For these ISAs, our distance will assume the use of an unconditional jmp
+ // with a 32-bit signed displacement.
+ return 0x80000000U;
+#else
+# error "Not defined for this processor arch"
+#endif
+ }
+
+ /**
+ * If we're reserving trampoline space for a specific module, we base the
+ * pivot off of the median address of the module's .text section. While this
+ * may not be precise, it should be accurate enough for our purposes: To
+ * ensure that the trampoline space is reachable by any executable code in the
+ * module.
+ */
+ Maybe<TrampPoolT> ReserveForModule(HMODULE aModule) {
+ nt::PEHeaders moduleHeaders(aModule);
+ if (!moduleHeaders) {
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_RESERVE_FOR_MODULE_PE_ERROR);
+ return Nothing();
+ }
+
+ Maybe<Span<const uint8_t>> textSectionInfo =
+ moduleHeaders.GetTextSectionInfo();
+ if (!textSectionInfo) {
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_RESERVE_FOR_MODULE_TEXT_ERROR);
+ return Nothing();
+ }
+
+ const uint8_t* median = textSectionInfo.value().data() +
+ (textSectionInfo.value().LengthBytes() / 2);
+
+ Maybe<TrampPoolT> maybeTrampPool = this->mVMPolicy.Reserve(
+ reinterpret_cast<uintptr_t>(median), GetDefaultPivotDistance());
+ if (!maybeTrampPool) {
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_RESERVE_FOR_MODULE_RESERVE_ERROR);
+ }
+ return maybeTrampPool;
+ }
+
+ Maybe<TrampPoolT> DoReserve(HMODULE aModule = nullptr) {
+ if (aModule) {
+ return ReserveForModule(aModule);
+ }
+
+ uintptr_t pivot = 0;
+ uint32_t distance = 0;
+
+#if defined(_M_X64)
+ if (mFlags.value() & DetourFlags::eEnable10BytePatch) {
+ // We must stay below the 2GB mark because a 10-byte patch uses movsxd
+ // (ie, sign extension) to expand the pointer to 64-bits, so bit 31 of any
+ // pointers into the reserved region must be 0.
+ pivot = 0x40000000U;
+ distance = 0x40000000U;
+ }
+#endif // defined(_M_X64)
+
+ Maybe<TrampPoolT> maybeTrampPool = this->mVMPolicy.Reserve(pivot, distance);
+#if defined(NIGHTLY_BUILD)
+ if (!maybeTrampPool && this->GetLastDetourError().isNothing()) {
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_DO_RESERVE_ERROR);
+ }
+#endif // defined(NIGHTLY_BUILD)
+ return maybeTrampPool;
+ }
+
+ protected:
+#if !defined(_M_ARM64)
+
+ const static int kPageSize = 4096;
+
+ // 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 ReadOnlyTargetFunction<MMPolicyT>& aModRm,
+ BYTE* aSubOpcode = nullptr) {
+ 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)
+ enum class JumpType{Je, Jne, Jae, Jmp, Call};
+
+ 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;
+ }
+
+ // Write an opposite conditional jump because the destination branches
+ // are swapped.
+ 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);
+ } else if (aType == JumpType::Jae) {
+ // JB RIP+14
+ aTramp.WriteByte(0x72);
+ aTramp.WriteByte(14);
+ }
+
+ // 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(const ReadOnlyTargetFunction<MMPolicyT>& aBytes,
+ unsigned char* aOutGroupBits) {
+ unsigned char& groupBits = *aOutGroupBits;
+ groupBits = eNoPrefixes;
+ int index = 0;
+ 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;
+ }
+ }
+ }
+
+ // 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;
+ }
+
+#endif // !defined(_M_ARM64)
+
+ // If originalFn is a recognized trampoline then patch it to call aDest,
+ // set *aTramp and *aOutTramp to that trampoline's target and return true.
+ bool PatchIfTargetIsRecognizedTrampoline(
+ Trampoline<MMPolicyT>& aTramp,
+ ReadOnlyTargetFunction<MMPolicyT>& aOriginalFn, intptr_t aDest,
+ void** aOutTramp) {
+#if defined(_M_X64)
+ // Variation 1:
+ // 48 b8 imm64 mov rax, imm64
+ // ff e0 jmp rax
+ //
+ // Variation 2:
+ // 48 b8 imm64 mov rax, imm64
+ // 50 push rax
+ // c3 ret
+ if ((aOriginalFn[0] == 0x48) && (aOriginalFn[1] == 0xB8) &&
+ ((aOriginalFn[10] == 0xFF && aOriginalFn[11] == 0xE0) ||
+ (aOriginalFn[10] == 0x50 && aOriginalFn[11] == 0xC3))) {
+ uintptr_t originalTarget =
+ (aOriginalFn + 2).template ChasePointer<uintptr_t>();
+
+ // Skip the first two bytes (48 b8) so that we can overwrite the imm64
+ WritableTargetFunction<MMPolicyT> target(aOriginalFn.Promote(8, 2));
+ if (!target) {
+ return false;
+ }
+
+ // Write the new JMP target address.
+ target.WritePointer(aDest);
+ if (!target.Commit()) {
+ return false;
+ }
+
+ // Store the old target address so we can restore it when we're cleared
+ aTramp.WritePointer(originalTarget);
+ if (!aTramp) {
+ return false;
+ }
+
+ *aOutTramp = reinterpret_cast<void*>(originalTarget);
+ return true;
+ }
+#endif // defined(_M_X64)
+
+ return false;
+ }
+
+#if defined(_M_ARM64)
+ bool Apply4BytePatch(TrampPoolT* aTrampPool, void* aTrampPtr,
+ WritableTargetFunction<MMPolicyT>& target,
+ intptr_t aDest) {
+ MOZ_ASSERT(aTrampPool);
+ if (!aTrampPool) {
+ return false;
+ }
+
+ uintptr_t hookDest = arm64::MakeVeneer(*aTrampPool, aTrampPtr, aDest);
+ if (!hookDest) {
+ return false;
+ }
+
+ Maybe<uint32_t> branchImm = arm64::BuildUnconditionalBranchImm(
+ target.GetCurrentAddress(), hookDest);
+ if (!branchImm) {
+ return false;
+ }
+
+ target.WriteLong(branchImm.value());
+
+ return true;
+ }
+#endif // defined(_M_ARM64)
+
+#if defined(_M_X64)
+ bool Apply10BytePatch(TrampPoolT* aTrampPool, void* aTrampPtr,
+ WritableTargetFunction<MMPolicyT>& target,
+ intptr_t aDest) {
+ // Note: Even if the target function is also below 2GB, we still use an
+ // intermediary trampoline so that we consistently have a 64-bit pointer
+ // that we can use to reset the trampoline upon interceptor shutdown.
+ Maybe<Trampoline<MMPolicyT>> maybeCallTramp(
+ aTrampPool->GetNextTrampoline());
+ if (!maybeCallTramp) {
+ return false;
+ }
+
+ Trampoline<MMPolicyT> callTramp(std::move(maybeCallTramp.ref()));
+
+ // Write a null instance so that Clear() does not consider this tramp to
+ // be a normal tramp to be torn down.
+ callTramp.WriteEncodedPointer(nullptr);
+ // Use the second pointer slot to store a pointer to the primary tramp
+ callTramp.WriteEncodedPointer(aTrampPtr);
+ callTramp.StartExecutableCode();
+
+ // mov r11, address
+ callTramp.WriteByte(0x49);
+ callTramp.WriteByte(0xbb);
+ callTramp.WritePointer(aDest);
+
+ // jmp r11
+ callTramp.WriteByte(0x41);
+ callTramp.WriteByte(0xff);
+ callTramp.WriteByte(0xe3);
+
+ void* callTrampStart = callTramp.EndExecutableCode();
+ if (!callTrampStart) {
+ return false;
+ }
+
+ target.WriteByte(0xB8); // MOV EAX, IMM32
+
+ // Assert that the topmost 33 bits are 0
+ MOZ_ASSERT(
+ !(reinterpret_cast<uintptr_t>(callTrampStart) & (~0x7FFFFFFFULL)));
+
+ target.WriteLong(static_cast<uint32_t>(
+ reinterpret_cast<uintptr_t>(callTrampStart) & 0x7FFFFFFFU));
+ target.WriteByte(0x48); // REX.W
+ target.WriteByte(0x63); // MOVSXD r64, r/m32
+ // dest: rax, src: eax
+ target.WriteByte(BuildModRmByte(kModReg, kRegAx, kRegAx));
+ target.WriteByte(0xFF); // JMP /4
+ target.WriteByte(BuildModRmByte(kModReg, 4, kRegAx)); // rax
+
+ return true;
+ }
+#endif // defined(_M_X64)
+
+ void CreateTrampoline(ReadOnlyTargetFunction<MMPolicyT>& origBytes,
+ TrampPoolT* aTrampPool, Trampoline<MMPolicyT>& aTramp,
+ intptr_t aDest, void** aOutTramp) {
+ *aOutTramp = nullptr;
+
+ Trampoline<MMPolicyT>& tramp = aTramp;
+ if (!tramp) {
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_INVALID_TRAMPOLINE);
+ return;
+ }
+
+ // 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 hooked
+ // function to its original behavior upon destruction. In rare cases
+ // where the function was already a different trampoline, this is
+ // just a pointer to that trampoline's target address.
+ tramp.WriteEncodedPointer(this);
+ if (!tramp) {
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_WRITE_POINTER_ERROR);
+ return;
+ }
+
+ auto clearInstanceOnFailure = MakeScopeExit([this, aOutTramp, &tramp,
+ &origBytes]() -> void {
+ // *aOutTramp is not set until CreateTrampoline has completed
+ // successfully, so we can use that to check for success.
+ if (*aOutTramp) {
+ return;
+ }
+
+ // Clear the instance pointer so that we don't try to reset a
+ // nonexistent hook.
+ tramp.Rewind();
+ tramp.WriteEncodedPointer(nullptr);
+
+#if defined(NIGHTLY_BUILD)
+ origBytes.Rewind();
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_CREATE_TRAMPOLINE_ERROR);
+ DetourError& lastError = *this->mVMPolicy.mLastError;
+ size_t bytesToCapture = std::min(
+ ArrayLength(lastError.mOrigBytes),
+ static_cast<size_t>(PrimitiveT::GetWorstCaseRequiredBytesToPatch()));
+# if defined(_M_ARM64)
+ size_t numInstructionsToCapture = bytesToCapture / sizeof(uint32_t);
+ auto origBytesDst = reinterpret_cast<uint32_t*>(lastError.mOrigBytes);
+ for (size_t i = 0; i < numInstructionsToCapture; ++i) {
+ origBytesDst[i] = origBytes.ReadNextInstruction();
+ }
+# else
+ for (size_t i = 0; i < bytesToCapture; ++i) {
+ lastError.mOrigBytes[i] = origBytes[i];
+ }
+# endif // defined(_M_ARM64)
+#else
+ // Silence -Wunused-lambda-capture in non-Nightly.
+ Unused << this;
+ Unused << origBytes;
+#endif // defined(NIGHTLY_BUILD)
+ });
+
+ tramp.WritePointer(origBytes.AsEncodedPtr());
+ if (!tramp) {
+ return;
+ }
+
+ if (PatchIfTargetIsRecognizedTrampoline(tramp, origBytes, aDest,
+ aOutTramp)) {
+ return;
+ }
+
+ tramp.StartExecutableCode();
+
+ constexpr uint32_t kWorstCaseBytesRequired =
+ PrimitiveT::GetWorstCaseRequiredBytesToPatch();
+
+#if defined(_M_IX86)
+ int pJmp32 = -1;
+ while (origBytes.GetOffset() < kWorstCaseBytesRequired) {
+ // 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, &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;
+ }
+
+ origBytes += numPrefixBytes;
+ if (*origBytes >= 0x88 && *origBytes <= 0x8B) {
+ // various MOVs
+ ++origBytes;
+ int len = CountModRmSib(origBytes);
+ if (len < 0) {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized MOV opcode sequence");
+ return;
+ }
+ origBytes += len;
+ } else if (*origBytes == 0x0f &&
+ (origBytes[1] == 0x10 || origBytes[1] == 0x11)) {
+ // SSE: movups xmm, xmm/m128
+ // movups xmm/m128, xmm
+ origBytes += 2;
+ int len = CountModRmSib(origBytes);
+ if (len < 0) {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized MOV opcode sequence");
+ return;
+ }
+ origBytes += len;
+ } else if (*origBytes == 0xA1) {
+ // MOV eax, [seg:offset]
+ origBytes += 5;
+ } else if (*origBytes == 0xB8) {
+ // MOV 0xB8: http://ref.x86asm.net/coder32.html#xB8
+ origBytes += 5;
+ } else if (*origBytes == 0x33 && (origBytes[1] & kMaskMod) == kModReg) {
+ // XOR r32, r32
+ origBytes += 2;
+ } else if ((*origBytes & 0xf8) == 0x40) {
+ // INC r32
+ origBytes += 1;
+ } else if (*origBytes == 0x83) {
+ uint8_t mod = static_cast<uint8_t>(origBytes[1]) & kMaskMod;
+ uint8_t rm = static_cast<uint8_t>(origBytes[1]) & kMaskRm;
+ if (mod == kModReg) {
+ // ADD|OR|ADC|SBB|AND|SUB|XOR|CMP r, imm8
+ origBytes += 3;
+ } else if (mod == kModDisp8 && rm != kRmNeedSib) {
+ // ADD|OR|ADC|SBB|AND|SUB|XOR|CMP [r+disp8], imm8
+ origBytes += 4;
+ } else {
+ // bail
+ MOZ_ASSERT_UNREACHABLE("Unrecognized bit opcode sequence");
+ return;
+ }
+ } else if (*origBytes == 0x68) {
+ // PUSH with 4-byte operand
+ origBytes += 5;
+ } else if ((*origBytes & 0xf0) == 0x50) {
+ // 1-byte PUSH/POP
+ ++origBytes;
+ } else if (*origBytes == 0x6A) {
+ // PUSH imm8
+ origBytes += 2;
+ } else if (*origBytes == 0xe9) {
+ pJmp32 = origBytes.GetOffset();
+ // jmp 32bit offset
+ origBytes += 5;
+ } else if (*origBytes == 0xff && origBytes[1] == 0x25) {
+ // jmp [disp32]
+ 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);
+ 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.
+ tramp.CopyFrom(origBytes.GetBaseAddress(), origBytes.GetOffset());
+ if (!tramp) {
+ return;
+ }
+#elif defined(_M_X64)
+ bool foundJmp = false;
+ // |use10BytePatch| should always default to |false| in production. It is
+ // not set to true unless we detect that a 10-byte patch is necessary.
+ // OTOH, for testing purposes, if we want to force a 10-byte patch, we
+ // always initialize |use10BytePatch| to |true|.
+ bool use10BytePatch =
+ (mFlags.value() & DetourFlags::eTestOnlyForceShortPatch) ==
+ DetourFlags::eTestOnlyForceShortPatch;
+ const uint32_t bytesRequired =
+ use10BytePatch ? 10 : kWorstCaseBytesRequired;
+
+ while (origBytes.GetOffset() < bytesRequired) {
+ // 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 == 0x90 || *origBytes == 0xcc) {
+ ++origBytes;
+ continue;
+ }
+
+ // If our trampoline space is located in the lowest 2GB, we can do a ten
+ // byte patch instead of a thirteen byte patch.
+ if (aTrampPool && aTrampPool->IsInLowest2GB() &&
+ origBytes.GetOffset() >= 10) {
+ use10BytePatch = true;
+ break;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Opcode sequence includes commands after JMP");
+ return;
+ }
+ if (*origBytes == 0x0f) {
+ COPY_CODES(1);
+ if (*origBytes == 0x1f) {
+ // nop (multibyte)
+ COPY_CODES(1);
+ if ((*origBytes & 0xc0) == 0x40 && (*origBytes & 0x7) == 0x04) {
+ COPY_CODES(3);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if (*origBytes == 0x05) {
+ // syscall
+ COPY_CODES(1);
+ } else if (*origBytes == 0x10 || *origBytes == 0x11) {
+ // SSE: movups xmm, xmm/m128
+ // movups xmm/m128, xmm
+ COPY_CODES(1);
+ int nModRmSibBytes = CountModRmSib(origBytes);
+ if (nModRmSibBytes < 0) {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ } else {
+ COPY_CODES(nModRmSibBytes);
+ }
+ } else if (*origBytes >= 0x83 && *origBytes <= 0x85) {
+ // 0f 83 cd JAE rel32
+ // 0f 84 cd JE rel32
+ // 0f 85 cd JNE rel32
+ const JumpType kJumpTypes[] = {JumpType::Jae, JumpType::Je,
+ JumpType::Jne};
+ auto jumpType = kJumpTypes[*origBytes - 0x83];
+ ++origBytes;
+ --tramp; // overwrite the 0x0f we copied above
+
+ if (!GenerateJump(tramp, origBytes.ReadDisp32AsAbsolute(),
+ jumpType)) {
+ return;
+ }
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } 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 & 0xf0) == 0x50) {
+ // push/pop with Rx register
+ COPY_CODES(1);
+ } else if (*origBytes >= 0xb8 && *origBytes <= 0xbf) {
+ // mov r32, imm32
+ COPY_CODES(5);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if (*origBytes == 0x44) {
+ // REX.R
+ COPY_CODES(1);
+
+ // TODO: Combine with the "0x89" case below in the REX.W section
+ if (*origBytes == 0x89) {
+ // mov r/m32, r32
+ COPY_CODES(1);
+ 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 == 0x45) {
+ // REX.R & REX.B
+ COPY_CODES(1);
+
+ if (*origBytes == 0x33) {
+ // xor r32, r32
+ COPY_CODES(2);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if ((*origBytes & 0xfa) == 0x48) {
+ // REX.W | REX.WR | REX.WRB | REX.WB
+ COPY_CODES(1);
+
+ if (*origBytes == 0x81 && (origBytes[1] & 0xf8) == 0xe8) {
+ // sub r, dword
+ COPY_CODES(6);
+ } else if (*origBytes == 0x83 && (origBytes[1] & 0xf8) == 0xe8) {
+ // sub r, byte
+ COPY_CODES(3);
+ } else if (*origBytes == 0x83 &&
+ (origBytes[1] & (kMaskMod | kMaskReg)) == kModReg) {
+ // add r, byte
+ COPY_CODES(3);
+ } else if (*origBytes == 0x83 && (origBytes[1] & 0xf8) == 0x60) {
+ // and [r+d], imm8
+ COPY_CODES(5);
+ } else if (*origBytes == 0x2b && (origBytes[1] & kMaskMod) == kModReg) {
+ // sub r64, r64
+ COPY_CODES(2);
+ } else if (*origBytes == 0x85) {
+ // 85 /r => TEST r/m32, r32
+ if ((origBytes[1] & 0xc0) == 0xc0) {
+ COPY_CODES(2);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if ((*origBytes & 0xfd) == 0x89) {
+ // MOV r/m64, r64 | MOV r64, r/m64
+ BYTE reg;
+ int len = CountModRmSib(origBytes + 1, &reg);
+ if (len < 0) {
+ MOZ_ASSERT(len == kModOperand64);
+ if (len != kModOperand64) {
+ return;
+ }
+ 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.
+ 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.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.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 & 0xf8) == 0xb8) {
+ // MOV r64, imm64
+ COPY_CODES(9);
+ } else if (*origBytes == 0xc7) {
+ // MOV r/m64, imm32
+ 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 == 0xff) {
+ // JMP /4
+ 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 == 0x8d) {
+ // LEA reg, addr
+ 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[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 + 1);
+ // We handled the kModOperand64 -- ie RIP-relative -- case above
+ MOZ_ASSERT(len > 0);
+ COPY_CODES(len + 1);
+ }
+ } 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 == 0x66) {
+ // operand override prefix
+ COPY_CODES(1);
+ // This is the same as the x86 version
+ if (*origBytes >= 0x88 && *origBytes <= 0x8B) {
+ // various MOVs
+ 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]
+ 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 == 0x44 && origBytes[1] == 0x89) {
+ // mov word ptr [reg+disp8], reg
+ COPY_CODES(2);
+ 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 & 0xf0) == 0x50) {
+ // 1-byte push/pop
+ COPY_CODES(1);
+ } 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[1] == 0x48 &&
+ (origBytes[2] >= 0x88 && origBytes[2] <= 0x8b)) {
+ COPY_CODES(3);
+ 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 == 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.WriteByte(0x49);
+ tramp.WriteByte(0x53);
+
+ uintptr_t absAddr = origBytes.ReadDisp32AsAbsolute();
+
+ // mov r11, absolute address
+ tramp.WriteByte(0x49);
+ tramp.WriteByte(0xbb);
+ tramp.WritePointer(absAddr);
+
+ // cmp byte ptr [r11],...
+ tramp.WriteByte(0x41);
+ tramp.WriteByte(0x80);
+ tramp.WriteByte(0x3b);
+
+ // ...imm8
+ COPY_CODES(1);
+
+ // pop r11 (doesn't affect the flags from the cmp)
+ tramp.WriteByte(0x49);
+ tramp.WriteByte(0x5b);
+ } else if (*origBytes == 0x90) {
+ // nop
+ COPY_CODES(1);
+ } else if ((*origBytes & 0xf8) == 0xb8) {
+ // MOV r32, imm32
+ COPY_CODES(5);
+ } else if (*origBytes == 0x33) {
+ // xor r32, r/m32
+ COPY_CODES(2);
+ } 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 + 1, &subOpcode);
+ if (nModRmSibBytes < 0 || subOpcode != 0) {
+ // Unsupported
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ COPY_CODES(2 + nModRmSibBytes);
+ } else if (*origBytes == 0x85) {
+ // test r/m32, r32
+ int nModRmSibBytes = CountModRmSib(origBytes + 1);
+ if (nModRmSibBytes < 0) {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ COPY_CODES(1 + nModRmSibBytes);
+ } 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 == 0x83 && (origBytes[1] & kMaskMod) == kModReg) {
+ // ADD|OR|ADC|SBB|AND|SUB|XOR|CMP r, imm8
+ COPY_CODES(3);
+ } else if (*origBytes == 0xc3) {
+ // ret
+ COPY_CODES(1);
+ } else if (*origBytes == 0xcc) {
+ // int 3
+ COPY_CODES(1);
+ } else if (*origBytes == 0xe8 || *origBytes == 0xe9) {
+ // CALL (0xe8) or JMP (0xe9) 32bit offset
+ foundJmp = *origBytes == 0xe9;
+ ++origBytes;
+
+ if (!GenerateJump(tramp, origBytes.ReadDisp32AsAbsolute(),
+ foundJmp ? JumpType::Jmp : JumpType::Call)) {
+ return;
+ }
+ } else if (*origBytes >= 0x73 && *origBytes <= 0x75) {
+ // 73 cb JAE rel8
+ // 74 cb JE rel8
+ // 75 cb JNE rel8
+ const JumpType kJumpTypes[] = {JumpType::Jae, JumpType::Je,
+ JumpType::Jne};
+ auto jumpType = kJumpTypes[*origBytes - 0x73];
+ uint8_t offset = origBytes[1];
+
+ origBytes += 2;
+
+ if (!GenerateJump(tramp, origBytes.OffsetToAbsolute(offset),
+ jumpType)) {
+ return;
+ }
+ } else if (*origBytes == 0xff) {
+ uint8_t mod = origBytes[1] & kMaskMod;
+ uint8_t reg = (origBytes[1] & kMaskReg) >> kRegFieldShift;
+ uint8_t rm = origBytes[1] & kMaskRm;
+ if (mod == kModReg && (reg == 0 || reg == 1 || reg == 2 || reg == 6)) {
+ // INC|DEC|CALL|PUSH r64
+ COPY_CODES(2);
+ } else if (mod == kModNoRegDisp && reg == 2 &&
+ rm == kRmNoRegDispDisp32) {
+ // FF 15 CALL [disp32]
+ origBytes += 2;
+ if (!GenerateJump(tramp, origBytes.ChasePointerFromDisp(),
+ JumpType::Call)) {
+ return;
+ }
+ } else if (reg == 4) {
+ // FF /4 (Opcode=ff, REG=4): JMP r/m
+ if (mod == kModNoRegDisp && rm == kRmNoRegDispDisp32) {
+ // FF 25 JMP [disp32]
+ foundJmp = true;
+
+ origBytes += 2;
+
+ uintptr_t jmpDest = origBytes.ChasePointerFromDisp();
+
+ if (!GenerateJump(tramp, jmpDest, JumpType::Jmp)) {
+ return;
+ }
+ } else {
+ // JMP r/m except JMP [disp32]
+ int len = CountModRmSib(origBytes + 1);
+ if (len < 0) {
+ // RIP-relative not yet supported
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+
+ COPY_CODES(len + 1);
+
+ foundJmp = true;
+ }
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if (*origBytes == 0x83 && (origBytes[1] & 0xf8) == 0x60) {
+ // and [r+d], imm8
+ COPY_CODES(5);
+ } else if (*origBytes == 0xc6) {
+ // mov [r+d], imm8
+ int len = CountModRmSib(origBytes + 1);
+ if (len < 0) {
+ // RIP-relative not yet supported
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ COPY_CODES(len + 2);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ }
+#elif defined(_M_ARM64)
+
+ // The number of bytes required to facilitate a detour depends on the
+ // proximity of the hook function to the target function. In the best case,
+ // we can branch within +/- 128MB of the current location, requiring only
+ // 4 bytes. In the worst case, we need 16 bytes to load an absolute address
+ // into a register and then branch to it.
+ const uint32_t bytesRequiredFromDecode =
+ (mFlags.value() & DetourFlags::eTestOnlyForceShortPatch)
+ ? 4
+ : kWorstCaseBytesRequired;
+
+ while (origBytes.GetOffset() < bytesRequiredFromDecode) {
+ uintptr_t curPC = origBytes.GetCurrentAbsolute();
+ uint32_t curInst = origBytes.ReadNextInstruction();
+
+ Result<arm64::LoadOrBranch, arm64::PCRelCheckError> pcRelInfo =
+ arm64::CheckForPCRel(curPC, curInst);
+ if (pcRelInfo.isErr()) {
+ if (pcRelInfo.unwrapErr() ==
+ arm64::PCRelCheckError::InstructionNotPCRel) {
+ // Instruction is not PC-relative, we can just copy it verbatim
+ tramp.WriteInstruction(curInst);
+ continue;
+ }
+
+ // At this point we have determined that there is no decoder available
+ // for the current, PC-relative, instruction.
+
+ // origBytes is now pointing one instruction past the one that we
+ // need the trampoline to jump back to.
+ if (!origBytes.BackUpOneInstruction()) {
+ return;
+ }
+
+ break;
+ }
+
+ // We need to load an absolute address into a particular register
+ tramp.WriteLoadLiteral(pcRelInfo.inspect().mAbsAddress,
+ pcRelInfo.inspect().mDestReg);
+ }
+
+#else
+# error "Unknown processor type"
+#endif
+
+ if (origBytes.GetOffset() > 100) {
+ // printf ("Too big!");
+ return;
+ }
+
+#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.
+ tramp.AdjustDisp32AtOffset(pJmp32 + 1, origBytes.GetBaseAddress());
+ } else {
+ tramp.WriteByte(0xe9); // jmp
+ tramp.WriteDisp32(origBytes.GetAddress());
+ }
+#elif defined(_M_X64)
+ // If 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) {
+ if (!GenerateJump(tramp, origBytes.GetAddress(), JumpType::Jmp)) {
+ return;
+ }
+ }
+#elif defined(_M_ARM64)
+ // Let's find out how many bytes we have available to us for patching
+ uint32_t numBytesForPatching = tramp.GetCurrentExecutableCodeLen();
+
+ if (!numBytesForPatching) {
+ // There's nothing we can do
+ return;
+ }
+
+ if (tramp.IsNull()) {
+ // Recursive case
+ HMODULE targetModule = nullptr;
+
+ if (numBytesForPatching < kWorstCaseBytesRequired) {
+ if (!::GetModuleHandleExW(
+ GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
+ GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+ reinterpret_cast<LPCWSTR>(origBytes.GetBaseAddress()),
+ &targetModule)) {
+ return;
+ }
+ }
+
+ Maybe<TrampPoolT> maybeTrampPool = DoReserve(targetModule);
+ MOZ_ASSERT(maybeTrampPool);
+ if (!maybeTrampPool) {
+ return;
+ }
+
+ Maybe<Trampoline<MMPolicyT>> maybeRealTramp(
+ maybeTrampPool.ref().GetNextTrampoline());
+ if (!maybeRealTramp) {
+ return;
+ }
+
+ origBytes.Rewind();
+ CreateTrampoline(origBytes, maybeTrampPool.ptr(), maybeRealTramp.ref(),
+ aDest, aOutTramp);
+ return;
+ }
+
+ // Write the branch from the trampoline back to the original code
+
+ tramp.WriteLoadLiteral(origBytes.GetAddress(), 16);
+ tramp.WriteInstruction(arm64::BuildUnconditionalBranchToRegister(16));
+#else
+# error "Unsupported processor architecture"
+#endif
+
+ // The trampoline is now complete.
+ void* trampPtr = tramp.EndExecutableCode();
+ if (!trampPtr) {
+ return;
+ }
+
+ WritableTargetFunction<MMPolicyT> target(origBytes.Promote());
+ if (!target) {
+ return;
+ }
+
+ do {
+ // Now patch the original function.
+ // When we're instructed to apply a non-default patch, apply it and exit.
+ // If non-default patching fails, bail out, no fallback.
+ // Otherwise, we go straight to the default patch.
+
+#if defined(_M_X64)
+ if (use10BytePatch) {
+ if (!Apply10BytePatch(aTrampPool, trampPtr, target, aDest)) {
+ return;
+ }
+ break;
+ }
+#elif defined(_M_ARM64)
+ if (numBytesForPatching < kWorstCaseBytesRequired) {
+ if (!Apply4BytePatch(aTrampPool, trampPtr, target, aDest)) {
+ return;
+ }
+ break;
+ }
+#endif
+
+ PrimitiveT::ApplyDefaultPatch(target, aDest);
+ } while (false);
+
+ if (!target.Commit()) {
+ return;
+ }
+
+ // Output the trampoline, thus signalling that this call was a success
+ *aOutTramp = trampPtr;
+ }
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_PatcherDetour_h
diff --git a/mozglue/misc/interceptor/PatcherNopSpace.h b/mozglue/misc/interceptor/PatcherNopSpace.h
new file mode 100644
index 0000000000..deee87e0f8
--- /dev/null
+++ b/mozglue/misc/interceptor/PatcherNopSpace.h
@@ -0,0 +1,205 @@
+/* -*- 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_PatcherNopSpace_h
+#define mozilla_interceptor_PatcherNopSpace_h
+
+#if defined(_M_IX86)
+
+# include "mozilla/interceptor/PatcherBase.h"
+
+namespace mozilla {
+namespace interceptor {
+
+template <typename VMPolicy>
+class WindowsDllNopSpacePatcher final : public WindowsDllPatcherBase<VMPolicy> {
+ typedef typename VMPolicy::MMPolicyT MMPolicyT;
+
+ // For remembering the addresses of functions we've patched.
+ mozilla::Vector<void*> mPatchedFns;
+
+ public:
+ template <typename... Args>
+ explicit WindowsDllNopSpacePatcher(Args&&... aArgs)
+ : WindowsDllPatcherBase<VMPolicy>(std::forward<Args>(aArgs)...) {}
+
+ ~WindowsDllNopSpacePatcher() { Clear(); }
+
+ WindowsDllNopSpacePatcher(const WindowsDllNopSpacePatcher&) = delete;
+ WindowsDllNopSpacePatcher(WindowsDllNopSpacePatcher&&) = delete;
+ WindowsDllNopSpacePatcher& operator=(const WindowsDllNopSpacePatcher&) =
+ delete;
+ WindowsDllNopSpacePatcher& operator=(WindowsDllNopSpacePatcher&&) = delete;
+
+ void Clear() {
+ // Restore the mov edi, edi to the beginning of each function we patched.
+
+ for (auto&& ptr : mPatchedFns) {
+ WritableTargetFunction<MMPolicyT> fn(
+ this->mVMPolicy, reinterpret_cast<uintptr_t>(ptr), sizeof(uint16_t));
+ if (!fn) {
+ continue;
+ }
+
+ // mov edi, edi
+ fn.CommitAndWriteShort(0xff8b);
+ }
+
+ 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
+ * 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 + 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;
+ 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(FARPROC aTargetFn, intptr_t aHookDest, void** aOrigFunc) {
+ if (!IsCompatible()) {
+# if defined(MOZILLA_INTERNAL_API)
+ NS_WARNING("NOP space patching is unavailable for compatibility reasons");
+# endif
+ return false;
+ }
+
+ MOZ_ASSERT(aTargetFn);
+ if (!aTargetFn) {
+ return false;
+ }
+
+ ReadOnlyTargetFunction<MMPolicyT> readOnlyTargetFn(
+ this->ResolveRedirectedAddress(aTargetFn));
+
+ if (!WriteHook(readOnlyTargetFn, aHookDest, aOrigFunc)) {
+ return false;
+ }
+
+ return mPatchedFns.append(
+ reinterpret_cast<void*>(readOnlyTargetFn.GetBaseAddress()));
+ }
+
+ bool WriteHook(const ReadOnlyTargetFunction<MMPolicyT>& aFn,
+ intptr_t aHookDest, void** aOrigFunc) {
+ // 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;
+ }
+
+ // Check that the 5 bytes before the function are NOP's or INT 3's,
+ const uint8_t nopOrBp[] = {0x90, 0xCC};
+ if (!writableFn.template 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:
+ //
+ // 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 0x8B 0xFF. We include 0x89 0xFF out of paranoia.
+
+ // (These look backwards because little-endian)
+ const uint16_t possibleEncodings[] = {0xFF8B, 0xFF89};
+ if (!writableFn.template VerifyValuesAreOneOf<uint16_t, 1>(
+ possibleEncodings, 5)) {
+ return false;
+ }
+
+ // Write a long jump into the space above the function.
+ writableFn.WriteByte(0xe9); // jmp
+ if (!writableFn) {
+ return false;
+ }
+
+ writableFn.WriteDisp32(aHookDest); // target
+ if (!writableFn) {
+ return false;
+ }
+
+ // 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));
+
+ // Short jump up into our long jump.
+ return writableFn.CommitAndWriteShort(0xF9EB); // jmp $-5
+ }
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // defined(_M_IX86)
+
+#endif // mozilla_interceptor_PatcherNopSpace_h
diff --git a/mozglue/misc/interceptor/RangeMap.h b/mozglue/misc/interceptor/RangeMap.h
new file mode 100644
index 0000000000..d45d031613
--- /dev/null
+++ b/mozglue/misc/interceptor/RangeMap.h
@@ -0,0 +1,142 @@
+/* -*- 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_RangeMap_h
+#define mozilla_interceptor_RangeMap_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/mozalloc.h"
+#include "mozilla/Span.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
+
+#include <algorithm>
+
+namespace mozilla {
+namespace interceptor {
+
+/**
+ * This class maintains a vector of VMSharingPolicyUnique objects, sorted on
+ * the memory range that is used for reserving each object.
+ *
+ * This is used by VMSharingPolicyShared for creating and looking up VM regions
+ * that are within proximity of the applicable range.
+ *
+ * VMSharingPolicyUnique objects managed by this class are reused whenever
+ * possible. If no range is required, we just return the first available
+ * policy.
+ *
+ * If no range is required and no policies have yet been allocated, we create
+ * a new one with a null range as a default.
+ */
+template <typename MMPolicyT>
+class RangeMap final {
+ private:
+ /**
+ * This class is used as the comparison key for sorting and insertion.
+ */
+ class Range {
+ public:
+ constexpr Range() : mBase(0), mLimit(0) {}
+
+ explicit Range(const Maybe<Span<const uint8_t>>& aBounds)
+ : mBase(aBounds ? reinterpret_cast<const uintptr_t>(
+ MMPolicyT::GetLowerBound(aBounds.ref()))
+ : 0),
+ mLimit(aBounds ? reinterpret_cast<const uintptr_t>(
+ MMPolicyT::GetUpperBoundIncl(aBounds.ref()))
+ : 0) {}
+
+ Range& operator=(const Range&) = default;
+ Range(const Range&) = default;
+ Range(Range&&) = default;
+ Range& operator=(Range&&) = default;
+
+ bool operator<(const Range& aOther) const {
+ return mBase < aOther.mBase ||
+ (mBase == aOther.mBase && mLimit < aOther.mLimit);
+ }
+
+ bool Contains(const Range& aOther) const {
+ return mBase <= aOther.mBase && mLimit >= aOther.mLimit;
+ }
+
+ private:
+ uintptr_t mBase;
+ uintptr_t mLimit;
+ };
+
+ class PolicyInfo final : public Range {
+ public:
+ explicit PolicyInfo(const Range& aRange)
+ : Range(aRange),
+ mPolicy(MakeUnique<VMSharingPolicyUnique<MMPolicyT>>()) {}
+
+ PolicyInfo(const PolicyInfo&) = delete;
+ PolicyInfo& operator=(const PolicyInfo&) = delete;
+
+ PolicyInfo(PolicyInfo&& aOther) = default;
+ PolicyInfo& operator=(PolicyInfo&& aOther) = default;
+
+ VMSharingPolicyUnique<MMPolicyT>* GetPolicy() { return mPolicy.get(); }
+
+ private:
+ UniquePtr<VMSharingPolicyUnique<MMPolicyT>> mPolicy;
+ };
+
+ using VectorType = Vector<PolicyInfo, 0, InfallibleAllocPolicy>;
+
+ public:
+ constexpr RangeMap() : mPolicies(nullptr) {}
+
+ VMSharingPolicyUnique<MMPolicyT>* GetPolicy(
+ const Maybe<Span<const uint8_t>>& aBounds) {
+ Range testRange(aBounds);
+
+ if (!mPolicies) {
+ mPolicies = new VectorType();
+ }
+
+ // If no bounds are specified, we just use the first available policy
+ if (!aBounds) {
+ if (mPolicies->empty()) {
+ if (!mPolicies->append(PolicyInfo(testRange))) {
+ return nullptr;
+ }
+ }
+
+ return GetFirstPolicy();
+ }
+
+ // mPolicies is sorted, so we search
+ auto itr =
+ std::lower_bound(mPolicies->begin(), mPolicies->end(), testRange);
+ if (itr != mPolicies->end() && itr->Contains(testRange)) {
+ return itr->GetPolicy();
+ }
+
+ itr = mPolicies->insert(itr, PolicyInfo(testRange));
+
+ MOZ_ASSERT(std::is_sorted(mPolicies->begin(), mPolicies->end()));
+
+ return itr->GetPolicy();
+ }
+
+ private:
+ VMSharingPolicyUnique<MMPolicyT>* GetFirstPolicy() {
+ MOZ_RELEASE_ASSERT(mPolicies && !mPolicies->empty());
+ return mPolicies->begin()->GetPolicy();
+ }
+
+ private:
+ VectorType* mPolicies;
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_RangeMap_h
diff --git a/mozglue/misc/interceptor/TargetFunction.h b/mozglue/misc/interceptor/TargetFunction.h
new file mode 100644
index 0000000000..40be1ad08b
--- /dev/null
+++ b/mozglue/misc/interceptor/TargetFunction.h
@@ -0,0 +1,1000 @@
+/* -*- 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/BinarySearch.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Tuple.h"
+#include "mozilla/Types.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Vector.h"
+
+#include <memory>
+#include <type_traits>
+
+namespace mozilla {
+namespace interceptor {
+
+#if defined(_M_IX86)
+
+template <typename T>
+bool CommitAndWriteShortInternal(const T& aMMPolicy, void* aDest,
+ uint16_t aValue);
+
+template <>
+inline bool CommitAndWriteShortInternal<MMPolicyInProcess>(
+ const MMPolicyInProcess& aMMPolicy, void* aDest, uint16_t aValue) {
+ return aMMPolicy.WriteAtomic(aDest, aValue);
+}
+
+template <>
+inline bool CommitAndWriteShortInternal<MMPolicyOutOfProcess>(
+ const MMPolicyOutOfProcess& aMMPolicy, void* aDest, uint16_t aValue) {
+ return aMMPolicy.Write(aDest, &aValue, sizeof(uint16_t));
+}
+
+#endif // defined(_M_IX86)
+
+// Forward declaration
+template <typename MMPolicy>
+class ReadOnlyTargetFunction;
+
+template <typename MMPolicy>
+class MOZ_STACK_CLASS WritableTargetFunction final {
+ class AutoProtect final {
+ using ProtectParams = Tuple<uintptr_t, uint32_t>;
+
+ public:
+ explicit AutoProtect(const MMPolicy& aMMPolicy) : mMMPolicy(aMMPolicy) {}
+
+ AutoProtect(const MMPolicy& aMMPolicy, uintptr_t aAddr, size_t aNumBytes,
+ uint32_t aNewProt)
+ : mMMPolicy(aMMPolicy) {
+ const uint32_t pageSize = mMMPolicy.GetPageSize();
+ const uintptr_t limit = aAddr + aNumBytes - 1;
+ const uintptr_t limitPageNum = limit / pageSize;
+ const uintptr_t basePageNum = aAddr / pageSize;
+ const uintptr_t numPagesToChange = limitPageNum - basePageNum + 1;
+
+ // We'll use the base address of the page instead of aAddr
+ uintptr_t curAddr = basePageNum * pageSize;
+
+ // Now change the protection on each page
+ for (uintptr_t curPage = 0; curPage < numPagesToChange;
+ ++curPage, curAddr += pageSize) {
+ uint32_t prevProt;
+ if (!aMMPolicy.Protect(reinterpret_cast<void*>(curAddr), pageSize,
+ aNewProt, &prevProt)) {
+ Clear();
+ return;
+ }
+
+ // Save the previous protection for curAddr so that we can revert this
+ // in the destructor.
+ if (!mProtects.append(MakeTuple(curAddr, prevProt))) {
+ Clear();
+ return;
+ }
+ }
+ }
+
+ AutoProtect(AutoProtect&& aOther)
+ : mMMPolicy(aOther.mMMPolicy), mProtects(std::move(aOther.mProtects)) {
+ aOther.mProtects.clear();
+ }
+
+ ~AutoProtect() { Clear(); }
+
+ explicit operator bool() const { return !mProtects.empty(); }
+
+ AutoProtect(const AutoProtect&) = delete;
+ AutoProtect& operator=(const AutoProtect&) = delete;
+ AutoProtect& operator=(AutoProtect&&) = delete;
+
+ private:
+ void Clear() {
+ const uint32_t pageSize = mMMPolicy.GetPageSize();
+ for (auto&& entry : mProtects) {
+ uint32_t prevProt;
+ DebugOnly<bool> ok =
+ mMMPolicy.Protect(reinterpret_cast<void*>(Get<0>(entry)), pageSize,
+ Get<1>(entry), &prevProt);
+ MOZ_ASSERT(ok);
+ }
+
+ mProtects.clear();
+ }
+
+ private:
+ const MMPolicy& mMMPolicy;
+ // We include two entries of inline storage as that is most common in the
+ // worst case.
+ Vector<ProtectParams, 2> mProtects;
+ };
+
+ 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),
+ mAccumulatedStatus(false),
+ mProtect(aMMPolicy) {}
+
+ WritableTargetFunction(const MMPolicy& aMMPolicy, uintptr_t aFunc,
+ size_t aNumBytes)
+ : mMMPolicy(aMMPolicy),
+ mFunc(aFunc),
+ mNumBytes(aNumBytes),
+ mOffset(0),
+ mStartWriteOffset(0),
+ mAccumulatedStatus(true),
+ mProtect(aMMPolicy, aFunc, aNumBytes, PAGE_EXECUTE_READWRITE) {}
+
+ WritableTargetFunction(WritableTargetFunction&& aOther)
+ : mMMPolicy(aOther.mMMPolicy),
+ mFunc(aOther.mFunc),
+ mNumBytes(aOther.mNumBytes),
+ mOffset(aOther.mOffset),
+ mStartWriteOffset(aOther.mStartWriteOffset),
+ mLocalBytes(std::move(aOther.mLocalBytes)),
+ mAccumulatedStatus(aOther.mAccumulatedStatus),
+ mProtect(std::move(aOther.mProtect)) {
+ aOther.mAccumulatedStatus = false;
+ }
+
+ ~WritableTargetFunction() {
+ MOZ_ASSERT(mLocalBytes.empty(), "Did you forget to call Commit?");
+ }
+
+ 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();
+
+ mStartWriteOffset += mLocalBytes.length();
+
+ mLocalBytes.clear();
+ return true;
+ }
+
+ explicit operator bool() const { return mProtect && 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);
+ }
+
+ Maybe<uintptr_t> ReadEncodedPtr() {
+ // Reading is only permitted prior to any writing
+ MOZ_ASSERT(mOffset == mStartWriteOffset);
+ if (mOffset > mStartWriteOffset) {
+ mAccumulatedStatus = false;
+ return Nothing();
+ }
+
+ uintptr_t value;
+ if (!mMMPolicy.Read(&value, reinterpret_cast<const void*>(mFunc + mOffset),
+ sizeof(uintptr_t))) {
+ mAccumulatedStatus = false;
+ return Nothing();
+ }
+
+ mOffset += sizeof(uintptr_t);
+ mStartWriteOffset += sizeof(uintptr_t);
+ return Some(ReadOnlyTargetFunction<MMPolicy>::DecodePtr(value));
+ }
+
+ Maybe<uint32_t> ReadLong() {
+ // Reading is only permitted prior to any writing
+ MOZ_ASSERT(mOffset == mStartWriteOffset);
+ if (mOffset > mStartWriteOffset) {
+ mAccumulatedStatus = false;
+ return Nothing();
+ }
+
+ uint32_t value;
+ if (!mMMPolicy.Read(&value, reinterpret_cast<const void*>(mFunc + mOffset),
+ sizeof(uint32_t))) {
+ mAccumulatedStatus = false;
+ return Nothing();
+ }
+
+ mOffset += sizeof(uint32_t);
+ mStartWriteOffset += sizeof(uint32_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);
+ }
+
+#if defined(_M_IX86)
+ public:
+ /**
+ * Commits any dirty writes, and then writes a short, atomically if possible.
+ * This call may succeed in both inproc and outproc cases, but atomicity
+ * is only guaranteed in the inproc case.
+ */
+ bool CommitAndWriteShort(const uint16_t aValue) {
+ // First, commit everything that has been written until now
+ if (!Commit()) {
+ return false;
+ }
+
+ // Now immediately write the short, atomically if inproc
+ bool ok = CommitAndWriteShortInternal(
+ mMMPolicy, reinterpret_cast<void*>(mFunc + mStartWriteOffset), aValue);
+ if (!ok) {
+ return false;
+ }
+
+ mMMPolicy.FlushInstructionCache();
+ mStartWriteOffset += sizeof(uint16_t);
+ return true;
+ }
+#endif // defined(_M_IX86)
+
+ 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);
+ }
+
+#if defined(_M_X64) || defined(_M_ARM64)
+ void WriteLong(const uint32_t aValue) {
+ if (!mLocalBytes.append(reinterpret_cast<const uint8_t*>(&aValue),
+ sizeof(uint32_t))) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ mOffset += sizeof(uint32_t);
+ }
+#endif // defined(_M_X64)
+
+ 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;
+
+ // In an ideal world, we'd only read 5 bytes on 32-bit and 13 bytes on 64-bit,
+ // to match the minimum bytes that we need to write in in order to patch the
+ // target function. Since the actual opcodes will often require us to pull in
+ // extra bytes above that minimum, we set the inline storage to be larger than
+ // those minima in an effort to give the Vector extra wiggle room before it
+ // needs to touch the heap.
+#if defined(_M_IX86)
+ static const size_t kInlineStorage = 16;
+#elif defined(_M_X64) || defined(_M_ARM64)
+ static const size_t kInlineStorage = 32;
+#endif
+ Vector<uint8_t, kInlineStorage> mLocalBytes;
+ bool mAccumulatedStatus;
+ AutoProtect mProtect;
+};
+
+template <typename MMPolicy>
+class ReadOnlyTargetBytes {
+ public:
+ ReadOnlyTargetBytes(const MMPolicy& 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.
+ }
+
+ uint32_t TryEnsureLimit(uint32_t aDesiredLimit) {
+ // Same as EnsureLimit above. We don't need to ensure for the in-process.
+ return aDesiredLimit;
+ }
+
+ 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(adjusted);
+ }
+
+ /**
+ * This returns a pointer to a *potentially local copy* of the target
+ * function's bytes. The returned pointer should not be used for any
+ * pointer arithmetic relating to the target function.
+ */
+ const uint8_t* GetLocalBytes() const { return mBase; }
+
+ /**
+ * This returns a pointer to the target function's bytes. The returned pointer
+ * may possibly belong to another process, so while it should be used for
+ * pointer arithmetic, it *must not* be dereferenced.
+ */
+ uintptr_t GetBase() const { return reinterpret_cast<uintptr_t>(mBase); }
+
+ const MMPolicy& GetMMPolicy() const { return mMMPolicy; }
+
+ ReadOnlyTargetBytes& operator=(const ReadOnlyTargetBytes&) = delete;
+ ReadOnlyTargetBytes& operator=(ReadOnlyTargetBytes&&) = delete;
+
+ private:
+ const MMPolicy& mMMPolicy;
+ uint8_t const* const mBase;
+};
+
+template <>
+class ReadOnlyTargetBytes<MMPolicyOutOfProcess> {
+ public:
+ ReadOnlyTargetBytes(const MMPolicyOutOfProcess& aMMPolicy, const void* aBase)
+ : mMMPolicy(aMMPolicy), mBase(reinterpret_cast<const uint8_t*>(aBase)) {}
+
+ ReadOnlyTargetBytes(ReadOnlyTargetBytes&& aOther)
+ : mMMPolicy(aOther.mMMPolicy),
+ mLocalBytes(std::move(aOther.mLocalBytes)),
+ mBase(aOther.mBase) {}
+
+ ReadOnlyTargetBytes(const ReadOnlyTargetBytes& aOther)
+ : mMMPolicy(aOther.mMMPolicy), mBase(aOther.mBase) {
+ Unused << mLocalBytes.appendAll(aOther.mLocalBytes);
+ }
+
+ ReadOnlyTargetBytes(const ReadOnlyTargetBytes& aOther,
+ const uint32_t aOffsetFromOther)
+ : mMMPolicy(aOther.mMMPolicy), mBase(aOther.mBase + aOffsetFromOther) {
+ if (aOffsetFromOther >= aOther.mLocalBytes.length()) {
+ return;
+ }
+
+ Unused << mLocalBytes.append(aOther.mLocalBytes.begin() + aOffsetFromOther,
+ aOther.mLocalBytes.end());
+ }
+
+ void EnsureLimit(uint32_t aDesiredLimit) {
+ size_t prevSize = mLocalBytes.length();
+ if (aDesiredLimit < prevSize) {
+ return;
+ }
+
+ size_t newSize = aDesiredLimit + 1;
+ if (newSize < kInlineStorage) {
+ // Always try to read as much memory as we can at once
+ newSize = kInlineStorage;
+ }
+
+ bool resizeOk = mLocalBytes.resize(newSize);
+ MOZ_RELEASE_ASSERT(resizeOk);
+
+ bool ok = mMMPolicy.Read(&mLocalBytes[prevSize], mBase + prevSize,
+ newSize - prevSize);
+ if (ok) {
+ return;
+ }
+
+ // We couldn't pull more bytes than needed (which may happen if those extra
+ // bytes are not accessible). In this case, we try just to get the bare
+ // minimum.
+ newSize = aDesiredLimit + 1;
+ resizeOk = mLocalBytes.resize(newSize);
+ MOZ_RELEASE_ASSERT(resizeOk);
+
+ ok = mMMPolicy.Read(&mLocalBytes[prevSize], mBase + prevSize,
+ newSize - prevSize);
+ MOZ_RELEASE_ASSERT(ok);
+ }
+
+ // This function tries to ensure as many bytes as possible up to
+ // |aDesiredLimit| bytes, returning how many bytes were actually ensured.
+ // As EnsureLimit does, we allocate an extra byte in local to make sure
+ // mLocalBytes always has at least one byte even though the target memory
+ // was inaccessible at all.
+ uint32_t TryEnsureLimit(uint32_t aDesiredLimit) {
+ size_t prevSize = mLocalBytes.length();
+ if (aDesiredLimit < prevSize) {
+ return aDesiredLimit;
+ }
+
+ size_t newSize = aDesiredLimit;
+ if (newSize < kInlineStorage) {
+ // Always try to read as much memory as we can at once
+ newSize = kInlineStorage;
+ }
+
+ bool resizeOk = mLocalBytes.resize(newSize);
+ MOZ_RELEASE_ASSERT(resizeOk);
+
+ size_t bytesRead = mMMPolicy.TryRead(&mLocalBytes[prevSize],
+ mBase + prevSize, newSize - prevSize);
+
+ newSize = prevSize + bytesRead;
+
+ resizeOk = mLocalBytes.resize(newSize + 1);
+ MOZ_RELEASE_ASSERT(resizeOk);
+
+ mLocalBytes[newSize] = 0;
+ return newSize;
+ }
+
+ 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(adjusted);
+ }
+
+ /**
+ * This returns a pointer to a *potentially local copy* of the target
+ * function's bytes. The returned pointer should not be used for any
+ * pointer arithmetic relating to the target function.
+ */
+ const uint8_t* GetLocalBytes() const {
+ if (mLocalBytes.empty()) {
+ return nullptr;
+ }
+
+ return mLocalBytes.begin();
+ }
+
+ /**
+ * This returns a pointer to the target function's bytes. The returned pointer
+ * may possibly belong to another process, so while it should be used for
+ * pointer arithmetic, it *must not* be dereferenced.
+ */
+ uintptr_t GetBase() const { return reinterpret_cast<uintptr_t>(mBase); }
+
+ const MMPolicyOutOfProcess& GetMMPolicy() const { return mMMPolicy; }
+
+ ReadOnlyTargetBytes& operator=(const ReadOnlyTargetBytes&) = delete;
+ ReadOnlyTargetBytes& operator=(ReadOnlyTargetBytes&&) = delete;
+
+ private:
+ // In an ideal world, we'd only read 5 bytes on 32-bit and 13 bytes on 64-bit,
+ // to match the minimum bytes that we need to write in in order to patch the
+ // target function. Since the actual opcodes will often require us to pull in
+ // extra bytes above that minimum, we set the inline storage to be larger than
+ // those minima in an effort to give the Vector extra wiggle room before it
+ // needs to touch the heap.
+#if defined(_M_IX86)
+ static const size_t kInlineStorage = 16;
+#elif defined(_M_X64) || defined(_M_ARM64)
+ static const size_t kInlineStorage = 32;
+#endif
+
+ const MMPolicyOutOfProcess& mMMPolicy;
+ Vector<uint8_t, kInlineStorage> mLocalBytes;
+ uint8_t const* const mBase;
+};
+
+template <typename MMPolicy>
+class TargetBytesPtr {
+ public:
+ typedef TargetBytesPtr<MMPolicy> Type;
+
+ static Type Make(const MMPolicy& aMMPolicy, const void* aFunc) {
+ return TargetBytesPtr(aMMPolicy, aFunc);
+ }
+
+ static Type CopyFromOffset(const TargetBytesPtr& aOther,
+ const uint32_t aOffsetFromOther) {
+ return TargetBytesPtr(aOther, aOffsetFromOther);
+ }
+
+ ReadOnlyTargetBytes<MMPolicy>* operator->() { return &mTargetBytes; }
+
+ TargetBytesPtr(TargetBytesPtr&& aOther)
+ : mTargetBytes(std::move(aOther.mTargetBytes)) {}
+
+ TargetBytesPtr(const TargetBytesPtr& aOther)
+ : mTargetBytes(aOther.mTargetBytes) {}
+
+ TargetBytesPtr& operator=(const TargetBytesPtr&) = delete;
+ TargetBytesPtr& operator=(TargetBytesPtr&&) = delete;
+
+ private:
+ TargetBytesPtr(const MMPolicy& aMMPolicy, const void* aFunc)
+ : mTargetBytes(aMMPolicy, aFunc) {}
+
+ TargetBytesPtr(const TargetBytesPtr& aOther, const uint32_t aOffsetFromOther)
+ : mTargetBytes(aOther.mTargetBytes, aOffsetFromOther) {}
+
+ ReadOnlyTargetBytes<MMPolicy> mTargetBytes;
+};
+
+template <>
+class TargetBytesPtr<MMPolicyOutOfProcess> {
+ public:
+ typedef std::shared_ptr<ReadOnlyTargetBytes<MMPolicyOutOfProcess>> Type;
+
+ static Type Make(const MMPolicyOutOfProcess& aMMPolicy, const void* aFunc) {
+ return std::make_shared<ReadOnlyTargetBytes<MMPolicyOutOfProcess>>(
+ aMMPolicy, aFunc);
+ }
+
+ static Type CopyFromOffset(const Type& aOther,
+ const uint32_t aOffsetFromOther) {
+ return std::make_shared<ReadOnlyTargetBytes<MMPolicyOutOfProcess>>(
+ *aOther, aOffsetFromOther);
+ }
+};
+
+template <typename MMPolicy>
+class MOZ_STACK_CLASS ReadOnlyTargetFunction final {
+ public:
+ ReadOnlyTargetFunction(const MMPolicy& aMMPolicy, const void* aFunc)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::Make(aMMPolicy, aFunc)),
+ mOffset(0) {}
+
+ ReadOnlyTargetFunction(const MMPolicy& aMMPolicy, FARPROC aFunc)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::Make(
+ aMMPolicy, reinterpret_cast<const void*>(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(std::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 mTargetBytes->GetBase(); }
+
+ uintptr_t GetAddress() const { return mTargetBytes->GetBase() + mOffset; }
+
+ uintptr_t AsEncodedPtr() const {
+ return EncodePtr(
+ reinterpret_cast<void*>(mTargetBytes->GetBase() + 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)));
+ }
+
+ bool IsValidAtOffset(const int8_t aOffset) const {
+ return mTargetBytes->IsValidAtOffset(aOffset);
+ }
+
+#if defined(_M_ARM64)
+
+ uint32_t ReadNextInstruction() {
+ mTargetBytes->EnsureLimit(mOffset + sizeof(uint32_t));
+ uint32_t instruction = *reinterpret_cast<const uint32_t*>(
+ mTargetBytes->GetLocalBytes() + mOffset);
+ mOffset += sizeof(uint32_t);
+ return instruction;
+ }
+
+ bool BackUpOneInstruction() {
+ if (mOffset < sizeof(uint32_t)) {
+ return false;
+ }
+
+ mOffset -= sizeof(uint32_t);
+ return true;
+ }
+
+#else
+
+ uint8_t const& operator*() const {
+ mTargetBytes->EnsureLimit(mOffset);
+ return *(mTargetBytes->GetLocalBytes() + mOffset);
+ }
+
+ uint8_t const& operator[](uint32_t aIndex) const {
+ mTargetBytes->EnsureLimit(mOffset + aIndex);
+ return *(mTargetBytes->GetLocalBytes() + mOffset + aIndex);
+ }
+
+ ReadOnlyTargetFunction& operator++() {
+ ++mOffset;
+ return *this;
+ }
+
+ ReadOnlyTargetFunction& operator+=(uint32_t aDelta) {
+ mOffset += aDelta;
+ return *this;
+ }
+
+ uintptr_t ReadDisp32AsAbsolute() {
+ mTargetBytes->EnsureLimit(mOffset + sizeof(int32_t));
+ int32_t disp = *reinterpret_cast<const int32_t*>(
+ mTargetBytes->GetLocalBytes() + mOffset);
+ uintptr_t result =
+ mTargetBytes->GetBase() + mOffset + sizeof(int32_t) + disp;
+ mOffset += sizeof(int32_t);
+ return result;
+ }
+
+ bool IsRelativeShortJump(uintptr_t* aOutTarget) {
+ if ((*this)[0] == 0xeb) {
+ int8_t offset = static_cast<int8_t>((*this)[1]);
+ *aOutTarget = GetAddress() + 2 + offset;
+ return true;
+ }
+ return false;
+ }
+
+# if defined(_M_X64)
+ // Currently this function is used only in x64.
+ bool IsRelativeNearJump(uintptr_t* aOutTarget) {
+ if ((*this)[0] == 0xe9) {
+ *aOutTarget = (*this + 1).ReadDisp32AsAbsolute();
+ return true;
+ }
+ return false;
+ }
+# endif // defined(_M_X64)
+
+ bool IsIndirectNearJump(uintptr_t* aOutTarget) {
+ if ((*this)[0] == 0xff && (*this)[1] == 0x25) {
+# if defined(_M_X64)
+ *aOutTarget = (*this + 2).ChasePointerFromDisp();
+# else
+ *aOutTarget = (*this + 2).template ChasePointer<uintptr_t*>();
+# endif // defined(_M_X64)
+ return true;
+ }
+# if defined(_M_X64)
+ else if ((*this)[0] == 0x48 && (*this)[1] == 0xff && (*this)[2] == 0x25) {
+ // According to Intel SDM, JMP does not have REX.W except JMP m16:64,
+ // but CPU can execute JMP r/m32 with REX.W. We handle it just in case.
+ *aOutTarget = (*this + 3).ChasePointerFromDisp();
+ return true;
+ }
+# endif // defined(_M_X64)
+ return false;
+ }
+
+#endif // defined(_M_ARM64)
+
+ void Rewind() { mOffset = 0; }
+
+ uint32_t GetOffset() const { return mOffset; }
+
+ uintptr_t OffsetToAbsolute(const uint8_t aOffset) const {
+ return mTargetBytes->GetBase() + mOffset + aOffset;
+ }
+
+ uintptr_t GetCurrentAbsolute() const { return OffsetToAbsolute(0); }
+
+ /**
+ * 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(),
+ mTargetBytes->GetBase() + aOffset,
+ effectiveLength);
+
+ return 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.template 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 std::remove_cv_t<T> result =
+ *reinterpret_cast<const std::remove_cv_t<T>*>(
+ mTargetBytes->GetLocalBytes() + mOffset);
+ return ChasePointerHelper<std::remove_cv_t<T>>::Result(
+ mTargetBytes->GetMMPolicy(), result);
+ }
+
+ uintptr_t ChasePointerFromDisp() {
+ uintptr_t ptrFromDisp = ReadDisp32AsAbsolute();
+ 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;
+};
+
+template <typename MMPolicy, typename T>
+class MOZ_STACK_CLASS TargetObject {
+ mutable typename TargetBytesPtr<MMPolicy>::Type mTargetBytes;
+
+ TargetObject(const MMPolicy& aMMPolicy, const void* aBaseAddress)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::Make(aMMPolicy, aBaseAddress)) {
+ mTargetBytes->EnsureLimit(sizeof(T));
+ }
+
+ public:
+ explicit TargetObject(const MMPolicy& aMMPolicy)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::Make(aMMPolicy, nullptr)) {}
+
+ TargetObject(const MMPolicy& aMMPolicy, uintptr_t aBaseAddress)
+ : TargetObject(aMMPolicy, reinterpret_cast<const void*>(aBaseAddress)) {}
+
+ TargetObject(const TargetObject&) = delete;
+ TargetObject(TargetObject&&) = delete;
+ TargetObject& operator=(const TargetObject&) = delete;
+ TargetObject& operator=(TargetObject&&) = delete;
+
+ explicit operator bool() const {
+ return mTargetBytes->GetBase() && mTargetBytes->GetLocalBytes();
+ }
+
+ const T* operator->() const {
+ return reinterpret_cast<const T*>(mTargetBytes->GetLocalBytes());
+ }
+
+ const T* GetLocalBase() const {
+ return reinterpret_cast<const T*>(mTargetBytes->GetLocalBytes());
+ }
+};
+
+template <typename MMPolicy, typename T>
+class MOZ_STACK_CLASS TargetObjectArray {
+ mutable typename TargetBytesPtr<MMPolicy>::Type mTargetBytes;
+ size_t mNumOfItems;
+
+ TargetObjectArray(const MMPolicy& aMMPolicy, const void* aBaseAddress,
+ size_t aNumOfItems)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::Make(aMMPolicy, aBaseAddress)),
+ mNumOfItems(aNumOfItems) {
+ uint32_t itemsRead =
+ mTargetBytes->TryEnsureLimit(sizeof(T) * mNumOfItems) / sizeof(T);
+ // itemsRead may be bigger than the requested amount because of buffering,
+ // but mNumOfItems should not include extra bytes of buffering.
+ if (itemsRead < mNumOfItems) {
+ mNumOfItems = itemsRead;
+ }
+ }
+
+ const T* GetLocalBase() const {
+ return reinterpret_cast<const T*>(mTargetBytes->GetLocalBytes());
+ }
+
+ public:
+ explicit TargetObjectArray(const MMPolicy& aMMPolicy)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::Make(aMMPolicy, nullptr)),
+ mNumOfItems(0) {}
+
+ TargetObjectArray(const MMPolicy& aMMPolicy, uintptr_t aBaseAddress,
+ size_t aNumOfItems)
+ : TargetObjectArray(aMMPolicy,
+ reinterpret_cast<const void*>(aBaseAddress),
+ aNumOfItems) {}
+
+ TargetObjectArray(const TargetObjectArray&) = delete;
+ TargetObjectArray(TargetObjectArray&&) = delete;
+ TargetObjectArray& operator=(const TargetObjectArray&) = delete;
+ TargetObjectArray& operator=(TargetObjectArray&&) = delete;
+
+ explicit operator bool() const {
+ return mTargetBytes->GetBase() && mNumOfItems;
+ }
+
+ const T* operator[](size_t aIndex) const {
+ if (aIndex >= mNumOfItems) {
+ return nullptr;
+ }
+
+ return &GetLocalBase()[aIndex];
+ }
+
+ template <typename Comparator>
+ bool BinarySearchIf(const Comparator& aCompare,
+ size_t* aMatchOrInsertionPoint) const {
+ return mozilla::BinarySearchIf(GetLocalBase(), 0, mNumOfItems, aCompare,
+ aMatchOrInsertionPoint);
+ }
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_TargetFunction_h
diff --git a/mozglue/misc/interceptor/Trampoline.h b/mozglue/misc/interceptor/Trampoline.h
new file mode 100644
index 0000000000..c471408bd1
--- /dev/null
+++ b/mozglue/misc/interceptor/Trampoline.h
@@ -0,0 +1,517 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_interceptor_Trampoline_h
+#define mozilla_interceptor_Trampoline_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Types.h"
+#include "mozilla/WindowsProcessMitigations.h"
+
+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) {
+ if (!::VirtualProtect(aLocalBase, aChunkSize,
+ MMPolicy::GetTrampWriteProtFlags(),
+ &mPrevLocalProt)) {
+ mPrevLocalProt = 0;
+ }
+ }
+
+ Trampoline(Trampoline&& aOther)
+ : mMMPolicy(aOther.mMMPolicy),
+ mPrevLocalProt(aOther.mPrevLocalProt),
+ mLocalBase(aOther.mLocalBase),
+ mRemoteBase(aOther.mRemoteBase),
+ mOffset(aOther.mOffset),
+ mExeOffset(aOther.mExeOffset),
+ 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&& aOther) {
+ Clear();
+
+ 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;
+
+ return *this;
+ }
+
+ ~Trampoline() { Clear(); }
+
+ explicit operator bool() const {
+ return IsNull() ||
+ (mLocalBase && mRemoteBase && mPrevLocalProt && mAccumulatedStatus);
+ }
+
+ bool IsNull() const { return !mMMPolicy; }
+
+#if defined(_M_ARM64)
+
+ void WriteInstruction(uint32_t aInstruction) {
+ const uint32_t kDelta = sizeof(uint32_t);
+
+ if (!mMMPolicy) {
+ // Null tramp, just track offset
+ mOffset += kDelta;
+ return;
+ }
+
+ if (mOffset + kDelta > mMaxOffset) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ *reinterpret_cast<uint32_t*>(mLocalBase + mOffset) = aInstruction;
+ mOffset += kDelta;
+ }
+
+ void WriteLoadLiteral(const uintptr_t aAddress, const uint8_t aReg) {
+ const uint32_t kDelta = sizeof(uint32_t) + sizeof(uintptr_t);
+
+ if (!mMMPolicy) {
+ // Null tramp, just track offset
+ mOffset += kDelta;
+ return;
+ }
+
+ // We grow the literal pool from the *end* of the tramp,
+ // so we need to ensure that there is enough room for both an instruction
+ // and a pointer
+ if (mOffset + kDelta > mMaxOffset) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ mMaxOffset -= sizeof(uintptr_t);
+ *reinterpret_cast<uintptr_t*>(mLocalBase + mMaxOffset) = aAddress;
+
+ CheckedInt<intptr_t> pc(GetCurrentRemoteAddress());
+ if (!pc.isValid()) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ CheckedInt<intptr_t> literal(reinterpret_cast<uintptr_t>(mLocalBase) +
+ mMaxOffset);
+ if (!literal.isValid()) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ CheckedInt<intptr_t> ptrOffset = (literal - pc);
+ if (!ptrOffset.isValid()) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ // ptrOffset must be properly aligned
+ MOZ_ASSERT((ptrOffset.value() % 4) == 0);
+ ptrOffset /= 4;
+
+ CheckedInt<int32_t> offset(ptrOffset.value());
+ if (!offset.isValid()) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ // Ensure that offset falls within the range of a signed 19-bit value
+ if (offset.value() < -0x40000 || offset.value() > 0x3FFFF) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ const int32_t kimm19Mask = 0x7FFFF;
+ int32_t masked = offset.value() & kimm19Mask;
+
+ MOZ_ASSERT(aReg < 32);
+ uint32_t loadInstr = 0x58000000 | (masked << 5) | aReg;
+ WriteInstruction(loadInstr);
+ }
+
+#else
+
+ void WriteByte(uint8_t aValue) {
+ const uint32_t kDelta = sizeof(uint8_t);
+
+ if (!mMMPolicy) {
+ // Null tramp, just track offset
+ mOffset += kDelta;
+ return;
+ }
+
+ if (mOffset >= mMaxOffset) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ *(mLocalBase + mOffset) = aValue;
+ ++mOffset;
+ }
+
+ void WriteInteger(int32_t aValue) {
+ const uint32_t kDelta = sizeof(int32_t);
+
+ if (!mMMPolicy) {
+ // Null tramp, just track offset
+ mOffset += kDelta;
+ return;
+ }
+
+ if (mOffset + kDelta > mMaxOffset) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ *reinterpret_cast<int32_t*>(mLocalBase + mOffset) = aValue;
+ mOffset += kDelta;
+ }
+
+ void WriteDisp32(uintptr_t aAbsTarget) {
+ const uint32_t kDelta = sizeof(int32_t);
+
+ if (!mMMPolicy) {
+ // Null tramp, just track offset
+ mOffset += kDelta;
+ return;
+ }
+
+ if (mOffset + kDelta > mMaxOffset) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ // This needs to be computed from the remote location
+ intptr_t remoteTrampPosition = static_cast<intptr_t>(mRemoteBase + mOffset);
+
+ intptr_t diff =
+ static_cast<intptr_t>(aAbsTarget) - (remoteTrampPosition + kDelta);
+
+ CheckedInt<int32_t> checkedDisp(diff);
+ MOZ_ASSERT(checkedDisp.isValid());
+ if (!checkedDisp.isValid()) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ int32_t disp = checkedDisp.value();
+ *reinterpret_cast<int32_t*>(mLocalBase + mOffset) = disp;
+ mOffset += kDelta;
+ }
+
+#endif
+
+ void WritePointer(uintptr_t aValue) {
+ const uint32_t kDelta = sizeof(uintptr_t);
+
+ if (!mMMPolicy) {
+ // Null tramp, just track offset
+ mOffset += kDelta;
+ return;
+ }
+
+ if (mOffset + kDelta > mMaxOffset) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ *reinterpret_cast<uintptr_t*>(mLocalBase + mOffset) = aValue;
+ mOffset += kDelta;
+ }
+
+ void WriteEncodedPointer(void* aValue) {
+ uintptr_t encoded = ReadOnlyTargetFunction<MMPolicy>::EncodePtr(aValue);
+ WritePointer(encoded);
+ }
+
+ Maybe<uintptr_t> ReadPointer() {
+ if (mOffset + sizeof(uintptr_t) > mMaxOffset) {
+ mAccumulatedStatus = false;
+ return Nothing();
+ }
+
+ auto result = Some(*reinterpret_cast<uintptr_t*>(mLocalBase + mOffset));
+ mOffset += sizeof(uintptr_t);
+ return std::move(result);
+ }
+
+ Maybe<uintptr_t> ReadEncodedPointer() {
+ Maybe<uintptr_t> encoded(ReadPointer());
+ if (!encoded) {
+ return encoded;
+ }
+
+ return Some(ReadOnlyTargetFunction<MMPolicy>::DecodePtr(encoded.value()));
+ }
+
+#if defined(_M_IX86)
+ // 32-bit only
+ void AdjustDisp32AtOffset(uint32_t aOffset, uintptr_t aAbsTarget) {
+ uint32_t effectiveOffset = mExeOffset + aOffset;
+
+ if (effectiveOffset + sizeof(int32_t) > mMaxOffset) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ intptr_t diff = static_cast<intptr_t>(aAbsTarget) -
+ static_cast<intptr_t>(mRemoteBase + mExeOffset);
+ *reinterpret_cast<int32_t*>(mLocalBase + effectiveOffset) += diff;
+ }
+#endif // defined(_M_IX86)
+
+ void CopyFrom(uintptr_t aOrigBytes, uint32_t aNumBytes) {
+ if (!mMMPolicy) {
+ // Null tramp, just track offset
+ mOffset += aNumBytes;
+ return;
+ }
+
+ if (!mMMPolicy || mOffset + aNumBytes > mMaxOffset) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ if (!mMMPolicy->Read(mLocalBase + mOffset,
+ reinterpret_cast<void*>(aOrigBytes), aNumBytes)) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ mOffset += aNumBytes;
+ }
+
+ void Rewind() { mOffset = 0; }
+
+ uintptr_t GetCurrentRemoteAddress() const { return mRemoteBase + mOffset; }
+
+ void StartExecutableCode() {
+ MOZ_ASSERT(!mExeOffset);
+ mExeOffset = mOffset;
+ }
+
+ void* EndExecutableCode() const {
+ if (!mAccumulatedStatus || !mMMPolicy) {
+ return nullptr;
+ }
+
+ // This must always return the start address the executable code
+ // *in the target process*
+ return reinterpret_cast<void*>(mRemoteBase + mExeOffset);
+ }
+
+ uint32_t GetCurrentExecutableCodeLen() const { return mOffset - mExeOffset; }
+
+ Trampoline<MMPolicy>& operator--() {
+ MOZ_ASSERT(mOffset);
+ --mOffset;
+ return *this;
+ }
+
+ private:
+ void Clear() {
+ if (!mLocalBase || !mPrevLocalProt) {
+ return;
+ }
+
+ DebugOnly<bool> ok = !!::VirtualProtect(mLocalBase, mMaxOffset,
+ mPrevLocalProt, &mPrevLocalProt);
+ MOZ_ASSERT(ok);
+
+ mLocalBase = nullptr;
+ mRemoteBase = 0;
+ mPrevLocalProt = 0;
+ mAccumulatedStatus = false;
+ }
+
+ private:
+ const MMPolicy* mMMPolicy;
+ DWORD mPrevLocalProt;
+ uint8_t* mLocalBase;
+ uintptr_t mRemoteBase;
+ uint32_t mOffset;
+ uint32_t mExeOffset;
+ 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),
+ mCS(nullptr) {}
+
+ TrampolineCollection(const MMPolicy& aMMPolicy, uint8_t* const aLocalBase,
+ const uintptr_t aRemoteBase, const uint32_t aTrampSize,
+ const uint32_t aNumTramps)
+ : mMMPolicy(aMMPolicy),
+ mLocalBase(aLocalBase),
+ mRemoteBase(aRemoteBase),
+ mTrampSize(aTrampSize),
+ mNumTramps(aNumTramps),
+ mPrevProt(0),
+ mCS(nullptr) {
+ if (!aNumTramps) {
+ return;
+ }
+
+ BOOL ok = mMMPolicy.Protect(aLocalBase, aNumTramps * aTrampSize,
+ PAGE_EXECUTE_READWRITE, &mPrevProt);
+ if (!ok) {
+ // When destroying a sandboxed process that uses
+ // MITIGATION_DYNAMIC_CODE_DISABLE, we won't be allowed to write to our
+ // executable memory so we just do nothing. If we fail to get access
+ // to memory for any other reason, we still don't want to crash but we
+ // do assert.
+ MOZ_ASSERT(IsDynamicCodeDisabled());
+ mNumTramps = 0;
+ mPrevProt = 0;
+ }
+ }
+
+ ~TrampolineCollection() {
+ if (!mPrevProt) {
+ return;
+ }
+
+ mMMPolicy.Protect(mLocalBase, mNumTramps * mTrampSize, mPrevProt,
+ &mPrevProt);
+
+ if (mCS) {
+ ::LeaveCriticalSection(mCS);
+ }
+ }
+
+ void Lock(CRITICAL_SECTION& aCS) {
+ if (!mPrevProt || mCS) {
+ return;
+ }
+
+ mCS = &aCS;
+ ::EnterCriticalSection(&aCS);
+ }
+
+ TrampolineIterator begin() const {
+ if (!mPrevProt) {
+ return end();
+ }
+
+ return TrampolineIterator(*this);
+ }
+
+ TrampolineIterator end() const {
+ return TrampolineIterator(*this, mNumTramps);
+ }
+
+ TrampolineCollection(const TrampolineCollection&) = delete;
+ TrampolineCollection& operator=(const TrampolineCollection&) = delete;
+ TrampolineCollection& operator=(TrampolineCollection&&) = delete;
+
+ TrampolineCollection(TrampolineCollection&& aOther)
+ : mMMPolicy(aOther.mMMPolicy),
+ mLocalBase(aOther.mLocalBase),
+ mRemoteBase(aOther.mRemoteBase),
+ mTrampSize(aOther.mTrampSize),
+ mNumTramps(aOther.mNumTramps),
+ mPrevProt(aOther.mPrevProt),
+ mCS(aOther.mCS) {
+ aOther.mPrevProt = 0;
+ aOther.mCS = nullptr;
+ }
+
+ private:
+ const MMPolicy& mMMPolicy;
+ uint8_t* const mLocalBase;
+ const uintptr_t mRemoteBase;
+ const uint32_t mTrampSize;
+ uint32_t mNumTramps;
+ uint32_t mPrevProt;
+ CRITICAL_SECTION* mCS;
+
+ friend class TrampolineIterator;
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_Trampoline_h
diff --git a/mozglue/misc/interceptor/VMSharingPolicies.h b/mozglue/misc/interceptor/VMSharingPolicies.h
new file mode 100644
index 0000000000..8f93f5c1ad
--- /dev/null
+++ b/mozglue/misc/interceptor/VMSharingPolicies.h
@@ -0,0 +1,285 @@
+/* -*- 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/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Types.h"
+
+namespace mozilla {
+namespace interceptor {
+
+/**
+ * This class is an abstraction of a reservation of virtual address space that
+ * has been obtained from a VMSharingPolicy via the policy's |Reserve| method.
+ *
+ * TrampolinePool allows us to obtain a trampoline without needing to concern
+ * ourselves with the underlying implementation of the VM sharing policy.
+ *
+ * For example, VMSharingPolicyShared delegates to VMSharingPolicyUnique, but
+ * also requires taking a lock before doing so. By invoking |GetNextTrampoline|
+ * on a TrampolinePool, the caller does not need to concern themselves with
+ * that detail.
+ *
+ * We accompolish this with a recursive implementation that provides an inner
+ * TrampolinePool that is provided by the delegated VMSharingPolicy.
+ */
+template <typename VMPolicyT, typename InnerT>
+class MOZ_STACK_CLASS TrampolinePool final {
+ public:
+ TrampolinePool(TrampolinePool&& aOther) = default;
+
+ TrampolinePool(VMPolicyT& aVMPolicy, InnerT&& aInner)
+ : mVMPolicy(aVMPolicy), mInner(std::move(aInner)) {}
+
+ TrampolinePool& operator=(TrampolinePool&& aOther) = delete;
+ TrampolinePool(const TrampolinePool&) = delete;
+ TrampolinePool& operator=(const TrampolinePool&) = delete;
+
+ using MMPolicyT = typename VMPolicyT::MMPolicyT;
+
+ Maybe<Trampoline<MMPolicyT>> GetNextTrampoline() {
+ return mVMPolicy.GetNextTrampoline(mInner);
+ }
+
+#if defined(_M_X64)
+ bool IsInLowest2GB() const {
+ return mVMPolicy.IsTrampolineSpaceInLowest2GB(mInner);
+ }
+#endif // defined(_M_X64)
+
+ private:
+ VMPolicyT& mVMPolicy;
+ InnerT mInner;
+};
+
+/**
+ * This specialization is the base case for TrampolinePool, and is used by
+ * VMSharingPolicyUnique (since that policy does not delegate anything).
+ */
+template <typename VMPolicyT>
+class MOZ_STACK_CLASS TrampolinePool<VMPolicyT, decltype(nullptr)> final {
+ public:
+ explicit TrampolinePool(VMPolicyT& aVMPolicy) : mVMPolicy(aVMPolicy) {}
+
+ TrampolinePool(TrampolinePool&& aOther) = default;
+
+ TrampolinePool& operator=(TrampolinePool&& aOther) = delete;
+ TrampolinePool(const TrampolinePool&) = delete;
+ TrampolinePool& operator=(const TrampolinePool&) = delete;
+
+ using MMPolicyT = typename VMPolicyT::MMPolicyT;
+
+ Maybe<Trampoline<MMPolicyT>> GetNextTrampoline() {
+ return mVMPolicy.GetNextTrampoline();
+ }
+
+#if defined(_M_X64)
+ bool IsInLowest2GB() const {
+ return mVMPolicy.IsTrampolineSpaceInLowest2GB();
+ }
+#endif // defined(_M_X64)
+
+ private:
+ VMPolicyT& mVMPolicy;
+};
+
+template <typename MMPolicy>
+class VMSharingPolicyUnique : public MMPolicy {
+ using ThisType = VMSharingPolicyUnique<MMPolicy>;
+
+ public:
+ using PoolType = TrampolinePool<ThisType, decltype(nullptr)>;
+
+ template <typename... Args>
+ explicit VMSharingPolicyUnique(Args&&... aArgs)
+ : MMPolicy(std::forward<Args>(aArgs)...), mNextChunkIndex(0) {}
+
+ Maybe<PoolType> Reserve(const uintptr_t aPivotAddr,
+ const uint32_t aMaxDistanceFromPivot) {
+ // Win32 allocates VM addresses at a 64KiB granularity, so we might as well
+ // utilize that entire 64KiB reservation.
+ uint32_t len = MMPolicy::GetAllocGranularity();
+
+ Maybe<Span<const uint8_t>> maybeBounds = MMPolicy::SpanFromPivotAndDistance(
+ len, aPivotAddr, aMaxDistanceFromPivot);
+
+ return Reserve(len, maybeBounds);
+ }
+
+ Maybe<PoolType> Reserve(const uint32_t aSize,
+ const Maybe<Span<const uint8_t>>& aBounds) {
+ uint32_t bytesReserved = MMPolicy::Reserve(aSize, aBounds);
+ if (!bytesReserved) {
+ return Nothing();
+ }
+
+ return Some(PoolType(*this));
+ }
+
+ TrampolineCollection<MMPolicy> Items() const {
+ return TrampolineCollection<MMPolicy>(*this, this->GetLocalView(),
+ this->GetRemoteView(), kChunkSize,
+ mNextChunkIndex);
+ }
+
+ void Clear() { mNextChunkIndex = 0; }
+
+ ~VMSharingPolicyUnique() = default;
+
+ VMSharingPolicyUnique(const VMSharingPolicyUnique&) = delete;
+ VMSharingPolicyUnique& operator=(const VMSharingPolicyUnique&) = delete;
+
+ VMSharingPolicyUnique(VMSharingPolicyUnique&& aOther)
+ : MMPolicy(std::move(aOther)), mNextChunkIndex(aOther.mNextChunkIndex) {
+ aOther.mNextChunkIndex = 0;
+ }
+
+ VMSharingPolicyUnique& operator=(VMSharingPolicyUnique&& aOther) {
+ static_cast<MMPolicy&>(*this) = std::move(aOther);
+ mNextChunkIndex = aOther.mNextChunkIndex;
+ aOther.mNextChunkIndex = 0;
+ return *this;
+ }
+
+ protected:
+ // In VMSharingPolicyUnique we do not implement the overload that accepts
+ // an inner trampoline pool, as this policy is expected to be the
+ // implementation of the base case.
+ Maybe<Trampoline<MMPolicy>> GetNextTrampoline() {
+ uint32_t offset = mNextChunkIndex * kChunkSize;
+ if (!this->MaybeCommitNextPage(offset, kChunkSize)) {
+ return Nothing();
+ }
+
+ Trampoline<MMPolicy> result(this, this->GetLocalView() + offset,
+ this->GetRemoteView() + offset, kChunkSize);
+ if (!!result) {
+ ++mNextChunkIndex;
+ }
+
+ return Some(std::move(result));
+ }
+
+ private:
+ uint32_t mNextChunkIndex;
+ static const uint32_t kChunkSize = 128;
+
+ template <typename VMPolicyT, typename FriendT>
+ friend class TrampolinePool;
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+// We don't include RangeMap.h until this point because it depends on the
+// TrampolinePool definitions from above.
+#include "mozilla/interceptor/RangeMap.h"
+
+namespace mozilla {
+namespace interceptor {
+
+// We only support this policy for in-proc MMPolicy.
+class MOZ_TRIVIAL_CTOR_DTOR VMSharingPolicyShared : public MMPolicyInProcess {
+ typedef VMSharingPolicyUnique<MMPolicyInProcess> UniquePolicyT;
+ typedef VMSharingPolicyShared ThisType;
+
+ public:
+ using PoolType = TrampolinePool<ThisType, UniquePolicyT::PoolType>;
+ using MMPolicyT = MMPolicyInProcess;
+
+ constexpr VMSharingPolicyShared() {}
+
+ bool ShouldUnhookUponDestruction() const { return false; }
+
+ Maybe<PoolType> Reserve(const uintptr_t aPivotAddr,
+ const uint32_t aMaxDistanceFromPivot) {
+ // Win32 allocates VM addresses at a 64KiB granularity, so we might as well
+ // utilize that entire 64KiB reservation.
+ uint32_t len = this->GetAllocGranularity();
+
+ Maybe<Span<const uint8_t>> maybeBounds =
+ MMPolicyInProcess::SpanFromPivotAndDistance(len, aPivotAddr,
+ aMaxDistanceFromPivot);
+
+ AutoCriticalSection lock(GetCS());
+ VMSharingPolicyUnique<MMPolicyT>* uniquePol = sVMMap.GetPolicy(maybeBounds);
+ MOZ_ASSERT(uniquePol);
+ if (!uniquePol) {
+ return Nothing();
+ }
+
+ Maybe<UniquePolicyT::PoolType> maybeUnique =
+ uniquePol->Reserve(len, maybeBounds);
+ if (!maybeUnique) {
+ return Nothing();
+ }
+
+ return Some(PoolType(*this, std::move(maybeUnique.ref())));
+ }
+
+ TrampolineCollection<MMPolicyInProcess> Items() const {
+ // Since ShouldUnhookUponDestruction returns false, this can be empty
+ return TrampolineCollection<MMPolicyInProcess>(*this);
+ }
+
+ void Clear() {
+ // This must be a no-op for shared VM policy; we can't have one interceptor
+ // wiping out trampolines for all interceptors in the process.
+ }
+
+ VMSharingPolicyShared(const VMSharingPolicyShared&) = delete;
+ VMSharingPolicyShared(VMSharingPolicyShared&&) = delete;
+ VMSharingPolicyShared& operator=(const VMSharingPolicyShared&) = delete;
+ VMSharingPolicyShared& operator=(VMSharingPolicyShared&&) = delete;
+
+ private:
+ static CRITICAL_SECTION* GetCS() {
+ static const bool isAlloc = []() -> bool {
+ DWORD flags = 0;
+#if defined(RELEASE_OR_BETA)
+ flags |= CRITICAL_SECTION_NO_DEBUG_INFO;
+#endif // defined(RELEASE_OR_BETA)
+ ::InitializeCriticalSectionEx(&sCS, 4000, flags);
+ return true;
+ }();
+ Unused << isAlloc;
+
+ return &sCS;
+ }
+
+ // In VMSharingPolicyShared, we only implement the overload that accepts
+ // a VMSharingPolicyUnique trampoline pool as |aInner|, since we require the
+ // former policy to wrap the latter.
+ Maybe<Trampoline<MMPolicyInProcess>> GetNextTrampoline(
+ UniquePolicyT::PoolType& aInner) {
+ AutoCriticalSection lock(GetCS());
+ return aInner.GetNextTrampoline();
+ }
+
+#if defined(_M_X64)
+ bool IsTrampolineSpaceInLowest2GB(
+ const UniquePolicyT::PoolType& aInner) const {
+ AutoCriticalSection lock(GetCS());
+ return aInner.IsInLowest2GB();
+ }
+#endif // defined(_M_X64)
+
+ private:
+ template <typename VMPolicyT, typename InnerT>
+ friend class TrampolinePool;
+
+ inline static RangeMap<MMPolicyInProcess> sVMMap;
+ inline static CRITICAL_SECTION sCS;
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_VMSharingPolicies_h
diff --git a/mozglue/misc/interceptor/moz.build b/mozglue/misc/interceptor/moz.build
new file mode 100644
index 0000000000..6966dc7543
--- /dev/null
+++ b/mozglue/misc/interceptor/moz.build
@@ -0,0 +1,24 @@
+# -*- 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/.
+
+EXPORTS.mozilla.interceptor += [
+ "Arm64.h",
+ "MMPolicies.h",
+ "PatcherBase.h",
+ "PatcherDetour.h",
+ "PatcherNopSpace.h",
+ "RangeMap.h",
+ "TargetFunction.h",
+ "Trampoline.h",
+ "VMSharingPolicies.h",
+]
+
+if CONFIG["CPU_ARCH"] == "aarch64":
+ FINAL_LIBRARY = "mozglue"
+
+ UNIFIED_SOURCES += [
+ "Arm64.cpp",
+ ]
diff --git a/mozglue/misc/moz.build b/mozglue/misc/moz.build
new file mode 100644
index 0000000000..ab2855d403
--- /dev/null
+++ b/mozglue/misc/moz.build
@@ -0,0 +1,107 @@
+# -*- 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/.
+
+FINAL_LIBRARY = "mozglue"
+
+EXPORTS.mozilla += [
+ "AutoProfilerLabel.h",
+ "decimal/Decimal.h",
+ "decimal/DoubleConversion.h",
+ "MmapFaultHandler.h",
+ "PlatformConditionVariable.h",
+ "PlatformMutex.h",
+ "Printf.h",
+ "StackWalk.h",
+ "TimeStamp.h",
+ "Uptime.h",
+]
+
+EXPORTS.mozilla.glue += [
+ "Debug.h",
+ "WinUtils.h",
+]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ EXPORTS.mozilla += [
+ "PreXULSkeletonUI.h",
+ "StackWalk_windows.h",
+ "TimeStamp_windows.h",
+ "WindowsDpiAwareness.h",
+ ]
+
+SOURCES += [
+ "AutoProfilerLabel.cpp",
+ "MmapFaultHandler.cpp",
+ "Printf.cpp",
+ "StackWalk.cpp",
+ "TimeStamp.cpp",
+ "Uptime.cpp",
+]
+
+OS_LIBS += CONFIG["REALTIME_LIBS"]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ DIRS += [
+ "interceptor",
+ ]
+ EXPORTS += [
+ "nsWindowsDllInterceptor.h",
+ ]
+ EXPORTS.mozilla += [
+ "DynamicallyLinkedFunctionPtr.h",
+ "ImportDir.h",
+ "NativeNt.h",
+ "WindowsMapRemoteView.h",
+ "WindowsProcessMitigations.h",
+ ]
+ EXPORTS.mozilla.glue += [
+ "WindowsUnicode.h",
+ ]
+ SOURCES += [
+ "PreXULSkeletonUI.cpp",
+ "TimeStamp_windows.cpp",
+ "WindowsMapRemoteView.cpp",
+ "WindowsProcessMitigations.cpp",
+ "WindowsUnicode.cpp",
+ ]
+ OS_LIBS += ["dbghelp"]
+elif CONFIG["HAVE_CLOCK_MONOTONIC"]:
+ SOURCES += [
+ "TimeStamp_posix.cpp",
+ ]
+elif CONFIG["OS_ARCH"] == "Darwin":
+ SOURCES += [
+ "TimeStamp_darwin.cpp",
+ ]
+elif CONFIG["COMPILE_ENVIRONMENT"]:
+ error("No TimeStamp implementation on this platform. Build will not succeed")
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ SOURCES += [
+ "ConditionVariable_windows.cpp",
+ "Mutex_windows.cpp",
+ ]
+else:
+ SOURCES += [
+ "ConditionVariable_posix.cpp",
+ "Mutex_posix.cpp",
+ ]
+
+if CONFIG["MOZ_LINKER"] and CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
+ LOCAL_INCLUDES += [
+ "/mozglue/linker",
+ ]
+
+SOURCES += [
+ "decimal/Decimal.cpp",
+]
+
+if CONFIG["CC_TYPE"] == "clang":
+ # Suppress warnings from third-party V8 Decimal code.
+ SOURCES["decimal/Decimal.cpp"].flags += ["-Wno-implicit-fallthrough"]
+
+for var in ("MOZ_APP_BASENAME", "MOZ_APP_VENDOR"):
+ DEFINES[var] = '"%s"' % CONFIG[var]
diff --git a/mozglue/misc/nsWindowsDllInterceptor.h b/mozglue/misc/nsWindowsDllInterceptor.h
new file mode 100644
index 0000000000..d6afc9bb7f
--- /dev/null
+++ b/mozglue/misc/nsWindowsDllInterceptor.h
@@ -0,0 +1,819 @@
+/* -*- 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 <wchar.h>
+#include <windows.h>
+#include <winternl.h>
+
+#include <utility>
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/Tuple.h"
+#include "mozilla/Types.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
+#include "mozilla/interceptor/MMPolicies.h"
+#include "mozilla/interceptor/PatcherDetour.h"
+#include "mozilla/interceptor/PatcherNopSpace.h"
+#include "mozilla/interceptor/VMSharingPolicies.h"
+#include "nsWindowsHelpers.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.
+ *
+ */
+
+#if defined(_M_IX86) && defined(__clang__) && __has_declspec_attribute(guard)
+// On x86, nop-space patches return to the second instruction of their target.
+// This is a deliberate violation of Control Flow Guard, so disable the check.
+# define INTERCEPTOR_DISABLE_CFGUARD __declspec(guard(nocf))
+#else
+# define INTERCEPTOR_DISABLE_CFGUARD /* nothing */
+#endif
+
+namespace mozilla {
+namespace interceptor {
+
+template <typename T>
+struct OriginalFunctionPtrTraits;
+
+template <typename R, typename... Args>
+struct OriginalFunctionPtrTraits<R (*)(Args...)> {
+ using ReturnType = R;
+};
+
+#if defined(_M_IX86)
+template <typename R, typename... Args>
+struct OriginalFunctionPtrTraits<R(__stdcall*)(Args...)> {
+ using ReturnType = R;
+};
+
+template <typename R, typename... Args>
+struct OriginalFunctionPtrTraits<R(__fastcall*)(Args...)> {
+ using ReturnType = R;
+};
+#endif // defined(_M_IX86)
+
+template <typename InterceptorT, typename FuncPtrT>
+class FuncHook final {
+ public:
+ using ThisType = FuncHook<InterceptorT, FuncPtrT>;
+ using ReturnType = typename OriginalFunctionPtrTraits<FuncPtrT>::ReturnType;
+
+ constexpr FuncHook() : mOrigFunc(nullptr), mInitOnce(INIT_ONCE_STATIC_INIT) {}
+
+ ~FuncHook() = default;
+
+ bool Set(InterceptorT& aInterceptor, const char* aName, FuncPtrT aHookDest) {
+ LPVOID addHookOk = nullptr;
+ InitOnceContext ctx(this, &aInterceptor, aName, aHookDest, false);
+
+ return ::InitOnceExecuteOnce(&mInitOnce, &InitOnceCallback, &ctx,
+ &addHookOk) &&
+ addHookOk;
+ }
+
+ bool SetDetour(InterceptorT& aInterceptor, const char* aName,
+ FuncPtrT aHookDest) {
+ LPVOID addHookOk = nullptr;
+ InitOnceContext ctx(this, &aInterceptor, aName, aHookDest, true);
+
+ return ::InitOnceExecuteOnce(&mInitOnce, &InitOnceCallback, &ctx,
+ &addHookOk) &&
+ addHookOk;
+ }
+
+ explicit operator bool() const { return !!mOrigFunc; }
+
+ template <typename... ArgsType>
+ INTERCEPTOR_DISABLE_CFGUARD ReturnType operator()(ArgsType&&... aArgs) const {
+ return mOrigFunc(std::forward<ArgsType>(aArgs)...);
+ }
+
+ FuncPtrT GetStub() const { return mOrigFunc; }
+
+ // One-time init stuff cannot be moved or copied
+ FuncHook(const FuncHook&) = delete;
+ FuncHook(FuncHook&&) = delete;
+ FuncHook& operator=(const FuncHook&) = delete;
+ FuncHook& operator=(FuncHook&& aOther) = delete;
+
+ private:
+ struct MOZ_RAII InitOnceContext final {
+ InitOnceContext(ThisType* aHook, InterceptorT* aInterceptor,
+ const char* aName, FuncPtrT aHookDest, bool aForceDetour)
+ : mHook(aHook),
+ mInterceptor(aInterceptor),
+ mName(aName),
+ mHookDest(reinterpret_cast<void*>(aHookDest)),
+ mForceDetour(aForceDetour) {}
+
+ ThisType* mHook;
+ InterceptorT* mInterceptor;
+ const char* mName;
+ void* mHookDest;
+ bool mForceDetour;
+ };
+
+ private:
+ bool Apply(InterceptorT* aInterceptor, const char* aName, void* aHookDest) {
+ return aInterceptor->AddHook(aName, reinterpret_cast<intptr_t>(aHookDest),
+ reinterpret_cast<void**>(&mOrigFunc));
+ }
+
+ bool ApplyDetour(InterceptorT* aInterceptor, const char* aName,
+ void* aHookDest) {
+ return aInterceptor->AddDetour(aName, reinterpret_cast<intptr_t>(aHookDest),
+ reinterpret_cast<void**>(&mOrigFunc));
+ }
+
+ static BOOL CALLBACK InitOnceCallback(PINIT_ONCE aInitOnce, PVOID aParam,
+ PVOID* aOutContext) {
+ MOZ_ASSERT(aOutContext);
+
+ bool result;
+ auto ctx = reinterpret_cast<InitOnceContext*>(aParam);
+ if (ctx->mForceDetour) {
+ result = ctx->mHook->ApplyDetour(ctx->mInterceptor, ctx->mName,
+ ctx->mHookDest);
+ } else {
+ result = ctx->mHook->Apply(ctx->mInterceptor, ctx->mName, ctx->mHookDest);
+ }
+
+ *aOutContext =
+ result ? reinterpret_cast<PVOID>(1U << INIT_ONCE_CTX_RESERVED_BITS)
+ : nullptr;
+ return TRUE;
+ }
+
+ private:
+ FuncPtrT mOrigFunc;
+ INIT_ONCE mInitOnce;
+};
+
+template <typename InterceptorT, typename FuncPtrT>
+class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS FuncHookCrossProcess final {
+ public:
+ using ThisType = FuncHookCrossProcess<InterceptorT, FuncPtrT>;
+ using ReturnType = typename OriginalFunctionPtrTraits<FuncPtrT>::ReturnType;
+
+#if defined(DEBUG)
+ FuncHookCrossProcess() {}
+#endif // defined(DEBUG)
+
+ bool Set(nt::CrossExecTransferManager& aTransferMgr,
+ InterceptorT& aInterceptor, const char* aName, FuncPtrT aHookDest) {
+ FuncPtrT origFunc;
+ if (!aInterceptor.AddHook(aName, reinterpret_cast<intptr_t>(aHookDest),
+ reinterpret_cast<void**>(&origFunc))) {
+ return false;
+ }
+
+ return CopyStubToChildProcess(aTransferMgr, aInterceptor, origFunc);
+ }
+
+ bool SetDetour(nt::CrossExecTransferManager& aTransferMgr,
+ InterceptorT& aInterceptor, const char* aName,
+ FuncPtrT aHookDest) {
+ FuncPtrT origFunc;
+ if (!aInterceptor.AddDetour(aName, reinterpret_cast<intptr_t>(aHookDest),
+ reinterpret_cast<void**>(&origFunc))) {
+ return false;
+ }
+
+ return CopyStubToChildProcess(aTransferMgr, aInterceptor, origFunc);
+ }
+
+ explicit operator bool() const { return !!mOrigFunc; }
+
+ /**
+ * NB: This operator is only meaningful when invoked in the target process!
+ */
+ template <typename... ArgsType>
+ ReturnType operator()(ArgsType&&... aArgs) const {
+ return mOrigFunc(std::forward<ArgsType>(aArgs)...);
+ }
+
+#if defined(DEBUG)
+ FuncHookCrossProcess(const FuncHookCrossProcess&) = delete;
+ FuncHookCrossProcess(FuncHookCrossProcess&&) = delete;
+ FuncHookCrossProcess& operator=(const FuncHookCrossProcess&) = delete;
+ FuncHookCrossProcess& operator=(FuncHookCrossProcess&& aOther) = delete;
+#endif // defined(DEBUG)
+
+ private:
+ bool CopyStubToChildProcess(nt::CrossExecTransferManager& aTransferMgr,
+ InterceptorT& aInterceptor, FuncPtrT aStub) {
+ LauncherVoidResult writeResult =
+ aTransferMgr.Transfer(&mOrigFunc, &aStub, sizeof(FuncPtrT));
+ if (writeResult.isErr()) {
+#ifdef MOZ_USE_LAUNCHER_ERROR
+ const mozilla::WindowsError& err = writeResult.inspectErr().mError;
+#else
+ const mozilla::WindowsError& err = writeResult.inspectErr();
+#endif
+ aInterceptor.SetLastDetourError(FUNCHOOKCROSSPROCESS_COPYSTUB_ERROR,
+ err.AsHResult());
+ return false;
+ }
+ return true;
+ }
+
+ private:
+ FuncPtrT mOrigFunc;
+};
+
+template <typename MMPolicyT, typename InterceptorT>
+struct TypeResolver;
+
+template <typename InterceptorT>
+struct TypeResolver<mozilla::interceptor::MMPolicyInProcess, InterceptorT> {
+ template <typename FuncPtrT>
+ using FuncHookType = FuncHook<InterceptorT, FuncPtrT>;
+};
+
+template <typename InterceptorT>
+struct TypeResolver<mozilla::interceptor::MMPolicyOutOfProcess, InterceptorT> {
+ template <typename FuncPtrT>
+ using FuncHookType = FuncHookCrossProcess<InterceptorT, FuncPtrT>;
+};
+
+template <typename VMPolicy = mozilla::interceptor::VMSharingPolicyShared>
+class WindowsDllInterceptor final
+ : public TypeResolver<typename VMPolicy::MMPolicyT,
+ WindowsDllInterceptor<VMPolicy>> {
+ typedef WindowsDllInterceptor<VMPolicy> ThisType;
+
+ interceptor::WindowsDllDetourPatcher<VMPolicy> mDetourPatcher;
+#if defined(_M_IX86)
+ interceptor::WindowsDllNopSpacePatcher<typename VMPolicy::MMPolicyT>
+ mNopSpacePatcher;
+#endif // defined(_M_IX86)
+
+ HMODULE mModule;
+
+ public:
+ template <typename... Args>
+ explicit WindowsDllInterceptor(Args&&... aArgs)
+ : mDetourPatcher(std::forward<Args>(aArgs)...)
+#if defined(_M_IX86)
+ ,
+ mNopSpacePatcher(std::forward<Args>(aArgs)...)
+#endif // defined(_M_IX86)
+ ,
+ mModule(nullptr) {
+ }
+
+ WindowsDllInterceptor(const WindowsDllInterceptor&) = delete;
+ WindowsDllInterceptor(WindowsDllInterceptor&&) = delete;
+ WindowsDllInterceptor& operator=(const WindowsDllInterceptor&) = delete;
+ WindowsDllInterceptor& operator=(WindowsDllInterceptor&&) = delete;
+
+ ~WindowsDllInterceptor() { Clear(); }
+
+ template <size_t N>
+ void Init(const char (&aModuleName)[N]) {
+ 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);
+ }
+
+ void Init(const wchar_t* aModuleName) {
+ if (mModule) {
+ return;
+ }
+
+ mModule = ::LoadLibraryW(aModuleName);
+ }
+
+ /** Force a specific configuration for testing purposes. NOT to be used in
+ production code! **/
+ void TestOnlyDetourInit(const wchar_t* aModuleName, DetourFlags aFlags) {
+ Init(aModuleName);
+ mDetourPatcher.Init(aFlags);
+ }
+
+ void Clear() {
+ if (!mModule) {
+ return;
+ }
+
+#if defined(_M_IX86)
+ mNopSpacePatcher.Clear();
+#endif // defined(_M_IX86)
+ mDetourPatcher.Clear();
+
+ // NB: We intentionally leak mModule
+ }
+
+#if defined(NIGHTLY_BUILD)
+ const Maybe<DetourError>& GetLastDetourError() const {
+ return mDetourPatcher.GetLastDetourError();
+ }
+#endif // defined(NIGHTLY_BUILD)
+ template <typename... Args>
+ void SetLastDetourError(Args&&... aArgs) {
+ return mDetourPatcher.SetLastDetourError(std::forward<Args>(aArgs)...);
+ }
+
+ constexpr static uint32_t GetWorstCaseRequiredBytesToPatch() {
+ return WindowsDllDetourPatcherPrimitive<
+ typename VMPolicy::MMPolicyT>::GetWorstCaseRequiredBytesToPatch();
+ }
+
+ private:
+ /**
+ * 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 (!mModule) {
+ mDetourPatcher.SetLastDetourError(DetourResultCode::INTERCEPTOR_MOD_NULL);
+ return false;
+ }
+
+ if (!mDetourPatcher.IsPageAccessible(
+ nt::PEHeaders::HModuleToBaseAddr<uintptr_t>(mModule))) {
+ mDetourPatcher.SetLastDetourError(
+ DetourResultCode::INTERCEPTOR_MOD_INACCESSIBLE);
+ return false;
+ }
+
+ FARPROC proc = mDetourPatcher.GetProcAddress(mModule, aName);
+ if (!proc) {
+ mDetourPatcher.SetLastDetourError(
+ DetourResultCode::INTERCEPTOR_PROC_NULL);
+ return false;
+ }
+
+ if (!mDetourPatcher.IsPageAccessible(reinterpret_cast<uintptr_t>(proc))) {
+ mDetourPatcher.SetLastDetourError(
+ DetourResultCode::INTERCEPTOR_PROC_INACCESSIBLE);
+ return false;
+ }
+
+#if defined(_M_IX86)
+ if (mNopSpacePatcher.AddHook(proc, aHookDest, aOrigFunc)) {
+ return true;
+ }
+#endif // defined(_M_IX86)
+
+ 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
+ * 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 (!mModule) {
+ mDetourPatcher.SetLastDetourError(DetourResultCode::INTERCEPTOR_MOD_NULL);
+ return false;
+ }
+
+ if (!mDetourPatcher.IsPageAccessible(
+ nt::PEHeaders::HModuleToBaseAddr<uintptr_t>(mModule))) {
+ mDetourPatcher.SetLastDetourError(
+ DetourResultCode::INTERCEPTOR_MOD_INACCESSIBLE);
+ return false;
+ }
+
+ FARPROC proc = mDetourPatcher.GetProcAddress(mModule, aName);
+ if (!proc) {
+ mDetourPatcher.SetLastDetourError(
+ DetourResultCode::INTERCEPTOR_PROC_NULL);
+ return false;
+ }
+
+ if (!mDetourPatcher.IsPageAccessible(reinterpret_cast<uintptr_t>(proc))) {
+ mDetourPatcher.SetLastDetourError(
+ DetourResultCode::INTERCEPTOR_PROC_INACCESSIBLE);
+ return false;
+ }
+
+ return AddDetour(proc, aHookDest, aOrigFunc);
+ }
+
+ bool AddDetour(FARPROC aProc, intptr_t aHookDest, void** aOrigFunc) {
+ MOZ_ASSERT(mModule && aProc);
+
+ if (!mDetourPatcher.Initialized()) {
+ DetourFlags flags = DetourFlags::eDefault;
+#if defined(_M_X64)
+ // NTDLL hooks should attempt to use a 10-byte patch because some
+ // injected DLLs do the same and interfere with our stuff.
+ bool needs10BytePatch = (mModule == ::GetModuleHandleW(L"ntdll.dll"));
+
+ bool isWin8Or81 = IsWin8OrLater() && (!IsWin10OrLater());
+ bool isWin8 = IsWin8OrLater() && (!IsWin8Point1OrLater());
+
+ bool isKernel32Dll = (mModule == ::GetModuleHandleW(L"kernel32.dll"));
+
+ bool isDuplicateHandle = (reinterpret_cast<void*>(aProc) ==
+ reinterpret_cast<void*>(&::DuplicateHandle));
+
+ // CloseHandle on Windows 8/8.1 only accomodates 10-byte patches.
+ needs10BytePatch |= isWin8Or81 && isKernel32Dll &&
+ (reinterpret_cast<void*>(aProc) ==
+ reinterpret_cast<void*>(&CloseHandle));
+
+ // CreateFileA and DuplicateHandle on Windows 8 require 10-byte patches.
+ needs10BytePatch |= isWin8 && isKernel32Dll &&
+ ((reinterpret_cast<void*>(aProc) ==
+ reinterpret_cast<void*>(&::CreateFileA)) ||
+ isDuplicateHandle);
+
+ if (needs10BytePatch) {
+ flags |= DetourFlags::eEnable10BytePatch;
+ }
+
+ if (isWin8 && isDuplicateHandle) {
+ // Because we can't detour Win8's KERNELBASE!DuplicateHandle,
+ // we detour kernel32!DuplicateHandle (See bug 1659398).
+ flags |= DetourFlags::eDontResolveRedirection;
+ }
+#endif // defined(_M_X64)
+
+ mDetourPatcher.Init(flags);
+ }
+
+ return mDetourPatcher.AddHook(aProc, aHookDest, aOrigFunc);
+ }
+
+ private:
+ template <typename InterceptorT, typename FuncPtrT>
+ friend class FuncHook;
+
+ template <typename InterceptorT, typename FuncPtrT>
+ friend class FuncHookCrossProcess;
+};
+
+/**
+ * IAT patching is intended for use when we only want to intercept a function
+ * call originating from a specific module.
+ */
+class WindowsIATPatcher final {
+ public:
+ template <typename FuncPtrT>
+ using FuncHookType = FuncHook<WindowsIATPatcher, FuncPtrT>;
+
+ private:
+ static bool CheckASCII(const char* aInStr) {
+ while (*aInStr) {
+ if (*aInStr & 0x80) {
+ return false;
+ }
+ ++aInStr;
+ }
+ return true;
+ }
+
+ static bool AddHook(HMODULE aFromModule, const char* aToModuleName,
+ const char* aTargetFnName, void* aHookDest,
+ Atomic<void*>* aOutOrigFunc) {
+ if (!aFromModule || !aToModuleName || !aTargetFnName || !aOutOrigFunc) {
+ return false;
+ }
+
+ // PE Spec requires ASCII names for imported module names
+ const bool isModuleNameAscii = CheckASCII(aToModuleName);
+ MOZ_ASSERT(isModuleNameAscii);
+ if (!isModuleNameAscii) {
+ return false;
+ }
+
+ // PE Spec requires ASCII names for imported function names
+ const bool isTargetFnNameAscii = CheckASCII(aTargetFnName);
+ MOZ_ASSERT(isTargetFnNameAscii);
+ if (!isTargetFnNameAscii) {
+ return false;
+ }
+
+ nt::PEHeaders headers(aFromModule);
+ if (!headers) {
+ return false;
+ }
+
+ PIMAGE_IMPORT_DESCRIPTOR impDesc =
+ headers.GetImportDescriptor(aToModuleName);
+ if (!nt::PEHeaders::IsValid(impDesc)) {
+ // Either aFromModule does not import aToModuleName at load-time, or
+ // aToModuleName is a (currently unsupported) delay-load import.
+ return false;
+ }
+
+ // Resolve the import name table (INT).
+ auto firstINTThunk = headers.template RVAToPtr<PIMAGE_THUNK_DATA>(
+ impDesc->OriginalFirstThunk);
+ if (!nt::PEHeaders::IsValid(firstINTThunk)) {
+ return false;
+ }
+
+ Maybe<ptrdiff_t> thunkIndex;
+
+ // Scan the INT for the location of the thunk for the function named
+ // 'aTargetFnName'.
+ for (PIMAGE_THUNK_DATA curINTThunk = firstINTThunk;
+ nt::PEHeaders::IsValid(curINTThunk); ++curINTThunk) {
+ if (IMAGE_SNAP_BY_ORDINAL(curINTThunk->u1.Ordinal)) {
+ // Currently not supporting import by ordinal; this isn't hard to add,
+ // but we won't bother unless necessary.
+ continue;
+ }
+
+ PIMAGE_IMPORT_BY_NAME curThunkFnName =
+ headers.template RVAToPtr<PIMAGE_IMPORT_BY_NAME>(
+ curINTThunk->u1.AddressOfData);
+ MOZ_ASSERT(curThunkFnName);
+ if (!curThunkFnName) {
+ // Looks like we have a bad name descriptor. Try to continue.
+ continue;
+ }
+
+ // Function name checks MUST be case-sensitive!
+ if (!strcmp(aTargetFnName, curThunkFnName->Name)) {
+ // We found the thunk. Save the index of this thunk, as the IAT thunk
+ // is located at the same index in that table as in the INT.
+ thunkIndex = Some(curINTThunk - firstINTThunk);
+ break;
+ }
+ }
+
+ if (thunkIndex.isNothing()) {
+ // We never found a thunk for that function. Perhaps it's not imported?
+ return false;
+ }
+
+ if (thunkIndex.value() < 0) {
+ // That's just wrong.
+ return false;
+ }
+
+ auto firstIATThunk =
+ headers.template RVAToPtr<PIMAGE_THUNK_DATA>(impDesc->FirstThunk);
+ if (!nt::PEHeaders::IsValid(firstIATThunk)) {
+ return false;
+ }
+
+ // Resolve the IAT thunk for the function we want
+ PIMAGE_THUNK_DATA targetThunk = &firstIATThunk[thunkIndex.value()];
+ if (!nt::PEHeaders::IsValid(targetThunk)) {
+ return false;
+ }
+
+ auto fnPtr = reinterpret_cast<Atomic<void*>*>(&targetThunk->u1.Function);
+
+ // Now we can just change out its pointer with our hook function.
+ AutoVirtualProtect prot(fnPtr, sizeof(void*), PAGE_EXECUTE_READWRITE);
+ if (!prot) {
+ return false;
+ }
+
+ // We do the exchange this way to ensure that *aOutOrigFunc is always valid
+ // once the atomic exchange has taken place.
+ void* tmp;
+
+ do {
+ tmp = *fnPtr;
+ *aOutOrigFunc = tmp;
+ } while (!fnPtr->compareExchange(tmp, aHookDest));
+
+ return true;
+ }
+
+ template <typename InterceptorT, typename FuncPtrT>
+ friend class FuncHook;
+};
+
+template <typename FuncPtrT>
+class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS
+ FuncHook<WindowsIATPatcher, FuncPtrT>
+ final {
+ public:
+ using ThisType = FuncHook<WindowsIATPatcher, FuncPtrT>;
+ using ReturnType = typename OriginalFunctionPtrTraits<FuncPtrT>::ReturnType;
+
+ constexpr FuncHook()
+ : mInitOnce(INIT_ONCE_STATIC_INIT),
+ mFromModule(nullptr),
+ mOrigFunc(nullptr) {}
+
+#if defined(DEBUG)
+ ~FuncHook() = default;
+#endif // defined(DEBUG)
+
+ bool Set(const wchar_t* aFromModuleName, const char* aToModuleName,
+ const char* aFnName, FuncPtrT aHookDest) {
+ nsModuleHandle fromModule(::LoadLibraryW(aFromModuleName));
+ if (!fromModule) {
+ return false;
+ }
+
+ return Set(fromModule, aToModuleName, aFnName, aHookDest);
+ }
+
+ // We offer this overload in case the client wants finer-grained control over
+ // loading aFromModule.
+ bool Set(nsModuleHandle& aFromModule, const char* aToModuleName,
+ const char* aFnName, FuncPtrT aHookDest) {
+ LPVOID addHookOk = nullptr;
+ InitOnceContext ctx(this, aFromModule, aToModuleName, aFnName, aHookDest);
+
+ bool result = ::InitOnceExecuteOnce(&mInitOnce, &InitOnceCallback, &ctx,
+ &addHookOk) &&
+ addHookOk;
+ if (!result) {
+ return result;
+ }
+
+ // If we successfully set the hook then we must retain a strong reference
+ // to the module that we modified.
+ mFromModule = aFromModule.disown();
+ return result;
+ }
+
+ explicit operator bool() const { return !!mOrigFunc; }
+
+ template <typename... ArgsType>
+ ReturnType operator()(ArgsType&&... aArgs) const {
+ return mOrigFunc(std::forward<ArgsType>(aArgs)...);
+ }
+
+ FuncPtrT GetStub() const { return mOrigFunc; }
+
+#if defined(DEBUG)
+ // One-time init stuff cannot be moved or copied
+ FuncHook(const FuncHook&) = delete;
+ FuncHook(FuncHook&&) = delete;
+ FuncHook& operator=(const FuncHook&) = delete;
+ FuncHook& operator=(FuncHook&& aOther) = delete;
+#endif // defined(DEBUG)
+
+ private:
+ struct MOZ_RAII InitOnceContext final {
+ InitOnceContext(ThisType* aHook, const nsModuleHandle& aFromModule,
+ const char* aToModuleName, const char* aFnName,
+ FuncPtrT aHookDest)
+ : mHook(aHook),
+ mFromModule(aFromModule),
+ mToModuleName(aToModuleName),
+ mFnName(aFnName),
+ mHookDest(reinterpret_cast<void*>(aHookDest)) {}
+
+ ThisType* mHook;
+ const nsModuleHandle& mFromModule;
+ const char* mToModuleName;
+ const char* mFnName;
+ void* mHookDest;
+ };
+
+ private:
+ bool Apply(const nsModuleHandle& aFromModule, const char* aToModuleName,
+ const char* aFnName, void* aHookDest) {
+ return WindowsIATPatcher::AddHook(
+ aFromModule, aToModuleName, aFnName, aHookDest,
+ reinterpret_cast<Atomic<void*>*>(&mOrigFunc));
+ }
+
+ static BOOL CALLBACK InitOnceCallback(PINIT_ONCE aInitOnce, PVOID aParam,
+ PVOID* aOutContext) {
+ MOZ_ASSERT(aOutContext);
+
+ auto ctx = reinterpret_cast<InitOnceContext*>(aParam);
+ bool result = ctx->mHook->Apply(ctx->mFromModule, ctx->mToModuleName,
+ ctx->mFnName, ctx->mHookDest);
+
+ *aOutContext =
+ result ? reinterpret_cast<PVOID>(1U << INIT_ONCE_CTX_RESERVED_BITS)
+ : nullptr;
+ return TRUE;
+ }
+
+ private:
+ INIT_ONCE mInitOnce;
+ HMODULE mFromModule; // never freed
+ FuncPtrT mOrigFunc;
+};
+
+/**
+ * This class applies an irreversible patch to jump to a target function
+ * without backing up the original function.
+ */
+class WindowsDllEntryPointInterceptor final {
+ using DllMainFn = BOOL(WINAPI*)(HINSTANCE, DWORD, LPVOID);
+ using MMPolicyT = MMPolicyInProcessEarlyStage;
+
+ MMPolicyT mMMPolicy;
+
+ public:
+ explicit WindowsDllEntryPointInterceptor(
+ const MMPolicyT::Kernel32Exports& aK32Exports)
+ : mMMPolicy(aK32Exports) {}
+
+ bool Set(const nt::PEHeaders& aHeaders, DllMainFn aDestination) {
+ if (!aHeaders) {
+ return false;
+ }
+
+ WindowsDllDetourPatcherPrimitive<MMPolicyT> patcher;
+ return patcher.AddIrreversibleHook(
+ mMMPolicy, aHeaders.GetEntryPoint(),
+ reinterpret_cast<uintptr_t>(aDestination));
+ }
+};
+
+} // namespace interceptor
+
+using WindowsDllInterceptor = interceptor::WindowsDllInterceptor<>;
+
+using CrossProcessDllInterceptor = interceptor::WindowsDllInterceptor<
+ mozilla::interceptor::VMSharingPolicyUnique<
+ mozilla::interceptor::MMPolicyOutOfProcess>>;
+
+using WindowsIATPatcher = interceptor::WindowsIATPatcher;
+
+} // namespace mozilla
+
+#endif /* NS_WINDOWS_DLL_INTERCEPTOR_H_ */