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 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 nsStringBuffer was used to
357 // store a char16_t string.
358 auto* buffer
= static_cast<const nsStringBuffer
*>(aHashEntry
.Key());
359 nsDependentCString
bufferString(static_cast<char*>(buffer
->Data()));
361 "Contents of leaked nsStringBuffer with storage size %d as a "
363 buffer
->StorageSize(), bufferString
.get());
366 if (!record
->allocationStack
.empty()) {
367 static const size_t bufLen
= 1024;
369 fprintf(outputFile
, "allocation stack:\n");
370 for (size_t i
= 0, length
= record
->allocationStack
.size(); i
< length
;
372 gCodeAddressService
->GetLocation(i
, record
->allocationStack
[i
], buf
,
374 fprintf(outputFile
, "%s\n", buf
);
379 if (record
->jsStack
) {
380 fprintf(outputFile
, "JS allocation stack:\n%s\n", record
->jsStack
.get());
382 fprintf(outputFile
, "There is no JS context on the stack.\n");
388 class nsDefaultComparator
<BloatEntry
*, BloatEntry
*> {
390 bool Equals(BloatEntry
* const& aEntry1
, BloatEntry
* const& aEntry2
) const {
391 return strcmp(aEntry1
->GetClassName(), aEntry2
->GetClassName()) == 0;
393 bool LessThan(BloatEntry
* const& aEntry1
, BloatEntry
* const& aEntry2
) const {
394 return strcmp(aEntry1
->GetClassName(), aEntry2
->GetClassName()) < 0;
398 nsresult
nsTraceRefcnt::DumpStatistics() {
399 if (!gBloatLog
|| !gBloatView
) {
400 return NS_ERROR_FAILURE
;
403 AutoTraceLogLock
lock(gTraceLog
);
405 MOZ_ASSERT(!gDumpedStatistics
,
406 "Calling DumpStatistics more than once may result in "
407 "bogus positive or negative leaks being reported");
408 gDumpedStatistics
= true;
410 // Don't try to log while we hold the lock, we'd deadlock.
411 AutoRestore
<LoggingType
> saveLogging(gLogging
);
412 gLogging
= NoLogging
;
414 BloatEntry
total("TOTAL", 0);
415 for (const auto& data
: gBloatView
->Values()) {
416 if (nsCRT::strcmp(data
->GetClassName(), "TOTAL") != 0) {
423 msg
= "ALL (cumulative) LEAK STATISTICS";
425 msg
= "ALL (cumulative) LEAK AND BLOAT STATISTICS";
427 const bool leaked
= total
.PrintDumpHeader(gBloatLog
, msg
);
429 nsTArray
<BloatEntry
*> entries(gBloatView
->Count());
430 for (const auto& data
: gBloatView
->Values()) {
431 entries
.AppendElement(data
.get());
434 const uint32_t count
= entries
.Length();
436 if (!gLogLeaksOnly
|| leaked
) {
437 // Sort the entries alphabetically by classname.
440 for (uint32_t i
= 0; i
< count
; ++i
) {
441 BloatEntry
* entry
= entries
[i
];
442 entry
->Dump(i
, gBloatLog
);
445 fprintf(gBloatLog
, "\n");
448 fprintf(gBloatLog
, "nsTraceRefcnt::DumpStatistics: %d entries\n", count
);
450 if (gSerialNumbers
) {
451 bool onlyLoggingStringBuffers
= gTypesToLog
&& gTypesToLog
->Count() == 1 &&
452 gTypesToLog
->Contains("nsStringBuffer");
454 fprintf(gBloatLog
, "\nSerial Numbers of Leaked Objects:\n");
455 for (auto iter
= gSerialNumbers
->ConstIter(); !iter
.Done(); iter
.Next()) {
456 DumpSerialNumbers(iter
, gBloatLog
, onlyLoggingStringBuffers
);
463 void nsTraceRefcnt::ResetStatistics() {
464 AutoTraceLogLock
lock(gTraceLog
);
465 gBloatView
= nullptr;
468 static intptr_t GetSerialNumber(void* aPtr
, bool aCreate
, void* aFirstFramePC
) {
470 auto record
= gSerialNumbers
->Get(aPtr
);
471 return record
? record
->serialNumber
: 0;
474 gSerialNumbers
->WithEntryHandle(aPtr
, [aFirstFramePC
](auto&& entry
) {
477 "If an object already has a serial number, we should be destroying "
481 auto& record
= entry
.Insert(mozilla::MakeUnique
<SerialNumberRecord
>());
482 WalkTheStackSavingLocations(record
->allocationStack
, aFirstFramePC
);
484 record
->SaveJSStack();
487 return gNextSerialNumber
;
490 static void RecycleSerialNumberPtr(void* aPtr
) { gSerialNumbers
->Remove(aPtr
); }
492 static bool LogThisObj(intptr_t aSerialNumber
) {
493 return gObjectsToLog
->Contains(aSerialNumber
);
496 using EnvCharType
= mozilla::filesystem::Path::value_type
;
498 static bool InitLog(const EnvCharType
* aEnvVar
, const char* aMsg
,
499 FILE** aResult
, const char* aProcType
) {
501 // This is gross, I know.
502 const wchar_t* envvar
= reinterpret_cast<const wchar_t*>(aEnvVar
);
503 const char16_t
* value
= reinterpret_cast<const char16_t
*>(::_wgetenv(envvar
));
504 # define ENVVAR_PRINTF "%S"
506 const char* envvar
= aEnvVar
;
507 const char* value
= ::getenv(aEnvVar
);
508 # define ENVVAR_PRINTF "%s"
512 nsTDependentString
<EnvCharType
> fname(value
);
513 if (fname
.EqualsLiteral("1")) {
515 fprintf(stdout
, "### " ENVVAR_PRINTF
" defined -- logging %s to stdout\n",
519 if (fname
.EqualsLiteral("2")) {
521 fprintf(stdout
, "### " ENVVAR_PRINTF
" defined -- logging %s to stderr\n",
525 if (!XRE_IsParentProcess()) {
526 nsTString
<EnvCharType
> extension
;
527 extension
.AssignLiteral(".log");
528 bool hasLogExtension
= StringEndsWith(fname
, extension
);
529 if (hasLogExtension
) {
530 fname
.Cut(fname
.Length() - 4, 4);
533 fname
.AppendASCII(aProcType
);
534 fname
.AppendLiteral("_pid");
535 fname
.AppendInt((uint32_t)getpid());
536 if (hasLogExtension
) {
537 fname
.AppendLiteral(".log");
541 FILE* stream
= ::_wfopen(fname
.get(), L
"wN");
542 const wchar_t* fp
= (const wchar_t*)fname
.get();
544 FILE* stream
= ::fopen(fname
.get(), "w");
545 const char* fp
= fname
.get();
548 MozillaRegisterDebugFD(fileno(stream
));
549 #ifdef MOZ_ENABLE_FORKSERVER
550 base::RegisterForkServerNoCloseFD(fileno(stream
));
554 "### " ENVVAR_PRINTF
" defined -- logging %s to " ENVVAR_PRINTF
563 " defined -- unable to log %s to " ENVVAR_PRINTF
"\n",
565 MOZ_ASSERT(false, "Tried and failed to create an XPCOM log");
572 static void maybeUnregisterAndCloseFile(FILE*& aFile
) {
577 MozillaUnRegisterDebugFILE(aFile
);
582 static void DoInitTraceLog(const char* aProcType
) {
584 # define ENVVAR(x) u"" x
589 bool defined
= InitLog(ENVVAR("XPCOM_MEM_BLOAT_LOG"), "bloat/leaks",
590 &gBloatLog
, aProcType
);
593 InitLog(ENVVAR("XPCOM_MEM_LEAK_LOG"), "leaks", &gBloatLog
, aProcType
);
595 if (defined
|| gLogLeaksOnly
) {
596 // Use the same bloat view, if there is one, to keep it consistent
597 // between the fork server and content processes.
599 } else if (gBloatView
) {
600 nsTraceRefcnt::ResetStatistics();
603 InitLog(ENVVAR("XPCOM_MEM_REFCNT_LOG"), "refcounts", &gRefcntsLog
, aProcType
);
605 InitLog(ENVVAR("XPCOM_MEM_ALLOC_LOG"), "new/delete", &gAllocLog
, aProcType
);
607 const char* classes
= getenv("XPCOM_MEM_LOG_CLASSES");
609 #ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
611 InitLog(ENVVAR("XPCOM_MEM_COMPTR_LOG"), "nsCOMPtr", &gCOMPtrLog
, aProcType
);
613 if (getenv("XPCOM_MEM_COMPTR_LOG")) {
615 "### XPCOM_MEM_COMPTR_LOG defined -- "
616 "but XPCOM_MEM_LOG_CLASSES is not defined\n");
620 const char* comptr_log
= getenv("XPCOM_MEM_COMPTR_LOG");
623 "### XPCOM_MEM_COMPTR_LOG defined -- "
624 "but it will not work without dynamic_cast\n");
626 #endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
631 // if XPCOM_MEM_LOG_CLASSES was set to some value, the value is interpreted
632 // as a list of class names to track
634 // Use the same |gTypesToLog| and |gSerialNumbers| to keep them
635 // consistent through the fork server and content processes.
636 // Without this, counters will be incorrect.
638 gTypesToLog
= new CharPtrSet(256);
642 "### XPCOM_MEM_LOG_CLASSES defined -- "
643 "only logging these classes: ");
644 const char* cp
= classes
;
646 char* cm
= (char*)strchr(cp
, ',');
650 if (!gTypesToLog
->Contains(cp
)) {
651 gTypesToLog
->PutEntry(cp
);
653 fprintf(stdout
, "%s ", cp
);
660 fprintf(stdout
, "\n");
662 if (!gSerialNumbers
) {
663 gSerialNumbers
= new SerialHash(256);
666 gTypesToLog
= nullptr;
667 gSerialNumbers
= nullptr;
670 const char* objects
= getenv("XPCOM_MEM_LOG_OBJECTS");
672 gObjectsToLog
= new IntPtrSet(256);
674 if (!(gRefcntsLog
|| gAllocLog
|| gCOMPtrLog
)) {
676 "### XPCOM_MEM_LOG_OBJECTS defined -- "
677 "but none of XPCOM_MEM_(REFCNT|ALLOC|COMPTR)_LOG is defined\n");
680 "### XPCOM_MEM_LOG_OBJECTS defined -- "
681 "only logging these objects: ");
682 const char* cp
= objects
;
684 char* cm
= (char*)strchr(cp
, ',');
703 for (intptr_t serialno
= bottom
; serialno
<= top
; serialno
++) {
704 gObjectsToLog
->PutEntry(serialno
);
705 fprintf(stdout
, "%" PRIdPTR
" ", serialno
);
713 fprintf(stdout
, "\n");
717 if (getenv("XPCOM_MEM_LOG_JS_STACK")) {
718 fprintf(stdout
, "### XPCOM_MEM_LOG_JS_STACK defined\n");
723 gLogging
= OnlyBloatLogging
;
726 if (gRefcntsLog
|| gAllocLog
|| gCOMPtrLog
) {
727 gLogging
= FullLogging
;
731 static void InitTraceLog() {
737 DoInitTraceLog(XRE_GetProcessTypeString());
742 static void EnsureWrite(FILE* aStream
, const char* aBuf
, size_t aLen
) {
744 int fd
= _fileno(aStream
);
746 int fd
= fileno(aStream
);
750 auto written
= _write(fd
, aBuf
, aLen
);
752 auto written
= write(fd
, aBuf
, aLen
);
754 if (written
<= 0 || size_t(written
) > aLen
) {
762 static void PrintStackFrameCached(uint32_t aFrameNumber
, void* aPC
, void* aSP
,
764 auto stream
= static_cast<FILE*>(aClosure
);
765 static const int buflen
= 1024;
766 char buf
[buflen
+ 5] = " "; // 5 for leading " " and trailing '\n'
768 gCodeAddressService
->GetLocation(aFrameNumber
, aPC
, buf
+ 4, buflen
);
769 len
= std::min(len
, buflen
+ 1 - 2) + 4;
773 EnsureWrite(stream
, buf
, len
);
776 static void RecordStackFrame(uint32_t /*aFrameNumber*/, void* aPC
,
777 void* /*aSP*/, void* aClosure
) {
778 auto locations
= static_cast<std::vector
<void*>*>(aClosure
);
779 locations
->push_back(aPC
);
784 * This is a variant of |MozWalkTheStack| that uses |CodeAddressService| to
785 * cache the results of |NS_DescribeCodeAddress|. If |WalkTheStackCached| is
786 * being called frequently, it will be a few orders of magnitude faster than
787 * |MozWalkTheStack|. However, the cache uses a lot of memory, which can cause
788 * OOM crashes. Therefore, this should only be used for things like refcount
789 * logging which walk the stack extremely frequently.
791 static void WalkTheStackCached(FILE* aStream
, const void* aFirstFramePC
) {
792 if (!gCodeAddressService
) {
793 gCodeAddressService
= new CodeAddressService
<>();
795 MozStackWalk(PrintStackFrameCached
, aFirstFramePC
, /* maxFrames */ 0,
799 static void WalkTheStackSavingLocations(std::vector
<void*>& aLocations
,
800 const void* aFirstFramePC
) {
801 if (!gCodeAddressService
) {
802 gCodeAddressService
= new CodeAddressService
<>();
804 MozStackWalk(RecordStackFrame
, aFirstFramePC
, /* maxFrames */ 0, &aLocations
);
807 //----------------------------------------------------------------------
809 EXPORT_XPCOM_API(void)
813 // FIXME: This is called multiple times, we should probably not allow that.
815 nsTraceRefcnt::SetActivityIsLegal(true);
819 EXPORT_XPCOM_API(void)
820 NS_LogTerm() { mozilla::LogTerm(); }
823 // If MOZ_DMD_SHUTDOWN_LOG is set, dump a DMD report to a file.
824 // The value of this environment variable is used as the prefix
825 // of the file name, so you probably want something like "/tmp/".
826 // By default, this is run in all processes, but you can record a
827 // log only for a specific process type by setting MOZ_DMD_LOG_PROCESS
828 // to the process type you want to log, such as "default" or "tab".
829 // This method can't use the higher level XPCOM file utilities
830 // because it is run very late in shutdown to avoid recording
831 // information about refcount logging entries.
832 static void LogDMDFile() {
833 const char* dmdFilePrefix
= PR_GetEnv("MOZ_DMD_SHUTDOWN_LOG");
834 if (!dmdFilePrefix
) {
838 const char* logProcessEnv
= PR_GetEnv("MOZ_DMD_LOG_PROCESS");
839 if (logProcessEnv
&& !!strcmp(logProcessEnv
, XRE_GetProcessTypeString())) {
843 nsPrintfCString
fileName("%sdmd-%" PRIPID
".log.gz", dmdFilePrefix
,
844 base::GetCurrentProcId());
845 FILE* logFile
= fopen(fileName
.get(), "w");
846 if (NS_WARN_IF(!logFile
)) {
850 nsMemoryInfoDumper::DumpDMDToFile(logFile
);
856 NS_ASSERTION(gInitCount
> 0, "NS_LogTerm without matching NS_LogInit");
858 if (--gInitCount
== 0) {
860 /* FIXME bug 491977: This is only going to operate on the
861 * BlockingResourceBase which is compiled into
862 * libxul/libxpcom_core.so. Anyone using external linkage will
863 * have their own copy of BlockingResourceBase statics which will
864 * not be freed by this method.
866 * It sounds like what we really want is to be able to register a
867 * callback function to call at XPCOM shutdown. Note that with
868 * this solution, however, we need to guarantee that
869 * BlockingResourceBase::Shutdown() runs after all other shutdown
872 BlockingResourceBase::Shutdown();
876 nsTraceRefcnt::DumpStatistics();
877 nsTraceRefcnt::ResetStatistics();
879 nsTraceRefcnt::Shutdown();
880 nsTraceRefcnt::SetActivityIsLegal(false);
881 gActivityTLS
= BAD_TLS_INDEX
;
889 } // namespace mozilla
891 EXPORT_XPCOM_API(MOZ_NEVER_INLINE
void)
892 NS_LogAddRef(void* aPtr
, nsrefcnt aRefcnt
, const char* aClass
,
893 uint32_t aClassSize
) {
894 ASSERT_ACTIVITY_IS_LEGAL(aClass
, "addrefed");
898 if (gLogging
== NoLogging
) {
901 if (aRefcnt
== 1 || gLogging
== FullLogging
) {
902 AutoTraceLogLock
lock(gTraceLog
);
904 if (aRefcnt
== 1 && gBloatLog
) {
905 BloatEntry
* entry
= GetBloatEntry(aClass
, aClassSize
);
911 // Here's the case where MOZ_COUNT_CTOR was not used,
912 // yet we still want to see creation information:
914 bool loggingThisType
= (!gTypesToLog
|| gTypesToLog
->Contains(aClass
));
915 intptr_t serialno
= 0;
916 if (gSerialNumbers
&& loggingThisType
) {
917 serialno
= GetSerialNumber(aPtr
, aRefcnt
== 1, CallerPC());
918 MOZ_ASSERT(serialno
!= 0,
919 "Serial number requested for unrecognized pointer! "
920 "Are you memmoving a refcounted object?");
921 auto record
= gSerialNumbers
->Get(aPtr
);
927 bool loggingThisObject
= (!gObjectsToLog
|| LogThisObj(serialno
));
928 if (aRefcnt
== 1 && gAllocLog
&& loggingThisType
&& loggingThisObject
) {
929 fprintf(gAllocLog
, "\n<%s> %p %" PRIdPTR
" Create [thread %p]\n", aClass
,
930 aPtr
, serialno
, PR_GetCurrentThread());
931 WalkTheStackCached(gAllocLog
, CallerPC());
934 if (gRefcntsLog
&& loggingThisType
&& loggingThisObject
) {
935 // Can't use MOZ_LOG(), b/c it truncates the line
937 "\n<%s> %p %" PRIuPTR
" AddRef %" PRIuPTR
" [thread %p]\n",
938 aClass
, aPtr
, serialno
, aRefcnt
, PR_GetCurrentThread());
939 WalkTheStackCached(gRefcntsLog
, CallerPC());
945 EXPORT_XPCOM_API(MOZ_NEVER_INLINE
void)
946 NS_LogRelease(void* aPtr
, nsrefcnt aRefcnt
, const char* aClass
) {
947 ASSERT_ACTIVITY_IS_LEGAL(aClass
, "released");
951 if (gLogging
== NoLogging
) {
954 if (aRefcnt
== 0 || gLogging
== FullLogging
) {
955 AutoTraceLogLock
lock(gTraceLog
);
957 if (aRefcnt
== 0 && gBloatLog
) {
958 BloatEntry
* entry
= GetBloatEntry(aClass
, 0);
964 bool loggingThisType
= (!gTypesToLog
|| gTypesToLog
->Contains(aClass
));
965 intptr_t serialno
= 0;
966 if (gSerialNumbers
&& loggingThisType
) {
967 serialno
= GetSerialNumber(aPtr
, false, CallerPC());
968 MOZ_ASSERT(serialno
!= 0,
969 "Serial number requested for unrecognized pointer! "
970 "Are you memmoving a refcounted object?");
971 auto record
= gSerialNumbers
->Get(aPtr
);
977 bool loggingThisObject
= (!gObjectsToLog
|| LogThisObj(serialno
));
978 if (gRefcntsLog
&& loggingThisType
&& loggingThisObject
) {
979 // Can't use MOZ_LOG(), b/c it truncates the line
981 "\n<%s> %p %" PRIuPTR
" Release %" PRIuPTR
" [thread %p]\n",
982 aClass
, aPtr
, serialno
, aRefcnt
, PR_GetCurrentThread());
983 WalkTheStackCached(gRefcntsLog
, CallerPC());
987 // Here's the case where MOZ_COUNT_DTOR was not used,
988 // yet we still want to see deletion information:
990 if (aRefcnt
== 0 && gAllocLog
&& loggingThisType
&& loggingThisObject
) {
991 fprintf(gAllocLog
, "\n<%s> %p %" PRIdPTR
" Destroy [thread %p]\n", aClass
,
992 aPtr
, serialno
, PR_GetCurrentThread());
993 WalkTheStackCached(gAllocLog
, CallerPC());
996 if (aRefcnt
== 0 && gSerialNumbers
&& loggingThisType
) {
997 RecycleSerialNumberPtr(aPtr
);
1002 EXPORT_XPCOM_API(MOZ_NEVER_INLINE
void)
1003 NS_LogCtor(void* aPtr
, const char* aType
, uint32_t aInstanceSize
) {
1004 ASSERT_ACTIVITY_IS_LEGAL(aType
, "constructed");
1005 if (!gInitialized
) {
1009 if (gLogging
== NoLogging
) {
1013 AutoTraceLogLock
lock(gTraceLog
);
1016 BloatEntry
* entry
= GetBloatEntry(aType
, aInstanceSize
);
1022 bool loggingThisType
= (!gTypesToLog
|| gTypesToLog
->Contains(aType
));
1023 intptr_t serialno
= 0;
1024 if (gSerialNumbers
&& loggingThisType
) {
1025 serialno
= GetSerialNumber(aPtr
, true, CallerPC());
1026 MOZ_ASSERT(serialno
!= 0,
1027 "GetSerialNumber should never return 0 when passed true");
1030 bool loggingThisObject
= (!gObjectsToLog
|| LogThisObj(serialno
));
1031 if (gAllocLog
&& loggingThisType
&& loggingThisObject
) {
1032 fprintf(gAllocLog
, "\n<%s> %p %" PRIdPTR
" Ctor (%d)\n", aType
, aPtr
,
1033 serialno
, aInstanceSize
);
1034 WalkTheStackCached(gAllocLog
, CallerPC());
1038 EXPORT_XPCOM_API(MOZ_NEVER_INLINE
void)
1039 NS_LogDtor(void* aPtr
, const char* aType
, uint32_t aInstanceSize
) {
1040 ASSERT_ACTIVITY_IS_LEGAL(aType
, "destroyed");
1041 if (!gInitialized
) {
1045 if (gLogging
== NoLogging
) {
1049 AutoTraceLogLock
lock(gTraceLog
);
1052 BloatEntry
* entry
= GetBloatEntry(aType
, aInstanceSize
);
1058 bool loggingThisType
= (!gTypesToLog
|| gTypesToLog
->Contains(aType
));
1059 intptr_t serialno
= 0;
1060 if (gSerialNumbers
&& loggingThisType
) {
1061 serialno
= GetSerialNumber(aPtr
, false, CallerPC());
1062 MOZ_ASSERT(serialno
!= 0,
1063 "Serial number requested for unrecognized pointer! "
1064 "Are you memmoving a MOZ_COUNT_CTOR-tracked object?");
1065 RecycleSerialNumberPtr(aPtr
);
1068 bool loggingThisObject
= (!gObjectsToLog
|| LogThisObj(serialno
));
1070 // (If we're on a losing architecture, don't do this because we'll be
1071 // using LogDeleteXPCOM instead to get file and line numbers.)
1072 if (gAllocLog
&& loggingThisType
&& loggingThisObject
) {
1073 fprintf(gAllocLog
, "\n<%s> %p %" PRIdPTR
" Dtor (%d)\n", aType
, aPtr
,
1074 serialno
, aInstanceSize
);
1075 WalkTheStackCached(gAllocLog
, CallerPC());
1079 EXPORT_XPCOM_API(MOZ_NEVER_INLINE
void)
1080 NS_LogCOMPtrAddRef(void* aCOMPtr
, nsISupports
* aObject
) {
1081 #ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
1082 // Get the most-derived object.
1083 void* object
= dynamic_cast<void*>(aObject
);
1085 // This is a very indirect way of finding out what the class is
1086 // of the object being logged. If we're logging a specific type,
1088 if (!gTypesToLog
|| !gSerialNumbers
) {
1091 if (!gInitialized
) {
1094 if (gLogging
== FullLogging
) {
1095 AutoTraceLogLock
lock(gTraceLog
);
1097 intptr_t serialno
= GetSerialNumber(object
, false, CallerPC());
1098 if (serialno
== 0) {
1102 auto record
= gSerialNumbers
->Get(object
);
1103 int32_t count
= record
? ++record
->COMPtrCount
: -1;
1104 bool loggingThisObject
= (!gObjectsToLog
|| LogThisObj(serialno
));
1106 if (gCOMPtrLog
&& loggingThisObject
) {
1107 fprintf(gCOMPtrLog
, "\n<?> %p %" PRIdPTR
" nsCOMPtrAddRef %d %p\n",
1108 object
, serialno
, count
, aCOMPtr
);
1109 WalkTheStackCached(gCOMPtrLog
, CallerPC());
1112 #endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
1115 EXPORT_XPCOM_API(MOZ_NEVER_INLINE
void)
1116 NS_LogCOMPtrRelease(void* aCOMPtr
, nsISupports
* aObject
) {
1117 #ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
1118 // Get the most-derived object.
1119 void* object
= dynamic_cast<void*>(aObject
);
1121 // This is a very indirect way of finding out what the class is
1122 // of the object being logged. If we're logging a specific type,
1124 if (!gTypesToLog
|| !gSerialNumbers
) {
1127 if (!gInitialized
) {
1130 if (gLogging
== FullLogging
) {
1131 AutoTraceLogLock
lock(gTraceLog
);
1133 intptr_t serialno
= GetSerialNumber(object
, false, CallerPC());
1134 if (serialno
== 0) {
1138 auto record
= gSerialNumbers
->Get(object
);
1139 int32_t count
= record
? --record
->COMPtrCount
: -1;
1140 bool loggingThisObject
= (!gObjectsToLog
|| LogThisObj(serialno
));
1142 if (gCOMPtrLog
&& loggingThisObject
) {
1143 fprintf(gCOMPtrLog
, "\n<?> %p %" PRIdPTR
" nsCOMPtrRelease %d %p\n",
1144 object
, serialno
, count
, aCOMPtr
);
1145 WalkTheStackCached(gCOMPtrLog
, CallerPC());
1148 #endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
1151 static void ClearLogs(bool aKeepCounters
) {
1152 gCodeAddressService
= nullptr;
1153 // These counters from the fork server process will be preserved
1154 // for the content processes to keep them consistent.
1155 if (!aKeepCounters
) {
1156 gBloatView
= nullptr;
1157 gTypesToLog
= nullptr;
1158 gSerialNumbers
= nullptr;
1160 gObjectsToLog
= nullptr;
1161 gLogJSStacks
= false;
1162 gLogLeaksOnly
= false;
1163 maybeUnregisterAndCloseFile(gBloatLog
);
1164 maybeUnregisterAndCloseFile(gRefcntsLog
);
1165 maybeUnregisterAndCloseFile(gAllocLog
);
1166 maybeUnregisterAndCloseFile(gCOMPtrLog
);
1169 void nsTraceRefcnt::Shutdown() { ClearLogs(false); }
1171 void nsTraceRefcnt::SetActivityIsLegal(bool aLegal
) {
1172 if (gActivityTLS
== BAD_TLS_INDEX
) {
1173 PR_NewThreadPrivateIndex(&gActivityTLS
, nullptr);
1176 PR_SetThreadPrivate(gActivityTLS
, reinterpret_cast<void*>(!aLegal
));
1179 void nsTraceRefcnt::StartLoggingClass(const char* aClass
) {
1180 gLogging
= FullLogging
;
1183 gTypesToLog
= new CharPtrSet(256);
1186 fprintf(stdout
, "### StartLoggingClass %s\n", aClass
);
1187 if (!gTypesToLog
->Contains(aClass
)) {
1188 gTypesToLog
->PutEntry(aClass
);
1191 // We are deliberately not initializing gSerialNumbers here, because
1192 // it would cause assertions. gObjectsToLog can't be used because it
1193 // relies on serial numbers.
1196 # define ENVVAR(x) u"" x
1198 # define ENVVAR(x) x
1202 InitLog(ENVVAR("XPCOM_MEM_LATE_REFCNT_LOG"), "refcounts", &gRefcntsLog
,
1203 XRE_GetProcessTypeString());
1209 #ifdef MOZ_ENABLE_FORKSERVER
1210 void nsTraceRefcnt::ResetLogFiles(const char* aProcType
) {
1211 AutoRestore
<LoggingType
> saveLogging(gLogging
);
1212 gLogging
= NoLogging
;
1216 // Create log files with the correct process type in the name.
1217 DoInitTraceLog(aProcType
);