Bug 1330833 - Add the new "modules" (DLLs) ping type. r=Dexter,mstange,jorendorff, data-review=bsmedberg
authorMarco Castelluccio <mcastelluccio@mozilla.com>
Sat, 18 Feb 2017 14:17:30 +0000
changeset 372826 8fc28f2d52d93245ed802f23c15c16290a7c22cc
parent 372825 6af6e3b0247f89ab98d04f16f294c6b21b9730cc
child 372827 df244a89b64a1064b926925d4e1fab85f5879492
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersDexter, mstange, jorendorff
bugs1330833
milestone54.0a1
Bug 1330833 - Add the new "modules" (DLLs) ping type. r=Dexter,mstange,jorendorff, data-review=bsmedberg
toolkit/components/telemetry/Telemetry.cpp
toolkit/components/telemetry/TelemetryController.jsm
toolkit/components/telemetry/TelemetryModules.jsm
toolkit/components/telemetry/docs/data/modules-ping.rst
toolkit/components/telemetry/docs/internals/preferences.rst
toolkit/components/telemetry/moz.build
toolkit/components/telemetry/nsITelemetry.idl
toolkit/components/telemetry/tests/modules-test.cpp
toolkit/components/telemetry/tests/moz.build
toolkit/components/telemetry/tests/unit/testNoPDB32.dll
toolkit/components/telemetry/tests/unit/testNoPDB64.dll
toolkit/components/telemetry/tests/unit/testUnicodePDB32.dll
toolkit/components/telemetry/tests/unit/testUnicodePDB64.dll
toolkit/components/telemetry/tests/unit/test_TelemetryModules.js
toolkit/components/telemetry/tests/unit/xpcshell.ini
tools/profiler/core/EHABIStackWalk.cpp
tools/profiler/core/platform.cpp
tools/profiler/core/shared-libraries-linux.cc
tools/profiler/core/shared-libraries-macos.cc
tools/profiler/core/shared-libraries-win32.cc
tools/profiler/lul/platform-linux-lul.cpp
tools/profiler/public/shared-libraries.h
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -7,28 +7,30 @@
 #include <algorithm>
 
 #include <fstream>
 
 #include <prio.h>
 #include <prproces.h>
 
 #include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/Promise.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/Scoped.h"
 #include "mozilla/Unused.h"
 
 #include "base/pickle.h"
 #include "nsIComponentManager.h"
 #include "nsIServiceManager.h"
 #include "nsThreadManager.h"
+#include "nsXPCOMCIDInternal.h"
 #include "nsCOMArray.h"
 #include "nsCOMPtr.h"
 #include "nsXPCOMPrivate.h"
 #include "nsIXULAppInfo.h"
 #include "nsVersionComparator.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/ModuleUtils.h"
 #include "nsIXPConnect.h"
@@ -71,16 +73,18 @@
 #include "mozilla/Mutex.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/IOInterposer.h"
 #include "mozilla/PoisonIOInterposer.h"
 #include "mozilla/StartupTimeline.h"
 #include "mozilla/HangMonitor.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsProxyRelease.h"
 
 #if defined(MOZ_GECKO_PROFILER)
 #include "shared-libraries.h"
 #define ENABLE_STACK_CAPTURE
 #include "mozilla/StackWalk.h"
 #include "nsPrintfCString.h"
 #endif // MOZ_GECKO_PROFILER
 
@@ -91,16 +95,18 @@ namespace mozilla {
                                             PR_Close);
 }
 
 namespace {
 
 using namespace mozilla;
 using namespace mozilla::HangMonitor;
 using Telemetry::Common::AutoHashtable;
+using mozilla::dom::Promise;
+using mozilla::dom::AutoJSAPI;
 
 // The maximum number of chrome hangs stacks that we're keeping.
 const size_t kMaxChromeStacksKept = 50;
 // The maximum depth of a single chrome hang stack.
 const size_t kMaxChromeStackDepth = 50;
 
 // This class is conceptually a list of ProcessedStack objects, but it represents them
 // more efficiently by keeping a single global list of modules.
@@ -1649,18 +1655,207 @@ CreateJSStackObject(JSContext *cx, const
         return nullptr;
       }
     }
   }
 
   return ret;
 }
 
+#if defined(MOZ_GECKO_PROFILER)
+class GetLoadedModulesResultRunnable final : public Runnable
+{
+  nsMainThreadPtrHandle<Promise> mPromise;
+  SharedLibraryInfo mRawModules;
+  nsCOMPtr<nsIThread> mWorkerThread;
+
+public:
+  GetLoadedModulesResultRunnable(const nsMainThreadPtrHandle<Promise>& aPromise, const SharedLibraryInfo& rawModules)
+    : mPromise(aPromise)
+    , mRawModules(rawModules)
+    , mWorkerThread(do_GetCurrentThread())
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+  }
+
+  NS_IMETHOD
+  Run() override
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    mWorkerThread->Shutdown();
+
+    AutoJSAPI jsapi;
+    if (NS_WARN_IF(!jsapi.Init(mPromise->GlobalJSObject()))) {
+      mPromise->MaybeReject(NS_ERROR_FAILURE);
+      return NS_OK;
+    }
+
+    JSContext* cx = jsapi.cx();
+
+    JS::RootedObject moduleArray(cx, JS_NewArrayObject(cx, 0));
+    if (!moduleArray) {
+      mPromise->MaybeReject(NS_ERROR_FAILURE);
+      return NS_OK;
+    }
+
+    for (unsigned int i = 0, n = mRawModules.GetSize(); i != n; i++) {
+      const SharedLibrary &info = mRawModules.GetEntry(i);
+
+      nsString basename = info.GetName();
+#if defined(XP_MACOSX) || defined(XP_LINUX)
+      int32_t pos = basename.RFindChar('/');
+      if (pos != kNotFound) {
+        basename.Cut(0, pos + 1);
+      }
+#endif
+
+      nsString debug_basename = info.GetDebugName();
+#if defined(XP_MACOSX) || defined(XP_LINUX)
+      pos = debug_basename.RFindChar('/');
+      if (pos != kNotFound) {
+        debug_basename.Cut(0, pos + 1);
+      }
+#endif
+
+      JS::RootedObject moduleObj(cx, JS_NewPlainObject(cx));
+      if (!moduleObj) {
+        mPromise->MaybeReject(NS_ERROR_FAILURE);
+        return NS_OK;
+      }
+
+      // Module name.
+      JS::RootedString moduleName(cx, JS_NewUCStringCopyZ(cx, basename.get()));
+      if (!moduleName || !JS_DefineProperty(cx, moduleObj, "name", moduleName, JSPROP_ENUMERATE)) {
+        mPromise->MaybeReject(NS_ERROR_FAILURE);
+        return NS_OK;
+      }
+
+      // Module debug name.
+      JS::RootedValue moduleDebugName(cx);
+
+      if (!debug_basename.IsEmpty()) {
+        JS::RootedString str_moduleDebugName(cx, JS_NewUCStringCopyZ(cx, debug_basename.get()));
+        if (!str_moduleDebugName) {
+          mPromise->MaybeReject(NS_ERROR_FAILURE);
+          return NS_OK;
+        }
+        moduleDebugName.setString(str_moduleDebugName);
+      }
+      else {
+        moduleDebugName.setNull();
+      }
+
+      if (!JS_DefineProperty(cx, moduleObj, "debugName", moduleDebugName, JSPROP_ENUMERATE)) {
+        mPromise->MaybeReject(NS_ERROR_FAILURE);
+        return NS_OK;
+      }
+
+      // Module Breakpad identifier.
+      JS::RootedValue id(cx);
+
+      if (!info.GetBreakpadId().empty()) {
+        JS::RootedString str_id(cx, JS_NewStringCopyZ(cx, info.GetBreakpadId().c_str()));
+        if (!str_id) {
+          mPromise->MaybeReject(NS_ERROR_FAILURE);
+          return NS_OK;
+        }
+        id.setString(str_id);
+      } else {
+        id.setNull();
+      }
+
+      if (!JS_DefineProperty(cx, moduleObj, "debugID", id, JSPROP_ENUMERATE)) {
+        mPromise->MaybeReject(NS_ERROR_FAILURE);
+        return NS_OK;
+      }
+
+      // Module version.
+      JS::RootedValue version(cx);
+
+      if (!info.GetVersion().empty()) {
+        JS::RootedString v(cx, JS_NewStringCopyZ(cx, info.GetVersion().c_str()));
+        if (!v) {
+          mPromise->MaybeReject(NS_ERROR_FAILURE);
+          return NS_OK;
+        }
+        version.setString(v);
+      } else {
+        version.setNull();
+      }
+
+      if (!JS_DefineProperty(cx, moduleObj, "version", version, JSPROP_ENUMERATE)) {
+        mPromise->MaybeReject(NS_ERROR_FAILURE);
+        return NS_OK;
+      }
+
+      if (!JS_DefineElement(cx, moduleArray, i, moduleObj, JSPROP_ENUMERATE)) {
+        mPromise->MaybeReject(NS_ERROR_FAILURE);
+        return NS_OK;
+      }
+    }
+
+    mPromise->MaybeResolve(moduleArray);
+    return NS_OK;
+  }
+};
+
+class GetLoadedModulesRunnable final : public Runnable
+{
+  nsMainThreadPtrHandle<Promise> mPromise;
+
+public:
+  explicit GetLoadedModulesRunnable(const nsMainThreadPtrHandle<Promise>& aPromise)
+    : mPromise(aPromise)
+  { }
+
+  NS_IMETHOD
+  Run() override
+  {
+    nsCOMPtr<nsIRunnable> resultRunnable = new GetLoadedModulesResultRunnable(mPromise, SharedLibraryInfo::GetInfoForSelf());
+    return NS_DispatchToMainThread(resultRunnable);
+  }
+};
+#endif // MOZ_GECKO_PROFILER
+
+NS_IMETHODIMP
+TelemetryImpl::GetLoadedModules(JSContext *cx, nsISupports** aPromise)
+{
+  nsIGlobalObject* global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(cx));
+  if (NS_WARN_IF(!global)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  ErrorResult result;
+  RefPtr<Promise> promise = Promise::Create(global, result);
+  if (NS_WARN_IF(result.Failed())) {
+    return result.StealNSResult();
+  }
+
+#if defined(MOZ_GECKO_PROFILER)
+  nsCOMPtr<nsIThreadManager> tm = do_GetService(NS_THREADMANAGER_CONTRACTID);
+  nsCOMPtr<nsIThread> getModulesThread;
+  nsresult rv = tm->NewThread(0, 0, getter_AddRefs(getModulesThread));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsMainThreadPtrHandle<Promise> mainThreadPromise(new nsMainThreadPtrHolder<Promise>(promise));
+  nsCOMPtr<nsIRunnable> runnable = new GetLoadedModulesRunnable(mainThreadPromise);
+  promise.forget(aPromise);
+
+  return getModulesThread->Dispatch(runnable, nsIEventTarget::DISPATCH_NORMAL);
+#else // MOZ_GECKO_PROFILER
+  return NS_ERROR_NOT_IMPLEMENTED;
+#endif // MOZ_GECKO_PROFILER
+}
+
 static bool
-IsValidBreakpadId(const std::string &breakpadId) {
+IsValidBreakpadId(const std::string &breakpadId)
+{
   if (breakpadId.size() < 33) {
     return false;
   }
   for (char c : breakpadId) {
     if ((c < '0' || c > '9') && (c < 'A' || c > 'F')) {
       return false;
     }
   }
@@ -3024,25 +3219,24 @@ GetStackAndModules(const std::vector<uin
   for (auto & rawFrame : rawStack) {
     mozilla::Telemetry::ProcessedStack::Frame frame = { rawFrame.mPC, rawFrame.mModIndex };
     Ret.AddFrame(frame);
   }
 
 #ifdef MOZ_GECKO_PROFILER
   for (unsigned i = 0, n = rawModules.GetSize(); i != n; ++i) {
     const SharedLibrary &info = rawModules.GetEntry(i);
-    const std::string &name = info.GetName();
-    std::string basename = name;
+    std::string basename = info.GetNativeDebugName();
 #if defined(XP_MACOSX) || defined(XP_LINUX)
     // We want to use just the basename as the libname, but the
     // current profiler addon needs the full path name, so we compute the
     // basename in here.
-    size_t pos = name.rfind('/');
+    size_t pos = basename.rfind('/');
     if (pos != std::string::npos) {
-      basename = name.substr(pos + 1);
+      basename = basename.substr(pos + 1);
     }
 #endif
     mozilla::Telemetry::ProcessedStack::Module module = {
       basename,
       info.GetBreakpadId()
     };
     Ret.AddModule(module);
   }
--- a/toolkit/components/telemetry/TelemetryController.jsm
+++ b/toolkit/components/telemetry/TelemetryController.jsm
@@ -79,16 +79,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryArchive",
                                   "resource://gre/modules/TelemetryArchive.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetrySession",
                                   "resource://gre/modules/TelemetrySession.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetrySend",
                                   "resource://gre/modules/TelemetrySend.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryReportingPolicy",
                                   "resource://gre/modules/TelemetryReportingPolicy.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TelemetryModules",
+                                  "resource://gre/modules/TelemetryModules.jsm");
 
 /**
  * Setup Telemetry logging. This function also gets called when loggin related
  * preferences change.
  */
 var gLogger = null;
 var gLogAppenderDump = null;
 function configureLogging() {
@@ -745,16 +747,19 @@ var Impl = {
         // shutdown.
         TelemetryStorage.runCleanPingArchiveTask();
 
         // Now that FHR/healthreporter is gone, make sure to remove FHR's DB from
         // the profile directory. This is a temporary measure that we should drop
         // in the future.
         TelemetryStorage.removeFHRDatabase();
 
+        // Report the modules loaded in the Firefox process.
+        TelemetryModules.start();
+
         this._delayedInitTaskDeferred.resolve();
       } catch (e) {
         this._delayedInitTaskDeferred.reject(e);
       } finally {
         this._delayedInitTask = null;
       }
     }.bind(this), this._testMode ? TELEMETRY_TEST_DELAY : TELEMETRY_DELAY);
 
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/TelemetryModules.jsm
@@ -0,0 +1,99 @@
+/* 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/. */
+
+"use strict";
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
+  "resource://gre/modules/Preferences.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Log",
+  "resource://gre/modules/Log.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TelemetryController",
+  "resource://gre/modules/TelemetryController.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+  "resource://gre/modules/AppConstants.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gUpdateTimerManager",
+  "@mozilla.org/updates/timer-manager;1", "nsIUpdateTimerManager");
+XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
+  "@mozilla.org/base/telemetry;1", "nsITelemetry");
+
+this.EXPORTED_SYMBOLS = [
+  "TelemetryModules",
+];
+
+const LOGGER_NAME = "Toolkit.Telemetry";
+const LOGGER_PREFIX = "TelemetryModules::";
+
+// The default is 1 week.
+const MODULES_PING_INTERVAL_SECONDS = 7 * 24 * 60 * 60;
+const MODULES_PING_INTERVAL_PREFERENCE = "toolkit.telemetry.modulesPing.interval";
+
+const MAX_MODULES_NUM = 512;
+const MAX_NAME_LENGTH = 64;
+const TRUNCATION_DELIMITER = "\u2026";
+
+this.TelemetryModules = Object.freeze({
+  _log: Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX),
+
+  start() {
+    // The list of loaded modules is obtainable only when the profiler is enabled.
+    // If it isn't, we don't want to send the ping at all.
+    if (!AppConstants.MOZ_GECKO_PROFILER) {
+      return;
+    }
+
+    // Use nsIUpdateTimerManager for a long-duration timer that survives across sessions.
+    gUpdateTimerManager.registerTimer(
+      "telemetry_modules_ping",
+      this,
+      Preferences.get(MODULES_PING_INTERVAL_PREFERENCE, MODULES_PING_INTERVAL_SECONDS)
+    );
+  },
+
+  /**
+   * Called when the 'telemetry_modules_ping' timer fires.
+   */
+  notify() {
+    try {
+      Telemetry.getLoadedModules().then(
+        modules => {
+          modules = modules.filter(module => module.name.length > 0);
+
+          // Cut the list of modules to MAX_MODULES_NUM entries.
+          if (modules.length > MAX_MODULES_NUM) {
+            modules = modules.slice(0, MAX_MODULES_NUM);
+          }
+
+          // Cut the file names of the modules to MAX_NAME_LENGTH characters.
+          for (let module of modules) {
+            if (module.name.length > MAX_NAME_LENGTH) {
+              module.name = module.name.substr(0, MAX_NAME_LENGTH - 1) + TRUNCATION_DELIMITER;
+            }
+
+            if (module.debugName !== null && module.debugName.length > MAX_NAME_LENGTH) {
+              module.debugName = module.debugName.substr(0, MAX_NAME_LENGTH - 1) + TRUNCATION_DELIMITER;
+            }
+          }
+
+          TelemetryController.submitExternalPing("modules",
+            {
+              version: 1,
+              modules,
+            },
+            {
+              addClientId: true,
+              addEnvironment: true,
+            }
+          );
+        },
+        err => this._log.error("notify - promise failed", ex)
+      );
+    } catch (ex) {
+      this._log.error("notify - caught exception", ex);
+    }
+  },
+});
+
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/docs/data/modules-ping.rst
@@ -0,0 +1,45 @@
+
+"modules" ping
+============
+
+This ping is sent once a week and includes the modules loaded in the Firefox process.
+
+The client ID and environment is submitted with this ping.
+
+Structure:
+
+.. code-block:: js
+
+    {
+      type: "modules",
+      ... common ping data
+      clientId: <UUID>,
+      environment: { ... },
+      payload: {
+        version: 1,
+        modules: [
+          {
+            name: <string>, // Name of the module file (e.g. xul.dll)
+            version: <string>, // Version of the module
+            debugID: <string>, // ID of the debug information file
+            debugName: <string>, // Name of the debug information file
+          },
+          ...
+        ],
+      }
+    }
+
+Notes
+~~~~~
+
+The version information is only available on Windows, it is null on other platforms.
+
+The debug name is the name of the PDB on Windows (which isn't always the same as the module name modulo the extension, e.g. the PDB for C:\Windows\SysWOW64\ntdll.dll is wntdll.pdb) and is the same as the module name on other platforms.
+
+The debug ID is platform-dependent. It is compatible with the Breakpad ID used on Socorro.
+
+Sometimes the debug name and debug ID are missing for Windows modules (often with malware). In this case, they will be "null".
+
+The length of the modules array is limited to 512 entries.
+
+The name and debug name are length limited, with a maximum of 64 characters.
--- a/toolkit/components/telemetry/docs/internals/preferences.rst
+++ b/toolkit/components/telemetry/docs/internals/preferences.rst
@@ -113,11 +113,15 @@ The following prefs are for testing purp
 ``toolkit.telemetry.scheduler.idleTickInterval``
 
   Interval between scheduler ticks when the user is idle (seconds).
 
 ``toolkit.telemetry.idleTimeout``
 
   Timeout until we decide whether a user is idle or not (seconds).
 
+``toolkit.telemetry.modulesPing.interval``
+
+  Interval between "modules" ping transmissions.
+
 ``toolkit.telemetry.send.overrideOfficialCheck``
 
   If true, allows sending pings on unofficial builds. Requires a restart.
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -25,16 +25,18 @@ LOCAL_INCLUDES += [
 SPHINX_TREES['telemetry'] = 'docs'
 
 if CONFIG['GNU_CXX']:
     CXXFLAGS += ['-Wno-error=shadow']
 
 if CONFIG['ENABLE_TESTS']:
     DIRS += ['tests/gtest']
 
+TEST_DIRS += ['tests']
+
 XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
 BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
 
 XPIDL_SOURCES += [
     'nsITelemetry.idl',
 ]
 
 XPIDL_MODULE = 'telemetry'
@@ -67,16 +69,17 @@ EXTRA_COMPONENTS += [
 ]
 
 EXTRA_JS_MODULES += [
     'GCTelemetry.jsm',
     'TelemetryArchive.jsm',
     'TelemetryController.jsm',
     'TelemetryEnvironment.jsm',
     'TelemetryLog.jsm',
+    'TelemetryModules.jsm',
     'TelemetryReportingPolicy.jsm',
     'TelemetrySend.jsm',
     'TelemetrySession.jsm',
     'TelemetryStopwatch.jsm',
     'TelemetryStorage.jsm',
     'TelemetryTimestamps.jsm',
     'TelemetryUtils.jsm',
     'ThirdPartyCookieProbe.jsm',
--- a/toolkit/components/telemetry/nsITelemetry.idl
+++ b/toolkit/components/telemetry/nsITelemetry.idl
@@ -176,16 +176,35 @@ interface nsITelemetry : nsISupports
    * @param clear Whether to clear out the subsession histograms after taking a  snapshot.
    *
    * @return A snapshot of captured stacks.
    */
   [implicit_jscontext]
   jsval snapshotCapturedStacks([optional] in boolean clear);
 
   /*
+   * Returns an array of the modules loaded in the process. The data has the following structure:
+   *
+   * [
+   *   {
+   *     "name": <string>, // Name of the module file (e.g. xul.dll)
+   *     "version": <string>, // Version of the module
+   *     "debugName": <string>, // ID of the debug information file
+   *     "debugID": <string>, // Name of the debug information file
+   *   },
+   *   ...
+   * ]
+   *
+   * @return An array of modules.
+   * @throws NS_ERROR_FAILURE on failure.
+   */
+  [implicit_jscontext]
+  nsISupports getLoadedModules();
+
+  /*
    * An array of thread hang stats,
    *   [<thread>, <thread>, ...]
    * <thread> represents a single thread,
    *   {"name": "<name>",
    *    "activity": <time>,
    *    "hangs": [<hang>, <hang>, ...]}
    * <time> represents a histogram of time intervals in milliseconds,
    *   with the same format as histogramSnapshots
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/tests/modules-test.cpp
@@ -0,0 +1,8 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+void nothing()
+{
+  return;
+}
+
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/tests/moz.build
@@ -0,0 +1,17 @@
+# -*- 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/.
+
+DIST_INSTALL = False
+
+SOURCES += [
+    'modules-test.cpp',
+]
+
+SharedLibrary('modules-test')
+
+if CONFIG['COMPILE_ENVIRONMENT']:
+    shared_library = '!%smodules-test%s' % (CONFIG['DLL_PREFIX'], CONFIG['DLL_SUFFIX'])
+    TEST_HARNESS_FILES.xpcshell.toolkit.components.telemetry.tests.unit += [shared_library]
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e7f9febc4b56d7e0c1acb9a218e815e9ae20078a
GIT binary patch
literal 8704
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..19f95c98ed5ffebfc673fa1e970d9876f2215835
GIT binary patch
literal 10240
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
G=f42!ubE2#
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d3eec65ea567a7bea41d1dc8902e316f7b7cd2fa
GIT binary patch
literal 8704
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
Cq6;hl
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c11f8453de8719bb8ca7a1f5e21456fa47dcf605
GIT binary patch
literal 10752
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
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryModules.js
@@ -0,0 +1,176 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://gre/modules/TelemetryModules.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+
+const MAX_NAME_LENGTH = 64;
+
+const libModules = ctypes.libraryName("modules-test");
+const libUnicode = ctypes.libraryName("modμles-test");
+const libLongName = "lorem_ipsum_dolor_sit_amet_consectetur_adipiscing_elit_Fusce_sit_amet_tellus_non_magna_euismod_vestibulum_Vivamus_turpis_duis.dll"
+const libUnicodePDB = Services.appinfo.is64Bit ? "testUnicodePDB64.dll" : "testUnicodePDB32.dll";
+const libNoPDB = Services.appinfo.is64Bit ? "testNoPDB64.dll" : "testNoPDB32.dll";
+const libxul = OS.Path.basename(OS.Constants.Path.libxul);
+
+const libModulesFile = do_get_file(libModules).path;
+const libUnicodeFile = OS.Path.join(OS.Path.dirname(libModulesFile), libUnicode);
+const libLongNameFile = OS.Path.join(OS.Path.dirname(libModulesFile), libLongName);
+const libUnicodePDBFile = do_get_file(libUnicodePDB).path;
+const libNoPDBFile = do_get_file(libNoPDB).path;
+
+let libModulesHandle, libUnicodeHandle, libLongNameHandle, libUnicodePDBHandle, libNoPDBHandle;
+
+let expectedLibs;
+if (AppConstants.platform === "win") {
+  const version = AppConstants.MOZ_APP_VERSION.substring(0, AppConstants.MOZ_APP_VERSION.indexOf(".") + 2);
+
+  expectedLibs = [
+    {
+      name: libxul,
+      debugName: libxul.replace(".dll", ".pdb"),
+      version,
+    },
+    {
+      name: libModules,
+      debugName: libModules.replace(".dll", ".pdb"),
+      version,
+    },
+    {
+      name: libUnicode,
+      debugName: libModules.replace(".dll", ".pdb"),
+      version,
+    },
+    {
+      name: libLongName.substring(0, MAX_NAME_LENGTH - 1) + "…",
+      debugName: libModules.replace(".dll", ".pdb"),
+      version,
+    },
+    {
+      name: libUnicodePDB,
+      debugName: "libmodμles.pdb",
+      version: null,
+    },
+    {
+      name: libNoPDB,
+      debugName: null,
+      version: null,
+    },
+  ];
+} else if (AppConstants.platform === "android") {
+  // Listing shared libraries doesn't work in Android xpcshell tests.
+  // https://hg.mozilla.org/mozilla-central/file/0eef1d5a39366059677c6d7944cfe8a97265a011/tools/profiler/core/shared-libraries-linux.cc#l95
+  expectedLibs = [];
+} else {
+  expectedLibs = [
+    {
+      name: libxul,
+      debugName: libxul,
+      version: null,
+    },
+    {
+      name: libModules,
+      debugName: libModules,
+      version: null,
+    },
+    {
+      name: libUnicode,
+      debugName: libUnicode,
+      version: null,
+    },
+    {
+      name: libLongName.substring(0, MAX_NAME_LENGTH - 1) + "…",
+      debugName: libLongName.substring(0, MAX_NAME_LENGTH - 1) + "…",
+      version: null,
+    },
+  ];
+}
+
+add_task(function* setup() {
+  do_get_profile();
+
+  yield OS.File.copy(libModulesFile, libUnicodeFile);
+  yield OS.File.copy(libModulesFile, libLongName);
+
+  if (AppConstants.platform !== "android") {
+    libModulesHandle = ctypes.open(libModulesFile);
+    libUnicodeHandle = ctypes.open(libUnicodeFile);
+    libLongNameHandle = ctypes.open(libLongNameFile);
+    if (AppConstants.platform === "win") {
+      libUnicodePDBHandle = ctypes.open(libUnicodePDBFile);
+      libNoPDBHandle = ctypes.open(libNoPDBFile);
+    }
+  }
+
+  // Force the timer to fire (using a small interval).
+  Cc["@mozilla.org/updates/timer-manager;1"].getService(Ci.nsIObserver).observe(null, "utm-test-init", "");
+  Preferences.set("toolkit.telemetry.modulesPing.interval", 0);
+  Preferences.set("app.update.url", "http:/localhost");
+
+  // Start the local ping server and setup Telemetry to use it during the tests.
+  PingServer.start();
+  Preferences.set("toolkit.telemetry.server", "http://localhost:" + PingServer.port);
+});
+
+do_register_cleanup(function() {
+  if (libModulesHandle) {
+    libModulesHandle.close();
+  }
+  if (libUnicodeHandle) {
+    libUnicodeHandle.close();
+  }
+  if (libLongNameHandle) {
+    libLongNameHandle.close();
+  }
+  if (libUnicodePDBHandle) {
+    libUnicodePDBHandle.close();
+  }
+  if (libNoPDBHandle) {
+    libNoPDBHandle.close();
+  }
+
+  return OS.File.remove(libUnicodeFile)
+  .then(() => OS.File.remove(libLongNameFile))
+  .then(() => PingServer.stop());
+});
+
+add_task({
+  skip_if: () => !AppConstants.MOZ_GECKO_PROFILER,
+}, function* test_send_ping() {
+  yield TelemetryController.testSetup();
+
+  let found = yield PingServer.promiseNextPing();
+  Assert.ok(!!found, "Telemetry ping submitted.");
+  Assert.strictEqual(found.type, "modules", "Ping type is 'modules'");
+  Assert.ok(found.environment, "'modules' ping has an environment.");
+  Assert.ok(!!found.clientId, "'modules' ping has a client ID.");
+  Assert.ok(!!found.payload.modules, "Telemetry ping payload contains the 'modules' array.");
+
+  for (let lib of expectedLibs) {
+    let test_lib = found.payload.modules.find(module => module.name === lib.name);
+
+    Assert.ok(!!test_lib, "There is a '" + lib.name + "' module.");
+
+    if (lib.version !== null) {
+      Assert.ok(test_lib.version.startsWith(lib.version), "The version of the " + lib.name + " module (" + test_lib.version + ") is correct (it starts with '" + lib.version + "').");
+    } else {
+      Assert.strictEqual(test_lib.version, null, "The version of the " + lib.name + " module is null.");
+    }
+
+    Assert.strictEqual(test_lib.debugName, lib.debugName, "The " + lib.name + " module has the correct debug name.");
+
+    if (lib.debugName === null) {
+      Assert.strictEqual(test_lib.debugID, null, "The " + lib.name + " module doesn't have a debug ID.");
+    } else {
+      Assert.greater(test_lib.debugID.length, 0, "The " + lib.name + " module has a debug ID.");
+    }
+  }
+
+  let test_lib = found.payload.modules.find(module => module.name === libLongName);
+  Assert.ok(!test_lib, "There isn't a '" + libLongName + "' module.");
+});
--- a/toolkit/components/telemetry/tests/unit/xpcshell.ini
+++ b/toolkit/components/telemetry/tests/unit/xpcshell.ini
@@ -9,16 +9,20 @@ support-files =
   dictionary.xpi
   experiment.xpi
   extension.xpi
   extension-2.xpi
   engine.xml
   system.xpi
   restartless.xpi
   theme.xpi
+  testUnicodePDB32.dll
+  testNoPDB32.dll
+  testUnicodePDB64.dll
+  testNoPDB64.dll
   !/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
 generated-files =
   dictionary.xpi
   experiment.xpi
   extension.xpi
   extension-2.xpi
   system.xpi
   restartless.xpi
@@ -58,10 +62,11 @@ tags = addons
 skip-if = os == "android" # Disabled due to crashes (see bug 1331366)
 [test_TelemetryReportingPolicy.js]
 tags = addons
 [test_TelemetryScalars.js]
 [test_TelemetryTimestamps.js]
 skip-if = toolkit == 'android'
 [test_TelemetryCaptureStack.js]
 [test_TelemetryEvents.js]
+[test_TelemetryModules.js]
 [test_PingSender.js]
 skip-if = (os == "android") || (os == "linux" && bits == 32)
--- a/tools/profiler/core/EHABIStackWalk.cpp
+++ b/tools/profiler/core/EHABIStackWalk.cpp
@@ -633,17 +633,17 @@ void EHAddrSpace::Update() {
     if (lib.GetOffset() != 0)
       // TODO: if it has a name, and we haven't seen a mapping of
       // offset 0 for that file, try opening it and reading the
       // headers instead.  The only thing I've seen so far that's
       // linked so as to need that treatment is the dynamic linker
       // itself.
       continue;
     EHTable tab(reinterpret_cast<const void *>(lib.GetStart()),
-              lib.GetEnd() - lib.GetStart(), lib.GetName());
+              lib.GetEnd() - lib.GetStart(), lib.GetNativeDebugName());
     if (tab.isValid())
       tables.push_back(tab);
   }
   space = new EHAddrSpace(tables);
 
   if (!sCurrent.compareExchange(nullptr, space)) {
     delete space;
     space = sCurrent;
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -883,44 +883,19 @@ NS_IMPL_ISUPPORTS(ProfileSaveEvent, nsIP
 
 static void
 AddSharedLibraryInfoToStream(std::ostream& aStream, const SharedLibrary& aLib)
 {
   aStream << "{";
   aStream << "\"start\":" << aLib.GetStart();
   aStream << ",\"end\":" << aLib.GetEnd();
   aStream << ",\"offset\":" << aLib.GetOffset();
-  aStream << ",\"name\":\"" << aLib.GetName() << "\"";
+  aStream << ",\"name\":\"" << aLib.GetNativeDebugName() << "\"";
   const std::string& breakpadId = aLib.GetBreakpadId();
   aStream << ",\"breakpadId\":\"" << breakpadId << "\"";
-
-#ifdef XP_WIN
-  // FIXME: remove this XP_WIN code when the profiler plugin has switched to
-  // using breakpadId.
-  std::string pdbSignature = breakpadId.substr(0, 32);
-  std::string pdbAgeStr = breakpadId.substr(32,  breakpadId.size() - 1);
-
-  std::stringstream stream;
-  stream << pdbAgeStr;
-
-  unsigned pdbAge;
-  stream << std::hex;
-  stream >> pdbAge;
-
-#ifdef DEBUG
-  std::ostringstream oStream;
-  oStream << pdbSignature << std::hex << std::uppercase << pdbAge;
-  MOZ_ASSERT(breakpadId == oStream.str());
-#endif
-
-  aStream << ",\"pdbSignature\":\"" << pdbSignature << "\"";
-  aStream << ",\"pdbAge\":" << pdbAge;
-  aStream << ",\"pdbName\":\"" << aLib.GetName() << "\"";
-#endif
-
   aStream << "}";
 }
 
 static std::string
 GetSharedLibraryInfoStringInternal()
 {
   SharedLibraryInfo info = SharedLibraryInfo::GetInfoForSelf();
   if (info.GetSize() == 0) {
--- a/tools/profiler/core/shared-libraries-linux.cc
+++ b/tools/profiler/core/shared-libraries-linux.cc
@@ -9,16 +9,18 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
 #include <limits.h>
 #include <unistd.h>
 #include <fstream>
 #include "platform.h"
 #include "shared-libraries.h"
+#include "mozilla/Unused.h"
+#include "nsNativeCharsetUtils.h"
 
 #include "common/linux/file_id.h"
 #include <algorithm>
 
 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
 
 // Get the breakpad Id for the binary file pointed by bin_name
 static std::string getId(const char *bin_name)
@@ -75,17 +77,21 @@ dl_iterate_callback(struct dl_phdr_info 
     unsigned long start = dl_info->dlpi_addr + dl_info->dlpi_phdr[i].p_vaddr;
     unsigned long end = start + dl_info->dlpi_phdr[i].p_memsz;
     if (start < libStart)
       libStart = start;
     if (end > libEnd)
       libEnd = end;
   }
   const char *name = dl_info->dlpi_name;
-  SharedLibrary shlib(libStart, libEnd, 0, getId(name), name);
+
+  nsAutoString nameStr;
+  mozilla::Unused << NS_WARN_IF(NS_FAILED(NS_CopyNativeToUnicode(nsDependentCString(name), nameStr)));
+
+  SharedLibrary shlib(libStart, libEnd, 0, getId(name), nameStr, nameStr, "");
   info.AddSharedLibrary(shlib);
 
   return 0;
 }
 
 #endif // !MOZ_WIDGET_GONK
 
 SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf()
@@ -141,17 +147,21 @@ SharedLibraryInfo SharedLibraryInfo::Get
 #else
     if (strcmp(perm, "r-xp") != 0) {
       // Ignore entries that are writable and/or shared.
       // At least one graphics driver uses short-lived "rwxs" mappings
       // (see bug 926734 comment 5), so just checking for 'x' isn't enough.
       continue;
     }
 #endif
-    SharedLibrary shlib(start, end, offset, getId(name), name);
+
+    nsAutoString nameStr;
+    mozilla::Unused << NS_WARN_IF(NS_FAILED(NS_CopyNativeToUnicode(nsDependentCString(name), nameStr)));
+
+    SharedLibrary shlib(start, end, offset, getId(name), nameStr, nameStr, "");
     info.AddSharedLibrary(shlib);
     if (count > 10000) {
       LOG("Get maps failed");
       break;
     }
     count++;
   }
 #endif // ANDROID || MOZ_WIDGET_GONK
--- a/tools/profiler/core/shared-libraries-macos.cc
+++ b/tools/profiler/core/shared-libraries-macos.cc
@@ -9,16 +9,18 @@
 #include <mach/task_info.h>
 #include <mach/task.h>
 #include <mach/mach_init.h>
 #include <mach/mach_traps.h>
 #include <string.h>
 #include <stdlib.h>
 #include <vector>
 #include <sstream>
+#include "mozilla/Unused.h"
+#include "nsNativeCharsetUtils.h"
 
 #include "shared-libraries.h"
 
 // Architecture specific abstraction.
 #ifdef __i386__
 typedef mach_header platform_mach_header;
 typedef segment_command mach_segment_command_type;
 #define MACHO_MAGIC_NUMBER MH_MAGIC
@@ -65,18 +67,21 @@ void addSharedLibrary(const platform_mac
   if (uuid_bytes != nullptr) {
     for (int i = 0; i < 16; ++i) {
       uuid << ((uuid_bytes[i] & 0xf0) >> 4);
       uuid << (uuid_bytes[i] & 0xf);
     }
     uuid << '0';
   }
 
+  nsAutoString nameStr;
+  mozilla::Unused << NS_WARN_IF(NS_FAILED(NS_CopyNativeToUnicode(nsDependentCString(name), nameStr)));
+
   info.AddSharedLibrary(SharedLibrary(start, start + size, 0, uuid.str(),
-                                      name));
+                                      nameStr, nameStr, ""));
 }
 
 // Use dyld to inspect the macho image information. We can build the SharedLibraryEntry structure
 // giving us roughtly the same info as /proc/PID/maps in Linux.
 SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf()
 {
   SharedLibraryInfo sharedLibraryInfo;
 
--- a/tools/profiler/core/shared-libraries-win32.cc
+++ b/tools/profiler/core/shared-libraries-win32.cc
@@ -1,28 +1,32 @@
 /* -*- 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/. */
 
 #include <windows.h>
-#include <tlhelp32.h>
 #include <dbghelp.h>
 #include <sstream>
+#include <psapi.h>
 
 #include "shared-libraries.h"
 #include "nsWindowsHelpers.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "nsNativeCharsetUtils.h"
 
 #define CV_SIGNATURE 0x53445352 // 'SDSR'
 
 struct CodeViewRecord70
 {
   uint32_t signature;
   GUID pdbSignature;
   uint32_t pdbAge;
+  // A UTF-8 string, according to https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/dbi/locator.cpp#L785
   char pdbFileName[1];
 };
 
 static bool GetPdbInfo(uintptr_t aStart, nsID& aSignature, uint32_t& aAge, char** aPdbName)
 {
   if (!aStart) {
     return false;
   }
@@ -60,78 +64,138 @@ static bool GetPdbInfo(uintptr_t aStart,
   GUID& pdbSignature = debugInfo->pdbSignature;
   aSignature.m0 = pdbSignature.Data1;
   aSignature.m1 = pdbSignature.Data2;
   aSignature.m2 = pdbSignature.Data3;
   memcpy(aSignature.m3, pdbSignature.Data4, sizeof(pdbSignature.Data4));
 
   // The PDB file name could be different from module filename, so report both
   // e.g. The PDB for C:\Windows\SysWOW64\ntdll.dll is wntdll.pdb
-  char * leafName = strrchr(debugInfo->pdbFileName, '\\');
-  if (leafName) {
-    // Only report the file portion of the path
-    *aPdbName = leafName + 1;
-  } else {
-    *aPdbName = debugInfo->pdbFileName;
-  }
+  *aPdbName = debugInfo->pdbFileName;
 
   return true;
 }
 
 static bool IsDashOrBraces(char c)
 {
   return c == '-' || c == '{' || c == '}';
 }
 
+std::string GetVersion(WCHAR* dllPath)
+{
+  DWORD infoSize = GetFileVersionInfoSizeW(dllPath, nullptr);
+  if (infoSize == 0) {
+    return "";
+  }
+
+  mozilla::UniquePtr<unsigned char[]> infoData = mozilla::MakeUnique<unsigned char[]>(infoSize);
+  if (!GetFileVersionInfoW(dllPath, 0, infoSize, infoData.get())) {
+    return "";
+  }
+
+  VS_FIXEDFILEINFO* vInfo;
+  UINT vInfoLen;
+  if (!VerQueryValueW(infoData.get(), L"\\", (LPVOID*)&vInfo, &vInfoLen)) {
+    return "";
+  }
+  if (!vInfo) {
+    return "";
+  }
+
+  std::ostringstream stream;
+  stream << (vInfo->dwFileVersionMS >> 16)    << "."
+         << (vInfo->dwFileVersionMS & 0xFFFF) << "."
+         << (vInfo->dwFileVersionLS >> 16)    << "."
+         << (vInfo->dwFileVersionLS & 0xFFFF);
+
+  return stream.str();
+}
+
 SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf()
 {
   SharedLibraryInfo sharedLibraryInfo;
 
-  nsAutoHandle snap(CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId()));
+  HANDLE hProcess = GetCurrentProcess();
+  mozilla::UniquePtr<HMODULE[]> hMods;
+  size_t modulesNum = 0;
+  if (hProcess != NULL) {
+    DWORD modulesSize;
+    if (!EnumProcessModules(hProcess, nullptr, 0, &modulesSize)) {
+      return sharedLibraryInfo;
+    }
+    modulesNum = modulesSize / sizeof(HMODULE);
+    hMods = mozilla::MakeUnique<HMODULE[]>(modulesNum);
+    if (!EnumProcessModules(hProcess, hMods.get(), modulesNum * sizeof(HMODULE), &modulesSize)) {
+      return sharedLibraryInfo;
+    }
+  }
 
-  MODULEENTRY32 module = {0};
-  module.dwSize = sizeof(MODULEENTRY32);
-  if (Module32First(snap, &module)) {
-    do {
-      nsID pdbSig;
-      uint32_t pdbAge;
-      char *pdbName = NULL;
+  for (unsigned int i = 0; i <= modulesNum; i++) {
+    nsID pdbSig;
+    uint32_t pdbAge;
+    nsAutoString pdbNameStr;
+    char *pdbName = NULL;
+    std::string breakpadId;
+    WCHAR modulePath[MAX_PATH + 1];
+
+    if (!GetModuleFileNameEx(hProcess, hMods[i], modulePath, sizeof(modulePath) / sizeof(WCHAR))) {
+      continue;
+    }
+
+    MODULEINFO module = {0};
+    if (!GetModuleInformation(hProcess, hMods[i], &module, sizeof(MODULEINFO))) {
+      continue;
+    }
 
-      // Load the module again to make sure that its handle will remain remain
-      // valid as we attempt to read the PDB information from it.  We load the
-      // DLL as a datafile so that if the module actually gets unloaded between
-      // the call to Module32Next and the following LoadLibraryEx, we don't end
-      // up running the now newly loaded module's DllMain function.  If the
-      // module is already loaded, LoadLibraryEx just increments its refcount.
-      //
-      // Note that because of the race condition above, merely loading the DLL
-      // again is not safe enough, therefore we also need to make sure that we
-      // can read the memory mapped at the base address before we can safely
-      // proceed to actually access those pages.
-      HMODULE handleLock = LoadLibraryEx(module.szExePath, NULL, LOAD_LIBRARY_AS_DATAFILE);
-      MEMORY_BASIC_INFORMATION vmemInfo = {0};
-      if (handleLock &&
-          sizeof(vmemInfo) == VirtualQuery(module.modBaseAddr, &vmemInfo, sizeof(vmemInfo)) &&
-          vmemInfo.State == MEM_COMMIT &&
-          GetPdbInfo((uintptr_t)module.modBaseAddr, pdbSig, pdbAge, &pdbName)) {
-        std::ostringstream stream;
-        stream << pdbSig.ToString() << std::hex << pdbAge;
-        std::string breakpadId = stream.str();
-        std::string::iterator end =
-          std::remove_if(breakpadId.begin(), breakpadId.end(), IsDashOrBraces);
-        breakpadId.erase(end, breakpadId.end());
-        std::transform(breakpadId.begin(), breakpadId.end(),
-                       breakpadId.begin(), toupper);
+    // Load the module again to make sure that its handle will remain remain
+    // valid as we attempt to read the PDB information from it.  We load the
+    // DLL as a datafile so that if the module actually gets unloaded between
+    // the call to EnumProcessModules and the following LoadLibraryEx, we don't
+    // end up running the now newly loaded module's DllMain function.  If the
+    // module is already loaded, LoadLibraryEx just increments its refcount.
+    //
+    // Note that because of the race condition above, merely loading the DLL
+    // again is not safe enough, therefore we also need to make sure that we
+    // can read the memory mapped at the base address before we can safely
+    // proceed to actually access those pages.
+    HMODULE handleLock = LoadLibraryEx(modulePath, NULL, LOAD_LIBRARY_AS_DATAFILE);
+    MEMORY_BASIC_INFORMATION vmemInfo = { 0 };
+    if (handleLock &&
+      sizeof(vmemInfo) == VirtualQuery(module.lpBaseOfDll, &vmemInfo, sizeof(vmemInfo)) &&
+      vmemInfo.State == MEM_COMMIT &&
+      GetPdbInfo((uintptr_t)module.lpBaseOfDll, pdbSig, pdbAge, &pdbName)) {
+      std::ostringstream stream;
+      stream << pdbSig.ToString() << std::hex << pdbAge;
+      breakpadId = stream.str();
+      std::string::iterator end =
+        std::remove_if(breakpadId.begin(), breakpadId.end(), IsDashOrBraces);
+      breakpadId.erase(end, breakpadId.end());
+      std::transform(breakpadId.begin(), breakpadId.end(),
+        breakpadId.begin(), toupper);
 
-        SharedLibrary shlib((uintptr_t)module.modBaseAddr,
-                            (uintptr_t)module.modBaseAddr+module.modBaseSize,
-                            0, // DLLs are always mapped at offset 0 on Windows
-                            breakpadId,
-                            pdbName);
-        sharedLibraryInfo.AddSharedLibrary(shlib);
+      pdbNameStr = NS_ConvertUTF8toUTF16(pdbName);
+      int32_t pos = pdbNameStr.RFindChar('\\');
+      if (pos != kNotFound) {
+        pdbNameStr.Cut(0, pos + 1);
       }
-      FreeLibrary(handleLock); // ok to free null handles
-    } while (Module32Next(snap, &module));
+    }
+
+    nsAutoString moduleName(modulePath);
+    int32_t pos = moduleName.RFindChar('\\');
+    if (pos != kNotFound) {
+      moduleName.Cut(0, pos + 1);
+    }
+
+    SharedLibrary shlib((uintptr_t)module.lpBaseOfDll,
+      (uintptr_t)module.lpBaseOfDll + module.SizeOfImage,
+      0, // DLLs are always mapped at offset 0 on Windows
+      breakpadId,
+      moduleName,
+      pdbNameStr,
+      GetVersion(modulePath));
+    sharedLibraryInfo.AddSharedLibrary(shlib);
+
+    FreeLibrary(handleLock); // ok to free null handles
   }
 
   return sharedLibraryInfo;
 }
 
--- a/tools/profiler/lul/platform-linux-lul.cpp
+++ b/tools/profiler/lul/platform-linux-lul.cpp
@@ -27,33 +27,35 @@ read_procmaps(lul::LUL* aLUL)
   MOZ_ASSERT(aLUL->CountMappings() == 0);
 
 # if defined(SPS_OS_linux) || defined(SPS_OS_android) || defined(SPS_OS_darwin)
   SharedLibraryInfo info = SharedLibraryInfo::GetInfoForSelf();
 
   for (size_t i = 0; i < info.GetSize(); i++) {
     const SharedLibrary& lib = info.GetEntry(i);
 
+    std::string nativeName = lib.GetNativeDebugName();
+
 #   if defined(USE_FAULTY_LIB)
     // We're using faulty.lib.  Use a special-case object mapper.
     AutoObjectMapperFaultyLib mapper(aLUL->mLog);
 #   else
     // We can use the standard POSIX-based mapper.
     AutoObjectMapperPOSIX mapper(aLUL->mLog);
 #   endif
 
     // Ask |mapper| to map the object.  Then hand its mapped address
     // to NotifyAfterMap().
     void*  image = nullptr;
     size_t size  = 0;
-    bool ok = mapper.Map(&image, &size, lib.GetName());
+    bool ok = mapper.Map(&image, &size, nativeName);
     if (ok && image && size > 0) {
       aLUL->NotifyAfterMap(lib.GetStart(), lib.GetEnd()-lib.GetStart(),
-                           lib.GetName().c_str(), image);
-    } else if (!ok && lib.GetName() == "") {
+                           nativeName.c_str(), image);
+    } else if (!ok && lib.GetDebugName().IsEmpty()) {
       // The object has no name and (as a consequence) the mapper
       // failed to map it.  This happens on Linux, where
       // GetInfoForSelf() produces two such mappings: one for the
       // executable and one for the VDSO.  The executable one isn't a
       // big deal since there's not much interesting code in there,
       // but the VDSO one is a problem on x86-{linux,android} because
       // lack of knowledge about the mapped area inhibits LUL's
       // special __kernel_syscall handling.  Hence notify |aLUL| at
--- a/tools/profiler/public/shared-libraries.h
+++ b/tools/profiler/public/shared-libraries.h
@@ -12,76 +12,99 @@
 #endif
 
 #include <algorithm>
 #include <vector>
 #include <string>
 #include <stdlib.h>
 #include <stdint.h>
 #include <nsID.h>
+#include "nsString.h"
+#include "nsNativeCharsetUtils.h"
 
 class SharedLibrary {
 public:
 
   SharedLibrary(uintptr_t aStart,
                 uintptr_t aEnd,
                 uintptr_t aOffset,
                 const std::string& aBreakpadId,
-                const std::string& aName)
+                const nsString& aName,
+                const nsString& aDebugName,
+                const std::string& aVersion)
     : mStart(aStart)
     , mEnd(aEnd)
     , mOffset(aOffset)
     , mBreakpadId(aBreakpadId)
     , mName(aName)
+    , mDebugName(aDebugName)
+    , mVersion(aVersion)
   {}
 
   SharedLibrary(const SharedLibrary& aEntry)
     : mStart(aEntry.mStart)
     , mEnd(aEntry.mEnd)
     , mOffset(aEntry.mOffset)
     , mBreakpadId(aEntry.mBreakpadId)
     , mName(aEntry.mName)
+    , mDebugName(aEntry.mDebugName)
+    , mVersion(aEntry.mVersion)
   {}
 
   SharedLibrary& operator=(const SharedLibrary& aEntry)
   {
     // Gracefully handle self assignment
     if (this == &aEntry) return *this;
 
     mStart = aEntry.mStart;
     mEnd = aEntry.mEnd;
     mOffset = aEntry.mOffset;
     mBreakpadId = aEntry.mBreakpadId;
     mName = aEntry.mName;
+    mDebugName = aEntry.mDebugName;
+    mVersion = aEntry.mVersion;
     return *this;
   }
 
   bool operator==(const SharedLibrary& other) const
   {
     return (mStart == other.mStart) &&
            (mEnd == other.mEnd) &&
            (mOffset == other.mOffset) &&
            (mName == other.mName) &&
-           (mBreakpadId == other.mBreakpadId);
+           (mDebugName == other.mDebugName) &&
+           (mBreakpadId == other.mBreakpadId) &&
+           (mVersion == other.mVersion);
   }
 
   uintptr_t GetStart() const { return mStart; }
   uintptr_t GetEnd() const { return mEnd; }
   uintptr_t GetOffset() const { return mOffset; }
   const std::string &GetBreakpadId() const { return mBreakpadId; }
-  const std::string &GetName() const { return mName; }
+  const nsString &GetName() const { return mName; }
+  const std::string GetNativeDebugName() const {
+    nsAutoCString debugNameStr;
+
+    NS_CopyUnicodeToNative(mDebugName, debugNameStr);
+
+    return debugNameStr.get();
+  }
+  const nsString &GetDebugName() const { return mDebugName; }
+  const std::string &GetVersion() const { return mVersion; }
 
 private:
   SharedLibrary() {}
 
   uintptr_t mStart;
   uintptr_t mEnd;
   uintptr_t mOffset;
   std::string mBreakpadId;
-  std::string mName;
+  nsString mName;
+  nsString mDebugName;
+  std::string mVersion;
 };
 
 static bool
 CompareAddresses(const SharedLibrary& first, const SharedLibrary& second)
 {
   return first.GetStart() < second.GetStart();
 }