diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /xpcom/build | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream/115.8.0esr.tar.xz firefox-esr-upstream/115.8.0esr.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
41 files changed, 8828 insertions, 0 deletions
diff --git a/xpcom/build/BinaryPath.h b/xpcom/build/BinaryPath.h new file mode 100644 index 0000000000..47bd6940cb --- /dev/null +++ b/xpcom/build/BinaryPath.h @@ -0,0 +1,321 @@ +/* -*- 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_BinaryPath_h +#define mozilla_BinaryPath_h + +#include "nsXPCOMPrivate.h" // for MAXPATHLEN +#ifdef XP_WIN +# include <windows.h> +#elif defined(XP_DARWIN) +# include <CoreFoundation/CoreFoundation.h> +#elif defined(XP_UNIX) +# include <unistd.h> +# include <stdlib.h> +# include <string.h> +#endif +#if defined(__FreeBSD__) || defined(__DragonFly__) || \ + defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__OpenBSD__) +# include <sys/sysctl.h> +#endif +#if defined(__OpenBSD__) +# include <sys/stat.h> +#endif +#include "mozilla/UniquePtr.h" +#include "mozilla/UniquePtrExtensions.h" + +#ifdef MOZILLA_INTERNAL_API +# include "nsCOMPtr.h" +# include "nsIFile.h" +# include "nsString.h" +#endif + +namespace mozilla { + +class BinaryPath { + public: +#ifdef XP_WIN + static nsresult Get(char aResult[MAXPATHLEN]) { + wchar_t wide_path[MAXPATHLEN]; + nsresult rv = GetW(wide_path); + if (NS_FAILED(rv)) { + return rv; + } + WideCharToMultiByte(CP_UTF8, 0, wide_path, -1, aResult, MAXPATHLEN, nullptr, + nullptr); + return NS_OK; + } + + static nsresult GetLong(wchar_t aResult[MAXPATHLEN]) { + static bool cached = false; + static wchar_t exeLongPath[MAXPATHLEN] = L""; + + if (!cached) { + nsresult rv = GetW(exeLongPath); + + if (NS_FAILED(rv)) { + return rv; + } + + if (!::GetLongPathNameW(exeLongPath, exeLongPath, MAXPATHLEN)) { + return NS_ERROR_FAILURE; + } + + cached = true; + } + + if (wcscpy_s(aResult, MAXPATHLEN, exeLongPath)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; + } + + private: + static nsresult GetW(wchar_t aResult[MAXPATHLEN]) { + static bool cached = false; + static wchar_t moduleFileName[MAXPATHLEN] = L""; + + if (!cached) { + if (!::GetModuleFileNameW(0, moduleFileName, MAXPATHLEN)) { + return NS_ERROR_FAILURE; + } + + cached = true; + } + + if (wcscpy_s(aResult, MAXPATHLEN, moduleFileName)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; + } + +#elif defined(XP_DARWIN) + static nsresult Get(char aResult[MAXPATHLEN]) { + // Works even if we're not bundled. + CFBundleRef appBundle = CFBundleGetMainBundle(); + if (!appBundle) { + return NS_ERROR_FAILURE; + } + + CFURLRef executableURL = CFBundleCopyExecutableURL(appBundle); + if (!executableURL) { + return NS_ERROR_FAILURE; + } + + nsresult rv; + if (CFURLGetFileSystemRepresentation(executableURL, false, (UInt8*)aResult, + MAXPATHLEN)) { + // Sanitize path in case the app was launched from Terminal via + // './firefox' for example. + size_t readPos = 0; + size_t writePos = 0; + while (aResult[readPos] != '\0') { + if (aResult[readPos] == '.' && aResult[readPos + 1] == '/') { + readPos += 2; + } else { + aResult[writePos] = aResult[readPos]; + readPos++; + writePos++; + } + } + aResult[writePos] = '\0'; + rv = NS_OK; + } else { + rv = NS_ERROR_FAILURE; + } + + CFRelease(executableURL); + return rv; + } + +#elif defined(ANDROID) + static nsresult Get(char aResult[MAXPATHLEN]) { + // On Android, we use the MOZ_ANDROID_LIBDIR variable that is set by the + // Java bootstrap code. + const char* libDir = getenv("MOZ_ANDROID_LIBDIR"); + if (!libDir) { + return NS_ERROR_FAILURE; + } + + snprintf(aResult, MAXPATHLEN, "%s/%s", libDir, "dummy"); + aResult[MAXPATHLEN - 1] = '\0'; + return NS_OK; + } + +#elif defined(XP_LINUX) || defined(XP_SOLARIS) + static nsresult Get(char aResult[MAXPATHLEN]) { +# if defined(XP_SOLARIS) + const char path[] = "/proc/self/path/a.out"; +# else + const char path[] = "/proc/self/exe"; +# endif + + ssize_t len = readlink(path, aResult, MAXPATHLEN - 1); + if (len < 0) { + return NS_ERROR_FAILURE; + } + aResult[len] = '\0'; +# if defined(XP_LINUX) + // Removing suffix " (deleted)" from the binary path + const char suffix[] = " (deleted)"; + const ssize_t suffix_len = sizeof(suffix); + if (len >= suffix_len) { + char* result_end = &aResult[len - (suffix_len - 1)]; + if (memcmp(result_end, suffix, suffix_len) == 0) { + *result_end = '\0'; + } + } +# endif + return NS_OK; + } + +#elif defined(__FreeBSD__) || defined(__DragonFly__) || \ + defined(__FreeBSD_kernel__) || defined(__NetBSD__) + static nsresult Get(char aResult[MAXPATHLEN]) { + int mib[4]; + mib[0] = CTL_KERN; +# ifdef __NetBSD__ + mib[1] = KERN_PROC_ARGS; + mib[2] = -1; + mib[3] = KERN_PROC_PATHNAME; +# else + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PATHNAME; + mib[3] = -1; +# endif + + size_t len = MAXPATHLEN; + if (sysctl(mib, 4, aResult, &len, nullptr, 0) < 0) { + return NS_ERROR_FAILURE; + } + + return NS_OK; + } + +#elif defined(__OpenBSD__) + static nsresult Get(char aResult[MAXPATHLEN]) { + static bool cached = false; + static char cachedPath[MAXPATHLEN]; + nsresult r; + int mib[4]; + if (cached) { + if (strlcpy(aResult, cachedPath, MAXPATHLEN) >= MAXPATHLEN) { + return NS_ERROR_FAILURE; + } + return NS_OK; + } + mib[0] = CTL_KERN; + mib[1] = KERN_PROC_ARGS; + mib[2] = getpid(); + mib[3] = KERN_PROC_ARGV; + + size_t len = 0; + if (sysctl(mib, 4, nullptr, &len, nullptr, 0) < 0) { + return NS_ERROR_FAILURE; + } + + auto argv = MakeUnique<const char*[]>(len / sizeof(const char*)); + if (sysctl(mib, 4, argv.get(), &len, nullptr, 0) < 0) { + return NS_ERROR_FAILURE; + } + + r = GetFromArgv0(argv[0], aResult); + if (NS_SUCCEEDED(r)) { + if (strlcpy(cachedPath, aResult, MAXPATHLEN) >= MAXPATHLEN) { + return NS_ERROR_FAILURE; + } + cached = true; + } + return r; + } + + static nsresult GetFromArgv0(const char* aArgv0, char aResult[MAXPATHLEN]) { + struct stat fileStat; + // 1) use realpath() on argv[0], which works unless we're loaded from the + // PATH. Only do so if argv[0] looks like a path (contains a /). + // 2) manually walk through the PATH and look for ourself + // 3) give up + if (strchr(aArgv0, '/') && realpath(aArgv0, aResult) && + stat(aResult, &fileStat) == 0) { + return NS_OK; + } + + const char* path = getenv("PATH"); + if (!path) { + return NS_ERROR_FAILURE; + } + + char* pathdup = strdup(path); + if (!pathdup) { + return NS_ERROR_OUT_OF_MEMORY; + } + + bool found = false; + char* token = strtok(pathdup, ":"); + while (token) { + char tmpPath[MAXPATHLEN]; + sprintf(tmpPath, "%s/%s", token, aArgv0); + if (realpath(tmpPath, aResult) && stat(aResult, &fileStat) == 0) { + found = true; + break; + } + token = strtok(nullptr, ":"); + } + free(pathdup); + if (found) { + return NS_OK; + } + return NS_ERROR_FAILURE; + } + +#else +# error Oops, you need platform-specific code here +#endif + + public: + static UniqueFreePtr<char> Get() { + char path[MAXPATHLEN]; + if (NS_FAILED(Get(path))) { + return nullptr; + } + UniqueFreePtr<char> result; + result.reset(strdup(path)); + return result; + } + +#ifdef MOZILLA_INTERNAL_API + static nsresult GetFile(nsIFile** aResult) { + nsCOMPtr<nsIFile> lf; +# ifdef XP_WIN + wchar_t exePath[MAXPATHLEN]; + nsresult rv = GetW(exePath); +# else + char exePath[MAXPATHLEN]; + nsresult rv = Get(exePath); +# endif + if (NS_FAILED(rv)) { + return rv; + } +# ifdef XP_WIN + rv = NS_NewLocalFile(nsDependentString(exePath), true, getter_AddRefs(lf)); +# else + rv = NS_NewNativeLocalFile(nsDependentCString(exePath), true, + getter_AddRefs(lf)); +# endif + if (NS_FAILED(rv)) { + return rv; + } + NS_ADDREF(*aResult = lf); + return NS_OK; + } +#endif +}; + +} // namespace mozilla + +#endif /* mozilla_BinaryPath_h */ diff --git a/xpcom/build/FileLocation.cpp b/xpcom/build/FileLocation.cpp new file mode 100644 index 0000000000..bb090c2b94 --- /dev/null +++ b/xpcom/build/FileLocation.cpp @@ -0,0 +1,212 @@ +/* -*- 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 "FileLocation.h" +#include "nsZipArchive.h" +#include "nsURLHelper.h" + +namespace mozilla { + +FileLocation::FileLocation() = default; + +FileLocation::~FileLocation() = default; + +FileLocation::FileLocation(nsIFile* aFile) { Init(aFile); } + +FileLocation::FileLocation(nsIFile* aFile, const char* aPath) { + Init(aFile, aPath); +} + +FileLocation::FileLocation(nsZipArchive* aZip, const char* aPath) { + Init(aZip, aPath); +} + +FileLocation::FileLocation(const FileLocation& aOther) + + = default; + +FileLocation::FileLocation(FileLocation&& aOther) + : mBaseFile(std::move(aOther.mBaseFile)), + mBaseZip(std::move(aOther.mBaseZip)), + mPath(std::move(aOther.mPath)) { + aOther.mPath.Truncate(); +} + +FileLocation::FileLocation(const FileLocation& aFile, const char* aPath) { + if (aFile.IsZip()) { + if (aFile.mBaseFile) { + Init(aFile.mBaseFile, aFile.mPath.get()); + } else { + Init(aFile.mBaseZip, aFile.mPath.get()); + } + if (aPath) { + int32_t i = mPath.RFindChar('/'); + if (kNotFound == i) { + mPath.Truncate(0); + } else { + mPath.Truncate(i + 1); + } + mPath += aPath; + } + } else { + if (aPath) { + nsCOMPtr<nsIFile> cfile; + aFile.mBaseFile->GetParent(getter_AddRefs(cfile)); + +#if defined(XP_WIN) + nsAutoCString pathStr(aPath); + char* p; + uint32_t len = pathStr.GetMutableData(&p); + for (; len; ++p, --len) { + if ('/' == *p) { + *p = '\\'; + } + } + cfile->AppendRelativeNativePath(pathStr); +#else + cfile->AppendRelativeNativePath(nsDependentCString(aPath)); +#endif + Init(cfile); + } else { + Init(aFile.mBaseFile); + } + } +} + +void FileLocation::Init(nsIFile* aFile) { + mBaseZip = nullptr; + mBaseFile = aFile; + mPath.Truncate(); +} + +void FileLocation::Init(nsIFile* aFile, const char* aPath) { + mBaseZip = nullptr; + mBaseFile = aFile; + mPath = aPath; +} + +void FileLocation::Init(nsZipArchive* aZip, const char* aPath) { + mBaseZip = aZip; + mBaseFile = nullptr; + mPath = aPath; +} + +void FileLocation::GetURIString(nsACString& aResult) const { + if (mBaseFile) { + net_GetURLSpecFromActualFile(mBaseFile, aResult); + } else if (mBaseZip) { + RefPtr<nsZipHandle> handler = mBaseZip->GetFD(); + handler->mFile.GetURIString(aResult); + } + if (IsZip()) { + aResult.InsertLiteral("jar:", 0); + aResult += "!/"; + aResult += mPath; + } +} + +already_AddRefed<nsIFile> FileLocation::GetBaseFile() { + if (IsZip() && mBaseZip) { + RefPtr<nsZipHandle> handler = mBaseZip->GetFD(); + if (handler) { + return handler->mFile.GetBaseFile(); + } + return nullptr; + } + + nsCOMPtr<nsIFile> file = mBaseFile; + return file.forget(); +} + +bool FileLocation::Equals(const FileLocation& aFile) const { + if (mPath != aFile.mPath) { + return false; + } + + if (mBaseFile && aFile.mBaseFile) { + bool eq; + return NS_SUCCEEDED(mBaseFile->Equals(aFile.mBaseFile, &eq)) && eq; + } + + const FileLocation* a = this; + const FileLocation* b = &aFile; + if (a->mBaseZip) { + RefPtr<nsZipHandle> handler = a->mBaseZip->GetFD(); + a = &handler->mFile; + } + if (b->mBaseZip) { + RefPtr<nsZipHandle> handler = b->mBaseZip->GetFD(); + b = &handler->mFile; + } + + return a->Equals(*b); +} + +nsresult FileLocation::GetData(Data& aData) { + if (!IsZip()) { + return mBaseFile->OpenNSPRFileDesc(PR_RDONLY, 0444, &aData.mFd.rwget()); + } + aData.mZip = mBaseZip; + if (!aData.mZip) { + // this can return nullptr + aData.mZip = nsZipArchive::OpenArchive(mBaseFile); + } + if (aData.mZip) { + aData.mItem = aData.mZip->GetItem(mPath.get()); + if (aData.mItem) { + return NS_OK; + } + } + return NS_ERROR_FILE_UNRECOGNIZED_PATH; +} + +nsresult FileLocation::Data::GetSize(uint32_t* aResult) { + if (mFd) { + PRFileInfo64 fileInfo; + if (PR_SUCCESS != PR_GetOpenFileInfo64(mFd, &fileInfo)) { + return NS_ErrorAccordingToNSPR(); + } + + if (fileInfo.size > int64_t(UINT32_MAX)) { + return NS_ERROR_FILE_TOO_BIG; + } + + *aResult = fileInfo.size; + return NS_OK; + } + if (mItem) { + *aResult = mItem->RealSize(); + return NS_OK; + } + return NS_ERROR_NOT_INITIALIZED; +} + +nsresult FileLocation::Data::Copy(char* aBuf, uint32_t aLen) { + if (mFd) { + for (uint32_t totalRead = 0; totalRead < aLen;) { + int32_t read = PR_Read(mFd, aBuf + totalRead, + XPCOM_MIN(aLen - totalRead, uint32_t(INT32_MAX))); + if (read < 0) { + return NS_ErrorAccordingToNSPR(); + } + totalRead += read; + } + return NS_OK; + } + if (mItem) { + nsZipCursor cursor(mItem, mZip, reinterpret_cast<uint8_t*>(aBuf), aLen, + true); + uint32_t readLen; + cursor.Copy(&readLen); + if (readLen != aLen) { + return NS_ERROR_FILE_CORRUPTED; + } + return NS_OK; + } + return NS_ERROR_NOT_INITIALIZED; +} + +} /* namespace mozilla */ diff --git a/xpcom/build/FileLocation.h b/xpcom/build/FileLocation.h new file mode 100644 index 0000000000..0988a9595f --- /dev/null +++ b/xpcom/build/FileLocation.h @@ -0,0 +1,140 @@ +/* -*- 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_FileLocation_h +#define mozilla_FileLocation_h + +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsIFile.h" +#include "FileUtils.h" + +class nsZipArchive; +class nsZipItem; + +namespace mozilla { + +class FileLocation { + public: + /** + * FileLocation is an helper to handle different kind of file locations + * within Gecko: + * - on filesystems + * - in archives + * - in archives within archives + * As such, it stores a path within an archive, as well as the archive + * path itself, or the complete file path alone when on a filesystem. + * When the archive is in an archive, an nsZipArchive is stored instead + * of a file path. + */ + FileLocation(); + ~FileLocation(); + + FileLocation(const FileLocation& aOther); + FileLocation(FileLocation&& aOther); + + FileLocation& operator=(const FileLocation&) = default; + + /** + * Constructor for plain files + */ + explicit FileLocation(nsIFile* aFile); + + /** + * Constructors for path within an archive. The archive can be given either + * as nsIFile or nsZipArchive. + */ + FileLocation(nsIFile* aZip, const char* aPath); + + FileLocation(nsZipArchive* aZip, const char* aPath); + + /** + * Creates a new file location relative to another one. + */ + FileLocation(const FileLocation& aFile, const char* aPath); + + /** + * Initialization functions corresponding to constructors + */ + void Init(nsIFile* aFile); + + void Init(nsIFile* aZip, const char* aPath); + + void Init(nsZipArchive* aZip, const char* aPath); + + /** + * Returns an URI string corresponding to the file location + */ + void GetURIString(nsACString& aResult) const; + + /** + * Returns the base file of the location, where base file is defined as: + * - The file itself when the location is on a filesystem + * - The archive file when the location is in an archive + * - The outer archive file when the location is in an archive in an archive + */ + already_AddRefed<nsIFile> GetBaseFile(); + + nsZipArchive* GetBaseZip() { return mBaseZip; } + + /** + * Returns whether the "base file" (see GetBaseFile) is an archive + */ + bool IsZip() const { return !mPath.IsEmpty(); } + + /** + * Returns the path within the archive, when within an archive + */ + void GetPath(nsACString& aResult) const { aResult = mPath; } + + /** + * Boolean value corresponding to whether the file location is initialized + * or not. + */ + explicit operator bool() const { return mBaseFile || mBaseZip; } + + /** + * Returns whether another FileLocation points to the same resource + */ + bool Equals(const FileLocation& aFile) const; + + /** + * Data associated with a FileLocation. + */ + class Data { + public: + /** + * Returns the data size + */ + nsresult GetSize(uint32_t* aResult); + + /** + * Copies the data in the given buffer + */ + nsresult Copy(char* aBuf, uint32_t aLen); + + protected: + friend class FileLocation; + nsZipItem* mItem; + RefPtr<nsZipArchive> mZip; + mozilla::AutoFDClose mFd; + }; + + /** + * Returns the data associated with the resource pointed at by the file + * location. + */ + nsresult GetData(Data& aData); + + private: + nsCOMPtr<nsIFile> mBaseFile; + RefPtr<nsZipArchive> mBaseZip; + nsCString mPath; +}; /* class FileLocation */ + +} /* namespace mozilla */ + +#endif /* mozilla_FileLocation_h */ diff --git a/xpcom/build/IOInterposer.cpp b/xpcom/build/IOInterposer.cpp new file mode 100644 index 0000000000..0420965b91 --- /dev/null +++ b/xpcom/build/IOInterposer.cpp @@ -0,0 +1,532 @@ +/* -*- 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 <algorithm> +#include <vector> + +#include "IOInterposer.h" + +#include "IOInterposerPrivate.h" +#include "MainThreadIOLogger.h" +#include "mozilla/Atomics.h" +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/ThreadLocal.h" +#include "nscore.h" // for NS_FREE_PERMANENT_DATA +#if !defined(XP_WIN) +# include "NSPRInterposer.h" +#endif // !defined(XP_WIN) +#include "nsXULAppAPI.h" +#include "PoisonIOInterposer.h" +#include "prenv.h" + +namespace { + +/** Find if a vector contains a specific element */ +template <class T> +bool VectorContains(const std::vector<T>& aVector, const T& aElement) { + return std::find(aVector.begin(), aVector.end(), aElement) != aVector.end(); +} + +/** Remove element from a vector */ +template <class T> +void VectorRemove(std::vector<T>& aVector, const T& aElement) { + typename std::vector<T>::iterator newEnd = + std::remove(aVector.begin(), aVector.end(), aElement); + aVector.erase(newEnd, aVector.end()); +} + +/** Lists of Observers */ +struct ObserverLists { + private: + ~ObserverLists() = default; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ObserverLists) + + ObserverLists() = default; + + ObserverLists(ObserverLists const& aOther) + : mCreateObservers(aOther.mCreateObservers), + mReadObservers(aOther.mReadObservers), + mWriteObservers(aOther.mWriteObservers), + mFSyncObservers(aOther.mFSyncObservers), + mStatObservers(aOther.mStatObservers), + mCloseObservers(aOther.mCloseObservers), + mStageObservers(aOther.mStageObservers) {} + // Lists of observers for I/O events. + // These are implemented as vectors since they are allowed to survive gecko, + // without reporting leaks. This is necessary for the IOInterposer to be used + // for late-write checks. + std::vector<mozilla::IOInterposeObserver*> mCreateObservers; + std::vector<mozilla::IOInterposeObserver*> mReadObservers; + std::vector<mozilla::IOInterposeObserver*> mWriteObservers; + std::vector<mozilla::IOInterposeObserver*> mFSyncObservers; + std::vector<mozilla::IOInterposeObserver*> mStatObservers; + std::vector<mozilla::IOInterposeObserver*> mCloseObservers; + std::vector<mozilla::IOInterposeObserver*> mStageObservers; +}; + +class PerThreadData { + public: + explicit PerThreadData(bool aIsMainThread = false) + : mIsMainThread(aIsMainThread), + mIsHandlingObservation(false), + mCurrentGeneration(0) { + MOZ_COUNT_CTOR(PerThreadData); + } + + MOZ_COUNTED_DTOR(PerThreadData) + + void CallObservers(mozilla::IOInterposeObserver::Observation& aObservation) { + // Prevent recursive reporting. + if (mIsHandlingObservation) { + return; + } + + mIsHandlingObservation = true; + // Decide which list of observers to inform + const std::vector<mozilla::IOInterposeObserver*>* observers = nullptr; + switch (aObservation.ObservedOperation()) { + case mozilla::IOInterposeObserver::OpCreateOrOpen: + observers = &mObserverLists->mCreateObservers; + break; + case mozilla::IOInterposeObserver::OpRead: + observers = &mObserverLists->mReadObservers; + break; + case mozilla::IOInterposeObserver::OpWrite: + observers = &mObserverLists->mWriteObservers; + break; + case mozilla::IOInterposeObserver::OpFSync: + observers = &mObserverLists->mFSyncObservers; + break; + case mozilla::IOInterposeObserver::OpStat: + observers = &mObserverLists->mStatObservers; + break; + case mozilla::IOInterposeObserver::OpClose: + observers = &mObserverLists->mCloseObservers; + break; + case mozilla::IOInterposeObserver::OpNextStage: + observers = &mObserverLists->mStageObservers; + break; + default: { + // Invalid IO operation, see documentation comment for + // IOInterposer::Report() + MOZ_ASSERT(false); + // Just ignore it in non-debug builds. + return; + } + } + MOZ_ASSERT(observers); + + // Inform observers + for (auto i = observers->begin(), e = observers->end(); i != e; ++i) { + (*i)->Observe(aObservation); + } + mIsHandlingObservation = false; + } + + inline uint32_t GetCurrentGeneration() const { return mCurrentGeneration; } + + inline bool IsMainThread() const { return mIsMainThread; } + + inline void SetObserverLists(uint32_t aNewGeneration, + RefPtr<const ObserverLists>& aNewLists) { + mCurrentGeneration = aNewGeneration; + mObserverLists = aNewLists; + } + + inline void ClearObserverLists() { + if (mObserverLists) { + mCurrentGeneration = 0; + mObserverLists = nullptr; + } + } + + private: + bool mIsMainThread; + bool mIsHandlingObservation; + uint32_t mCurrentGeneration; + RefPtr<const ObserverLists> mObserverLists; +}; + +// Thread-safe list of observers, from which `PerThreadData` sources its own +// local list when needed. +class SourceList { + public: + SourceList() + : mObservedOperations(mozilla::IOInterposeObserver::OpNone), + mIsEnabled(true) { + MOZ_COUNT_CTOR(SourceList); + } + + MOZ_COUNTED_DTOR(SourceList) + + inline void Disable() { mIsEnabled = false; } + inline void Enable() { mIsEnabled = true; } + + void Register(mozilla::IOInterposeObserver::Operation aOp, + mozilla::IOInterposeObserver* aStaticObserver) { + mozilla::IOInterposer::AutoLock lock(mLock); + + ObserverLists* newLists = nullptr; + if (mObserverLists) { + newLists = new ObserverLists(*mObserverLists); + } else { + newLists = new ObserverLists(); + } + // You can register to observe multiple types of observations + // but you'll never be registered twice for the same observations. + if (aOp & mozilla::IOInterposeObserver::OpCreateOrOpen && + !VectorContains(newLists->mCreateObservers, aStaticObserver)) { + newLists->mCreateObservers.push_back(aStaticObserver); + } + if (aOp & mozilla::IOInterposeObserver::OpRead && + !VectorContains(newLists->mReadObservers, aStaticObserver)) { + newLists->mReadObservers.push_back(aStaticObserver); + } + if (aOp & mozilla::IOInterposeObserver::OpWrite && + !VectorContains(newLists->mWriteObservers, aStaticObserver)) { + newLists->mWriteObservers.push_back(aStaticObserver); + } + if (aOp & mozilla::IOInterposeObserver::OpFSync && + !VectorContains(newLists->mFSyncObservers, aStaticObserver)) { + newLists->mFSyncObservers.push_back(aStaticObserver); + } + if (aOp & mozilla::IOInterposeObserver::OpStat && + !VectorContains(newLists->mStatObservers, aStaticObserver)) { + newLists->mStatObservers.push_back(aStaticObserver); + } + if (aOp & mozilla::IOInterposeObserver::OpClose && + !VectorContains(newLists->mCloseObservers, aStaticObserver)) { + newLists->mCloseObservers.push_back(aStaticObserver); + } + if (aOp & mozilla::IOInterposeObserver::OpNextStage && + !VectorContains(newLists->mStageObservers, aStaticObserver)) { + newLists->mStageObservers.push_back(aStaticObserver); + } + mObserverLists = newLists; + mObservedOperations = + (mozilla::IOInterposeObserver::Operation)(mObservedOperations | aOp); + + mCurrentGeneration++; + } + + void Unregister(mozilla::IOInterposeObserver::Operation aOp, + mozilla::IOInterposeObserver* aStaticObserver) { + mozilla::IOInterposer::AutoLock lock(mLock); + + ObserverLists* newLists = nullptr; + if (mObserverLists) { + newLists = new ObserverLists(*mObserverLists); + } else { + newLists = new ObserverLists(); + } + + if (aOp & mozilla::IOInterposeObserver::OpCreateOrOpen) { + VectorRemove(newLists->mCreateObservers, aStaticObserver); + if (newLists->mCreateObservers.empty()) { + mObservedOperations = (mozilla::IOInterposeObserver::Operation)( + mObservedOperations & + ~mozilla::IOInterposeObserver::OpCreateOrOpen); + } + } + if (aOp & mozilla::IOInterposeObserver::OpRead) { + VectorRemove(newLists->mReadObservers, aStaticObserver); + if (newLists->mReadObservers.empty()) { + mObservedOperations = (mozilla::IOInterposeObserver::Operation)( + mObservedOperations & ~mozilla::IOInterposeObserver::OpRead); + } + } + if (aOp & mozilla::IOInterposeObserver::OpWrite) { + VectorRemove(newLists->mWriteObservers, aStaticObserver); + if (newLists->mWriteObservers.empty()) { + mObservedOperations = (mozilla::IOInterposeObserver::Operation)( + mObservedOperations & ~mozilla::IOInterposeObserver::OpWrite); + } + } + if (aOp & mozilla::IOInterposeObserver::OpFSync) { + VectorRemove(newLists->mFSyncObservers, aStaticObserver); + if (newLists->mFSyncObservers.empty()) { + mObservedOperations = (mozilla::IOInterposeObserver::Operation)( + mObservedOperations & ~mozilla::IOInterposeObserver::OpFSync); + } + } + if (aOp & mozilla::IOInterposeObserver::OpStat) { + VectorRemove(newLists->mStatObservers, aStaticObserver); + if (newLists->mStatObservers.empty()) { + mObservedOperations = (mozilla::IOInterposeObserver::Operation)( + mObservedOperations & ~mozilla::IOInterposeObserver::OpStat); + } + } + if (aOp & mozilla::IOInterposeObserver::OpClose) { + VectorRemove(newLists->mCloseObservers, aStaticObserver); + if (newLists->mCloseObservers.empty()) { + mObservedOperations = (mozilla::IOInterposeObserver::Operation)( + mObservedOperations & ~mozilla::IOInterposeObserver::OpClose); + } + } + if (aOp & mozilla::IOInterposeObserver::OpNextStage) { + VectorRemove(newLists->mStageObservers, aStaticObserver); + if (newLists->mStageObservers.empty()) { + mObservedOperations = (mozilla::IOInterposeObserver::Operation)( + mObservedOperations & ~mozilla::IOInterposeObserver::OpNextStage); + } + } + mObserverLists = newLists; + mCurrentGeneration++; + } + + void Update(PerThreadData& aPtd) { + if (mCurrentGeneration == aPtd.GetCurrentGeneration()) { + return; + } + // If the generation counts don't match then we need to update the current + // thread's observer list with the new source list. + mozilla::IOInterposer::AutoLock lock(mLock); + aPtd.SetObserverLists(mCurrentGeneration, mObserverLists); + } + + inline bool IsObservedOperation(mozilla::IOInterposeObserver::Operation aOp) { + // This does not occur inside of a lock, so this makes no guarantees that + // the observers are in sync with this. That is acceptable; it is not a + // problem if we occasionally report more or less IO than is actually + // occurring. + return mIsEnabled && !!(mObservedOperations & aOp); + } + + private: + RefPtr<const ObserverLists> mObserverLists MOZ_GUARDED_BY(mLock); + // Note, we cannot use mozilla::Mutex here as the ObserverLists may be leaked + // (We want to monitor IO during shutdown). Furthermore, as we may have to + // unregister observers during shutdown an OffTheBooksMutex is not an option + // either, as its base calls into sDeadlockDetector which may be nullptr + // during shutdown. + mozilla::IOInterposer::Mutex mLock; + // Flags tracking which operations are being observed + mozilla::Atomic<mozilla::IOInterposeObserver::Operation, + mozilla::MemoryOrdering::Relaxed> + mObservedOperations; + // Used for quickly disabling everything by IOInterposer::Disable() + mozilla::Atomic<bool> mIsEnabled; + // Used to inform threads that the source observer list has changed + mozilla::Atomic<uint32_t> mCurrentGeneration; +}; + +// Special observation used by IOInterposer::EnteringNextStage() +class NextStageObservation : public mozilla::IOInterposeObserver::Observation { + public: + NextStageObservation() + : mozilla::IOInterposeObserver::Observation( + mozilla::IOInterposeObserver::OpNextStage, "IOInterposer", false) { + mStart = mozilla::TimeStamp::Now(); + mEnd = mStart; + } +}; + +// List of observers registered +static mozilla::StaticAutoPtr<SourceList> sSourceList; +static MOZ_THREAD_LOCAL(PerThreadData*) sThreadLocalData; +static bool sThreadLocalDataInitialized; + +} // anonymous namespace + +namespace mozilla { + +IOInterposeObserver::Observation::Observation(Operation aOperation, + const char* aReference, + bool aShouldReport) + : mOperation(aOperation), + mReference(aReference), + mShouldReport(IOInterposer::IsObservedOperation(aOperation) && + aShouldReport) { + if (mShouldReport) { + mStart = TimeStamp::Now(); + } +} + +IOInterposeObserver::Observation::Observation(Operation aOperation, + const TimeStamp& aStart, + const TimeStamp& aEnd, + const char* aReference) + : mOperation(aOperation), + mStart(aStart), + mEnd(aEnd), + mReference(aReference), + mShouldReport(false) {} + +const char* IOInterposeObserver::Observation::ObservedOperationString() const { + switch (mOperation) { + case OpCreateOrOpen: + return "create/open"; + case OpRead: + return "read"; + case OpWrite: + return "write"; + case OpFSync: + return "fsync"; + case OpStat: + return "stat"; + case OpClose: + return "close"; + case OpNextStage: + return "NextStage"; + default: + return "unknown"; + } +} + +void IOInterposeObserver::Observation::Report() { + if (mShouldReport) { + mEnd = TimeStamp::Now(); + IOInterposer::Report(*this); + } +} + +bool IOInterposer::Init() { + // Don't initialize twice... + if (sSourceList) { + return true; + } + if (!sThreadLocalData.init()) { + return false; + } + sThreadLocalDataInitialized = true; + bool isMainThread = true; + RegisterCurrentThread(isMainThread); + sSourceList = new SourceList(); + + MainThreadIOLogger::Init(); + + // Now we initialize the various interposers depending on platform + + // Under certain conditions it may be unsafe to initialize PoisonIOInterposer, + // such as when a background thread is already running. We set this variable + // elsewhere when such a condition applies. + if (!PR_GetEnv("MOZ_DISABLE_POISON_IO_INTERPOSER")) { + InitPoisonIOInterposer(); + } + + // We don't hook NSPR on Windows because PoisonIOInterposer captures a + // superset of the former's events. +#if !defined(XP_WIN) + InitNSPRIOInterposing(); +#endif + return true; +} + +bool IOInterposeObserver::IsMainThread() { + if (!sThreadLocalDataInitialized) { + return false; + } + PerThreadData* ptd = sThreadLocalData.get(); + if (!ptd) { + return false; + } + return ptd->IsMainThread(); +} + +void IOInterposer::Clear() { + /* Clear() is a no-op on release builds so that we may continue to trap I/O + until process termination. In leak-checking builds, we need to shut down + IOInterposer so that all references are properly released. */ +#ifdef NS_FREE_PERMANENT_DATA + UnregisterCurrentThread(); + sSourceList = nullptr; +#endif +} + +void IOInterposer::Disable() { + if (!sSourceList) { + return; + } + sSourceList->Disable(); +} + +void IOInterposer::Enable() { + if (!sSourceList) { + return; + } + sSourceList->Enable(); +} + +void IOInterposer::Report(IOInterposeObserver::Observation& aObservation) { + PerThreadData* ptd = sThreadLocalData.get(); + if (!ptd) { + // In this case the current thread is not registered with IOInterposer. + // Alternatively we could take the slow path and just lock everything if + // we're not registered. That could potentially perform poorly, though. + return; + } + + if (!sSourceList) { + // If there is no longer a source list then we should clear the local one. + ptd->ClearObserverLists(); + return; + } + + sSourceList->Update(*ptd); + + // Don't try to report if there's nobody listening. + if (!IOInterposer::IsObservedOperation(aObservation.ObservedOperation())) { + return; + } + + ptd->CallObservers(aObservation); +} + +bool IOInterposer::IsObservedOperation(IOInterposeObserver::Operation aOp) { + return sSourceList && sSourceList->IsObservedOperation(aOp); +} + +void IOInterposer::Register(IOInterposeObserver::Operation aOp, + IOInterposeObserver* aStaticObserver) { + MOZ_ASSERT(aStaticObserver); + if (!sSourceList || !aStaticObserver) { + return; + } + + sSourceList->Register(aOp, aStaticObserver); +} + +void IOInterposer::Unregister(IOInterposeObserver::Operation aOp, + IOInterposeObserver* aStaticObserver) { + if (!sSourceList) { + return; + } + + sSourceList->Unregister(aOp, aStaticObserver); +} + +void IOInterposer::RegisterCurrentThread(bool aIsMainThread) { + if (!sThreadLocalDataInitialized) { + return; + } + MOZ_ASSERT(!sThreadLocalData.get()); + PerThreadData* curThreadData = new PerThreadData(aIsMainThread); + sThreadLocalData.set(curThreadData); +} + +void IOInterposer::UnregisterCurrentThread() { + if (!sThreadLocalDataInitialized) { + return; + } + if (PerThreadData* curThreadData = sThreadLocalData.get()) { + sThreadLocalData.set(nullptr); + delete curThreadData; + } +} + +void IOInterposer::EnteringNextStage() { + if (!sSourceList) { + return; + } + NextStageObservation observation; + Report(observation); +} + +} // namespace mozilla diff --git a/xpcom/build/IOInterposer.h b/xpcom/build/IOInterposer.h new file mode 100644 index 0000000000..917aa26cd4 --- /dev/null +++ b/xpcom/build/IOInterposer.h @@ -0,0 +1,288 @@ +/* -*- 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_IOInterposer_h +#define mozilla_IOInterposer_h + +#include "mozilla/Attributes.h" +#include "mozilla/TimeStamp.h" +#include "nsString.h" + +namespace mozilla { + +/** + * Interface for I/O interposer observers. This is separate from the + * IOInterposer because we have multiple uses for these observations. + */ +class IOInterposeObserver { + public: + enum Operation { + OpNone = 0, + OpCreateOrOpen = (1 << 0), + OpRead = (1 << 1), + OpWrite = (1 << 2), + OpFSync = (1 << 3), + OpStat = (1 << 4), + OpClose = (1 << 5), + OpNextStage = + (1 << 6), // Meta - used when leaving startup, entering shutdown + OpWriteFSync = (OpWrite | OpFSync), + OpAll = (OpCreateOrOpen | OpRead | OpWrite | OpFSync | OpStat | OpClose), + OpAllWithStaging = (OpAll | OpNextStage) + }; + + /** A representation of an I/O observation */ + class Observation { + protected: + /** + * This constructor is for use by subclasses that are intended to take + * timing measurements via RAII. The |aShouldReport| parameter may be + * used to make the measurement and reporting conditional on the + * satisfaction of an arbitrary predicate that was evaluated + * in the subclass. Note that IOInterposer::IsObservedOperation() is + * always ANDed with aShouldReport, so the subclass does not need to + * include a call to that function explicitly. + */ + Observation(Operation aOperation, const char* aReference, + bool aShouldReport = true); + + public: + /** + * Since this constructor accepts start and end times, it does *not* take + * its own timings, nor does it report itself. + */ + Observation(Operation aOperation, const TimeStamp& aStart, + const TimeStamp& aEnd, const char* aReference); + + /** + * Operation observed, this is one of the individual Operation values. + * Combinations of these flags are only used when registering observers. + */ + Operation ObservedOperation() const { return mOperation; } + + /** + * Return the observed operation as a human-readable string. + */ + const char* ObservedOperationString() const; + + /** Time at which the I/O operation was started */ + TimeStamp Start() const { return mStart; } + + /** + * Time at which the I/O operation ended, for asynchronous methods this is + * the time at which the call initiating the asynchronous request returned. + */ + TimeStamp End() const { return mEnd; } + + /** + * Duration of the operation, for asynchronous I/O methods this is the + * duration of the call initiating the asynchronous request. + */ + TimeDuration Duration() const { return mEnd - mStart; } + + /** + * IO reference, function name or name of component (sqlite) that did IO + * this is in addition the generic operation. This attribute may be platform + * specific, but should only take a finite number of distinct values. + * E.g. sqlite-commit, CreateFile, NtReadFile, fread, fsync, mmap, etc. + * I.e. typically the platform specific function that did the IO. + */ + const char* Reference() const { return mReference; } + + virtual const char* FileType() const { return "File"; } + + /** Request filename associated with the I/O operation, empty if unknown */ + virtual void Filename(nsAString& aString) { aString.Truncate(); } + + virtual ~Observation() = default; + + protected: + void Report(); + + Operation mOperation; + TimeStamp mStart; + TimeStamp mEnd; + const char* mReference; // Identifies the source of the Observation + bool mShouldReport; // Measure and report if true + }; + + /** + * Invoked whenever an implementation of the IOInterposeObserver should + * observe aObservation. Implement this and do your thing... + * But do consider if it is wise to use IO functions in this method, they are + * likely to cause recursion :) + * At least, see PoisonIOInterposer.h and register your handle as a debug file + * even, if you don't initialize the poison IO interposer, someone else might. + * + * Remark: Observations may occur on any thread. + */ + virtual void Observe(Observation& aObservation) = 0; + + virtual ~IOInterposeObserver() = default; + + protected: + /** + * We don't use NS_IsMainThread() because we need to be able to determine the + * main thread outside of XPCOM Initialization. IOInterposer observers should + * call this function instead. + */ + static bool IsMainThread(); +}; + +/** + * These functions are responsible for ensuring that events are routed to the + * appropriate observers. + */ +namespace IOInterposer { + +/** + * This function must be called from the main-thread when no other threads are + * running before any of the other methods on this class may be used. + * + * IO reports can however, safely assume that IsObservedOperation() will + * return false until the IOInterposer is initialized. + * + * Remark, it's safe to call this method multiple times, so just call it when + * you to utilize IO interposing. + * + * Using the IOInterposerInit class is preferred to calling this directly. + */ +bool Init(); + +/** + * This function must be called from the main thread, and furthermore + * it must be called when no other threads are executing. Effectively + * restricting us to calling it only during shutdown. + * + * Callers should take care that no other consumers are subscribed to events, + * as these events will stop when this function is called. + * + * In practice, we don't use this method as the IOInterposer is used for + * late-write checks. + */ +void Clear(); + +/** + * This function immediately disables IOInterposer functionality in a fast, + * thread-safe manner. Primarily for use by the crash reporter. + */ +void Disable(); + +/** + * This function re-enables IOInterposer functionality in a fast, thread-safe + * manner. Primarily for use by the crash reporter. + */ +void Enable(); + +/** + * Report IO to registered observers. + * Notice that the reported operation must be either OpRead, OpWrite or + * OpFSync. You are not allowed to report an observation with OpWriteFSync or + * OpAll, these are just auxiliary values for use with Register(). + * + * If the IO call you're reporting does multiple things, write and fsync, you + * can choose to call Report() twice once with write and once with FSync. You + * may not call Report() with OpWriteFSync! The Observation::mOperation + * attribute is meant to be generic, not perfect. + * + * Notice that there is no reason to report an observation with an operation + * which is not being observed. Use IsObservedOperation() to check if the + * operation you are about to report is being observed. This is especially + * important if you are constructing expensive observations containing + * filename and full-path. + * + * Remark: Init() must be called before any IO is reported. But + * IsObservedOperation() will return false until Init() is called. + */ +void Report(IOInterposeObserver::Observation& aObservation); + +/** + * Return whether or not an operation is observed. Reporters should not + * report operations that are not being observed by anybody. This mechanism + * allows us to avoid reporting I/O when no observers are registered. + */ +bool IsObservedOperation(IOInterposeObserver::Operation aOp); + +/** + * Register IOInterposeObserver, the observer object will receive all + * observations for the given operation aOp. + * + * Remarks: + * - Init() must be called before observers are registered. + * - The IOInterposeObserver object should be static, because it could still be + * used on another thread shortly after Unregister(). + */ +void Register(IOInterposeObserver::Operation aOp, + IOInterposeObserver* aStaticObserver); + +/** + * Unregister an IOInterposeObserver for a given operation + * Remark: It is always safe to unregister for all operations, even if yoú + * didn't register for them all. + * I.e. IOInterposer::Unregister(IOInterposeObserver::OpAll, aObserver) + * + * Remarks: + * - Init() must be called before observers are registered. + * - The IOInterposeObserver object should be static, because it could still be + * used on another thread shortly after this Unregister() call. + */ +void Unregister(IOInterposeObserver::Operation aOp, + IOInterposeObserver* aStaticObserver); + +/** + * Registers the current thread with the IOInterposer. This must be done to + * ensure that per-thread data is created in an orderly fashion. + * We could have written this to initialize that data lazily, however this + * could have unintended consequences if a thread that is not aware of + * IOInterposer was implicitly registered: its per-thread data would never + * be deleted because it would not know to unregister itself. + * + * @param aIsMainThread true if IOInterposer should treat the current thread + * as the main thread. + */ +void RegisterCurrentThread(bool aIsMainThread = false); + +/** + * Unregisters the current thread with the IOInterposer. This is important + * to call when a thread is shutting down because it cleans up data that + * is stored in a TLS slot. + */ +void UnregisterCurrentThread(); + +/** + * Called to inform observers that the process has transitioned out of the + * startup stage or into the shutdown stage. Main thread only. + */ +void EnteringNextStage(); + +} // namespace IOInterposer + +class IOInterposerInit { + public: + IOInterposerInit() { +#if defined(EARLY_BETA_OR_EARLIER) + IOInterposer::Init(); +#endif + } + + ~IOInterposerInit() { +#if defined(EARLY_BETA_OR_EARLIER) + IOInterposer::Clear(); +#endif + } +}; + +class MOZ_RAII AutoIOInterposerDisable final { + public: + explicit AutoIOInterposerDisable() { IOInterposer::Disable(); } + ~AutoIOInterposerDisable() { IOInterposer::Enable(); } + + private: +}; + +} // namespace mozilla + +#endif // mozilla_IOInterposer_h diff --git a/xpcom/build/IOInterposerPrivate.h b/xpcom/build/IOInterposerPrivate.h new file mode 100644 index 0000000000..14f71603b8 --- /dev/null +++ b/xpcom/build/IOInterposerPrivate.h @@ -0,0 +1,117 @@ +/* -*- 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 xpcom_build_IOInterposerPrivate_h +#define xpcom_build_IOInterposerPrivate_h + +/* This header file contains declarations for helper classes that are + to be used exclusively by IOInterposer and its observers. This header + file is not to be used by anything else and MUST NOT be exported! */ + +#include <prcvar.h> +#include <prlock.h> + +#include "mozilla/ThreadSafety.h" + +namespace mozilla { +namespace IOInterposer { + +/** + * The following classes are simple wrappers for PRLock and PRCondVar. + * IOInterposer and friends use these instead of Mozilla::Mutex et al because + * of the fact that IOInterposer is permitted to run until the process + * terminates; we can't use anything that plugs into leak checkers or deadlock + * detectors because IOInterposer will outlive those and generate false + * positives. + */ + +class MOZ_CAPABILITY("monitor") Monitor { + public: + Monitor() : mLock(PR_NewLock()), mCondVar(PR_NewCondVar(mLock)) {} + + ~Monitor() { + PR_DestroyCondVar(mCondVar); + mCondVar = nullptr; + PR_DestroyLock(mLock); + mLock = nullptr; + } + + void Lock() MOZ_CAPABILITY_ACQUIRE() { PR_Lock(mLock); } + + void Unlock() MOZ_CAPABILITY_RELEASE() { PR_Unlock(mLock); } + + bool Wait(PRIntervalTime aTimeout = PR_INTERVAL_NO_TIMEOUT) + MOZ_REQUIRES(this) { + return PR_WaitCondVar(mCondVar, aTimeout) == PR_SUCCESS; + } + + bool Notify() { return PR_NotifyCondVar(mCondVar) == PR_SUCCESS; } + + private: + PRLock* mLock; + PRCondVar* mCondVar; +}; + +class MOZ_SCOPED_CAPABILITY MonitorAutoLock { + public: + explicit MonitorAutoLock(Monitor& aMonitor) MOZ_CAPABILITY_ACQUIRE(aMonitor) + : mMonitor(aMonitor) { + mMonitor.Lock(); + } + + ~MonitorAutoLock() MOZ_CAPABILITY_RELEASE() { mMonitor.Unlock(); } + + private: + Monitor& mMonitor; +}; + +class MOZ_SCOPED_CAPABILITY MonitorAutoUnlock { + public: + explicit MonitorAutoUnlock(Monitor& aMonitor) + MOZ_SCOPED_UNLOCK_RELEASE(aMonitor) + : mMonitor(aMonitor) { + mMonitor.Unlock(); + } + + ~MonitorAutoUnlock() MOZ_SCOPED_UNLOCK_REACQUIRE() { mMonitor.Lock(); } + + private: + Monitor& mMonitor; +}; + +class MOZ_CAPABILITY("mutex") Mutex { + public: + Mutex() : mPRLock(PR_NewLock()) {} + + ~Mutex() { + PR_DestroyLock(mPRLock); + mPRLock = nullptr; + } + + void Lock() MOZ_CAPABILITY_ACQUIRE() { PR_Lock(mPRLock); } + + void Unlock() MOZ_CAPABILITY_RELEASE() { PR_Unlock(mPRLock); } + + private: + PRLock* mPRLock; +}; + +class MOZ_SCOPED_CAPABILITY AutoLock { + public: + explicit AutoLock(Mutex& aLock) MOZ_CAPABILITY_ACQUIRE(aLock) : mLock(aLock) { + mLock.Lock(); + } + + ~AutoLock() MOZ_CAPABILITY_RELEASE() { mLock.Unlock(); } + + private: + Mutex& mLock; +}; + +} // namespace IOInterposer +} // namespace mozilla + +#endif // xpcom_build_IOInterposerPrivate_h diff --git a/xpcom/build/LateWriteChecks.cpp b/xpcom/build/LateWriteChecks.cpp new file mode 100644 index 0000000000..e3bf73d73c --- /dev/null +++ b/xpcom/build/LateWriteChecks.cpp @@ -0,0 +1,263 @@ +/* -*- 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 <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 "mozilla/Unused.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsLocalFile.h" +#include "nsPrintfCString.h" +#include "mozilla/StackWalk.h" +#include "prio.h" + +#ifdef XP_WIN +# 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" + +/*************************** Auxiliary Declarations ***************************/ + +static MOZ_THREAD_LOCAL(int) tlsSuspendLateWriteChecks; + +bool SuspendingLateWriteChecksForCurrentThread() { + if (!tlsSuspendLateWriteChecks.init()) { + return true; + } + return tlsSuspendLateWriteChecks.get() > 0; +} + +// 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* aStream) : mFile(aStream) { + MozillaRegisterDebugFILE(mFile); + } + + void Printf(const char* aFormat, ...) MOZ_FORMAT_PRINTF(2, 3) { + MOZ_ASSERT(mFile); + va_list list; + va_start(list, aFormat); + nsAutoCString str; + str.AppendVprintf(aFormat, list); + va_end(list); + mSHA1.update(str.get(), str.Length()); + mozilla::Unused << fwrite(str.get(), 1, str.Length(), mFile); + } + void Finish(mozilla::SHA1Sum::Hash& aHash) { + int fd = fileno(mFile); + fflush(mFile); + MozillaUnRegisterDebugFD(fd); + fclose(mFile); + mSHA1.finish(aHash); + mFile = nullptr; + } + + private: + FILE* mFile; + mozilla::SHA1Sum mSHA1; +}; + +static void RecordStackWalker(uint32_t aFrameNumber, 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 final : public mozilla::IOInterposeObserver { + using char_type = mozilla::filesystem::Path::value_type; + + public: + explicit LateWriteObserver(const char_type* aProfileDirectory) + : mProfileDirectory(NS_xstrdup(aProfileDirectory)) {} + ~LateWriteObserver() { + free(mProfileDirectory); + mProfileDirectory = nullptr; + } + + void Observe( + mozilla::IOInterposeObserver::Observation& aObservation) override; + + private: + char_type* mProfileDirectory; +}; + +void LateWriteObserver::Observe( + mozilla::IOInterposeObserver::Observation& aOb) { + if (SuspendingLateWriteChecksForCurrentThread()) { + return; + } + +#ifdef DEBUG + MOZ_CRASH(); +#endif + + // If we can't record then abort + if (!mozilla::Telemetry::CanRecordExtended()) { + 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; + + MozStackWalk(RecordStackWalker, nullptr, /* maxFrames */ 0, &rawStack); + mozilla::Telemetry::ProcessedStack stack = + mozilla::Telemetry::GetStackAndModules(rawStack); + + nsTAutoString<char_type> nameAux(mProfileDirectory); + nameAux.AppendLiteral(NS_SLASH "Telemetry.LateWriteTmpXXXXXX"); + char_type* name = nameAux.BeginWriting(); + + // 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 + _wmktemp_s(char16ptr_t(name), NS_strlen(name) + 1); + hFile = CreateFileW(char16ptr_t(name), GENERIC_WRITE, 0, nullptr, + CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); + } while (GetLastError() == ERROR_FILE_EXISTS); + + if (hFile == INVALID_HANDLE_VALUE) { + MOZ_CRASH("Um, how did we get here?"); + } + + // http://support.microsoft.com/kb/139640 + int fd = _open_osfhandle((intptr_t)hFile, _O_APPEND); + if (fd == -1) { + MOZ_CRASH("Um, how did we get here?"); + } + + stream = _fdopen(fd, "w"); +#else + int fd = mkstemp(name); + if (fd == -1) { + MOZ_CRASH("mkstemp failed"); + } + 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) { + mozilla::Telemetry::ProcessedStack::Module module = stack.GetModule(i); + sha1Stream.Printf("%s %s\n", module.mBreakpadId.get(), + NS_ConvertUTF16toUTF8(module.mName).get()); + } + + size_t numFrames = stack.GetStackSize(); + sha1Stream.Printf("%u\n", (unsigned)numFrames); + for (size_t i = 0; i < numFrames; ++i) { + const mozilla::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); + } + + mozilla::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. + nsAutoString finalName(u"Telemetry.LateWriteFinal-"_ns); + for (int i = 0; i < 20; ++i) { + finalName.AppendPrintf("%02x", sha1[i]); + } + RefPtr<nsLocalFile> file = new nsLocalFile(nameAux); + file->RenameTo(nullptr, finalName); +} + +/******************************* Setup/Teardown *******************************/ + +static mozilla::StaticAutoPtr<LateWriteObserver> sLateWriteObserver; + +namespace mozilla { + +void InitLateWriteChecks() { + nsCOMPtr<nsIFile> mozFile; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mozFile)); + if (mozFile) { + PathString nativePath = mozFile->NativePath(); + if (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; + } +} + +void PushSuspendLateWriteChecks() { + if (!tlsSuspendLateWriteChecks.init()) { + return; + } + tlsSuspendLateWriteChecks.set(tlsSuspendLateWriteChecks.get() + 1); +} + +void PopSuspendLateWriteChecks() { + if (!tlsSuspendLateWriteChecks.init()) { + return; + } + int current = tlsSuspendLateWriteChecks.get(); + MOZ_ASSERT(current > 0); + tlsSuspendLateWriteChecks.set(current - 1); +} + +} // namespace mozilla diff --git a/xpcom/build/LateWriteChecks.h b/xpcom/build/LateWriteChecks.h new file mode 100644 index 0000000000..6c9ee419f9 --- /dev/null +++ b/xpcom/build/LateWriteChecks.h @@ -0,0 +1,78 @@ +/* -*- 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_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(); + +/** + * Temporarily suspend late write checks for the current thread. This is useful + * if you're about to perform a write, but it would be fine if this write were + * interrupted or skipped during a fast shutdown. + */ +void PushSuspendLateWriteChecks(); + +/** + * Resume late write checks for the current thread, assuming an ancestor in the + * call stack hasn't also pushed a suspension. + */ +void PopSuspendLateWriteChecks(); + +class MOZ_RAII AutoSuspendLateWriteChecks { + public: + AutoSuspendLateWriteChecks() { PushSuspendLateWriteChecks(); } + ~AutoSuspendLateWriteChecks() { PopSuspendLateWriteChecks(); } +}; + +} // namespace mozilla + +#endif // mozilla_LateWriteChecks_h diff --git a/xpcom/build/MainThreadIOLogger.cpp b/xpcom/build/MainThreadIOLogger.cpp new file mode 100644 index 0000000000..8fe2d9eba5 --- /dev/null +++ b/xpcom/build/MainThreadIOLogger.cpp @@ -0,0 +1,206 @@ +/* -*- 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 "MainThreadIOLogger.h" + +#include "GeckoProfiler.h" +#include "IOInterposerPrivate.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" +#include "nsNativeCharsetUtils.h" +#include "nsThreadUtils.h" + +/** + * This code uses NSPR stuff and STL containers because it must be detached + * from leak checking code; this observer runs until the process terminates. + */ + +#include <prenv.h> +#include <prprf.h> +#include <prthread.h> +#include <vector> + +namespace { + +struct ObservationWithStack { + explicit ObservationWithStack(mozilla::IOInterposeObserver::Observation& aObs, + ProfilerBacktrace* aStack) + : mObservation(aObs), mStack(aStack) { + aObs.Filename(mFilename); + } + + mozilla::IOInterposeObserver::Observation mObservation; + ProfilerBacktrace* mStack; + nsString mFilename; +}; + +class MainThreadIOLoggerImpl final : public mozilla::IOInterposeObserver { + public: + MainThreadIOLoggerImpl(); + ~MainThreadIOLoggerImpl(); + + bool Init(); + + void Observe(Observation& aObservation) override; + + private: + static void sIOThreadFunc(void* aArg); + void IOThreadFunc(); + + mozilla::TimeStamp mLogStartTime; + const char* mFileName; + PRThread* mIOThread; + mozilla::IOInterposer::Monitor mMonitor; + bool mShutdownRequired MOZ_GUARDED_BY(mMonitor); + std::vector<ObservationWithStack> mObservations MOZ_GUARDED_BY(mMonitor); +}; + +static mozilla::StaticAutoPtr<MainThreadIOLoggerImpl> sImpl; + +MainThreadIOLoggerImpl::MainThreadIOLoggerImpl() + : mFileName(nullptr), mIOThread(nullptr), mShutdownRequired(false) {} + +MainThreadIOLoggerImpl::~MainThreadIOLoggerImpl() { + if (!mIOThread) { + return; + } + { + // Scope for lock + mozilla::IOInterposer::MonitorAutoLock lock(mMonitor); + mShutdownRequired = true; + mMonitor.Notify(); + } + PR_JoinThread(mIOThread); + mIOThread = nullptr; +} + +bool MainThreadIOLoggerImpl::Init() { + if (mFileName) { + // Already initialized + return true; + } + mFileName = PR_GetEnv("MOZ_MAIN_THREAD_IO_LOG"); + if (!mFileName) { + // Can't start + return false; + } + mIOThread = + PR_CreateThread(PR_USER_THREAD, &sIOThreadFunc, this, PR_PRIORITY_LOW, + PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0); + if (!mIOThread) { + return false; + } + return true; +} + +/* static */ +void MainThreadIOLoggerImpl::sIOThreadFunc(void* aArg) { + AUTO_PROFILER_REGISTER_THREAD("MainThreadIOLogger"); + + NS_SetCurrentThreadName("MainThreadIOLogger"); + MainThreadIOLoggerImpl* obj = static_cast<MainThreadIOLoggerImpl*>(aArg); + obj->IOThreadFunc(); +} + +void MainThreadIOLoggerImpl::IOThreadFunc() { + PRFileDesc* fd = PR_Open(mFileName, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, + PR_IRUSR | PR_IWUSR | PR_IRGRP); + if (!fd) { + mozilla::IOInterposer::MonitorAutoLock lock(mMonitor); + mShutdownRequired = true; + std::vector<ObservationWithStack>().swap(mObservations); + return; + } + mLogStartTime = mozilla::TimeStamp::Now(); + { + // Scope for lock + mozilla::IOInterposer::MonitorAutoLock lock(mMonitor); + while (true) { + while (!mShutdownRequired && mObservations.empty()) { + mMonitor.Wait(); + } + if (mShutdownRequired) { + break; + } + // Pull events off the shared array onto a local one + std::vector<ObservationWithStack> observationsToWrite; + observationsToWrite.swap(mObservations); + + // Release the lock so that we're not holding anybody up during I/O + mozilla::IOInterposer::MonitorAutoUnlock unlock(mMonitor); + + // Now write the events. + for (auto i = observationsToWrite.begin(), e = observationsToWrite.end(); + i != e; ++i) { + if (i->mObservation.ObservedOperation() == OpNextStage) { + PR_fprintf( + fd, "%f,NEXT-STAGE\n", + (mozilla::TimeStamp::Now() - mLogStartTime).ToMilliseconds()); + continue; + } + double durationMs = i->mObservation.Duration().ToMilliseconds(); + nsAutoCString nativeFilename; + nativeFilename.AssignLiteral("(not available)"); + if (!i->mFilename.IsEmpty()) { + if (NS_FAILED(NS_CopyUnicodeToNative(i->mFilename, nativeFilename))) { + nativeFilename.AssignLiteral("(conversion failed)"); + } + } + // clang-format off + /** + * Format: + * Start Timestamp (Milliseconds), Operation, Duration (Milliseconds), Event Source, Filename + */ + // clang-format on + if (PR_fprintf( + fd, "%f,%s,%f,%s,%s\n", + (i->mObservation.Start() - mLogStartTime).ToMilliseconds(), + i->mObservation.ObservedOperationString(), durationMs, + i->mObservation.Reference(), nativeFilename.get()) > 0) { + // TODO: Write out the callstack + i->mStack = nullptr; + } + } + } + } + PR_Close(fd); +} + +void MainThreadIOLoggerImpl::Observe(Observation& aObservation) { + if (!mFileName || !IsMainThread()) { + return; + } + mozilla::IOInterposer::MonitorAutoLock lock(mMonitor); + if (mShutdownRequired) { + // The writer thread isn't running. Don't enqueue any more data. + return; + } + // Passing nullptr as aStack parameter for now + mObservations.push_back(ObservationWithStack(aObservation, nullptr)); + mMonitor.Notify(); +} + +} // namespace + +namespace mozilla { + +namespace MainThreadIOLogger { + +bool Init() { + auto impl = MakeUnique<MainThreadIOLoggerImpl>(); + if (!impl->Init()) { + return false; + } + sImpl = impl.release(); + IOInterposer::Register(IOInterposeObserver::OpAllWithStaging, sImpl); + return true; +} + +} // namespace MainThreadIOLogger + +} // namespace mozilla diff --git a/xpcom/build/MainThreadIOLogger.h b/xpcom/build/MainThreadIOLogger.h new file mode 100644 index 0000000000..cdf3b8728a --- /dev/null +++ b/xpcom/build/MainThreadIOLogger.h @@ -0,0 +1,18 @@ +/* -*- 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_MainThreadIOLogger_h +#define mozilla_MainThreadIOLogger_h + +namespace mozilla { +namespace MainThreadIOLogger { + +bool Init(); + +} // namespace MainThreadIOLogger +} // namespace mozilla + +#endif // mozilla_MainThreadIOLogger_h diff --git a/xpcom/build/NSPRInterposer.cpp b/xpcom/build/NSPRInterposer.cpp new file mode 100644 index 0000000000..184c3793e7 --- /dev/null +++ b/xpcom/build/NSPRInterposer.cpp @@ -0,0 +1,207 @@ +/* -*- 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 "IOInterposer.h" +#include "NSPRInterposer.h" + +#include "prio.h" +#include "private/pprio.h" +#include "nsDebug.h" +#include "nscore.h" + +#include <sys/param.h> +#ifdef XP_MACOSX +# include <fcntl.h> +#else +# include "prprf.h" +# include <unistd.h> +#endif + +namespace { + +/* Original IO methods */ +PRCloseFN sCloseFn = nullptr; +PRReadFN sReadFn = nullptr; +PRWriteFN sWriteFn = nullptr; +PRFsyncFN sFSyncFn = nullptr; +PRFileInfoFN sFileInfoFn = nullptr; +PRFileInfo64FN sFileInfo64Fn = nullptr; + +static int32_t GetPathFromFd(int32_t aFd, char* aBuf, size_t aBufSize) { +#ifdef XP_MACOSX + NS_ASSERTION(aBufSize >= MAXPATHLEN, + "aBufSize should be a least MAXPATHLEN long"); + + return fcntl(aFd, F_GETPATH, aBuf); +#else + char procPath[32]; + if (PR_snprintf(procPath, sizeof(procPath), "/proc/self/fd/%i", aFd) == + (PRUint32)-1) { + return -1; + } + + int32_t ret = readlink(procPath, aBuf, aBufSize - 1); + if (ret > -1) { + aBuf[ret] = '\0'; + } + + return ret; +#endif +} + +/** + * RAII class for timing the duration of an NSPR I/O call and reporting the + * result to the mozilla::IOInterposeObserver API. + */ +class NSPRIOAutoObservation : public mozilla::IOInterposeObserver::Observation { + public: + explicit NSPRIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp, + PRFileDesc* aFd) + : mozilla::IOInterposeObserver::Observation(aOp, "NSPRIOInterposer") { + char filename[MAXPATHLEN]; + if (mShouldReport && aFd && + GetPathFromFd(PR_FileDesc2NativeHandle(aFd), filename, + sizeof(filename)) != -1) { + CopyUTF8toUTF16(mozilla::MakeStringSpan(filename), mFilename); + } else { + mFilename.Truncate(); + } + } + + void Filename(nsAString& aFilename) override { aFilename = mFilename; } + + ~NSPRIOAutoObservation() override { Report(); } + + private: + nsString mFilename; +}; + +PRStatus PR_CALLBACK interposedClose(PRFileDesc* aFd) { + // If we don't have a valid original function pointer something is very wrong. + NS_ASSERTION(sCloseFn, "NSPR IO Interposing: sCloseFn is NULL"); + + NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpClose, aFd); + return sCloseFn(aFd); +} + +int32_t PR_CALLBACK interposedRead(PRFileDesc* aFd, void* aBuf, int32_t aAmt) { + // If we don't have a valid original function pointer something is very wrong. + NS_ASSERTION(sReadFn, "NSPR IO Interposing: sReadFn is NULL"); + + NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpRead, aFd); + return sReadFn(aFd, aBuf, aAmt); +} + +int32_t PR_CALLBACK interposedWrite(PRFileDesc* aFd, const void* aBuf, + int32_t aAmt) { + // If we don't have a valid original function pointer something is very wrong. + NS_ASSERTION(sWriteFn, "NSPR IO Interposing: sWriteFn is NULL"); + + NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFd); + return sWriteFn(aFd, aBuf, aAmt); +} + +PRStatus PR_CALLBACK interposedFSync(PRFileDesc* aFd) { + // If we don't have a valid original function pointer something is very wrong. + NS_ASSERTION(sFSyncFn, "NSPR IO Interposing: sFSyncFn is NULL"); + + NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpFSync, aFd); + return sFSyncFn(aFd); +} + +PRStatus PR_CALLBACK interposedFileInfo(PRFileDesc* aFd, PRFileInfo* aInfo) { + // If we don't have a valid original function pointer something is very wrong. + NS_ASSERTION(sFileInfoFn, "NSPR IO Interposing: sFileInfoFn is NULL"); + + NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpStat, aFd); + return sFileInfoFn(aFd, aInfo); +} + +PRStatus PR_CALLBACK interposedFileInfo64(PRFileDesc* aFd, + PRFileInfo64* aInfo) { + // If we don't have a valid original function pointer something is very wrong. + NS_ASSERTION(sFileInfo64Fn, "NSPR IO Interposing: sFileInfo64Fn is NULL"); + + NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpStat, aFd); + return sFileInfo64Fn(aFd, aInfo); +} + +} // namespace + +namespace mozilla { + +void InitNSPRIOInterposing() { + // Check that we have not interposed any of the IO methods before + MOZ_ASSERT(!sCloseFn && !sReadFn && !sWriteFn && !sFSyncFn && !sFileInfoFn && + !sFileInfo64Fn); + + // We can't actually use this assertion because we initialize this code + // before XPCOM is initialized, so NS_IsMainThread() always returns false. + // MOZ_ASSERT(NS_IsMainThread()); + + // Get IO methods from NSPR and const cast the structure so we can modify it. + PRIOMethods* methods = const_cast<PRIOMethods*>(PR_GetFileMethods()); + + // Something is badly wrong if we don't get IO methods... However, we don't + // want to crash over that in non-debug builds. This is unlikely to happen + // so an assert is enough, no need to report it to the caller. + MOZ_ASSERT(methods); + if (!methods) { + return; + } + + // Store original functions + sCloseFn = methods->close; + sReadFn = methods->read; + sWriteFn = methods->write; + sFSyncFn = methods->fsync; + sFileInfoFn = methods->fileInfo; + sFileInfo64Fn = methods->fileInfo64; + + // Overwrite with our interposed functions + methods->close = &interposedClose; + methods->read = &interposedRead; + methods->write = &interposedWrite; + methods->fsync = &interposedFSync; + methods->fileInfo = &interposedFileInfo; + methods->fileInfo64 = &interposedFileInfo64; +} + +void ClearNSPRIOInterposing() { + // If we have already cleared IO interposing, or not initialized it this is + // actually bad. + MOZ_ASSERT(sCloseFn && sReadFn && sWriteFn && sFSyncFn && sFileInfoFn && + sFileInfo64Fn); + + // Get IO methods from NSPR and const cast the structure so we can modify it. + PRIOMethods* methods = const_cast<PRIOMethods*>(PR_GetFileMethods()); + + // Something is badly wrong if we don't get IO methods... However, we don't + // want to crash over that in non-debug builds. This is unlikely to happen + // so an assert is enough, no need to report it to the caller. + MOZ_ASSERT(methods); + if (!methods) { + return; + } + + // Restore original functions + methods->close = sCloseFn; + methods->read = sReadFn; + methods->write = sWriteFn; + methods->fsync = sFSyncFn; + methods->fileInfo = sFileInfoFn; + methods->fileInfo64 = sFileInfo64Fn; + + // Forget about original functions + sCloseFn = nullptr; + sReadFn = nullptr; + sWriteFn = nullptr; + sFSyncFn = nullptr; + sFileInfoFn = nullptr; + sFileInfo64Fn = nullptr; +} + +} // namespace mozilla diff --git a/xpcom/build/NSPRInterposer.h b/xpcom/build/NSPRInterposer.h new file mode 100644 index 0000000000..3f811df60c --- /dev/null +++ b/xpcom/build/NSPRInterposer.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 NSPRINTERPOSER_H_ +#define NSPRINTERPOSER_H_ + +namespace mozilla { + +/** + * Initialize IO interposing for NSPR. This will report NSPR read, writes and + * fsyncs to the IOInterposerObserver. It is only safe to call this from the + * main-thread when no other threads are running. + */ +void InitNSPRIOInterposing(); + +/** + * Removes interception of NSPR IO methods as setup by InitNSPRIOInterposing. + * Note, that it is only safe to call this on the main-thread when all other + * threads have stopped. Which is typically the case at shutdown. + */ +void ClearNSPRIOInterposing(); + +} // namespace mozilla + +#endif // NSPRINTERPOSER_H_ diff --git a/xpcom/build/Omnijar.cpp b/xpcom/build/Omnijar.cpp new file mode 100644 index 0000000000..2c6e3eb478 --- /dev/null +++ b/xpcom/build/Omnijar.cpp @@ -0,0 +1,198 @@ +/* -*- 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 "Omnijar.h" + +#include "nsDirectoryService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIFile.h" +#include "nsZipArchive.h" +#include "nsNetUtil.h" + +namespace mozilla { + +StaticRefPtr<nsIFile> Omnijar::sPath[2]; +StaticRefPtr<nsZipArchive> Omnijar::sReader[2]; +StaticRefPtr<nsZipArchive> Omnijar::sOuterReader[2]; +bool Omnijar::sInitialized = false; +bool Omnijar::sIsUnified = false; + +static const char* sProp[2] = {NS_GRE_DIR, NS_XPCOM_CURRENT_PROCESS_DIR}; + +#define SPROP(Type) ((Type == mozilla::Omnijar::GRE) ? sProp[GRE] : sProp[APP]) + +void Omnijar::CleanUpOne(Type aType) { + if (sReader[aType]) { + sReader[aType] = nullptr; + } + if (sOuterReader[aType]) { + sOuterReader[aType] = nullptr; + } + sPath[aType] = nullptr; +} + +void Omnijar::InitOne(nsIFile* aPath, Type aType) { + nsCOMPtr<nsIFile> file; + if (aPath) { + file = aPath; + } else { + nsCOMPtr<nsIFile> dir; + nsDirectoryService::gService->Get(SPROP(aType), NS_GET_IID(nsIFile), + getter_AddRefs(dir)); + constexpr auto kOmnijarName = nsLiteralCString{MOZ_STRINGIFY(OMNIJAR_NAME)}; + if (NS_FAILED(dir->Clone(getter_AddRefs(file))) || + NS_FAILED(file->AppendNative(kOmnijarName))) { + return; + } + } + bool isFile; + if (NS_FAILED(file->IsFile(&isFile)) || !isFile) { + // If we're not using an omni.jar for GRE, and we don't have an + // omni.jar for APP, check if both directories are the same. + if ((aType == APP) && (!sPath[GRE])) { + nsCOMPtr<nsIFile> greDir, appDir; + bool equals; + nsDirectoryService::gService->Get(sProp[GRE], NS_GET_IID(nsIFile), + getter_AddRefs(greDir)); + nsDirectoryService::gService->Get(sProp[APP], NS_GET_IID(nsIFile), + getter_AddRefs(appDir)); + if (NS_SUCCEEDED(greDir->Equals(appDir, &equals)) && equals) { + sIsUnified = true; + } + } + return; + } + + bool equals; + if ((aType == APP) && (sPath[GRE]) && + NS_SUCCEEDED(sPath[GRE]->Equals(file, &equals)) && equals) { + // If we're using omni.jar on both GRE and APP and their path + // is the same, we're in the unified case. + sIsUnified = true; + return; + } + + RefPtr<nsZipArchive> zipReader = nsZipArchive::OpenArchive(file); + if (!zipReader) { + return; + } + + RefPtr<nsZipArchive> outerReader; + RefPtr<nsZipHandle> handle; + if (NS_SUCCEEDED(nsZipHandle::Init(zipReader, MOZ_STRINGIFY(OMNIJAR_NAME), + getter_AddRefs(handle)))) { + outerReader = zipReader; + zipReader = nsZipArchive::OpenArchive(handle); + if (!zipReader) { + return; + } + } + + CleanUpOne(aType); + sReader[aType] = zipReader; + sOuterReader[aType] = outerReader; + sPath[aType] = file; +} + +void Omnijar::Init(nsIFile* aGrePath, nsIFile* aAppPath) { + InitOne(aGrePath, GRE); + InitOne(aAppPath, APP); + sInitialized = true; +} + +void Omnijar::CleanUp() { + CleanUpOne(GRE); + CleanUpOne(APP); + sInitialized = false; +} + +already_AddRefed<nsZipArchive> Omnijar::GetReader(nsIFile* aPath) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + + bool equals; + nsresult rv; + + if (sPath[GRE]) { + rv = sPath[GRE]->Equals(aPath, &equals); + if (NS_SUCCEEDED(rv) && equals) { + return IsNested(GRE) ? GetOuterReader(GRE) : GetReader(GRE); + } + } + if (sPath[APP]) { + rv = sPath[APP]->Equals(aPath, &equals); + if (NS_SUCCEEDED(rv) && equals) { + return IsNested(APP) ? GetOuterReader(APP) : GetReader(APP); + } + } + return nullptr; +} + +already_AddRefed<nsZipArchive> Omnijar::GetInnerReader( + nsIFile* aPath, const nsACString& aEntry) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + + if (!aEntry.EqualsLiteral(MOZ_STRINGIFY(OMNIJAR_NAME))) { + return nullptr; + } + + bool equals; + nsresult rv; + + if (sPath[GRE]) { + rv = sPath[GRE]->Equals(aPath, &equals); + if (NS_SUCCEEDED(rv) && equals) { + return IsNested(GRE) ? GetReader(GRE) : nullptr; + } + } + if (sPath[APP]) { + rv = sPath[APP]->Equals(aPath, &equals); + if (NS_SUCCEEDED(rv) && equals) { + return IsNested(APP) ? GetReader(APP) : nullptr; + } + } + return nullptr; +} + +nsresult Omnijar::GetURIString(Type aType, nsACString& aResult) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + + aResult.Truncate(); + + // Return an empty string for APP in the unified case. + if ((aType == APP) && sIsUnified) { + return NS_OK; + } + + nsAutoCString omniJarSpec; + if (sPath[aType]) { + nsresult rv = NS_GetURLSpecFromActualFile(sPath[aType], omniJarSpec); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + aResult = "jar:"; + if (IsNested(aType)) { + aResult += "jar:"; + } + aResult += omniJarSpec; + aResult += "!"; + if (IsNested(aType)) { + aResult += "/" MOZ_STRINGIFY(OMNIJAR_NAME) "!"; + } + } else { + nsCOMPtr<nsIFile> dir; + nsDirectoryService::gService->Get(SPROP(aType), NS_GET_IID(nsIFile), + getter_AddRefs(dir)); + nsresult rv = NS_GetURLSpecFromActualFile(dir, aResult); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + aResult += "/"; + return NS_OK; +} + +} /* namespace mozilla */ diff --git a/xpcom/build/Omnijar.h b/xpcom/build/Omnijar.h new file mode 100644 index 0000000000..1609a3199b --- /dev/null +++ b/xpcom/build/Omnijar.h @@ -0,0 +1,171 @@ +/* -*- 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_Omnijar_h +#define mozilla_Omnijar_h + +#include "nscore.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsIFile.h" +#include "nsZipArchive.h" + +#include "mozilla/StaticPtr.h" + +namespace mozilla { + +class Omnijar { + private: + /** + * Store an nsIFile for an omni.jar. We can store two paths here, one + * for GRE (corresponding to resource://gre/) and one for APP + * (corresponding to resource:/// and resource://app/), but only + * store one when both point to the same location (unified). + */ + static StaticRefPtr<nsIFile> sPath[2]; + + /** + * Cached nsZipArchives for the corresponding sPath + */ + static StaticRefPtr<nsZipArchive> sReader[2]; + + /** + * Cached nsZipArchives for the outer jar, when using nested jars. + * Otherwise nullptr. + */ + static StaticRefPtr<nsZipArchive> sOuterReader[2]; + + /** + * Has Omnijar::Init() been called? + */ + static bool sInitialized; + + /** + * Is using unified GRE/APP jar? + */ + static bool sIsUnified; + + public: + enum Type { GRE = 0, APP = 1 }; + + private: + /** + * Returns whether we are using nested jars. + */ + static inline bool IsNested(Type aType) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + return !!sOuterReader[aType]; + } + + /** + * Returns a nsZipArchive pointer for the outer jar file when using nested + * jars. Returns nullptr in the same cases GetPath() would, or if not using + * nested jars. + */ + static inline already_AddRefed<nsZipArchive> GetOuterReader(Type aType) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + RefPtr<nsZipArchive> reader = sOuterReader[aType].get(); + return reader.forget(); + } + + public: + /** + * Returns whether SetBase has been called at least once with + * a valid nsIFile + */ + static inline bool IsInitialized() { return sInitialized; } + + /** + * Initializes the Omnijar API with the given directory or file for GRE and + * APP. Each of the paths given can be: + * - a file path, pointing to the omnijar file, + * - a directory path, pointing to a directory containing an "omni.jar" file, + * - nullptr for autodetection of an "omni.jar" file. + */ + static void Init(nsIFile* aGrePath = nullptr, nsIFile* aAppPath = nullptr); + + /** + * Cleans up the Omnijar API + */ + static void CleanUp(); + + /** + * Returns an nsIFile pointing to the omni.jar file for GRE or APP. + * Returns nullptr when there is no corresponding omni.jar. + * Also returns nullptr for APP in the unified case. + */ + static inline already_AddRefed<nsIFile> GetPath(Type aType) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + nsCOMPtr<nsIFile> path = sPath[aType].get(); + return path.forget(); + } + + /** + * Returns whether GRE or APP use an omni.jar. Returns PR_False for + * APP when using an omni.jar in the unified case. + */ + static inline bool HasOmnijar(Type aType) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + return !!sPath[aType]; + } + + /** + * Returns a nsZipArchive pointer for the omni.jar file for GRE or + * APP. Returns nullptr in the same cases GetPath() would. + */ + static inline already_AddRefed<nsZipArchive> GetReader(Type aType) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + RefPtr<nsZipArchive> reader = sReader[aType].get(); + return reader.forget(); + } + + /** + * Returns a nsZipArchive pointer for the given path IAOI the given + * path is the omni.jar for either GRE or APP. + */ + static already_AddRefed<nsZipArchive> GetReader(nsIFile* aPath); + + /** + * In the case of a nested omnijar, this returns the inner reader for the + * omnijar if aPath points to the outer archive and aEntry is the omnijar + * entry name. Returns null otherwise. + * In concrete terms: On Android the omnijar is nested inside the apk archive. + * GetReader("path/to.apk") returns the outer reader and GetInnerReader( + * "path/to.apk", "assets/omni.ja") returns the inner reader. + */ + static already_AddRefed<nsZipArchive> GetInnerReader( + nsIFile* aPath, const nsACString& aEntry); + + /** + * Returns the URI string corresponding to the omni.jar or directory + * for GRE or APP. i.e. jar:/path/to/omni.jar!/ for omni.jar and + * /path/to/base/dir/ otherwise. Returns an empty string for APP in + * the unified case. + * The returned URI is guaranteed to end with a slash. + */ + static nsresult GetURIString(Type aType, nsACString& aResult); + + private: + /** + * Used internally, respectively by Init() and CleanUp() + */ + static void InitOne(nsIFile* aPath, Type aType); + static void CleanUpOne(Type aType); +}; /* class Omnijar */ + +/** + * Returns whether or not the currently running build is an unpackaged + * developer build. This check is implemented by looking for omni.ja in the + * the obj/dist dir. We use this routine to detect when the build dir will + * use symlinks to the repo and object dir. + */ +inline bool IsPackagedBuild() { + return Omnijar::HasOmnijar(mozilla::Omnijar::GRE); +} + +} /* namespace mozilla */ + +#endif /* mozilla_Omnijar_h */ diff --git a/xpcom/build/PoisonIOInterposer.h b/xpcom/build/PoisonIOInterposer.h new file mode 100644 index 0000000000..20adeb835b --- /dev/null +++ b/xpcom/build/PoisonIOInterposer.h @@ -0,0 +1,93 @@ +/* -*- 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_PoisonIOInterposer_h +#define mozilla_PoisonIOInterposer_h + +#include "mozilla/Types.h" +#include <stdio.h> + +MOZ_BEGIN_EXTERN_C + +/** Register file handle to be ignored by poisoning IO interposer. This function + * and the corresponding UnRegister function are necessary for exchange of + * handles between binaries not using the same CRT on Windows (which happens + * when one of them links the static CRT). In such cases, giving file + * descriptors or FILEs + * doesn't work because _get_osfhandle fails with "invalid parameter". */ +void MozillaRegisterDebugHandle(intptr_t aHandle); + +/** Register file descriptor to be ignored by poisoning IO interposer */ +void MozillaRegisterDebugFD(int aFd); + +/** Register file to be ignored by poisoning IO interposer */ +void MozillaRegisterDebugFILE(FILE* aFile); + +/** Unregister file handle from being ignored by poisoning IO interposer */ +void MozillaUnRegisterDebugHandle(intptr_t aHandle); + +/** Unregister file descriptor from being ignored by poisoning IO interposer */ +void MozillaUnRegisterDebugFD(int aFd); + +/** Unregister file from being ignored by poisoning IO interposer */ +void MozillaUnRegisterDebugFILE(FILE* aFile); + +MOZ_END_EXTERN_C + +#if defined(XP_MACOSX) || defined(XP_WIN) + +# 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. + * Never called! See bug 1647107. + */ +void ClearPoisonIOInterposer(); + +} // namespace mozilla +# endif /* __cplusplus */ + +#else /* defined(XP_MACOSX) || defined(XP_WIN) */ + +# 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 /* XP_WIN || XP_MACOSX */ + +#endif // mozilla_PoisonIOInterposer_h diff --git a/xpcom/build/PoisonIOInterposerBase.cpp b/xpcom/build/PoisonIOInterposerBase.cpp new file mode 100644 index 0000000000..8add6740da --- /dev/null +++ b/xpcom/build/PoisonIOInterposerBase.cpp @@ -0,0 +1,268 @@ +/* -*- 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/Maybe.h" +#include "mozilla/Mutex.h" +#include "mozilla/Scoped.h" +#include "mozilla/UniquePtr.h" + +#include <algorithm> + +#include "PoisonIOInterposer.h" + +#include "prlock.h" + +#ifdef MOZ_REPLACE_MALLOC +# include "replace_malloc_bridge.h" +#endif + +// Auxiliary method to convert file descriptors to ids +#if defined(XP_WIN) +# include <io.h> +inline mozilla::Maybe<intptr_t> FileDescriptorToHandle(int aFd) { + intptr_t handle = _get_osfhandle(aFd); + if ((handle == -1) || (handle == -2)) { + // -1: Invalid handle. -2: stdin/out/err not associated with a stream. + return mozilla::Nothing(); + } + return mozilla::Some(handle); +} +#else +inline mozilla::Maybe<intptr_t> FileDescriptorToHandle(int aFd) { + return mozilla::Some<intptr_t>(aFd); +} +#endif /* if not XP_WIN */ + +namespace { + +struct DebugFilesAutoLockTraits { + typedef PRLock* type; + typedef const PRLock* const_type; + static const_type empty() { return nullptr; } + static void release(type aL) { PR_Unlock(aL); } +}; + +class DebugFilesAutoLock : public mozilla::Scoped<DebugFilesAutoLockTraits> { + static PRLock* Lock; + + public: + 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() + : mozilla::Scoped<DebugFilesAutoLockTraits>(getDebugFileIDsLock()) { + PR_Lock(get()); + } +}; + +PRLock* DebugFilesAutoLock::Lock; + +// The ChunkedList<T> class implements, at the high level, a non-iterable +// list of instances of T. Its goal is to be somehow minimalist for the +// use case of storing the debug files handles here, with the property of +// not requiring a lock to look up whether it contains a specific value. +// It is also chunked in blocks of chunk_size bytes so that its +// initialization doesn't require a memory allocation, while keeping the +// possibility to increase its size as necessary. Note that chunks are +// never deallocated (except in the destructor). +// All operations are essentially O(N) but N is not expected to be large +// enough to matter. +template <typename T, size_t chunk_size = 64> +class ChunkedList { + struct ListChunk { + static const size_t kLength = + (chunk_size - sizeof(ListChunk*)) / sizeof(mozilla::Atomic<T>); + + mozilla::Atomic<T> mElements[kLength]; + mozilla::UniquePtr<ListChunk> mNext; + + ListChunk() : mNext(nullptr) {} + }; + + ListChunk mList; + mozilla::Atomic<size_t> mLength; + + public: + ChunkedList() : mLength(0) {} + + ~ChunkedList() { + // There can be writes happening after this destructor runs, so keep + // the list contents and don't reset mLength. But if there are more + // elements left than the first chunk can hold, then all hell breaks + // loose for any write that would happen after that because any extra + // chunk would be deallocated, so just crash in that case. + MOZ_RELEASE_ASSERT(mLength <= ListChunk::kLength); + } + + // Add an element at the end of the last chunk of the list. Create a new + // chunk if there is not enough room. + // This is not thread-safe with another thread calling Add or Remove. + void Add(T aValue) { + ListChunk* list = &mList; + size_t position = mLength; + for (; position >= ListChunk::kLength; position -= ListChunk::kLength) { + if (!list->mNext) { + list->mNext.reset(new ListChunk()); + } + list = list->mNext.get(); + } + // Use an order of operations that ensures any racing Contains call + // can't be hurt. + list->mElements[position] = aValue; + mLength++; + } + + // Remove an element from the list by replacing it with the last element + // of the list, and then shrinking the list. + // This is not thread-safe with another thread calling Add or Remove. + void Remove(T aValue) { + if (!mLength) { + return; + } + ListChunk* list = &mList; + size_t last = mLength - 1; + do { + size_t position = 0; + // Look for an element matching the given value. + for (; position < ListChunk::kLength; position++) { + if (aValue == list->mElements[position]) { + ListChunk* last_list = list; + // Look for the last element in the list, starting from where we are + // instead of starting over. + for (; last >= ListChunk::kLength; last -= ListChunk::kLength) { + last_list = last_list->mNext.get(); + } + // Use an order of operations that ensures any racing Contains call + // can't be hurt. + T value = last_list->mElements[last]; + list->mElements[position] = value; + mLength--; + return; + } + } + last -= ListChunk::kLength; + list = list->mNext.get(); + } while (list); + } + + // Returns whether the list contains the given value. It is meant to be safe + // to use without locking, with the tradeoff of being not entirely accurate + // if another thread adds or removes an element while this function runs. + bool Contains(T aValue) { + ListChunk* list = &mList; + // Fix the range of the lookup to whatever the list length is when the + // function is called. + size_t length = mLength; + do { + size_t list_length = ListChunk::kLength; + list_length = std::min(list_length, length); + for (size_t position = 0; position < list_length; position++) { + if (aValue == list->mElements[position]) { + return true; + } + } + length -= ListChunk::kLength; + list = list->mNext.get(); + } while (list); + + return false; + } +}; + +typedef ChunkedList<intptr_t> FdList; + +// Return a list 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. +FdList& getDebugFileIDs() { + static FdList DebugFileIDs; + return DebugFileIDs; +} + +} // 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) { + return getDebugFileIDs().Contains(aFileID); +} + +} // namespace mozilla + +extern "C" { + +void MozillaRegisterDebugHandle(intptr_t aHandle) { + DebugFilesAutoLock lockedScope; + FdList& DebugFileIDs = getDebugFileIDs(); + MOZ_ASSERT(!DebugFileIDs.Contains(aHandle)); + DebugFileIDs.Add(aHandle); +} + +void MozillaRegisterDebugFD(int aFd) { + mozilla::Maybe<intptr_t> handle = FileDescriptorToHandle(aFd); + if (!handle.isSome()) { + return; + } + MozillaRegisterDebugHandle(handle.value()); +} + +void MozillaRegisterDebugFILE(FILE* aFile) { + int fd = fileno(aFile); + if (fd == 1 || fd == 2) { + return; + } + MozillaRegisterDebugFD(fd); +} + +void MozillaUnRegisterDebugHandle(intptr_t aHandle) { + DebugFilesAutoLock lockedScope; + FdList& DebugFileIDs = getDebugFileIDs(); + MOZ_ASSERT(DebugFileIDs.Contains(aHandle)); + DebugFileIDs.Remove(aHandle); +} + +void MozillaUnRegisterDebugFD(int aFd) { + mozilla::Maybe<intptr_t> handle = FileDescriptorToHandle(aFd); + if (!handle.isSome()) { + return; + } + MozillaUnRegisterDebugHandle(handle.value()); +} + +void MozillaUnRegisterDebugFILE(FILE* aFile) { + int fd = fileno(aFile); + if (fd == 1 || fd == 2) { + return; + } + fflush(aFile); + MozillaUnRegisterDebugFD(fd); +} + +} // extern "C" + +#ifdef MOZ_REPLACE_MALLOC +void mozilla::DebugFdRegistry::RegisterHandle(intptr_t aHandle) { + MozillaRegisterDebugHandle(aHandle); +} + +void mozilla::DebugFdRegistry::UnRegisterHandle(intptr_t aHandle) { + MozillaUnRegisterDebugHandle(aHandle); +} +#endif diff --git a/xpcom/build/PoisonIOInterposerMac.cpp b/xpcom/build/PoisonIOInterposerMac.cpp new file mode 100644 index 0000000000..21eca58757 --- /dev/null +++ b/xpcom/build/PoisonIOInterposerMac.cpp @@ -0,0 +1,348 @@ +/* -*- 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 "PoisonIOInterposer.h" +// Disabled until bug 1658385 is fixed. +#ifndef __aarch64__ +# include "mach_override.h" +#endif + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/Mutex.h" +#include "mozilla/ProcessedStack.h" +#include "mozilla/Telemetry.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsPrintfCString.h" +#include "mozilla/StackWalk.h" +#include "nsTraceRefcnt.h" +#include "prio.h" + +#include <algorithm> +#include <vector> + +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <aio.h> +#include <dlfcn.h> +#include <fcntl.h> +#include <unistd.h> + +#ifdef MOZ_REPLACE_MALLOC +# include "replace_malloc_bridge.h" +#endif + +namespace { + +// 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 aFd, const void* aWbuf, size_t aCount); +bool IsIPCWrite(int aFd, const struct stat& aBuf); + +/******************************** IO AutoTimer ********************************/ + +/** + * RAII class for timing the duration of an I/O call and reporting the result + * to the mozilla::IOInterposeObserver API. + */ +class MacIOAutoObservation : public mozilla::IOInterposeObserver::Observation { + public: + MacIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp, int aFd) + : mozilla::IOInterposeObserver::Observation( + aOp, sReference, sIsEnabled && !mozilla::IsDebugFile(aFd)), + mFd(aFd), + mHasQueriedFilename(false) {} + + MacIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp, int aFd, + const void* aBuf, size_t aCount) + : mozilla::IOInterposeObserver::Observation( + aOp, sReference, + sIsEnabled && !mozilla::IsDebugFile(aFd) && + IsValidWrite(aFd, aBuf, aCount)), + mFd(aFd), + mHasQueriedFilename(false) {} + + // Custom implementation of + // mozilla::IOInterposeObserver::Observation::Filename + void Filename(nsAString& aFilename) override; + + ~MacIOAutoObservation() { Report(); } + + private: + int mFd; + bool mHasQueriedFilename; + nsString mFilename; + static const char* sReference; +}; + +const char* MacIOAutoObservation::sReference = "PoisonIOInterposer"; + +// Get filename for this observation +void MacIOAutoObservation::Filename(nsAString& aFilename) { + // If mHasQueriedFilename is true, then we already have it + if (mHasQueriedFilename) { + aFilename = mFilename; + return; + } + + char filename[MAXPATHLEN]; + if (fcntl(mFd, F_GETPATH, filename) != -1) { + CopyUTF8toUTF16(filename, mFilename); + } else { + mFilename.Truncate(); + } + mHasQueriedFilename = true; + + aFilename = mFilename; +} + +/****************************** 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 aFd, const struct stat& aBuf) { + if ((aBuf.st_mode & S_IFMT) == S_IFIFO) { + return true; + } + + if ((aBuf.st_mode & S_IFMT) != S_IFSOCK) { + return false; + } + + sockaddr_storage address; + socklen_t len = sizeof(address); + if (getsockname(aFd, (sockaddr*)&address, &len) != 0) { + return true; // Ignore the aFd 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 aFd, const void* aWbuf, size_t aCount) { + // Ignore writes of zero bytes, Firefox does some during shutdown. + if (aCount == 0) { + return false; + } + + { + struct stat buf; + int rv = fstat(aFd, &buf); + if (rv != 0) { + return true; + } + + if (IsIPCWrite(aFd, buf)) { + return false; + } + } + + // For writev we pass a nullptr aWbuf. We should only get here from + // dbm, and it uses write, so assert that we have aWbuf. + if (!aWbuf) { + 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. + auto wbuf2 = mozilla::MakeUniqueFallible<char[]>(aCount); + if (!wbuf2) { + return true; + } + off_t pos = lseek(aFd, 0, SEEK_CUR); + if (pos == -1) { + return true; + } + ssize_t r = read(aFd, wbuf2.get(), aCount); + if (r < 0 || (size_t)r != aCount) { + return true; + } + int cmp = memcmp(aWbuf, wbuf2.get(), aCount); + if (cmp != 0) { + return true; + } + off_t pos2 = lseek(aFd, 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* aAioCbp); +ssize_t wrap_aio_write(struct aiocb* aAioCbp); +FuncData aio_write_data = {0, (void*)wrap_aio_write, (void*)aio_write}; +ssize_t wrap_aio_write(struct aiocb* aAioCbp) { + MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, + aAioCbp->aio_fildes); + + aio_write_t old_write = (aio_write_t)aio_write_data.Buffer; + return old_write(aAioCbp); +} + +// Wrap pwrite-like functions. +// We have not seen them before, so just assert/report it. +typedef ssize_t (*pwrite_t)(int aFd, const void* buf, size_t aNumBytes, + off_t aOffset); +template <FuncData& foo> +ssize_t wrap_pwrite_temp(int aFd, const void* aBuf, size_t aNumBytes, + off_t aOffset) { + MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFd); + pwrite_t old_write = (pwrite_t)foo.Buffer; + return old_write(aFd, aBuf, aNumBytes, aOffset); +} + +// 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 aFd, const struct iovec* aIov, int aIovCount); +template <FuncData& foo> +ssize_t wrap_writev_temp(int aFd, const struct iovec* aIov, int aIovCount) { + MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFd, + nullptr, aIovCount); + writev_t old_write = (writev_t)foo.Buffer; + return old_write(aFd, aIov, aIovCount); +} + +// 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 aFd, const void* aBuf, size_t aCount); +template <FuncData& foo> +ssize_t wrap_write_temp(int aFd, const void* aBuf, size_t aCount) { + MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFd, aBuf, + aCount); + write_t old_write = (write_t)foo.Buffer; + return old_write(aFd, aBuf, aCount); +} + +// 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 = mozilla::ArrayLength(Functions); + +} // 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); + +#ifdef MOZ_REPLACE_MALLOC + // The contract with InitDebugFd is that the given registry can be used + // at any moment, so the instance needs to persist longer than the scope + // of this functions. + static DebugFdRegistry registry; + ReplaceMalloc::InitDebugFd(registry); +#endif + + 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; + } +#ifndef __aarch64__ + DebugOnly<mach_error_t> t = + mach_override_ptr(d->Function, d->Wrapper, &d->Buffer); + MOZ_ASSERT(t == err_none); +#endif + } +} + +void OnlyReportDirtyWrites() { sOnlyReportDirtyWrites = true; } + +// Never called! See bug 1647107. +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 diff --git a/xpcom/build/PoisonIOInterposerStub.cpp b/xpcom/build/PoisonIOInterposerStub.cpp new file mode 100644 index 0000000000..1f3daa3539 --- /dev/null +++ b/xpcom/build/PoisonIOInterposerStub.cpp @@ -0,0 +1,16 @@ +/* -*- 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 <stdio.h> + +extern "C" { + +void MozillaRegisterDebugFD(int aFd) {} +void MozillaRegisterDebugFILE(FILE* aFile) {} +void MozillaUnRegisterDebugFD(int aFd) {} +void MozillaUnRegisterDebugFILE(FILE* aFile) {} + +} // extern "C" diff --git a/xpcom/build/PoisonIOInterposerWin.cpp b/xpcom/build/PoisonIOInterposerWin.cpp new file mode 100644 index 0000000000..0d78e073ca --- /dev/null +++ b/xpcom/build/PoisonIOInterposerWin.cpp @@ -0,0 +1,508 @@ +/* -*- 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 "PoisonIOInterposer.h" + +#include <algorithm> +#include <stdio.h> +#include <vector> + +#include <io.h> +#include <windows.h> +#include <winternl.h> + +#include "mozilla/Assertions.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/FileUtilsWin.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/Mutex.h" +#include "mozilla/NativeNt.h" +#include "mozilla/SmallArrayLRUCache.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" +#include "nsTArray.h" +#include "nsWindowsDllInterceptor.h" + +#ifdef MOZ_REPLACE_MALLOC +# include "replace_malloc_bridge.h" +#endif + +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 create/open files. + * For documentation on the NtCreateFile routine, see MSDN. + */ +typedef NTSTATUS(NTAPI* NtCreateFileFn)( + PHANDLE aFileHandle, ACCESS_MASK aDesiredAccess, + POBJECT_ATTRIBUTES aObjectAttributes, PIO_STATUS_BLOCK aIoStatusBlock, + PLARGE_INTEGER aAllocationSize, ULONG aFileAttributes, ULONG aShareAccess, + ULONG aCreateDisposition, ULONG aCreateOptions, PVOID aEaBuffer, + ULONG aEaLength); + +/** + * Function pointer declaration for internal NT routine to read data from file. + * For documentation on the NtReadFile routine, see ZwReadFile on MSDN. + */ +typedef NTSTATUS(NTAPI* NtReadFileFn)(HANDLE aFileHandle, HANDLE aEvent, + PIO_APC_ROUTINE aApc, PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, PVOID aBuffer, + ULONG aLength, PLARGE_INTEGER aOffset, + PULONG aKey); + +/** + * Function pointer declaration for internal NT routine to read data from file. + * No documentation exists, see wine sources for details. + */ +typedef NTSTATUS(NTAPI* NtReadFileScatterFn)( + 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); + +/** + * Function pointer declaration for internal NT routine to write data to file. + * For documentation on the NtWriteFile routine, see ZwWriteFile on MSDN. + */ +typedef NTSTATUS(NTAPI* NtWriteFileFn)(HANDLE aFileHandle, HANDLE aEvent, + PIO_APC_ROUTINE aApc, PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, + PVOID aBuffer, ULONG aLength, + PLARGE_INTEGER aOffset, PULONG aKey); + +/** + * Function pointer declaration for internal NT routine to write data to file. + * No documentation exists, see wine sources for details. + */ +typedef NTSTATUS(NTAPI* NtWriteFileGatherFn)( + 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); + +/** + * Function pointer declaration for internal NT routine to flush to disk. + * For documentation on the NtFlushBuffersFile routine, see ZwFlushBuffersFile + * on MSDN. + */ +typedef NTSTATUS(NTAPI* NtFlushBuffersFileFn)(HANDLE aFileHandle, + PIO_STATUS_BLOCK aIoStatusBlock); + +typedef struct _FILE_NETWORK_OPEN_INFORMATION* PFILE_NETWORK_OPEN_INFORMATION; +/** + * Function pointer delaration for internal NT routine to query file attributes. + * (equivalent to stat) + */ +typedef NTSTATUS(NTAPI* NtQueryFullAttributesFileFn)( + POBJECT_ATTRIBUTES aObjectAttributes, + PFILE_NETWORK_OPEN_INFORMATION aFileInformation); + +/*************************** Auxiliary Declarations ***************************/ + +// Cache of filenames associated with handles. +// `static` to be shared between all calls to `Filename()`. +// This assumes handles are not reused, at least within a windows of 32 +// handles. +// Profiling showed that during startup, around half of `Filename()` calls are +// resolved with the first entry (best case), and 32 entries cover >95% of +// cases, reducing the average `Filename()` cost by 5-10x. +using HandleToFilenameCache = mozilla::SmallArrayLRUCache<HANDLE, nsString, 32>; +static mozilla::UniquePtr<HandleToFilenameCache> sHandleToFilenameCache; + +/** + * RAII class for timing the duration of an I/O call and reporting the result + * to the mozilla::IOInterposeObserver API. + */ +class WinIOAutoObservation : public mozilla::IOInterposeObserver::Observation { + public: + WinIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp, + HANDLE aFileHandle, const LARGE_INTEGER* aOffset) + : mozilla::IOInterposeObserver::Observation( + aOp, sReference, + !mozilla::IsDebugFile(reinterpret_cast<intptr_t>(aFileHandle))), + mFileHandle(aFileHandle), + mFileHandleType(GetFileType(aFileHandle)), + mHasQueriedFilename(false) { + if (mShouldReport) { + mOffset.QuadPart = aOffset ? aOffset->QuadPart : 0; + } + } + + WinIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp, + nsAString& aFilename) + : mozilla::IOInterposeObserver::Observation(aOp, sReference), + mFileHandle(nullptr), + mFileHandleType(FILE_TYPE_UNKNOWN), + mHasQueriedFilename(false) { + if (mShouldReport) { + nsAutoString dosPath; + if (mozilla::NtPathToDosPath(aFilename, dosPath)) { + mFilename = dosPath; + } else { + // If we can't get a dosPath, what we have is better than nothing. + mFilename = aFilename; + } + mHasQueriedFilename = true; + mOffset.QuadPart = 0; + } + } + + void SetHandle(HANDLE aFileHandle) { + mFileHandle = aFileHandle; + if (aFileHandle) { + // Note: `GetFileType()` is fast enough that we don't need to cache it. + mFileHandleType = GetFileType(aFileHandle); + + if (mHasQueriedFilename) { + // `mHasQueriedFilename` indicates we already have a filename, add it to + // the cache with the now-known handle. + sHandleToFilenameCache->Add(aFileHandle, mFilename); + } + } + } + + const char* FileType() const override; + + void Filename(nsAString& aFilename) override; + + ~WinIOAutoObservation() { Report(); } + + private: + HANDLE mFileHandle; + DWORD mFileHandleType; + LARGE_INTEGER mOffset; + bool mHasQueriedFilename; + nsString mFilename; + static const char* sReference; +}; + +const char* WinIOAutoObservation::sReference = "PoisonIOInterposer"; + +// Get filename for this observation +void WinIOAutoObservation::Filename(nsAString& aFilename) { + // If mHasQueriedFilename is true, then filename is already stored in + // mFilename + if (mHasQueriedFilename) { + aFilename = mFilename; + return; + } + + if (mFileHandle) { + mFilename = sHandleToFilenameCache->FetchOrAdd(mFileHandle, [&]() { + nsString filename; + if (!mozilla::HandleToFilename(mFileHandle, mOffset, filename)) { + // HandleToFilename could fail (return false) but still have added + // something to `filename`, so it should be cleared in this case. + filename.Truncate(); + } + return filename; + }); + } + mHasQueriedFilename = true; + + aFilename = mFilename; +} + +const char* WinIOAutoObservation::FileType() const { + if (mFileHandle) { + switch (mFileHandleType) { + case FILE_TYPE_CHAR: + return "Char"; + case FILE_TYPE_DISK: + return "File"; + case FILE_TYPE_PIPE: + return "Pipe"; + case FILE_TYPE_REMOTE: + return "Remote"; + case FILE_TYPE_UNKNOWN: + default: + break; + } + } + // Fallback to base class default implementation. + return mozilla::IOInterposeObserver::Observation::FileType(); +} + +/*************************** IO Interposing Methods ***************************/ + +// Function pointers to original functions +static mozilla::WindowsDllInterceptor::FuncHookType<NtCreateFileFn> + gOriginalNtCreateFile; +static mozilla::WindowsDllInterceptor::FuncHookType<NtReadFileFn> + gOriginalNtReadFile; +static mozilla::WindowsDllInterceptor::FuncHookType<NtReadFileScatterFn> + gOriginalNtReadFileScatter; +static mozilla::WindowsDllInterceptor::FuncHookType<NtWriteFileFn> + gOriginalNtWriteFile; +static mozilla::WindowsDllInterceptor::FuncHookType<NtWriteFileGatherFn> + gOriginalNtWriteFileGather; +static mozilla::WindowsDllInterceptor::FuncHookType<NtFlushBuffersFileFn> + gOriginalNtFlushBuffersFile; +static mozilla::WindowsDllInterceptor::FuncHookType<NtQueryFullAttributesFileFn> + gOriginalNtQueryFullAttributesFile; + +static NTSTATUS NTAPI InterposedNtCreateFile( + PHANDLE aFileHandle, ACCESS_MASK aDesiredAccess, + POBJECT_ATTRIBUTES aObjectAttributes, PIO_STATUS_BLOCK aIoStatusBlock, + PLARGE_INTEGER aAllocationSize, ULONG aFileAttributes, ULONG aShareAccess, + ULONG aCreateDisposition, ULONG aCreateOptions, PVOID aEaBuffer, + ULONG aEaLength) { + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtCreateFile); + + if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) { + return gOriginalNtCreateFile( + aFileHandle, aDesiredAccess, aObjectAttributes, aIoStatusBlock, + aAllocationSize, aFileAttributes, aShareAccess, aCreateDisposition, + aCreateOptions, aEaBuffer, aEaLength); + } + + // Report IO + const wchar_t* buf = + aObjectAttributes ? aObjectAttributes->ObjectName->Buffer : L""; + uint32_t len = aObjectAttributes + ? aObjectAttributes->ObjectName->Length / sizeof(WCHAR) + : 0; + nsDependentSubstring filename(buf, len); + WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpCreateOrOpen, + filename); + + // Execute original function + NTSTATUS status = gOriginalNtCreateFile( + aFileHandle, aDesiredAccess, aObjectAttributes, aIoStatusBlock, + aAllocationSize, aFileAttributes, aShareAccess, aCreateDisposition, + aCreateOptions, aEaBuffer, aEaLength); + if (NT_SUCCESS(status) && aFileHandle) { + timer.SetHandle(*aFileHandle); + } + return status; +} + +static NTSTATUS NTAPI InterposedNtReadFile(HANDLE aFileHandle, HANDLE aEvent, + PIO_APC_ROUTINE aApc, PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, + PVOID aBuffer, ULONG aLength, + PLARGE_INTEGER aOffset, + PULONG aKey) { + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtReadFile); + + if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) { + return gOriginalNtReadFile(aFileHandle, aEvent, aApc, aApcCtx, aIoStatus, + aBuffer, aLength, aOffset, aKey); + } + + // Report IO + WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpRead, aFileHandle, + aOffset); + + // Execute original function + return gOriginalNtReadFile(aFileHandle, aEvent, aApc, aApcCtx, aIoStatus, + aBuffer, aLength, aOffset, aKey); +} + +static NTSTATUS NTAPI InterposedNtReadFileScatter( + 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) { + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtReadFileScatter); + + if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) { + return gOriginalNtReadFileScatter(aFileHandle, aEvent, aApc, aApcCtx, + aIoStatus, aSegments, aLength, aOffset, + aKey); + } + + // Report IO + WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpRead, aFileHandle, + aOffset); + + // Execute original function + return gOriginalNtReadFileScatter(aFileHandle, aEvent, aApc, aApcCtx, + aIoStatus, aSegments, aLength, aOffset, + aKey); +} + +// Interposed NtWriteFile function +static NTSTATUS NTAPI InterposedNtWriteFile(HANDLE aFileHandle, HANDLE aEvent, + PIO_APC_ROUTINE aApc, PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, + PVOID aBuffer, ULONG aLength, + PLARGE_INTEGER aOffset, + PULONG aKey) { + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtWriteFile); + + if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) { + return gOriginalNtWriteFile(aFileHandle, aEvent, aApc, aApcCtx, aIoStatus, + aBuffer, aLength, aOffset, aKey); + } + + // Report IO + WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFileHandle, + aOffset); + + // Execute original function + return gOriginalNtWriteFile(aFileHandle, aEvent, aApc, aApcCtx, aIoStatus, + aBuffer, aLength, aOffset, aKey); +} + +// Interposed NtWriteFileGather function +static NTSTATUS NTAPI 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) { + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtWriteFileGather); + + if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) { + return gOriginalNtWriteFileGather(aFileHandle, aEvent, aApc, aApcCtx, + aIoStatus, aSegments, aLength, aOffset, + aKey); + } + + // Report IO + WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFileHandle, + aOffset); + + // Execute original function + return gOriginalNtWriteFileGather(aFileHandle, aEvent, aApc, aApcCtx, + aIoStatus, aSegments, aLength, aOffset, + aKey); +} + +static NTSTATUS NTAPI InterposedNtFlushBuffersFile( + HANDLE aFileHandle, PIO_STATUS_BLOCK aIoStatusBlock) { + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtFlushBuffersFile); + + if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) { + return gOriginalNtFlushBuffersFile(aFileHandle, aIoStatusBlock); + } + + // Report IO + WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpFSync, aFileHandle, + nullptr); + + // Execute original function + return gOriginalNtFlushBuffersFile(aFileHandle, aIoStatusBlock); +} + +static NTSTATUS NTAPI InterposedNtQueryFullAttributesFile( + POBJECT_ATTRIBUTES aObjectAttributes, + PFILE_NETWORK_OPEN_INFORMATION aFileInformation) { + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtQueryFullAttributesFile); + + if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) { + return gOriginalNtQueryFullAttributesFile(aObjectAttributes, + aFileInformation); + } + + // Report IO + const wchar_t* buf = + aObjectAttributes ? aObjectAttributes->ObjectName->Buffer : L""; + uint32_t len = aObjectAttributes + ? aObjectAttributes->ObjectName->Length / sizeof(WCHAR) + : 0; + nsDependentSubstring filename(buf, len); + WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpStat, filename); + + // Execute original function + return gOriginalNtQueryFullAttributesFile(aObjectAttributes, + aFileInformation); +} + +} // namespace + +/******************************** IO Poisoning ********************************/ + +// Windows DLL interceptor +static mozilla::WindowsDllInterceptor sNtDllInterceptor; + +namespace mozilla { + +void InitPoisonIOInterposer() { + // Currently we hook the functions not early enough to precede third-party + // injections. Until we implement a compatible way e.g. applying a hook + // in the parent process (bug 1646804), we skip interposing functions under + // the known condition(s). + + // Bug 1679741: Kingsoft Internet Security calls NtReadFile in their thread + // simultaneously when we're applying a hook on NtReadFile. + if (::GetModuleHandleW(L"kwsui64.dll")) { + return; + } + + // 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; + + MOZ_RELEASE_ASSERT(!sHandleToFilenameCache); + sHandleToFilenameCache = mozilla::MakeUnique<HandleToFilenameCache>(); + mozilla::RunOnShutdown([]() { + // The interposer may still be active after the final shutdown phase + // (especially since ClearPoisonIOInterposer() is never called, see bug + // 1647107), so we cannot just reset the pointer. Instead we put the cache + // in shutdown mode, to clear its memory and stop caching operations. + sHandleToFilenameCache->Shutdown(); + }); + + // Stdout and Stderr are OK. + MozillaRegisterDebugFD(1); + if (::GetStdHandle(STD_OUTPUT_HANDLE) != ::GetStdHandle(STD_ERROR_HANDLE)) { + MozillaRegisterDebugFD(2); + } + +#ifdef MOZ_REPLACE_MALLOC + // The contract with InitDebugFd is that the given registry can be used + // at any moment, so the instance needs to persist longer than the scope + // of this functions. + static DebugFdRegistry registry; + ReplaceMalloc::InitDebugFd(registry); +#endif + + // Initialize dll interceptor and add hooks + sNtDllInterceptor.Init("ntdll.dll"); + gOriginalNtCreateFile.Set(sNtDllInterceptor, "NtCreateFile", + &InterposedNtCreateFile); + gOriginalNtReadFile.Set(sNtDllInterceptor, "NtReadFile", + &InterposedNtReadFile); + gOriginalNtReadFileScatter.Set(sNtDllInterceptor, "NtReadFileScatter", + &InterposedNtReadFileScatter); + gOriginalNtWriteFile.Set(sNtDllInterceptor, "NtWriteFile", + &InterposedNtWriteFile); + gOriginalNtWriteFileGather.Set(sNtDllInterceptor, "NtWriteFileGather", + &InterposedNtWriteFileGather); + gOriginalNtFlushBuffersFile.Set(sNtDllInterceptor, "NtFlushBuffersFile", + &InterposedNtFlushBuffersFile); + gOriginalNtQueryFullAttributesFile.Set(sNtDllInterceptor, + "NtQueryFullAttributesFile", + &InterposedNtQueryFullAttributesFile); +} + +void ClearPoisonIOInterposer() { + MOZ_ASSERT(false, "Never called! See bug 1647107"); + if (sIOPoisoned) { + // Destroy the DLL interceptor + sIOPoisoned = false; + sNtDllInterceptor.Clear(); + sHandleToFilenameCache->Clear(); + } +} + +} // namespace mozilla diff --git a/xpcom/build/Services.py b/xpcom/build/Services.py new file mode 100644 index 0000000000..4c6c14807f --- /dev/null +++ b/xpcom/build/Services.py @@ -0,0 +1,170 @@ +# 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/. + +# NOTE: Although this generates headers and code for C++, using Services.h +# is deprecated in favour of Components.h. + + +services = [] + + +def service(name, iface, contractid): + """Define a convenient service getter""" + services.append((name, iface, contractid)) + + +# The `name` parameter is derived from the `iface` by removing the `nsI` +# prefix. (This often matches the `contractid`, but not always.) +service("ChromeRegistry", "nsIChromeRegistry", "@mozilla.org/chrome/chrome-registry;1") +service("IOService", "nsIIOService", "@mozilla.org/network/io-service;1") +service("ObserverService", "nsIObserverService", "@mozilla.org/observer-service;1") +service("PermissionManager", "nsIPermissionManager", "@mozilla.org/permissionmanager;1") +service( + "AsyncShutdownService", + "nsIAsyncShutdownService", + "@mozilla.org/async-shutdown-service;1", +) + +# The definition file needs access to the definitions of the particular +# interfaces. If you add a new interface here, make sure the necessary includes +# are also listed in the following code snippet. +CPP_INCLUDES = """ +#include "mozilla/Likely.h" +#include "mozilla/Services.h" +#include "mozIThirdPartyUtil.h" +#include "nsComponentManager.h" +#include "nsIObserverService.h" +#include "nsNetCID.h" +#include "nsObserverService.h" +#include "nsXPCOMPrivate.h" +#include "nsIIOService.h" +#include "nsIDirectoryService.h" +#include "nsIChromeRegistry.h" +#include "nsIStringBundle.h" +#include "nsIToolkitChromeRegistry.h" +#include "IHistory.h" +#include "nsIXPConnect.h" +#include "nsIPermissionManager.h" +#include "nsIPrefService.h" +#include "nsIServiceWorkerManager.h" +#include "nsICacheStorageService.h" +#include "nsIStreamTransportService.h" +#include "nsISocketTransportService.h" +#include "nsIURIClassifier.h" +#include "nsIHttpActivityObserver.h" +#include "nsIAsyncShutdown.h" +#include "nsIUUIDGenerator.h" +#include "nsIGfxInfo.h" +#include "nsIURIFixup.h" +#include "nsIBits.h" +#include "nsIXULRuntime.h" +""" + + +##### +# Codegen Logic +# +# The following code consumes the data listed above to generate the files +# Services.h, and Services.cpp which provide access to these service getters in +# C++ code. + + +def services_h(output): + output.write( + """\ +/* THIS FILE IS GENERATED BY Services.py - DO NOT EDIT */ + +#ifndef mozilla_Services_h +#define mozilla_Services_h + +#include "nscore.h" +#include "nsCOMPtr.h" +""" + ) + + for (name, iface, contractid) in services: + # Write out a forward declaration for the type in question + segs = iface.split("::") + for namespace in segs[:-1]: + output.write("namespace %s {\n" % namespace) + output.write("class %s;\n" % segs[-1]) + for namespace in reversed(segs[:-1]): + output.write("} // namespace %s\n" % namespace) + + # Write out the C-style function signature, and the C++ wrapper + output.write( + """ +#ifdef MOZILLA_INTERNAL_API +namespace mozilla { +namespace services { +/** + * Fetch a cached instance of the %(name)s. + * This function will return nullptr during XPCOM shutdown. + * Prefer using static components to this method. + * WARNING: This method is _not_ threadsafe! + */ +already_AddRefed<%(type)s> Get%(name)s(); +} // namespace services +} // namespace mozilla +#endif // defined(MOZILLA_INTERNAL_API) +""" + % { + "name": name, + "type": iface, + } + ) + + output.write("#endif // !defined(mozilla_Services_h)\n") + + +def services_cpp(output): + output.write( + """\ +/* THIS FILE IS GENERATED BY Services.py - DO NOT EDIT */ +""" + ) + output.write(CPP_INCLUDES) + + for (name, iface, contractid) in services: + output.write( + """ +static %(type)s* g%(name)s = nullptr; + +namespace mozilla { +namespace services { +already_AddRefed<%(type)s> Get%(name)s() +{ + if (MOZ_UNLIKELY(gXPCOMShuttingDown)) { + return nullptr; + } + if (!g%(name)s) { + nsCOMPtr<%(type)s> os = do_GetService("%(contractid)s"); + os.swap(g%(name)s); + } + return do_AddRef(g%(name)s); +} +} +} +""" + % { + "name": name, + "type": iface, + "contractid": contractid, + } + ) + + output.write( + """ +/** + * Clears service cache, sets gXPCOMShuttingDown + */ +void +mozilla::services::Shutdown() +{ + gXPCOMShuttingDown = true; +""" + ) + for (name, iface, contractid) in services: + output.write(" NS_IF_RELEASE(g%s);\n" % name) + output.write("}\n") diff --git a/xpcom/build/SmallArrayLRUCache.h b/xpcom/build/SmallArrayLRUCache.h new file mode 100644 index 0000000000..e7ac727652 --- /dev/null +++ b/xpcom/build/SmallArrayLRUCache.h @@ -0,0 +1,199 @@ +/* -*- 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 SmallArrayLRUCache_h +#define SmallArrayLRUCache_h + +#include "mozilla/Mutex.h" + +#include <algorithm> +#include <type_traits> +#include <utility> + +// Uncomment the next line to get shutdown stats about cache usage. +// #define SMALLARRAYLRUCACHE_STATS + +#ifdef SMALLARRAYLRUCACHE_STATS +# include <cstdio> +#endif + +namespace mozilla { + +// Array-based LRU cache, thread-safe. +// Optimized for cases where `FetchOrAdd` is used with the same key most +// recently, and assuming the cost of running the value-builder function is much +// more expensive than going through the whole list. +// Note: No time limits on keeping items. +// TODO: Move to more public place, if this could be used elsewhere; make sure +// the cost/benefits are highlighted. +template <typename Key, typename Value, unsigned LRUCapacity> +class SmallArrayLRUCache { + public: + static_assert(std::is_default_constructible_v<Key>); + static_assert(std::is_trivially_constructible_v<Key>); + static_assert(std::is_trivially_copyable_v<Key>); + static_assert(std::is_default_constructible_v<Value>); + static_assert(LRUCapacity >= 2); + static_assert(LRUCapacity <= 1024, + "This seems a bit big, is this the right cache for your use?"); + + // Reset all stored values to their default, and set cache size to zero. + void Clear() { + mozilla::OffTheBooksMutexAutoLock lock(mMutex); + if (mSize == ShutdownSize) { + return; + } + Clear(lock); + } + + // Clear the cache, and then prevent further uses (other functions will do + // nothing). + void Shutdown() { + mozilla::OffTheBooksMutexAutoLock lock(mMutex); + if (mSize == ShutdownSize) { + return; + } + Clear(lock); + mSize = ShutdownSize; + } + + // Add a key-value. + template <typename ToValue> + void Add(Key aKey, ToValue&& aValue) { + mozilla::OffTheBooksMutexAutoLock lock(mMutex); + + if (mSize == ShutdownSize) { + return; + } + + // Quick add to the front, don't remove possible duplicate handles later in + // the list, they will eventually drop off the end. + KeyAndValue* const item0 = &mLRUArray[0]; + mSize = std::min(mSize + 1, LRUCapacity); + if (MOZ_LIKELY(mSize != 1)) { + // List is not empty. + // Make a hole at the start. + std::move_backward(item0, item0 + mSize - 1, item0 + mSize); + } + item0->mKey = aKey; + item0->mValue = std::forward<ToValue>(aValue); + return; + } + + // Look for the value associated with `aKey` in the cache. + // If not found, run `aValueFunction()`, add it in the cache before returning. + // After shutdown, just run `aValueFunction()`. + template <typename ValueFunction> + Value FetchOrAdd(Key aKey, ValueFunction&& aValueFunction) { + Value value; + mozilla::OffTheBooksMutexAutoLock lock(mMutex); + + if (mSize == ShutdownSize) { + value = std::forward<ValueFunction>(aValueFunction)(); + return value; + } + + KeyAndValue* const item0 = &mLRUArray[0]; + if (MOZ_UNLIKELY(mSize == 0)) { + // List is empty. + value = std::forward<ValueFunction>(aValueFunction)(); + item0->mKey = aKey; + item0->mValue = value; + mSize = 1; + return value; + } + + if (MOZ_LIKELY(item0->mKey == aKey)) { + // This is already at the beginning of the list, we're done. +#ifdef SMALLARRAYLRUCACHE_STATS + ++mCacheFoundAt[0]; +#endif // SMALLARRAYLRUCACHE_STATS + value = item0->mValue; + return value; + } + + for (KeyAndValue* item = item0 + 1; item != item0 + mSize; ++item) { + if (item->mKey == aKey) { + // Found handle in the middle. +#ifdef SMALLARRAYLRUCACHE_STATS + ++mCacheFoundAt[unsigned(item - item0)]; +#endif // SMALLARRAYLRUCACHE_STATS + value = item->mValue; + // Move this item to the start of the list. + std::rotate(item0, item, item + 1); + return value; + } + } + + // Handle was not in the list. +#ifdef SMALLARRAYLRUCACHE_STATS + ++mCacheFoundAt[LRUCapacity]; +#endif // SMALLARRAYLRUCACHE_STATS + { + // Don't lock while doing the potentially-expensive ValueFunction(). + // This means that the list could change when we lock again, but + // it's okay because we'll want to add the new entry at the beginning + // anyway, whatever else is in the list then. + // In the worst case, it could be the same handle as another `FetchOrAdd` + // in parallel, it just means it will be duplicated, so it's a little bit + // less efficient (using the extra space), but not wrong (the next + // `FetchOrAdd` will find the first one). + mozilla::OffTheBooksMutexAutoUnlock unlock(mMutex); + value = std::forward<ValueFunction>(aValueFunction)(); + } + // Make a hole at the start, and put the value there. + mSize = std::min(mSize + 1, LRUCapacity); + std::move_backward(item0, item0 + mSize - 1, item0 + mSize); + item0->mKey = aKey; + item0->mValue = value; + return value; + } + +#ifdef SMALLARRAYLRUCACHE_STATS + ~SmallArrayLRUCache() { + if (mSize != 0 && mSize != ShutdownSize) { + fprintf(stderr, + "***** SmallArrayLRUCache stats: (position -> hit count)\n"); + for (unsigned i = 0; i < mSize; ++i) { + fprintf(stderr, "***** %3u -> %6u\n", i, mCacheFoundAt[i]); + } + fprintf(stderr, "***** not found -> %6u\n", mCacheFoundAt[LRUCapacity]); + } + } +#endif // SMALLARRAYLRUCACHE_STATS + + private: + void Clear(const mozilla::OffTheBooksMutexAutoLock&) MOZ_REQUIRES(mMutex) { + for (KeyAndValue* item = &mLRUArray[0]; item != &mLRUArray[mSize]; ++item) { + item->mValue = Value{}; + } + mSize = 0; + } + + struct KeyAndValue { + Key mKey; + Value mValue; + + KeyAndValue() = default; + KeyAndValue(KeyAndValue&&) = default; + KeyAndValue& operator=(KeyAndValue&&) = default; + }; + + // Special size value that indicates the cache is in shutdown mode. + constexpr static unsigned ShutdownSize = unsigned(-1); + + mozilla::OffTheBooksMutex mMutex{"LRU cache"}; + unsigned mSize MOZ_GUARDED_BY(mMutex) = 0; + KeyAndValue mLRUArray[LRUCapacity] MOZ_GUARDED_BY(mMutex); +#ifdef SMALLARRAYLRUCACHE_STATS + // Hit count for each position in the case. +1 for counting not-found cases. + unsigned mCacheFoundAt[LRUCapacity + 1] MOZ_GUARDED_BY(mMutex) = {0u}; +#endif // SMALLARRAYLRUCACHE_STATS +}; + +} // namespace mozilla + +#endif // SmallArrayLRUCache_h diff --git a/xpcom/build/XPCOM.h b/xpcom/build/XPCOM.h new file mode 100644 index 0000000000..1a04091dc4 --- /dev/null +++ b/xpcom/build/XPCOM.h @@ -0,0 +1,167 @@ +/* -*- 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_XPCOM_h +#define mozilla_XPCOM_h + +// NOTE: the following headers are sorted topologically, not alphabetically. +// Do not reorder them without review from bsmedberg. + +// system headers required by XPCOM headers + +#include <string.h> + +#ifndef MOZILLA_INTERNAL_API +# error "MOZILLA_INTERNAL_API must be defined" +#endif + +// core headers required by pretty much everything else + +#include "nscore.h" + +#include "nsXPCOMCID.h" +#include "nsXPCOM.h" + +#include "nsError.h" +#include "nsDebug.h" + +#include "nsID.h" + +#include "nsISupports.h" + +#include "nsTArray.h" + +#include "nsCOMPtr.h" +#include "nsCOMArray.h" + +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsNativeCharsetUtils.h" + +#include "nsISupportsUtils.h" +#include "nsISupportsImpl.h" + +// core data structures + +#include "nsTHashtable.h" +#include "nsHashKeys.h" +#include "nsBaseHashtable.h" +#include "nsTHashMap.h" +#include "nsInterfaceHashtable.h" +#include "nsClassHashtable.h" +#include "nsRefPtrHashtable.h" + +// interfaces that inherit directly from nsISupports + +#include "nsIArray.h" +#include "nsAtom.h" +#include "nsICategoryManager.h" +#include "nsIClassInfo.h" +#include "nsIComponentManager.h" +#include "nsIConsoleListener.h" +#include "nsIConsoleMessage.h" +#include "nsIConsoleService.h" +#include "nsIDebug2.h" +#include "nsIDirectoryEnumerator.h" +#include "nsIEnvironment.h" +#include "nsIEventTarget.h" +#include "nsIException.h" +#include "nsIFactory.h" +#include "nsIFile.h" +#include "nsIINIParser.h" +#include "nsIInputStream.h" +#include "nsIInterfaceRequestor.h" +#include "nsILineInputStream.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIOutputStream.h" +#include "nsIProcess.h" +#include "nsIProperties.h" +#include "nsIRunnable.h" +#include "nsISeekableStream.h" +#include "nsISerializable.h" +#include "nsIServiceManager.h" +#include "nsIScriptableInputStream.h" +#include "nsISimpleEnumerator.h" +#include "nsIStreamBufferAccess.h" +#include "nsIStringEnumerator.h" +#include "nsIStorageStream.h" +#include "nsISupportsIterators.h" +#include "nsISupportsPrimitives.h" +#include "nsISupportsPriority.h" +#include "nsIThreadManager.h" +#include "nsITimer.h" +#include "nsIUUIDGenerator.h" +#include "nsIUnicharInputStream.h" +#include "nsIUnicharOutputStream.h" +#include "nsIUnicharLineInputStream.h" +#include "nsIVariant.h" +#include "nsIVersionComparator.h" +#include "nsIWritablePropertyBag2.h" + +// interfaces that include something above + +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIBinaryInputStream.h" +#include "nsIBinaryOutputStream.h" +#include "nsIConverterInputStream.h" +#include "nsIConverterOutputStream.h" +#include "nsIInputStreamTee.h" +#include "nsIMultiplexInputStream.h" +#include "nsIMutableArray.h" +#include "nsIPersistentProperties2.h" +#include "nsIStringStream.h" +#include "nsIThread.h" +#include "nsIThreadPool.h" + +// interfaces that include something above + +#include "nsILocalFileWin.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsIPipe.h" + +#ifdef MOZ_WIDGET_COCOA +# include "nsILocalFileMac.h" +#endif + +// xpcom/glue utility headers + +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" + +#include "nsWeakReference.h" + +#include "nsArrayEnumerator.h" +#include "nsArrayUtils.h" +#include "nsCRTGlue.h" +#include "nsCycleCollectionParticipant.h" +#include "nsDeque.h" +#include "nsEnumeratorUtils.h" +#include "nsIClassInfoImpl.h" +#include "mozilla/ModuleUtils.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsINIParser.h" +#include "nsProxyRelease.h" +#include "nsTObserverArray.h" +#include "nsTextFormatter.h" +#include "nsThreadUtils.h" +#include "nsVersionComparator.h" +#include "nsXPTCUtils.h" + +// xpcom/base utility headers + +#include "nsAutoRef.h" +#include "nsInterfaceRequestorAgg.h" + +// xpcom/io utility headers + +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" + +#endif // mozilla_XPCOM_h diff --git a/xpcom/build/XPCOMInit.cpp b/xpcom/build/XPCOMInit.cpp new file mode 100644 index 0000000000..cd6c7f7052 --- /dev/null +++ b/xpcom/build/XPCOMInit.cpp @@ -0,0 +1,790 @@ +/* -*- 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 "ThreadEventTarget.h" +#include "XPCOMModule.h" + +#include "base/basictypes.h" + +#include "mozilla/AbstractThread.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Poison.h" +#include "mozilla/SharedThreadPool.h" +#include "mozilla/TaskController.h" +#include "mozilla/Unused.h" +#include "mozilla/XPCOM.h" +#include "mozJSModuleLoader.h" +#include "nsXULAppAPI.h" + +#ifndef ANDROID +# include "nsTerminator.h" +#endif + +#include "nsXPCOMPrivate.h" +#include "nsXPCOMCIDInternal.h" + +#include "mozilla/dom/JSExecutionManager.h" +#include "mozilla/layers/ImageBridgeChild.h" +#include "mozilla/layers/CompositorBridgeParent.h" + +#include "prlink.h" + +#include "nsCycleCollector.h" +#include "nsObserverService.h" + +#include "nsDebugImpl.h" +#include "nsSystemInfo.h" + +#include "nsComponentManager.h" +#include "nsCategoryManagerUtils.h" +#include "nsIServiceManager.h" + +#include "nsThreadManager.h" +#include "nsThreadPool.h" + +#include "nsTimerImpl.h" +#include "TimerThread.h" + +#include "nsThread.h" +#include "nsVersionComparatorImpl.h" + +#include "nsIFile.h" +#include "nsLocalFile.h" +#include "nsDirectoryService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsCategoryManager.h" +#include "nsMultiplexInputStream.h" + +#include "nsAtomTable.h" +#include "nsISupportsImpl.h" + +#include "nsSystemInfo.h" +#include "nsMemoryReporterManager.h" +#include "nsMessageLoop.h" +#include "nss.h" +#include "nsNSSComponent.h" + +#include <locale.h> +#include "mozilla/Services.h" +#include "mozilla/Omnijar.h" +#include "mozilla/ScriptPreloader.h" +#include "mozilla/Telemetry.h" +#include "mozilla/BackgroundHangMonitor.h" + +#include "mozilla/PoisonIOInterposer.h" +#include "mozilla/LateWriteChecks.h" + +#include "mozilla/scache/StartupCache.h" + +#include "base/at_exit.h" +#include "base/command_line.h" +#include "base/message_loop.h" + +#include "mozilla/ipc/BrowserProcessSubThread.h" +#include "mozilla/AvailableMemoryTracker.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/CountingAllocatorBase.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/ServoStyleConsts.h" + +#include "mozilla/ipc/GeckoChildProcessHost.h" + +#include "ogg/ogg.h" + +#include "GeckoProfiler.h" +#include "ProfilerControl.h" + +#include "jsapi.h" +#include "js/Initialization.h" +#include "mozilla/StaticPrefs_javascript.h" +#include "XPCSelfHostedShmem.h" + +#include "gfxPlatform.h" + +using base::AtExitManager; +using mozilla::ipc::BrowserProcessSubThread; + +// From toolkit/library/rust/lib.rs +extern "C" void GkRust_Init(); +extern "C" void GkRust_Shutdown(); + +namespace { + +static AtExitManager* sExitManager; +static MessageLoop* sMessageLoop; +static bool sCommandLineWasInitialized; +static BrowserProcessSubThread* sIOThread; +static mozilla::BackgroundHangMonitor* sMainHangMonitor; + +} /* anonymous namespace */ + +// Registry Factory creation function defined in nsRegistry.cpp +// We hook into this function locally to create and register the registry +// Since noone outside xpcom needs to know about this and nsRegistry.cpp +// does not have a local include file, we are putting this definition +// here rather than in nsIRegistry.h +extern nsresult NS_RegistryGetFactory(nsIFactory** aFactory); +extern nsresult NS_CategoryManagerGetFactory(nsIFactory**); + +#ifdef XP_WIN +extern nsresult CreateAnonTempFileRemover(); +#endif + +nsresult nsThreadManagerGetSingleton(const nsIID& aIID, void** aInstancePtr) { + NS_ASSERTION(aInstancePtr, "null outptr"); + return nsThreadManager::get().QueryInterface(aIID, aInstancePtr); +} + +nsresult nsLocalFileConstructor(const nsIID& aIID, void** aInstancePtr) { + return nsLocalFile::nsLocalFileConstructor(aIID, aInstancePtr); +} + +nsComponentManagerImpl* nsComponentManagerImpl::gComponentManager = nullptr; +bool gXPCOMShuttingDown = false; +bool gXPCOMMainThreadEventsAreDoomed = false; +char16_t* gGREBinPath = nullptr; + +// gDebug will be freed during shutdown. +static nsIDebug2* gDebug = nullptr; + +EXPORT_XPCOM_API(nsresult) +NS_GetDebug(nsIDebug2** aResult) { + return nsDebugImpl::Create(NS_GET_IID(nsIDebug2), (void**)aResult); +} + +class ICUReporter final : public nsIMemoryReporter, + public mozilla::CountingAllocatorBase<ICUReporter> { + public: + NS_DECL_ISUPPORTS + + static void* Alloc(const void*, size_t aSize) { + return CountingMalloc(aSize); + } + + static void* Realloc(const void*, void* aPtr, size_t aSize) { + return CountingRealloc(aPtr, aSize); + } + + static void Free(const void*, void* aPtr) { return CountingFree(aPtr); } + + private: + NS_IMETHOD + CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) override { + MOZ_COLLECT_REPORT( + "explicit/icu", KIND_HEAP, UNITS_BYTES, MemoryAllocated(), + "Memory used by ICU, a Unicode and globalization support library."); + + return NS_OK; + } + + ~ICUReporter() = default; +}; + +NS_IMPL_ISUPPORTS(ICUReporter, nsIMemoryReporter) + +class OggReporter final : public nsIMemoryReporter, + public mozilla::CountingAllocatorBase<OggReporter> { + public: + NS_DECL_ISUPPORTS + + private: + NS_IMETHOD + CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) override { + MOZ_COLLECT_REPORT( + "explicit/media/libogg", KIND_HEAP, UNITS_BYTES, MemoryAllocated(), + "Memory allocated through libogg for Ogg, Theora, and related media " + "files."); + + return NS_OK; + } + + ~OggReporter() = default; +}; + +NS_IMPL_ISUPPORTS(OggReporter, nsIMemoryReporter) + +static bool sInitializedJS = false; + +static void InitializeJS() { +#if defined(ENABLE_WASM_SIMD) && \ + (defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)) + // Update static engine preferences, such as AVX, before + // `JS_InitWithFailureDiagnostic` is called. + JS::SetAVXEnabled(mozilla::StaticPrefs::javascript_options_wasm_simd_avx()); +#endif + + const char* jsInitFailureReason = JS_InitWithFailureDiagnostic(); + if (jsInitFailureReason) { + MOZ_CRASH_UNSAFE(jsInitFailureReason); + } +} + +// Note that on OSX, aBinDirectory will point to .app/Contents/Resources/browser +EXPORT_XPCOM_API(nsresult) +NS_InitXPCOM(nsIServiceManager** aResult, nsIFile* aBinDirectory, + nsIDirectoryServiceProvider* aAppFileLocationProvider, + bool aInitJSContext) { + static bool sInitialized = false; + if (sInitialized) { + return NS_ERROR_FAILURE; + } + + sInitialized = true; + + NS_LogInit(); + + NS_InitAtomTable(); + + // We don't have the arguments by hand here. If logging has already been + // initialized by a previous call to LogModule::Init with the arguments + // passed, passing (0, nullptr) is alright here. + mozilla::LogModule::Init(0, nullptr); + + GkRust_Init(); + + nsresult rv = NS_OK; + + // We are not shutting down + gXPCOMShuttingDown = false; + +#ifdef XP_UNIX + // Discover the current value of the umask, and save it where + // nsSystemInfo::Init can retrieve it when necessary. There is no way + // to read the umask without changing it, and the setting is process- + // global, so this must be done while we are still single-threaded; the + // nsSystemInfo object is typically created much later, when some piece + // of chrome JS wants it. The system call is specified as unable to fail. + nsSystemInfo::gUserUmask = ::umask(0777); + ::umask(nsSystemInfo::gUserUmask); +#endif + + // Set up chromium libs + NS_ASSERTION(!sExitManager && !sMessageLoop, "Bad logic!"); + + if (!AtExitManager::AlreadyRegistered()) { + sExitManager = new AtExitManager(); + } + + MessageLoop* messageLoop = MessageLoop::current(); + if (!messageLoop) { + sMessageLoop = new MessageLoopForUI(MessageLoop::TYPE_MOZILLA_PARENT); + sMessageLoop->set_thread_name("Gecko"); + // Set experimental values for main thread hangs: + // 128ms for transient hangs and 8192ms for permanent hangs + sMessageLoop->set_hang_timeouts(128, 8192); + } else if (messageLoop->type() == MessageLoop::TYPE_MOZILLA_CHILD) { + messageLoop->set_thread_name("Gecko_Child"); + messageLoop->set_hang_timeouts(128, 8192); + } + + if (XRE_IsParentProcess() && + !BrowserProcessSubThread::GetMessageLoop(BrowserProcessSubThread::IO)) { + mozilla::UniquePtr<BrowserProcessSubThread> ioThread = + mozilla::MakeUnique<BrowserProcessSubThread>( + BrowserProcessSubThread::IO); + + base::Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_IO; + if (NS_WARN_IF(!ioThread->StartWithOptions(options))) { + return NS_ERROR_FAILURE; + } + + sIOThread = ioThread.release(); + } + + // Establish the main thread here. + rv = nsThreadManager::get().Init(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + AUTO_PROFILER_INIT2; + + // Set up the timer globals/timer thread + rv = nsTimerImpl::Startup(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + +#ifndef ANDROID + // If the locale hasn't already been setup by our embedder, + // get us out of the "C" locale and into the system + if (strcmp(setlocale(LC_ALL, nullptr), "C") == 0) { + setlocale(LC_ALL, ""); + } +#endif + + nsDirectoryService::RealInit(); + + bool value; + + if (aBinDirectory) { + rv = aBinDirectory->IsDirectory(&value); + + if (NS_SUCCEEDED(rv) && value) { + nsDirectoryService::gService->SetCurrentProcessDirectory(aBinDirectory); + } + } + + if (aAppFileLocationProvider) { + rv = nsDirectoryService::gService->RegisterProvider( + aAppFileLocationProvider); + if (NS_FAILED(rv)) { + return rv; + } + } + + nsCOMPtr<nsIFile> xpcomLib; + nsDirectoryService::gService->Get(NS_GRE_BIN_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(xpcomLib)); + MOZ_ASSERT(xpcomLib); + + // set gGREBinPath + nsAutoString path; + xpcomLib->GetPath(path); + gGREBinPath = ToNewUnicode(path); + + xpcomLib->AppendNative(nsDependentCString(XPCOM_DLL)); + nsDirectoryService::gService->Set(NS_XPCOM_LIBRARY_FILE, xpcomLib); + + if (!mozilla::Omnijar::IsInitialized()) { + mozilla::Omnijar::Init(); + } + + if ((sCommandLineWasInitialized = !CommandLine::IsInitialized())) { +#ifdef OS_WIN + CommandLine::Init(0, nullptr); +#else + nsCOMPtr<nsIFile> binaryFile; + nsDirectoryService::gService->Get(NS_XPCOM_CURRENT_PROCESS_DIR, + NS_GET_IID(nsIFile), + getter_AddRefs(binaryFile)); + if (NS_WARN_IF(!binaryFile)) { + return NS_ERROR_FAILURE; + } + + rv = binaryFile->AppendNative("nonexistent-executable"_ns); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCString binaryPath; + rv = binaryFile->GetNativePath(binaryPath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + static char const* const argv = {strdup(binaryPath.get())}; + CommandLine::Init(1, &argv); +#endif + } + + NS_ASSERTION(nsComponentManagerImpl::gComponentManager == nullptr, + "CompMgr not null at init"); + + // Create the Component/Service Manager + nsComponentManagerImpl::gComponentManager = new nsComponentManagerImpl(); + NS_ADDREF(nsComponentManagerImpl::gComponentManager); + + // Global cycle collector initialization. + if (!nsCycleCollector_init()) { + return NS_ERROR_UNEXPECTED; + } + + // And start it up for this thread too. + nsCycleCollector_startup(); + + // Register ICU memory functions. This really shouldn't be necessary: the + // JS engine should do this on its own inside JS_Init, and memory-reporting + // code should call a JSAPI function to observe ICU memory usage. But we + // can't define the alloc/free functions in the JS engine, because it can't + // depend on the XPCOM-based memory reporting goop. So for now, we have + // this oddness. + mozilla::SetICUMemoryFunctions(); + + // Do the same for libogg. + ogg_set_mem_functions( + OggReporter::CountingMalloc, OggReporter::CountingCalloc, + OggReporter::CountingRealloc, OggReporter::CountingFree); + + // Initialize the JS engine. + InitializeJS(); + sInitializedJS = true; + + rv = nsComponentManagerImpl::gComponentManager->Init(); + if (NS_FAILED(rv)) { + NS_RELEASE(nsComponentManagerImpl::gComponentManager); + return rv; + } + + if (aResult) { + NS_ADDREF(*aResult = nsComponentManagerImpl::gComponentManager); + } + + // After autoreg, but before we actually instantiate any components, + // add any services listed in the "xpcom-directory-providers" category + // to the directory service. + nsDirectoryService::gService->RegisterCategoryProviders(); + + // Init mozilla::SharedThreadPool (which needs the service manager). + mozilla::SharedThreadPool::InitStatics(); + + mozilla::scache::StartupCache::GetSingleton(); + mozilla::AvailableMemoryTracker::Init(); + + // Notify observers of xpcom autoregistration start + NS_CreateServicesFromCategory(NS_XPCOM_STARTUP_CATEGORY, nullptr, + NS_XPCOM_STARTUP_OBSERVER_ID); +#ifdef XP_WIN + CreateAnonTempFileRemover(); +#endif + + // The memory reporter manager is up and running -- register our reporters. + RegisterStrongMemoryReporter(new ICUReporter()); + RegisterStrongMemoryReporter(new OggReporter()); + xpc::SelfHostedShmem::GetSingleton().InitMemoryReporter(); + + mozilla::Telemetry::Init(); + + mozilla::BackgroundHangMonitor::Startup(); + + const MessageLoop* const loop = MessageLoop::current(); + sMainHangMonitor = new mozilla::BackgroundHangMonitor( + loop->thread_name().c_str(), loop->transient_hang_timeout(), + loop->permanent_hang_timeout()); + + mozilla::dom::JSExecutionManager::Initialize(); + + if (aInitJSContext) { + xpc::InitializeJSContext(); + } + + return NS_OK; +} + +EXPORT_XPCOM_API(nsresult) +NS_InitMinimalXPCOM() { + NS_SetMainThread(); + mozilla::TimeStamp::Startup(); + NS_LogInit(); + NS_InitAtomTable(); + + // We don't have the arguments by hand here. If logging has already been + // initialized by a previous call to LogModule::Init with the arguments + // passed, passing (0, nullptr) is alright here. + mozilla::LogModule::Init(0, nullptr); + + GkRust_Init(); + + nsresult rv = nsThreadManager::get().Init(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Set up the timer globals/timer thread. + rv = nsTimerImpl::Startup(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Create the Component/Service Manager + nsComponentManagerImpl::gComponentManager = new nsComponentManagerImpl(); + NS_ADDREF(nsComponentManagerImpl::gComponentManager); + + rv = nsComponentManagerImpl::gComponentManager->Init(); + if (NS_FAILED(rv)) { + NS_RELEASE(nsComponentManagerImpl::gComponentManager); + return rv; + } + + // Global cycle collector initialization. + if (!nsCycleCollector_init()) { + return NS_ERROR_UNEXPECTED; + } + + mozilla::SharedThreadPool::InitStatics(); + mozilla::Telemetry::Init(); + mozilla::BackgroundHangMonitor::Startup(); + + return NS_OK; +} + +// +// NS_ShutdownXPCOM() +// +// The shutdown sequence for xpcom would be +// +// - Notify "xpcom-shutdown" for modules to release primary (root) references +// - Shutdown XPCOM timers +// - Notify "xpcom-shutdown-threads" for thread joins +// - Shutdown the event queues +// - Release the Global Service Manager +// - Release all service instances held by the global service manager +// - Release the Global Service Manager itself +// - Release the Component Manager +// - Release all factories cached by the Component Manager +// - Notify module loaders to shut down +// - Unload Libraries +// - Release Contractid Cache held by Component Manager +// - Release dll abstraction held by Component Manager +// - Release the Registry held by Component Manager +// - Finally, release the component manager itself +// +EXPORT_XPCOM_API(nsresult) +NS_ShutdownXPCOM(nsIServiceManager* aServMgr) { + return mozilla::ShutdownXPCOM(aServMgr); +} + +namespace mozilla { + +void SetICUMemoryFunctions() { + static bool sICUReporterInitialized = false; + if (!sICUReporterInitialized) { + if (!JS_SetICUMemoryFunctions(ICUReporter::Alloc, ICUReporter::Realloc, + ICUReporter::Free)) { + MOZ_CRASH("JS_SetICUMemoryFunctions failed."); + } + sICUReporterInitialized = true; + } +} + +nsresult ShutdownXPCOM(nsIServiceManager* aServMgr) { + // Make sure the hang monitor is enabled for shutdown. + BackgroundHangMonitor().NotifyActivity(); + + if (!NS_IsMainThread()) { + MOZ_CRASH("Shutdown on wrong thread"); + } + + // Notify observers of xpcom shutting down + { + // Block it so that the COMPtr will get deleted before we hit + // servicemanager shutdown + + nsCOMPtr<nsIThread> thread = do_GetCurrentThread(); + if (NS_WARN_IF(!thread)) { + return NS_ERROR_UNEXPECTED; + } + + mozilla::AppShutdown::AdvanceShutdownPhase( + mozilla::ShutdownPhase::XPCOMWillShutdown); + + // We want the service manager to be the subject of notifications + nsCOMPtr<nsIServiceManager> mgr; + Unused << NS_GetServiceManager(getter_AddRefs(mgr)); + MOZ_DIAGNOSTIC_ASSERT(mgr != nullptr, "Service manager not present!"); + mozilla::AppShutdown::AdvanceShutdownPhase( + mozilla::ShutdownPhase::XPCOMShutdown, nullptr, do_QueryInterface(mgr)); + + // This must happen after the shutdown of media and widgets, which + // are triggered by the NS_XPCOM_SHUTDOWN_OBSERVER_ID notification. + gfxPlatform::ShutdownLayersIPC(); + + mozilla::AppShutdown::AdvanceShutdownPhase( + mozilla::ShutdownPhase::XPCOMShutdownThreads); +#ifdef DEBUG + // Prime an assertion at ThreadEventTarget::Dispatch to avoid late + // dispatches to non main-thread threads. + ThreadEventTarget::XPCOMShutdownThreadsNotificationFinished(); +#endif + + // Shutdown the timer thread and all timers that might still be alive + nsTimerImpl::Shutdown(); + + // Have an extra round of processing after the timers went away. + NS_ProcessPendingEvents(thread); + + // Shutdown all remaining threads. This method does not return until + // all threads created using the thread manager (with the exception of + // the main thread) have exited. + nsThreadManager::get().ShutdownNonMainThreads(); + + RefPtr<nsObserverService> observerService; + CallGetService("@mozilla.org/observer-service;1", + (nsObserverService**)getter_AddRefs(observerService)); + if (observerService) { + observerService->Shutdown(); + } + + // XPCOMShutdownFinal is the default phase for ClearOnShutdown. + // This AdvanceShutdownPhase will thus free most ClearOnShutdown()'ed + // smart pointers. Some destructors may fire extra main thread runnables + // that will be processed inside AdvanceShutdownPhase. + AppShutdown::AdvanceShutdownPhase(ShutdownPhase::XPCOMShutdownFinal); + + // Shutdown the main thread, processing our very last round of events, and + // then mark that we've finished main thread event processing. + nsThreadManager::get().ShutdownMainThread(); + gXPCOMMainThreadEventsAreDoomed = true; + + BackgroundHangMonitor().NotifyActivity(); + + mozilla::dom::JSExecutionManager::Shutdown(); + } + + // XPCOM is officially in shutdown mode NOW + // Set this only after the observers have been notified as this + // will cause servicemanager to become inaccessible. + mozilla::services::Shutdown(); + + // We may have AddRef'd for the caller of NS_InitXPCOM, so release it + // here again: + NS_IF_RELEASE(aServMgr); + + // Shutdown global servicemanager + if (nsComponentManagerImpl::gComponentManager) { + nsComponentManagerImpl::gComponentManager->FreeServices(); + } + + // Remove the remaining main thread representations + nsThreadManager::get().ReleaseMainThread(); + AbstractThread::ShutdownMainThread(); + + // Release the directory service + nsDirectoryService::gService = nullptr; + + free(gGREBinPath); + gGREBinPath = nullptr; + + // FIXME: This can cause harmless writes from sqlite committing + // log files. We have to ignore them before we can move + // the mozilla::PoisonWrite call before this point. See bug + // 834945 for the details. + mozJSModuleLoader::UnloadLoaders(); + + // Clear the profiler's JS context before cycle collection. The profiler will + // notify the JS engine that it can let go of any data it's holding on to for + // profiling purposes. + PROFILER_CLEAR_JS_CONTEXT(); + + bool shutdownCollect; +#ifdef NS_FREE_PERMANENT_DATA + shutdownCollect = true; +#else + shutdownCollect = !!PR_GetEnv("MOZ_CC_RUN_DURING_SHUTDOWN"); +#endif + nsCycleCollector_shutdown(shutdownCollect); + + // There can be code trying to refer to global objects during the final cc + // shutdown. This is the phase for such global objects to correctly release. + AppShutdown::AdvanceShutdownPhase(ShutdownPhase::CCPostLastCycleCollection); + + mozilla::scache::StartupCache::DeleteSingleton(); + mozilla::ScriptPreloader::DeleteSingleton(); + + PROFILER_MARKER_UNTYPED("Shutdown xpcom", OTHER); + + // Shutdown xpcom. This will release all loaders and cause others holding + // a refcount to the component manager to release it. + if (nsComponentManagerImpl::gComponentManager) { + DebugOnly<nsresult> rv = + (nsComponentManagerImpl::gComponentManager)->Shutdown(); + NS_ASSERTION(NS_SUCCEEDED(rv.value), "Component Manager shutdown failed."); + } else { + NS_WARNING("Component Manager was never created ..."); + } + + if (sInitializedJS) { + // Shut down the JS engine. + JS_ShutDown(); + sInitializedJS = false; + } + + mozilla::ScriptPreloader::DeleteCacheDataSingleton(); + + // Release shared memory which might be borrowed by the JS engine. + xpc::SelfHostedShmem::Shutdown(); + + // After all threads have been joined and the component manager has been shut + // down, any remaining objects that could be holding NSS resources (should) + // have been released, so we can safely shut down NSS. + if (NSS_IsInitialized()) { + nsNSSComponent::DoClearSSLExternalAndInternalSessionCache(); + if (NSS_Shutdown() != SECSuccess) { + // If you're seeing this crash and/or warning, some NSS resources are + // still in use (see bugs 1417680 and 1230312). Set the environment + // variable 'MOZ_IGNORE_NSS_SHUTDOWN_LEAKS' to some value to ignore this. + // Also, if leak checking is enabled, report this as a fake leak instead + // of crashing. +#if defined(DEBUG) && !defined(ANDROID) + if (!getenv("MOZ_IGNORE_NSS_SHUTDOWN_LEAKS") && + !getenv("XPCOM_MEM_BLOAT_LOG") && !getenv("XPCOM_MEM_LEAK_LOG") && + !getenv("XPCOM_MEM_REFCNT_LOG") && !getenv("XPCOM_MEM_COMPTR_LOG")) { + MOZ_CRASH("NSS_Shutdown failed"); + } else { +# ifdef NS_BUILD_REFCNT_LOGGING + // Create a fake leak. + NS_LogCtor((void*)0x100, "NSSShutdownFailed", 100); +# endif // NS_BUILD_REFCNT_LOGGING + NS_WARNING("NSS_Shutdown failed"); + } +#else + NS_WARNING("NSS_Shutdown failed"); +#endif // defined(DEBUG) && !defined(ANDROID) + } + } + + // Finally, release the component manager last because it unloads the + // libraries: + if (nsComponentManagerImpl::gComponentManager) { + nsrefcnt cnt; + NS_RELEASE2(nsComponentManagerImpl::gComponentManager, cnt); + NS_ASSERTION(cnt == 0, "Component Manager being held past XPCOM shutdown."); + } + nsComponentManagerImpl::gComponentManager = nullptr; + nsCategoryManager::Destroy(); + + GkRust_Shutdown(); + +#ifdef NS_FREE_PERMANENT_DATA + // By the time we're shutting down, there may still be async parse tasks going + // on in the Servo thread-pool. This is fairly uncommon, though not + // impossible. CSS parsing heavily uses the atom table, so obviously it's not + // fine to get rid of it. + // + // In leak-checking / ASAN / etc. builds, shut down the servo thread-pool, + // which will wait for all the work to be done. For other builds, we don't + // really want to wait on shutdown for possibly slow tasks. So just leak the + // atom table in those. + Servo_ShutdownThreadPool(); + NS_ShutdownAtomTable(); +#endif + + NS_IF_RELEASE(gDebug); + + delete sIOThread; + sIOThread = nullptr; + + delete sMessageLoop; + sMessageLoop = nullptr; + + mozilla::TaskController::Shutdown(); + + if (sCommandLineWasInitialized) { + CommandLine::Terminate(); + sCommandLineWasInitialized = false; + } + + delete sExitManager; + sExitManager = nullptr; + + Omnijar::CleanUp(); + + BackgroundHangMonitor::Shutdown(); + + delete sMainHangMonitor; + sMainHangMonitor = nullptr; + + NS_LogTerm(); + + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/build/XPCOMModule.h b/xpcom/build/XPCOMModule.h new file mode 100644 index 0000000000..e98afc21c0 --- /dev/null +++ b/xpcom/build/XPCOMModule.h @@ -0,0 +1,20 @@ +/* -*- 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 XPCOMModule_h +#define XPCOMModule_h + +#include "nsID.h" + +class nsISupports; + +nsresult nsThreadManagerGetSingleton(const nsIID& aIID, void** aInstancePtr); + +nsresult nsLocalFileConstructor(const nsIID& aIID, void** aInstancePtr); + +extern nsresult nsStringInputStreamConstructor(const nsID&, void**); + +#endif // XPCOMModule_h diff --git a/xpcom/build/XPCOMModule.inc b/xpcom/build/XPCOMModule.inc new file mode 100644 index 0000000000..4f860fd08e --- /dev/null +++ b/xpcom/build/XPCOMModule.inc @@ -0,0 +1,3 @@ +#if defined(XP_WIN) + COMPONENT(WINDOWSREGKEY, nsWindowsRegKeyConstructor) +#endif diff --git a/xpcom/build/XREAppData.h b/xpcom/build/XREAppData.h new file mode 100644 index 0000000000..61572dfc76 --- /dev/null +++ b/xpcom/build/XREAppData.h @@ -0,0 +1,231 @@ +/* -*- 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 nsXREAppData_h +#define nsXREAppData_h + +#include <stdint.h> +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsCOMPtr.h" +#include "nsCRTGlue.h" +#include "nsStringFwd.h" +#include "nsIFile.h" + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +namespace sandbox { +class BrokerServices; +} +#endif + +namespace mozilla { + +struct StaticXREAppData; + +/** + * Application-specific data needed to start the apprunner. + */ +class XREAppData { + public: + XREAppData() = default; + ~XREAppData() = default; + XREAppData(const XREAppData& aOther) { *this = aOther; } + + explicit XREAppData(const StaticXREAppData& aOther) { *this = aOther; } + + XREAppData& operator=(const StaticXREAppData& aOther); + XREAppData& operator=(const XREAppData& aOther); + XREAppData& operator=(XREAppData&& aOther) = default; + + // Lots of code reads these fields directly like a struct, so rather + // than using UniquePtr directly, use an auto-converting wrapper. + class CharPtr { + public: + explicit CharPtr() = default; + explicit CharPtr(const char* v) { *this = v; } + CharPtr(CharPtr&&) = default; + ~CharPtr() = default; + + CharPtr& operator=(const char* v) { + if (v) { + mValue.reset(NS_xstrdup(v)); + } else { + mValue = nullptr; + } + return *this; + } + CharPtr& operator=(const CharPtr& v) { + *this = (const char*)v; + return *this; + } + + operator const char*() const { return mValue.get(); } + + private: + UniqueFreePtr<const char> mValue; + }; + + /** + * The directory of the application to be run. May be null if the + * xulrunner and the app are installed into the same directory. + */ + nsCOMPtr<nsIFile> directory; + + /** + * The name of the application vendor. This must be ASCII, and is normally + * mixed-case, e.g. "Mozilla". Optional (may be null), but highly + * recommended. Must not be the empty string. + */ + CharPtr vendor; + + /** + * The name of the application. This must be ASCII, and is normally + * mixed-case, e.g. "Firefox". Required (must not be null or an empty + * string). + */ + CharPtr name; + + /** + * The internal name of the application for remoting purposes. When left + * unspecified, "name" is used instead. This must be ASCII, and is normally + * lowercase, e.g. "firefox". Optional (may be null but not an empty string). + */ + CharPtr remotingName; + + /** + * The major version, e.g. "0.8.0+". Optional (may be null), but + * required for advanced application features such as the extension + * manager and update service. Must not be the empty string. + */ + CharPtr version; + + /** + * The application's build identifier, e.g. "2004051604" + */ + CharPtr buildID; + + /** + * The application's UUID. Used by the extension manager to determine + * compatible extensions. Optional, but required for advanced application + * features such as the extension manager and update service. + * + * This has traditionally been in the form + * "{AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE}" but for new applications + * a more readable form is encouraged: "appname@vendor.tld". Only + * the following characters are allowed: a-z A-Z 0-9 - . @ _ { } * + */ + CharPtr ID; + + /** + * The copyright information to print for the -h commandline flag, + * e.g. "Copyright (c) 2003 mozilla.org". + */ + CharPtr copyright; + + /** + * Combination of NS_XRE_ prefixed flags (defined below). + */ + uint32_t flags = 0; + + /** + * The location of the XRE. XRE_main may not be able to figure this out + * programatically. + */ + nsCOMPtr<nsIFile> xreDirectory; + + /** + * The minimum/maximum compatible XRE version. + */ + CharPtr minVersion; + CharPtr maxVersion; + + /** + * The server URL to send crash reports to. + */ + CharPtr crashReporterURL; + + /** + * The profile directory that will be used. Optional (may be null). Must not + * be the empty string, must be ASCII. The path is split into components + * along the path separator characters '/' and '\'. + * + * The application data directory ("UAppData", see below) is normally + * composed as follows, where $HOME is platform-specific: + * + * UAppData = $HOME[/$vendor]/$name + * + * If present, the 'profile' string will be used instead of the combination of + * vendor and name as follows: + * + * UAppData = $HOME/$profile + */ + CharPtr profile; + + /** + * The application name to use in the User Agent string. + */ + CharPtr UAName; + + /** + * The URL to the source revision for this build of the application. + */ + CharPtr sourceURL; + + /** + * The URL to use to check for updates. + */ + CharPtr updateURL; + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + /** + * Chromium sandbox BrokerServices. + */ + sandbox::BrokerServices* sandboxBrokerServices = nullptr; +#endif + + // Returns a name suitable for DBUS services. + static void SanitizeNameForDBus(nsACString&); + void GetDBusAppName(nsACString&) const; +}; + +/** + * Indicates whether or not the profile migrator service may be + * invoked at startup when creating a profile. + */ +#define NS_XRE_ENABLE_PROFILE_MIGRATOR (1 << 1) + +/** + * Indicates whether or not to use Breakpad crash reporting. + */ +#define NS_XRE_ENABLE_CRASH_REPORTER (1 << 3) + +/** + * A static version of the XRE app data is compiled into the application + * so that it is not necessary to read application.ini at startup. + * + * This structure is initialized into and matches nsXREAppData + */ +struct StaticXREAppData { + const char* vendor; + const char* name; + const char* remotingName; + const char* version; + const char* buildID; + const char* ID; + const char* copyright; + uint32_t flags; + const char* minVersion; + const char* maxVersion; + const char* crashReporterURL; + const char* profile; + const char* UAName; + const char* sourceURL; + const char* updateURL; +}; + +} // namespace mozilla + +#endif // XREAppData_h diff --git a/xpcom/build/XREChildData.h b/xpcom/build/XREChildData.h new file mode 100644 index 0000000000..8050fb8afc --- /dev/null +++ b/xpcom/build/XREChildData.h @@ -0,0 +1,44 @@ +/* -*- 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 XREChildData_h +#define XREChildData_h + +#include "mozilla/UniquePtr.h" + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +# include "mozilla/sandboxing/loggingTypes.h" + +namespace sandbox { +class BrokerServices; +class TargetServices; +} // namespace sandbox +#endif + +/** + * Data needed to start a child process. + */ +struct XREChildData { +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + /** + * Chromium sandbox TargetServices. + */ + sandbox::TargetServices* sandboxTargetServices = nullptr; + + /** + * Function to provide a logging function to the chromium sandbox code. + */ + mozilla::sandboxing::ProvideLogFunctionCb ProvideLogFunction = nullptr; + + /** + * Chromium sandbox broker services; needed by the remote sandbox + * launcher process. + */ + sandbox::BrokerServices* sandboxBrokerServices = nullptr; +#endif +}; + +#endif // XREChildData_h diff --git a/xpcom/build/XREShellData.h b/xpcom/build/XREShellData.h new file mode 100644 index 0000000000..a0f736f658 --- /dev/null +++ b/xpcom/build/XREShellData.h @@ -0,0 +1,39 @@ +/* -*- 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 XREShellData_h +#define XREShellData_h + +#if defined(LIBFUZZER) +# include "FuzzerRegistry.h" // LibFuzzerDriver +#endif + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +namespace sandbox { +class BrokerServices; +} +#endif + +/** + * Data needed by XRE_XPCShellMain. + */ +struct XREShellData { +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + /** + * Chromium sandbox BrokerServices. + */ + sandbox::BrokerServices* sandboxBrokerServices; +#endif +#if defined(ANDROID) + FILE* outFile; + FILE* errFile; +#endif +#if defined(LIBFUZZER) + LibFuzzerDriver fuzzerDriver; +#endif +}; + +#endif // XREShellData_h diff --git a/xpcom/build/components.conf b/xpcom/build/components.conf new file mode 100644 index 0000000000..6bf301c26f --- /dev/null +++ b/xpcom/build/components.conf @@ -0,0 +1,266 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Classes = [ + { + 'cid': '{c521a612-2aad-46db-b6ab-3b821fb150b1}', + 'contract_ids': ['@mozilla.org/binaryinputstream;1'], + 'type': 'nsBinaryInputStream', + 'headers': ['/xpcom/io/nsBinaryStream.h'], + }, + { + 'cid': '{86c37b9a-74e7-4672-844e-6e7dd83ba484}', + 'contract_ids': ['@mozilla.org/binaryoutputstream;1'], + 'type': 'nsBinaryOutputStream', + 'headers': ['/xpcom/io/nsBinaryStream.h'], + }, + { + 'cid': '{61ba33c0-3031-11d3-8cd0-0060b0fc14a3}', + 'contract_ids': ['@mozilla.org/network/protocol;1?name=chrome'], + 'type': 'nsChromeProtocolHandler', + 'headers': ['/chrome/nsChromeProtocolHandler.h'], + 'protocol_config': { + 'scheme': 'chrome', + 'flags': [ + 'URI_STD', + 'URI_IS_UI_RESOURCE', + 'URI_IS_LOCAL_RESOURCE', + ], + }, + }, + { + 'name': 'ChromeRegistry', + 'cid': '{47049e42-1d87-482a-984d-56ae185e367a}', + 'contract_ids': ['@mozilla.org/chrome/chrome-registry;1'], + 'singleton': True, + 'type': 'nsChromeRegistry', + 'headers': ['/chrome/nsChromeRegistry.h'], + 'constructor': 'nsChromeRegistry::GetSingleton', + }, + { + 'js_name': 'console', + 'cid': '{7e3ff85c-1dd2-11b2-8d4b-eb452cb0ff40}', + 'contract_ids': ['@mozilla.org/consoleservice;1'], + 'interfaces': ['nsIConsoleService'], + 'type': 'nsConsoleService', + 'headers': ['/xpcom/base/nsConsoleService.h'], + 'init_method': 'Init', + }, + { + 'cid': '{678c50b8-6bcb-4ad0-b9b8-c81175955199}', + 'contract_ids': ['@mozilla.org/hash-property-bag;1'], + 'type': 'nsHashPropertyBagCC', + 'headers': ['nsHashPropertyBag.h'], + }, + { + 'cid': '{eb833911-4f49-4623-845f-e58a8e6de4c2}', + 'contract_ids': ['@mozilla.org/io-util;1'], + 'type': 'nsIOUtil', + 'headers': ['/xpcom/io/nsIOUtil.h'], + }, + { + 'cid': '{2e23e220-60be-11d3-8c4a-000064657374}', + 'contract_ids': ['@mozilla.org/file/local;1'], + 'legacy_constructor': 'nsLocalFileConstructor', + }, + { + 'cid': '{00bd71fb-7f09-4ec3-96af-a0b522b77969}', + 'contract_ids': ['@mozilla.org/memory-info-dumper;1'], + 'type': 'nsMemoryInfoDumper', + 'headers': ['mozilla/nsMemoryInfoDumper.h'], + }, + { + 'cid': '{fb97e4f5-32dd-497a-baa2-7d1e55079910}', + 'contract_ids': ['@mozilla.org/memory-reporter-manager;1'], + 'type': 'nsMemoryReporterManager', + 'headers': ['/xpcom/base/nsMemoryReporterManager.h'], + 'init_method': 'Init', + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS, + }, + { + 'cid': '{7b4eeb20-d781-11d4-8a83-0010a4e0c9ca}', + 'contract_ids': ['@mozilla.org/process/util;1'], + 'type': 'nsProcess', + 'headers': ['nsProcess.h'], + }, + { + 'cid': '{aaf68860-f849-40ee-bb7a-b229bce036a3}', + 'contract_ids': ['@mozilla.org/scriptablebase64encoder;1'], + 'type': 'nsScriptableBase64Encoder', + 'headers': ['/xpcom/io/nsScriptableBase64Encoder.h'], + }, + { + 'cid': '{43ebf210-8a7b-4ddb-a83d-b87c51a058db}', + 'contract_ids': ['@mozilla.org/securityconsole/message;1'], + 'type': 'nsSecurityConsoleMessage', + 'headers': ['/xpcom/base/nsSecurityConsoleMessage.h'], + }, + { + 'cid': '{669a9795-6ff7-4ed4-9150-c34ce2971b63}', + 'contract_ids': ['@mozilla.org/storagestream;1'], + 'type': 'nsStorageStream', + 'headers': ['nsStorageStream.h'], + }, + { + 'cid': '{acf8dc41-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-cstring;1'], + 'type': 'nsSupportsCString', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc4a-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-char;1'], + 'type': 'nsSupportsChar', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{cbf86871-4ac0-11d3-baea-00805f8a5dd7}', + 'contract_ids': ['@mozilla.org/supports-double;1'], + 'type': 'nsSupportsDouble', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{cbf86870-4ac0-11d3-baea-00805f8a5dd7}', + 'contract_ids': ['@mozilla.org/supports-float;1'], + 'type': 'nsSupportsFloat', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{a99febba-1dd1-11b2-a943-b02334a6d083}', + 'contract_ids': ['@mozilla.org/supports-interface-pointer;1'], + 'type': 'nsSupportsInterfacePointer', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc43-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRBool;1'], + 'type': 'nsSupportsPRBool', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc4b-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRInt16;1'], + 'type': 'nsSupportsPRInt16', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc4c-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRInt32;1'], + 'type': 'nsSupportsPRInt32', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc4d-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRInt64;1'], + 'type': 'nsSupportsPRInt64', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc49-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRTime;1'], + 'type': 'nsSupportsPRTime', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc46-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRUint16;1'], + 'type': 'nsSupportsPRUint16', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc47-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRUint32;1'], + 'type': 'nsSupportsPRUint32', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc48-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRUint64;1'], + 'type': 'nsSupportsPRUint64', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc44-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRUint8;1'], + 'type': 'nsSupportsPRUint8', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc42-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-string;1'], + 'type': 'nsSupportsString', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'js_name': 'sysinfo', + 'cid': '{d962398a-99e5-49b2-857a-c159049c7f6c}', + 'contract_ids': ['@mozilla.org/system-info;1'], + 'interfaces': ['nsIPropertyBag2', 'nsISystemInfo'], + 'type': 'nsSystemInfo', + 'headers': ['nsSystemInfo.h'], + 'init_method': 'Init', + 'overridable': True, + }, + { + 'js_name': 'tm', + 'cid': '{7a4204c6-e45a-4c37-8ebb-6709a22c917c}', + 'contract_ids': ['@mozilla.org/thread-manager;1'], + 'interfaces': ['nsIThreadManager'], + 'legacy_constructor': 'nsThreadManagerGetSingleton', + 'headers': ['/xpcom/build/XPCOMModule.h'], + }, + { + 'js_name': 'uuid', + 'name': 'UUIDGenerator', + 'cid': '{706d36bb-bf79-4293-81f2-8f6828c18f9d}', + 'contract_ids': ['@mozilla.org/uuid-generator;1'], + 'interfaces': ['nsIUUIDGenerator'], + 'type': 'nsUUIDGenerator', + 'headers': ['/xpcom/base/nsUUIDGenerator.h'], + 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS, + }, + { + 'cid': '{0d6ea1d0-879c-11d5-90ef-0010a4e73d9a}', + 'contract_ids': ['@mozilla.org/variant;1'], + 'type': 'nsVariantCC', + 'headers': ['nsVariant.h'], + }, + { + 'js_name': 'vc', + 'cid': '{c6e47036-ca94-4be3-963a-9abd8705f7a8}', + 'contract_ids': ['@mozilla.org/xpcom/version-comparator;1'], + 'interfaces': ['nsIVersionComparator'], + 'type': 'nsVersionComparatorImpl', + 'headers': ['/xpcom/base/nsVersionComparatorImpl.h'], + }, + { + 'cid': '{dfac10a9-dd24-43cf-a095-6ffa2e4b6a6c}', + 'contract_ids': ['@mozilla.org/xpcom/ini-parser-factory;1'], + 'type': 'nsINIParserFactory', + 'headers': ['/xpcom/ds/nsINIParserImpl.h'], + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS, + }, +] + +if buildconfig.substs['OS_ARCH'] == 'WINNT': + Classes += [ + { + 'cid': '{a53bc624-d577-4839-b8ec-bb5040a52ff4}', + 'contract_ids': ['@mozilla.org/windows-registry-key;1'], + 'legacy_constructor': 'nsWindowsRegKeyConstructor', + 'headers': ['nsWindowsRegKey.h'], + }, + ] + +if buildconfig.substs['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + Classes += [ + { + 'cid': '{b0f20595-88ce-4738-a1a4-24de78eb8051}', + 'contract_ids': ['@mozilla.org/mac-preferences-reader;1'], + 'type': 'nsMacPreferencesReader', + 'headers': ['mozilla/nsMacPreferencesReader.h'], + }, + ] diff --git a/xpcom/build/gen_process_types.py b/xpcom/build/gen_process_types.py new file mode 100644 index 0000000000..9e16ecfd4c --- /dev/null +++ b/xpcom/build/gen_process_types.py @@ -0,0 +1,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/. + +from geckoprocesstypes import process_types + + +def main(output): + output.write( + """\ +/* THIS FILE IS GENERATED BY gen_process_types.py - DO NOT EDIT */ +""" + ) + + for p in process_types: + output.write( + """ +#ifndef SKIP_PROCESS_TYPE_%(allcaps_name)s +GECKO_PROCESS_TYPE(%(enum_value)d, %(enum_name)s, "%(string_name)s", """ + """%(proc_typename)s, %(process_bin_type)s, %(procinfo_typename)s, """ + """%(webidl_typename)s, %(allcaps_name)s) +#endif // SKIP_PROCESS_TYPE_%(allcaps_name)s +""" + % { + "enum_value": p.enum_value, + "enum_name": p.enum_name, + "string_name": p.string_name, + "proc_typename": p.proc_typename, + "process_bin_type": p.process_bin_type, + "procinfo_typename": p.procinfo_typename, + "webidl_typename": p.webidl_typename, + "allcaps_name": p.allcaps_name, + } + ) diff --git a/xpcom/build/mach_override.c b/xpcom/build/mach_override.c new file mode 100644 index 0000000000..1ece045e2c --- /dev/null +++ b/xpcom/build/mach_override.c @@ -0,0 +1,793 @@ +// Copied from upstream at revision 195c13743fe0ebc658714e2a9567d86529f20443. +// mach_override.c semver:1.2.0 +// Copyright (c) 2003-2012 Jonathan 'Wolf' Rentzsch: http://rentzsch.com +// Some rights reserved: http://opensource.org/licenses/mit +// https://github.com/rentzsch/mach_override + +#include "mach_override.h" + +#include <mach-o/dyld.h> +#include <mach/mach_host.h> +#include <mach/mach_init.h> +#include <mach/vm_map.h> +#include <sys/mman.h> + +#include <CoreServices/CoreServices.h> + +/************************** +* +* Constants +* +**************************/ +#pragma mark - +#pragma mark (Constants) + +#define kPageSize 4096 +#if defined(__ppc__) || defined(__POWERPC__) + +long kIslandTemplate[] = { + 0x9001FFFC, // stw r0,-4(SP) + 0x3C00DEAD, // lis r0,0xDEAD + 0x6000BEEF, // ori r0,r0,0xBEEF + 0x7C0903A6, // mtctr r0 + 0x8001FFFC, // lwz r0,-4(SP) + 0x60000000, // nop ; optionally replaced + 0x4E800420 // bctr +}; + +#define kAddressHi 3 +#define kAddressLo 5 +#define kInstructionHi 10 +#define kInstructionLo 11 + +#elif defined(__i386__) + +#define kOriginalInstructionsSize 16 +// On X86 we migh need to instert an add with a 32 bit immediate after the +// original instructions. +#define kMaxFixupSizeIncrease 5 + +unsigned char kIslandTemplate[] = { + // kOriginalInstructionsSize nop instructions so that we + // should have enough space to host original instructions + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + // Now the real jump instruction + 0xE9, 0xEF, 0xBE, 0xAD, 0xDE +}; + +#define kInstructions 0 +#define kJumpAddress kInstructions + kOriginalInstructionsSize + 1 +#elif defined(__x86_64__) + +#define kOriginalInstructionsSize 32 +// On X86-64 we never need to instert a new instruction. +#define kMaxFixupSizeIncrease 0 + +#define kJumpAddress kOriginalInstructionsSize + 6 + +unsigned char kIslandTemplate[] = { + // kOriginalInstructionsSize nop instructions so that we + // should have enough space to host original instructions + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + // Now the real jump instruction + 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + +#endif + +/************************** +* +* Data Types +* +**************************/ +#pragma mark - +#pragma mark (Data Types) + +typedef struct { + char instructions[sizeof(kIslandTemplate)]; +} BranchIsland; + +/************************** +* +* Funky Protos +* +**************************/ +#pragma mark - +#pragma mark (Funky Protos) + +static mach_error_t +allocateBranchIsland( + BranchIsland **island, + void *originalFunctionAddress); + + mach_error_t +freeBranchIsland( + BranchIsland *island ); + +#if defined(__ppc__) || defined(__POWERPC__) + mach_error_t +setBranchIslandTarget( + BranchIsland *island, + const void *branchTo, + long instruction ); +#endif + +#if defined(__i386__) || defined(__x86_64__) +mach_error_t +setBranchIslandTarget_i386( + BranchIsland *island, + const void *branchTo, + char* instructions ); +void +atomic_mov64( + uint64_t *targetAddress, + uint64_t value ); + + static Boolean +eatKnownInstructions( + unsigned char *code, + uint64_t *newInstruction, + int *howManyEaten, + char *originalInstructions, + int *originalInstructionCount, + uint8_t *originalInstructionSizes ); + + static void +fixupInstructions( + uint32_t offset, + void *instructionsToFix, + int instructionCount, + uint8_t *instructionSizes ); +#endif + +/******************************************************************************* +* +* Interface +* +*******************************************************************************/ +#pragma mark - +#pragma mark (Interface) + +#if defined(__i386__) || defined(__x86_64__) +mach_error_t makeIslandExecutable(void *address) { + mach_error_t err = err_none; + uintptr_t page = (uintptr_t)address & ~(uintptr_t)(kPageSize-1); + int e = err_none; + e |= mprotect((void *)page, kPageSize, PROT_EXEC | PROT_READ | PROT_WRITE); + e |= msync((void *)page, kPageSize, MS_INVALIDATE ); + if (e) { + err = err_cannot_override; + } + return err; +} +#endif + + mach_error_t +mach_override_ptr( + void *originalFunctionAddress, + const void *overrideFunctionAddress, + void **originalFunctionReentryIsland ) +{ + assert( originalFunctionAddress ); + assert( overrideFunctionAddress ); + + // this addresses overriding such functions as AudioOutputUnitStart() + // test with modified DefaultOutputUnit project +#if defined(__x86_64__) + for(;;){ + if(*(uint16_t*)originalFunctionAddress==0x25FF) // jmp qword near [rip+0x????????] + originalFunctionAddress=*(void**)((char*)originalFunctionAddress+6+*(int32_t *)((uint16_t*)originalFunctionAddress+1)); + else break; + } +#elif defined(__i386__) + for(;;){ + if(*(uint16_t*)originalFunctionAddress==0x25FF) // jmp *0x???????? + originalFunctionAddress=**(void***)((uint16_t*)originalFunctionAddress+1); + else break; + } +#endif + + long *originalFunctionPtr = (long*) originalFunctionAddress; + mach_error_t err = err_none; + +#if defined(__ppc__) || defined(__POWERPC__) + // Ensure first instruction isn't 'mfctr'. + #define kMFCTRMask 0xfc1fffff + #define kMFCTRInstruction 0x7c0903a6 + + long originalInstruction = *originalFunctionPtr; + if( !err && ((originalInstruction & kMFCTRMask) == kMFCTRInstruction) ) + err = err_cannot_override; +#elif defined(__i386__) || defined(__x86_64__) + int eatenCount = 0; + int originalInstructionCount = 0; + char originalInstructions[kOriginalInstructionsSize]; + uint8_t originalInstructionSizes[kOriginalInstructionsSize]; + uint64_t jumpRelativeInstruction = 0; // JMP + + Boolean overridePossible = eatKnownInstructions ((unsigned char *)originalFunctionPtr, + &jumpRelativeInstruction, &eatenCount, + originalInstructions, &originalInstructionCount, + originalInstructionSizes ); + if (eatenCount + kMaxFixupSizeIncrease > kOriginalInstructionsSize) { + //printf ("Too many instructions eaten\n"); + overridePossible = false; + } + if (!overridePossible) err = err_cannot_override; + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); +#endif + + // Make the original function implementation writable. + if( !err ) { + err = vm_protect( mach_task_self(), + (vm_address_t) originalFunctionPtr, 8, false, + (VM_PROT_ALL | VM_PROT_COPY) ); + if( err ) + err = vm_protect( mach_task_self(), + (vm_address_t) originalFunctionPtr, 8, false, + (VM_PROT_DEFAULT | VM_PROT_COPY) ); + } + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); + + // Allocate and target the escape island to the overriding function. + BranchIsland *escapeIsland = NULL; + if( !err ) { + err = allocateBranchIsland( &escapeIsland, originalFunctionAddress ); + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); + } + +#if defined(__ppc__) || defined(__POWERPC__) + if( !err ) + err = setBranchIslandTarget( escapeIsland, overrideFunctionAddress, 0 ); + + // Build the branch absolute instruction to the escape island. + long branchAbsoluteInstruction = 0; // Set to 0 just to silence warning. + if( !err ) { + long escapeIslandAddress = ((long) escapeIsland) & 0x3FFFFFF; + branchAbsoluteInstruction = 0x48000002 | escapeIslandAddress; + } +#elif defined(__i386__) || defined(__x86_64__) + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); + + if( !err ) + err = setBranchIslandTarget_i386( escapeIsland, overrideFunctionAddress, 0 ); + + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); + // Build the jump relative instruction to the escape island +#endif + + +#if defined(__i386__) || defined(__x86_64__) + if (!err) { + uint32_t addressOffset = ((char*)escapeIsland - (char*)originalFunctionPtr - 5); + addressOffset = OSSwapInt32(addressOffset); + + jumpRelativeInstruction |= 0xE900000000000000LL; + jumpRelativeInstruction |= ((uint64_t)addressOffset & 0xffffffff) << 24; + jumpRelativeInstruction = OSSwapInt64(jumpRelativeInstruction); + } +#endif + + // Optionally allocate & return the reentry island. This may contain relocated + // jmp instructions and so has all the same addressing reachability requirements + // the escape island has to the original function, except the escape island is + // technically our original function. + BranchIsland *reentryIsland = NULL; + if( !err && originalFunctionReentryIsland ) { + err = allocateBranchIsland( &reentryIsland, escapeIsland); + if( !err ) + *originalFunctionReentryIsland = reentryIsland; + } + +#if defined(__ppc__) || defined(__POWERPC__) + // Atomically: + // o If the reentry island was allocated: + // o Insert the original instruction into the reentry island. + // o Target the reentry island at the 2nd instruction of the + // original function. + // o Replace the original instruction with the branch absolute. + if( !err ) { + int escapeIslandEngaged = false; + do { + if( reentryIsland ) + err = setBranchIslandTarget( reentryIsland, + (void*) (originalFunctionPtr+1), originalInstruction ); + if( !err ) { + escapeIslandEngaged = CompareAndSwap( originalInstruction, + branchAbsoluteInstruction, + (UInt32*)originalFunctionPtr ); + if( !escapeIslandEngaged ) { + // Someone replaced the instruction out from under us, + // re-read the instruction, make sure it's still not + // 'mfctr' and try again. + originalInstruction = *originalFunctionPtr; + if( (originalInstruction & kMFCTRMask) == kMFCTRInstruction) + err = err_cannot_override; + } + } + } while( !err && !escapeIslandEngaged ); + } +#elif defined(__i386__) || defined(__x86_64__) + // Atomically: + // o If the reentry island was allocated: + // o Insert the original instructions into the reentry island. + // o Target the reentry island at the first non-replaced + // instruction of the original function. + // o Replace the original first instructions with the jump relative. + // + // Note that on i386, we do not support someone else changing the code under our feet + if ( !err ) { + uint32_t offset = (uintptr_t)originalFunctionPtr - (uintptr_t)reentryIsland; + fixupInstructions(offset, originalInstructions, + originalInstructionCount, originalInstructionSizes ); + + if( reentryIsland ) + err = setBranchIslandTarget_i386( reentryIsland, + (void*) ((char *)originalFunctionPtr+eatenCount), originalInstructions ); + // try making islands executable before planting the jmp +#if defined(__x86_64__) || defined(__i386__) + if( !err ) + err = makeIslandExecutable(escapeIsland); + if( !err && reentryIsland ) + err = makeIslandExecutable(reentryIsland); +#endif + if ( !err ) + atomic_mov64((uint64_t *)originalFunctionPtr, jumpRelativeInstruction); + } +#endif + + // Clean up on error. + if( err ) { + if( reentryIsland ) + freeBranchIsland( reentryIsland ); + if( escapeIsland ) + freeBranchIsland( escapeIsland ); + } + + return err; +} + +/******************************************************************************* +* +* Implementation +* +*******************************************************************************/ +#pragma mark - +#pragma mark (Implementation) + +static bool jump_in_range(intptr_t from, intptr_t to) { + intptr_t field_value = to - from - 5; + int32_t field_value_32 = field_value; + return field_value == field_value_32; +} + +/******************************************************************************* + Implementation: Allocates memory for a branch island. + + @param island <- The allocated island. + @result <- mach_error_t + + ***************************************************************************/ + +static mach_error_t +allocateBranchIslandAux( + BranchIsland **island, + void *originalFunctionAddress, + bool forward) +{ + assert( island ); + assert( sizeof( BranchIsland ) <= kPageSize ); + + vm_map_t task_self = mach_task_self(); + vm_address_t original_address = (vm_address_t) originalFunctionAddress; + vm_address_t address = original_address; + + for (;;) { + vm_size_t vmsize = 0; + memory_object_name_t object = 0; + kern_return_t kr = 0; + vm_region_flavor_t flavor = VM_REGION_BASIC_INFO; + // Find the region the address is in. +#if __WORDSIZE == 32 + vm_region_basic_info_data_t info; + mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT; + kr = vm_region(task_self, &address, &vmsize, flavor, + (vm_region_info_t)&info, &info_count, &object); +#else + vm_region_basic_info_data_64_t info; + mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64; + kr = vm_region_64(task_self, &address, &vmsize, flavor, + (vm_region_info_t)&info, &info_count, &object); +#endif + if (kr != KERN_SUCCESS) + return kr; + assert((address & (kPageSize - 1)) == 0); + + // Go to the first page before or after this region + vm_address_t new_address = forward ? address + vmsize : address - kPageSize; +#if __WORDSIZE == 64 + if(!jump_in_range(original_address, new_address)) + break; +#endif + address = new_address; + + // Try to allocate this page. + kr = vm_allocate(task_self, &address, kPageSize, 0); + if (kr == KERN_SUCCESS) { + *island = (BranchIsland*) address; + return err_none; + } + if (kr != KERN_NO_SPACE) + return kr; + } + + return KERN_NO_SPACE; +} + +static mach_error_t +allocateBranchIsland( + BranchIsland **island, + void *originalFunctionAddress) +{ + mach_error_t err = + allocateBranchIslandAux(island, originalFunctionAddress, true); + if (!err) + return err; + return allocateBranchIslandAux(island, originalFunctionAddress, false); +} + + +/******************************************************************************* + Implementation: Deallocates memory for a branch island. + + @param island -> The island to deallocate. + @result <- mach_error_t + + ***************************************************************************/ + + mach_error_t +freeBranchIsland( + BranchIsland *island ) +{ + assert( island ); + assert( (*(long*)&island->instructions[0]) == kIslandTemplate[0] ); + assert( sizeof( BranchIsland ) <= kPageSize ); + return vm_deallocate( mach_task_self(), (vm_address_t) island, + kPageSize ); +} + +/******************************************************************************* + Implementation: Sets the branch island's target, with an optional + instruction. + + @param island -> The branch island to insert target into. + @param branchTo -> The address of the target. + @param instruction -> Optional instruction to execute prior to branch. Set + to zero for nop. + @result <- mach_error_t + + ***************************************************************************/ +#if defined(__ppc__) || defined(__POWERPC__) + mach_error_t +setBranchIslandTarget( + BranchIsland *island, + const void *branchTo, + long instruction ) +{ + // Copy over the template code. + bcopy( kIslandTemplate, island->instructions, sizeof( kIslandTemplate ) ); + + // Fill in the address. + ((short*)island->instructions)[kAddressLo] = ((long) branchTo) & 0x0000FFFF; + ((short*)island->instructions)[kAddressHi] + = (((long) branchTo) >> 16) & 0x0000FFFF; + + // Fill in the (optional) instuction. + if( instruction != 0 ) { + ((short*)island->instructions)[kInstructionLo] + = instruction & 0x0000FFFF; + ((short*)island->instructions)[kInstructionHi] + = (instruction >> 16) & 0x0000FFFF; + } + + //MakeDataExecutable( island->instructions, sizeof( kIslandTemplate ) ); + msync( island->instructions, sizeof( kIslandTemplate ), MS_INVALIDATE ); + + return err_none; +} +#endif + +#if defined(__i386__) + mach_error_t +setBranchIslandTarget_i386( + BranchIsland *island, + const void *branchTo, + char* instructions ) +{ + + // Copy over the template code. + bcopy( kIslandTemplate, island->instructions, sizeof( kIslandTemplate ) ); + + // copy original instructions + if (instructions) { + bcopy (instructions, island->instructions + kInstructions, kOriginalInstructionsSize); + } + + // Fill in the address. + int32_t addressOffset = (char *)branchTo - (island->instructions + kJumpAddress + 4); + *((int32_t *)(island->instructions + kJumpAddress)) = addressOffset; + + msync( island->instructions, sizeof( kIslandTemplate ), MS_INVALIDATE ); + return err_none; +} + +#elif defined(__x86_64__) +mach_error_t +setBranchIslandTarget_i386( + BranchIsland *island, + const void *branchTo, + char* instructions ) +{ + // Copy over the template code. + bcopy( kIslandTemplate, island->instructions, sizeof( kIslandTemplate ) ); + + // Copy original instructions. + if (instructions) { + bcopy (instructions, island->instructions, kOriginalInstructionsSize); + } + + // Fill in the address. + *((uint64_t *)(island->instructions + kJumpAddress)) = (uint64_t)branchTo; + msync( island->instructions, sizeof( kIslandTemplate ), MS_INVALIDATE ); + + return err_none; +} +#endif + + +#if defined(__i386__) || defined(__x86_64__) +// simplistic instruction matching +typedef struct { + unsigned int length; // max 15 + unsigned char mask[15]; // sequence of bytes in memory order + unsigned char constraint[15]; // sequence of bytes in memory order +} AsmInstructionMatch; + +#if defined(__i386__) +static AsmInstructionMatch possibleInstructions[] = { + // clang-format off + { 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xE9, 0x00, 0x00, 0x00, 0x00} }, // jmp 0x???????? + { 0x5, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, {0x55, 0x89, 0xe5, 0xc9, 0xc3} }, // push %ebp; mov %esp,%ebp; leave; ret + { 0x1, {0xFF}, {0x90} }, // nop + { 0x1, {0xFF}, {0x55} }, // push %esp + { 0x2, {0xFF, 0xFF}, {0x89, 0xE5} }, // mov %esp,%ebp + { 0x1, {0xFF}, {0x53} }, // push %ebx + { 0x3, {0xFF, 0xFF, 0x00}, {0x83, 0xEC, 0x00} }, // sub 0x??, %esp + { 0x6, {0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}, {0x81, 0xEC, 0x00, 0x00, 0x00, 0x00} }, // sub 0x??, %esp with 32bit immediate + { 0x1, {0xFF}, {0x57} }, // push %edi + { 0x1, {0xFF}, {0x56} }, // push %esi + { 0x2, {0xFF, 0xFF}, {0x31, 0xC0} }, // xor %eax, %eax + { 0x3, {0xFF, 0x4F, 0x00}, {0x8B, 0x45, 0x00} }, // mov $imm(%ebp), %reg + { 0x3, {0xFF, 0x4C, 0x00}, {0x8B, 0x40, 0x00} }, // mov $imm(%eax-%edx), %reg + { 0x4, {0xFF, 0xFF, 0xFF, 0x00}, {0x8B, 0x4C, 0x24, 0x00} }, // mov $imm(%esp), %ecx + { 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xB8, 0x00, 0x00, 0x00, 0x00} }, // mov $imm, %eax + { 0x6, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, {0xE8, 0x00, 0x00, 0x00, 0x00, 0x58} }, // call $imm; pop %eax + { 0x0 } + // clang-format on +}; +#elif defined(__x86_64__) +static AsmInstructionMatch possibleInstructions[] = { + // clang-format off + { 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xE9, 0x00, 0x00, 0x00, 0x00} }, // jmp 0x???????? + { 0x1, {0xFF}, {0x90} }, // nop + { 0x1, {0xF8}, {0x50} }, // push %rX + { 0x3, {0xFF, 0xFF, 0xFF}, {0x48, 0x89, 0xE5} }, // mov %rsp,%rbp + { 0x4, {0xFF, 0xFF, 0xFF, 0x00}, {0x48, 0x83, 0xEC, 0x00} }, // sub 0x??, %rsp + { 0x4, {0xFB, 0xFF, 0x00, 0x00}, {0x48, 0x89, 0x00, 0x00} }, // move onto rbp + { 0x4, {0xFF, 0xFF, 0xFF, 0xFF}, {0x40, 0x0f, 0xbe, 0xce} }, // movsbl %sil, %ecx + { 0x2, {0xFF, 0x00}, {0x41, 0x00} }, // push %rXX + { 0x2, {0xFF, 0x00}, {0x85, 0x00} }, // test %rX,%rX + { 0x5, {0xF8, 0x00, 0x00, 0x00, 0x00}, {0xB8, 0x00, 0x00, 0x00, 0x00} }, // mov $imm, %reg + { 0x3, {0xFF, 0xFF, 0x00}, {0xFF, 0x77, 0x00} }, // pushq $imm(%rdi) + { 0x2, {0xFF, 0xFF}, {0x31, 0xC0} }, // xor %eax, %eax + { 0x2, {0xFF, 0xFF}, {0x89, 0xF8} }, // mov %edi, %eax + + //leaq offset(%rip),%rax + { 0x7, {0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}, {0x48, 0x8d, 0x05, 0x00, 0x00, 0x00, 0x00} }, + + { 0x0 } + // clang-format on +}; +#endif + +static Boolean codeMatchesInstruction(unsigned char *code, AsmInstructionMatch* instruction) +{ + Boolean match = true; + + size_t i; + for (i=0; i<instruction->length; i++) { + unsigned char mask = instruction->mask[i]; + unsigned char constraint = instruction->constraint[i]; + unsigned char codeValue = code[i]; + + match = ((codeValue & mask) == constraint); + if (!match) break; + } + + return match; +} + +#if defined(__i386__) || defined(__x86_64__) + static Boolean +eatKnownInstructions( + unsigned char *code, + uint64_t *newInstruction, + int *howManyEaten, + char *originalInstructions, + int *originalInstructionCount, + uint8_t *originalInstructionSizes ) +{ + Boolean allInstructionsKnown = true; + int totalEaten = 0; + unsigned char* ptr = code; + int remainsToEat = 5; // a JMP instruction takes 5 bytes + int instructionIndex = 0; + + if (howManyEaten) *howManyEaten = 0; + if (originalInstructionCount) *originalInstructionCount = 0; + while (remainsToEat > 0) { + Boolean curInstructionKnown = false; + + // See if instruction matches one we know + AsmInstructionMatch* curInstr = possibleInstructions; + do { + if ((curInstructionKnown = codeMatchesInstruction(ptr, curInstr))) break; + curInstr++; + } while (curInstr->length > 0); + + // if all instruction matches failed, we don't know current instruction then, stop here + if (!curInstructionKnown) { + allInstructionsKnown = false; + fprintf(stderr, "mach_override: some instructions unknown! Need to update mach_override.c\n"); + break; + } + + // At this point, we've matched curInstr + int eaten = curInstr->length; + ptr += eaten; + remainsToEat -= eaten; + totalEaten += eaten; + + if (originalInstructionSizes) originalInstructionSizes[instructionIndex] = eaten; + instructionIndex += 1; + if (originalInstructionCount) *originalInstructionCount = instructionIndex; + } + + + if (howManyEaten) *howManyEaten = totalEaten; + + if (originalInstructions) { + Boolean enoughSpaceForOriginalInstructions = (totalEaten < kOriginalInstructionsSize); + + if (enoughSpaceForOriginalInstructions) { + memset(originalInstructions, 0x90 /* NOP */, kOriginalInstructionsSize); // fill instructions with NOP + bcopy(code, originalInstructions, totalEaten); + } else { + // printf ("Not enough space in island to store original instructions. Adapt the island definition and kOriginalInstructionsSize\n"); + return false; + } + } + + if (allInstructionsKnown) { + // save last 3 bytes of first 64bits of codre we'll replace + uint64_t currentFirst64BitsOfCode = *((uint64_t *)code); + currentFirst64BitsOfCode = OSSwapInt64(currentFirst64BitsOfCode); // back to memory representation + currentFirst64BitsOfCode &= 0x0000000000FFFFFFLL; + + // keep only last 3 instructions bytes, first 5 will be replaced by JMP instr + *newInstruction &= 0xFFFFFFFFFF000000LL; // clear last 3 bytes + *newInstruction |= (currentFirst64BitsOfCode & 0x0000000000FFFFFFLL); // set last 3 bytes + } + + return allInstructionsKnown; +} + + static void +fixupInstructions( + uint32_t offset, + void *instructionsToFix, + int instructionCount, + uint8_t *instructionSizes ) +{ + // The start of "leaq offset(%rip),%rax" + static const uint8_t LeaqHeader[] = {0x48, 0x8d, 0x05}; + + int index; + for (index = 0;index < instructionCount;index += 1) + { + if (*(uint8_t*)instructionsToFix == 0xE9) // 32-bit jump relative + { + uint32_t *jumpOffsetPtr = (uint32_t*)((uintptr_t)instructionsToFix + 1); + *jumpOffsetPtr += offset; + } + + // leaq offset(%rip),%rax + if (memcmp(instructionsToFix, LeaqHeader, 3) == 0) { + uint32_t *LeaqOffsetPtr = (uint32_t*)((uintptr_t)instructionsToFix + 3); + *LeaqOffsetPtr += offset; + } + + // 32-bit call relative to the next addr; pop %eax + if (*(uint8_t*)instructionsToFix == 0xE8) + { + // Just this call is larger than the jump we use, so we + // know this is the last instruction. + assert(index == (instructionCount - 1)); + assert(instructionSizes[index] == 6); + + // Insert "addl $offset, %eax" in the end so that when + // we jump to the rest of the function %eax has the + // value it would have if eip had been pushed by the + // call in its original position. + uint8_t *op = instructionsToFix; + op += 6; + *op = 0x05; // addl + uint32_t *addImmPtr = (uint32_t*)(op + 1); + *addImmPtr = offset; + } + + instructionsToFix = (void*)((uintptr_t)instructionsToFix + instructionSizes[index]); + } +} +#endif + +#if defined(__i386__) +__asm( + ".text;" + ".align 2, 0x90;" + "_atomic_mov64:;" + " pushl %ebp;" + " movl %esp, %ebp;" + " pushl %esi;" + " pushl %ebx;" + " pushl %ecx;" + " pushl %eax;" + " pushl %edx;" + + // atomic push of value to an address + // we use cmpxchg8b, which compares content of an address with + // edx:eax. If they are equal, it atomically puts 64bit value + // ecx:ebx in address. + // We thus put contents of address in edx:eax to force ecx:ebx + // in address + " mov 8(%ebp), %esi;" // esi contains target address + " mov 12(%ebp), %ebx;" + " mov 16(%ebp), %ecx;" // ecx:ebx now contains value to put in target address + " mov (%esi), %eax;" + " mov 4(%esi), %edx;" // edx:eax now contains value currently contained in target address + " lock; cmpxchg8b (%esi);" // atomic move. + + // restore registers + " popl %edx;" + " popl %eax;" + " popl %ecx;" + " popl %ebx;" + " popl %esi;" + " popl %ebp;" + " ret" +); +#elif defined(__x86_64__) +void atomic_mov64( + uint64_t *targetAddress, + uint64_t value ) +{ + *targetAddress = value; +} +#endif +#endif diff --git a/xpcom/build/mach_override.h b/xpcom/build/mach_override.h new file mode 100644 index 0000000000..21fbc7ff4d --- /dev/null +++ b/xpcom/build/mach_override.h @@ -0,0 +1,121 @@ +/******************************************************************************* + mach_override.h + Copyright (c) 2003-2009 Jonathan 'Wolf' Rentzsch: <http://rentzsch.com> + Some rights reserved: <http://opensource.org/licenses/mit-license.php> + + ***************************************************************************/ + +/***************************************************************************//** + @mainpage mach_override + @author Jonathan 'Wolf' Rentzsch: <http://rentzsch.com> + + This package, coded in C to the Mach API, allows you to override ("patch") + program- and system-supplied functions at runtime. You can fully replace + functions with your implementations, or merely head- or tail-patch the + original implementations. + + Use it by #include'ing mach_override.h from your .c, .m or .mm file(s). + + @todo Discontinue use of Carbon's MakeDataExecutable() and + CompareAndSwap() calls and start using the Mach equivalents, if they + exist. If they don't, write them and roll them in. That way, this + code will be pure Mach, which will make it easier to use everywhere. + Update: MakeDataExecutable() has been replaced by + msync(MS_INVALIDATE). There is an OSCompareAndSwap in libkern, but + I'm currently unsure if I can link against it. May have to roll in + my own version... + @todo Stop using an entire 4K high-allocated VM page per 28-byte escape + branch island. Done right, this will dramatically speed up escape + island allocations when they number over 250. Then again, if you're + overriding more than 250 functions, maybe speed isn't your main + concern... + @todo Add detection of: b, bl, bla, bc, bcl, bcla, bcctrl, bclrl + first-instructions. Initially, we should refuse to override + functions beginning with these instructions. Eventually, we should + dynamically rewrite them to make them position-independent. + @todo Write mach_unoverride(), which would remove an override placed on a + function. Must be multiple-override aware, which means an almost + complete rewrite under the covers, because the target address can't + be spread across two load instructions like it is now since it will + need to be atomically updatable. + @todo Add non-rentry variants of overrides to test_mach_override. + + ***************************************************************************/ + +#ifndef _mach_override_ +#define _mach_override_ + +#include <sys/types.h> +#include <mach/error.h> + +#ifdef __cplusplus + extern "C" { +#endif + +/** + Returned if the function to be overrided begins with a 'mfctr' instruction. +*/ +#define err_cannot_override (err_local|1) + +/************************************************************************************//** + Dynamically overrides the function implementation referenced by + originalFunctionAddress with the implentation pointed to by overrideFunctionAddress. + Optionally returns a pointer to a "reentry island" which, if jumped to, will resume + the original implementation. + + @param originalFunctionAddress -> Required address of the function to + override (with overrideFunctionAddress). + @param overrideFunctionAddress -> Required address to the overriding + function. + @param originalFunctionReentryIsland <- Optional pointer to pointer to the + reentry island. Can be nullptr. + @result <- err_cannot_override if the original + function's implementation begins with + the 'mfctr' instruction. + + ************************************************************************************/ + + mach_error_t +mach_override_ptr( + void *originalFunctionAddress, + const void *overrideFunctionAddress, + void **originalFunctionReentryIsland ); + +/************************************************************************************//** + + + ************************************************************************************/ + +#ifdef __cplusplus + +#define MACH_OVERRIDE( ORIGINAL_FUNCTION_RETURN_TYPE, ORIGINAL_FUNCTION_NAME, ORIGINAL_FUNCTION_ARGS, ERR ) \ + { \ + static ORIGINAL_FUNCTION_RETURN_TYPE (*ORIGINAL_FUNCTION_NAME##_reenter)ORIGINAL_FUNCTION_ARGS; \ + static bool ORIGINAL_FUNCTION_NAME##_overriden = false; \ + class mach_override_class__##ORIGINAL_FUNCTION_NAME { \ + public: \ + static kern_return_t override(void *originalFunctionPtr) { \ + kern_return_t result = err_none; \ + if (!ORIGINAL_FUNCTION_NAME##_overriden) { \ + ORIGINAL_FUNCTION_NAME##_overriden = true; \ + result = mach_override_ptr( (void*)originalFunctionPtr, \ + (void*)mach_override_class__##ORIGINAL_FUNCTION_NAME::replacement, \ + (void**)&ORIGINAL_FUNCTION_NAME##_reenter ); \ + } \ + return result; \ + } \ + static ORIGINAL_FUNCTION_RETURN_TYPE replacement ORIGINAL_FUNCTION_ARGS { + +#define END_MACH_OVERRIDE( ORIGINAL_FUNCTION_NAME ) \ + } \ + }; \ + \ + err = mach_override_class__##ORIGINAL_FUNCTION_NAME::override((void*)ORIGINAL_FUNCTION_NAME); \ + } + +#endif + +#ifdef __cplusplus + } +#endif +#endif // _mach_override_ diff --git a/xpcom/build/moz.build b/xpcom/build/moz.build new file mode 100644 index 0000000000..8d08e73978 --- /dev/null +++ b/xpcom/build/moz.build @@ -0,0 +1,105 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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 += [ + "nsXPCOM.h", + "nsXPCOMCID.h", + "nsXPCOMCIDInternal.h", + "nsXULAppAPI.h", + "XREChildData.h", + "XREShellData.h", +] + +EXPORTS.mozilla += [ + "!GeckoProcessTypes.h", + "!Services.h", + "FileLocation.h", + "IOInterposer.h", + "LateWriteChecks.h", + "Omnijar.h", + "PoisonIOInterposer.h", + "SmallArrayLRUCache.h", + "XPCOM.h", + "XREAppData.h", +] + +if CONFIG["OS_ARCH"] == "WINNT": + EXPORTS.mozilla += [ + "perfprobe.h", + ] + SOURCES += [ + "perfprobe.cpp", + "PoisonIOInterposerBase.cpp", + "PoisonIOInterposerWin.cpp", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + UNIFIED_SOURCES += [ + "PoisonIOInterposerBase.cpp", + "PoisonIOInterposerMac.cpp", + ] + if CONFIG["CPU_ARCH"] != "aarch64": + SOURCES += ["mach_override.c"] + SOURCES["mach_override.c"].flags += ["-Wno-unused-function"] +else: + SOURCES += ["PoisonIOInterposerStub.cpp"] + +include("../glue/objs.mozbuild") + +XPCOM_MANIFESTS += [ + "components.conf", +] + +UNIFIED_SOURCES += xpcom_gluens_src_cppsrcs +UNIFIED_SOURCES += xpcom_glue_src_cppsrcs + +UNIFIED_SOURCES += [ + "FileLocation.cpp", + "IOInterposer.cpp", + "LateWriteChecks.cpp", + "MainThreadIOLogger.cpp", + "Omnijar.cpp", + "XPCOMInit.cpp", +] + +SOURCES += ["!Services.cpp"] + +if CONFIG["OS_ARCH"] != "WINNT": + SOURCES += [ + "NSPRInterposer.cpp", + ] + +GeneratedFile("Services.cpp", script="Services.py", entry_point="services_cpp") +GeneratedFile("Services.h", script="Services.py", entry_point="services_h") +GeneratedFile( + "GeckoProcessTypes.h", + script="gen_process_types.py", + entry_point="main", +) + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +DEFINES["_IMPL_NS_STRINGAPI"] = True +DEFINES["OMNIJAR_NAME"] = CONFIG["OMNIJAR_NAME"] + +LOCAL_INCLUDES += [ + "!..", + "../base", + "../components", + "../ds", + "../glue", + "../io", + "../threads", + "/chrome", + "/docshell/base", + "/js/xpconnect/loader", +] + +if CONFIG["MOZ_VPX"]: + LOCAL_INCLUDES += [ + "/media/libvpx", + ] diff --git a/xpcom/build/nsXPCOM.h b/xpcom/build/nsXPCOM.h new file mode 100644 index 0000000000..a000f7b39c --- /dev/null +++ b/xpcom/build/nsXPCOM.h @@ -0,0 +1,383 @@ +/* -*- 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 nsXPCOM_h__ +#define nsXPCOM_h__ + +#include "nscore.h" +#include "nsXPCOMCID.h" +#include "mozilla/Attributes.h" +#include "mozilla/Atomics.h" + +#ifdef __cplusplus +# define DECL_CLASS(c) class c +# define DECL_STRUCT(c) struct c +#else +# define DECL_CLASS(c) typedef struct c c +# define DECL_STRUCT(c) typedef struct c c +#endif + +DECL_CLASS(nsISupports); +DECL_CLASS(nsIComponentManager); +DECL_CLASS(nsIComponentRegistrar); +DECL_CLASS(nsIServiceManager); +DECL_CLASS(nsIFile); +DECL_CLASS(nsIDirectoryServiceProvider); +DECL_CLASS(nsIMemory); +DECL_CLASS(nsIDebug2); + +#ifdef __cplusplus +extern bool gXPCOMShuttingDown; +extern bool gXPCOMMainThreadEventsAreDoomed; +#endif + +#ifdef __cplusplus +# include "nsStringFwd.h" +#endif + +/** + * Initialises XPCOM. You must call one of the NS_InitXPCOM methods + * before proceeding to use xpcom. The one exception is that you may + * call NS_NewLocalFile to create a nsIFile. + * + * @note Use <CODE>NS_NewLocalFile</CODE> or <CODE>NS_NewNativeLocalFile</CODE> + * to create the file object you supply as the bin directory path in this + * call. The function may be safely called before the rest of XPCOM or + * embedding has been initialised. + * + * @param aResult The service manager. You may pass null. + * + * @param aBinDirectory The directory containing the component + * registry and runtime libraries; + * or use <CODE>nullptr</CODE> to use the working + * directory. + * + * @param aAppFileLocationProvider The object to be used by Gecko that + * specifies to Gecko where to find profiles, the + * component registry preferences and so on; or use + * <CODE>nullptr</CODE> for the default behaviour. + * + * @param aInitJSContext Whether the nsXPCJSContext should be initialized at + * this point. + * + * @see NS_NewLocalFile + * @see nsIFile + * @see nsIDirectoryServiceProvider + * + * @return NS_OK for success; + * NS_ERROR_NOT_INITIALIZED if static globals were not initialized, + * which can happen if XPCOM is reloaded, but did not completly + * shutdown. Other error codes indicate a failure during + * initialisation. + */ +XPCOM_API(nsresult) +NS_InitXPCOM(nsIServiceManager** aResult, nsIFile* aBinDirectory, + nsIDirectoryServiceProvider* aAppFileLocationProvider, + bool aInitJSContext = true); + +/** + * Initialize only minimal components of XPCOM. This ensures nsThreadManager, + * logging, and timers will work. + */ +XPCOM_API(nsresult) +NS_InitMinimalXPCOM(); + +/** + * Shutdown XPCOM. You must call this method after you are finished + * using xpcom. + * + * @param aServMgr The service manager which was returned by + * NS_InitXPCOM. This will release servMgr. You may pass null. + * + * @return NS_OK for success; + * other error codes indicate a failure during initialisation. + * + * MOZ_CAN_RUN_SCRIPT_BOUNDARY for now, but really it should maybe be + * MOZ_CAN_RUN_SCRIPT. + */ +XPCOM_API(MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult) +NS_ShutdownXPCOM(nsIServiceManager* aServMgr); + +/** + * Public Method to access to the service manager. + * + * @param aResult Interface pointer to the service manager + * + * @return NS_OK for success; + * other error codes indicate a failure during initialisation. + */ +XPCOM_API(nsresult) NS_GetServiceManager(nsIServiceManager** aResult); + +/** + * Public Method to access to the component manager. + * + * @param aResult Interface pointer to the service + * + * @return NS_OK for success; + * other error codes indicate a failure during initialisation. + */ +XPCOM_API(nsresult) NS_GetComponentManager(nsIComponentManager** aResult); + +/** + * Public Method to access to the component registration manager. + * + * @param aResult Interface pointer to the service + * + * @return NS_OK for success; + * other error codes indicate a failure during initialisation. + */ +XPCOM_API(nsresult) NS_GetComponentRegistrar(nsIComponentRegistrar** aResult); + +/** + * Public Method to create an instance of a nsIFile. This function + * may be called prior to NS_InitXPCOM. + * + * @param aPath + * A string which specifies a full file path to a + * location. Relative paths will be treated as an + * error (NS_ERROR_FILE_UNRECOGNIZED_PATH). + * |NS_NewNativeLocalFile|'s path must be in the + * filesystem charset. + * @param aFollowLinks + * This attribute will determine if the nsLocalFile will auto + * resolve symbolic links. By default, this value will be false + * on all non unix systems. On unix, this attribute is effectively + * a noop. + * @param aResult Interface pointer to a new instance of an nsIFile + * + * @return NS_OK for success; + * other error codes indicate a failure. + */ + +#ifdef __cplusplus + +XPCOM_API(nsresult) +NS_NewLocalFile(const nsAString& aPath, bool aFollowLinks, nsIFile** aResult); + +XPCOM_API(nsresult) +NS_NewNativeLocalFile(const nsACString& aPath, bool aFollowLinks, + nsIFile** aResult); + +// Use NS_NewLocalFile if you already have a UTF-16 string. +// Otherwise non-ASCII paths will break on some platforms +// including Windows. +class NS_ConvertUTF16toUTF8; +nsresult NS_NewNativeLocalFile(const NS_ConvertUTF16toUTF8& aPath, + bool aFollowLinks, nsIFile** aResult) = delete; + +#endif + +/** + * Support for warnings, assertions, and debugging breaks. + */ + +enum { + NS_DEBUG_WARNING = 0, + NS_DEBUG_ASSERTION = 1, + NS_DEBUG_BREAK = 2, + NS_DEBUG_ABORT = 3 +}; + +/** + * Print a runtime assertion. This function is available in both debug and + * release builds. + * + * @note Based on the value of aSeverity and the XPCOM_DEBUG_BREAK + * environment variable, this function may cause the application to + * print the warning, print a stacktrace, break into a debugger, or abort + * immediately. + * + * @param aSeverity A NS_DEBUG_* value + * @param aStr A readable error message (ASCII, may be null) + * @param aExpr The expression evaluated (may be null) + * @param aFile The source file containing the assertion (may be null) + * @param aLine The source file line number (-1 indicates no line number) + */ +XPCOM_API(void) +NS_DebugBreak(uint32_t aSeverity, const char* aStr, const char* aExpr, + const char* aFile, int32_t aLine); + +/** + * Perform a stack-walk to a debugging log under various + * circumstances. Used to aid debugging of leaked object graphs. + * + * The NS_Log* functions are available in both debug and release + * builds of XPCOM, but the output will be useless unless binary + * debugging symbols for all modules in the stacktrace are available. + */ + +/** + * By default, refcount logging is enabled at NS_InitXPCOM and + * refcount statistics are printed at NS_ShutdownXPCOM. NS_LogInit and + * NS_LogTerm allow applications to enable logging earlier and delay + * printing of logging statistics. They should always be used as a + * matched pair. + */ +XPCOM_API(void) NS_LogInit(); + +XPCOM_API(void) NS_LogTerm(); + +#ifdef __cplusplus +/** + * A helper class that calls NS_LogInit in its constructor and + * NS_LogTerm in its destructor. + */ + +class ScopedLogging { + public: + ScopedLogging() { NS_LogInit(); } + + ~ScopedLogging() { NS_LogTerm(); } +}; +#endif + +/** + * Log construction and destruction of objects. Processing tools can use the + * stacktraces printed by these functions to identify objects that are being + * leaked. + * + * @param aPtr A pointer to the concrete object. + * @param aTypeName The class name of the type + * @param aInstanceSize The size of the type + */ + +XPCOM_API(void) +NS_LogCtor(void* aPtr, const char* aTypeName, uint32_t aInstanceSize); + +XPCOM_API(void) +NS_LogDtor(void* aPtr, const char* aTypeName, uint32_t aInstanceSize); + +/** + * Log a stacktrace when an XPCOM object's refcount is incremented or + * decremented. Processing tools can use the stacktraces printed by these + * functions to identify objects that were leaked due to XPCOM references. + * + * @param aPtr A pointer to the concrete object + * @param aNewRefCnt The new reference count. + * @param aTypeName The class name of the type + * @param aInstanceSize The size of the type + */ +XPCOM_API(void) +NS_LogAddRef(void* aPtr, nsrefcnt aNewRefCnt, const char* aTypeName, + uint32_t aInstanceSize); + +XPCOM_API(void) +NS_LogRelease(void* aPtr, nsrefcnt aNewRefCnt, const char* aTypeName); + +/** + * Log reference counting performed by COMPtrs. Processing tools can + * use the stacktraces printed by these functions to simplify reports + * about leaked objects generated from the data printed by + * NS_LogAddRef/NS_LogRelease. + * + * @param aCOMPtr the address of the COMPtr holding a strong reference + * @param aObject the object being referenced by the COMPtr + */ + +XPCOM_API(void) NS_LogCOMPtrAddRef(void* aCOMPtr, nsISupports* aObject); + +XPCOM_API(void) NS_LogCOMPtrRelease(void* aCOMPtr, nsISupports* aObject); + +/** + * The XPCOM cycle collector analyzes and breaks reference cycles between + * participating XPCOM objects. All objects in the cycle must implement + * nsCycleCollectionParticipant to break cycles correctly. + */ + +#ifdef __cplusplus + +class nsCycleCollectionParticipant; +class nsCycleCollectingAutoRefCnt; + +XPCOM_API(void) +NS_CycleCollectorSuspect3(void* aPtr, nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt, + bool* aShouldDelete); + +XPCOM_API(void) +NS_CycleCollectorSuspectUsingNursery(void* aPtr, + nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt, + bool* aShouldDelete); + +#endif + +/** + * Categories (in the category manager service) used by XPCOM: + */ + +/** + * A category which is read after component registration but before + * the "xpcom-startup" notifications. Each category entry is treated + * as the contract ID of a service which implements + * nsIDirectoryServiceProvider. Each directory service provider is + * installed in the global directory service. + */ +#define XPCOM_DIRECTORY_PROVIDER_CATEGORY "xpcom-directory-providers" + +/** + * A category which is read after component registration but before + * NS_InitXPCOM returns. Each category entry is treated as the contractID of + * a service: each service is instantiated, and if it implements nsIObserver + * the nsIObserver.observe method is called with the "xpcom-startup" topic. + */ +#define NS_XPCOM_STARTUP_CATEGORY "xpcom-startup" + +/** + * Observer topics (in the observer service) used by XPCOM: + */ + +/** + * At XPCOM startup after component registration is complete, the + * following topic is notified. In order to receive this notification, + * component must register their contract ID in the category manager, + * + * @see NS_XPCOM_STARTUP_CATEGORY + */ +#define NS_XPCOM_STARTUP_OBSERVER_ID "xpcom-startup" + +/** + * At XPCOM shutdown, this topic is notified just before "xpcom-shutdown". + * Components should only use this to mark themselves as 'being destroyed'. + * Nothing should be dispatched to any event loop. + */ +#define NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID "xpcom-will-shutdown" + +/** + * At XPCOM shutdown, this topic is notified. All components must + * release any interface references to objects in other modules when + * this topic is notified. + */ +#define NS_XPCOM_SHUTDOWN_OBSERVER_ID "xpcom-shutdown" + +/** + * This topic is notified when an entry was added to a category in the + * category manager. The subject of the notification will be the name of + * the added entry as an nsISupportsCString, and the data will be the + * name of the category. The notification will occur on the main thread. + */ +#define NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID "xpcom-category-entry-added" + +/** + * This topic is notified when an entry was removed from a category in the + * category manager. The subject of the notification will be the name of + * the removed entry as an nsISupportsCString, and the data will be the + * name of the category. The notification will occur on the main thread. + */ +#define NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID \ + "xpcom-category-entry-removed" + +/** + * This topic is notified when an a category was cleared in the category + * manager. The subject of the notification will be the category manager, + * and the data will be the name of the cleared category. + * The notification will occur on the main thread. + */ +#define NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID "xpcom-category-cleared" + +XPCOM_API(nsresult) NS_GetDebug(nsIDebug2** aResult); + +#endif diff --git a/xpcom/build/nsXPCOMCID.h b/xpcom/build/nsXPCOMCID.h new file mode 100644 index 0000000000..a5df26717a --- /dev/null +++ b/xpcom/build/nsXPCOMCID.h @@ -0,0 +1,220 @@ +/* -*- 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 nsXPCOMCID_h__ +#define nsXPCOMCID_h__ + +/** + * XPCOM Directory Service Contract ID + * The directory service provides ways to obtain file system locations. The + * directory service is a singleton. + * + * This contract supports the nsIDirectoryService and the nsIProperties + * interfaces. + * + */ +#define NS_DIRECTORY_SERVICE_CONTRACTID "@mozilla.org/file/directory_service;1" + +/** + * XPCOM File + * The file abstraction provides ways to obtain and access files and + * directories located on the local system. + * + * This contract supports the nsIFile interface. + * This contract may also support platform specific interfaces such as + * nsILocalFileMac on platforms where additional interfaces are required. + * + */ +#define NS_LOCAL_FILE_CONTRACTID "@mozilla.org/file/local;1" + +/** + * XPCOM Category Manager Contract ID + * The contract supports the nsICategoryManager interface. The + * category manager is a singleton. + * The "enumerateCategory" method of nsICategoryManager will return an object + * that implements nsIUTF8StringEnumerator. In addition, the enumerator will + * return the entries in sorted order (sorted by byte comparison). + */ +#define NS_CATEGORYMANAGER_CONTRACTID "@mozilla.org/categorymanager;1" + +/** + * XPCOM Properties Object Contract ID + * Simple mapping object which supports the nsIProperties interface. + */ +#define NS_PROPERTIES_CONTRACTID "@mozilla.org/properties;1" + +/** + * XPCOM Array Object ContractID + * Simple array implementation which supports the nsIArray and + * nsIMutableArray interfaces. + */ +#define NS_ARRAY_CONTRACTID "@mozilla.org/array;1" + +/** + * Observer Service ContractID + * The observer service implements the global nsIObserverService object. + * It should be used from the main thread only. + */ +#define NS_OBSERVERSERVICE_CONTRACTID "@mozilla.org/observer-service;1" + +/** + * IO utilities service contract id. + * This guarantees implementation of nsIIOUtil. Usable from any thread. + */ +#define NS_IOUTIL_CONTRACTID "@mozilla.org/io-util;1" + +/** + * Memory reporter service CID + */ +#define NS_MEMORY_REPORTER_MANAGER_CONTRACTID \ + "@mozilla.org/memory-reporter-manager;1" + +/** + * Memory info dumper service CID + */ +#define NS_MEMORY_INFO_DUMPER_CONTRACTID "@mozilla.org/memory-info-dumper;1" + +/** + * nsMessageLoop contract id + */ +#define NS_MESSAGE_LOOP_CONTRACTID "@mozilla.org/message-loop;1" + +#define NS_COMPARTMENT_INFO_CONTRACTID "@mozilla.org/compartment-info;1" + +/** + * The following are the CIDs and Contract IDs of the nsISupports wrappers for + * primative types. + */ +#define NS_SUPPORTS_ID_CID \ + { \ + 0xacf8dc40, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_ID_CONTRACTID "@mozilla.org/supports-id;1" + +#define NS_SUPPORTS_CSTRING_CID \ + { \ + 0xacf8dc41, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_CSTRING_CONTRACTID "@mozilla.org/supports-cstring;1" + +#define NS_SUPPORTS_STRING_CID \ + { \ + 0xacf8dc42, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_STRING_CONTRACTID "@mozilla.org/supports-string;1" + +#define NS_SUPPORTS_PRBOOL_CID \ + { \ + 0xacf8dc43, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRBOOL_CONTRACTID "@mozilla.org/supports-PRBool;1" + +#define NS_SUPPORTS_PRUINT8_CID \ + { \ + 0xacf8dc44, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRUINT8_CONTRACTID "@mozilla.org/supports-PRUint8;1" + +#define NS_SUPPORTS_PRUINT16_CID \ + { \ + 0xacf8dc46, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRUINT16_CONTRACTID "@mozilla.org/supports-PRUint16;1" + +#define NS_SUPPORTS_PRUINT32_CID \ + { \ + 0xacf8dc47, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRUINT32_CONTRACTID "@mozilla.org/supports-PRUint32;1" + +#define NS_SUPPORTS_PRUINT64_CID \ + { \ + 0xacf8dc48, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRUINT64_CONTRACTID "@mozilla.org/supports-PRUint64;1" + +#define NS_SUPPORTS_PRTIME_CID \ + { \ + 0xacf8dc49, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRTIME_CONTRACTID "@mozilla.org/supports-PRTime;1" + +#define NS_SUPPORTS_CHAR_CID \ + { \ + 0xacf8dc4a, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_CHAR_CONTRACTID "@mozilla.org/supports-char;1" + +#define NS_SUPPORTS_PRINT16_CID \ + { \ + 0xacf8dc4b, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRINT16_CONTRACTID "@mozilla.org/supports-PRInt16;1" + +#define NS_SUPPORTS_PRINT32_CID \ + { \ + 0xacf8dc4c, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRINT32_CONTRACTID "@mozilla.org/supports-PRInt32;1" + +#define NS_SUPPORTS_PRINT64_CID \ + { \ + 0xacf8dc4d, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRINT64_CONTRACTID "@mozilla.org/supports-PRInt64;1" + +#define NS_SUPPORTS_FLOAT_CID \ + { \ + 0xcbf86870, 0x4ac0, 0x11d3, { \ + 0xba, 0xea, 0x0, 0x80, 0x5f, 0x8a, 0x5d, 0xd7 \ + } \ + } +#define NS_SUPPORTS_FLOAT_CONTRACTID "@mozilla.org/supports-float;1" + +#define NS_SUPPORTS_DOUBLE_CID \ + { \ + 0xcbf86871, 0x4ac0, 0x11d3, { \ + 0xba, 0xea, 0x0, 0x80, 0x5f, 0x8a, 0x5d, 0xd7 \ + } \ + } +#define NS_SUPPORTS_DOUBLE_CONTRACTID "@mozilla.org/supports-double;1" + +#define NS_SUPPORTS_INTERFACE_POINTER_CID \ + { \ + 0xA99FEBBA, 0x1DD1, 0x11B2, { \ + 0xA9, 0x43, 0xB0, 0x23, 0x34, 0xA6, 0xD0, 0x83 \ + } \ + } +#define NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID \ + "@mozilla.org/supports-interface-pointer;1" + +#endif diff --git a/xpcom/build/nsXPCOMCIDInternal.h b/xpcom/build/nsXPCOMCIDInternal.h new file mode 100644 index 0000000000..d07fcb1c4d --- /dev/null +++ b/xpcom/build/nsXPCOMCIDInternal.h @@ -0,0 +1,49 @@ +/* -*- 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 nsXPCOMCIDInternal_h__ +#define nsXPCOMCIDInternal_h__ + +#include "nsXPCOMCID.h" + +/** + * A hashtable-based property bag component. + * @implements nsIWritablePropertyBag, nsIWritablePropertyBag2 + */ +#define NS_HASH_PROPERTY_BAG_CID \ + { \ + 0x678c50b8, 0x6bcb, 0x4ad0, { \ + 0xb9, 0xb8, 0xc8, 0x11, 0x75, 0x95, 0x51, 0x99 \ + } \ + } +#define NS_HASH_PROPERTY_BAG_CONTRACTID "@mozilla.org/hash-property-bag;1" + +/** + * Factory for creating nsIUnicharInputStream + * @implements nsIUnicharInputStreamFactory + * @note nsIUnicharInputStream instances cannot be created via + * createInstance. Code must use one of the custom factory methods. + */ +#define NS_SIMPLE_UNICHAR_STREAM_FACTORY_CONTRACTID \ + "@mozilla.org/xpcom/simple-unichar-stream-factory;1" + +/** + * The global thread manager service. This component is a singleton. + * @implements nsIThreadManager + */ +#define NS_THREADMANAGER_CONTRACTID "@mozilla.org/thread-manager;1" + +/** + * The contract id for the nsIXULAppInfo service. + */ +#define XULAPPINFO_SERVICE_CONTRACTID "@mozilla.org/xre/app-info;1" + +/** + * The contract id for the nsIXULRuntime service. + */ +#define XULRUNTIME_SERVICE_CONTRACTID "@mozilla.org/xre/runtime;1" + +#endif // nsXPCOMCIDInternal_h__ diff --git a/xpcom/build/nsXPCOMPrivate.h b/xpcom/build/nsXPCOMPrivate.h new file mode 100644 index 0000000000..49f36c709c --- /dev/null +++ b/xpcom/build/nsXPCOMPrivate.h @@ -0,0 +1,126 @@ +/* -*- 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 nsXPCOMPrivate_h__ +#define nsXPCOMPrivate_h__ + +#include "nscore.h" +#include "nsXPCOM.h" +#include "mozilla/Attributes.h" + +/** + * During this shutdown notification all threads which run XPCOM code must + * be joined. + */ +#define NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID "xpcom-shutdown-threads" + +// PUBLIC +namespace mozilla { + +/** + * Shutdown XPCOM. You must call this method after you are finished + * using xpcom. + * + * @param aServMgr The service manager which was returned by + * NS_InitXPCOM. This will release servMgr. You may pass null. + * + * @return NS_OK for success; + * other error codes indicate a failure during shutdown + * + */ +MOZ_CAN_RUN_SCRIPT +nsresult ShutdownXPCOM(nsIServiceManager* aServMgr); + +void SetICUMemoryFunctions(); + +/** + * C++ namespaced version of NS_LogTerm. + */ +void LogTerm(); + +} // namespace mozilla + +/* XPCOM Specific Defines + * + * XPCOM_DLL - name of the loadable xpcom library on disk. + * XUL_DLL - name of the loadable XUL library on disk + * XPCOM_SEARCH_KEY - name of the environment variable that can be + * modified to include additional search paths. + * GRE_CONF_NAME - Name of the GRE Configuration file + */ + +#if defined(XP_WIN) + +# define XPCOM_SEARCH_KEY "PATH" +# define GRE_CONF_NAME "gre.config" +# define GRE_WIN_REG_LOC L"Software\\mozilla.org\\GRE" +# define XPCOM_DLL XUL_DLL +# define LXPCOM_DLL LXUL_DLL +# define XUL_DLL "xul.dll" +# define LXUL_DLL L"xul.dll" + +#else // Unix +# include <limits.h> // for PATH_MAX + +# define XPCOM_DLL XUL_DLL + +// you have to love apple.. +# ifdef XP_MACOSX +# define XPCOM_SEARCH_KEY "DYLD_LIBRARY_PATH" +# define GRE_FRAMEWORK_NAME "XUL.framework" +# define XUL_DLL "XUL" +# else +# define XPCOM_SEARCH_KEY "LD_LIBRARY_PATH" +# define XUL_DLL "libxul" MOZ_DLL_SUFFIX +# endif + +# define GRE_CONF_NAME ".gre.config" +# define GRE_CONF_PATH "/etc/gre.conf" +# define GRE_CONF_DIR "/etc/gre.d" +# define GRE_USER_CONF_DIR ".gre.d" +#endif + +#if defined(XP_WIN) +# define XPCOM_FILE_PATH_SEPARATOR "\\" +# define XPCOM_ENV_PATH_SEPARATOR ";" +#elif defined(XP_UNIX) +# define XPCOM_FILE_PATH_SEPARATOR "/" +# define XPCOM_ENV_PATH_SEPARATOR ":" +#else +# error need_to_define_your_file_path_separator_and_illegal_characters +#endif + +#ifdef AIX +# include <sys/param.h> +#endif + +#ifndef MAXPATHLEN +# ifdef PATH_MAX +# define MAXPATHLEN PATH_MAX +# elif defined(_MAX_PATH) +# define MAXPATHLEN _MAX_PATH +# elif defined(CCHMAXPATH) +# define MAXPATHLEN CCHMAXPATH +# else +# define MAXPATHLEN 1024 +# endif +#endif + +// Needed by the IPC layer from off the main thread +extern char16_t* gGREBinPath; + +namespace mozilla { +namespace services { + +/** + * Clears service cache, sets gXPCOMShuttingDown + */ +void Shutdown(); + +} // namespace services +} // namespace mozilla + +#endif diff --git a/xpcom/build/nsXULAppAPI.h b/xpcom/build/nsXULAppAPI.h new file mode 100644 index 0000000000..c3fec696fd --- /dev/null +++ b/xpcom/build/nsXULAppAPI.h @@ -0,0 +1,387 @@ +/* -*- 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 _nsXULAppAPI_h__ +#define _nsXULAppAPI_h__ + +#include "js/TypeDecls.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/ProcessType.h" +#include "mozilla/TimeStamp.h" +#include "nscore.h" + +#if defined(MOZ_WIDGET_ANDROID) +# include <jni.h> +#endif + +class JSString; +class MessageLoop; +class nsIDirectoryServiceProvider; +class nsIFile; +class nsISupports; +struct JSContext; +struct XREChildData; +struct XREShellData; + +namespace mozilla { +class XREAppData; +struct BootstrapConfig; +} // namespace mozilla + +/** + * A directory service key which provides the platform-correct "application + * data" directory as follows, where $name and $vendor are as defined above and + * $vendor is optional: + * + * Windows: + * HOME = Documents and Settings\$USER\Application Data + * UAppData = $HOME[\$vendor]\$name + * + * Unix: + * HOME = ~ + * UAppData = $HOME/.[$vendor/]$name + * + * Mac: + * HOME = ~ + * UAppData = $HOME/Library/Application Support/$name + * + * Note that the "profile" member above will change the value of UAppData as + * follows: + * + * Windows: + * UAppData = $HOME\$profile + * + * Unix: + * UAppData = $HOME/.$profile + * + * Mac: + * UAppData = $HOME/Library/Application Support/$profile + */ +#define XRE_USER_APP_DATA_DIR "UAppData" + +/** + * A directory service key which provides the executable file used to + * launch the current process. This is the same value returned by the + * XRE_GetBinaryPath function defined below. + */ +#define XRE_EXECUTABLE_FILE "XREExeF" + +/** + * A directory service key which specifies the profile + * directory. Unlike the NS_APP_USER_PROFILE_50_DIR key, this key may + * be available when the profile hasn't been "started", or after is + * has been shut down. If the application is running without a + * profile, such as when showing the profile manager UI, this key will + * not be available. This key is provided by the XUL apprunner or by + * the aAppDirProvider object passed to XRE_InitEmbedding. + */ +#define NS_APP_PROFILE_DIR_STARTUP "ProfDS" + +/** + * A directory service key which specifies the profile + * directory. Unlike the NS_APP_USER_PROFILE_LOCAL_50_DIR key, this key may + * be available when the profile hasn't been "started", or after is + * has been shut down. If the application is running without a + * profile, such as when showing the profile manager UI, this key will + * not be available. This key is provided by the XUL apprunner or by + * the aAppDirProvider object passed to XRE_InitEmbedding. + */ +#define NS_APP_PROFILE_LOCAL_DIR_STARTUP "ProfLDS" + +/** + * A directory service key which specifies the system extension + * parent directory containing platform-specific extensions. + * This key may not be available on all platforms. + */ +#define XRE_SYS_LOCAL_EXTENSION_PARENT_DIR "XRESysLExtPD" + +/** + * A directory service key which specifies the system extension + * parent directory containing platform-independent extensions. + * This key may not be available on all platforms. + * Additionally, the directory may be equal to that returned by + * XRE_SYS_LOCAL_EXTENSION_PARENT_DIR on some platforms. + */ +#define XRE_SYS_SHARE_EXTENSION_PARENT_DIR "XRESysSExtPD" + +#if defined(XP_UNIX) || defined(XP_MACOSX) +/** + * Directory service keys for the system-wide and user-specific + * directories where native manifests used by the WebExtensions + * native messaging and managed storage features are found. + */ +# define XRE_SYS_NATIVE_MANIFESTS "XRESysNativeManifests" +# define XRE_USER_NATIVE_MANIFESTS "XREUserNativeManifests" +#endif + +/** + * A directory service key which specifies the user system extension + * parent directory. + */ +#define XRE_USER_SYS_EXTENSION_DIR "XREUSysExt" + +/** + * A directory service key which specifies the distribution specific files for + * the application. + */ +#define XRE_APP_DISTRIBUTION_DIR "XREAppDist" + +/** + * A directory service key which specifies the location for system add-ons. + */ +#define XRE_APP_FEATURES_DIR "XREAppFeat" + +/** + * A directory service key which specifies the location for app dir add-ons. + * Should be a synonym for XCurProcD everywhere except in tests. + */ +#define XRE_ADDON_APP_DIR "XREAddonAppDir" + +/** + * A directory service key which specifies the distribution specific files for + * the application unique for each user. + * It's located at /run/user/$UID/<product name>/ + */ +#define XRE_USER_RUNTIME_DIR "XREUserRunTimeDir" + +/** + * A directory service key which provides the update directory. Callers should + * fall back to appDir. + * Windows: If vendor name exists: + * ProgramData\<vendor name>\updates\ + * <hash of the path to XRE_EXECUTABLE_FILE's parent directory> + * + * If vendor name doesn't exist, but product name exists: + * ProgramData\<product name>\updates\ + * <hash of the path to XRE_EXECUTABLE_FILE's parent directory> + * + * If neither vendor nor product name exists: + * ProgramData\Mozilla\updates + * + * Mac: ~/Library/Caches/Mozilla/updates/<absolute path to app dir> + * + * All others: Parent directory of XRE_EXECUTABLE_FILE. + */ +#define XRE_UPDATE_ROOT_DIR "UpdRootD" + +/** + * A directory service key which provides the *old* update directory. This + * path should only be used when data needs to be migrated from the old update + * directory. + * Windows: If vendor name exists: + * Documents and Settings\<User>\Local Settings\Application Data\ + * <vendor name>\updates\ + * <hash of the path to XRE_EXECUTABLE_FILE's parent directory> + * + * If vendor name doesn't exist, but product name exists: + * Documents and Settings\<User>\Local Settings\Application Data\ + * <product name>\updates\ + * <hash of the path to XRE_EXECUTABLE_FILE's parent directory> + * + * If neither vendor nor product name exists: + * Documents and Settings\<User>\Local Settings\Application Data\ + * Mozilla\updates + * + * This path does not exist on other operating systems + */ +#define XRE_OLD_UPDATE_ROOT_DIR "OldUpdRootD" + +/** + * Begin an XUL application. Does not return until the user exits the + * application. + * + * @param argc/argv Command-line parameters to pass to the application. On + * Windows, these should be in UTF8. On unix-like platforms + * these are in the "native" character set. + * + * @param aConfig Information about the application to be run. + * + * @return A native result code suitable for returning from main(). + * + * @note If the binary is linked against the standalone XPCOM glue, + * XPCOMGlueStartup() should be called before this method. + */ +int XRE_main(int argc, char* argv[], const mozilla::BootstrapConfig& aConfig); + +/** + * Given a path relative to the current working directory (or an absolute + * path), return an appropriate nsIFile object. + * + * @note Pass UTF8 strings on Windows... native charset on other platforms. + */ +nsresult XRE_GetFileFromPath(const char* aPath, nsIFile** aResult); + +/** + * Get the path of the running application binary and store it in aResult. + */ +nsresult XRE_GetBinaryPath(nsIFile** aResult); + +/** + * Register XPCOM components found in an array of files/directories. + * This method may be called at any time before or after XRE_main or + * XRE_InitEmbedding. + * + * @param aFiles An array of files or directories. + * @param aFileCount the number of items in the aFiles array. + * @note appdir/components is registered automatically. + * + * NS_APP_LOCATION specifies a location to search for binary XPCOM + * components as well as component/chrome manifest files. + * + * NS_EXTENSION_LOCATION excludes binary XPCOM components but allows other + * manifest instructions. + * + * NS_SKIN_LOCATION specifies a location to search for chrome manifest files + * which are only allowed to register skin packages. + */ +enum NSLocationType { + NS_APP_LOCATION, + NS_EXTENSION_LOCATION, + NS_SKIN_LOCATION, + NS_BOOTSTRAPPED_LOCATION +}; + +nsresult XRE_AddManifestLocation(NSLocationType aType, nsIFile* aLocation); + +/** + * Register XPCOM components found in a JAR. + * This is similar to XRE_AddManifestLocation except the file specified + * must be a zip archive with a manifest named chrome.manifest + * This method may be called at any time before or after XRE_main or + * XRE_InitEmbedding. + * + * @param aFiles An array of files or directories. + * @param aFileCount the number of items in the aFiles array. + * @note appdir/components is registered automatically. + * + * NS_COMPONENT_LOCATION specifies a location to search for binary XPCOM + * components as well as component/chrome manifest files. + * + * NS_SKIN_LOCATION specifies a location to search for chrome manifest files + * which are only allowed to register skin packages. + */ +nsresult XRE_AddJarManifestLocation(NSLocationType aType, nsIFile* aLocation); + +/** + * Parse an INI file (application.ini or override.ini) into an existing + * nsXREAppData structure. + * + * @param aINIFile The INI file to parse + * @param aAppData The nsXREAppData structure to fill. + */ +nsresult XRE_ParseAppData(nsIFile* aINIFile, mozilla::XREAppData& aAppData); + +const char* XRE_GeckoProcessTypeToString(GeckoProcessType aProcessType); +const char* XRE_ChildProcessTypeToAnnotation(GeckoProcessType aProcessType); + +#if defined(MOZ_WIDGET_ANDROID) +struct XRE_AndroidChildFds { + int mPrefsFd; + int mPrefMapFd; + int mIpcFd; + int mCrashFd; + int mCrashAnnotationFd; +}; + +void XRE_SetAndroidChildFds(JNIEnv* env, const XRE_AndroidChildFds& fds); +#endif // defined(MOZ_WIDGET_ANDROID) + +void XRE_SetProcessType(const char* aProcessTypeString); + +nsresult XRE_InitChildProcess(int aArgc, char* aArgv[], + const XREChildData* aChildData); + +/** + * Return the GeckoProcessType of the current process. + */ +GeckoProcessType XRE_GetProcessType(); + +/** + * Return the string representation of the GeckoProcessType of the current + * process. + */ +const char* XRE_GetProcessTypeString(); + +/** + * Returns true when called in the e10s parent process. Does *NOT* return true + * when called in the main process if e10s is disabled. + */ +bool XRE_IsE10sParentProcess(); + +/** + * Defines XRE_IsParentProcess, XRE_IsContentProcess, etc. + * + * XRE_IsParentProcess is unique in that it returns true when called in + * the e10s parent process or called in the main process when e10s is + * disabled. + */ +#define GECKO_PROCESS_TYPE(enum_value, enum_name, string_name, proc_typename, \ + process_bin_type, procinfo_typename, \ + webidl_typename, allcaps_name) \ + bool XRE_Is##proc_typename##Process(); +#include "mozilla/GeckoProcessTypes.h" +#undef GECKO_PROCESS_TYPE + +bool XRE_IsSocketProcess(); + +/** + * Returns true if the appshell should run its own native event loop. Returns + * false if we should rely solely on the Gecko event loop. + */ +bool XRE_UseNativeEventProcessing(); + +typedef void (*MainFunction)(void* aData); + +int XRE_RunIPDLTest(int aArgc, char* aArgv[]); + +nsresult XRE_RunAppShell(); + +nsresult XRE_InitCommandLine(int aArgc, char* aArgv[]); + +nsresult XRE_DeinitCommandLine(); + +void XRE_ShutdownChildProcess(); + +MessageLoop* XRE_GetIOMessageLoop(); + +bool XRE_SendTestShellCommand(JSContext* aCx, JSString* aCommand, + JS::Value* aCallback); +bool XRE_ShutdownTestShell(); + +void XRE_InstallX11ErrorHandler(); +void XRE_CleanupX11ErrorHandler(); + +void XRE_TelemetryAccumulate(int aID, uint32_t aSample); + +void XRE_StartupTimelineRecord(int aEvent, mozilla::TimeStamp aWhen); + +void XRE_InitOmnijar(nsIFile* aGreOmni, nsIFile* aAppOmni); +void XRE_StopLateWriteChecks(void); + +void XRE_EnableSameExecutableForContentProc(); + +namespace mozilla { +enum class BinPathType { Self, PluginContainer }; +} +mozilla::BinPathType XRE_GetChildProcBinPathType(GeckoProcessType aProcessType); + +int XRE_XPCShellMain(int argc, char** argv, char** envp, + const XREShellData* aShellData); + +#ifdef LIBFUZZER +# include "FuzzerRegistry.h" + +void XRE_LibFuzzerSetDriver(LibFuzzerDriver); + +#endif // LIBFUZZER + +#ifdef MOZ_ENABLE_FORKSERVER + +int XRE_ForkServer(int* aArgc, char*** aArgv); + +#endif // MOZ_ENABLE_FORKSERVER + +#endif // _nsXULAppAPI_h__ diff --git a/xpcom/build/perfprobe.cpp b/xpcom/build/perfprobe.cpp new file mode 100644 index 0000000000..d5a82be59d --- /dev/null +++ b/xpcom/build/perfprobe.cpp @@ -0,0 +1,215 @@ +/* -*- 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/. */ + +/***************************** + Windows implementation of probes, using xperf + *****************************/ +#include <windows.h> +#include <wmistr.h> +#include <evntrace.h> + +#include "perfprobe.h" + +namespace mozilla { +namespace probes { + +#if defined(MOZ_LOGGING) +static LazyLogModule sProbeLog("SysProbe"); +# define LOG(x) MOZ_LOG(sProbeLog, mozilla::LogLevel::Debug, x) +#else +# define LOG(x) +#endif + +// Utility function +GUID CID_to_GUID(const nsCID& aCID) { + GUID result; + result.Data1 = aCID.m0; + result.Data2 = aCID.m1; + result.Data3 = aCID.m2; + for (int i = 0; i < 8; ++i) { + result.Data4[i] = aCID.m3[i]; + } + return result; +} + +// Implementation of Probe + +Probe::Probe(const nsCID& aGUID, const nsACString& aName, + ProbeManager* aManager) + : mGUID(CID_to_GUID(aGUID)), mName(aName), mManager(aManager) {} + +nsresult Probe::Trigger() { + if (!(mManager->mIsActive)) { + // Do not trigger if there is no session + return NS_OK; + } + + _EVENT_TRACE_HEADER event; + ZeroMemory(&event, sizeof(event)); + event.Size = sizeof(event); + event.Flags = WNODE_FLAG_TRACED_GUID; + event.Guid = (const GUID)mGUID; + event.Class.Type = 1; + event.Class.Version = 0; + event.Class.Level = TRACE_LEVEL_INFORMATION; + + ULONG result = TraceEvent(mManager->mSessionHandle, &event); + + LOG(("Probes: Triggered %s, %s, %ld", mName.Data(), + result == ERROR_SUCCESS ? "success" : "failure", result)); + + nsresult rv; + switch (result) { + case ERROR_SUCCESS: + rv = NS_OK; + break; + case ERROR_INVALID_FLAG_NUMBER: + case ERROR_MORE_DATA: + case ERROR_INVALID_PARAMETER: + rv = NS_ERROR_INVALID_ARG; + break; + case ERROR_INVALID_HANDLE: + rv = NS_ERROR_FAILURE; + break; + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_OUTOFMEMORY: + rv = NS_ERROR_OUT_OF_MEMORY; + break; + default: + rv = NS_ERROR_UNEXPECTED; + } + return rv; +} + +// Implementation of ProbeManager + +ProbeManager::~ProbeManager() { + // If the manager goes out of scope, stop the session. + if (mIsActive && mRegistrationHandle) { + StopSession(); + } +} + +ProbeManager::ProbeManager(const nsCID& aApplicationUID, + const nsACString& aApplicationName) + : mIsActive(false), + mApplicationUID(aApplicationUID), + mApplicationName(aApplicationName), + mSessionHandle(0), + mRegistrationHandle(0), + mInitialized(false) { +#if defined(MOZ_LOGGING) + char cidStr[NSID_LENGTH]; + aApplicationUID.ToProvidedString(cidStr); + LOG(("ProbeManager::Init for application %s, %s", aApplicationName.Data(), + cidStr)); +#endif +} + +// Note: The Windows API is just a little bit scary there. +// The only way to obtain the session handle is to +//- ignore the session handle obtained from RegisterTraceGuids +//- pass a callback +//- in that callback, request the session handle through +// GetTraceLoggerHandle and some opaque value received by the callback + +ULONG WINAPI ControlCallback(WMIDPREQUESTCODE aRequestCode, PVOID aContext, + ULONG* aReserved, PVOID aBuffer) { + ProbeManager* context = (ProbeManager*)aContext; + switch (aRequestCode) { + case WMI_ENABLE_EVENTS: { + context->mIsActive = true; + TRACEHANDLE sessionHandle = GetTraceLoggerHandle(aBuffer); + // Note: We only accept one handle + if ((HANDLE)sessionHandle == INVALID_HANDLE_VALUE) { + ULONG result = GetLastError(); + LOG(("Probes: ControlCallback failed, %lu", result)); + return result; + } else if (context->mIsActive && context->mSessionHandle && + context->mSessionHandle != sessionHandle) { + LOG( + ("Probes: Can only handle one context at a time, " + "ignoring activation")); + return ERROR_SUCCESS; + } else { + context->mSessionHandle = sessionHandle; + LOG(("Probes: ControlCallback activated")); + return ERROR_SUCCESS; + } + } + + case WMI_DISABLE_EVENTS: + context->mIsActive = false; + context->mSessionHandle = 0; + LOG(("Probes: ControlCallback deactivated")); + return ERROR_SUCCESS; + + default: + LOG(("Probes: ControlCallback does not know what to do with %d", + aRequestCode)); + return ERROR_INVALID_PARAMETER; + } +} + +already_AddRefed<Probe> ProbeManager::GetProbe(const nsCID& aEventUID, + const nsACString& aEventName) { + RefPtr<Probe> result(new Probe(aEventUID, aEventName, this)); + mAllProbes.AppendElement(result); + return result.forget(); +} + +nsresult ProbeManager::StartSession() { return StartSession(mAllProbes); } + +nsresult ProbeManager::StartSession(nsTArray<RefPtr<Probe>>& aProbes) { + const size_t probesCount = aProbes.Length(); + _TRACE_GUID_REGISTRATION* probes = new _TRACE_GUID_REGISTRATION[probesCount]; + for (unsigned int i = 0; i < probesCount; ++i) { + const Probe* probe = aProbes[i]; + const Probe* probeX = static_cast<const Probe*>(probe); + probes[i].Guid = (LPCGUID)&probeX->mGUID; + } + ULONG result = + RegisterTraceGuids(&ControlCallback + /*RequestAddress: Sets mSessions appropriately.*/, + this + /*RequestContext: Passed to ControlCallback*/, + (LPGUID)&mApplicationUID + /*ControlGuid: Tracing GUID + the cast comes from MSDN examples*/ + , + probesCount + /*GuidCount: Number of probes*/, + probes + /*TraceGuidReg: Probes registration*/, + nullptr + /*MofImagePath: Must be nullptr, says MSDN*/, + nullptr + /*MofResourceName:Must be nullptr, says MSDN*/, + &mRegistrationHandle + /*RegistrationHandle: Handler. + used only for unregistration*/ + ); + delete[] probes; + if (NS_WARN_IF(result != ERROR_SUCCESS)) { + return NS_ERROR_UNEXPECTED; + } + return NS_OK; +} + +nsresult ProbeManager::StopSession() { + LOG(("Probes: Stopping measures")); + if (mSessionHandle != 0) { + ULONG result = UnregisterTraceGuids(mSessionHandle); + mSessionHandle = 0; + if (result != ERROR_SUCCESS) { + return NS_ERROR_INVALID_ARG; + } + } + return NS_OK; +} + +} // namespace probes +} // namespace mozilla diff --git a/xpcom/build/perfprobe.h b/xpcom/build/perfprobe.h new file mode 100644 index 0000000000..e5a0382322 --- /dev/null +++ b/xpcom/build/perfprobe.h @@ -0,0 +1,198 @@ +/* -*- 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/. */ + +/** + * A mechanism for interacting with operating system-provided + * debugging/profiling tools such as Microsoft EWT/Windows Performance Toolkit. + */ + +#ifndef mozilla_perfprobe_h +#define mozilla_perfprobe_h + +#if !defined(XP_WIN) +# error "For the moment, perfprobe.h is defined only for Windows platforms" +#endif + +#include "nsError.h" +#include "nsString.h" +#include "mozilla/Logging.h" +#include "nsTArray.h" +#include <windows.h> +#undef GetStartupInfo // Prevent Windows from polluting global namespace +#include <wmistr.h> +#include <evntrace.h> + +namespace mozilla { +namespace probes { + +class ProbeManager; + +/** + * A data structure supporting a trigger operation that can be used to + * send information to the operating system. + */ + +class Probe { + public: + NS_INLINE_DECL_REFCOUNTING(Probe) + + /** + * Trigger the event. + * + * Note: Can be called from any thread. + */ + nsresult Trigger(); + + protected: + ~Probe(){}; + + Probe(const nsCID& aGUID, const nsACString& aName, ProbeManager* aManager); + friend class ProbeManager; + + protected: + /** + * The system GUID associated to this probe. See the documentation + * of |ProbeManager::Make| for more details. + */ + const GUID mGUID; + + /** + * The name of this probe. See the documentation + * of |ProbeManager::Make| for more details. + */ + const nsCString mName; + + /** + * The ProbeManager managing this probe. + * + * Note: This is a weak reference to avoid a useless cycle. + */ + class ProbeManager* mManager; +}; + +/** + * A manager for a group of probes. + * + * You can have several managers in one application, provided that they all + * have distinct IDs and names. However, having more than 2 is considered a bad + * practice. + */ +class ProbeManager { + public: + NS_INLINE_DECL_REFCOUNTING(ProbeManager) + + /** + * Create a new probe manager. + * + * This constructor should be called from the main thread. + * + * @param aApplicationUID The unique ID of the probe. Under Windows, this + * unique ID must have been previously registered using an external tool. + * See MyCategory on http://msdn.microsoft.com/en-us/library/aa364100.aspx + * @param aApplicationName A name for the probe. Currently used only for + * logging purposes. In the future, may be attached to the data sent to the + * operating system. + * + * Note: If two ProbeManagers are constructed with the same uid and/or name, + * behavior is unspecified. + */ + ProbeManager(const nsCID& aApplicationUID, + const nsACString& aApplicationName); + + /** + * Acquire a probe. + * + * Note: Only probes acquired before the call to SetReady are taken into + * account + * Note: Can be called only from the main thread. + * + * @param aEventUID The unique ID of the probe. Under Windows, this unique + * ID must have been previously registered using an external tool. + * See MyCategory on http://msdn.microsoft.com/en-us/library/aa364100.aspx + * @param aEventName A name for the probe. Currently used only for logging + * purposes. In the + * future, may be attached to the data sent to the operating system. + * @return Either |null| in case of error or a valid |Probe*|. + * + * Note: If this method is called twice with the same uid and/or name, + * behavior is undefined. + */ + already_AddRefed<Probe> GetProbe(const nsCID& aEventUID, + const nsACString& aEventName); + + /** + * Start/stop the measuring session. + * + * This method should be called from the main thread. + * + * Note that starting an already started probe manager has no effect, + * nor does stopping an already stopped probe manager. + */ + nsresult StartSession(); + nsresult StopSession(); + + /** + * @return true If measures are currently on, i.e. if triggering probes is any + * is useful. You do not have to check this before triggering a probe, unless + * this can avoid complex computations. + */ + bool IsActive(); + + protected: + ~ProbeManager(); + + nsresult StartSession(nsTArray<RefPtr<Probe>>& aProbes); + nsresult Init(const nsCID& aApplicationUID, + const nsACString& aApplicationName); + + protected: + /** + * `true` if a session is in activity, `false` otherwise. + */ + bool mIsActive; + + /** + * The UID of this manager. + * See documentation above for registration steps that you + * may have to take. + */ + nsCID mApplicationUID; + + /** + * The name of the application. + */ + nsCString mApplicationName; + + /** + * All the probes that have been created for this manager. + */ + nsTArray<RefPtr<Probe>> mAllProbes; + + /** + * Handle used for triggering events + */ + TRACEHANDLE mSessionHandle; + + /** + * Handle used for registration/unregistration + */ + TRACEHANDLE mRegistrationHandle; + + /** + * `true` if initialization has been performed, `false` until then. + */ + bool mInitialized; + + friend class Probe; // Needs to access |mSessionHandle| + friend ULONG WINAPI ControlCallback(WMIDPREQUESTCODE aRequestCode, + PVOID aContext, ULONG* aReserved, + PVOID aBuffer); // Sets |mSessionHandle| +}; + +} // namespace probes +} // namespace mozilla + +#endif // mozilla_perfprobe_h diff --git a/xpcom/build/xpcom_alpha.def b/xpcom/build/xpcom_alpha.def new file mode 100644 index 0000000000..38fedfa17f --- /dev/null +++ b/xpcom/build/xpcom_alpha.def @@ -0,0 +1,256 @@ +;+# 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/. + +LIBRARY xpcom +DESCRIPTION "xpcom library" + +EXPORTS + ?Stub3@nsXPTCStubBase@@UAAIXZ + ?Stub4@nsXPTCStubBase@@UAAIXZ + ?Stub5@nsXPTCStubBase@@UAAIXZ + ?Stub6@nsXPTCStubBase@@UAAIXZ + ?Stub7@nsXPTCStubBase@@UAAIXZ + ?Stub8@nsXPTCStubBase@@UAAIXZ + ?Stub9@nsXPTCStubBase@@UAAIXZ + ?Stub10@nsXPTCStubBase@@UAAIXZ + ?Stub11@nsXPTCStubBase@@UAAIXZ + ?Stub12@nsXPTCStubBase@@UAAIXZ + ?Stub13@nsXPTCStubBase@@UAAIXZ + ?Stub14@nsXPTCStubBase@@UAAIXZ + ?Stub15@nsXPTCStubBase@@UAAIXZ + ?Stub16@nsXPTCStubBase@@UAAIXZ + ?Stub17@nsXPTCStubBase@@UAAIXZ + ?Stub18@nsXPTCStubBase@@UAAIXZ + ?Stub19@nsXPTCStubBase@@UAAIXZ + ?Stub20@nsXPTCStubBase@@UAAIXZ + ?Stub21@nsXPTCStubBase@@UAAIXZ + ?Stub22@nsXPTCStubBase@@UAAIXZ + ?Stub23@nsXPTCStubBase@@UAAIXZ + ?Stub24@nsXPTCStubBase@@UAAIXZ + ?Stub25@nsXPTCStubBase@@UAAIXZ + ?Stub26@nsXPTCStubBase@@UAAIXZ + ?Stub27@nsXPTCStubBase@@UAAIXZ + ?Stub28@nsXPTCStubBase@@UAAIXZ + ?Stub29@nsXPTCStubBase@@UAAIXZ + ?Stub30@nsXPTCStubBase@@UAAIXZ + ?Stub31@nsXPTCStubBase@@UAAIXZ + ?Stub32@nsXPTCStubBase@@UAAIXZ + ?Stub33@nsXPTCStubBase@@UAAIXZ + ?Stub34@nsXPTCStubBase@@UAAIXZ + ?Stub35@nsXPTCStubBase@@UAAIXZ + ?Stub36@nsXPTCStubBase@@UAAIXZ + ?Stub37@nsXPTCStubBase@@UAAIXZ + ?Stub38@nsXPTCStubBase@@UAAIXZ + ?Stub39@nsXPTCStubBase@@UAAIXZ + ?Stub40@nsXPTCStubBase@@UAAIXZ + ?Stub41@nsXPTCStubBase@@UAAIXZ + ?Stub42@nsXPTCStubBase@@UAAIXZ + ?Stub43@nsXPTCStubBase@@UAAIXZ + ?Stub44@nsXPTCStubBase@@UAAIXZ + ?Stub45@nsXPTCStubBase@@UAAIXZ + ?Stub46@nsXPTCStubBase@@UAAIXZ + ?Stub47@nsXPTCStubBase@@UAAIXZ + ?Stub48@nsXPTCStubBase@@UAAIXZ + ?Stub49@nsXPTCStubBase@@UAAIXZ + ?Stub50@nsXPTCStubBase@@UAAIXZ + ?Stub51@nsXPTCStubBase@@UAAIXZ + ?Stub52@nsXPTCStubBase@@UAAIXZ + ?Stub53@nsXPTCStubBase@@UAAIXZ + ?Stub54@nsXPTCStubBase@@UAAIXZ + ?Stub55@nsXPTCStubBase@@UAAIXZ + ?Stub56@nsXPTCStubBase@@UAAIXZ + ?Stub57@nsXPTCStubBase@@UAAIXZ + ?Stub58@nsXPTCStubBase@@UAAIXZ + ?Stub59@nsXPTCStubBase@@UAAIXZ + ?Stub60@nsXPTCStubBase@@UAAIXZ + ?Stub61@nsXPTCStubBase@@UAAIXZ + ?Stub62@nsXPTCStubBase@@UAAIXZ + ?Stub63@nsXPTCStubBase@@UAAIXZ + ?Stub64@nsXPTCStubBase@@UAAIXZ + ?Stub65@nsXPTCStubBase@@UAAIXZ + ?Stub66@nsXPTCStubBase@@UAAIXZ + ?Stub67@nsXPTCStubBase@@UAAIXZ + ?Stub68@nsXPTCStubBase@@UAAIXZ + ?Stub69@nsXPTCStubBase@@UAAIXZ + ?Stub70@nsXPTCStubBase@@UAAIXZ + ?Stub71@nsXPTCStubBase@@UAAIXZ + ?Stub72@nsXPTCStubBase@@UAAIXZ + ?Stub73@nsXPTCStubBase@@UAAIXZ + ?Stub74@nsXPTCStubBase@@UAAIXZ + ?Stub75@nsXPTCStubBase@@UAAIXZ + ?Stub76@nsXPTCStubBase@@UAAIXZ + ?Stub77@nsXPTCStubBase@@UAAIXZ + ?Stub78@nsXPTCStubBase@@UAAIXZ + ?Stub79@nsXPTCStubBase@@UAAIXZ + ?Stub80@nsXPTCStubBase@@UAAIXZ + ?Stub81@nsXPTCStubBase@@UAAIXZ + ?Stub82@nsXPTCStubBase@@UAAIXZ + ?Stub83@nsXPTCStubBase@@UAAIXZ + ?Stub84@nsXPTCStubBase@@UAAIXZ + ?Stub85@nsXPTCStubBase@@UAAIXZ + ?Stub86@nsXPTCStubBase@@UAAIXZ + ?Stub87@nsXPTCStubBase@@UAAIXZ + ?Stub88@nsXPTCStubBase@@UAAIXZ + ?Stub89@nsXPTCStubBase@@UAAIXZ + ?Stub90@nsXPTCStubBase@@UAAIXZ + ?Stub91@nsXPTCStubBase@@UAAIXZ + ?Stub92@nsXPTCStubBase@@UAAIXZ + ?Stub93@nsXPTCStubBase@@UAAIXZ + ?Stub94@nsXPTCStubBase@@UAAIXZ + ?Stub95@nsXPTCStubBase@@UAAIXZ + ?Stub96@nsXPTCStubBase@@UAAIXZ + ?Stub97@nsXPTCStubBase@@UAAIXZ + ?Stub98@nsXPTCStubBase@@UAAIXZ + ?Stub99@nsXPTCStubBase@@UAAIXZ + ?Stub100@nsXPTCStubBase@@UAAIXZ + ?Stub101@nsXPTCStubBase@@UAAIXZ + ?Stub102@nsXPTCStubBase@@UAAIXZ + ?Stub103@nsXPTCStubBase@@UAAIXZ + ?Stub104@nsXPTCStubBase@@UAAIXZ + ?Stub105@nsXPTCStubBase@@UAAIXZ + ?Stub106@nsXPTCStubBase@@UAAIXZ + ?Stub107@nsXPTCStubBase@@UAAIXZ + ?Stub108@nsXPTCStubBase@@UAAIXZ + ?Stub109@nsXPTCStubBase@@UAAIXZ + ?Stub110@nsXPTCStubBase@@UAAIXZ + ?Stub111@nsXPTCStubBase@@UAAIXZ + ?Stub112@nsXPTCStubBase@@UAAIXZ + ?Stub113@nsXPTCStubBase@@UAAIXZ + ?Stub114@nsXPTCStubBase@@UAAIXZ + ?Stub115@nsXPTCStubBase@@UAAIXZ + ?Stub116@nsXPTCStubBase@@UAAIXZ + ?Stub117@nsXPTCStubBase@@UAAIXZ + ?Stub118@nsXPTCStubBase@@UAAIXZ + ?Stub119@nsXPTCStubBase@@UAAIXZ + ?Stub120@nsXPTCStubBase@@UAAIXZ + ?Stub121@nsXPTCStubBase@@UAAIXZ + ?Stub122@nsXPTCStubBase@@UAAIXZ + ?Stub123@nsXPTCStubBase@@UAAIXZ + ?Stub124@nsXPTCStubBase@@UAAIXZ + ?Stub125@nsXPTCStubBase@@UAAIXZ + ?Stub126@nsXPTCStubBase@@UAAIXZ + ?Stub127@nsXPTCStubBase@@UAAIXZ + ?Stub128@nsXPTCStubBase@@UAAIXZ + ?Stub129@nsXPTCStubBase@@UAAIXZ + ?Stub130@nsXPTCStubBase@@UAAIXZ + ?Stub131@nsXPTCStubBase@@UAAIXZ + ?Stub132@nsXPTCStubBase@@UAAIXZ + ?Stub133@nsXPTCStubBase@@UAAIXZ + ?Stub134@nsXPTCStubBase@@UAAIXZ + ?Stub135@nsXPTCStubBase@@UAAIXZ + ?Stub136@nsXPTCStubBase@@UAAIXZ + ?Stub137@nsXPTCStubBase@@UAAIXZ + ?Stub138@nsXPTCStubBase@@UAAIXZ + ?Stub139@nsXPTCStubBase@@UAAIXZ + ?Stub140@nsXPTCStubBase@@UAAIXZ + ?Stub141@nsXPTCStubBase@@UAAIXZ + ?Stub142@nsXPTCStubBase@@UAAIXZ + ?Stub143@nsXPTCStubBase@@UAAIXZ + ?Stub144@nsXPTCStubBase@@UAAIXZ + ?Stub145@nsXPTCStubBase@@UAAIXZ + ?Stub146@nsXPTCStubBase@@UAAIXZ + ?Stub147@nsXPTCStubBase@@UAAIXZ + ?Stub148@nsXPTCStubBase@@UAAIXZ + ?Stub149@nsXPTCStubBase@@UAAIXZ + ?Stub150@nsXPTCStubBase@@UAAIXZ + ?Stub151@nsXPTCStubBase@@UAAIXZ + ?Stub152@nsXPTCStubBase@@UAAIXZ + ?Stub153@nsXPTCStubBase@@UAAIXZ + ?Stub154@nsXPTCStubBase@@UAAIXZ + ?Stub155@nsXPTCStubBase@@UAAIXZ + ?Stub156@nsXPTCStubBase@@UAAIXZ + ?Stub157@nsXPTCStubBase@@UAAIXZ + ?Stub158@nsXPTCStubBase@@UAAIXZ + ?Stub159@nsXPTCStubBase@@UAAIXZ + ?Stub160@nsXPTCStubBase@@UAAIXZ + ?Stub161@nsXPTCStubBase@@UAAIXZ + ?Stub162@nsXPTCStubBase@@UAAIXZ + ?Stub163@nsXPTCStubBase@@UAAIXZ + ?Stub164@nsXPTCStubBase@@UAAIXZ + ?Stub165@nsXPTCStubBase@@UAAIXZ + ?Stub166@nsXPTCStubBase@@UAAIXZ + ?Stub167@nsXPTCStubBase@@UAAIXZ + ?Stub168@nsXPTCStubBase@@UAAIXZ + ?Stub169@nsXPTCStubBase@@UAAIXZ + ?Stub170@nsXPTCStubBase@@UAAIXZ + ?Stub171@nsXPTCStubBase@@UAAIXZ + ?Stub172@nsXPTCStubBase@@UAAIXZ + ?Stub173@nsXPTCStubBase@@UAAIXZ + ?Stub174@nsXPTCStubBase@@UAAIXZ + ?Stub175@nsXPTCStubBase@@UAAIXZ + ?Stub176@nsXPTCStubBase@@UAAIXZ + ?Stub177@nsXPTCStubBase@@UAAIXZ + ?Stub178@nsXPTCStubBase@@UAAIXZ + ?Stub179@nsXPTCStubBase@@UAAIXZ + ?Stub180@nsXPTCStubBase@@UAAIXZ + ?Stub181@nsXPTCStubBase@@UAAIXZ + ?Stub182@nsXPTCStubBase@@UAAIXZ + ?Stub183@nsXPTCStubBase@@UAAIXZ + ?Stub184@nsXPTCStubBase@@UAAIXZ + ?Stub185@nsXPTCStubBase@@UAAIXZ + ?Stub186@nsXPTCStubBase@@UAAIXZ + ?Stub187@nsXPTCStubBase@@UAAIXZ + ?Stub188@nsXPTCStubBase@@UAAIXZ + ?Stub189@nsXPTCStubBase@@UAAIXZ + ?Stub190@nsXPTCStubBase@@UAAIXZ + ?Stub191@nsXPTCStubBase@@UAAIXZ + ?Stub192@nsXPTCStubBase@@UAAIXZ + ?Stub193@nsXPTCStubBase@@UAAIXZ + ?Stub194@nsXPTCStubBase@@UAAIXZ + ?Stub195@nsXPTCStubBase@@UAAIXZ + ?Stub196@nsXPTCStubBase@@UAAIXZ + ?Stub197@nsXPTCStubBase@@UAAIXZ + ?Stub198@nsXPTCStubBase@@UAAIXZ + ?Stub199@nsXPTCStubBase@@UAAIXZ + ?Stub200@nsXPTCStubBase@@UAAIXZ + ?Stub201@nsXPTCStubBase@@UAAIXZ + ?Stub202@nsXPTCStubBase@@UAAIXZ + ?Stub203@nsXPTCStubBase@@UAAIXZ + ?Stub204@nsXPTCStubBase@@UAAIXZ + ?Stub205@nsXPTCStubBase@@UAAIXZ + ?Stub206@nsXPTCStubBase@@UAAIXZ + ?Stub207@nsXPTCStubBase@@UAAIXZ + ?Stub208@nsXPTCStubBase@@UAAIXZ + ?Stub209@nsXPTCStubBase@@UAAIXZ + ?Stub210@nsXPTCStubBase@@UAAIXZ + ?Stub211@nsXPTCStubBase@@UAAIXZ + ?Stub212@nsXPTCStubBase@@UAAIXZ + ?Stub213@nsXPTCStubBase@@UAAIXZ + ?Stub214@nsXPTCStubBase@@UAAIXZ + ?Stub215@nsXPTCStubBase@@UAAIXZ + ?Stub216@nsXPTCStubBase@@UAAIXZ + ?Stub217@nsXPTCStubBase@@UAAIXZ + ?Stub218@nsXPTCStubBase@@UAAIXZ + ?Stub219@nsXPTCStubBase@@UAAIXZ + ?Stub220@nsXPTCStubBase@@UAAIXZ + ?Stub221@nsXPTCStubBase@@UAAIXZ + ?Stub222@nsXPTCStubBase@@UAAIXZ + ?Stub223@nsXPTCStubBase@@UAAIXZ + ?Stub224@nsXPTCStubBase@@UAAIXZ + ?Stub225@nsXPTCStubBase@@UAAIXZ + ?Stub226@nsXPTCStubBase@@UAAIXZ + ?Stub227@nsXPTCStubBase@@UAAIXZ + ?Stub228@nsXPTCStubBase@@UAAIXZ + ?Stub229@nsXPTCStubBase@@UAAIXZ + ?Stub230@nsXPTCStubBase@@UAAIXZ + ?Stub231@nsXPTCStubBase@@UAAIXZ + ?Stub232@nsXPTCStubBase@@UAAIXZ + ?Stub233@nsXPTCStubBase@@UAAIXZ + ?Stub234@nsXPTCStubBase@@UAAIXZ + ?Stub235@nsXPTCStubBase@@UAAIXZ + ?Stub236@nsXPTCStubBase@@UAAIXZ + ?Stub237@nsXPTCStubBase@@UAAIXZ + ?Stub238@nsXPTCStubBase@@UAAIXZ + ?Stub239@nsXPTCStubBase@@UAAIXZ + ?Stub240@nsXPTCStubBase@@UAAIXZ + ?Stub241@nsXPTCStubBase@@UAAIXZ + ?Stub242@nsXPTCStubBase@@UAAIXZ + ?Stub243@nsXPTCStubBase@@UAAIXZ + ?Stub244@nsXPTCStubBase@@UAAIXZ + ?Stub245@nsXPTCStubBase@@UAAIXZ + ?Stub246@nsXPTCStubBase@@UAAIXZ + ?Stub247@nsXPTCStubBase@@UAAIXZ + ?Stub248@nsXPTCStubBase@@UAAIXZ + ?Stub249@nsXPTCStubBase@@UAAIXZ + |