diff options
Diffstat (limited to 'xpcom/build/PoisonIOInterposerBase.cpp')
-rw-r--r-- | xpcom/build/PoisonIOInterposerBase.cpp | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/xpcom/build/PoisonIOInterposerBase.cpp b/xpcom/build/PoisonIOInterposerBase.cpp new file mode 100644 index 000000000..0e5754392 --- /dev/null +++ b/xpcom/build/PoisonIOInterposerBase.cpp @@ -0,0 +1,291 @@ +/* -*- 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/Mutex.h" +#include "mozilla/Scoped.h" +#include "mozilla/UniquePtr.h" + +#include <algorithm> + +#include "PoisonIOInterposer.h" + +#ifdef MOZ_REPLACE_MALLOC +#include "replace_malloc_bridge.h" +#endif + +// Auxiliary method to convert file descriptors to ids +#if defined(XP_WIN32) +#include <io.h> +inline intptr_t +FileDescriptorToHandle(int aFd) +{ + return _get_osfhandle(aFd); +} +#else +inline intptr_t +FileDescriptorToHandle(int aFd) +{ + return aFd; +} +#endif /* if not XP_WIN32 */ + +using namespace mozilla; + +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 Scoped<DebugFilesAutoLockTraits> +{ + static PRLock* Lock; +public: + static void Clear(); + static PRLock* getDebugFileIDsLock() + { + // On windows this static is not thread safe, but we know that the first + // call is from + // * An early registration of a debug FD or + // * The call to InitWritePoisoning. + // Since the early debug FDs are logs created early in the main thread + // and no writes are trapped before InitWritePoisoning, we are safe. + if (!Lock) { + Lock = PR_NewLock(); + } + + // We have to use something lower level than a mutex. If we don't, we + // can get recursive in here when called from logging a call to free. + return Lock; + } + + DebugFilesAutoLock() + : Scoped<DebugFilesAutoLockTraits>(getDebugFileIDsLock()) + { + PR_Lock(get()); + } +}; + +PRLock* DebugFilesAutoLock::Lock; +void +DebugFilesAutoLock::Clear() +{ + MOZ_ASSERT(Lock != nullptr); + Lock = nullptr; +} + +// 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) +{ + MozillaRegisterDebugHandle(FileDescriptorToHandle(aFd)); +} + +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) +{ + MozillaUnRegisterDebugHandle(FileDescriptorToHandle(aFd)); +} + +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 +DebugFdRegistry::RegisterHandle(intptr_t aHandle) +{ + MozillaRegisterDebugHandle(aHandle); +} + +void +DebugFdRegistry::UnRegisterHandle(intptr_t aHandle) +{ + MozillaUnRegisterDebugHandle(aHandle); +} +#endif |