зеркало из https://github.com/mozilla/gecko-dev.git
526 строки
15 KiB
C++
526 строки
15 KiB
C++
/* -*- 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 <callobj.h>
|
|
|
|
#include <utility>
|
|
|
|
#include "MainThreadUtils.h"
|
|
#include "mozilla/ClearOnShutdown.h"
|
|
#include "mozilla/DebugOnly.h"
|
|
#include "mozilla/IntegerRange.h"
|
|
#include "mozilla/Mutex.h"
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/StaticPtr.h"
|
|
#include "mozilla/TimeStamp.h"
|
|
#include "mozilla/Unused.h"
|
|
#include "mozilla/mscom/Registration.h"
|
|
#include "mozilla/mscom/Utils.h"
|
|
#include "nsAppDirectoryServiceDefs.h"
|
|
#include "nsDirectoryServiceDefs.h"
|
|
#include "nsDirectoryServiceUtils.h"
|
|
#include "nsIObserver.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsIOutputStream.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsPrintfCString.h"
|
|
#include "nsReadableUtils.h"
|
|
#include "nsTArray.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsXPCOMPrivate.h"
|
|
#include "nsXULAppAPI.h"
|
|
#include "prenv.h"
|
|
|
|
using mozilla::DebugOnly;
|
|
using mozilla::Mutex;
|
|
using mozilla::MutexAutoLock;
|
|
using mozilla::NewNonOwningRunnableMethod;
|
|
using mozilla::StaticAutoPtr;
|
|
using mozilla::TimeDuration;
|
|
using mozilla::TimeStamp;
|
|
using mozilla::Unused;
|
|
using mozilla::mscom::ArrayData;
|
|
using mozilla::mscom::FindArrayData;
|
|
using mozilla::mscom::IsValidGUID;
|
|
using mozilla::services::GetObserverService;
|
|
|
|
namespace {
|
|
|
|
class ShutdownEvent final : public nsIObserver {
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIOBSERVER
|
|
|
|
private:
|
|
~ShutdownEvent() {}
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(ShutdownEvent, nsIObserver)
|
|
|
|
class Logger final {
|
|
public:
|
|
explicit Logger(const nsACString& aLeafBaseName);
|
|
bool IsValid() {
|
|
MutexAutoLock lock(mMutex);
|
|
return !!mThread;
|
|
}
|
|
void LogQI(HRESULT aResult, IUnknown* aTarget, REFIID aIid,
|
|
IUnknown* aInterface, const TimeDuration* aOverheadDuration,
|
|
const TimeDuration* aGeckoDuration);
|
|
void CaptureFrame(ICallFrame* aCallFrame, IUnknown* aTargetInterface,
|
|
nsACString& aCapturedFrame);
|
|
void LogEvent(const nsACString& aCapturedFrame,
|
|
const TimeDuration& aOverheadDuration,
|
|
const TimeDuration& aGeckoDuration);
|
|
nsresult Shutdown();
|
|
|
|
private:
|
|
void OpenFile();
|
|
void Flush();
|
|
void CloseFile();
|
|
void AssertRunningOnLoggerThread();
|
|
bool VariantToString(const VARIANT& aVariant, nsACString& aOut,
|
|
LONG aIndex = 0);
|
|
bool TryParamAsGuid(REFIID aIid, ICallFrame* aCallFrame,
|
|
const CALLFRAMEPARAMINFO& aParamInfo, nsACString& aLine);
|
|
static double GetElapsedTime();
|
|
|
|
nsCOMPtr<nsIFile> mLogFileName;
|
|
nsCOMPtr<nsIOutputStream> mLogFile; // Only accessed by mThread
|
|
Mutex mMutex MOZ_UNANNOTATED; // 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;
|
|
nsresult rv =
|
|
NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(logFileName));
|
|
if (NS_FAILED(rv)) {
|
|
return;
|
|
}
|
|
|
|
GeckoProcessType procType = XRE_GetProcessType();
|
|
nsAutoCString leafName(aLeafBaseName);
|
|
if (procType == GeckoProcessType_Default) {
|
|
leafName.AppendLiteral("-Parent-");
|
|
} else if (procType == GeckoProcessType_Content) {
|
|
// Note that this logging won't work in opt build content processes unless
|
|
// the sandbox is disabled. It should still work in debug builds because we
|
|
// give access to the os temp dir.
|
|
leafName.AppendLiteral("-Content-");
|
|
} else {
|
|
return;
|
|
}
|
|
DWORD pid = GetCurrentProcessId();
|
|
leafName.AppendPrintf("%lu.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("Logger::OpenFile", 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("Logger::CloseFile", this, &Logger::CloseFile),
|
|
NS_DISPATCH_NORMAL);
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Dispatch failed");
|
|
|
|
rv = mThread->Shutdown();
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Shutdown failed");
|
|
(void)rv;
|
|
return NS_OK;
|
|
}
|
|
|
|
bool Logger::VariantToString(const VARIANT& aVariant, nsACString& aOut,
|
|
LONG aIndex) {
|
|
switch (aVariant.vt) {
|
|
case VT_DISPATCH: {
|
|
aOut.AppendPrintf("(IDispatch*) 0x%p", aVariant.pdispVal);
|
|
return true;
|
|
}
|
|
case VT_DISPATCH | VT_BYREF: {
|
|
aOut.AppendPrintf("(IDispatch*) 0x%p", (aVariant.ppdispVal)[aIndex]);
|
|
return true;
|
|
}
|
|
case VT_UNKNOWN: {
|
|
aOut.AppendPrintf("(IUnknown*) 0x%p", aVariant.punkVal);
|
|
return true;
|
|
}
|
|
case VT_UNKNOWN | VT_BYREF: {
|
|
aOut.AppendPrintf("(IUnknown*) 0x%p", (aVariant.ppunkVal)[aIndex]);
|
|
return true;
|
|
}
|
|
case VT_VARIANT | VT_BYREF: {
|
|
return VariantToString((aVariant.pvarVal)[aIndex], aOut);
|
|
}
|
|
case VT_I4 | VT_BYREF: {
|
|
aOut.AppendPrintf("%ld", aVariant.plVal[aIndex]);
|
|
return true;
|
|
}
|
|
case VT_UI4 | VT_BYREF: {
|
|
aOut.AppendPrintf("%lu", aVariant.pulVal[aIndex]);
|
|
return true;
|
|
}
|
|
case VT_I4: {
|
|
aOut.AppendPrintf("%ld", aVariant.lVal);
|
|
return true;
|
|
}
|
|
case VT_UI4: {
|
|
aOut.AppendPrintf("%lu", 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();
|
|
TimeDuration duration = ts - TimeStamp::ProcessCreation();
|
|
return duration.ToMicroseconds();
|
|
}
|
|
|
|
void Logger::LogQI(HRESULT aResult, IUnknown* aTarget, REFIID aIid,
|
|
IUnknown* aInterface, const TimeDuration* aOverheadDuration,
|
|
const TimeDuration* aGeckoDuration) {
|
|
if (FAILED(aResult)) {
|
|
return;
|
|
}
|
|
|
|
double elapsed = GetElapsedTime();
|
|
|
|
nsAutoCString strOverheadDuration;
|
|
if (aOverheadDuration) {
|
|
strOverheadDuration.AppendPrintf("%.3f",
|
|
aOverheadDuration->ToMicroseconds());
|
|
} else {
|
|
strOverheadDuration.AppendLiteral("(none)");
|
|
}
|
|
|
|
nsAutoCString strGeckoDuration;
|
|
if (aGeckoDuration) {
|
|
strGeckoDuration.AppendPrintf("%.3f", aGeckoDuration->ToMicroseconds());
|
|
} else {
|
|
strGeckoDuration.AppendLiteral("(none)");
|
|
}
|
|
|
|
nsPrintfCString line("%.3f\t%s\t%s\t0x%p\tIUnknown::QueryInterface\t([in] ",
|
|
elapsed, strOverheadDuration.get(),
|
|
strGeckoDuration.get(), 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%08lX\n", aInterface, aResult);
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
mEntries.AppendElement(std::move(line));
|
|
mThread->Dispatch(
|
|
NewNonOwningRunnableMethod("Logger::Flush", this, &Logger::Flush),
|
|
NS_DISPATCH_NORMAL);
|
|
}
|
|
|
|
bool Logger::TryParamAsGuid(REFIID aIid, ICallFrame* aCallFrame,
|
|
const CALLFRAMEPARAMINFO& aParamInfo,
|
|
nsACString& aLine) {
|
|
if (aIid != IID_IServiceProvider) {
|
|
return false;
|
|
}
|
|
|
|
GUID** guid = reinterpret_cast<GUID**>(
|
|
static_cast<BYTE*>(aCallFrame->GetStackLocation()) +
|
|
aParamInfo.stackOffset);
|
|
|
|
if (!IsValidGUID(**guid)) {
|
|
return false;
|
|
}
|
|
|
|
WCHAR buf[39] = {0};
|
|
if (!StringFromGUID2(**guid, buf, mozilla::ArrayLength(buf))) {
|
|
return false;
|
|
}
|
|
|
|
aLine.AppendPrintf("%S", buf);
|
|
return true;
|
|
}
|
|
|
|
void Logger::CaptureFrame(ICallFrame* aCallFrame, IUnknown* aTargetInterface,
|
|
nsACString& aCapturedFrame) {
|
|
aCapturedFrame.Truncate();
|
|
|
|
// (1) Gather info about the call
|
|
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("0x%p\t%S::%S\t(", 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, ¶mInfo);
|
|
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, ¶mValue);
|
|
if (SUCCEEDED(hr)) {
|
|
if (arrayData && paramIndex == arrayData->mArrayParamIndex) {
|
|
VARIANT lengthParam;
|
|
hr = aCallFrame->GetParam(arrayData->mLengthParamIndex, &lengthParam);
|
|
if (SUCCEEDED(hr)) {
|
|
line.AppendLiteral("{ ");
|
|
StringJoinAppend(line, ", "_ns,
|
|
mozilla::IntegerRange<LONG>(0, *lengthParam.plVal),
|
|
[this, ¶mValue](nsACString& line, const LONG i) {
|
|
VariantToString(paramValue, line, i);
|
|
});
|
|
line.AppendLiteral(" }");
|
|
} else {
|
|
line.AppendPrintf("(GetParam failed with HRESULT 0x%08lX)", hr);
|
|
}
|
|
} else {
|
|
VariantToString(paramValue, line);
|
|
}
|
|
} else if (hr != DISP_E_BADVARTYPE ||
|
|
!TryParamAsGuid(callInfo.iid, aCallFrame, paramInfo, line)) {
|
|
line.AppendPrintf("(GetParam failed with HRESULT 0x%08lX)", hr);
|
|
}
|
|
if (paramIndex < callInfo.cParams - 1) {
|
|
line.AppendLiteral(", ");
|
|
}
|
|
}
|
|
line.AppendLiteral(")\t");
|
|
|
|
HRESULT callResult = aCallFrame->GetReturnValue();
|
|
line.AppendPrintf("0x%08lX\n", callResult);
|
|
|
|
aCapturedFrame = std::move(line);
|
|
}
|
|
|
|
void Logger::LogEvent(const nsACString& aCapturedFrame,
|
|
const TimeDuration& aOverheadDuration,
|
|
const TimeDuration& aGeckoDuration) {
|
|
double elapsed = GetElapsedTime();
|
|
|
|
nsPrintfCString line("%.3f\t%.3f\t%.3f\t%s", elapsed,
|
|
aOverheadDuration.ToMicroseconds(),
|
|
aGeckoDuration.ToMicroseconds(),
|
|
PromiseFlatCString(aCapturedFrame).get());
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
mEntries.AppendElement(line);
|
|
mThread->Dispatch(
|
|
NewNonOwningRunnableMethod("Logger::Flush", 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 = std::move(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);
|
|
Unused << 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);
|
|
Unused << 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,
|
|
const TimeDuration* aOverheadDuration,
|
|
const TimeDuration* aGeckoDuration) {
|
|
if (!sLogger) {
|
|
return;
|
|
}
|
|
sLogger->LogQI(aResult, aTarget, aIid, aInterface, aOverheadDuration,
|
|
aGeckoDuration);
|
|
}
|
|
|
|
/* static */
|
|
void InterceptorLog::CaptureFrame(ICallFrame* aCallFrame,
|
|
IUnknown* aTargetInterface,
|
|
nsACString& aCapturedFrame) {
|
|
if (!sLogger) {
|
|
return;
|
|
}
|
|
sLogger->CaptureFrame(aCallFrame, aTargetInterface, aCapturedFrame);
|
|
}
|
|
|
|
/* static */
|
|
void InterceptorLog::Event(const nsACString& aCapturedFrame,
|
|
const TimeDuration& aOverheadDuration,
|
|
const TimeDuration& aGeckoDuration) {
|
|
if (!sLogger) {
|
|
return;
|
|
}
|
|
sLogger->LogEvent(aCapturedFrame, aOverheadDuration, aGeckoDuration);
|
|
}
|
|
|
|
} // namespace mscom
|
|
} // namespace mozilla
|