diff options
Diffstat (limited to '')
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, ¤tTableEntry->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, ®); + 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_ */ |