1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsTraceRefcnt.h"
9 #include "base/process_util.h"
10 #include "mozilla/Attributes.h"
11 #include "mozilla/AutoRestore.h"
12 #include "mozilla/CycleCollectedJSContext.h"
13 #include "mozilla/IntegerPrintfMacros.h"
14 #include "mozilla/Path.h"
15 #include "mozilla/Sprintf.h"
16 #include "mozilla/StaticPtr.h"
17 #include "nsXPCOMPrivate.h"
19 #include "nsClassHashtable.h"
20 #include "nsContentUtils.h"
21 #include "nsISupports.h"
22 #include "nsHashKeys.h"
23 #include "nsPrintfCString.h"
25 #include "nsTHashtable.h"
30 #include "nsHashKeys.h"
31 #include "mozilla/StackWalk.h"
32 #include "nsThreadUtils.h"
33 #include "CodeAddressService.h"
35 #include "nsXULAppAPI.h"
39 # define getpid _getpid
44 #include "mozilla/Atomics.h"
45 #include "mozilla/AutoRestore.h"
46 #include "mozilla/BlockingResourceBase.h"
47 #include "mozilla/PoisonIOInterposer.h"
48 #include "mozilla/UniquePtr.h"
58 # include "nsMemoryInfoDumper.h"
61 // dynamic_cast<void*> is not supported on Windows without RTTI.
63 # define HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
66 ////////////////////////////////////////////////////////////////////////////////
70 class MOZ_CAPABILITY("mutex") TraceLogMutex
71 : private mozilla::detail::MutexImpl
{
73 explicit TraceLogMutex() = default;
76 friend class AutoTraceLogLock
;
78 void Lock() MOZ_CAPABILITY_ACQUIRE() { ::mozilla::detail::MutexImpl::lock(); }
79 void Unlock() MOZ_CAPABILITY_RELEASE() {
80 ::mozilla::detail::MutexImpl::unlock();
84 MOZ_RUNINIT
static TraceLogMutex gTraceLog
;
86 class MOZ_RAII AutoTraceLogLock
{
88 explicit AutoTraceLogLock(TraceLogMutex
& aMutex
) : mMutex(aMutex
) {
92 AutoTraceLogLock(const AutoTraceLogLock
&) = delete;
93 AutoTraceLogLock
& operator=(const AutoTraceLogLock
&) = delete;
94 AutoTraceLogLock(AutoTraceLogLock
&&) = delete;
95 AutoTraceLogLock
& operator=(AutoTraceLogLock
&&) = delete;
97 ~AutoTraceLogLock() { mMutex
.Unlock(); }
100 TraceLogMutex
& mMutex
;
104 struct SerialNumberRecord
;
106 using mozilla::AutoRestore
;
107 using mozilla::CodeAddressService
;
108 using mozilla::CycleCollectedJSContext
;
109 using mozilla::StaticAutoPtr
;
111 using BloatHash
= nsClassHashtable
<nsDepCharHashKey
, BloatEntry
>;
112 using CharPtrSet
= nsTHashtable
<nsCharPtrHashKey
>;
113 using IntPtrSet
= nsTHashtable
<IntPtrHashKey
>;
114 using SerialHash
= nsClassHashtable
<nsVoidPtrHashKey
, SerialNumberRecord
>;
116 static StaticAutoPtr
<BloatHash
> gBloatView
;
117 static StaticAutoPtr
<CharPtrSet
> gTypesToLog
;
118 static StaticAutoPtr
<IntPtrSet
> gObjectsToLog
;
119 static StaticAutoPtr
<SerialHash
> gSerialNumbers
;
121 static intptr_t gNextSerialNumber
;
122 static bool gDumpedStatistics
= false;
123 static bool gLogJSStacks
= false;
125 // By default, debug builds only do bloat logging. Bloat logging
126 // only tries to record when an object is created or destroyed, so we
127 // optimize the common case in NS_LogAddRef and NS_LogRelease where
128 // only bloat logging is enabled and no logging needs to be done.
129 enum LoggingType
{ NoLogging
, OnlyBloatLogging
, FullLogging
};
131 static LoggingType gLogging
;
133 static bool gLogLeaksOnly
;
135 #define BAD_TLS_INDEX ((unsigned)-1)
137 // if gActivityTLS == BAD_TLS_INDEX, then we're
138 // unitialized... otherwise this points to a NSPR TLS thread index
139 // indicating whether addref activity is legal. If the PTR_TO_INT32 is 0 then
140 // activity is ok, otherwise not!
141 static unsigned gActivityTLS
= BAD_TLS_INDEX
;
143 static bool gInitialized
;
144 static nsrefcnt gInitCount
;
146 static FILE* gBloatLog
= nullptr;
147 static FILE* gRefcntsLog
= nullptr;
148 static FILE* gAllocLog
= nullptr;
149 static FILE* gCOMPtrLog
= nullptr;
151 static void WalkTheStackSavingLocations(std::vector
<void*>& aLocations
,
152 const void* aFirstFramePC
);
154 struct SerialNumberRecord
{
156 : serialNumber(++gNextSerialNumber
), refCount(0), COMPtrCount(0) {}
158 intptr_t serialNumber
;
161 // We use std:: classes here rather than the XPCOM equivalents because the
162 // XPCOM equivalents do leak-checking, and if you try to leak-check while
163 // leak-checking, you're gonna have a bad time.
164 std::vector
<void*> allocationStack
;
165 mozilla::UniquePtr
<char[]> jsStack
;
168 // If this thread isn't running JS, there's nothing to do.
169 if (!CycleCollectedJSContext::Get()) {
173 if (!nsContentUtils::IsInitialized()) {
177 JSContext
* cx
= nsContentUtils::GetCurrentJSContext();
182 JS::UniqueChars chars
= xpc_PrintJSStack(cx
,
184 /*showLocals=*/false,
185 /*showThisProps=*/false);
186 size_t len
= strlen(chars
.get());
187 jsStack
= mozilla::MakeUnique
<char[]>(len
+ 1);
188 memcpy(jsStack
.get(), chars
.get(), len
+ 1);
192 struct nsTraceRefcntStats
{
196 bool HaveLeaks() const { return mCreates
!= mDestroys
; }
203 int64_t NumLeaked() const { return (int64_t)(mCreates
- mDestroys
); }
207 static void AssertActivityIsLegal(const char* aType
, const char* aAction
) {
208 if (gActivityTLS
== BAD_TLS_INDEX
|| PR_GetThreadPrivate(gActivityTLS
)) {
210 SprintfLiteral(buf
, "XPCOM object %s %s from static ctor/dtor", aType
,
213 if (PR_GetEnv("MOZ_FATAL_STATIC_XPCOM_CTORS_DTORS")) {
214 MOZ_CRASH_UNSAFE_PRINTF("%s", buf
);
220 # define ASSERT_ACTIVITY_IS_LEGAL(type_, action_) \
222 AssertActivityIsLegal(type_, action_); \
225 # define ASSERT_ACTIVITY_IS_LEGAL(type_, action_) \
230 ////////////////////////////////////////////////////////////////////////////////
232 mozilla::StaticAutoPtr
<CodeAddressService
<>> gCodeAddressService
;
234 ////////////////////////////////////////////////////////////////////////////////
238 BloatEntry(const char* aClassName
, uint32_t aClassSize
)
239 : mClassSize(aClassSize
), mStats() {
240 MOZ_ASSERT(strlen(aClassName
) > 0, "BloatEntry name must be non-empty");
241 mClassName
= aClassName
;
246 ~BloatEntry() = default;
248 uint32_t GetClassSize() { return (uint32_t)mClassSize
; }
249 const char* GetClassName() { return mClassName
; }
251 void Ctor() { mStats
.mCreates
++; }
253 void Dtor() { mStats
.mDestroys
++; }
255 void Total(BloatEntry
* aTotal
) {
256 aTotal
->mStats
.mCreates
+= mStats
.mCreates
;
257 aTotal
->mStats
.mDestroys
+= mStats
.mDestroys
;
258 aTotal
->mClassSize
+=
259 mClassSize
* mStats
.mCreates
; // adjust for average in DumpTotal
260 aTotal
->mTotalLeaked
+= mClassSize
* mStats
.NumLeaked();
263 void DumpTotal(FILE* aOut
) {
264 mClassSize
/= mStats
.mCreates
;
268 bool PrintDumpHeader(FILE* aOut
, const char* aMsg
) {
269 fprintf(aOut
, "\n== BloatView: %s, %s process %d\n", aMsg
,
270 XRE_GetProcessTypeString(), getpid());
271 if (gLogLeaksOnly
&& !mStats
.HaveLeaks()) {
278 " |<----------------Class--------------->|<-----Bytes------>|<----Objects---->|\n" \
279 " | | Per-Inst Leaked| Total Rem|\n");
282 this->DumpTotal(aOut
);
287 void Dump(int aIndex
, FILE* aOut
) {
288 if (gLogLeaksOnly
&& !mStats
.HaveLeaks()) {
292 if (mStats
.HaveLeaks() || mStats
.mCreates
!= 0) {
294 "%4d |%-38.38s| %8d %8" PRId64
"|%8" PRIu64
" %8" PRId64
"|\n",
295 aIndex
+ 1, mClassName
, GetClassSize(),
296 nsCRT::strcmp(mClassName
, "TOTAL")
297 ? (mStats
.NumLeaked() * GetClassSize())
299 mStats
.mCreates
, mStats
.NumLeaked());
304 const char* mClassName
;
305 // mClassSize is stored as a double because of the way we compute the avg
306 // class size for total bloat.
308 // mTotalLeaked is only used for the TOTAL entry.
309 int64_t mTotalLeaked
;
310 nsTraceRefcntStats mStats
;
313 static void EnsureBloatView() {
315 gBloatView
= new BloatHash(256);
319 static BloatEntry
* GetBloatEntry(const char* aTypeName
,
320 uint32_t aInstanceSize
) {
322 BloatEntry
* entry
= gBloatView
->Get(aTypeName
);
323 if (!entry
&& aInstanceSize
> 0) {
325 ->InsertOrUpdate(aTypeName
, mozilla::MakeUnique
<BloatEntry
>(
326 aTypeName
, aInstanceSize
))
330 aInstanceSize
== 0 || entry
->GetClassSize() == aInstanceSize
,
331 "Mismatched sizes were recorded in the memory leak logging table. "
332 "The usual cause of this is having a templated class that uses "
333 "MOZ_COUNT_{C,D}TOR in the constructor or destructor, respectively. "
334 "As a workaround, the MOZ_COUNT_{C,D}TOR calls can be moved to a "
335 "non-templated base class. Another possible cause is a runnable with "
336 "an mName that matches another refcounted class, or two refcounted "
337 "classes with the same class name in different C++ namespaces.");
342 static void DumpSerialNumbers(const SerialHash::ConstIterator
& aHashEntry
,
343 FILE* aFd
, bool aDumpAsStringBuffer
) {
344 SerialNumberRecord
* record
= aHashEntry
.UserData();
345 auto* outputFile
= aFd
;
346 #ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
347 fprintf(outputFile
, "%" PRIdPTR
" @%p (%d references; %d from COMPtrs)\n",
348 record
->serialNumber
, aHashEntry
.Key(), record
->refCount
,
349 record
->COMPtrCount
);
351 fprintf(outputFile
, "%" PRIdPTR
" @%p (%d references)\n",
352 record
->serialNumber
, aHashEntry
.Key(), record
->refCount
);
355 if (aDumpAsStringBuffer
) {
356 // This output will be wrong if the StringBuffer was used to
357 // store a char16_t string.
358 auto* buffer
= static_cast<const mozilla::StringBuffer
*>(aHashEntry
.Key());
359 nsDependentCString
bufferString(static_cast<char*>(buffer
->Data()));
362 "Contents of leaked mozilla::StringBuffer with storage size %d as a "
364 buffer
->StorageSize(), bufferString
.get());
367 if (!record
->allocationStack
.empty()) {
368 static const size_t bufLen
= 1024;
370 fprintf(outputFile
, "allocation stack:\n");
371 for (size_t i
= 0, length
= record
->allocationStack
.size(); i
< length
;
373 gCodeAddressService
->GetLocation(i
, record
->allocationStack
[i
], buf
,
375 fprintf(outputFile
, "%s\n", buf
);
380 if (record
->jsStack
) {
381 fprintf(outputFile
, "JS allocation stack:\n%s\n", record
->jsStack
.get());
383 fprintf(outputFile
, "There is no JS context on the stack.\n");
389 class nsDefaultComparator
<BloatEntry
*, BloatEntry
*> {
391 bool Equals(BloatEntry
* const& aEntry1
, BloatEntry
* const& aEntry2
) const {
392 return strcmp(aEntry1
->GetClassName(), aEntry2
->GetClassName()) == 0;
394 bool LessThan(BloatEntry
* const& aEntry1
, BloatEntry
* const& aEntry2
) const {
395 return strcmp(aEntry1
->GetClassName(), aEntry2
->GetClassName()) < 0;
399 nsresult
nsTraceRefcnt::DumpStatistics() {
400 if (!gBloatLog
|| !gBloatView
) {
401 return NS_ERROR_FAILURE
;
404 AutoTraceLogLock
lock(gTraceLog
);
406 MOZ_ASSERT(!gDumpedStatistics
,
407 "Calling DumpStatistics more than once may result in "
408 "bogus positive or negative leaks being reported");
409 gDumpedStatistics
= true;
411 // Don't try to log while we hold the lock, we'd deadlock.
412 AutoRestore
<LoggingType
> saveLogging(gLogging
);
413 gLogging
= NoLogging
;
415 BloatEntry
total("TOTAL", 0);
416 for (const auto& data
: gBloatView
->Values()) {
417 if (nsCRT::strcmp(data
->GetClassName(), "TOTAL") != 0) {
424 msg
= "ALL (cumulative) LEAK STATISTICS";
426 msg
= "ALL (cumulative) LEAK AND BLOAT STATISTICS";
428 const bool leaked
= total
.PrintDumpHeader(gBloatLog
, msg
);
430 nsTArray
<BloatEntry
*> entries(gBloatView
->Count());
431 for (const auto& data
: gBloatView
->Values()) {
432 entries
.AppendElement(data
.get());
435 const uint32_t count
= entries
.Length();
437 if (!gLogLeaksOnly
|| leaked
) {
438 // Sort the entries alphabetically by classname.
441 for (uint32_t i
= 0; i
< count
; ++i
) {
442 BloatEntry
* entry
= entries
[i
];
443 entry
->Dump(i
, gBloatLog
);
446 fprintf(gBloatLog
, "\n");
449 fprintf(gBloatLog
, "nsTraceRefcnt::DumpStatistics: %d entries\n", count
);
451 if (gSerialNumbers
) {
452 bool onlyLoggingStringBuffers
= gTypesToLog
&& gTypesToLog
->Count() == 1 &&
453 gTypesToLog
->Contains("StringBuffer");
455 fprintf(gBloatLog
, "\nSerial Numbers of Leaked Objects:\n");
456 for (auto iter
= gSerialNumbers
->ConstIter(); !iter
.Done(); iter
.Next()) {
457 DumpSerialNumbers(iter
, gBloatLog
, onlyLoggingStringBuffers
);
464 void nsTraceRefcnt::ResetStatistics() {
465 AutoTraceLogLock
lock(gTraceLog
);
466 gBloatView
= nullptr;
469 static intptr_t GetSerialNumber(void* aPtr
, bool aCreate
, void* aFirstFramePC
) {
471 auto record
= gSerialNumbers
->Get(aPtr
);
472 return record
? record
->serialNumber
: 0;
475 gSerialNumbers
->WithEntryHandle(aPtr
, [aFirstFramePC
](auto&& entry
) {
478 "If an object already has a serial number, we should be destroying "
482 auto& record
= entry
.Insert(mozilla::MakeUnique
<SerialNumberRecord
>());
483 WalkTheStackSavingLocations(record
->allocationStack
, aFirstFramePC
);
485 record
->SaveJSStack();
488 return gNextSerialNumber
;
491 static void RecycleSerialNumberPtr(void* aPtr
) { gSerialNumbers
->Remove(aPtr
); }
493 static bool LogThisObj(intptr_t aSerialNumber
) {
494 return gObjectsToLog
->Contains(aSerialNumber
);
497 using EnvCharType
= mozilla::filesystem::Path::value_type
;
499 static bool InitLog(const EnvCharType
* aEnvVar
, const char* aMsg
,
500 FILE** aResult
, const char* aProcType
) {
502 // This is gross, I know.
503 const wchar_t* envvar
= reinterpret_cast<const wchar_t*>(aEnvVar
);
504 const char16_t
* value
= reinterpret_cast<const char16_t
*>(::_wgetenv(envvar
));
505 # define ENVVAR_PRINTF "%S"
507 const char* envvar
= aEnvVar
;
508 const char* value
= ::getenv(aEnvVar
);
509 # define ENVVAR_PRINTF "%s"
513 nsTDependentString
<EnvCharType
> fname(value
);
514 if (fname
.EqualsLiteral("1")) {
516 fprintf(stdout
, "### " ENVVAR_PRINTF
" defined -- logging %s to stdout\n",
520 if (fname
.EqualsLiteral("2")) {
522 fprintf(stdout
, "### " ENVVAR_PRINTF
" defined -- logging %s to stderr\n",
526 if (!XRE_IsParentProcess()) {
527 nsTString
<EnvCharType
> extension
;
528 extension
.AssignLiteral(".log");
529 bool hasLogExtension
= StringEndsWith(fname
, extension
);
530 if (hasLogExtension
) {
531 fname
.Cut(fname
.Length() - 4, 4);
534 fname
.AppendASCII(aProcType
);
535 fname
.AppendLiteral("_pid");
536 fname
.AppendInt((uint32_t)getpid());
537 if (hasLogExtension
) {
538 fname
.AppendLiteral(".log");
542 FILE* stream
= ::_wfopen(fname
.get(), L
"wN");
543 const wchar_t* fp
= (const wchar_t*)fname
.get();
545 FILE* stream
= ::fopen(fname
.get(), "w");
546 const char* fp
= fname
.get();
549 MozillaRegisterDebugFD(fileno(stream
));
552 "### " ENVVAR_PRINTF
" defined -- logging %s to " ENVVAR_PRINTF
561 " defined -- unable to log %s to " ENVVAR_PRINTF
"\n",
563 MOZ_ASSERT(false, "Tried and failed to create an XPCOM log");
570 static void maybeUnregisterAndCloseFile(FILE*& aFile
) {
575 MozillaUnRegisterDebugFILE(aFile
);
580 static void DoInitTraceLog(const char* aProcType
) {
582 # define ENVVAR(x) u"" x
587 bool defined
= InitLog(ENVVAR("XPCOM_MEM_BLOAT_LOG"), "bloat/leaks",
588 &gBloatLog
, aProcType
);
591 InitLog(ENVVAR("XPCOM_MEM_LEAK_LOG"), "leaks", &gBloatLog
, aProcType
);
593 if (defined
|| gLogLeaksOnly
) {
594 // Use the same bloat view, if there is one, to keep it consistent
595 // between the fork server and content processes.
597 } else if (gBloatView
) {
598 nsTraceRefcnt::ResetStatistics();
601 InitLog(ENVVAR("XPCOM_MEM_REFCNT_LOG"), "refcounts", &gRefcntsLog
, aProcType
);
603 InitLog(ENVVAR("XPCOM_MEM_ALLOC_LOG"), "new/delete", &gAllocLog
, aProcType
);
605 const char* classes
= getenv("XPCOM_MEM_LOG_CLASSES");
607 #ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
609 InitLog(ENVVAR("XPCOM_MEM_COMPTR_LOG"), "nsCOMPtr", &gCOMPtrLog
, aProcType
);
611 if (getenv("XPCOM_MEM_COMPTR_LOG")) {
613 "### XPCOM_MEM_COMPTR_LOG defined -- "
614 "but XPCOM_MEM_LOG_CLASSES is not defined\n");
618 const char* comptr_log
= getenv("XPCOM_MEM_COMPTR_LOG");
621 "### XPCOM_MEM_COMPTR_LOG defined -- "
622 "but it will not work without dynamic_cast\n");
624 #endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
629 // if XPCOM_MEM_LOG_CLASSES was set to some value, the value is interpreted
630 // as a list of class names to track
632 // Use the same |gTypesToLog| and |gSerialNumbers| to keep them
633 // consistent through the fork server and content processes.
634 // Without this, counters will be incorrect.
636 gTypesToLog
= new CharPtrSet(256);
640 "### XPCOM_MEM_LOG_CLASSES defined -- "
641 "only logging these classes: ");
642 const char* cp
= classes
;
644 char* cm
= (char*)strchr(cp
, ',');
648 if (!gTypesToLog
->Contains(cp
)) {
649 gTypesToLog
->PutEntry(cp
);
651 fprintf(stdout
, "%s ", cp
);
658 fprintf(stdout
, "\n");
660 if (!gSerialNumbers
) {
661 gSerialNumbers
= new SerialHash(256);
664 gTypesToLog
= nullptr;
665 gSerialNumbers
= nullptr;
668 const char* objects
= getenv("XPCOM_MEM_LOG_OBJECTS");
670 gObjectsToLog
= new IntPtrSet(256);
672 if (!(gRefcntsLog
|| gAllocLog
|| gCOMPtrLog
)) {
674 "### XPCOM_MEM_LOG_OBJECTS defined -- "
675 "but none of XPCOM_MEM_(REFCNT|ALLOC|COMPTR)_LOG is defined\n");
678 "### XPCOM_MEM_LOG_OBJECTS defined -- "
679 "only logging these objects: ");
680 const char* cp
= objects
;
682 char* cm
= (char*)strchr(cp
, ',');
701 for (intptr_t serialno
= bottom
; serialno
<= top
; serialno
++) {
702 gObjectsToLog
->PutEntry(serialno
);
703 fprintf(stdout
, "%" PRIdPTR
" ", serialno
);
711 fprintf(stdout
, "\n");
715 if (getenv("XPCOM_MEM_LOG_JS_STACK")) {
716 fprintf(stdout
, "### XPCOM_MEM_LOG_JS_STACK defined\n");
721 gLogging
= OnlyBloatLogging
;
724 if (gRefcntsLog
|| gAllocLog
|| gCOMPtrLog
) {
725 gLogging
= FullLogging
;
729 static void InitTraceLog() {
735 DoInitTraceLog(XRE_GetProcessTypeString());
740 static void EnsureWrite(FILE* aStream
, const char* aBuf
, size_t aLen
) {
742 int fd
= _fileno(aStream
);
744 int fd
= fileno(aStream
);
748 auto written
= _write(fd
, aBuf
, aLen
);
750 auto written
= write(fd
, aBuf
, aLen
);
752 if (written
<= 0 || size_t(written
) > aLen
) {
760 static void PrintStackFrameCached(uint32_t aFrameNumber
, void* aPC
, void* aSP
,
762 auto stream
= static_cast<FILE*>(aClosure
);
763 static const int buflen
= 1024;
764 char buf
[buflen
+ 5] = " "; // 5 for leading " " and trailing '\n'
766 gCodeAddressService
->GetLocation(aFrameNumber
, aPC
, buf
+ 4, buflen
);
767 len
= std::min(len
, buflen
+ 1 - 2) + 4;
771 EnsureWrite(stream
, buf
, len
);
774 static void RecordStackFrame(uint32_t /*aFrameNumber*/, void* aPC
,
775 void* /*aSP*/, void* aClosure
) {
776 auto locations
= static_cast<std::vector
<void*>*>(aClosure
);
777 locations
->push_back(aPC
);
782 * This is a variant of |MozWalkTheStack| that uses |CodeAddressService| to
783 * cache the results of |NS_DescribeCodeAddress|. If |WalkTheStackCached| is
784 * being called frequently, it will be a few orders of magnitude faster than
785 * |MozWalkTheStack|. However, the cache uses a lot of memory, which can cause
786 * OOM crashes. Therefore, this should only be used for things like refcount
787 * logging which walk the stack extremely frequently.
789 static void WalkTheStackCached(FILE* aStream
, const void* aFirstFramePC
) {
790 if (!gCodeAddressService
) {
791 gCodeAddressService
= new CodeAddressService
<>();
793 MozStackWalk(PrintStackFrameCached
, aFirstFramePC
, /* maxFrames */ 0,
797 static void WalkTheStackSavingLocations(std::vector
<void*>& aLocations
,
798 const void* aFirstFramePC
) {
799 if (!gCodeAddressService
) {
800 gCodeAddressService
= new CodeAddressService
<>();
802 MozStackWalk(RecordStackFrame
, aFirstFramePC
, /* maxFrames */ 0, &aLocations
);
805 //----------------------------------------------------------------------
807 EXPORT_XPCOM_API(void)
811 #if defined(NS_BUILD_REFCNT_LOGGING)
812 mozilla::detail::RefCountLogger::SetLeakCheckingFunctions(NS_LogAddRef
,
816 // FIXME: This is called multiple times, we should probably not allow that.
818 nsTraceRefcnt::SetActivityIsLegal(true);
822 EXPORT_XPCOM_API(void)
823 NS_LogTerm() { mozilla::LogTerm(); }
826 // If MOZ_DMD_SHUTDOWN_LOG is set, dump a DMD report to a file.
827 // The value of this environment variable is used as the prefix
828 // of the file name, so you probably want something like "/tmp/".
829 // By default, this is run in all processes, but you can record a
830 // log only for a specific process type by setting MOZ_DMD_LOG_PROCESS
831 // to the process type you want to log, such as "default" or "tab".
832 // This method can't use the higher level XPCOM file utilities
833 // because it is run very late in shutdown to avoid recording
834 // information about refcount logging entries.
835 static void LogDMDFile() {
836 const char* dmdFilePrefix
= PR_GetEnv("MOZ_DMD_SHUTDOWN_LOG");
837 if (!dmdFilePrefix
) {
841 const char* logProcessEnv
= PR_GetEnv("MOZ_DMD_LOG_PROCESS");
842 if (logProcessEnv
&& !!strcmp(logProcessEnv
, XRE_GetProcessTypeString())) {
846 nsPrintfCString
fileName("%sdmd-%" PRIPID
".log.gz", dmdFilePrefix
,
847 base::GetCurrentProcId());
848 FILE* logFile
= fopen(fileName
.get(), "w");
849 if (NS_WARN_IF(!logFile
)) {
853 nsMemoryInfoDumper::DumpDMDToFile(logFile
);
859 NS_ASSERTION(gInitCount
> 0, "NS_LogTerm without matching NS_LogInit");
861 if (--gInitCount
== 0) {
863 /* FIXME bug 491977: This is only going to operate on the
864 * BlockingResourceBase which is compiled into
865 * libxul/libxpcom_core.so. Anyone using external linkage will
866 * have their own copy of BlockingResourceBase statics which will
867 * not be freed by this method.
869 * It sounds like what we really want is to be able to register a
870 * callback function to call at XPCOM shutdown. Note that with
871 * this solution, however, we need to guarantee that
872 * BlockingResourceBase::Shutdown() runs after all other shutdown
875 BlockingResourceBase::Shutdown();
879 nsTraceRefcnt::DumpStatistics();
880 nsTraceRefcnt::ResetStatistics();
882 nsTraceRefcnt::Shutdown();
883 nsTraceRefcnt::SetActivityIsLegal(false);
884 gActivityTLS
= BAD_TLS_INDEX
;
892 } // namespace mozilla
894 EXPORT_XPCOM_API(MOZ_NEVER_INLINE
void)
895 NS_LogAddRef(void* aPtr
, nsrefcnt aRefcnt
, const char* aClass
,
896 uint32_t aClassSize
) {
897 ASSERT_ACTIVITY_IS_LEGAL(aClass
, "addrefed");
901 if (gLogging
== NoLogging
) {
904 if (aRefcnt
== 1 || gLogging
== FullLogging
) {
905 AutoTraceLogLock
lock(gTraceLog
);
907 if (aRefcnt
== 1 && gBloatLog
) {
908 BloatEntry
* entry
= GetBloatEntry(aClass
, aClassSize
);
914 // Here's the case where MOZ_COUNT_CTOR was not used,
915 // yet we still want to see creation information:
917 bool loggingThisType
= (!gTypesToLog
|| gTypesToLog
->Contains(aClass
));
918 intptr_t serialno
= 0;
919 if (gSerialNumbers
&& loggingThisType
) {
920 serialno
= GetSerialNumber(aPtr
, aRefcnt
== 1, CallerPC());
921 MOZ_ASSERT(serialno
!= 0,
922 "Serial number requested for unrecognized pointer! "
923 "Are you memmoving a refcounted object?");
924 auto record
= gSerialNumbers
->Get(aPtr
);
930 bool loggingThisObject
= (!gObjectsToLog
|| LogThisObj(serialno
));
931 if (aRefcnt
== 1 && gAllocLog
&& loggingThisType
&& loggingThisObject
) {
932 fprintf(gAllocLog
, "\n<%s> %p %" PRIdPTR
" Create [thread %p]\n", aClass
,
933 aPtr
, serialno
, PR_GetCurrentThread());
934 WalkTheStackCached(gAllocLog
, CallerPC());
937 if (gRefcntsLog
&& loggingThisType
&& loggingThisObject
) {
938 // Can't use MOZ_LOG(), b/c it truncates the line
940 "\n<%s> %p %" PRIuPTR
" AddRef %" PRIuPTR
" [thread %p]\n",
941 aClass
, aPtr
, serialno
, aRefcnt
, PR_GetCurrentThread());
942 WalkTheStackCached(gRefcntsLog
, CallerPC());
948 EXPORT_XPCOM_API(MOZ_NEVER_INLINE
void)
949 NS_LogRelease(void* aPtr
, nsrefcnt aRefcnt
, const char* aClass
) {
950 ASSERT_ACTIVITY_IS_LEGAL(aClass
, "released");
954 if (gLogging
== NoLogging
) {
957 if (aRefcnt
== 0 || gLogging
== FullLogging
) {
958 AutoTraceLogLock
lock(gTraceLog
);
960 if (aRefcnt
== 0 && gBloatLog
) {
961 BloatEntry
* entry
= GetBloatEntry(aClass
, 0);
967 bool loggingThisType
= (!gTypesToLog
|| gTypesToLog
->Contains(aClass
));
968 intptr_t serialno
= 0;
969 if (gSerialNumbers
&& loggingThisType
) {
970 serialno
= GetSerialNumber(aPtr
, false, CallerPC());
971 MOZ_ASSERT(serialno
!= 0,
972 "Serial number requested for unrecognized pointer! "
973 "Are you memmoving a refcounted object?");
974 auto record
= gSerialNumbers
->Get(aPtr
);
980 bool loggingThisObject
= (!gObjectsToLog
|| LogThisObj(serialno
));
981 if (gRefcntsLog
&& loggingThisType
&& loggingThisObject
) {
982 // Can't use MOZ_LOG(), b/c it truncates the line
984 "\n<%s> %p %" PRIuPTR
" Release %" PRIuPTR
" [thread %p]\n",
985 aClass
, aPtr
, serialno
, aRefcnt
, PR_GetCurrentThread());
986 WalkTheStackCached(gRefcntsLog
, CallerPC());
990 // Here's the case where MOZ_COUNT_DTOR was not used,
991 // yet we still want to see deletion information:
993 if (aRefcnt
== 0 && gAllocLog
&& loggingThisType
&& loggingThisObject
) {
994 fprintf(gAllocLog
, "\n<%s> %p %" PRIdPTR
" Destroy [thread %p]\n", aClass
,
995 aPtr
, serialno
, PR_GetCurrentThread());
996 WalkTheStackCached(gAllocLog
, CallerPC());
999 if (aRefcnt
== 0 && gSerialNumbers
&& loggingThisType
) {
1000 RecycleSerialNumberPtr(aPtr
);
1005 EXPORT_XPCOM_API(MOZ_NEVER_INLINE
void)
1006 NS_LogCtor(void* aPtr
, const char* aType
, uint32_t aInstanceSize
) {
1007 ASSERT_ACTIVITY_IS_LEGAL(aType
, "constructed");
1008 if (!gInitialized
) {
1012 if (gLogging
== NoLogging
) {
1016 AutoTraceLogLock
lock(gTraceLog
);
1019 BloatEntry
* entry
= GetBloatEntry(aType
, aInstanceSize
);
1025 bool loggingThisType
= (!gTypesToLog
|| gTypesToLog
->Contains(aType
));
1026 intptr_t serialno
= 0;
1027 if (gSerialNumbers
&& loggingThisType
) {
1028 serialno
= GetSerialNumber(aPtr
, true, CallerPC());
1029 MOZ_ASSERT(serialno
!= 0,
1030 "GetSerialNumber should never return 0 when passed true");
1033 bool loggingThisObject
= (!gObjectsToLog
|| LogThisObj(serialno
));
1034 if (gAllocLog
&& loggingThisType
&& loggingThisObject
) {
1035 fprintf(gAllocLog
, "\n<%s> %p %" PRIdPTR
" Ctor (%d)\n", aType
, aPtr
,
1036 serialno
, aInstanceSize
);
1037 WalkTheStackCached(gAllocLog
, CallerPC());
1041 EXPORT_XPCOM_API(MOZ_NEVER_INLINE
void)
1042 NS_LogDtor(void* aPtr
, const char* aType
, uint32_t aInstanceSize
) {
1043 ASSERT_ACTIVITY_IS_LEGAL(aType
, "destroyed");
1044 if (!gInitialized
) {
1048 if (gLogging
== NoLogging
) {
1052 AutoTraceLogLock
lock(gTraceLog
);
1055 BloatEntry
* entry
= GetBloatEntry(aType
, aInstanceSize
);
1061 bool loggingThisType
= (!gTypesToLog
|| gTypesToLog
->Contains(aType
));
1062 intptr_t serialno
= 0;
1063 if (gSerialNumbers
&& loggingThisType
) {
1064 serialno
= GetSerialNumber(aPtr
, false, CallerPC());
1065 MOZ_ASSERT(serialno
!= 0,
1066 "Serial number requested for unrecognized pointer! "
1067 "Are you memmoving a MOZ_COUNT_CTOR-tracked object?");
1068 RecycleSerialNumberPtr(aPtr
);
1071 bool loggingThisObject
= (!gObjectsToLog
|| LogThisObj(serialno
));
1073 // (If we're on a losing architecture, don't do this because we'll be
1074 // using LogDeleteXPCOM instead to get file and line numbers.)
1075 if (gAllocLog
&& loggingThisType
&& loggingThisObject
) {
1076 fprintf(gAllocLog
, "\n<%s> %p %" PRIdPTR
" Dtor (%d)\n", aType
, aPtr
,
1077 serialno
, aInstanceSize
);
1078 WalkTheStackCached(gAllocLog
, CallerPC());
1082 EXPORT_XPCOM_API(MOZ_NEVER_INLINE
void)
1083 NS_LogCOMPtrAddRef(void* aCOMPtr
, nsISupports
* aObject
) {
1084 #ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
1085 // Get the most-derived object.
1086 void* object
= dynamic_cast<void*>(aObject
);
1088 // This is a very indirect way of finding out what the class is
1089 // of the object being logged. If we're logging a specific type,
1091 if (!gTypesToLog
|| !gSerialNumbers
) {
1094 if (!gInitialized
) {
1097 if (gLogging
== FullLogging
) {
1098 AutoTraceLogLock
lock(gTraceLog
);
1100 intptr_t serialno
= GetSerialNumber(object
, false, CallerPC());
1101 if (serialno
== 0) {
1105 auto record
= gSerialNumbers
->Get(object
);
1106 int32_t count
= record
? ++record
->COMPtrCount
: -1;
1107 bool loggingThisObject
= (!gObjectsToLog
|| LogThisObj(serialno
));
1109 if (gCOMPtrLog
&& loggingThisObject
) {
1110 fprintf(gCOMPtrLog
, "\n<?> %p %" PRIdPTR
" nsCOMPtrAddRef %d %p\n",
1111 object
, serialno
, count
, aCOMPtr
);
1112 WalkTheStackCached(gCOMPtrLog
, CallerPC());
1115 #endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
1118 EXPORT_XPCOM_API(MOZ_NEVER_INLINE
void)
1119 NS_LogCOMPtrRelease(void* aCOMPtr
, nsISupports
* aObject
) {
1120 #ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
1121 // Get the most-derived object.
1122 void* object
= dynamic_cast<void*>(aObject
);
1124 // This is a very indirect way of finding out what the class is
1125 // of the object being logged. If we're logging a specific type,
1127 if (!gTypesToLog
|| !gSerialNumbers
) {
1130 if (!gInitialized
) {
1133 if (gLogging
== FullLogging
) {
1134 AutoTraceLogLock
lock(gTraceLog
);
1136 intptr_t serialno
= GetSerialNumber(object
, false, CallerPC());
1137 if (serialno
== 0) {
1141 auto record
= gSerialNumbers
->Get(object
);
1142 int32_t count
= record
? --record
->COMPtrCount
: -1;
1143 bool loggingThisObject
= (!gObjectsToLog
|| LogThisObj(serialno
));
1145 if (gCOMPtrLog
&& loggingThisObject
) {
1146 fprintf(gCOMPtrLog
, "\n<?> %p %" PRIdPTR
" nsCOMPtrRelease %d %p\n",
1147 object
, serialno
, count
, aCOMPtr
);
1148 WalkTheStackCached(gCOMPtrLog
, CallerPC());
1151 #endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
1154 static void ClearLogs(bool aKeepCounters
) {
1155 gCodeAddressService
= nullptr;
1156 // These counters from the fork server process will be preserved
1157 // for the content processes to keep them consistent.
1158 if (!aKeepCounters
) {
1159 gBloatView
= nullptr;
1160 gTypesToLog
= nullptr;
1161 gSerialNumbers
= nullptr;
1163 gObjectsToLog
= nullptr;
1164 gLogJSStacks
= false;
1165 gLogLeaksOnly
= false;
1166 maybeUnregisterAndCloseFile(gBloatLog
);
1167 maybeUnregisterAndCloseFile(gRefcntsLog
);
1168 maybeUnregisterAndCloseFile(gAllocLog
);
1169 maybeUnregisterAndCloseFile(gCOMPtrLog
);
1172 void nsTraceRefcnt::Shutdown() { ClearLogs(false); }
1174 void nsTraceRefcnt::SetActivityIsLegal(bool aLegal
) {
1175 if (gActivityTLS
== BAD_TLS_INDEX
) {
1176 PR_NewThreadPrivateIndex(&gActivityTLS
, nullptr);
1179 PR_SetThreadPrivate(gActivityTLS
, reinterpret_cast<void*>(!aLegal
));
1182 void nsTraceRefcnt::StartLoggingClass(const char* aClass
) {
1183 gLogging
= FullLogging
;
1186 gTypesToLog
= new CharPtrSet(256);
1189 fprintf(stdout
, "### StartLoggingClass %s\n", aClass
);
1190 if (!gTypesToLog
->Contains(aClass
)) {
1191 gTypesToLog
->PutEntry(aClass
);
1194 // We are deliberately not initializing gSerialNumbers here, because
1195 // it would cause assertions. gObjectsToLog can't be used because it
1196 // relies on serial numbers.
1199 # define ENVVAR(x) u"" x
1201 # define ENVVAR(x) x
1205 InitLog(ENVVAR("XPCOM_MEM_LATE_REFCNT_LOG"), "refcounts", &gRefcntsLog
,
1206 XRE_GetProcessTypeString());
1212 #ifdef MOZ_ENABLE_FORKSERVER
1213 void nsTraceRefcnt::CloseLogFilesAfterFork() {
1214 // Disable logging, and close our log files. We can't have open file
1215 // descriptors on the fork server during the FileDescriptorShuffle
1217 gLogging
= NoLogging
;
1222 void nsTraceRefcnt::ReopenLogFilesAfterFork(const char* aProcType
) {
1223 // Create log files with the correct process type in the name.
1224 // This will re-initialize gLogging after it was cleared in
1225 // CloseLogFilesAfterFork.
1226 DoInitTraceLog(aProcType
);