/* -*- 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/. */
using mozilla::AutoRestore; using mozilla::CodeAddressService; using mozilla::CycleCollectedJSContext; using mozilla::StaticAutoPtr;
using BloatHash = nsClassHashtable<nsDepCharHashKey, BloatEntry>; using CharPtrSet = nsTHashtable<nsCharPtrHashKey>; using IntPtrSet = nsTHashtable<IntPtrHashKey>; using SerialHash = nsClassHashtable<nsVoidPtrHashKey, SerialNumberRecord>;
// By default, debug builds only do bloat logging. Bloat logging // only tries to record when an object is created or destroyed, so we // optimize the common case in NS_LogAddRef and NS_LogRelease where // only bloat logging is enabled and no logging needs to be done. enum LoggingType { NoLogging, OnlyBloatLogging, FullLogging };
static LoggingType gLogging;
staticbool gLogLeaksOnly;
#define BAD_TLS_INDEX ((unsigned)-1)
// if gActivityTLS == BAD_TLS_INDEX, then we're // unitialized... otherwise this points to a NSPR TLS thread index // indicating whether addref activity is legal. If the PTR_TO_INT32 is 0 then // activity is ok, otherwise not! staticunsigned gActivityTLS = BAD_TLS_INDEX;
intptr_t serialNumber;
int32_t refCount;
int32_t COMPtrCount; // We use std:: classes here rather than the XPCOM equivalents because the // XPCOM equivalents do leak-checking, and if you try to leak-check while // leak-checking, you're gonna have a bad time.
std::vector<void*> allocationStack;
mozilla::UniquePtr<char[]> jsStack;
void SaveJSStack() { // If this thread isn't running JS, there's nothing to do. if (!CycleCollectedJSContext::Get()) { return;
}
if (!nsContentUtils::IsInitialized()) { return;
}
JSContext* cx = nsContentUtils::GetCurrentJSContext(); if (!cx) { return;
}
protected: constchar* mClassName; // mClassSize is stored as a double because of the way we compute the avg // class size for total bloat. double mClassSize; // mTotalLeaked is only used for the TOTAL entry.
int64_t mTotalLeaked;
nsTraceRefcntStats mStats;
};
staticvoid EnsureBloatView() { if (!gBloatView) {
gBloatView = new BloatHash(256);
}
}
static BloatEntry* GetBloatEntry(constchar* aTypeName,
uint32_t aInstanceSize) {
EnsureBloatView();
BloatEntry* entry = gBloatView->Get(aTypeName); if (!entry && aInstanceSize > 0) {
entry = gBloatView
->InsertOrUpdate(aTypeName, mozilla::MakeUnique<BloatEntry>(
aTypeName, aInstanceSize))
.get();
} else {
MOZ_ASSERT(
aInstanceSize == 0 || entry->GetClassSize() == aInstanceSize, "Mismatched sizes were recorded in the memory leak logging table. " "The usual cause of this is having a templated class that uses " "MOZ_COUNT_{C,D}TOR in the constructor or destructor, respectively. " "As a workaround, the MOZ_COUNT_{C,D}TOR calls can be moved to a " "non-templated base class. Another possible cause is a runnable with " "an mName that matches another refcounted class, or two refcounted " "classes with the same class name in different C++ namespaces.");
} return entry;
}
if (aDumpAsStringBuffer) { // This output will be wrong if the StringBuffer was used to // store a char16_t string. auto* buffer = static_cast<const mozilla::StringBuffer*>(aHashEntry.Key());
nsDependentCString bufferString(static_cast<char*>(buffer->Data()));
fprintf(
outputFile, "Contents of leaked mozilla::StringBuffer with storage size %d as a " "char*: %s\n",
buffer->StorageSize(), bufferString.get());
}
if (!record->allocationStack.empty()) { staticconst size_t bufLen = 1024; char buf[bufLen];
fprintf(outputFile, "allocation stack:\n"); for (size_t i = 0, length = record->allocationStack.size(); i < length;
++i) {
gCodeAddressService->GetLocation(i, record->allocationStack[i], buf,
bufLen);
fprintf(outputFile, "%s\n", buf);
}
}
if (gLogJSStacks) { if (record->jsStack) {
fprintf(outputFile, "JS allocation stack:\n%s\n", record->jsStack.get());
} else {
fprintf(outputFile, "There is no JS context on the stack.\n");
}
}
}
MOZ_ASSERT(!gDumpedStatistics, "Calling DumpStatistics more than once may result in " "bogus positive or negative leaks being reported");
gDumpedStatistics = true;
// Don't try to log while we hold the lock, we'd deadlock.
AutoRestore<LoggingType> saveLogging(gLogging);
gLogging = NoLogging;
BloatEntry total("TOTAL", 0); for (constauto& data : gBloatView->Values()) { if (nsCRT::strcmp(data->GetClassName(), "TOTAL") != 0) {
data->Total(&total);
}
}
constchar* msg; if (gLogLeaksOnly) {
msg = "ALL (cumulative) LEAK STATISTICS";
} else {
msg = "ALL (cumulative) LEAK AND BLOAT STATISTICS";
} constbool leaked = total.PrintDumpHeader(gBloatLog, msg);
nsTArray<BloatEntry*> entries(gBloatView->Count()); for (constauto& data : gBloatView->Values()) {
entries.AppendElement(data.get());
}
const uint32_t count = entries.Length();
if (!gLogLeaksOnly || leaked) { // Sort the entries alphabetically by classname.
entries.Sort();
for (uint32_t i = 0; i < count; ++i) {
BloatEntry* entry = entries[i];
entry->Dump(i, gBloatLog);
}
static intptr_t GetSerialNumber(void* aPtr, bool aCreate, void* aFirstFramePC) { if (!aCreate) { auto record = gSerialNumbers->Get(aPtr); return record ? record->serialNumber : 0;
}
gSerialNumbers->WithEntryHandle(aPtr, [aFirstFramePC](auto&& entry) { if (entry) {
MOZ_CRASH( "If an object already has a serial number, we should be destroying " "it.");
}
auto& record = entry.Insert(mozilla::MakeUnique<SerialNumberRecord>());
WalkTheStackSavingLocations(record->allocationStack, aFirstFramePC); if (gLogJSStacks) {
record->SaveJSStack();
}
}); return gNextSerialNumber;
}
using EnvCharType = mozilla::filesystem::Path::value_type;
staticbool InitLog(const EnvCharType* aEnvVar, constchar* aMsg,
FILE** aResult, constchar* aProcType) { #ifdef XP_WIN // This is gross, I know. constwchar_t* envvar = reinterpret_cast<constwchar_t*>(aEnvVar); const char16_t* value = reinterpret_cast<const char16_t*>(::_wgetenv(envvar)); # define ENVVAR_PRINTF "%S" #else constchar* envvar = aEnvVar; constchar* value = ::getenv(aEnvVar); # define ENVVAR_PRINTF "%s" #endif
if (value) {
nsTDependentString<EnvCharType> fname(value); if (fname.EqualsLiteral("1")) {
*aResult = stdout;
fprintf(stdout, "### " ENVVAR_PRINTF " defined -- logging %s to stdout\n",
envvar, aMsg); returntrue;
} if (fname.EqualsLiteral("2")) {
*aResult = stderr;
fprintf(stdout, "### " ENVVAR_PRINTF " defined -- logging %s to stderr\n",
envvar, aMsg); returntrue;
} if (!XRE_IsParentProcess()) {
nsTString<EnvCharType> extension;
extension.AssignLiteral(".log"); bool hasLogExtension = StringEndsWith(fname, extension); if (hasLogExtension) {
fname.Cut(fname.Length() - 4, 4);
}
fname.Append('_');
fname.AppendASCII(aProcType);
fname.AppendLiteral("_pid");
fname.AppendInt((uint32_t)getpid()); if (hasLogExtension) {
fname.AppendLiteral(".log");
}
} #ifdef XP_WIN
FILE* stream = ::_wfopen(fname.get(), L"wN"); constwchar_t* fp = (constwchar_t*)fname.get(); #else
FILE* stream = ::fopen(fname.get(), "w"); constchar* fp = fname.get(); #endif if (stream) {
MozillaRegisterDebugFD(fileno(stream));
*aResult = stream;
fprintf(stderr, "### " ENVVAR_PRINTF " defined -- logging %s to " ENVVAR_PRINTF "\n",
envvar, aMsg, fp);
returntrue;
}
fprintf(stderr, "### " ENVVAR_PRINTF " defined -- unable to log %s to " ENVVAR_PRINTF "\n",
envvar, aMsg, fp);
MOZ_ASSERT(false, "Tried and failed to create an XPCOM log");
#undef ENVVAR_PRINTF
} returnfalse;
}
staticvoid maybeUnregisterAndCloseFile(FILE*& aFile) { if (!aFile) { return;
}
staticvoid DoInitTraceLog(constchar* aProcType) { #ifdef XP_WIN # define ENVVAR(x) u"" x #else # define ENVVAR(x) x #endif
booldefined = InitLog(ENVVAR("XPCOM_MEM_BLOAT_LOG"), "bloat/leaks",
&gBloatLog, aProcType); if (!defined) {
gLogLeaksOnly =
InitLog(ENVVAR("XPCOM_MEM_LEAK_LOG"), "leaks", &gBloatLog, aProcType);
} if (defined || gLogLeaksOnly) { // Use the same bloat view, if there is one, to keep it consistent // between the fork server and content processes.
EnsureBloatView();
} elseif (gBloatView) {
nsTraceRefcnt::ResetStatistics();
}
#ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR if (classes) {
InitLog(ENVVAR("XPCOM_MEM_COMPTR_LOG"), "nsCOMPtr", &gCOMPtrLog, aProcType);
} else { if (getenv("XPCOM_MEM_COMPTR_LOG")) {
fprintf(stdout, "### XPCOM_MEM_COMPTR_LOG defined -- " "but XPCOM_MEM_LOG_CLASSES is not defined\n");
}
} #else constchar* comptr_log = getenv("XPCOM_MEM_COMPTR_LOG"); if (comptr_log) {
fprintf(stdout, "### XPCOM_MEM_COMPTR_LOG defined -- " "but it will not work without dynamic_cast\n");
} #endif// HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
#undef ENVVAR
if (classes) { // if XPCOM_MEM_LOG_CLASSES was set to some value, the value is interpreted // as a list of class names to track // // Use the same |gTypesToLog| and |gSerialNumbers| to keep them // consistent through the fork server and content processes. // Without this, counters will be incorrect. if (!gTypesToLog) {
gTypesToLog = new CharPtrSet(256);
}
fprintf(stdout, "### XPCOM_MEM_LOG_CLASSES defined -- " "only logging these classes: "); constchar* cp = classes; for (;;) { char* cm = (char*)strchr(cp, ','); if (cm) {
*cm = '\0';
} if (!gTypesToLog->Contains(cp)) {
gTypesToLog->PutEntry(cp);
}
fprintf(stdout, "%s ", cp); if (!cm) { break;
}
*cm = ',';
cp = cm + 1;
}
fprintf(stdout, "\n");
if (!gSerialNumbers) {
gSerialNumbers = new SerialHash(256);
}
} else {
gTypesToLog = nullptr;
gSerialNumbers = nullptr;
}
constchar* objects = getenv("XPCOM_MEM_LOG_OBJECTS"); if (objects) {
gObjectsToLog = new IntPtrSet(256);
if (!(gRefcntsLog || gAllocLog || gCOMPtrLog)) {
fprintf(stdout, "### XPCOM_MEM_LOG_OBJECTS defined -- " "but none of XPCOM_MEM_(REFCNT|ALLOC|COMPTR)_LOG is defined\n");
} else {
fprintf(stdout, "### XPCOM_MEM_LOG_OBJECTS defined -- " "only logging these objects: "); constchar* cp = objects; for (;;) { char* cm = (char*)strchr(cp, ','); if (cm) {
*cm = '\0';
}
intptr_t top = 0;
intptr_t bottom = 0; while (*cp) { if (*cp == '-') {
bottom = top;
top = 0;
++cp;
}
top *= 10;
top += *cp - '0';
++cp;
} if (!bottom) {
bottom = top;
} for (intptr_t serialno = bottom; serialno <= top; serialno++) {
gObjectsToLog->PutEntry(serialno);
fprintf(stdout, "%" PRIdPTR " ", serialno);
} if (!cm) { break;
}
*cm = ',';
cp = cm + 1;
}
fprintf(stdout, "\n");
}
}
/** * This is a variant of |MozWalkTheStack| that uses |CodeAddressService| to * cache the results of |NS_DescribeCodeAddress|. If |WalkTheStackCached| is * being called frequently, it will be a few orders of magnitude faster than * |MozWalkTheStack|. However, the cache uses a lot of memory, which can cause * OOM crashes. Therefore, this should only be used for things like refcount * logging which walk the stack extremely frequently.
*/ staticvoid WalkTheStackCached(FILE* aStream, constvoid* aFirstFramePC) { if (!gCodeAddressService) {
gCodeAddressService = new CodeAddressService<>();
}
MozStackWalk(PrintStackFrameCached, aFirstFramePC, /* maxFrames */ 0,
aStream);
}
#ifdef MOZ_DMD // If MOZ_DMD_SHUTDOWN_LOG is set, dump a DMD report to a file. // The value of this environment variable is used as the prefix // of the file name, so you probably want something like "/tmp/". // By default, this is run in all processes, but you can record a // log only for a specific process type by setting MOZ_DMD_LOG_PROCESS // to the process type you want to log, such as "default" or "tab". // This method can't use the higher level XPCOM file utilities // because it is run very late in shutdown to avoid recording // information about refcount logging entries. staticvoid LogDMDFile() { constchar* dmdFilePrefix = PR_GetEnv("MOZ_DMD_SHUTDOWN_LOG"); if (!dmdFilePrefix) { return;
}
namespace mozilla { void LogTerm() {
NS_ASSERTION(gInitCount > 0, "NS_LogTerm without matching NS_LogInit");
if (--gInitCount == 0) { #ifdef DEBUG /* FIXME bug 491977: This is only going to operate on the * BlockingResourceBase which is compiled into * libxul/libxpcom_core.so. Anyone using external linkage will * have their own copy of BlockingResourceBase statics which will * not be freed by this method. * * It sounds like what we really want is to be able to register a * callback function to call at XPCOM shutdown. Note that with * this solution, however, we need to guarantee that * BlockingResourceBase::Shutdown() runs after all other shutdown * functions.
*/
BlockingResourceBase::Shutdown(); #endif
// (If we're on a losing architecture, don't do this because we'll be // using LogDeleteXPCOM instead to get file and line numbers.) if (gAllocLog && loggingThisType && loggingThisObject) {
fprintf(gAllocLog, "\n<%s> %p %" PRIdPTR " Dtor (%d)\n", aType, aPtr,
serialno, aInstanceSize);
WalkTheStackCached(gAllocLog, CallerPC());
}
}
EXPORT_XPCOM_API(MOZ_NEVER_INLINE void)
NS_LogCOMPtrAddRef(void* aCOMPtr, nsISupports* aObject) { #ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR // Get the most-derived object. void* object = dynamic_cast<void*>(aObject);
// This is a very indirect way of finding out what the class is // of the object being logged. If we're logging a specific type, // then if (!gTypesToLog || !gSerialNumbers) { return;
} if (!gInitialized) {
InitTraceLog();
} if (gLogging == FullLogging) {
AutoTraceLogLock lock(gTraceLog);
EXPORT_XPCOM_API(MOZ_NEVER_INLINE void)
NS_LogCOMPtrRelease(void* aCOMPtr, nsISupports* aObject) { #ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR // Get the most-derived object. void* object = dynamic_cast<void*>(aObject);
// This is a very indirect way of finding out what the class is // of the object being logged. If we're logging a specific type, // then if (!gTypesToLog || !gSerialNumbers) { return;
} if (!gInitialized) {
InitTraceLog();
} if (gLogging == FullLogging) {
AutoTraceLogLock lock(gTraceLog);
staticvoid ClearLogs(bool aKeepCounters) {
gCodeAddressService = nullptr; // These counters from the fork server process will be preserved // for the content processes to keep them consistent. if (!aKeepCounters) {
gBloatView = nullptr;
gTypesToLog = nullptr;
gSerialNumbers = nullptr;
}
gObjectsToLog = nullptr;
gLogJSStacks = false;
gLogLeaksOnly = false;
maybeUnregisterAndCloseFile(gBloatLog);
maybeUnregisterAndCloseFile(gRefcntsLog);
maybeUnregisterAndCloseFile(gAllocLog);
maybeUnregisterAndCloseFile(gCOMPtrLog);
}
if (!gTypesToLog) {
gTypesToLog = new CharPtrSet(256);
}
fprintf(stdout, "### StartLoggingClass %s\n", aClass); if (!gTypesToLog->Contains(aClass)) {
gTypesToLog->PutEntry(aClass);
}
// We are deliberately not initializing gSerialNumbers here, because // it would cause assertions. gObjectsToLog can't be used because it // relies on serial numbers.
#ifdef XP_WIN # define ENVVAR(x) u"" x #else # define ENVVAR(x) x #endif
if (!gRefcntsLog) {
InitLog(ENVVAR("XPCOM_MEM_LATE_REFCNT_LOG"), "refcounts", &gRefcntsLog,
XRE_GetProcessTypeString());
}
#undef ENVVAR
}
#ifdef MOZ_ENABLE_FORKSERVER void nsTraceRefcnt::CloseLogFilesAfterFork() { // Disable logging, and close our log files. We can't have open file // descriptors on the fork server during the FileDescriptorShuffle // (bug 1909125).
gLogging = NoLogging;
ClearLogs(true);
}
void nsTraceRefcnt::ReopenLogFilesAfterFork(constchar* aProcType) { // Create log files with the correct process type in the name. // This will re-initialize gLogging after it was cleared in // CloseLogFilesAfterFork.
DoInitTraceLog(aProcType);
} #endif
¤ Dauer der Verarbeitung: 0.31 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.