Bug 1023941 - Part 5: Loader hook to redirect the missing import. r=bsmedberg
authorDavid Major <dmajor@mozilla.com>
Thu, 28 Aug 2014 14:53:38 +1200
changeset 223737 1910714b56c64101bdd58e518c43e9248c5ed461
parent 223736 3c59642f6445c2262562d82f20de24ce022873d3
child 223738 b276ce8752758dac03c664093192c796cdecebb9
push id3979
push userraliiev@mozilla.com
push dateMon, 13 Oct 2014 16:35:44 +0000
treeherdermozilla-beta@30f2cc610691 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbsmedberg
bugs1023941
milestone34.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 1023941 - Part 5: Loader hook to redirect the missing import. r=bsmedberg
b2g/app/nsBrowserApp.cpp
ipc/ipdl/test/cxx/app/TestIPDL.cpp
js/xpconnect/shell/xpcshell.cpp
toolkit/xre/WindowsCrtPatch.h
toolkit/xre/nsWindowsWMain.cpp
xulrunner/app/nsXULRunnerApp.cpp
--- a/b2g/app/nsBrowserApp.cpp
+++ b/b2g/app/nsBrowserApp.cpp
@@ -20,16 +20,17 @@
 #include <string.h>
 
 #include "nsCOMPtr.h"
 #include "nsIFile.h"
 #include "nsStringGlue.h"
 
 #ifdef XP_WIN
 // we want a wmain entry point
+#define XRE_DONT_SUPPORT_XPSP2 // See https://bugzil.la/1023941#c32
 #include "nsWindowsWMain.cpp"
 #define snprintf _snprintf
 #define strcasecmp _stricmp
 #endif
 
 #ifdef MOZ_WIDGET_GONK
 #include "GonkDisplay.h"
 #endif
--- a/ipc/ipdl/test/cxx/app/TestIPDL.cpp
+++ b/ipc/ipdl/test/cxx/app/TestIPDL.cpp
@@ -1,16 +1,17 @@
 /* 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 "nsXULAppAPI.h"
 
 #if defined(XP_WIN)
 #include <windows.h>
+#define XRE_DONT_SUPPORT_XPSP2 // this app doesn't ship
 #include "nsWindowsWMain.cpp"
 #endif
 
 int
 main(int argc, char** argv)
 {
     // the first argument specifies which IPDL test case/suite to load
     if (argc < 2)
--- a/js/xpconnect/shell/xpcshell.cpp
+++ b/js/xpconnect/shell/xpcshell.cpp
@@ -15,16 +15,17 @@
 #include "xpcshellMacUtils.h"
 #endif
 #ifdef XP_WIN
 #include <windows.h>
 #include <shlobj.h>
 
 // we want a wmain entry point
 #define XRE_DONT_PROTECT_DLL_LOAD
+#define XRE_DONT_SUPPORT_XPSP2 // xpcshell does not ship
 #define XRE_WANT_ENVIRON
 #include "nsWindowsWMain.cpp"
 #endif
 
 int
 main(int argc, char** argv, char** envp)
 {
 #ifdef XP_MACOSX
new file mode 100644
--- /dev/null
+++ b/toolkit/xre/WindowsCrtPatch.h
@@ -0,0 +1,146 @@
+/* -*- Mode: C++; tab-width: 40; 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/. */
+
+/*
+ * This file works around an incompatibility between Visual Studio 2013's
+ * C Runtime DLL and Windows XP Service Pack 2.
+ *
+ * On XP SP2, msvcr120.dll fails to load, because it has a load-time dependency
+ * on a kernel32 export named GetLogicalProcessorInformation, which is only
+ * available in XP SP3 and newer. Microsoft has declared this to be by design.
+ * See: https://connect.microsoft.com/VisualStudio/feedback/details/811379/
+ *
+ * The CRT calls GetLogicalProcessorInformation only from the concurrency
+ * runtime, which our code does not use. A potential workaround is to 
+ * static-link the CRT into all of our binaries and let the linker drop the
+ * unused API calls. We don't want to take that approach, due to concerns
+ * about binary bloat and jemalloc integration.
+ *
+ * Instead we hook the Windows loader and patch out the missing import.
+ * We intercept ntdll!RtlImageNtHeader, which is a helper API called during
+ * the DLL loading process. We walk the PE image and redirect the
+ * GetLogicalProcessorInformation import to something benign like DebugBreak,
+ * before the loader populates msvcr120.dll's import table.
+ *
+ * This is a fragile hack that only works if we can set up the hook before
+ * Windows tries to load msvcr120.dll. This means that all .exe files:
+ *  1) must static-link the CRT
+ *  2) must delay-load anything with ties to msvcr120.dll (e.g. mozglue.dll)
+ *  3) must not call malloc, because the linker would substitute our mozglue
+ *     replacements, which leads to the CRT loading mozglue before main.
+ * The remainder of our binaries can continue to dynamic-link the CRT.
+ * Assertions enforce that our hooks are installed before msvcr120.dll.
+ */
+
+#ifndef WindowsCrtPatch_h
+#define WindowsCrtPatch_h
+
+#include "nsWindowsDllInterceptor.h"
+#include "mozilla/WindowsVersion.h"
+
+namespace WindowsCrtPatch {
+
+mozilla::WindowsDllInterceptor NtdllIntercept;
+
+typedef PIMAGE_NT_HEADERS (NTAPI *RtlImageNtHeader_func)(HMODULE module);
+static RtlImageNtHeader_func stub_RtlImageNtHeader = 0;
+
+// A helper to simplify the use of Relative Virtual Addresses.
+template <typename T>
+class RVAPtr
+{
+public:
+  RVAPtr(HMODULE module, size_t rva)
+    : _ptr(reinterpret_cast<T*>(reinterpret_cast<char*>(module) + rva)) {}
+  operator T*() { return _ptr; }
+  T* operator ->() { return _ptr; }
+  T* operator ++() { return ++_ptr; }
+
+private:
+  T* _ptr;
+};
+
+void
+PatchModuleImports(HMODULE module, PIMAGE_NT_HEADERS headers)
+{
+  static const WORD MAGIC_DOS = 0x5a4d; // "MZ"
+  static const DWORD MAGIC_PE = 0x4550; // "PE\0\0"
+  RVAPtr<IMAGE_DOS_HEADER> dosStub(module, 0);
+
+  if (!module ||
+      !headers ||
+      dosStub->e_magic != MAGIC_DOS ||
+      headers != RVAPtr<IMAGE_NT_HEADERS>(module, dosStub->e_lfanew) ||
+      headers->Signature != MAGIC_PE ||
+      headers->FileHeader.SizeOfOptionalHeader < sizeof(IMAGE_OPTIONAL_HEADER)) {
+    return;
+  }
+
+  // The format of the import directory is described in:
+  // "An In-Depth Look into the Win32 Portable Executable File Format, Part 2"
+  // http://msdn.microsoft.com/en-us/magazine/cc301808.aspx
+
+  IMAGE_DATA_DIRECTORY* importDirectory =
+    &headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
+  RVAPtr<IMAGE_IMPORT_DESCRIPTOR> descriptor(module, importDirectory->VirtualAddress);
+
+  for (; descriptor->OriginalFirstThunk; ++descriptor) {
+    RVAPtr<char> importedModule(module, descriptor->Name);
+    if (!stricmp(importedModule, "kernel32.dll")) {
+      RVAPtr<IMAGE_THUNK_DATA> thunk(module, descriptor->OriginalFirstThunk);
+      for (; thunk->u1.AddressOfData; ++thunk) {
+        RVAPtr<IMAGE_IMPORT_BY_NAME> import(module, thunk->u1.AddressOfData);
+        if (!strcmp(import->Name, "GetLogicalProcessorInformation")) {
+          memcpy(import->Name, "DebugBreak", sizeof("DebugBreak"));
+        }
+      }
+    }
+  }
+}
+
+PIMAGE_NT_HEADERS NTAPI
+patched_RtlImageNtHeader(HMODULE module)
+{
+  PIMAGE_NT_HEADERS headers = stub_RtlImageNtHeader(module);
+
+  if (module == GetModuleHandleA("msvcr120.dll")) {
+    PatchModuleImports(module, headers);
+  }
+
+  return headers;
+}
+
+// Non-inline to make the asserts stand out
+MOZ_NEVER_INLINE void
+Init()
+{
+  // If the C Runtime DLL is already loaded, our hooks will be ineffective,
+  // and we will fail to load on XP SP2 when built with Visual Studio 2013.
+  // We assert the absence of these modules on all Windows builds in order to
+  // catch breakage faster.
+  //
+  // If these assertions fail, see the comment at the top of this file for
+  // possible causes. Any changes to the lines below MUST be tested on XP SP2!
+  MOZ_ASSERT(!GetModuleHandleA("mozglue.dll"));
+  MOZ_ASSERT(!GetModuleHandleA("msvcr120.dll"));
+  MOZ_ASSERT(!GetModuleHandleA("msvcr120d.dll"));
+
+  // Temporary until we fully switch over to VS 2013:
+  MOZ_ASSERT(!GetModuleHandleA("msvcr100.dll"));
+  MOZ_ASSERT(!GetModuleHandleA("msvcr100d.dll"));
+
+#if defined(_M_IX86) && defined(_MSC_VER) && _MSC_VER >= 1800
+  if (!mozilla::IsXPSP3OrLater()) {
+    NtdllIntercept.Init("ntdll.dll");
+    NtdllIntercept.AddHook("RtlImageNtHeader",
+                           reinterpret_cast<intptr_t>(patched_RtlImageNtHeader),
+                           reinterpret_cast<void**>(&stub_RtlImageNtHeader));
+  }
+#endif
+}
+
+} // namespace WindowsCrtPatch
+
+#endif // WindowsCrtPatch_h
--- a/toolkit/xre/nsWindowsWMain.cpp
+++ b/toolkit/xre/nsWindowsWMain.cpp
@@ -11,16 +11,20 @@
 #endif
 
 #include "nsUTF8Utils.h"
 
 #ifndef XRE_DONT_PROTECT_DLL_LOAD
 #include "nsSetDllDirectory.h"
 #endif
 
+#ifndef XRE_DONT_SUPPORT_XPSP2
+#include "WindowsCrtPatch.h"
+#endif
+
 #ifdef __MINGW32__
 
 /* MingW currently does not implement a wide version of the
    startup routines.  Workaround is to implement something like
    it ourselves.  See bug 411826 */
 
 #include <shellapi.h>
 
@@ -71,16 +75,20 @@ FreeAllocStrings(int argc, char **argv)
     delete [] argv[argc];
   }
 
   delete [] argv;
 }
 
 int wmain(int argc, WCHAR **argv)
 {
+#ifndef XRE_DONT_SUPPORT_XPSP2
+  WindowsCrtPatch::Init();
+#endif
+
 #ifndef XRE_DONT_PROTECT_DLL_LOAD
   mozilla::SanitizeEnvironmentVariables();
   SetDllDirectoryW(L"");
 #endif
 
   char **argvConverted = new char*[argc + 1];
   if (!argvConverted)
     return 127;
--- a/xulrunner/app/nsXULRunnerApp.cpp
+++ b/xulrunner/app/nsXULRunnerApp.cpp
@@ -20,16 +20,17 @@
 #include "nsStringAPI.h"
 #include "nsServiceManagerUtils.h"
 #include "plstr.h"
 #include "prprf.h"
 #include "prenv.h"
 #include "nsINIParser.h"
 
 #ifdef XP_WIN
+#define XRE_DONT_SUPPORT_XPSP2 // See https://bugzil.la/1023941#c32
 #include "nsWindowsWMain.cpp"
 #endif
 
 #include "BinaryPath.h"
 
 #include "nsXPCOMPrivate.h" // for MAXPATHLEN and XPCOM_DLL
 
 using namespace mozilla;