зеркало из https://github.com/mozilla/gecko-dev.git
Backed out changeset 2d6c5cbcc213 (bug 902587) for failures during linking on a CLOSED TREE
--HG-- rename : xpcom/build/PoisonIOInterposer.h => xpcom/build/mozPoisonWrite.h rename : xpcom/build/PoisonIOInterposerBase.cpp => xpcom/build/mozPoisonWriteBase.cpp rename : xpcom/build/PoisonIOInterposerMac.cpp => xpcom/build/mozPoisonWriteMac.cpp rename : xpcom/build/PoisonIOInterposerWin.cpp => xpcom/build/mozPoisonWriteWin.cpp
This commit is contained in:
Родитель
4de5a040d1
Коммит
019abea10a
|
@ -150,7 +150,7 @@ XRE_SetupDllBlocklistType XRE_SetupDllBlocklist;
|
|||
XRE_TelemetryAccumulateType XRE_TelemetryAccumulate;
|
||||
XRE_StartupTimelineRecordType XRE_StartupTimelineRecord;
|
||||
XRE_mainType XRE_main;
|
||||
XRE_StopLateWriteChecksType XRE_StopLateWriteChecks;
|
||||
XRE_DisableWritePoisoningType XRE_DisableWritePoisoning;
|
||||
|
||||
static const nsDynamicFunctionLoad kXULFuncs[] = {
|
||||
{ "XRE_GetFileFromPath", (NSFuncPtr*) &XRE_GetFileFromPath },
|
||||
|
@ -162,7 +162,7 @@ static const nsDynamicFunctionLoad kXULFuncs[] = {
|
|||
{ "XRE_TelemetryAccumulate", (NSFuncPtr*) &XRE_TelemetryAccumulate },
|
||||
{ "XRE_StartupTimelineRecord", (NSFuncPtr*) &XRE_StartupTimelineRecord },
|
||||
{ "XRE_main", (NSFuncPtr*) &XRE_main },
|
||||
{ "XRE_StopLateWriteChecks", (NSFuncPtr*) &XRE_StopLateWriteChecks },
|
||||
{ "XRE_DisableWritePoisoning", (NSFuncPtr*) &XRE_DisableWritePoisoning },
|
||||
{ nullptr, nullptr }
|
||||
};
|
||||
|
||||
|
@ -642,7 +642,7 @@ int main(int argc, char* argv[])
|
|||
// at least one such write that we don't control (see bug 826029). For
|
||||
// now we enable writes again and early exits will have to use exit instead
|
||||
// of _exit.
|
||||
XRE_StopLateWriteChecks();
|
||||
XRE_DisableWritePoisoning();
|
||||
#endif
|
||||
|
||||
return result;
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "nsPISocketTransportService.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "TestHarness.h"
|
||||
#include "mozilla/mozPoisonWrite.h"
|
||||
|
||||
class MtransportTestUtils {
|
||||
public:
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
#include "nsIPropertyBag2.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/LateWriteChecks.h"
|
||||
#include "mozilla/mozPoisonWrite.h"
|
||||
#include "mozIStorageCompletionCallback.h"
|
||||
|
||||
#include "sqlite3.h"
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include "jsapi.h"
|
||||
#include "prenv.h"
|
||||
#include "nsAppDirectoryServiceDefs.h"
|
||||
#include "mozilla/mozPoisonWrite.h"
|
||||
|
||||
#if defined(XP_WIN)
|
||||
// Prevent collisions with nsAppStartup::GetStartupInfo()
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/FileUtils.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/PoisonIOInterposer.h"
|
||||
#include "mozilla/mozPoisonWrite.h"
|
||||
#if defined(MOZ_ENABLE_PROFILER_SPS)
|
||||
#include "shared-libraries.h"
|
||||
#endif
|
||||
|
|
|
@ -82,7 +82,7 @@ using mozilla::InjectCrashRunnable;
|
|||
#include <vector>
|
||||
|
||||
#include "mozilla/mozalloc_oom.h"
|
||||
#include "mozilla/LateWriteChecks.h"
|
||||
#include "mozilla/mozPoisonWrite.h"
|
||||
|
||||
#if defined(XP_MACOSX)
|
||||
CFStringRef reporterClientAppID = CFSTR("org.mozilla.crashreporter");
|
||||
|
@ -792,7 +792,7 @@ static bool ShouldReport()
|
|||
|
||||
namespace {
|
||||
bool Filter(void* context) {
|
||||
mozilla::StopLateWriteChecks();
|
||||
mozilla::DisableWritePoisoning();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -2234,7 +2234,7 @@ OOPInitialized()
|
|||
|
||||
#ifdef XP_MACOSX
|
||||
static bool ChildFilter(void *context) {
|
||||
mozilla::StopLateWriteChecks();
|
||||
mozilla::DisableWritePoisoning();
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -129,7 +129,7 @@
|
|||
#include "nsINIParser.h"
|
||||
#include "mozilla/Omnijar.h"
|
||||
#include "mozilla/StartupTimeline.h"
|
||||
#include "mozilla/LateWriteChecks.h"
|
||||
#include "mozilla/mozPoisonWrite.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
|
@ -3961,10 +3961,10 @@ XREMain::XRE_main(int argc, char* argv[], const nsXREAppData* aAppData)
|
|||
// corresponds to nsIAppStartup.quit(eRestart)
|
||||
if (rv == NS_SUCCESS_RESTART_APP) {
|
||||
appInitiatedRestart = true;
|
||||
|
||||
// We have an application restart don't do any shutdown checks here
|
||||
// In particular we don't want to poison IO for checking late-writes.
|
||||
gShutdownChecks = SCM_NOTHING;
|
||||
} else {
|
||||
// We will have a real shutdown, let ShutdownXPCOM poison writes to
|
||||
// find any late ones.
|
||||
mozilla::EnableWritePoisoning();
|
||||
}
|
||||
|
||||
if (!mShuttingDown) {
|
||||
|
@ -4144,8 +4144,8 @@ void SetWindowsEnvironment(WindowsEnvironmentType aEnvID);
|
|||
#endif // MOZ_METRO || !defined(XP_WIN)
|
||||
|
||||
void
|
||||
XRE_StopLateWriteChecks(void) {
|
||||
mozilla::StopLateWriteChecks();
|
||||
XRE_DisableWritePoisoning(void) {
|
||||
mozilla::DisableWritePoisoning();
|
||||
}
|
||||
|
||||
int
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
#include "nsStackWalk.h"
|
||||
#include "nsTraceMallocCallbacks.h"
|
||||
#include "nsTypeInfo.h"
|
||||
#include "mozilla/PoisonIOInterposer.h"
|
||||
#include "mozilla/mozPoisonWrite.h"
|
||||
|
||||
#if defined(XP_MACOSX)
|
||||
|
||||
|
|
|
@ -123,7 +123,7 @@
|
|||
#include <stdio.h>
|
||||
|
||||
#include "mozilla/Likely.h"
|
||||
#include "mozilla/PoisonIOInterposer.h"
|
||||
#include "mozilla/mozPoisonWrite.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "mozilla/ThreadLocal.h"
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
#endif
|
||||
|
||||
#include "mozilla/BlockingResourceBase.h"
|
||||
#include "mozilla/PoisonIOInterposer.h"
|
||||
#include "mozilla/mozPoisonWrite.h"
|
||||
|
||||
#ifdef HAVE_DLOPEN
|
||||
#include <dlfcn.h>
|
||||
|
|
|
@ -1,248 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* vim:set ts=4 sw=4 sts=4 ci et: */
|
||||
/* 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 <algorithm>
|
||||
|
||||
#include "mozilla/IOInterposer.h"
|
||||
#include "mozilla/PoisonIOInterposer.h"
|
||||
#include "mozilla/ProcessedStack.h"
|
||||
#include "mozilla/SHA1.h"
|
||||
#include "mozilla/Scoped.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "nsAppDirectoryServiceDefs.h"
|
||||
#include "nsDirectoryServiceUtils.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "nsStackWalk.h"
|
||||
#include "plstr.h"
|
||||
|
||||
#ifdef XP_WIN
|
||||
#define NS_T(str) L ## str
|
||||
#define NS_SLASH "\\"
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <windows.h>
|
||||
#else
|
||||
#define NS_SLASH "/"
|
||||
#endif
|
||||
|
||||
#include "LateWriteChecks.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
/*************************** Auxiliary Declarations ***************************/
|
||||
|
||||
// This a wrapper over a file descriptor that provides a Printf method and
|
||||
// computes the sha1 of the data that passes through it.
|
||||
class SHA1Stream
|
||||
{
|
||||
public:
|
||||
explicit SHA1Stream(FILE *stream)
|
||||
: mFile(stream)
|
||||
{
|
||||
MozillaRegisterDebugFILE(mFile);
|
||||
}
|
||||
|
||||
void Printf(const char *aFormat, ...)
|
||||
{
|
||||
MOZ_ASSERT(mFile);
|
||||
va_list list;
|
||||
va_start(list, aFormat);
|
||||
nsAutoCString str;
|
||||
str.AppendPrintf(aFormat, list);
|
||||
va_end(list);
|
||||
mSHA1.update(str.get(), str.Length());
|
||||
fwrite(str.get(), 1, str.Length(), mFile);
|
||||
}
|
||||
void Finish(SHA1Sum::Hash &aHash)
|
||||
{
|
||||
int fd = fileno(mFile);
|
||||
fflush(mFile);
|
||||
MozillaUnRegisterDebugFD(fd);
|
||||
fclose(mFile);
|
||||
mSHA1.finish(aHash);
|
||||
mFile = NULL;
|
||||
}
|
||||
private:
|
||||
FILE *mFile;
|
||||
SHA1Sum mSHA1;
|
||||
};
|
||||
|
||||
static void RecordStackWalker(void *aPC, void *aSP, void *aClosure)
|
||||
{
|
||||
std::vector<uintptr_t> *stack =
|
||||
static_cast<std::vector<uintptr_t>*>(aClosure);
|
||||
stack->push_back(reinterpret_cast<uintptr_t>(aPC));
|
||||
}
|
||||
|
||||
/**************************** Late-Write Observer ****************************/
|
||||
|
||||
/**
|
||||
* An implementation of IOInterposeObserver to be registered with IOInterposer.
|
||||
* This observer logs all writes as late writes.
|
||||
*/
|
||||
class LateWriteObserver MOZ_FINAL : public IOInterposeObserver
|
||||
{
|
||||
public:
|
||||
LateWriteObserver(const char* aProfileDirectory)
|
||||
: mProfileDirectory(PL_strdup(aProfileDirectory))
|
||||
{
|
||||
}
|
||||
~LateWriteObserver() {
|
||||
PL_strfree(mProfileDirectory);
|
||||
mProfileDirectory = nullptr;
|
||||
}
|
||||
|
||||
void Observe(IOInterposeObserver::Observation& aObservation);
|
||||
private:
|
||||
char* mProfileDirectory;
|
||||
};
|
||||
|
||||
void LateWriteObserver::Observe(IOInterposeObserver::Observation& aOb)
|
||||
{
|
||||
// Crash if that is the shutdown check mode
|
||||
if (gShutdownChecks == SCM_CRASH) {
|
||||
MOZ_CRASH();
|
||||
}
|
||||
|
||||
// If we have shutdown mode SCM_NOTHING or we can't record then abort
|
||||
if (gShutdownChecks == SCM_NOTHING || !Telemetry::CanRecord()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Write the stack and loaded libraries to a file. We can get here
|
||||
// concurrently from many writes, so we use multiple temporary files.
|
||||
std::vector<uintptr_t> rawStack;
|
||||
|
||||
NS_StackWalk(RecordStackWalker, /* skipFrames */ 0, /* maxFrames */ 0,
|
||||
reinterpret_cast<void*>(&rawStack), 0, nullptr);
|
||||
Telemetry::ProcessedStack stack = Telemetry::GetStackAndModules(rawStack);
|
||||
|
||||
nsPrintfCString nameAux("%s%s%s", mProfileDirectory,
|
||||
NS_SLASH, "Telemetry.LateWriteTmpXXXXXX");
|
||||
char *name;
|
||||
nameAux.GetMutableData(&name);
|
||||
|
||||
// We want the sha1 of the entire file, so please don't write to fd
|
||||
// directly; use sha1Stream.
|
||||
FILE *stream;
|
||||
#ifdef XP_WIN
|
||||
HANDLE hFile;
|
||||
do {
|
||||
// mkstemp isn't supported so keep trying until we get a file
|
||||
int result = _mktemp_s(name, strlen(name) + 1);
|
||||
hFile = CreateFileA(name, GENERIC_WRITE, 0, NULL, CREATE_NEW,
|
||||
FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
} while (GetLastError() == ERROR_FILE_EXISTS);
|
||||
|
||||
if (hFile == INVALID_HANDLE_VALUE) {
|
||||
NS_RUNTIMEABORT("Um, how did we get here?");
|
||||
}
|
||||
|
||||
// http://support.microsoft.com/kb/139640
|
||||
int fd = _open_osfhandle((intptr_t)hFile, _O_APPEND);
|
||||
if (fd == -1) {
|
||||
NS_RUNTIMEABORT("Um, how did we get here?");
|
||||
}
|
||||
|
||||
stream = _fdopen(fd, "w");
|
||||
#else
|
||||
int fd = mkstemp(name);
|
||||
stream = fdopen(fd, "w");
|
||||
#endif
|
||||
|
||||
SHA1Stream sha1Stream(stream);
|
||||
|
||||
size_t numModules = stack.GetNumModules();
|
||||
sha1Stream.Printf("%u\n", (unsigned)numModules);
|
||||
for (size_t i = 0; i < numModules; ++i) {
|
||||
Telemetry::ProcessedStack::Module module = stack.GetModule(i);
|
||||
sha1Stream.Printf("%s %s\n", module.mBreakpadId.c_str(),
|
||||
module.mName.c_str());
|
||||
}
|
||||
|
||||
size_t numFrames = stack.GetStackSize();
|
||||
sha1Stream.Printf("%u\n", (unsigned)numFrames);
|
||||
for (size_t i = 0; i < numFrames; ++i) {
|
||||
const Telemetry::ProcessedStack::Frame &frame =
|
||||
stack.GetFrame(i);
|
||||
// NOTE: We write the offsets, while the atos tool expects a value with
|
||||
// the virtual address added. For example, running otool -l on the the firefox
|
||||
// binary shows
|
||||
// cmd LC_SEGMENT_64
|
||||
// cmdsize 632
|
||||
// segname __TEXT
|
||||
// vmaddr 0x0000000100000000
|
||||
// so to print the line matching the offset 123 one has to run
|
||||
// atos -o firefox 0x100000123.
|
||||
sha1Stream.Printf("%d %x\n", frame.mModIndex, (unsigned)frame.mOffset);
|
||||
}
|
||||
|
||||
SHA1Sum::Hash sha1;
|
||||
sha1Stream.Finish(sha1);
|
||||
|
||||
// Note: These files should be deleted by telemetry once it reads them. If
|
||||
// there were no telemetry runs by the time we shut down, we just add files
|
||||
// to the existing ones instead of replacing them. Given that each of these
|
||||
// files is a bug to be fixed, that is probably the right thing to do.
|
||||
|
||||
// We append the sha1 of the contents to the file name. This provides a simple
|
||||
// client side deduplication.
|
||||
nsPrintfCString finalName("%s%s", mProfileDirectory,
|
||||
"/Telemetry.LateWriteFinal-");
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
finalName.AppendPrintf("%02x", sha1[i]);
|
||||
}
|
||||
PR_Delete(finalName.get());
|
||||
PR_Rename(name, finalName.get());
|
||||
}
|
||||
|
||||
/******************************* Setup/Teardown *******************************/
|
||||
|
||||
static StaticAutoPtr<LateWriteObserver> sLateWriteObserver;
|
||||
|
||||
namespace mozilla{
|
||||
|
||||
void InitLateWriteChecks()
|
||||
{
|
||||
nsCOMPtr<nsIFile> mozFile;
|
||||
NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mozFile));
|
||||
if (mozFile) {
|
||||
nsAutoCString nativePath;
|
||||
nsresult rv = mozFile->GetNativePath(nativePath);
|
||||
if (NS_SUCCEEDED(rv) && nativePath.get()) {
|
||||
sLateWriteObserver = new LateWriteObserver(nativePath.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BeginLateWriteChecks()
|
||||
{
|
||||
if (sLateWriteObserver) {
|
||||
IOInterposer::Register(
|
||||
IOInterposeObserver::OpWriteFSync,
|
||||
sLateWriteObserver
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void StopLateWriteChecks()
|
||||
{
|
||||
if (sLateWriteObserver) {
|
||||
IOInterposer::Unregister(
|
||||
IOInterposeObserver::OpAll,
|
||||
sLateWriteObserver
|
||||
);
|
||||
// Deallocation would not be thread-safe, and StopLateWriteChecks() is
|
||||
// called at shutdown and only in special cases.
|
||||
// sLateWriteObserver = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -1,59 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* vim:set ts=4 sw=4 sts=4 ci et: */
|
||||
/* 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_LateWriteChecks_h
|
||||
#define mozilla_LateWriteChecks_h
|
||||
|
||||
// This file, along with LateWriteChecks.cpp, serves to check for and report
|
||||
// late writes. The idea is discover writes to the file system that happens
|
||||
// during shutdown such that these maybe be moved forward and the process may be
|
||||
// killed without waiting for static destructors.
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/** Different shutdown check modes */
|
||||
enum ShutdownChecksMode {
|
||||
SCM_CRASH, /** Crash on shutdown check failure */
|
||||
SCM_RECORD, /** Record shutdown check violations */
|
||||
SCM_NOTHING /** Don't attempt any shutdown checks */
|
||||
};
|
||||
|
||||
/**
|
||||
* Current shutdown check mode.
|
||||
* This variable is defined and initialized in nsAppRunner.cpp
|
||||
*/
|
||||
extern ShutdownChecksMode gShutdownChecks;
|
||||
|
||||
/**
|
||||
* Allocate structures and acquire information from XPCOM necessary to do late
|
||||
* write checks. This function must be invoked before BeginLateWriteChecks()
|
||||
* and before XPCOM has stopped working.
|
||||
*/
|
||||
void InitLateWriteChecks();
|
||||
|
||||
/**
|
||||
* Begin recording all writes as late-writes. This function should be called
|
||||
* when all legitimate writes have occurred. This function does not rely on
|
||||
* XPCOM as it is designed to be invoked during XPCOM shutdown.
|
||||
*
|
||||
* For late-write checks to work you must initialize one or more backends that
|
||||
* reports IO through the IOInterposer API. PoisonIOInterposer would probably
|
||||
* be the backend of choice in this case.
|
||||
*
|
||||
* Note: BeginLateWriteChecks() must have been invoked before this function.
|
||||
*/
|
||||
void BeginLateWriteChecks();
|
||||
|
||||
/**
|
||||
* Stop recording all writes as late-writes, call this function when you want
|
||||
* late-write checks to stop. I.e. exception handling, or the special case on
|
||||
* Mac described in bug 826029.
|
||||
*/
|
||||
void StopLateWriteChecks();
|
||||
|
||||
} // mozilla
|
||||
|
||||
#endif // mozilla_LateWriteChecks_h
|
|
@ -1,88 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* vim:set ts=4 sw=4 sts=4 ci et: */
|
||||
/* 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_PoisonIOInterposer_h
|
||||
#define mozilla_PoisonIOInterposer_h
|
||||
|
||||
#include "mozilla/Types.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#if defined(MOZ_ENABLE_PROFILER_SPS) && (defined(XP_WIN) || defined(XP_MACOSX))
|
||||
|
||||
MOZ_BEGIN_EXTERN_C
|
||||
|
||||
/** Register file descriptor to be ignored by poisoning IO interposer */
|
||||
void MozillaRegisterDebugFD(int fd);
|
||||
|
||||
/** Register file to be ignored by poisoning IO interposer */
|
||||
void MozillaRegisterDebugFILE(FILE *f);
|
||||
|
||||
/** Unregister file descriptor from being ignored by poisoning IO interposer */
|
||||
void MozillaUnRegisterDebugFD(int fd);
|
||||
|
||||
/** Unregister file from being ignored by poisoning IO interposer */
|
||||
void MozillaUnRegisterDebugFILE(FILE *f);
|
||||
|
||||
MOZ_END_EXTERN_C
|
||||
|
||||
#ifdef __cplusplus
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* Check if a file is registered as a debug file.
|
||||
*/
|
||||
bool IsDebugFile(intptr_t aFileID);
|
||||
|
||||
/**
|
||||
* Initialize IO poisoning, this is only safe to do on the main-thread when no
|
||||
* other threads are running.
|
||||
*
|
||||
* Please, note that this probably has performance implications as all
|
||||
*/
|
||||
void InitPoisonIOInterposer();
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
/**
|
||||
* Check that writes are dirty before reporting I/O (Mac OS X only)
|
||||
* This is necessary for late-write checks on Mac OS X, but reading the buffer
|
||||
* from file to see if we're writing dirty bits is expensive, so we don't want
|
||||
* to do this for everything else that uses
|
||||
*/
|
||||
void OnlyReportDirtyWrites();
|
||||
#endif /* XP_MACOSX */
|
||||
|
||||
/**
|
||||
* Clear IO poisoning, this is only safe to do on the main-thread when no other
|
||||
* threads are running.
|
||||
*/
|
||||
void ClearPoisonIOInterposer();
|
||||
|
||||
} // namespace mozilla
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#else /* MOZ_ENABLE_PROFILER_SPS && (XP_WIN || XP_MACOSX) */
|
||||
|
||||
MOZ_BEGIN_EXTERN_C
|
||||
inline void MozillaRegisterDebugFD(int fd){}
|
||||
inline void MozillaRegisterDebugFILE(FILE *f){}
|
||||
inline void MozillaUnRegisterDebugFD(int fd){}
|
||||
inline void MozillaUnRegisterDebugFILE(FILE *f){}
|
||||
MOZ_END_EXTERN_C
|
||||
|
||||
#ifdef __cplusplus
|
||||
namespace mozilla {
|
||||
inline bool IsDebugFile(intptr_t aFileID){ return true; }
|
||||
inline void InitPoisonIOInterposer(){}
|
||||
inline void ClearPoisonIOInterposer(){}
|
||||
#ifdef XP_MACOSX
|
||||
inline void OnlyReportDirtyWrites(){}
|
||||
#endif /* XP_MACOSX */
|
||||
} // namespace mozilla
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MOZ_ENABLE_PROFILER_SPS && (XP_WIN || XP_MACOSX) */
|
||||
|
||||
#endif // mozilla_PoisonIOInterposer_h
|
|
@ -1,150 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 ci et: */
|
||||
/* 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/Mutex.h"
|
||||
#include "mozilla/Scoped.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "PoisonIOInterposer.h"
|
||||
|
||||
// Auxiliary method to convert file descriptors to ids
|
||||
#if defined(XP_WIN32)
|
||||
#include <io.h>
|
||||
inline intptr_t FileDescriptorToID(int aFd) {
|
||||
return _get_osfhandle(aFd);
|
||||
}
|
||||
#else
|
||||
inline intptr_t FileDescriptorToID(int aFd) {
|
||||
return aFd;
|
||||
}
|
||||
#endif /* if not XP_WIN32 */
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
namespace {
|
||||
struct DebugFilesAutoLockTraits {
|
||||
typedef PRLock *type;
|
||||
const static type empty() {
|
||||
return nullptr;
|
||||
}
|
||||
const static void release(type aL) {
|
||||
PR_Unlock(aL);
|
||||
}
|
||||
};
|
||||
|
||||
class DebugFilesAutoLock : public Scoped<DebugFilesAutoLockTraits> {
|
||||
static PRLock *Lock;
|
||||
public:
|
||||
static void Clear();
|
||||
static PRLock *getDebugFileIDsLock() {
|
||||
// On windows this static is not thread safe, but we know that the first
|
||||
// call is from
|
||||
// * An early registration of a debug FD or
|
||||
// * The call to InitWritePoisoning.
|
||||
// Since the early debug FDs are logs created early in the main thread
|
||||
// and no writes are trapped before InitWritePoisoning, we are safe.
|
||||
if (!Lock) {
|
||||
Lock = PR_NewLock();
|
||||
}
|
||||
|
||||
// We have to use something lower level than a mutex. If we don't, we
|
||||
// can get recursive in here when called from logging a call to free.
|
||||
return Lock;
|
||||
}
|
||||
|
||||
DebugFilesAutoLock() :
|
||||
Scoped<DebugFilesAutoLockTraits>(getDebugFileIDsLock()) {
|
||||
PR_Lock(get());
|
||||
}
|
||||
};
|
||||
|
||||
PRLock *DebugFilesAutoLock::Lock;
|
||||
void DebugFilesAutoLock::Clear() {
|
||||
MOZ_ASSERT(Lock != nullptr);
|
||||
Lock = nullptr;
|
||||
}
|
||||
|
||||
// Return a vector used to hold the IDs of the current debug files. On unix
|
||||
// an ID is a file descriptor. On Windows it is a file HANDLE.
|
||||
std::vector<intptr_t>* getDebugFileIDs() {
|
||||
PR_ASSERT_CURRENT_THREAD_OWNS_LOCK(DebugFilesAutoLock::getDebugFileIDsLock());
|
||||
// We have to use new as some write happen during static destructors
|
||||
// so an static std::vector might be destroyed while we still need it.
|
||||
static std::vector<intptr_t> *DebugFileIDs = new std::vector<intptr_t>();
|
||||
return DebugFileIDs;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
namespace mozilla{
|
||||
|
||||
// Auxiliary Method to test if a file descriptor is registered to be ignored
|
||||
// by the poisoning IO interposer
|
||||
bool IsDebugFile(intptr_t aFileID) {
|
||||
DebugFilesAutoLock lockedScope;
|
||||
|
||||
std::vector<intptr_t> &Vec = *getDebugFileIDs();
|
||||
return std::find(Vec.begin(), Vec.end(), aFileID) != Vec.end();
|
||||
}
|
||||
|
||||
// Clean-up for the registered debug files.
|
||||
// We should probably make sure all debug files are unregistered instead.
|
||||
// But as the poison IO interposer is used for late-write checks we're not
|
||||
// disabling it at any point yet. So Really no need for this.
|
||||
//
|
||||
// void ClearDebugFileRegister() {
|
||||
// PRLock *Lock;
|
||||
// {
|
||||
// DebugFilesAutoLock lockedScope;
|
||||
// delete getDebugFileIDs();
|
||||
// Lock = DebugFilesAutoLock::getDebugFileIDsLock();
|
||||
// DebugFilesAutoLock::Clear();
|
||||
// }
|
||||
// PR_DestroyLock(Lock);
|
||||
// }
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
extern "C" {
|
||||
|
||||
void MozillaRegisterDebugFD(int fd) {
|
||||
intptr_t fileId = FileDescriptorToID(fd);
|
||||
DebugFilesAutoLock lockedScope;
|
||||
std::vector<intptr_t> &Vec = *getDebugFileIDs();
|
||||
MOZ_ASSERT(std::find(Vec.begin(), Vec.end(), fileId) == Vec.end());
|
||||
Vec.push_back(fileId);
|
||||
}
|
||||
|
||||
void MozillaRegisterDebugFILE(FILE *f) {
|
||||
int fd = fileno(f);
|
||||
if (fd == 1 || fd == 2) {
|
||||
return;
|
||||
}
|
||||
MozillaRegisterDebugFD(fd);
|
||||
}
|
||||
|
||||
void MozillaUnRegisterDebugFD(int fd) {
|
||||
DebugFilesAutoLock lockedScope;
|
||||
intptr_t fileId = FileDescriptorToID(fd);
|
||||
std::vector<intptr_t> &Vec = *getDebugFileIDs();
|
||||
std::vector<intptr_t>::iterator i =
|
||||
std::find(Vec.begin(), Vec.end(), fileId);
|
||||
MOZ_ASSERT(i != Vec.end());
|
||||
Vec.erase(i);
|
||||
}
|
||||
|
||||
void MozillaUnRegisterDebugFILE(FILE *f) {
|
||||
int fd = fileno(f);
|
||||
if (fd == 1 || fd == 2) {
|
||||
return;
|
||||
}
|
||||
fflush(f);
|
||||
MozillaUnRegisterDebugFD(fd);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,340 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* vim:set ts=4 sw=4 sts=4 ci et: */
|
||||
/* 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 "PoisonIOInterposer.h"
|
||||
#include "mach_override.h"
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/IOInterposer.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/ProcessedStack.h"
|
||||
#include "mozilla/Scoped.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "mozilla/Util.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "nsStackWalk.h"
|
||||
#include "nsTraceRefcntImpl.h"
|
||||
#include "plstr.h"
|
||||
#include "prio.h"
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/uio.h>
|
||||
#include <aio.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
// Bit tracking if poisoned writes are enabled
|
||||
static bool sIsEnabled = false;
|
||||
|
||||
// Check if writes are dirty before reporting IO
|
||||
static bool sOnlyReportDirtyWrites = false;
|
||||
|
||||
// Routines for write validation
|
||||
bool IsValidWrite(int fd, const void *wbuf, size_t count);
|
||||
bool IsIPCWrite(int fd, const struct stat &buf);
|
||||
|
||||
/******************************** IO AutoTimer ********************************/
|
||||
|
||||
/**
|
||||
* RAII class for timing the duration of an I/O call and reporting the result
|
||||
* to the IOInterposeObserver API.
|
||||
*/
|
||||
class MacIOAutoObservation : public IOInterposeObserver::Observation
|
||||
{
|
||||
public:
|
||||
MacIOAutoObservation(IOInterposeObserver::Operation aOp,
|
||||
const char* aReference, int aFd)
|
||||
: mFd(aFd),
|
||||
mShouldObserve(sIsEnabled && IOInterposer::IsObservedOperation(aOp) &&
|
||||
!IsDebugFile(aFd))
|
||||
{
|
||||
if (mShouldObserve) {
|
||||
mOperation = aOp;
|
||||
mReference = aReference;
|
||||
mStart = TimeStamp::Now();
|
||||
}
|
||||
}
|
||||
|
||||
MacIOAutoObservation(IOInterposeObserver::Operation aOp,
|
||||
const char* aReference, int aFd, const void *aBuf,
|
||||
size_t aCount)
|
||||
: mFd(aFd),
|
||||
mShouldObserve(sIsEnabled && IOInterposer::IsObservedOperation(aOp) &&
|
||||
!IsDebugFile(aFd))
|
||||
{
|
||||
if (mShouldObserve) {
|
||||
mShouldObserve = IsValidWrite(aFd, aBuf, aCount);
|
||||
if (mShouldObserve) {
|
||||
mOperation = aOp;
|
||||
mReference = aReference;
|
||||
mStart = TimeStamp::Now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~MacIOAutoObservation()
|
||||
{
|
||||
if (mShouldObserve) {
|
||||
mEnd = TimeStamp::Now();
|
||||
|
||||
// Report this observation
|
||||
IOInterposer::Report(*this);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int mFd;
|
||||
bool mShouldObserve;
|
||||
};
|
||||
|
||||
/****************************** Write Validation ******************************/
|
||||
|
||||
// We want to detect "actual" writes, not IPC. Some IPC mechanisms are
|
||||
// implemented with file descriptors, so filter them out.
|
||||
bool IsIPCWrite(int fd, const struct stat &buf) {
|
||||
if ((buf.st_mode & S_IFMT) == S_IFIFO) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((buf.st_mode & S_IFMT) != S_IFSOCK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sockaddr_storage address;
|
||||
socklen_t len = sizeof(address);
|
||||
if (getsockname(fd, (sockaddr*) &address, &len) != 0) {
|
||||
return true; // Ignore the fd if we can't find what it is.
|
||||
}
|
||||
|
||||
return address.ss_family == AF_UNIX;
|
||||
}
|
||||
|
||||
// We want to report actual disk IO not things that don't move bits on the disk
|
||||
bool IsValidWrite(int fd, const void *wbuf, size_t count)
|
||||
{
|
||||
// Ignore writes of zero bytes, Firefox does some during shutdown.
|
||||
if (count == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
struct stat buf;
|
||||
int rv = fstat(fd, &buf);
|
||||
if (rv != 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IsIPCWrite(fd, buf)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// For writev we pass NULL in wbuf. We should only get here from
|
||||
// dbm, and it uses write, so assert that we have wbuf.
|
||||
if (!wbuf) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Break, here if we're allowed to report non-dirty writes
|
||||
if(!sOnlyReportDirtyWrites) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// As a really bad hack, accept writes that don't change the on disk
|
||||
// content. This is needed because dbm doesn't keep track of dirty bits
|
||||
// and can end up writing the same data to disk twice. Once when the
|
||||
// user (nss) asks it to sync and once when closing the database.
|
||||
ScopedFreePtr<void> wbuf2(malloc(count));
|
||||
if (!wbuf2) {
|
||||
return true;
|
||||
}
|
||||
off_t pos = lseek(fd, 0, SEEK_CUR);
|
||||
if (pos == -1) {
|
||||
return true;
|
||||
}
|
||||
ssize_t r = read(fd, wbuf2, count);
|
||||
if (r < 0 || (size_t)r != count) {
|
||||
return true;
|
||||
}
|
||||
int cmp = memcmp(wbuf, wbuf2, count);
|
||||
if (cmp != 0) {
|
||||
return true;
|
||||
}
|
||||
off_t pos2 = lseek(fd, pos, SEEK_SET);
|
||||
if (pos2 != pos) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise this is not a valid write
|
||||
return false;
|
||||
}
|
||||
|
||||
/*************************** Function Interception ***************************/
|
||||
|
||||
/** Structure for declaration of function override */
|
||||
struct FuncData {
|
||||
const char *Name; // Name of the function for the ones we use dlsym
|
||||
const void *Wrapper; // The function that we will replace 'Function' with
|
||||
void *Function; // The function that will be replaced with 'Wrapper'
|
||||
void *Buffer; // Will point to the jump buffer that lets us call
|
||||
// 'Function' after it has been replaced.
|
||||
};
|
||||
|
||||
// Wrap aio_write. We have not seen it before, so just assert/report it.
|
||||
typedef ssize_t (*aio_write_t)(struct aiocb *aiocbp);
|
||||
ssize_t wrap_aio_write(struct aiocb *aiocbp);
|
||||
FuncData aio_write_data = { 0, (void*) wrap_aio_write, (void*) aio_write };
|
||||
ssize_t wrap_aio_write(struct aiocb *aiocbp) {
|
||||
const char* ref = "aio_write";
|
||||
MacIOAutoObservation timer(IOInterposeObserver::OpWrite, ref,
|
||||
aiocbp->aio_fildes);
|
||||
|
||||
aio_write_t old_write = (aio_write_t) aio_write_data.Buffer;
|
||||
return old_write(aiocbp);
|
||||
}
|
||||
|
||||
// Wrap pwrite-like functions.
|
||||
// We have not seen them before, so just assert/report it.
|
||||
typedef ssize_t (*pwrite_t)(int fd, const void *buf, size_t nbyte, off_t offset);
|
||||
template<FuncData &foo>
|
||||
ssize_t wrap_pwrite_temp(int fd, const void *buf, size_t nbyte, off_t offset) {
|
||||
const char* ref = "pwrite_*";
|
||||
MacIOAutoObservation timer(IOInterposeObserver::OpWrite, ref, fd);
|
||||
pwrite_t old_write = (pwrite_t) foo.Buffer;
|
||||
return old_write(fd, buf, nbyte, offset);
|
||||
}
|
||||
|
||||
// Define a FuncData for a pwrite-like functions.
|
||||
#define DEFINE_PWRITE_DATA(X, NAME) \
|
||||
FuncData X ## _data = { NAME, (void*) wrap_pwrite_temp<X ## _data> }; \
|
||||
|
||||
// This exists everywhere.
|
||||
DEFINE_PWRITE_DATA(pwrite, "pwrite")
|
||||
// These exist on 32 bit OS X
|
||||
DEFINE_PWRITE_DATA(pwrite_NOCANCEL_UNIX2003, "pwrite$NOCANCEL$UNIX2003");
|
||||
DEFINE_PWRITE_DATA(pwrite_UNIX2003, "pwrite$UNIX2003");
|
||||
// This exists on 64 bit OS X
|
||||
DEFINE_PWRITE_DATA(pwrite_NOCANCEL, "pwrite$NOCANCEL");
|
||||
|
||||
|
||||
typedef ssize_t (*writev_t)(int fd, const struct iovec *iov, int iovcnt);
|
||||
template<FuncData &foo>
|
||||
ssize_t wrap_writev_temp(int fd, const struct iovec *iov, int iovcnt) {
|
||||
const char* ref = "pwrite_*";
|
||||
MacIOAutoObservation timer(IOInterposeObserver::OpWrite, ref, fd, nullptr,
|
||||
iovcnt);
|
||||
writev_t old_write = (writev_t) foo.Buffer;
|
||||
return old_write(fd, iov, iovcnt);
|
||||
}
|
||||
|
||||
// Define a FuncData for a writev-like functions.
|
||||
#define DEFINE_WRITEV_DATA(X, NAME) \
|
||||
FuncData X ## _data = { NAME, (void*) wrap_writev_temp<X ## _data> }; \
|
||||
|
||||
// This exists everywhere.
|
||||
DEFINE_WRITEV_DATA(writev, "writev");
|
||||
// These exist on 32 bit OS X
|
||||
DEFINE_WRITEV_DATA(writev_NOCANCEL_UNIX2003, "writev$NOCANCEL$UNIX2003");
|
||||
DEFINE_WRITEV_DATA(writev_UNIX2003, "writev$UNIX2003");
|
||||
// This exists on 64 bit OS X
|
||||
DEFINE_WRITEV_DATA(writev_NOCANCEL, "writev$NOCANCEL");
|
||||
|
||||
typedef ssize_t (*write_t)(int fd, const void *buf, size_t count);
|
||||
template<FuncData &foo>
|
||||
ssize_t wrap_write_temp(int fd, const void *buf, size_t count) {
|
||||
const char* ref = "pwrite_*";
|
||||
MacIOAutoObservation timer(IOInterposeObserver::OpWrite, ref, fd, buf,
|
||||
count);
|
||||
write_t old_write = (write_t) foo.Buffer;
|
||||
return old_write(fd, buf, count);
|
||||
}
|
||||
|
||||
// Define a FuncData for a write-like functions.
|
||||
#define DEFINE_WRITE_DATA(X, NAME) \
|
||||
FuncData X ## _data = { NAME, (void*) wrap_write_temp<X ## _data> }; \
|
||||
|
||||
// This exists everywhere.
|
||||
DEFINE_WRITE_DATA(write, "write");
|
||||
// These exist on 32 bit OS X
|
||||
DEFINE_WRITE_DATA(write_NOCANCEL_UNIX2003, "write$NOCANCEL$UNIX2003");
|
||||
DEFINE_WRITE_DATA(write_UNIX2003, "write$UNIX2003");
|
||||
// This exists on 64 bit OS X
|
||||
DEFINE_WRITE_DATA(write_NOCANCEL, "write$NOCANCEL");
|
||||
|
||||
FuncData *Functions[] = { &aio_write_data,
|
||||
|
||||
&pwrite_data,
|
||||
&pwrite_NOCANCEL_UNIX2003_data,
|
||||
&pwrite_UNIX2003_data,
|
||||
&pwrite_NOCANCEL_data,
|
||||
|
||||
&write_data,
|
||||
&write_NOCANCEL_UNIX2003_data,
|
||||
&write_UNIX2003_data,
|
||||
&write_NOCANCEL_data,
|
||||
|
||||
&writev_data,
|
||||
&writev_NOCANCEL_UNIX2003_data,
|
||||
&writev_UNIX2003_data,
|
||||
&writev_NOCANCEL_data};
|
||||
|
||||
const int NumFunctions = ArrayLength(Functions);
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
/******************************** IO Poisoning ********************************/
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
void InitPoisonIOInterposer() {
|
||||
// Enable reporting from poisoned write methods
|
||||
sIsEnabled = true;
|
||||
|
||||
// Make sure we only poison writes once!
|
||||
static bool WritesArePoisoned = false;
|
||||
if (WritesArePoisoned) {
|
||||
return;
|
||||
}
|
||||
WritesArePoisoned = true;
|
||||
|
||||
// stdout and stderr are OK.
|
||||
MozillaRegisterDebugFD(1);
|
||||
MozillaRegisterDebugFD(2);
|
||||
|
||||
for (int i = 0; i < NumFunctions; ++i) {
|
||||
FuncData *d = Functions[i];
|
||||
if (!d->Function) {
|
||||
d->Function = dlsym(RTLD_DEFAULT, d->Name);
|
||||
}
|
||||
if (!d->Function) {
|
||||
continue;
|
||||
}
|
||||
DebugOnly<mach_error_t> t = mach_override_ptr(d->Function, d->Wrapper,
|
||||
&d->Buffer);
|
||||
MOZ_ASSERT(t == err_none);
|
||||
}
|
||||
}
|
||||
|
||||
void OnlyReportDirtyWrites() {
|
||||
sOnlyReportDirtyWrites = true;
|
||||
}
|
||||
|
||||
void ClearPoisonIOInterposer() {
|
||||
// Not sure how or if we can unpoison the functions. Would be nice, but no
|
||||
// worries we won't need to do this anyway.
|
||||
sIsEnabled = false;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -1,222 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 ci et: */
|
||||
/* 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 "PoisonIOInterposer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
|
||||
#include <io.h>
|
||||
#include <windows.h>
|
||||
#include <winternl.h>
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/IOInterposer.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "nsTArray.h"
|
||||
#include "nsWindowsDllInterceptor.h"
|
||||
#include "plstr.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
namespace {
|
||||
|
||||
// Keep track of poisoned state. Notice that there is no reason to lock access
|
||||
// to this variable as it's only changed in InitPoisonIOInterposer and
|
||||
// ClearPoisonIOInterposer which may only be called on the main-thread when no
|
||||
// other threads are running.
|
||||
static bool sIOPoisoned = false;
|
||||
|
||||
/************************ Internal NT API Declarations ************************/
|
||||
|
||||
/**
|
||||
* Function pointer declaration for internal NT routine to write data to file.
|
||||
* For documentation on the NtWriteFile routine, see ZwWriteFile on MSDN.
|
||||
*/
|
||||
typedef NTSTATUS (WINAPI *NtWriteFileFn)(
|
||||
IN HANDLE aFileHandle,
|
||||
IN HANDLE aEvent,
|
||||
IN PIO_APC_ROUTINE aApc,
|
||||
IN PVOID aApcCtx,
|
||||
OUT PIO_STATUS_BLOCK aIoStatus,
|
||||
IN PVOID aBuffer,
|
||||
IN ULONG aLength,
|
||||
IN PLARGE_INTEGER aOffset,
|
||||
IN PULONG aKey
|
||||
);
|
||||
|
||||
/**
|
||||
* Function pointer declaration for internal NT routine to write data to file.
|
||||
* No documentation exists, see wine sources for details.
|
||||
*/
|
||||
typedef NTSTATUS (WINAPI *NtWriteFileGatherFn)(
|
||||
IN HANDLE aFileHandle,
|
||||
IN HANDLE aEvent,
|
||||
IN PIO_APC_ROUTINE aApc,
|
||||
IN PVOID aApcCtx,
|
||||
OUT PIO_STATUS_BLOCK aIoStatus,
|
||||
IN FILE_SEGMENT_ELEMENT* aSegments,
|
||||
IN ULONG aLength,
|
||||
IN PLARGE_INTEGER aOffset,
|
||||
IN PULONG aKey
|
||||
);
|
||||
|
||||
/*************************** Auxiliary Declarations ***************************/
|
||||
|
||||
/**
|
||||
* RAII class for timing the duration of an I/O call and reporting the result
|
||||
* to the IOInterposeObserver API.
|
||||
*/
|
||||
class WinIOAutoObservation : public IOInterposeObserver::Observation
|
||||
{
|
||||
public:
|
||||
WinIOAutoObservation(IOInterposeObserver::Operation aOp,
|
||||
const char* aReference, HANDLE aFileHandle)
|
||||
: mFileHandle(aFileHandle),
|
||||
mShouldObserve(IOInterposer::IsObservedOperation(aOp) &&
|
||||
!IsDebugFile(reinterpret_cast<intptr_t>(aFileHandle)))
|
||||
{
|
||||
if (mShouldObserve) {
|
||||
mOperation = aOp;
|
||||
mReference = aReference;
|
||||
mStart = TimeStamp::Now();
|
||||
}
|
||||
}
|
||||
|
||||
~WinIOAutoObservation()
|
||||
{
|
||||
if (mShouldObserve) {
|
||||
mEnd = TimeStamp::Now();
|
||||
// Report this observation
|
||||
IOInterposer::Report(*this);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
HANDLE mFileHandle;
|
||||
bool mShouldObserve;
|
||||
};
|
||||
|
||||
/*************************** IO Interposing Methods ***************************/
|
||||
|
||||
// Function pointers to original functions
|
||||
static NtWriteFileFn gOriginalNtWriteFile;
|
||||
static NtWriteFileGatherFn gOriginalNtWriteFileGather;
|
||||
|
||||
// Interposed NtWriteFile function
|
||||
static NTSTATUS WINAPI InterposedNtWriteFile(
|
||||
HANDLE aFileHandle,
|
||||
HANDLE aEvent,
|
||||
PIO_APC_ROUTINE aApc,
|
||||
PVOID aApcCtx,
|
||||
PIO_STATUS_BLOCK aIoStatus,
|
||||
PVOID aBuffer,
|
||||
ULONG aLength,
|
||||
PLARGE_INTEGER aOffset,
|
||||
PULONG aKey)
|
||||
{
|
||||
// Report IO
|
||||
const char* ref = "NtWriteFile";
|
||||
WinIOAutoObservation timer(IOInterposeObserver::OpWrite, ref, aFileHandle);
|
||||
|
||||
// Something is badly wrong if this function is undefined
|
||||
MOZ_ASSERT(gOriginalNtWriteFile);
|
||||
|
||||
// Execute original function
|
||||
return gOriginalNtWriteFile(
|
||||
aFileHandle,
|
||||
aEvent,
|
||||
aApc,
|
||||
aApcCtx,
|
||||
aIoStatus,
|
||||
aBuffer,
|
||||
aLength,
|
||||
aOffset,
|
||||
aKey
|
||||
);
|
||||
}
|
||||
|
||||
// Interposed NtWriteFileGather function
|
||||
static NTSTATUS WINAPI InterposedNtWriteFileGather(
|
||||
HANDLE aFileHandle,
|
||||
HANDLE aEvent,
|
||||
PIO_APC_ROUTINE aApc,
|
||||
PVOID aApcCtx,
|
||||
PIO_STATUS_BLOCK aIoStatus,
|
||||
FILE_SEGMENT_ELEMENT* aSegments,
|
||||
ULONG aLength,
|
||||
PLARGE_INTEGER aOffset,
|
||||
PULONG aKey)
|
||||
{
|
||||
// Report IO
|
||||
const char* ref = "NtWriteFileGather";
|
||||
WinIOAutoObservation timer(IOInterposeObserver::OpWrite, ref, aFileHandle);
|
||||
|
||||
// Something is badly wrong if this function is undefined
|
||||
MOZ_ASSERT(gOriginalNtWriteFileGather);
|
||||
|
||||
// Execute original function
|
||||
return gOriginalNtWriteFileGather(
|
||||
aFileHandle,
|
||||
aEvent,
|
||||
aApc,
|
||||
aApcCtx,
|
||||
aIoStatus,
|
||||
aSegments,
|
||||
aLength,
|
||||
aOffset,
|
||||
aKey
|
||||
);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
/******************************** IO Poisoning ********************************/
|
||||
|
||||
// Windows DLL interceptor
|
||||
static WindowsDllInterceptor sNtDllInterceptor;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
void InitPoisonIOInterposer() {
|
||||
// Don't poison twice... as this function may only be invoked on the main
|
||||
// thread when no other threads are running, it safe to allow multiple calls
|
||||
// to InitPoisonIOInterposer() without complaining (ie. failing assertions).
|
||||
if (sIOPoisoned) {
|
||||
return;
|
||||
}
|
||||
sIOPoisoned = true;
|
||||
|
||||
// Stdout and Stderr are OK.
|
||||
MozillaRegisterDebugFD(1);
|
||||
MozillaRegisterDebugFD(2);
|
||||
|
||||
// Initialize dll interceptor and add hooks
|
||||
sNtDllInterceptor.Init("ntdll.dll");
|
||||
sNtDllInterceptor.AddHook(
|
||||
"NtWriteFile",
|
||||
reinterpret_cast<intptr_t>(InterposedNtWriteFile),
|
||||
reinterpret_cast<void**>(&gOriginalNtWriteFile)
|
||||
);
|
||||
sNtDllInterceptor.AddHook(
|
||||
"NtWriteFileGather",
|
||||
reinterpret_cast<intptr_t>(InterposedNtWriteFileGather),
|
||||
reinterpret_cast<void**>(&gOriginalNtWriteFileGather)
|
||||
);
|
||||
}
|
||||
|
||||
void ClearPoisonIOInterposer() {
|
||||
MOZ_ASSERT(false);
|
||||
if (sIOPoisoned) {
|
||||
// Destroy the DLL interceptor
|
||||
sIOPoisoned = false;
|
||||
sNtDllInterceptor = WindowsDllInterceptor();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -17,9 +17,8 @@ EXPORTS += [
|
|||
|
||||
EXPORTS.mozilla += [
|
||||
'FileLocation.h',
|
||||
'LateWriteChecks.h',
|
||||
'mozPoisonWrite.h',
|
||||
'Omnijar.h',
|
||||
'PoisonIOInterposer.h',
|
||||
'ServiceList.h',
|
||||
'Services.h',
|
||||
'XPCOM.h',
|
||||
|
@ -27,6 +26,20 @@ EXPORTS.mozilla += [
|
|||
|
||||
if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
EXPORTS.mozilla += ['perfprobe.h']
|
||||
SOURCES += [
|
||||
'mozPoisonWriteBase.cpp',
|
||||
'mozPoisonWriteWin.cpp',
|
||||
'perfprobe.cpp',
|
||||
]
|
||||
elif CONFIG['OS_ARCH'] == 'Darwin':
|
||||
SOURCES += [
|
||||
'mozPoisonWriteBase.cpp',
|
||||
'mozPoisonWriteMac.cpp',
|
||||
]
|
||||
else:
|
||||
SOURCES += [
|
||||
'mozPoisonWriteStub.cpp',
|
||||
]
|
||||
|
||||
include('../glue/objs.mozbuild')
|
||||
|
||||
|
@ -36,24 +49,12 @@ SOURCES += xpcom_glue_src_cppsrcs
|
|||
SOURCES += [
|
||||
'FileLocation.cpp',
|
||||
'FrozenFunctions.cpp',
|
||||
'LateWriteChecks.cpp',
|
||||
'nsXPComInit.cpp',
|
||||
'nsXPCOMStrings.cpp',
|
||||
'Omnijar.cpp',
|
||||
'Services.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['OS_TARGET'] == 'Darwin':
|
||||
SOURCES += [
|
||||
'PoisonIOInterposerBase.cpp',
|
||||
'PoisonIOInterposerMac.cpp',
|
||||
]
|
||||
elif CONFIG['OS_TARGET'] == 'WINNT':
|
||||
SOURCES += [
|
||||
'PoisonIOInterposerBase.cpp',
|
||||
'PoisonIOInterposerWin.cpp',
|
||||
]
|
||||
|
||||
LIBXUL_LIBRARY = True
|
||||
|
||||
MSVC_ENABLE_PGO = True
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* vim:set ts=4 sw=4 sts=4 ci et: */
|
||||
/* 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 MOZPOISONWRITE_H
|
||||
#define MOZPOISONWRITE_H
|
||||
|
||||
#include "mozilla/Types.h"
|
||||
#include <stdio.h>
|
||||
|
||||
MOZ_BEGIN_EXTERN_C
|
||||
void MozillaRegisterDebugFD(int fd);
|
||||
void MozillaRegisterDebugFILE(FILE *f);
|
||||
void MozillaUnRegisterDebugFD(int fd);
|
||||
void MozillaUnRegisterDebugFILE(FILE *f);
|
||||
MOZ_END_EXTERN_C
|
||||
|
||||
#ifdef __cplusplus
|
||||
namespace mozilla {
|
||||
enum ShutdownChecksMode {
|
||||
SCM_CRASH,
|
||||
SCM_RECORD,
|
||||
SCM_NOTHING
|
||||
};
|
||||
extern ShutdownChecksMode gShutdownChecks;
|
||||
|
||||
void InitWritePoisoning();
|
||||
void PoisonWrite();
|
||||
void DisableWritePoisoning();
|
||||
void EnableWritePoisoning();
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,343 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 ci et: */
|
||||
/* 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 "mozPoisonWrite.h"
|
||||
#include "mozPoisonWriteBase.h"
|
||||
#include "mozilla/ProcessedStack.h"
|
||||
#include "mozilla/Scoped.h"
|
||||
#include "mozilla/SHA1.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "nsAppDirectoryServiceDefs.h"
|
||||
#include "nsDirectoryServiceUtils.h"
|
||||
#include "nsStackWalk.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "plstr.h"
|
||||
#include <algorithm>
|
||||
#ifdef XP_WIN
|
||||
#define NS_T(str) L ## str
|
||||
#define NS_SLASH "\\"
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#else
|
||||
#define NS_SLASH "/"
|
||||
#endif
|
||||
using namespace mozilla;
|
||||
|
||||
namespace {
|
||||
struct DebugFilesAutoLockTraits {
|
||||
typedef PRLock *type;
|
||||
const static type empty() {
|
||||
return nullptr;
|
||||
}
|
||||
const static void release(type aL) {
|
||||
PR_Unlock(aL);
|
||||
}
|
||||
};
|
||||
|
||||
class DebugFilesAutoLock : public Scoped<DebugFilesAutoLockTraits> {
|
||||
static PRLock *Lock;
|
||||
public:
|
||||
static void Clear();
|
||||
static PRLock *getDebugFileIDsLock() {
|
||||
// On windows this static is not thread safe, but we know that the first
|
||||
// call is from
|
||||
// * An early registration of a debug FD or
|
||||
// * The call to InitWritePoisoning.
|
||||
// Since the early debug FDs are logs created early in the main thread
|
||||
// and no writes are trapped before InitWritePoisoning, we are safe.
|
||||
static bool Initialized = false;
|
||||
if (!Initialized) {
|
||||
Lock = PR_NewLock();
|
||||
Initialized = true;
|
||||
}
|
||||
|
||||
// We have to use something lower level than a mutex. If we don't, we
|
||||
// can get recursive in here when called from logging a call to free.
|
||||
return Lock;
|
||||
}
|
||||
|
||||
DebugFilesAutoLock() :
|
||||
Scoped<DebugFilesAutoLockTraits>(getDebugFileIDsLock()) {
|
||||
PR_Lock(get());
|
||||
}
|
||||
};
|
||||
|
||||
PRLock *DebugFilesAutoLock::Lock;
|
||||
void DebugFilesAutoLock::Clear() {
|
||||
MOZ_ASSERT(Lock != nullptr);
|
||||
Lock = nullptr;
|
||||
}
|
||||
|
||||
static char *sProfileDirectory = nullptr;
|
||||
|
||||
// Return a vector used to hold the IDs of the current debug files. On unix
|
||||
// an ID is a file descriptor. On Windows it is a file HANDLE.
|
||||
std::vector<intptr_t>* getDebugFileIDs() {
|
||||
PRLock *lock = DebugFilesAutoLock::getDebugFileIDsLock();
|
||||
PR_ASSERT_CURRENT_THREAD_OWNS_LOCK(lock);
|
||||
// We have to use new as some write happen during static destructors
|
||||
// so an static std::vector might be destroyed while we still need it.
|
||||
static std::vector<intptr_t> *DebugFileIDs = new std::vector<intptr_t>();
|
||||
return DebugFileIDs;
|
||||
}
|
||||
|
||||
// This a wrapper over a file descriptor that provides a Printf method and
|
||||
// computes the sha1 of the data that passes through it.
|
||||
class SHA1Stream
|
||||
{
|
||||
public:
|
||||
explicit SHA1Stream(FILE *stream)
|
||||
: mFile(stream)
|
||||
{
|
||||
MozillaRegisterDebugFILE(mFile);
|
||||
}
|
||||
|
||||
void Printf(const char *aFormat, ...)
|
||||
{
|
||||
MOZ_ASSERT(mFile);
|
||||
va_list list;
|
||||
va_start(list, aFormat);
|
||||
nsAutoCString str;
|
||||
str.AppendPrintf(aFormat, list);
|
||||
va_end(list);
|
||||
mSHA1.update(str.get(), str.Length());
|
||||
fwrite(str.get(), 1, str.Length(), mFile);
|
||||
}
|
||||
void Finish(SHA1Sum::Hash &aHash)
|
||||
{
|
||||
int fd = fileno(mFile);
|
||||
fflush(mFile);
|
||||
MozillaUnRegisterDebugFD(fd);
|
||||
fclose(mFile);
|
||||
mSHA1.finish(aHash);
|
||||
mFile = nullptr;
|
||||
}
|
||||
private:
|
||||
FILE *mFile;
|
||||
SHA1Sum mSHA1;
|
||||
};
|
||||
|
||||
static void RecordStackWalker(void *aPC, void *aSP, void *aClosure)
|
||||
{
|
||||
std::vector<uintptr_t> *stack =
|
||||
static_cast<std::vector<uintptr_t>*>(aClosure);
|
||||
stack->push_back(reinterpret_cast<uintptr_t>(aPC));
|
||||
}
|
||||
|
||||
|
||||
enum PoisonState {
|
||||
POISON_UNINITIALIZED = 0,
|
||||
POISON_ON,
|
||||
POISON_OFF
|
||||
};
|
||||
|
||||
// POISON_OFF has two consequences
|
||||
// * It prevents PoisonWrite from patching the write functions.
|
||||
// * If the patching has already been done, it prevents AbortOnBadWrite from
|
||||
// asserting. Note that not all writes use AbortOnBadWrite at this point
|
||||
// (aio_write for example), so disabling writes after patching doesn't
|
||||
// completely undo it.
|
||||
PoisonState sPoisoningState = POISON_UNINITIALIZED;
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
void InitWritePoisoning()
|
||||
{
|
||||
// Stdout and Stderr are OK.
|
||||
MozillaRegisterDebugFD(1);
|
||||
MozillaRegisterDebugFD(2);
|
||||
|
||||
nsCOMPtr<nsIFile> mozFile;
|
||||
NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mozFile));
|
||||
if (mozFile) {
|
||||
nsAutoCString nativePath;
|
||||
nsresult rv = mozFile->GetNativePath(nativePath);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
sProfileDirectory = PL_strdup(nativePath.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ValidWriteAssert(bool ok)
|
||||
{
|
||||
if (gShutdownChecks == SCM_CRASH && !ok) {
|
||||
MOZ_CRASH();
|
||||
}
|
||||
|
||||
// We normally don't poison writes if gShutdownChecks is SCM_NOTHING, but
|
||||
// write poisoning can get more users in the future (profiling for example),
|
||||
// so make sure we behave correctly.
|
||||
if (gShutdownChecks == SCM_NOTHING || ok || !sProfileDirectory ||
|
||||
!Telemetry::CanRecord()) {
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Write the stack and loaded libraries to a file. We can get here
|
||||
// concurrently from many writes, so we use multiple temporary files.
|
||||
std::vector<uintptr_t> rawStack;
|
||||
|
||||
NS_StackWalk(RecordStackWalker, /* skipFrames */ 0, /* maxFrames */ 0,
|
||||
reinterpret_cast<void*>(&rawStack), 0, nullptr);
|
||||
Telemetry::ProcessedStack stack = Telemetry::GetStackAndModules(rawStack);
|
||||
|
||||
nsPrintfCString nameAux("%s%s%s", sProfileDirectory,
|
||||
NS_SLASH, "Telemetry.LateWriteTmpXXXXXX");
|
||||
char *name;
|
||||
nameAux.GetMutableData(&name);
|
||||
|
||||
// We want the sha1 of the entire file, so please don't write to fd
|
||||
// directly; use sha1Stream.
|
||||
FILE *stream;
|
||||
#ifdef XP_WIN
|
||||
HANDLE hFile;
|
||||
do {
|
||||
// mkstemp isn't supported so keep trying until we get a file
|
||||
int result = _mktemp_s(name, strlen(name) + 1);
|
||||
hFile = CreateFileA(name, GENERIC_WRITE, 0, nullptr,
|
||||
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
} while (GetLastError() == ERROR_FILE_EXISTS);
|
||||
|
||||
if (hFile == INVALID_HANDLE_VALUE) {
|
||||
NS_RUNTIMEABORT("Um, how did we get here?");
|
||||
}
|
||||
|
||||
// http://support.microsoft.com/kb/139640
|
||||
int fd = _open_osfhandle((intptr_t)hFile, _O_APPEND);
|
||||
if (fd == -1) {
|
||||
NS_RUNTIMEABORT("Um, how did we get here?");
|
||||
}
|
||||
|
||||
stream = _fdopen(fd, "w");
|
||||
#else
|
||||
int fd = mkstemp(name);
|
||||
stream = fdopen(fd, "w");
|
||||
#endif
|
||||
|
||||
SHA1Stream sha1Stream(stream);
|
||||
|
||||
size_t numModules = stack.GetNumModules();
|
||||
sha1Stream.Printf("%u\n", (unsigned)numModules);
|
||||
for (size_t i = 0; i < numModules; ++i) {
|
||||
Telemetry::ProcessedStack::Module module = stack.GetModule(i);
|
||||
sha1Stream.Printf("%s %s\n", module.mBreakpadId.c_str(),
|
||||
module.mName.c_str());
|
||||
}
|
||||
|
||||
size_t numFrames = stack.GetStackSize();
|
||||
sha1Stream.Printf("%u\n", (unsigned)numFrames);
|
||||
for (size_t i = 0; i < numFrames; ++i) {
|
||||
const Telemetry::ProcessedStack::Frame &frame =
|
||||
stack.GetFrame(i);
|
||||
// NOTE: We write the offsets, while the atos tool expects a value with
|
||||
// the virtual address added. For example, running otool -l on the the firefox
|
||||
// binary shows
|
||||
// cmd LC_SEGMENT_64
|
||||
// cmdsize 632
|
||||
// segname __TEXT
|
||||
// vmaddr 0x0000000100000000
|
||||
// so to print the line matching the offset 123 one has to run
|
||||
// atos -o firefox 0x100000123.
|
||||
sha1Stream.Printf("%d %x\n", frame.mModIndex, (unsigned)frame.mOffset);
|
||||
}
|
||||
|
||||
SHA1Sum::Hash sha1;
|
||||
sha1Stream.Finish(sha1);
|
||||
|
||||
// Note: These files should be deleted by telemetry once it reads them. If
|
||||
// there were no telemery runs by the time we shut down, we just add files
|
||||
// to the existing ones instead of replacing them. Given that each of these
|
||||
// files is a bug to be fixed, that is probably the right thing to do.
|
||||
|
||||
// We append the sha1 of the contents to the file name. This provides a simple
|
||||
// client side deduplication.
|
||||
nsPrintfCString finalName("%s%s", sProfileDirectory, "/Telemetry.LateWriteFinal-");
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
finalName.AppendPrintf("%02x", sha1[i]);
|
||||
}
|
||||
PR_Delete(finalName.get());
|
||||
PR_Rename(name, finalName.get());
|
||||
return false;
|
||||
}
|
||||
|
||||
void DisableWritePoisoning() {
|
||||
if (sPoisoningState != POISON_ON)
|
||||
return;
|
||||
|
||||
sPoisoningState = POISON_OFF;
|
||||
PL_strfree(sProfileDirectory);
|
||||
sProfileDirectory = nullptr;
|
||||
|
||||
PRLock *Lock;
|
||||
{
|
||||
DebugFilesAutoLock lockedScope;
|
||||
delete getDebugFileIDs();
|
||||
Lock = DebugFilesAutoLock::getDebugFileIDsLock();
|
||||
DebugFilesAutoLock::Clear();
|
||||
}
|
||||
PR_DestroyLock(Lock);
|
||||
}
|
||||
void EnableWritePoisoning() {
|
||||
sPoisoningState = POISON_ON;
|
||||
}
|
||||
|
||||
bool PoisonWriteEnabled()
|
||||
{
|
||||
return sPoisoningState == POISON_ON;
|
||||
}
|
||||
|
||||
bool IsDebugFile(intptr_t aFileID) {
|
||||
DebugFilesAutoLock lockedScope;
|
||||
|
||||
std::vector<intptr_t> &Vec = *getDebugFileIDs();
|
||||
return std::find(Vec.begin(), Vec.end(), aFileID) != Vec.end();
|
||||
}
|
||||
|
||||
} // mozilla
|
||||
|
||||
extern "C" {
|
||||
void MozillaRegisterDebugFD(int fd) {
|
||||
if (sPoisoningState == POISON_OFF)
|
||||
return;
|
||||
DebugFilesAutoLock lockedScope;
|
||||
intptr_t fileId = FileDescriptorToID(fd);
|
||||
std::vector<intptr_t> &Vec = *getDebugFileIDs();
|
||||
MOZ_ASSERT(std::find(Vec.begin(), Vec.end(), fileId) == Vec.end());
|
||||
Vec.push_back(fileId);
|
||||
}
|
||||
void MozillaRegisterDebugFILE(FILE *f) {
|
||||
if (sPoisoningState == POISON_OFF)
|
||||
return;
|
||||
int fd = fileno(f);
|
||||
if (fd == 1 || fd == 2)
|
||||
return;
|
||||
MozillaRegisterDebugFD(fd);
|
||||
}
|
||||
void MozillaUnRegisterDebugFD(int fd) {
|
||||
if (sPoisoningState == POISON_OFF)
|
||||
return;
|
||||
DebugFilesAutoLock lockedScope;
|
||||
intptr_t fileId = FileDescriptorToID(fd);
|
||||
std::vector<intptr_t> &Vec = *getDebugFileIDs();
|
||||
std::vector<intptr_t>::iterator i =
|
||||
std::find(Vec.begin(), Vec.end(), fileId);
|
||||
MOZ_ASSERT(i != Vec.end());
|
||||
Vec.erase(i);
|
||||
}
|
||||
void MozillaUnRegisterDebugFILE(FILE *f) {
|
||||
if (sPoisoningState == POISON_OFF)
|
||||
return;
|
||||
int fd = fileno(f);
|
||||
if (fd == 1 || fd == 2)
|
||||
return;
|
||||
fflush(f);
|
||||
MozillaUnRegisterDebugFD(fd);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 ci et: */
|
||||
/* 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/. */
|
||||
|
||||
// Private interface for code shared between the platforms of mozPoisonWriteXXX
|
||||
|
||||
#ifndef MOZPOISONWRITEBASE_H
|
||||
#define MOZPOISONWRITEBASE_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
#include "nspr.h"
|
||||
#include "mozilla/NullPtr.h"
|
||||
#include "mozilla/Util.h"
|
||||
#include "mozilla/Scoped.h"
|
||||
|
||||
namespace mozilla {
|
||||
bool PoisonWriteEnabled();
|
||||
bool ValidWriteAssert(bool ok);
|
||||
bool IsDebugFile(intptr_t aFileID);
|
||||
intptr_t FileDescriptorToID(int aFd);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,234 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* vim:set ts=4 sw=4 sts=4 ci et: */
|
||||
/* 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/mozPoisonWrite.h"
|
||||
#include "mozPoisonWriteBase.h"
|
||||
#include "mozilla/Util.h"
|
||||
#include "nsTraceRefcntImpl.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/Scoped.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "mozilla/ProcessedStack.h"
|
||||
#include "nsStackWalk.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "mach_override.h"
|
||||
#include "prio.h"
|
||||
#include "plstr.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsAppDirectoryServiceDefs.h"
|
||||
#include "nsDirectoryServiceUtils.h"
|
||||
#include "mozilla/SHA1.h"
|
||||
#include <sys/stat.h>
|
||||
#include <sys/socket.h>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <string.h>
|
||||
#include <sys/uio.h>
|
||||
#include <aio.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
namespace {
|
||||
using namespace mozilla;
|
||||
|
||||
struct FuncData {
|
||||
const char *Name; // Name of the function for the ones we use dlsym
|
||||
const void *Wrapper; // The function that we will replace 'Function' with
|
||||
void *Function; // The function that will be replaced with 'Wrapper'
|
||||
void *Buffer; // Will point to the jump buffer that lets us call
|
||||
// 'Function' after it has been replaced.
|
||||
};
|
||||
|
||||
// Wrap aio_write. We have not seen it before, so just assert/report it.
|
||||
typedef ssize_t (*aio_write_t)(struct aiocb *aiocbp);
|
||||
ssize_t wrap_aio_write(struct aiocb *aiocbp);
|
||||
FuncData aio_write_data = { 0, (void*) wrap_aio_write, (void*) aio_write };
|
||||
ssize_t wrap_aio_write(struct aiocb *aiocbp) {
|
||||
ValidWriteAssert(0);
|
||||
aio_write_t old_write = (aio_write_t) aio_write_data.Buffer;
|
||||
return old_write(aiocbp);
|
||||
}
|
||||
|
||||
// Wrap pwrite-like functions.
|
||||
// We have not seen them before, so just assert/report it.
|
||||
typedef ssize_t (*pwrite_t)(int fd, const void *buf, size_t nbyte, off_t offset);
|
||||
template<FuncData &foo>
|
||||
ssize_t wrap_pwrite_temp(int fd, const void *buf, size_t nbyte, off_t offset) {
|
||||
ValidWriteAssert(0);
|
||||
pwrite_t old_write = (pwrite_t) foo.Buffer;
|
||||
return old_write(fd, buf, nbyte, offset);
|
||||
}
|
||||
|
||||
// Define a FuncData for a pwrite-like functions.
|
||||
#define DEFINE_PWRITE_DATA(X, NAME) \
|
||||
FuncData X ## _data = { NAME, (void*) wrap_pwrite_temp<X ## _data> }; \
|
||||
|
||||
// This exists everywhere.
|
||||
DEFINE_PWRITE_DATA(pwrite, "pwrite")
|
||||
// These exist on 32 bit OS X
|
||||
DEFINE_PWRITE_DATA(pwrite_NOCANCEL_UNIX2003, "pwrite$NOCANCEL$UNIX2003");
|
||||
DEFINE_PWRITE_DATA(pwrite_UNIX2003, "pwrite$UNIX2003");
|
||||
// This exists on 64 bit OS X
|
||||
DEFINE_PWRITE_DATA(pwrite_NOCANCEL, "pwrite$NOCANCEL");
|
||||
|
||||
void AbortOnBadWrite(int fd, const void *wbuf, size_t count);
|
||||
|
||||
typedef ssize_t (*writev_t)(int fd, const struct iovec *iov, int iovcnt);
|
||||
template<FuncData &foo>
|
||||
ssize_t wrap_writev_temp(int fd, const struct iovec *iov, int iovcnt) {
|
||||
AbortOnBadWrite(fd, 0, iovcnt);
|
||||
writev_t old_write = (writev_t) foo.Buffer;
|
||||
return old_write(fd, iov, iovcnt);
|
||||
}
|
||||
|
||||
// Define a FuncData for a writev-like functions.
|
||||
#define DEFINE_WRITEV_DATA(X, NAME) \
|
||||
FuncData X ## _data = { NAME, (void*) wrap_writev_temp<X ## _data> }; \
|
||||
|
||||
// This exists everywhere.
|
||||
DEFINE_WRITEV_DATA(writev, "writev");
|
||||
// These exist on 32 bit OS X
|
||||
DEFINE_WRITEV_DATA(writev_NOCANCEL_UNIX2003, "writev$NOCANCEL$UNIX2003");
|
||||
DEFINE_WRITEV_DATA(writev_UNIX2003, "writev$UNIX2003");
|
||||
// This exists on 64 bit OS X
|
||||
DEFINE_WRITEV_DATA(writev_NOCANCEL, "writev$NOCANCEL");
|
||||
|
||||
typedef ssize_t (*write_t)(int fd, const void *buf, size_t count);
|
||||
template<FuncData &foo>
|
||||
ssize_t wrap_write_temp(int fd, const void *buf, size_t count) {
|
||||
AbortOnBadWrite(fd, buf, count);
|
||||
write_t old_write = (write_t) foo.Buffer;
|
||||
return old_write(fd, buf, count);
|
||||
}
|
||||
|
||||
// Define a FuncData for a write-like functions.
|
||||
#define DEFINE_WRITE_DATA(X, NAME) \
|
||||
FuncData X ## _data = { NAME, (void*) wrap_write_temp<X ## _data> }; \
|
||||
|
||||
// This exists everywhere.
|
||||
DEFINE_WRITE_DATA(write, "write");
|
||||
// These exist on 32 bit OS X
|
||||
DEFINE_WRITE_DATA(write_NOCANCEL_UNIX2003, "write$NOCANCEL$UNIX2003");
|
||||
DEFINE_WRITE_DATA(write_UNIX2003, "write$UNIX2003");
|
||||
// This exists on 64 bit OS X
|
||||
DEFINE_WRITE_DATA(write_NOCANCEL, "write$NOCANCEL");
|
||||
|
||||
FuncData *Functions[] = { &aio_write_data,
|
||||
|
||||
&pwrite_data,
|
||||
&pwrite_NOCANCEL_UNIX2003_data,
|
||||
&pwrite_UNIX2003_data,
|
||||
&pwrite_NOCANCEL_data,
|
||||
|
||||
&write_data,
|
||||
&write_NOCANCEL_UNIX2003_data,
|
||||
&write_UNIX2003_data,
|
||||
&write_NOCANCEL_data,
|
||||
|
||||
&writev_data,
|
||||
&writev_NOCANCEL_UNIX2003_data,
|
||||
&writev_UNIX2003_data,
|
||||
&writev_NOCANCEL_data};
|
||||
|
||||
const int NumFunctions = ArrayLength(Functions);
|
||||
|
||||
// We want to detect "actual" writes, not IPC. Some IPC mechanisms are
|
||||
// implemented with file descriptors, so filter them out.
|
||||
bool IsIPCWrite(int fd, const struct stat &buf) {
|
||||
if ((buf.st_mode & S_IFMT) == S_IFIFO) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((buf.st_mode & S_IFMT) != S_IFSOCK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sockaddr_storage address;
|
||||
socklen_t len = sizeof(address);
|
||||
if (getsockname(fd, (sockaddr*) &address, &len) != 0) {
|
||||
return true; // Ignore the fd if we can't find what it is.
|
||||
}
|
||||
|
||||
return address.ss_family == AF_UNIX;
|
||||
}
|
||||
|
||||
void AbortOnBadWrite(int fd, const void *wbuf, size_t count) {
|
||||
if (!PoisonWriteEnabled())
|
||||
return;
|
||||
|
||||
// Ignore writes of zero bytes, firefox does some during shutdown.
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
struct stat buf;
|
||||
int rv = fstat(fd, &buf);
|
||||
if (!ValidWriteAssert(rv == 0))
|
||||
return;
|
||||
|
||||
if (IsIPCWrite(fd, buf))
|
||||
return;
|
||||
|
||||
// Debugging FDs are OK
|
||||
if (IsDebugFile(fd))
|
||||
return;
|
||||
|
||||
// For writev we pass nullptr in wbuf. We should only get here from
|
||||
// dbm, and it uses write, so assert that we have wbuf.
|
||||
if (!ValidWriteAssert(wbuf))
|
||||
return;
|
||||
|
||||
// As a really bad hack, accept writes that don't change the on disk
|
||||
// content. This is needed because dbm doesn't keep track of dirty bits
|
||||
// and can end up writing the same data to disk twice. Once when the
|
||||
// user (nss) asks it to sync and once when closing the database.
|
||||
ScopedFreePtr<void> wbuf2(malloc(count));
|
||||
if (!ValidWriteAssert(wbuf2))
|
||||
return;
|
||||
off_t pos = lseek(fd, 0, SEEK_CUR);
|
||||
if (!ValidWriteAssert(pos != -1))
|
||||
return;
|
||||
ssize_t r = read(fd, wbuf2, count);
|
||||
if (!ValidWriteAssert(r == static_cast<ssize_t>(count)))
|
||||
return;
|
||||
int cmp = memcmp(wbuf, wbuf2, count);
|
||||
if (!ValidWriteAssert(cmp == 0))
|
||||
return;
|
||||
off_t pos2 = lseek(fd, pos, SEEK_SET);
|
||||
if (!ValidWriteAssert(pos2 == pos))
|
||||
return;
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
intptr_t FileDescriptorToID(int aFd) {
|
||||
return aFd;
|
||||
}
|
||||
|
||||
void PoisonWrite() {
|
||||
// Quick sanity check that we don't poison twice.
|
||||
static bool WritesArePoisoned = false;
|
||||
MOZ_ASSERT(!WritesArePoisoned);
|
||||
if (WritesArePoisoned)
|
||||
return;
|
||||
WritesArePoisoned = true;
|
||||
|
||||
if (!PoisonWriteEnabled())
|
||||
return;
|
||||
|
||||
for (int i = 0; i < NumFunctions; ++i) {
|
||||
FuncData *d = Functions[i];
|
||||
if (!d->Function)
|
||||
d->Function = dlsym(RTLD_DEFAULT, d->Name);
|
||||
if (!d->Function)
|
||||
continue;
|
||||
DebugOnly<mach_error_t> t = mach_override_ptr(d->Function, d->Wrapper,
|
||||
&d->Buffer);
|
||||
MOZ_ASSERT(t == err_none);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* vim:set ts=4 sw=4 sts=4 ci et: */
|
||||
/* 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 <stdio.h>
|
||||
namespace mozilla {
|
||||
void PoisonWrite() {
|
||||
}
|
||||
void DisableWritePoisoning() {
|
||||
}
|
||||
void EnableWritePoisoning() {
|
||||
}
|
||||
void InitWritePoisoning() {
|
||||
}
|
||||
}
|
||||
extern "C" {
|
||||
void MozillaRegisterDebugFD(int fd) {
|
||||
}
|
||||
void MozillaRegisterDebugFILE(FILE *f) {
|
||||
}
|
||||
void MozillaUnRegisterDebugFD(int fd) {
|
||||
}
|
||||
void MozillaUnRegisterDebugFILE(FILE *f) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 ci et: */
|
||||
/* 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 <stdio.h>
|
||||
#include <windows.h>
|
||||
#include <winternl.h> // NTSTATUS
|
||||
#include <io.h>
|
||||
#include "nsWindowsDllInterceptor.h"
|
||||
#include "mozilla/mozPoisonWrite.h"
|
||||
#include "mozPoisonWriteBase.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
intptr_t FileDescriptorToID(int aFd) {
|
||||
return _get_osfhandle(aFd);
|
||||
}
|
||||
|
||||
static WindowsDllInterceptor sNtDllInterceptor;
|
||||
|
||||
void AbortOnBadWrite(HANDLE);
|
||||
bool ValidWriteAssert(bool ok);
|
||||
|
||||
typedef NTSTATUS (WINAPI* WriteFile_fn)(HANDLE, HANDLE, PIO_APC_ROUTINE,
|
||||
void*, PIO_STATUS_BLOCK,
|
||||
const void*, ULONG, PLARGE_INTEGER,
|
||||
PULONG);
|
||||
WriteFile_fn gOriginalWriteFile;
|
||||
|
||||
static NTSTATUS WINAPI
|
||||
patched_WriteFile(HANDLE aFile, HANDLE aEvent, PIO_APC_ROUTINE aApc,
|
||||
void* aApcUser, PIO_STATUS_BLOCK aIoStatus,
|
||||
const void* aBuffer, ULONG aLength,
|
||||
PLARGE_INTEGER aOffset, PULONG aKey)
|
||||
{
|
||||
AbortOnBadWrite(aFile);
|
||||
return gOriginalWriteFile(aFile, aEvent, aApc, aApcUser, aIoStatus,
|
||||
aBuffer, aLength, aOffset, aKey);
|
||||
}
|
||||
|
||||
|
||||
typedef NTSTATUS (WINAPI* WriteFileGather_fn)(HANDLE, HANDLE, PIO_APC_ROUTINE,
|
||||
void*, PIO_STATUS_BLOCK,
|
||||
FILE_SEGMENT_ELEMENT*,
|
||||
ULONG, PLARGE_INTEGER, PULONG);
|
||||
WriteFileGather_fn gOriginalWriteFileGather;
|
||||
|
||||
static NTSTATUS WINAPI
|
||||
patched_WriteFileGather(HANDLE aFile, HANDLE aEvent, PIO_APC_ROUTINE aApc,
|
||||
void* aApcUser, PIO_STATUS_BLOCK aIoStatus,
|
||||
FILE_SEGMENT_ELEMENT* aSegments, ULONG aLength,
|
||||
PLARGE_INTEGER aOffset, PULONG aKey)
|
||||
{
|
||||
AbortOnBadWrite(aFile);
|
||||
return gOriginalWriteFileGather(aFile, aEvent, aApc, aApcUser, aIoStatus,
|
||||
aSegments, aLength, aOffset, aKey);
|
||||
}
|
||||
|
||||
void AbortOnBadWrite(HANDLE aFile)
|
||||
{
|
||||
if (!PoisonWriteEnabled())
|
||||
return;
|
||||
|
||||
// Debugging files are OK.
|
||||
if (IsDebugFile(reinterpret_cast<intptr_t>(aFile)))
|
||||
return;
|
||||
|
||||
ValidWriteAssert(false);
|
||||
}
|
||||
|
||||
void PoisonWrite() {
|
||||
// Quick sanity check that we don't poison twice.
|
||||
static bool WritesArePoisoned = false;
|
||||
MOZ_ASSERT(!WritesArePoisoned);
|
||||
if (WritesArePoisoned)
|
||||
return;
|
||||
WritesArePoisoned = true;
|
||||
|
||||
if (!PoisonWriteEnabled())
|
||||
return;
|
||||
|
||||
sNtDllInterceptor.Init("ntdll.dll");
|
||||
sNtDllInterceptor.AddHook("NtWriteFile", reinterpret_cast<intptr_t>(patched_WriteFile), reinterpret_cast<void**>(&gOriginalWriteFile));
|
||||
sNtDllInterceptor.AddHook("NtWriteFileGather", reinterpret_cast<intptr_t>(patched_WriteFileGather), reinterpret_cast<void**>(&gOriginalWriteFileGather));
|
||||
}
|
||||
}
|
||||
|
|
@ -109,9 +109,7 @@ extern nsresult nsStringInputStreamConstructor(nsISupports *, REFNSIID, void **)
|
|||
|
||||
#include "nsChromeRegistry.h"
|
||||
#include "nsChromeProtocolHandler.h"
|
||||
#include "mozilla/IOInterposer.h"
|
||||
#include "mozilla/PoisonIOInterposer.h"
|
||||
#include "mozilla/LateWriteChecks.h"
|
||||
#include "mozilla/mozPoisonWrite.h"
|
||||
|
||||
#include "mozilla/scache/StartupCache.h"
|
||||
|
||||
|
@ -684,10 +682,10 @@ ShutdownXPCOM(nsIServiceManager* servMgr)
|
|||
|
||||
HangMonitor::NotifyActivity();
|
||||
|
||||
// Late-write checks needs to find the profile directory, so it has to
|
||||
// Write poisoning needs to find the profile directory, so it has to
|
||||
// be initialized before mozilla::services::Shutdown or (because of
|
||||
// xpcshell tests replacing the service) modules being unloaded.
|
||||
mozilla::InitLateWriteChecks();
|
||||
InitWritePoisoning();
|
||||
|
||||
// We save the "xpcom-shutdown-loaders" observers to notify after
|
||||
// the observerservice is gone.
|
||||
|
@ -755,15 +753,7 @@ ShutdownXPCOM(nsIServiceManager* servMgr)
|
|||
PROFILER_MARKER("Shutdown xpcom");
|
||||
// If we are doing any shutdown checks, poison writes.
|
||||
if (gShutdownChecks != SCM_NOTHING) {
|
||||
// Calling InitIOInterposer or InitPoisonIOInterposer twice doesn't
|
||||
// cause any problems, they'll safely abort the initialization on their
|
||||
// own initiative.
|
||||
mozilla::IOInterposer::Init();
|
||||
mozilla::InitPoisonIOInterposer();
|
||||
#ifdef XP_MACOSX
|
||||
mozilla::OnlyReportDirtyWrites();
|
||||
#endif /* XP_MACOSX */
|
||||
mozilla::BeginLateWriteChecks();
|
||||
mozilla::PoisonWrite();
|
||||
}
|
||||
|
||||
// Shutdown nsLocalFile string conversion
|
||||
|
|
|
@ -453,7 +453,7 @@ XRE_API(void,
|
|||
XRE_InitOmnijar, (nsIFile* greOmni,
|
||||
nsIFile* appOmni))
|
||||
XRE_API(void,
|
||||
XRE_StopLateWriteChecks, (void))
|
||||
XRE_DisableWritePoisoning, (void))
|
||||
|
||||
#ifdef XP_WIN
|
||||
/**
|
||||
|
|
Загрузка…
Ссылка в новой задаче