Bug 1287875: Adds a logging facility for intercepted COM traffic; r?jimm draft
authorAaron Klotz <aklotz@mozilla.com>
Thu, 14 Jul 2016 17:11:33 -0600
changeset 389588 eb3764f842ddf97e563d4f2b8c4854cb925682e0
parent 389577 19b671c5f6fa10560fdd7777f34bb99f0e5390ea
child 389803 0a467315d78883ddaedf6cccd7be0cdd63ef9226
push id23463
push useraklotz@mozilla.com
push dateTue, 19 Jul 2016 18:14:38 +0000
reviewersjimm
bugs1287875
milestone50.0a1
Bug 1287875: Adds a logging facility for intercepted COM traffic; r?jimm MozReview-Commit-ID: BfjSAVOo18G
ipc/mscom/InterceptorLog.cpp
ipc/mscom/InterceptorLog.h
ipc/mscom/moz.build
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/InterceptorLog.cpp
@@ -0,0 +1,456 @@
+/* -*- 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/mscom/InterceptorLog.h"
+
+#include "MainThreadUtils.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/mscom/Registration.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIFileStreams.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOMPrivate.h"
+#include "nsXULAppAPI.h"
+#include "prenv.h"
+
+#include <callobj.h>
+
+using mozilla::DebugOnly;
+using mozilla::mscom::ArrayData;
+using mozilla::mscom::FindArrayData;
+using mozilla::Mutex;
+using mozilla::MutexAutoLock;
+using mozilla::NewNonOwningRunnableMethod;
+using mozilla::services::GetObserverService;
+using mozilla::StaticAutoPtr;
+using mozilla::TimeDuration;
+using mozilla::TimeStamp;
+
+namespace {
+
+class ShutdownEvent : public nsIObserver
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+};
+
+NS_IMPL_ISUPPORTS(ShutdownEvent, nsIObserver)
+
+class Logger
+{
+public:
+  explicit Logger(const nsACString& aLeafBaseName);
+  bool IsValid()
+  {
+    MutexAutoLock lock(mMutex);
+    return !!mThread;
+  }
+  void LogQI(HRESULT aResult, IUnknown* aTarget, REFIID aIid, IUnknown* aInterface);
+  void LogEvent(ICallFrame* aCallFrame, IUnknown* aTargetInterface);
+  nsresult Shutdown();
+
+private:
+  void OpenFile();
+  void Flush();
+  void CloseFile();
+  void AssertRunningOnLoggerThread();
+  bool VariantToString(const VARIANT& aVariant, nsACString& aOut, LONG aIndex = 0);
+  static double GetElapsedTime();
+
+  nsCOMPtr<nsIFile>         mLogFileName;
+  nsCOMPtr<nsIOutputStream> mLogFile; // Only accessed by mThread
+  Mutex                     mMutex; // Guards mThread and mEntries
+  nsCOMPtr<nsIThread>       mThread;
+  nsTArray<nsCString>       mEntries;
+};
+
+Logger::Logger(const nsACString& aLeafBaseName)
+  : mMutex("mozilla::com::InterceptorLog::Logger")
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  nsCOMPtr<nsIFile> logFileName;
+  GeckoProcessType procType = XRE_GetProcessType();
+  nsAutoCString leafName(aLeafBaseName);
+  nsresult rv;
+  if (procType == GeckoProcessType_Default) {
+    leafName.AppendLiteral("-Parent-");
+    rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(logFileName));
+  } else if (procType == GeckoProcessType_Content) {
+    leafName.AppendLiteral("-Content-");
+    rv = NS_GetSpecialDirectory(NS_APP_CONTENT_PROCESS_TEMP_DIR,
+                                getter_AddRefs(logFileName));
+  } else {
+    return;
+  }
+  if (NS_FAILED(rv)) {
+    return;
+  }
+  DWORD pid = GetCurrentProcessId();
+  leafName.AppendPrintf("%u.log", pid);
+  // Using AppendNative here because Windows
+  rv = logFileName->AppendNative(leafName);
+  if (NS_FAILED(rv)) {
+    return;
+  }
+  mLogFileName.swap(logFileName);
+
+  nsCOMPtr<nsIObserverService> obsSvc = GetObserverService();
+  nsCOMPtr<nsIObserver> shutdownEvent = new ShutdownEvent();
+  rv = obsSvc->AddObserver(shutdownEvent, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID,
+                           false);
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  nsCOMPtr<nsIRunnable> openRunnable(
+      NewNonOwningRunnableMethod(this, &Logger::OpenFile));
+  rv = NS_NewNamedThread("COM Intcpt Log", getter_AddRefs(mThread),
+                         openRunnable);
+  if (NS_FAILED(rv)) {
+    obsSvc->RemoveObserver(shutdownEvent, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID);
+  }
+}
+
+void
+Logger::AssertRunningOnLoggerThread()
+{
+#if defined(DEBUG)
+  nsCOMPtr<nsIThread> curThread;
+  if (NS_FAILED(NS_GetCurrentThread(getter_AddRefs(curThread)))) {
+    return;
+  }
+  MutexAutoLock lock(mMutex);
+  MOZ_ASSERT(curThread == mThread);
+#endif
+}
+
+void
+Logger::OpenFile()
+{
+  AssertRunningOnLoggerThread();
+  MOZ_ASSERT(mLogFileName && !mLogFile);
+  NS_NewLocalFileOutputStream(getter_AddRefs(mLogFile), mLogFileName,
+                              PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
+                              PR_IRUSR | PR_IWUSR | PR_IRGRP);
+}
+
+void
+Logger::CloseFile()
+{
+  AssertRunningOnLoggerThread();
+  MOZ_ASSERT(mLogFile);
+  if (!mLogFile) {
+    return;
+  }
+  Flush();
+  mLogFile->Close();
+  mLogFile = nullptr;
+}
+
+nsresult
+Logger::Shutdown()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  nsresult rv = mThread->Dispatch(NewNonOwningRunnableMethod(this,
+                                                             &Logger::CloseFile),
+                                  NS_DISPATCH_NORMAL);
+  NS_WARN_IF(NS_FAILED(rv));
+
+  rv = mThread->Shutdown();
+  NS_WARN_IF(NS_FAILED(rv));
+  return NS_OK;
+}
+
+bool
+Logger::VariantToString(const VARIANT& aVariant, nsACString& aOut, LONG aIndex)
+{
+  switch (aVariant.vt) {
+    case VT_DISPATCH: {
+      aOut.AppendPrintf("(IDispatch*) 0x%0p", aVariant.pdispVal);
+      return true;
+    }
+    case VT_DISPATCH | VT_BYREF: {
+      aOut.AppendPrintf("(IDispatch*) 0x%0p", (aVariant.ppdispVal)[aIndex]);
+      return true;
+    }
+    case VT_UNKNOWN: {
+      aOut.AppendPrintf("(IUnknown*) 0x%0p", aVariant.punkVal);
+      return true;
+    }
+    case VT_UNKNOWN | VT_BYREF: {
+      aOut.AppendPrintf("(IUnknown*) 0x%0p", (aVariant.ppunkVal)[aIndex]);
+      return true;
+    }
+    case VT_VARIANT | VT_BYREF: {
+      return VariantToString((aVariant.pvarVal)[aIndex], aOut);
+    }
+    case VT_I4 | VT_BYREF: {
+      aOut.AppendPrintf("%d", aVariant.plVal[aIndex]);
+      return true;
+    }
+    case VT_UI4 | VT_BYREF: {
+      aOut.AppendPrintf("%u", aVariant.pulVal[aIndex]);
+      return true;
+    }
+    case VT_I4: {
+      aOut.AppendPrintf("%d", aVariant.lVal);
+      return true;
+    }
+    case VT_UI4: {
+      aOut.AppendPrintf("%u", aVariant.ulVal);
+      return true;
+    }
+    case VT_EMPTY: {
+      aOut.AppendLiteral("(empty VARIANT)");
+      return true;
+    }
+    case VT_NULL: {
+      aOut.AppendLiteral("(null VARIANT)");
+      return true;
+    }
+    case VT_BSTR: {
+      aOut.AppendPrintf("\"%S\"", aVariant.bstrVal);
+      return true;
+    }
+    case VT_BSTR | VT_BYREF: {
+      aOut.AppendPrintf("\"%S\"", *aVariant.pbstrVal);
+      return true;
+    }
+    default: {
+      aOut.AppendPrintf("(VariantToString failed, VARTYPE == 0x%04hx)",
+                        aVariant.vt);
+      return false;
+    }
+  }
+}
+
+/* static */ double
+Logger::GetElapsedTime()
+{
+  TimeStamp ts = TimeStamp::Now();
+  bool inconsistent;
+  TimeDuration duration = ts - TimeStamp::ProcessCreation(inconsistent);
+  return duration.ToMicroseconds();
+}
+
+void
+Logger::LogQI(HRESULT aResult, IUnknown* aTarget, REFIID aIid, IUnknown* aInterface)
+{
+  if (FAILED(aResult)) {
+    return;
+  }
+  double elapsed = GetElapsedTime();
+
+  nsPrintfCString line("%fus\t0x%0p\tIUnknown::QueryInterface\t([in] ", elapsed,
+                       aTarget);
+
+  WCHAR buf[39] = {0};
+  if (StringFromGUID2(aIid, buf, mozilla::ArrayLength(buf))) {
+    line.AppendPrintf("%S", buf);
+  } else {
+    line.AppendLiteral("(IID Conversion Failed)");
+  }
+  line.AppendPrintf(", [out] 0x%p)\t0x%08X\n", aInterface, aResult);
+
+  MutexAutoLock lock(mMutex);
+  mEntries.AppendElement(line);
+  mThread->Dispatch(NewNonOwningRunnableMethod(this, &Logger::Flush),
+                    NS_DISPATCH_NORMAL);
+}
+
+void
+Logger::LogEvent(ICallFrame* aCallFrame, IUnknown* aTargetInterface)
+{
+  // (1) Gather info about the call
+  double elapsed = GetElapsedTime();
+
+  CALLFRAMEINFO callInfo;
+  HRESULT hr = aCallFrame->GetInfo(&callInfo);
+  if (FAILED(hr)) {
+    return;
+  }
+
+  PWSTR interfaceName = nullptr;
+  PWSTR methodName = nullptr;
+  hr = aCallFrame->GetNames(&interfaceName, &methodName);
+  if (FAILED(hr)) {
+    return;
+  }
+
+  // (2) Serialize the call
+  nsPrintfCString line("%fus\t0x%p\t%S::%S\t(", elapsed,
+                       aTargetInterface, interfaceName, methodName);
+
+  CoTaskMemFree(interfaceName);
+  interfaceName = nullptr;
+  CoTaskMemFree(methodName);
+  methodName = nullptr;
+
+  // Check for supplemental array data
+  const ArrayData* arrayData = FindArrayData(callInfo.iid, callInfo.iMethod);
+
+  for (ULONG paramIndex = 0; paramIndex < callInfo.cParams; ++paramIndex) {
+    CALLFRAMEPARAMINFO paramInfo;
+    hr = aCallFrame->GetParamInfo(paramIndex, &paramInfo);
+    if (SUCCEEDED(hr)) {
+      line.AppendLiteral("[");
+      if (paramInfo.fIn) {
+        line.AppendLiteral("in");
+      }
+      if (paramInfo.fOut) {
+        line.AppendLiteral("out");
+      }
+      line.AppendLiteral("] ");
+    }
+    VARIANT paramValue;
+    hr = aCallFrame->GetParam(paramIndex, &paramValue);
+    if (SUCCEEDED(hr)) {
+      if (arrayData && paramIndex == arrayData->mArrayParamIndex) {
+        VARIANT lengthParam;
+        hr = aCallFrame->GetParam(arrayData->mLengthParamIndex, &lengthParam);
+        if (SUCCEEDED(hr)) {
+          line.AppendLiteral("{ ");
+          for (LONG i = 0; i < *lengthParam.plVal; ++i) {
+            VariantToString(paramValue, line, i);
+            if (i < *lengthParam.plVal - 1) {
+              line.AppendLiteral(", ");
+            }
+          }
+          line.AppendLiteral(" }");
+        } else {
+          line.AppendPrintf("(GetParam failed with HRESULT 0x%08X)", hr);
+        }
+      } else {
+        VariantToString(paramValue, line);
+      }
+    } else {
+      line.AppendPrintf("(GetParam failed with HRESULT 0x%08X)", hr);
+    }
+    if (paramIndex < callInfo.cParams - 1) {
+      line.AppendLiteral(", ");
+    }
+  }
+  line.AppendLiteral(")\t");
+
+  HRESULT callResult = aCallFrame->GetReturnValue();
+  line.AppendPrintf("0x%08X\n", callResult);
+
+  // (3) Enqueue event for logging
+  MutexAutoLock lock(mMutex);
+  mEntries.AppendElement(line);
+  mThread->Dispatch(NewNonOwningRunnableMethod(this, &Logger::Flush),
+                    NS_DISPATCH_NORMAL);
+}
+
+void
+Logger::Flush()
+{
+  AssertRunningOnLoggerThread();
+  MOZ_ASSERT(mLogFile);
+  if (!mLogFile) {
+    return;
+  }
+  nsTArray<nsCString> linesToWrite;
+  { // Scope for lock
+    MutexAutoLock lock(mMutex);
+    linesToWrite.SwapElements(mEntries);
+  }
+
+  for (uint32_t i = 0, len = linesToWrite.Length(); i < len; ++i) {
+    uint32_t bytesWritten;
+    nsCString& line = linesToWrite[i];
+    nsresult rv = mLogFile->Write(line.get(), line.Length(), &bytesWritten);
+    NS_WARN_IF(NS_FAILED(rv));
+  }
+}
+
+StaticAutoPtr<Logger> sLogger;
+
+NS_IMETHODIMP
+ShutdownEvent::Observe(nsISupports* aSubject, const char* aTopic,
+                       const char16_t* aData)
+{
+  if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID)) {
+    MOZ_ASSERT(false);
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+  MOZ_ASSERT(sLogger);
+  NS_WARN_IF(NS_FAILED(sLogger->Shutdown()));
+  nsCOMPtr<nsIObserver> kungFuDeathGrip(this);
+  nsCOMPtr<nsIObserverService> obsSvc = GetObserverService();
+  obsSvc->RemoveObserver(this, aTopic);
+  return NS_OK;
+}
+} // anonymous namespace
+
+
+static bool
+MaybeCreateLog(const char* aEnvVarName)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(XRE_IsContentProcess() || XRE_IsParentProcess());
+  MOZ_ASSERT(!sLogger);
+  const char* leafBaseName = PR_GetEnv(aEnvVarName);
+  if (!leafBaseName) {
+    return false;
+  }
+  nsDependentCString strLeafBaseName(leafBaseName);
+  if (strLeafBaseName.IsEmpty()) {
+    return false;
+  }
+  sLogger = new Logger(strLeafBaseName);
+  if (!sLogger->IsValid()) {
+    sLogger = nullptr;
+    return false;
+  }
+  ClearOnShutdown(&sLogger);
+  return true;
+}
+
+namespace mozilla {
+namespace mscom {
+
+/* static */ bool
+InterceptorLog::Init()
+{
+  static const bool isEnabled = MaybeCreateLog("MOZ_MSCOM_LOG_BASENAME");
+  return isEnabled;
+}
+
+/* static */ void
+InterceptorLog::QI(HRESULT aResult, IUnknown* aTarget, REFIID aIid, IUnknown* aInterface)
+{
+  if (!sLogger) {
+    return;
+  }
+  sLogger->LogQI(aResult, aTarget, aIid, aInterface);
+}
+
+/* static */ void
+InterceptorLog::Event(ICallFrame* aCallFrame, IUnknown* aTargetInterface)
+{
+  if (!sLogger) {
+    return;
+  }
+  sLogger->LogEvent(aCallFrame, aTargetInterface);
+}
+
+} // namespace mscom
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/ipc/mscom/InterceptorLog.h
@@ -0,0 +1,28 @@
+/* -*- 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_mscom_InterceptorLog_h
+#define mozilla_mscom_InterceptorLog_h
+
+struct ICallFrame;
+struct IUnknown;
+
+namespace mozilla {
+namespace mscom {
+
+class InterceptorLog
+{
+public:
+  static bool Init();
+  static void QI(HRESULT aResult, IUnknown* aTarget, REFIID aIid, IUnknown* aInterface);
+  static void Event(ICallFrame* aCallFrame, IUnknown* aTarget);
+};
+
+} // namespace mscom
+} // namespace mozilla
+
+#endif // mozilla_mscom_InterceptorLog_h
+
--- a/ipc/mscom/moz.build
+++ b/ipc/mscom/moz.build
@@ -3,28 +3,34 @@
 # 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.mscom += [
     'COMApartmentRegion.h',
     'COMPtrHolder.h',
     'EnsureMTA.h',
+    'InterceptorLog.h',
     'MainThreadRuntime.h',
     'ProxyStream.h',
     'Registration.h',
     'Utils.h',
 ]
 
 SOURCES += [
     'Registration.cpp',
     'Utils.cpp',
 ]
 
 UNIFIED_SOURCES += [
     'EnsureMTA.cpp',
+    'InterceptorLog.cpp',
     'MainThreadRuntime.cpp',
     'ProxyStream.cpp',
 ]
 
+LOCAL_INCLUDES += [
+    '/xpcom/build',
+]
+
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'