diff --git a/xpcom/base/MemoryInfo.cpp b/xpcom/base/MemoryInfo.cpp new file mode 100644 index 000000000000..8c5f37dfcfcc --- /dev/null +++ b/xpcom/base/MemoryInfo.cpp @@ -0,0 +1,105 @@ +/* -*- 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/MemoryInfo.h" + +#include "mozilla/DebugOnly.h" + +#include + +namespace mozilla { + +/* static */ MemoryInfo +MemoryInfo::Get(const void* aPtr, size_t aSize) +{ + MemoryInfo result; + + result.mStart = uintptr_t(aPtr); + const char* ptr = reinterpret_cast(aPtr); + const char* end = ptr + aSize; + DebugOnly base = nullptr; + while (ptr < end) { + MEMORY_BASIC_INFORMATION basicInfo; + if (!VirtualQuery(ptr, &basicInfo, sizeof(basicInfo))) { + break; + } + + MOZ_ASSERT_IF(base, base == basicInfo.AllocationBase); + base = basicInfo.AllocationBase; + + size_t regionSize = std::min(size_t(basicInfo.RegionSize), + size_t(end - ptr)); + + if (basicInfo.State == MEM_COMMIT) { + result.mCommitted += regionSize; + } else if (basicInfo.State == MEM_RESERVE) { + result.mReserved += regionSize; + } else if (basicInfo.State == MEM_FREE) { + result.mFree += regionSize; + } else { + MOZ_ASSERT_UNREACHABLE("Unexpected region state"); + } + result.mSize += regionSize; + ptr += regionSize; + + if (result.mType.isEmpty()) { + if (basicInfo.Type & MEM_IMAGE) { + result.mType += PageType::Image; + } + if (basicInfo.Type & MEM_MAPPED) { + result.mType += PageType::Mapped; + } + if (basicInfo.Type & MEM_PRIVATE) { + result.mType += PageType::Private; + } + + // The first 8 bits of AllocationProtect are an enum. The remaining bits + // are flags. + switch (basicInfo.AllocationProtect & 0xff) { + case PAGE_EXECUTE_WRITECOPY: + result.mPerms += Perm::CopyOnWrite; + MOZ_FALLTHROUGH; + case PAGE_EXECUTE_READWRITE: + result.mPerms += Perm::Write; + MOZ_FALLTHROUGH; + case PAGE_EXECUTE_READ: + result.mPerms += Perm::Read; + MOZ_FALLTHROUGH; + case PAGE_EXECUTE: + result.mPerms += Perm::Execute; + break; + + case PAGE_WRITECOPY: + result.mPerms += Perm::CopyOnWrite; + MOZ_FALLTHROUGH; + case PAGE_READWRITE: + result.mPerms += Perm::Write; + MOZ_FALLTHROUGH; + case PAGE_READONLY: + result.mPerms += Perm::Read; + break; + + default: + break; + } + + if (basicInfo.AllocationProtect & PAGE_GUARD) { + result.mPerms += Perm::Guard; + } + if (basicInfo.AllocationProtect & PAGE_NOCACHE) { + result.mPerms += Perm::NoCache; + } + if (basicInfo.AllocationProtect & PAGE_WRITECOMBINE) { + result.mPerms += Perm::WriteCombine; + } + } + } + + result.mEnd = uintptr_t(ptr); + return result; +} + +} // namespace mozilla diff --git a/xpcom/base/MemoryInfo.h b/xpcom/base/MemoryInfo.h new file mode 100644 index 000000000000..2e025f4c895f --- /dev/null +++ b/xpcom/base/MemoryInfo.h @@ -0,0 +1,83 @@ +/* -*- 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_MemoryInfo_h +#define mozilla_MemoryInfo_h + +#include "mozilla/EnumSet.h" +#include "nsTArray.h" + +/** + * MemoryInfo is a helper class which describes the attributes and sizes of a + * particular region of VM memory on Windows. It roughtly corresponds to the + * values in a MEMORY_BASIC_INFORMATION struct, summed over an entire region or + * memory. + */ + +namespace mozilla { + +class MemoryInfo final +{ +public: + enum class Perm : uint8_t + { + Read, + Write, + Execute, + CopyOnWrite, + Guard, + NoCache, + WriteCombine, + }; + enum class PageType : uint8_t + { + Image, + Mapped, + Private, + }; + + using PermSet = EnumSet; + using PageTypeSet = EnumSet; + + MemoryInfo() = default; + MOZ_IMPLICIT MemoryInfo(const MemoryInfo&) = default; + + uintptr_t Start() const { return mStart; } + uintptr_t End() const { return mEnd; } + + PageTypeSet Type() const { return mType; } + PermSet Perms() const { return mPerms; } + + size_t Reserved() const { return mReserved; } + size_t Committed() const { return mCommitted; } + size_t Free() const { return mFree; } + size_t Size() const { return mSize; } + + // Returns a MemoryInfo object containing the sums of all region sizes, + // divided into Reserved, Committed, and Free, depending on their State + // properties. + // + // The entire range of aSize bytes starting at aPtr must correspond to a + // single allocation. This restriction is enforced in debug builds. + static MemoryInfo Get(const void* aPtr, size_t aSize); + +private: + uintptr_t mStart = 0; + uintptr_t mEnd = 0; + + size_t mReserved = 0; + size_t mCommitted = 0; + size_t mFree = 0; + size_t mSize = 0; + + PageTypeSet mType{}; + + PermSet mPerms{}; +}; + +} // namespace mozilla + +#endif // mozilla_MemoryInfo_h diff --git a/xpcom/base/moz.build b/xpcom/base/moz.build index 1d30a7b885d1..a83b753e3840 100644 --- a/xpcom/base/moz.build +++ b/xpcom/base/moz.build @@ -112,6 +112,7 @@ EXPORTS.mozilla += [ 'IntentionalCrash.h', 'JSObjectHolder.h', 'Logging.h', + 'MemoryInfo.h', 'MemoryMapping.h', 'MemoryReportingProcess.h', 'nsMemoryInfoDumper.h', @@ -179,6 +180,11 @@ if CONFIG['OS_TARGET'] in ('Linux', 'Android'): 'MemoryMapping.cpp', ] +if CONFIG['OS_TARGET'] == 'WINNT': + UNIFIED_SOURCES += [ + 'MemoryInfo.cpp', + ] + GENERATED_FILES += [ "error_list.rs", "ErrorList.h", diff --git a/xpcom/base/nsMemoryReporterManager.cpp b/xpcom/base/nsMemoryReporterManager.cpp index efe9f74fbc33..522d85ec5e48 100644 --- a/xpcom/base/nsMemoryReporterManager.cpp +++ b/xpcom/base/nsMemoryReporterManager.cpp @@ -25,6 +25,7 @@ #include "nsMemoryInfoDumper.h" #endif #include "nsNetCID.h" +#include "nsThread.h" #include "mozilla/Attributes.h" #include "mozilla/MemoryReportingProcess.h" #include "mozilla/PodOperations.h" @@ -39,6 +40,8 @@ #include "mozilla/ipc/FileDescriptorUtils.h" #ifdef XP_WIN +#include "mozilla/MemoryInfo.h" + #include #ifndef getpid #define getpid _getpid @@ -58,7 +61,6 @@ using namespace dom; #if defined(XP_LINUX) #include "mozilla/MemoryMapping.h" -#include "nsThread.h" #include #include @@ -1402,7 +1404,7 @@ public: }; NS_IMPL_ISUPPORTS(AtomTablesReporter, nsIMemoryReporter) -#ifdef XP_LINUX +#if defined(XP_LINUX) || defined(XP_WIN) class ThreadStacksReporter final : public nsIMemoryReporter { ~ThreadStacksReporter() = default; @@ -1413,8 +1415,10 @@ public: NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) override { +#ifdef XP_LINUX nsTArray mappings(1024); MOZ_TRY(GetMemoryMappings(mappings)); +#endif // Enumerating over active threads requires holding a lock, so we collect // info on all threads, and then call our reporter callbacks after releasing @@ -1432,6 +1436,7 @@ public: continue; } +#ifdef XP_LINUX int idx = mappings.BinaryIndexOf(thread->StackBase()); if (idx < 0) { continue; @@ -1472,6 +1477,10 @@ public: // matches the allocated size of the thread stack. MOZ_ASSERT(mappings[idx].Size() == thread->StackSize(), "Mapping region size doesn't match stack allocation size"); +#else + auto memInfo = MemoryInfo::Get(thread->StackBase(), thread->StackSize()); + size_t privateSize = memInfo.Committed(); +#endif threads.AppendElement(ThreadData{ nsCString(PR_GetThreadName(thread->GetPRThread())), @@ -1665,7 +1674,7 @@ nsMemoryReporterManager::Init() RegisterStrongReporter(new AtomTablesReporter()); -#ifdef XP_LINUX +#if defined(XP_LINUX) || defined(XP_WIN) RegisterStrongReporter(new ThreadStacksReporter()); #endif diff --git a/xpcom/threads/nsThread.cpp b/xpcom/threads/nsThread.cpp index 9657caeffa3d..2ae20e0b8667 100644 --- a/xpcom/threads/nsThread.cpp +++ b/xpcom/threads/nsThread.cpp @@ -61,6 +61,15 @@ #include #endif +#ifdef XP_WIN +#include "mozilla/DynamicallyLinkedFunctionPtr.h" + +#include + +using GetCurrentThreadStackLimitsFn = void (WINAPI*)( + PULONG_PTR LowLimit, PULONG_PTR HighLimit); +#endif + #define HAVE_UALARM _BSD_SOURCE || (_XOPEN_SOURCE >= 500 || \ _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED) && \ !(_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700) @@ -420,8 +429,8 @@ nsThread::ThreadFunc(void* aArg) NS_SetCurrentThreadName(initData->name.BeginReading()); } -#ifdef XP_LINUX { +#if defined(XP_LINUX) pthread_attr_t attr; pthread_attr_init(&attr); pthread_getattr_np(pthread_self(), &attr); @@ -473,8 +482,18 @@ nsThread::ThreadFunc(void* aArg) madvise(self->mStackBase, stackSize, MADV_NOHUGEPAGE); pthread_attr_destroy(&attr); - } +#elif defined(XP_WIN) + static const DynamicallyLinkedFunctionPtr + sGetStackLimits(L"kernel32.dll", "GetCurrentThreadStackLimits"); + + if (sGetStackLimits) { + ULONG_PTR stackBottom, stackTop; + sGetStackLimits(&stackBottom, &stackTop); + self->mStackBase = reinterpret_cast(stackBottom); + self->mStackSize = stackTop - stackBottom; + } #endif + } // Inform the ThreadManager nsThreadManager::get().RegisterCurrentThread(*self);