/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is Mozilla Communicator client code. * * The Initial Developer of the Original Code is Netscape Communications * Corporation. Portions created by Netscape are Copyright (C) 1998 * Netscape Communications Corporation. All Rights Reserved. */ #include "nsISupports.h" #include "nsVoidArray.h" #include "prprf.h" #include "prlog.h" #include "plstr.h" #include #include "nsCOMPtr.h" #include "nsIOutputStream.h" #include "nsIFileStream.h" #include "nsCRT.h" #if defined(_WIN32) #include #elif defined(linux) && defined(__GLIBC__) && defined(__i386) #include // // On glibc 2.1, the Dl_info api defined in is only exposed // if __USE_GNU is defined. I suppose its some kind of standards // adherence thing. // #if (__GLIBC_MINOR__ >= 1) #define __USE_GNU #endif #include #endif #ifdef HAVE_LIBDL #include #endif #ifdef NS_BUILD_REFCNT_LOGGING #include "plhash.h" #include #if defined(NS_MT_SUPPORTED) #include "prlock.h" static PRLock* gTraceLock; #define LOCK_TRACELOG() PR_Lock(gTraceLock) #define UNLOCK_TRACELOG() PR_Unlock(gTraceLock) #else /* ! NT_MT_SUPPORTED */ #define LOCK_TRACELOG() #define UNLOCK_TRACELOG() #endif /* ! NS_MT_SUPPORTED */ static PRLogModuleInfo* gTraceRefcntLog; static PLHashTable* gBloatView; static PLHashTable* gTypesToLog; static PRBool gLogging; static PRBool gLogAllRefcnts; static PRBool gLogSomeRefcnts; static PRBool gLogToLeaky; static PRBool gTrackBloat; static PRBool gLogCalls; static PRBool gLogNewAndDelete; static PRBool gDumpLeaksOnly; static void (*leakyLogAddRef)(void* p, int oldrc, int newrc); static void (*leakyLogRelease)(void* p, int oldrc, int newrc); static FILE *gLoggingStream = stderr; #define XPCOM_REFCNT_TRACK_BLOAT 0x1 #define XPCOM_REFCNT_LOG_ALL 0x2 #define XPCOM_REFCNT_LOG_SOME 0x4 #define XPCOM_REFCNT_LOG_TO_LEAKY 0x8 // Should only use this on NS_LOSING_ARCHITECTURE... #define XPCOM_REFCNT_LOG_CALLS 0x10 #define XPCOM_REFCNT_LOG_NEW 0x20 struct GatherArgs { nsTraceRefcntStatFunc func; void* closure; }; class BloatEntry { public: BloatEntry(const char* className, PRUint32 classSize) : mClassName(className), mClassSize(classSize) { Clear(&mNewStats); Clear(&mAllStats); } ~BloatEntry() {} static void Clear(nsTraceRefcntStats* stats) { stats->mAddRefs = 0; stats->mReleases = 0; stats->mCreates = 0; stats->mDestroys = 0; stats->mRefsOutstandingTotal = 0; stats->mRefsOutstandingVariance = 0; stats->mObjsOutstandingTotal = 0; stats->mObjsOutstandingVariance = 0; } void Accumulate() { mAllStats.mAddRefs += mNewStats.mAddRefs; mAllStats.mReleases += mNewStats.mReleases; mAllStats.mCreates += mNewStats.mCreates; mAllStats.mDestroys += mNewStats.mDestroys; mAllStats.mRefsOutstandingTotal += mNewStats.mRefsOutstandingTotal; mAllStats.mRefsOutstandingVariance += mNewStats.mRefsOutstandingVariance; mAllStats.mObjsOutstandingTotal += mNewStats.mObjsOutstandingTotal; mAllStats.mObjsOutstandingVariance += mNewStats.mObjsOutstandingVariance; Clear(&mNewStats); } void AddRef(nsrefcnt refcnt) { mNewStats.mAddRefs++; if (refcnt == 1) { Ctor(); } AccountRefs(); } void Release(nsrefcnt refcnt) { mNewStats.mReleases++; if (refcnt == 0) { Dtor(); } AccountRefs(); } void Ctor() { mNewStats.mCreates++; AccountObjs(); } void Dtor() { mNewStats.mDestroys++; AccountObjs(); } void AccountRefs() { PRInt32 cnt = (mNewStats.mAddRefs - mNewStats.mReleases); mNewStats.mRefsOutstandingTotal += cnt; mNewStats.mRefsOutstandingVariance += cnt * cnt; } void AccountObjs() { PRInt32 cnt = (mNewStats.mCreates - mNewStats.mDestroys); mNewStats.mObjsOutstandingTotal += cnt; mNewStats.mObjsOutstandingVariance += cnt * cnt; } static PRIntn DumpNewEntry(PLHashEntry *he, PRIntn i, void *arg) { BloatEntry* entry = (BloatEntry*)he->value; if (entry) { nsresult rv = entry->Dump(i, (nsIOutputStream*)arg, &entry->mNewStats); NS_ASSERTION(NS_SUCCEEDED(rv), "Dump failed"); entry->Accumulate(); } return HT_ENUMERATE_NEXT; } static PRIntn DumpAllEntry(PLHashEntry *he, PRIntn i, void *arg) { BloatEntry* entry = (BloatEntry*)he->value; if (entry) { entry->Accumulate(); nsresult rv = entry->Dump(i, (nsIOutputStream*)arg, &entry->mAllStats); NS_ASSERTION(NS_SUCCEEDED(rv), "Dump failed"); } return HT_ENUMERATE_NEXT; } static PRIntn TotalEntries(PLHashEntry *he, PRIntn i, void *arg) { BloatEntry* entry = (BloatEntry*)he->value; if (entry) { entry->Total((BloatEntry*)arg); } return HT_ENUMERATE_NEXT; } void Total(BloatEntry* total) { total->mAllStats.mAddRefs += mNewStats.mAddRefs + mAllStats.mAddRefs; total->mAllStats.mReleases += mNewStats.mReleases + mAllStats.mReleases; total->mAllStats.mCreates += mNewStats.mCreates + mAllStats.mCreates; total->mAllStats.mDestroys += mNewStats.mDestroys + mAllStats.mDestroys; total->mAllStats.mRefsOutstandingTotal += mNewStats.mRefsOutstandingTotal + mAllStats.mRefsOutstandingTotal; total->mAllStats.mRefsOutstandingVariance += mNewStats.mRefsOutstandingVariance + mAllStats.mRefsOutstandingVariance; total->mAllStats.mObjsOutstandingTotal += mNewStats.mObjsOutstandingTotal + mAllStats.mObjsOutstandingTotal; total->mAllStats.mObjsOutstandingVariance += mNewStats.mObjsOutstandingVariance + mAllStats.mObjsOutstandingVariance; total->mClassSize += mClassSize; // adjust for average in DumpTotal } nsresult DumpTotal(PRUint32 nClasses, nsIOutputStream* out) { mClassSize /= nClasses; return Dump(-1, out, &mAllStats); } static PRIntn DestroyEntry(PLHashEntry *he, PRIntn i, void *arg) { BloatEntry* entry = (BloatEntry*)he->value; if (entry) { delete entry; } return HT_ENUMERATE_REMOVE | HT_ENUMERATE_NEXT; } static PRIntn GatherEntry(PLHashEntry *he, PRIntn i, void *arg) { BloatEntry* entry = (BloatEntry*)he->value; GatherArgs* ga = (GatherArgs*) arg; if (arg && entry && ga->func) { PRBool stop = (*ga->func)(entry->mClassName, entry->mClassSize, &entry->mNewStats, &entry->mAllStats, ga->closure); if (stop) { return HT_ENUMERATE_STOP; } } return HT_ENUMERATE_NEXT; } static PRBool HaveLeaks(nsTraceRefcntStats* stats) { return ((stats->mAddRefs != stats->mReleases) || (stats->mCreates != stats->mDestroys)); } static nsresult PrintDumpHeader(nsIOutputStream* out, const char* msg) { nsresult rv; char buf[256]; PRUint32 cnt, writeCnt; cnt = PR_snprintf(buf, 256, " %s -- Bloaty: Refcounting and Memory Bloat Statistics\n", msg); rv = out->Write(buf, cnt, &writeCnt); if (NS_FAILED(rv)) return rv; NS_ASSERTION(cnt == writeCnt, "failed to write all data"); cnt = PR_snprintf(buf, 256, " |<------Class----->|<-----Bytes------>|<----------------Objects---------------->|<--------------References-------------->|\n"); rv = out->Write(buf, cnt, &writeCnt); if (NS_FAILED(rv)) return rv; NS_ASSERTION(cnt == writeCnt, "failed to write all data"); cnt = PR_snprintf(buf, 256, " Per-Inst Leaked Total Rem Mean StdDev Total Rem Mean StdDev\n"); rv = out->Write(buf, cnt, &writeCnt); if (NS_FAILED(rv)) return rv; NS_ASSERTION(cnt == writeCnt, "failed to write all data"); return NS_OK; } nsresult Dump(PRIntn i, nsIOutputStream* out, nsTraceRefcntStats* stats) { if (gDumpLeaksOnly && !HaveLeaks(stats)) { return NS_OK; } double nRefs = stats->mAddRefs + stats->mReleases; double meanRefs = nRefs != 0.0 ? stats->mRefsOutstandingTotal / nRefs : 0.0; double varRefs = fabs(stats->mRefsOutstandingVariance / stats->mRefsOutstandingTotal - meanRefs * meanRefs); // for some reason, Windows says sqrt(0.0) is "-1.#J" (?!) so do this: double stddevRefs = varRefs != 0.0 ? sqrt(varRefs) : 0.0; double nObjs = stats->mCreates + stats->mDestroys; double meanObjs = nObjs != 0.0 ? stats->mObjsOutstandingTotal / nObjs : 0.0; double varObjs = fabs(stats->mObjsOutstandingVariance / stats->mObjsOutstandingTotal - meanObjs * meanObjs); // for some reason, Windows says sqrt(0.0) is "-1.#J" (?!) so do this: double stddevObjs = varObjs != 0.0 ? sqrt(varObjs) : 0.0; if ((stats->mAddRefs - stats->mReleases) != 0 || stats->mAddRefs != 0 || meanRefs != 0 || stddevRefs != 0 || (stats->mCreates - stats->mDestroys) != 0 || stats->mCreates != 0 || meanObjs != 0 || stddevObjs != 0) { char buf[256]; PRUint32 cnt, writeCnt; cnt = PR_snprintf(buf, 256, "%4d %-20.20s %8d %8d %8d %8d (%8.2f +/- %8.2f) %8d %8d (%8.2f +/- %8.2f)\n", i+1, mClassName, mClassSize, (stats->mCreates - stats->mDestroys) * mClassSize, stats->mCreates, (stats->mCreates - stats->mDestroys), meanObjs, stddevObjs, stats->mAddRefs, (stats->mAddRefs - stats->mReleases), meanRefs, stddevRefs); nsresult rv = out->Write(buf, cnt, &writeCnt); if (NS_FAILED(rv)) return rv; NS_ASSERTION(cnt == writeCnt, "failed to write all data"); } return NS_OK; } protected: const char* mClassName; PRUint32 mClassSize; nsTraceRefcntStats mNewStats; nsTraceRefcntStats mAllStats; }; static void RecreateBloatView() { gBloatView = PL_NewHashTable(256, PL_HashString, PL_CompareStrings, PL_CompareValues, NULL, NULL); } static BloatEntry* GetBloatEntry(const char* aTypeName, PRUint32 aInstanceSize) { if (!gBloatView) { RecreateBloatView(); } BloatEntry* entry = NULL; if (gBloatView) { entry = (BloatEntry*)PL_HashTableLookup(gBloatView, aTypeName); if (entry == NULL) { entry = new BloatEntry(aTypeName, aInstanceSize); PLHashEntry* e = PL_HashTableAdd(gBloatView, aTypeName, entry); if (e == NULL) { delete entry; entry = NULL; } } } return entry; } #endif /* NS_BUILD_REFCNT_LOGGING */ nsresult nsTraceRefcnt::DumpStatistics(StatisticsType type, nsIOutputStream* out) { nsresult rv = NS_OK; #ifdef NS_BUILD_REFCNT_LOGGING if (!gTrackBloat || !gBloatView) { return NS_OK; } LOCK_TRACELOG(); if (gDumpLeaksOnly) { fprintf(gLoggingStream, "Bloaty: Only dumping data about objects that leaked\n"); } PRBool wasLogging = gLogging; gLogging = PR_FALSE; // turn off logging for this method BloatEntry total("TOTAL", 0); nsCOMPtr outStr = dont_QueryInterface(out); if (out == nsnull) { nsCOMPtr outSupports; rv = NS_NewOutputConsoleStream(getter_AddRefs(outSupports)); if (NS_FAILED(rv)) goto done; outStr = do_QueryInterface(outSupports, &rv); if (NS_FAILED(rv)) goto done; } PRIntn (*dump)(PLHashEntry *he, PRIntn i, void *arg); const char* msg; if (type == NEW_STATS) { dump = BloatEntry::DumpNewEntry; msg = "NEW RESULTS"; } else { dump = BloatEntry::DumpAllEntry; msg = "ALL RESULTS"; } rv = BloatEntry::PrintDumpHeader(outStr, msg); if (NS_FAILED(rv)) goto done; PL_HashTableEnumerateEntries(gBloatView, BloatEntry::TotalEntries, &total); total.DumpTotal(gBloatView->nentries, outStr); PL_HashTableEnumerateEntries(gBloatView, dump, outStr); done: gLogging = wasLogging; UNLOCK_TRACELOG(); #endif return rv; } void nsTraceRefcnt::ResetStatistics() { #ifdef NS_BUILD_REFCNT_LOGGING LOCK_TRACELOG(); if (gBloatView) { PL_HashTableEnumerateEntries(gBloatView, BloatEntry::DestroyEntry, 0); PL_HashTableDestroy(gBloatView); gBloatView = nsnull; } UNLOCK_TRACELOG(); #endif } void nsTraceRefcnt::GatherStatistics(nsTraceRefcntStatFunc aFunc, void* aClosure) { #ifdef NS_BUILD_REFCNT_LOGGING LOCK_TRACELOG(); if (gBloatView) { GatherArgs ga; ga.func = aFunc; ga.closure = aClosure; PL_HashTableEnumerateEntries(gBloatView, BloatEntry::GatherEntry, (void*) &ga); } UNLOCK_TRACELOG(); #endif } #ifdef NS_BUILD_REFCNT_LOGGING static PRBool LogThisType(const char* aTypeName) { void* he = PL_HashTableLookup(gTypesToLog, aTypeName); return nsnull != he; } static void InitTraceLog(void) { if (0 == gTraceRefcntLog) { gTraceRefcntLog = PR_NewLogModule("xpcomrefcnt"); if (getenv("MOZ_DUMP_LEAKS")) { gDumpLeaksOnly = PR_TRUE; } // See if user is redirecting the trace. const char* traceLogName = getenv("MOZ_TRACE_LOG"); if (traceLogName) { FILE *stream = ::fopen(traceLogName, "w"); if (stream != NULL) gLoggingStream = stream; } // See if bloaty is enabled if (XPCOM_REFCNT_TRACK_BLOAT & gTraceRefcntLog->level) { gTrackBloat = PR_TRUE; RecreateBloatView(); if (NS_WARN_IF_FALSE(gBloatView, "out of memory")) { gTrackBloat = PR_FALSE; } else { fprintf(gLoggingStream, "XPCOM: using turbo mega bloatvision\n"); } } // See if raw nspr logging is enabled if (XPCOM_REFCNT_LOG_ALL & gTraceRefcntLog->level) { gLogAllRefcnts = PR_TRUE; fprintf(gLoggingStream, "XPCOM: logging all refcnt calls\n"); } else if (XPCOM_REFCNT_LOG_SOME & gTraceRefcntLog->level) { gLogSomeRefcnts = PR_TRUE; gTypesToLog = PL_NewHashTable(256, PL_HashString, PL_CompareStrings, PL_CompareValues, NULL, NULL); if (NS_WARN_IF_FALSE(gTypesToLog, "out of memory")) { gLogSomeRefcnts = PR_FALSE; } else { #if defined(XP_UNIX) || defined (XP_PC) || defined(XP_MAC) char* types = getenv("MOZ_TRACE_REFCNT_TYPES"); if (types) { fprintf(gLoggingStream, "XPCOM: logging some refcnt calls: "); char* cp = types; for (;;) { char* cm = strchr(cp, ','); if (cm) { *cm = '\0'; } PL_HashTableAdd(gTypesToLog, nsCRT::strdup(cp), (void*)1); fprintf(gLoggingStream, "%s ", cp); if (!cm) break; *cm = ','; cp = cm + 1; } fprintf(gLoggingStream, "\n"); } else { fprintf(gLoggingStream, "XPCOM: MOZ_TRACE_REFCNTS_TYPE wasn't set; can't log some refcnts\n"); gLogSomeRefcnts = PR_FALSE; } #endif } } if (XPCOM_REFCNT_LOG_CALLS & gTraceRefcntLog->level) { gLogCalls = PR_TRUE; } if (XPCOM_REFCNT_LOG_NEW & gTraceRefcntLog->level) { gLogNewAndDelete = PR_TRUE; } // See if we should log to leaky instead of to nspr if (XPCOM_REFCNT_LOG_TO_LEAKY & gTraceRefcntLog->level) { gLogToLeaky = PR_TRUE; #ifdef HAVE_LIBDL void* p = dlsym(0, "__log_addref"); if (p) { leakyLogAddRef = (void (*)(void*,int,int)) p; p = dlsym(0, "__log_release"); if (p) { leakyLogRelease = (void (*)(void*,int,int)) p; fprintf(gLoggingStream, "XPCOM: logging addref/release calls to leaky\n"); } else { gLogToLeaky = PR_FALSE; } } else { gLogToLeaky = PR_FALSE; } #endif } if (gTrackBloat || gLogAllRefcnts || gLogSomeRefcnts || gLogCalls || gLogNewAndDelete) { gLogging = PR_TRUE; } #if defined(NS_MT_SUPPORTED) gTraceLock = PR_NewLock(); #endif /* NS_MT_SUPPORTED */ } } #endif #if defined(_WIN32) && defined(_M_IX86) // WIN32 x86 stack walking code #include "imagehlp.h" #include // Define these as static pointers so that we can load the DLL on the // fly (and not introduce a link-time dependency on it). Tip o' the // hat to Matt Pietrick for this idea. See: // // http://msdn.microsoft.com/library/periodic/period97/F1/D3/S245C6.htm // typedef BOOL (__stdcall *SYMINITIALIZEPROC)(HANDLE, LPSTR, BOOL); static SYMINITIALIZEPROC _SymInitialize; typedef BOOL (__stdcall *SYMCLEANUPPROC)(HANDLE); static SYMCLEANUPPROC _SymCleanup; typedef BOOL (__stdcall *STACKWALKPROC)(DWORD, HANDLE, HANDLE, LPSTACKFRAME, LPVOID, PREAD_PROCESS_MEMORY_ROUTINE, PFUNCTION_TABLE_ACCESS_ROUTINE, PGET_MODULE_BASE_ROUTINE, PTRANSLATE_ADDRESS_ROUTINE); static STACKWALKPROC _StackWalk; typedef LPVOID (__stdcall *SYMFUNCTIONTABLEACCESSPROC)(HANDLE, DWORD); static SYMFUNCTIONTABLEACCESSPROC _SymFunctionTableAccess; typedef DWORD (__stdcall *SYMGETMODULEBASEPROC)(HANDLE, DWORD); static SYMGETMODULEBASEPROC _SymGetModuleBase; typedef BOOL (__stdcall *SYMGETSYMFROMADDRPROC)(HANDLE, DWORD, PDWORD, PIMAGEHLP_SYMBOL); static SYMGETSYMFROMADDRPROC _SymGetSymFromAddr; static PRBool EnsureSymInitialized() { PRBool gInitialized = PR_FALSE; if (! gInitialized) { HMODULE module = ::LoadLibrary("IMAGEHLP.DLL"); if (!module) return PR_FALSE; _SymInitialize = (SYMINITIALIZEPROC) ::GetProcAddress(module, "SymInitialize"); if (!_SymInitialize) return PR_FALSE; _SymCleanup = (SYMCLEANUPPROC)GetProcAddress(module, "SymCleanup"); if (!_SymCleanup) return PR_FALSE; _StackWalk = (STACKWALKPROC)GetProcAddress(module, "StackWalk"); if (!_StackWalk) return PR_FALSE; _SymFunctionTableAccess = (SYMFUNCTIONTABLEACCESSPROC) GetProcAddress(module, "SymFunctionTableAccess"); if (!_SymFunctionTableAccess) return PR_FALSE; _SymGetModuleBase = (SYMGETMODULEBASEPROC)GetProcAddress(module, "SymGetModuleBase"); if (!_SymGetModuleBase) return PR_FALSE; _SymGetSymFromAddr = (SYMGETSYMFROMADDRPROC)GetProcAddress(module, "SymGetSymFromAddr"); if (!_SymGetSymFromAddr) return PR_FALSE; gInitialized = _SymInitialize(GetCurrentProcess(), 0, TRUE); } return gInitialized; } /** * Walk the stack, translating PC's found into strings and recording the * chain in aBuffer. For this to work properly, the dll's must be rebased * so that the address in the file agrees with the address in memory. * Otherwise StackWalk will return FALSE when it hits a frame in a dll's * whose in memory address doesn't match it's in-file address. * * Fortunately, there is a handy dandy routine in IMAGEHLP.DLL that does * the rebasing and accordingly I've made a tool to use it to rebase the * DLL's in one fell swoop (see xpcom/tools/windows/rebasedlls.cpp). */ void nsTraceRefcnt::WalkTheStack(FILE* aStream) { HANDLE myProcess = ::GetCurrentProcess(); HANDLE myThread = ::GetCurrentThread(); BOOL ok; ok = EnsureSymInitialized(); if (! ok) return; // Get the context information for this thread. That way we will // know where our sp, fp, pc, etc. are and can fill in the // STACKFRAME with the initial values. CONTEXT context; context.ContextFlags = CONTEXT_FULL; ok = GetThreadContext(myThread, &context); if (! ok) return; // Setup initial stack frame to walk from STACKFRAME frame; memset(&frame, 0, sizeof(frame)); frame.AddrPC.Offset = context.Eip; frame.AddrPC.Mode = AddrModeFlat; frame.AddrStack.Offset = context.Esp; frame.AddrStack.Mode = AddrModeFlat; frame.AddrFrame.Offset = context.Ebp; frame.AddrFrame.Mode = AddrModeFlat; // Now walk the stack and map the pc's to symbol names int skip = 2; while (1) { ok = _StackWalk(IMAGE_FILE_MACHINE_I386, myProcess, myThread, &frame, &context, 0, // read process memory routine _SymFunctionTableAccess, // function table access routine _SymGetModuleBase, // module base routine 0); // translate address routine if (!ok || frame.AddrPC.Offset == 0) break; if (skip-- > 0) continue; char buf[sizeof(IMAGEHLP_SYMBOL) + 512]; PIMAGEHLP_SYMBOL symbol = (PIMAGEHLP_SYMBOL) buf; symbol->SizeOfStruct = sizeof(buf); symbol->MaxNameLength = 512; DWORD displacement; ok = _SymGetSymFromAddr(myProcess, frame.AddrPC.Offset, &displacement, symbol); if (ok) { fprintf(aStream, "%s+0x%08X\n", symbol->Name, displacement); } else { fprintf(aStream, "0x%08X\n", frame.AddrPC.Offset); } } } #elif defined(linux) && defined(__GLIBC__) && defined(__i386) // i386 Linux stackwalking code void nsTraceRefcnt::WalkTheStack(FILE* aStream) { jmp_buf jb; setjmp(jb); // Stack walking code courtesy Kipp's "leaky". u_long* bp = (u_long*) (jb[0].__jmpbuf[JB_BP]); int skip = 2; for (;;) { u_long* nextbp = (u_long*) *bp++; u_long pc = *bp; if ((pc < 0x08000000) || (pc > 0x7fffffff) || (nextbp < bp)) { break; } if (--skip <= 0) { Dl_info info; int ok = dladdr((void*) pc, &info); if (ok < 0) break; const char * symbol = info.dli_sname; int len = strlen(symbol); if (! len) break; // XXX Lazy. We could look at the filename or something. char demangled[4096] = "\0"; DemangleSymbol(symbol,demangled,sizeof(demangled)); if (strlen(demangled)) { symbol = demangled; len = strlen(symbol); } PRUint32 off = (char*)pc - (char*)info.dli_saddr; fprintf(aStream, "%s+0x%08X\n", symbol, off); } bp = nextbp; } } #elif defined(XP_MAC) /** * Stack walking code for the Mac OS. */ extern "C" { int GC_address_to_source(char* codeAddr, char fileName[256], UInt32* fileOffset); void MWUnmangle(const char *mangled_name, char *unmangled_name, size_t buffersize); } static asm void *GetSP() { mr r3, sp blr } struct traceback_table { long zero; long magic; long reserved; long codeSize; short nameLength; char name[2]; }; typedef struct traceback_table traceback_table; static char* pc2name(long* pc, char name[], long size) { name[0] = '\0'; // make sure pc is instruction aligned (at least). if (UInt32(pc) == (UInt32(pc) & 0xFFFFFFFC)) { long instructionsToLook = 4096; long* instruction = (long*)pc; // look for the traceback table. while (instructionsToLook--) { if (instruction[0] == 0x4E800020 && instruction[1] == 0x00000000) { traceback_table* tb = (traceback_table*)&instruction[1]; long nameLength = (tb->nameLength > --size ? size : tb->nameLength); memcpy(name, tb->name + 1, --nameLength); name[nameLength] = '\0'; break; } ++instruction; } } return name; } NS_COM void nsTraceRefcnt::WalkTheStack(FILE* aStream) { void* currentSP; currentSP = GetSP(); // WalkTheStack's frame. currentSP = *((void **)currentSP); // WalkTheStack's caller's frame. currentSP = *((void **)currentSP); // WalkTheStack's caller's caller's frame. while (true) { long** linkageArea = (long**)currentSP; // LR saved at 8(SP) in each frame. subtract 4 to get address of calling instruction. long* pc = linkageArea[2] - 1; // convert PC to name, unmangle it, and generate source location, if possible. static char name[1024], unmangled_name[1024], file_name[256]; UInt32 file_offset; pc2name(pc, name, sizeof(name)); MWUnmangle(name, unmangled_name, sizeof(unmangled_name)); if (GC_address_to_source((char*)pc, file_name, &file_offset)) fprintf(aStream, "%s[%s,%ld]\n", unmangled_name, file_name, file_offset); else fprintf(aStream, "%s(0x%08X)\n", unmangled_name, pc); currentSP = *((void **)currentSP); // the bottom-most frame is marked as pointing to NULL. if (currentSP == NULL || UInt32(currentSP) & 0x1) break; } } #else // unsupported platform. void nsTraceRefcnt::WalkTheStack(FILE* aStream) { fprintf(aStream, "write me, dammit!\n"); } #endif //---------------------------------------------------------------------- // This thing is exported by libiberty.a (-liberty) // Yes, this is a gcc only hack #if defined(MOZ_DEMANGLE_SYMBOLS) extern "C" char * cplus_demangle(const char *,int); #include // for free() #endif // MOZ_DEMANGLE_SYMBOLS #ifdef __linux__ NS_COM void nsTraceRefcnt::DemangleSymbol(const char * aSymbol, char * aBuffer, int aBufLen) { NS_ASSERTION(nsnull != aSymbol,"null symbol"); NS_ASSERTION(nsnull != aBuffer,"null buffer"); NS_ASSERTION(aBufLen >= 32 ,"pulled 32 out of you know where"); aBuffer[0] = '\0'; #if defined(MOZ_DEMANGLE_SYMBOLS) /* See demangle.h in the gcc source for the voodoo */ char * demangled = cplus_demangle(aSymbol,3); if (demangled) { strncpy(aBuffer,demangled,aBufLen); free(demangled); } #endif // MOZ_DEMANGLE_SYMBOLS } #else // __linux__ NS_COM void nsTraceRefcnt::DemangleSymbol(const char * aSymbol, char * aBuffer, int aBufLen) { NS_ASSERTION(nsnull != aSymbol,"null symbol"); NS_ASSERTION(nsnull != aBuffer,"null buffer"); // lose aBuffer[0] = '\0'; } #endif // __linux__ //---------------------------------------------------------------------- NS_COM void nsTraceRefcnt::LoadLibrarySymbols(const char* aLibraryName, void* aLibrayHandle) { #ifdef NS_BUILD_REFCNT_LOGGING #if defined(_WIN32) && defined(_M_IX86) /* Win32 x86 only */ if (gTraceRefcntLog == nsnull) InitTraceLog(); if (PR_LOG_TEST(gTraceRefcntLog,PR_LOG_DEBUG)) { HANDLE myProcess = ::GetCurrentProcess(); if (!SymInitialize(myProcess, ".;..\\lib", TRUE)) { return; } BOOL b = ::SymLoadModule(myProcess, NULL, (char*)aLibraryName, (char*)aLibraryName, 0, 0); // DWORD lastError = 0; // if (!b) lastError = ::GetLastError(); // fprintf(gLoggingStream, "loading symbols for library %s => %s [%d]\n", aLibraryName, // b ? "true" : "false", lastError); } #endif #endif } //---------------------------------------------------------------------- /* For consistency, and ease of munging the output, the following record format will be used: 0xADDRESS Verb [optional data] */ NS_COM void nsTraceRefcnt::LogAddRef(void* aPtr, nsrefcnt aRefCnt, const char* aClazz, PRUint32 classSize) { #ifdef NS_BUILD_REFCNT_LOGGING if (gTraceRefcntLog == nsnull) InitTraceLog(); if (gLogging) { LOCK_TRACELOG(); if (gTrackBloat) { BloatEntry* entry = GetBloatEntry(aClazz, classSize); if (entry) { entry->AddRef(aRefCnt); } } // Here's the case where neither NS_NEWXPCOM nor MOZ_COUNT_CTOR were used, // yet we still want to see creation information: #ifndef NS_LOSING_ARCHITECTURE // (If we're on a losing architecture, don't do this because we'll be // using LogNewXPCOM instead to get file and line numbers.) PRBool loggingThisType = (gTypesToLog && LogThisType(aClazz)); if (aRefCnt == 1 && (gLogNewAndDelete || loggingThisType)) { fprintf(gLoggingStream, "\n<%s> 0x%08X Create\n", aClazz, PRInt32(aPtr)); WalkTheStack(gLoggingStream); } // (If we're on a losing architecture, don't do this because we'll be // using LogAddRefCall instead to get file and line numbers.) if (gLogAllRefcnts || loggingThisType) { if (gLogToLeaky) { (*leakyLogAddRef)(aPtr, aRefCnt - 1, aRefCnt); } else { // Can't use PR_LOG(), b/c it truncates the line fprintf(gLoggingStream, "\n<%s> 0x%08X AddRef %d\n", aClazz, PRInt32(aPtr), aRefCnt); WalkTheStack(gLoggingStream); } } #endif UNLOCK_TRACELOG(); } #endif } NS_COM void nsTraceRefcnt::LogRelease(void* aPtr, nsrefcnt aRefCnt, const char* aClazz) { #ifdef NS_BUILD_REFCNT_LOGGING if (gTraceRefcntLog == nsnull) InitTraceLog(); if (gLogging) { LOCK_TRACELOG(); if (gTrackBloat) { BloatEntry* entry = GetBloatEntry(aClazz, (PRUint32)-1); if (entry) { entry->Release(aRefCnt); } } #ifndef NS_LOSING_ARCHITECTURE // (If we're on a losing architecture, don't do this because we'll be // using LogReleaseCall instead to get file and line numbers.) PRBool loggingThisType = (gTypesToLog && LogThisType(aClazz)); if (gLogAllRefcnts || loggingThisType) { if (gLogToLeaky) { (*leakyLogRelease)(aPtr, aRefCnt + 1, aRefCnt); } else { // Can't use PR_LOG(), b/c it truncates the line fprintf(gLoggingStream, "\n<%s> 0x%08X Release %d\n", aClazz, PRInt32(aPtr), aRefCnt); WalkTheStack(gLoggingStream); } } // Here's the case where neither NS_DELETEXPCOM nor MOZ_COUNT_DTOR were used, // yet we still want to see deletion information: // (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 (aRefCnt == 0 && (gLogNewAndDelete || loggingThisType)) { fprintf(gLoggingStream, "\n<%s> 0x%08X Destroy\n", aClazz, PRInt32(aPtr)); WalkTheStack(gLoggingStream); } #endif UNLOCK_TRACELOG(); } #endif } NS_COM nsrefcnt nsTraceRefcnt::LogAddRefCall(void* aPtr, nsrefcnt aNewRefcnt, const char* aFile, int aLine) { #ifdef NS_BUILD_REFCNT_LOGGING #ifdef NS_LOSING_ARCHITECTURE if (gTraceRefcntLog == nsnull) InitTraceLog(); if (gLogCalls) { LOCK_TRACELOG(); fprintf(gLoggingStream, "\n 0x%08X AddRef %d=>%d in %s (line %d)\n", aPtr, aNewRefcnt-1, aNewRefcnt, aFile, aLine); WalkTheStack(gLoggingStream); UNLOCK_TRACELOG(); } #endif #endif return aNewRefcnt; } NS_COM nsrefcnt nsTraceRefcnt::LogReleaseCall(void* aPtr, nsrefcnt aNewRefcnt, const char* aFile, int aLine) { #ifdef NS_BUILD_REFCNT_LOGGING #ifdef NS_LOSING_ARCHITECTURE if (gTraceRefcntLog == nsnull) InitTraceLog(); if (gLogCalls) { LOCK_TRACELOG(); fprintf(gLoggingStream, "\n 0x%08X Release %d=>%d in %s (line %d)\n", aPtr, aNewRefcnt+1, aNewRefcnt, aFile, aLine); WalkTheStack(gLoggingStream); UNLOCK_TRACELOG(); } #endif #endif return aNewRefcnt; } NS_COM void nsTraceRefcnt::LogNewXPCOM(void* aPtr, const char* aType, PRUint32 aInstanceSize, const char* aFile, int aLine) { #ifdef NS_BUILD_REFCNT_LOGGING #ifdef NS_LOSING_ARCHITECTURE if (gTraceRefcntLog == nsnull) InitTraceLog(); if (gLogNewAndDelete) { LOCK_TRACELOG(); fprintf(gLoggingStream, "\n<%s> 0x%08X NewXPCOM in %s (line %d)\n", aType, aPtr, aFile, aLine); WalkTheStack(gLoggingStream); UNLOCK_TRACELOG(); } #endif #endif } NS_COM void nsTraceRefcnt::LogDeleteXPCOM(void* aPtr, const char* aFile, int aLine) { #ifdef NS_BUILD_REFCNT_LOGGING #ifdef NS_LOSING_ARCHITECTURE if (gTraceRefcntLog == nsnull) InitTraceLog(); if (gLogNewAndDelete) { LOCK_TRACELOG(); fprintf(gLoggingStream, "\n<%s> 0x%08X Destroy in %s (line %d)\n", aType, aPtr, aFile, aLine); WalkTheStack(gLoggingStream); UNLOCK_TRACELOG(); } #endif #endif } NS_COM void nsTraceRefcnt::LogCtor(void* aPtr, const char* aType, PRUint32 aInstanceSize) { #ifdef NS_BUILD_REFCNT_LOGGING if (gTraceRefcntLog == nsnull) InitTraceLog(); if (gLogging) { LOCK_TRACELOG(); if (gTrackBloat) { BloatEntry* entry = GetBloatEntry(aType, aInstanceSize); if (entry) { entry->Ctor(); } } #ifndef NS_LOSING_ARCHITECTURE // (If we're on a losing architecture, don't do this because we'll be // using LogNewXPCOM instead to get file and line numbers.) if (gLogNewAndDelete || (gTypesToLog && LogThisType(aType))) { fprintf(gLoggingStream, "\n<%s> 0x%08X Ctor (%d)\n", aType, PRInt32(aPtr), aInstanceSize); WalkTheStack(gLoggingStream); } #endif UNLOCK_TRACELOG(); } #endif } NS_COM void nsTraceRefcnt::LogDtor(void* aPtr, const char* aType, PRUint32 aInstanceSize) { #ifdef NS_BUILD_REFCNT_LOGGING if (gTraceRefcntLog == nsnull) InitTraceLog(); if (gLogging) { LOCK_TRACELOG(); if (gTrackBloat) { BloatEntry* entry = GetBloatEntry(aType, aInstanceSize); if (entry) { entry->Dtor(); } } #ifndef NS_LOSING_ARCHITECTURE // (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 (gLogNewAndDelete || (gTypesToLog && LogThisType(aType))) { fprintf(gLoggingStream, "\n<%s> 0x%08X Dtor (%d)\n", aType, PRInt32(aPtr), aInstanceSize); WalkTheStack(gLoggingStream); } #endif UNLOCK_TRACELOG(); } #endif }