Bug 1422394: Add a mechanism to report dll loads to an observer; r=jimm
authorAaron Klotz <aklotz@mozilla.com>
Mon, 04 Dec 2017 18:08:17 -0700
changeset 449618 d9ec1dda6c57b28b460a3e5bc35451519854593b
parent 449617 4bedf3a1a515e04d74816267ad94c81fa5fd1f02
child 449619 99a3b09ac1898eb1db05430e2876d6643ba0d4a8
child 449675 1a9cbc25dd9dfe03dc8face38c2b7e1b597b925e
push id1648
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 12:45:47 +0000
treeherdermozilla-release@cbb9688c2eeb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimm
bugs1422394
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1422394: Add a mechanism to report dll loads to an observer; r=jimm MozReview-Commit-ID: 1ocag6jTBVV
mozglue/build/WindowsDllBlocklist.cpp
mozglue/build/WindowsDllBlocklist.h
mozglue/build/WindowsDllServices.h
mozglue/build/moz.build
toolkit/xre/nsAppRunner.cpp
--- a/mozglue/build/WindowsDllBlocklist.cpp
+++ b/mozglue/build/WindowsDllBlocklist.cpp
@@ -28,16 +28,17 @@
 #include "nsWindowsDllInterceptor.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/StackWalk_windows.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WindowsVersion.h"
 #include "nsWindowsHelpers.h"
 #include "WindowsDllBlocklist.h"
 #include "mozilla/AutoProfilerLabel.h"
+#include "mozilla/WindowsDllServices.h"
 
 using namespace mozilla;
 
 #define ALL_VERSIONS   ((unsigned long long)-1LL)
 
 // DLLs sometimes ship without a version number, particularly early
 // releases. Blocking "version <= 0" has the effect of blocking unversioned
 // DLLs (since the call to get version info fails), but not blocking
@@ -925,8 +926,145 @@ DllBlocklist_WriteNotes(HANDLE file)
 
 MFBT_API bool
 DllBlocklist_CheckStatus()
 {
   if (sBlocklistInitFailed || sUser32BeforeBlocklist)
     return false;
   return true;
 }
+
+// ============================================================================
+// This section is for DLL Services
+// ============================================================================
+
+
+static SRWLOCK gDllServicesLock = SRWLOCK_INIT;
+static mozilla::detail::DllServicesBase* gDllServices;
+
+class MOZ_RAII AutoSharedLock final
+{
+public:
+  explicit AutoSharedLock(SRWLOCK& aLock)
+    : mLock(aLock)
+  {
+    ::AcquireSRWLockShared(&aLock);
+  }
+
+  ~AutoSharedLock()
+  {
+    ::ReleaseSRWLockShared(&mLock);
+  }
+
+  AutoSharedLock(const AutoSharedLock&) = delete;
+  AutoSharedLock(AutoSharedLock&&) = delete;
+  AutoSharedLock& operator=(const AutoSharedLock&) = delete;
+  AutoSharedLock& operator=(AutoSharedLock&&) = delete;
+
+private:
+  SRWLOCK& mLock;
+};
+
+class MOZ_RAII AutoExclusiveLock final
+{
+public:
+  explicit AutoExclusiveLock(SRWLOCK& aLock)
+    : mLock(aLock)
+  {
+    ::AcquireSRWLockExclusive(&aLock);
+  }
+
+  ~AutoExclusiveLock()
+  {
+    ::ReleaseSRWLockExclusive(&mLock);
+  }
+
+  AutoExclusiveLock(const AutoExclusiveLock&) = delete;
+  AutoExclusiveLock(AutoExclusiveLock&&) = delete;
+  AutoExclusiveLock& operator=(const AutoExclusiveLock&) = delete;
+  AutoExclusiveLock& operator=(AutoExclusiveLock&&) = delete;
+
+private:
+  SRWLOCK& mLock;
+};
+
+// These types are documented on MSDN but not provided in any SDK headers
+
+enum DllNotificationReason
+{
+  LDR_DLL_NOTIFICATION_REASON_LOADED = 1,
+  LDR_DLL_NOTIFICATION_REASON_UNLOADED = 2
+};
+
+typedef struct _LDR_DLL_LOADED_NOTIFICATION_DATA {
+  ULONG Flags;                    //Reserved.
+  PCUNICODE_STRING FullDllName;   //The full path name of the DLL module.
+  PCUNICODE_STRING BaseDllName;   //The base file name of the DLL module.
+  PVOID DllBase;                  //A pointer to the base address for the DLL in memory.
+  ULONG SizeOfImage;              //The size of the DLL image, in bytes.
+} LDR_DLL_LOADED_NOTIFICATION_DATA, *PLDR_DLL_LOADED_NOTIFICATION_DATA;
+
+typedef struct _LDR_DLL_UNLOADED_NOTIFICATION_DATA {
+  ULONG Flags;                    //Reserved.
+  PCUNICODE_STRING FullDllName;   //The full path name of the DLL module.
+  PCUNICODE_STRING BaseDllName;   //The base file name of the DLL module.
+  PVOID DllBase;                  //A pointer to the base address for the DLL in memory.
+  ULONG SizeOfImage;              //The size of the DLL image, in bytes.
+} LDR_DLL_UNLOADED_NOTIFICATION_DATA, *PLDR_DLL_UNLOADED_NOTIFICATION_DATA;
+
+typedef union _LDR_DLL_NOTIFICATION_DATA {
+  LDR_DLL_LOADED_NOTIFICATION_DATA Loaded;
+  LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded;
+} LDR_DLL_NOTIFICATION_DATA, *PLDR_DLL_NOTIFICATION_DATA;
+
+typedef const LDR_DLL_NOTIFICATION_DATA* PCLDR_DLL_NOTIFICATION_DATA;
+
+typedef VOID (CALLBACK* PLDR_DLL_NOTIFICATION_FUNCTION)(
+          ULONG aReason,
+          PCLDR_DLL_NOTIFICATION_DATA aNotificationData,
+          PVOID aContext);
+
+NTSTATUS NTAPI
+LdrRegisterDllNotification(ULONG aFlags,
+                           PLDR_DLL_NOTIFICATION_FUNCTION aCallback,
+                           PVOID aContext, PVOID* aCookie);
+
+static PVOID gNotificationCookie;
+
+static VOID CALLBACK
+DllLoadNotification(ULONG aReason, PCLDR_DLL_NOTIFICATION_DATA aNotificationData,
+                    PVOID aContext)
+{
+  if (aReason != LDR_DLL_NOTIFICATION_REASON_LOADED) {
+    // We don't care about unloads
+    return;
+  }
+
+  AutoSharedLock lock(gDllServicesLock);
+  if (!gDllServices) {
+    return;
+  }
+
+  PCUNICODE_STRING fullDllName = aNotificationData->Loaded.FullDllName;
+  gDllServices->DispatchDllLoadNotification(fullDllName);
+}
+
+MFBT_API void
+DllBlocklist_SetDllServices(mozilla::detail::DllServicesBase* aSvc)
+{
+  AutoExclusiveLock lock(gDllServicesLock);
+
+  if (aSvc && !gNotificationCookie) {
+    auto pLdrRegisterDllNotification =
+      reinterpret_cast<decltype(&::LdrRegisterDllNotification)>(
+        ::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"),
+                         "LdrRegisterDllNotification"));
+
+    MOZ_DIAGNOSTIC_ASSERT(pLdrRegisterDllNotification);
+
+    NTSTATUS ntStatus = pLdrRegisterDllNotification(0, &DllLoadNotification,
+                                                    nullptr, &gNotificationCookie);
+    MOZ_DIAGNOSTIC_ASSERT(NT_SUCCESS(ntStatus));
+  }
+
+  gDllServices = aSvc;
+}
+
--- a/mozglue/build/WindowsDllBlocklist.h
+++ b/mozglue/build/WindowsDllBlocklist.h
@@ -19,10 +19,19 @@ enum DllBlocklistInitFlags
   eDllBlocklistInitFlagDefault = 0,
   eDllBlocklistInitFlagIsChildProcess = 1
 };
 
 MFBT_API void DllBlocklist_Initialize(uint32_t aInitFlags = eDllBlocklistInitFlagDefault);
 MFBT_API void DllBlocklist_WriteNotes(HANDLE file);
 MFBT_API bool DllBlocklist_CheckStatus();
 
+// Forward declaration
+namespace mozilla {
+namespace detail {
+class DllServicesBase;
+} // namespace detail
+} // namespace mozilla
+
+MFBT_API void DllBlocklist_SetDllServices(mozilla::detail::DllServicesBase* aSvc);
+
 #endif // defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
 #endif // mozilla_windowsdllblocklist_h
new file mode 100644
--- /dev/null
+++ b/mozglue/build/WindowsDllServices.h
@@ -0,0 +1,91 @@
+/* -*- 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_WindowsDllServices_h
+#define mozilla_WindowsDllServices_h
+
+#include "mozilla/WindowsDllBlocklist.h"
+
+#if defined(MOZILLA_INTERNAL_API)
+
+#include "mozilla/SystemGroup.h"
+#include "nsISupportsImpl.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+
+#endif // defined(MOZILLA_INTERNAL_API)
+
+// For PCUNICODE_STRING
+#include <winternl.h>
+
+namespace mozilla {
+namespace detail {
+
+class DllServicesBase
+{
+public:
+  /**
+   * WARNING: This method is called from within an unsafe context that holds
+   *          multiple locks inside the Windows loader. The only thing that
+   *          this function should be used for is dispatching the event to our
+   *          event loop so that it may be handled in a safe context.
+   */
+  virtual void DispatchDllLoadNotification(PCUNICODE_STRING aDllName) = 0;
+
+  void Disable()
+  {
+    DllBlocklist_SetDllServices(nullptr);
+  }
+
+  DllServicesBase(const DllServicesBase&) = delete;
+  DllServicesBase(DllServicesBase&&) = delete;
+  DllServicesBase& operator=(const DllServicesBase&) = delete;
+  DllServicesBase& operator=(DllServicesBase&&) = delete;
+
+protected:
+  DllServicesBase() = default;
+  virtual ~DllServicesBase() = default;
+
+  void Enable()
+  {
+    DllBlocklist_SetDllServices(this);
+  }
+};
+
+} // namespace detail
+
+#if defined(MOZILLA_INTERNAL_API)
+
+class DllServices : public detail::DllServicesBase
+{
+public:
+  virtual void DispatchDllLoadNotification(PCUNICODE_STRING aDllName) override final
+  {
+    nsDependentString strDllName(aDllName->Buffer,
+                                 aDllName->Length / sizeof(wchar_t));
+
+    nsCOMPtr<nsIRunnable> runnable(
+      NewRunnableMethod<bool, nsString>("DllServices::NotifyDllLoad",
+                                        this, &DllServices::NotifyDllLoad,
+                                        NS_IsMainThread(), strDllName));
+
+    SystemGroup::Dispatch(TaskCategory::Other, runnable.forget());
+  }
+
+  NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING(DllServices)
+
+protected:
+  DllServices() = default;
+  ~DllServices() = default;
+
+  virtual void NotifyDllLoad(const bool aIsMainThread, const nsString& aDllName) = 0;
+};
+
+#endif // defined(MOZILLA_INTERNAL_API)
+
+} // namespace mozilla
+
+#endif // mozilla_WindowsDllServices_h
--- a/mozglue/build/moz.build
+++ b/mozglue/build/moz.build
@@ -52,16 +52,17 @@ if CONFIG['MOZ_WIDGET_TOOLKIT']:
             'version',
         ]
 
     EXPORTS.mozilla += [
         'arm.h',
         'mips.h',
         'SSE.h',
         'WindowsDllBlocklist.h',
+        'WindowsDllServices.h',
     ]
 
     if CONFIG['CPU_ARCH'].startswith('x86'):
         SOURCES += [
             'SSE.cpp',
         ]
 
     if CONFIG['CPU_ARCH'] == 'arm':
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -154,16 +154,17 @@
 #include <sys/stat.h>
 #include <unistd.h>
 #include <pwd.h>
 #endif
 
 #ifdef XP_WIN
 #include <process.h>
 #include <shlobj.h>
+#include "mozilla/WindowsDllServices.h"
 #include "nsThreadUtils.h"
 #include <comdef.h>
 #include <wbemidl.h>
 #include "WinUtils.h"
 #endif
 
 #ifdef XP_MACOSX
 #include "nsILocalFileMac.h"
@@ -1649,16 +1650,50 @@ ScopedXPCOMStartup::CreateAppSupport(nsI
   if (!gNativeAppSupport)
     return NS_ERROR_NOT_INITIALIZED;
 
   return gNativeAppSupport->QueryInterface(aIID, aResult);
 }
 
 nsINativeAppSupport* ScopedXPCOMStartup::gNativeAppSupport;
 
+#if defined(XP_WIN)
+
+class DllNotifications : public mozilla::DllServices
+{
+public:
+  DllNotifications()
+  {
+    Enable();
+  }
+
+private:
+  ~DllNotifications() = default;
+
+  void NotifyDllLoad(const bool aIsMainThread, const nsString& aDllName) override;
+};
+
+void
+DllNotifications::NotifyDllLoad(const bool aIsMainThread,
+                                const nsString& aDllName)
+{
+  const char* topic;
+
+  if (aIsMainThread) {
+    topic = "dll-loaded-main-thread";
+  } else {
+    topic = "dll-loaded-non-main-thread";
+  }
+
+  nsCOMPtr<nsIObserverService> obsServ(mozilla::services::GetObserverService());
+  obsServ->NotifyObservers(nullptr, topic, aDllName.get());
+}
+
+#endif // defined(XP_WIN)
+
 static void DumpArbitraryHelp()
 {
   nsresult rv;
 
   ScopedLogging log;
 
   {
     ScopedXPCOMStartup xpcom;
@@ -4297,16 +4332,23 @@ void AddSandboxAnnotations()
  * the calling of appStartup->Run().
  */
 nsresult
 XREMain::XRE_mainRun()
 {
   nsresult rv = NS_OK;
   NS_ASSERTION(mScopedXPCOM, "Scoped xpcom not initialized.");
 
+#if defined(XP_WIN)
+  RefPtr<DllNotifications> dllNotifications(new DllNotifications());
+  auto dllNotificationsDisable = MakeScopeExit([&dllNotifications]() {
+    dllNotifications->Disable();
+  });
+#endif // defined(XP_WIN)
+
 #ifdef NS_FUNCTION_TIMER
   // initialize some common services, so we don't pay the cost for these at odd times later on;
   // SetWindowCreator -> ChromeRegistry -> IOService -> SocketTransportService -> (nspr wspm init), Prefs
   {
     nsCOMPtr<nsISupports> comp;
 
     comp = do_GetService("@mozilla.org/preferences-service;1");