Bug 769048 part E - Core crashreporter support for injecting a crashreporter DLL into an arbitrary process and callbacks for notifications when that process has crashed, r=ehsan
authorBenjamin Smedberg <benjamin@smedbergs.us>
Mon, 02 Jul 2012 14:55:23 -0400
changeset 98192 52b93da290236f84bf62bac550eb07a4e359be01
parent 98191 6990667ebd08aa06536c93ed7b0a4e42c187fab1
child 98193 1d370ca5bb8dcf173889ff14659560412996b748
push id11405
push usereakhgari@mozilla.com
push dateTue, 03 Jul 2012 15:14:26 +0000
treeherdermozilla-inbound@d17fc82f43ed [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan
bugs769048
milestone16.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 769048 part E - Core crashreporter support for injecting a crashreporter DLL into an arbitrary process and callbacks for notifications when that process has crashed, r=ehsan
toolkit/crashreporter/InjectCrashReporter.cpp
toolkit/crashreporter/InjectCrashReporter.h
toolkit/crashreporter/Makefile.in
toolkit/crashreporter/nsExceptionHandler.cpp
toolkit/crashreporter/nsExceptionHandler.h
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/InjectCrashReporter.cpp
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 8; 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 "InjectCrashReporter.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "client/windows/crash_generation/crash_generation_client.h"
+#include "nsExceptionHandler.h"
+#include "LoadLibraryRemote.h"
+#include "nsWindowsHelpers.h"
+
+using google_breakpad::CrashGenerationClient;
+using CrashReporter::GetChildNotificationPipe;
+
+namespace mozilla {
+
+InjectCrashRunnable::InjectCrashRunnable(DWORD pid)
+  : mPID(pid)
+{
+  nsCOMPtr<nsIFile> dll;
+  nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(dll));
+  if (NS_SUCCEEDED(rv)) {
+    dll->Append(NS_LITERAL_STRING("breakpadinjector.dll"));
+    dll->GetPath(mInjectorPath);
+  }
+}
+
+NS_IMETHODIMP
+InjectCrashRunnable::Run()
+{
+  if (mInjectorPath.IsEmpty())
+    return NS_OK;
+
+  nsAutoHandle hProcess(
+    OpenProcess(PROCESS_CREATE_THREAD |
+                PROCESS_QUERY_INFORMATION |
+                PROCESS_DUP_HANDLE |
+                PROCESS_VM_OPERATION |
+                PROCESS_VM_WRITE |
+                PROCESS_VM_READ, FALSE, mPID));
+  if (!hProcess) {
+    NS_WARNING("Unable to open remote process handle for crashreporter injection.");
+    return NS_OK;
+  }
+
+  void* proc = LoadRemoteLibraryAndGetAddress(hProcess, mInjectorPath.get(),
+                                              "Start");
+  if (!proc) {
+    NS_WARNING("Unable to inject crashreporter DLL.");
+    return NS_OK;
+  }
+
+  HANDLE hRemotePipe =
+    CrashGenerationClient::DuplicatePipeToClientProcess(
+      NS_ConvertASCIItoUTF16(GetChildNotificationPipe()).get(),
+      hProcess);
+  if (INVALID_HANDLE_VALUE == hRemotePipe) {
+    NS_WARNING("Unable to duplicate crash reporter pipe to process.");
+    return NS_OK;
+  }
+
+  nsAutoHandle hThread(CreateRemoteThread(hProcess, NULL, 0,
+                                          (LPTHREAD_START_ROUTINE) proc,
+                                          (void*) hRemotePipe, 0, NULL));
+  if (!hThread) {
+    NS_WARNING("Unable to CreateRemoteThread");
+
+    // We have to close the remote pipe or else our crash generation client
+    // will be stuck unable to accept other remote requests.
+    HANDLE toClose = INVALID_HANDLE_VALUE;
+    if (DuplicateHandle(hProcess, hRemotePipe, ::GetCurrentProcess(),
+                        &toClose, 0, FALSE,
+                        DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {
+      CloseHandle(toClose);
+      return NS_OK;
+    }
+  }
+
+  return NS_OK;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/InjectCrashReporter.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; 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 "nsThreadUtils.h"
+#include <windows.h>
+
+namespace mozilla {
+
+class InjectCrashRunnable : public nsRunnable
+{
+public:
+  InjectCrashRunnable(DWORD pid);
+
+  NS_IMETHOD Run();
+
+private:
+  DWORD mPID;
+  nsString mInjectorPath;
+};
+  
+} // Namespace mozilla
--- a/toolkit/crashreporter/Makefile.in
+++ b/toolkit/crashreporter/Makefile.in
@@ -85,16 +85,17 @@ EXPORTS = \
 
 CPPSRCS = \
 	nsExceptionHandler.cpp \
 	$(NULL)
 
 ifdef MOZ_CRASHREPORTER_INJECTOR
 CPPSRCS += \
   LoadLibraryRemote.cpp \
+  InjectCrashReporter.cpp \
   $(NULL)
 endif
 
 FORCE_STATIC_LIB = 1
 
 EXTRA_JS_MODULES = \
   CrashSubmit.jsm \
   $(NULL)
--- a/toolkit/crashreporter/nsExceptionHandler.cpp
+++ b/toolkit/crashreporter/nsExceptionHandler.cpp
@@ -57,16 +57,21 @@
 #include "client/solaris/handler/exception_handler.h"
 #include <fcntl.h>
 #include <sys/types.h>
 #include <unistd.h>
 #else
 #error "Not yet implemented for this platform"
 #endif // defined(XP_WIN32)
 
+#ifdef MOZ_CRASHREPORTER_INJECTOR
+#include "InjectCrashReporter.h"
+using mozilla::InjectCrashRunnable;
+#endif
+
 #include <stdlib.h>
 #include <time.h>
 #include <prenv.h>
 #include <prio.h>
 #include <prmem.h>
 #include "mozilla/Mutex.h"
 #include "nsDebug.h"
 #include "nsCRT.h"
@@ -213,16 +218,33 @@ static const int kMagicChildCrashReportF
 
 #  endif
 
 // |dumpMapLock| must protect all access to |pidToMinidump|.
 static Mutex* dumpMapLock;
 typedef nsInterfaceHashtable<nsUint32HashKey, nsIFile> ChildMinidumpMap;
 static ChildMinidumpMap* pidToMinidump;
 
+#ifdef MOZ_CRASHREPORTER_INJECTOR
+static nsIThread* sInjectorThread;
+typedef nsDataHashtable<nsUint32HashKey, InjectorCrashCallback*> InjectorPIDMap;
+static InjectorPIDMap* pidToInjectorCallback;
+
+class ReportInjectedCrash : public nsRunnable
+{
+public:
+  ReportInjectedCrash(PRUint32 pid) : mPID(pid) { }
+
+  NS_IMETHOD Run();
+
+private:
+  PRUint32 mPID;
+};
+#endif // MOZ_CRASHREPORTER_INJECTOR
+
 // Crashreporter annotations that we don't send along in subprocess
 // reports
 static const char* kSubprocessBlacklist[] = {
   "FramePoisonBase",
   "FramePoisonSize",
   "StartupTime",
   "URL"
 };
@@ -1897,18 +1919,23 @@ OnChildProcessDumpRequested(void* aConte
   {
     PRUint32 pid =
 #ifdef XP_MACOSX
       aClientInfo.pid();
 #else
       aClientInfo->pid();
 #endif
 
-    MutexAutoLock lock(*dumpMapLock);
-    pidToMinidump->Put(pid, minidump);
+    {
+      MutexAutoLock lock(*dumpMapLock);
+      pidToMinidump->Put(pid, minidump);
+    }
+#ifdef MOZ_CRASHREPORTER_INJECTOR
+    NS_DispatchToMainThread(new ReportInjectedCrash(pid));
+#endif
   }
 }
 
 static bool
 OOPInitialized()
 {
   return pidToMinidump != NULL;
 }
@@ -1981,16 +2008,26 @@ OOPInit()
 static void
 OOPDeinit()
 {
   if (!OOPInitialized()) {
     NS_WARNING("OOPDeinit() without successful OOPInit()");
     return;
   }
 
+#ifdef MOZ_CRASHREPORTER_INJECTOR
+  if (sInjectorThread) {
+    sInjectorThread->Shutdown();
+    NS_RELEASE(sInjectorThread);
+  }
+
+  delete pidToInjectorCallback;
+  pidToInjectorCallback = NULL;
+#endif
+
   delete crashServer;
   crashServer = NULL;
 
   delete dumpMapLock;
   dumpMapLock = NULL;
 
   delete pidToMinidump;
   pidToMinidump = NULL;
@@ -2011,16 +2048,77 @@ GetChildNotificationPipe()
 
   if (!OOPInitialized())
     OOPInit();
 
   return childCrashNotifyPipe;
 }
 #endif
 
+#ifdef MOZ_CRASHREPORTER_INJECTOR
+void
+InjectCrashReporterIntoProcess(DWORD processID, InjectorCrashCallback* cb)
+{
+  if (!GetEnabled())
+    return;
+
+  if (!OOPInitialized())
+    OOPInit();
+
+  if (!pidToInjectorCallback) {
+    pidToInjectorCallback = new InjectorPIDMap;
+    pidToInjectorCallback->Init();
+  }
+
+  if (!sInjectorThread) {
+    if (NS_FAILED(NS_NewThread(&sInjectorThread)))
+      return;
+  }
+
+  pidToInjectorCallback->Put(processID, cb);
+
+  nsCOMPtr<nsIRunnable> r = new InjectCrashRunnable(processID);
+  sInjectorThread->Dispatch(r, nsIEventTarget::DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+ReportInjectedCrash::Run()
+{
+  // Crash reporting may have been disabled after this method was dispatched
+  if (!pidToInjectorCallback)
+    return NS_OK;
+
+  InjectorCrashCallback* cb = pidToInjectorCallback->Get(mPID);
+  if (!cb)
+    return NS_OK;
+
+  nsCOMPtr<nsIFile> minidump;
+  if (!TakeMinidumpForChild(mPID, getter_AddRefs(minidump))) {
+    NS_WARNING("No minidump for crash notification.");
+    return NS_OK;
+  }
+
+  nsString id;
+  GetIDFromMinidump(minidump, id);
+
+  cb->OnCrash(mPID, id);
+  return NS_OK;
+}
+
+void
+UnregisterInjectorCallback(DWORD processID)
+{
+  if (!OOPInitialized())
+    return;
+
+  pidToInjectorCallback->Remove(processID);
+}
+
+#endif // MOZ_CRASHREPORTER_INJECTOR
+
 #if defined(XP_WIN)
 // Child-side API
 bool
 SetRemoteExceptionHandler(const nsACString& crashPipe)
 {
   // crash reporting is disabled
   if (crashPipe.Equals(kNullNotifyPipe))
     return true;
--- a/toolkit/crashreporter/nsExceptionHandler.h
+++ b/toolkit/crashreporter/nsExceptionHandler.h
@@ -106,16 +106,32 @@ bool CreatePairedMinidumps(ProcessHandle
                            nsAString* pairGUID NS_OUTPARAM,
                            nsIFile** childDump NS_OUTPARAM,
                            nsIFile** parentDump NS_OUTPARAM);
 
 #  if defined(XP_WIN32) || defined(XP_MACOSX)
 // Parent-side API for children
 const char* GetChildNotificationPipe();
 
+#ifdef MOZ_CRASHREPORTER_INJECTOR
+// Inject a crash report client into an arbitrary process, and inform the
+// callback object when it crashes. Parent process only.
+
+class InjectorCrashCallback
+{
+public:
+  InjectorCrashCallback() { }
+
+  virtual void OnCrash(DWORD processID, const nsAString& aDumpID) = 0;
+};
+
+void InjectCrashReporterIntoProcess(DWORD processID, InjectorCrashCallback* cb);
+void UnregisterInjectorCallback(DWORD processID);
+#endif
+
 // Child-side API
 bool SetRemoteExceptionHandler(const nsACString& crashPipe);
 
 #  elif defined(XP_LINUX)
 // Parent-side API for children
 
 // Set the outparams for crash reporter server's fd (|childCrashFd|)
 // and the magic fd number it should be remapped to