static void InitTraceLog(void) { if (gInitialized) return; gInitialized = true; bool defined; defined = InitLog("XPCOM_MEM_BLOAT_LOG", "bloat/leaks", &gBloatLog); if (!defined) gLogLeaksOnly = InitLog("XPCOM_MEM_LEAK_LOG", "leaks", &gBloatLog); if (defined || gLogLeaksOnly) { RecreateBloatView(); if (!gBloatView) { NS_WARNING("out of memory"); gBloatLog = nullptr; gLogLeaksOnly = false; } } (void)InitLog("XPCOM_MEM_REFCNT_LOG", "refcounts", &gRefcntsLog); (void)InitLog("XPCOM_MEM_ALLOC_LOG", "new/delete", &gAllocLog); defined = InitLog("XPCOM_MEM_LEAKY_LOG", "for leaky", &gLeakyLog); if (defined) { gLogToLeaky = true; PRFuncPtr p = nullptr, q = nullptr; #ifdef HAVE_DLOPEN { PRLibrary *lib = nullptr; p = PR_FindFunctionSymbolAndLibrary("__log_addref", &lib); if (lib) { PR_UnloadLibrary(lib); lib = nullptr; } q = PR_FindFunctionSymbolAndLibrary("__log_release", &lib); if (lib) { PR_UnloadLibrary(lib); } } #endif if (p && q) { leakyLogAddRef = (void (*)(void*,int,int)) p; leakyLogRelease = (void (*)(void*,int,int)) q; } else { gLogToLeaky = false; fprintf(stdout, "### ERROR: XPCOM_MEM_LEAKY_LOG defined, but can't locate __log_addref and __log_release symbols\n"); fflush(stdout); } } const char* classes = getenv("XPCOM_MEM_LOG_CLASSES"); #ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR if (classes) { (void)InitLog("XPCOM_MEM_COMPTR_LOG", "nsCOMPtr", &gCOMPtrLog); } else { if (getenv("XPCOM_MEM_COMPTR_LOG")) { fprintf(stdout, "### XPCOM_MEM_COMPTR_LOG defined -- but XPCOM_MEM_LOG_CLASSES is not defined\n"); } } #else const char* 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 if (classes) { // if XPCOM_MEM_LOG_CLASSES was set to some value, the value is interpreted // as a list of class names to track gTypesToLog = PL_NewHashTable(256, PL_HashString, PL_CompareStrings, PL_CompareValues, &typesToLogHashAllocOps, NULL); if (!gTypesToLog) { NS_WARNING("out of memory"); fprintf(stdout, "### XPCOM_MEM_LOG_CLASSES defined -- unable to log specific classes\n"); } else { fprintf(stdout, "### XPCOM_MEM_LOG_CLASSES defined -- only logging these classes: "); const char* cp = classes; for (;;) { char* cm = (char*) strchr(cp, ','); if (cm) { *cm = '\0'; } PL_HashTableAdd(gTypesToLog, nsCRT::strdup(cp), (void*)1); fprintf(stdout, "%s ", cp); if (!cm) break; *cm = ','; cp = cm + 1; } fprintf(stdout, "\n"); } gSerialNumbers = PL_NewHashTable(256, HashNumber, PL_CompareValues, PL_CompareValues, &serialNumberHashAllocOps, NULL); } const char* objects = getenv("XPCOM_MEM_LOG_OBJECTS"); if (objects) { gObjectsToLog = PL_NewHashTable(256, HashNumber, PL_CompareValues, PL_CompareValues, NULL, NULL); if (!gObjectsToLog) { NS_WARNING("out of memory"); fprintf(stdout, "### XPCOM_MEM_LOG_OBJECTS defined -- unable to log specific objects\n"); } else 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: "); const char* cp = objects; for (;;) { char* cm = (char*) strchr(cp, ','); if (cm) { *cm = '\0'; } int32_t top = 0; int32_t bottom = 0; while (*cp) { if (*cp == '-') { bottom = top; top = 0; ++cp; } top *= 10; top += *cp - '0'; ++cp; } if (!bottom) { bottom = top; } for(int32_t serialno = bottom; serialno <= top; serialno++) { PL_HashTableAdd(gObjectsToLog, (const void*)serialno, (void*)1); fprintf(stdout, "%d ", serialno); } if (!cm) break; *cm = ','; cp = cm + 1; } fprintf(stdout, "\n"); } } if (gBloatLog || gRefcntsLog || gAllocLog || gLeakyLog || gCOMPtrLog) { gLogging = true; } gTraceLock = PR_NewLock(); }
int main(int argc, char **argv) { if (argc != 2) { fprintf(stderr, "Expected usage: %s <sd-leak-file>\n" " sd-leak-file: Output of --shutdown-leaks=<file> option.\n", argv[0]); return 1; } NS_InitXPCOM2(NULL, NULL, NULL); ADLog log; if (!log.Read(argv[1])) { fprintf(stderr, "%s: Error reading input file %s.\n", argv[0], argv[1]); } const size_t count = log.count(); PLHashTable *memory_map = PL_NewHashTable(count * 8, hash_pointer, PL_CompareValues, PL_CompareValues, 0, 0); if (!memory_map) { fprintf(stderr, "%s: Out of memory.\n", argv[0]); return 1; } // Create one |AllocationNode| object for each log entry, and create // entries in the hashtable pointing to it for each byte it occupies. AllocationNode *nodes = new AllocationNode[count]; if (!nodes) { fprintf(stderr, "%s: Out of memory.\n", argv[0]); return 1; } { AllocationNode *cur_node = nodes; for (ADLog::const_iterator entry = log.begin(), entry_end = log.end(); entry != entry_end; ++entry, ++cur_node) { const ADLog::Entry *e = cur_node->entry = *entry; cur_node->reached = PR_FALSE; for (ADLog::Pointer p = e->address, p_end = e->address + e->datasize; p != p_end; ++p) { PLHashEntry *e = PL_HashTableAdd(memory_map, p, cur_node); if (!e) { fprintf(stderr, "%s: Out of memory.\n", argv[0]); return 1; } } } } // Construct graph based on pointers. for (AllocationNode *node = nodes, *node_end = nodes + count; node != node_end; ++node) { const ADLog::Entry *e = node->entry; for (const char *d = e->data, *d_end = e->data + e->datasize - e->datasize % sizeof(ADLog::Pointer); d != d_end; d += sizeof(ADLog::Pointer)) { AllocationNode *target = (AllocationNode*) PL_HashTableLookup(memory_map, *(void**)d); if (target) { target->pointers_from.AppendElement(node); node->pointers_to.AppendElement(target); } } } // Do a depth-first search on the graph (i.e., by following // |pointers_to|) and assign the post-order index to |index|. { PRUint32 dfs_index = 0; nsVoidArray stack; for (AllocationNode *n = nodes, *n_end = nodes+count; n != n_end; ++n) { if (n->reached) { continue; } stack.AppendElement(n); do { PRUint32 pos = stack.Count() - 1; AllocationNode *n = static_cast<AllocationNode*>(stack[pos]); if (n->reached) { n->index = dfs_index++; stack.RemoveElementAt(pos); } else { n->reached = PR_TRUE; // When doing post-order processing, we have to be // careful not to put reached nodes into the stack. nsVoidArray &pt = n->pointers_to; for (PRInt32 i = pt.Count() - 1; i >= 0; --i) { if (!static_cast<AllocationNode*>(pt[i])->reached) { stack.AppendElement(pt[i]); } } } } while (stack.Count() > 0); } } // Sort the nodes by their DFS index, in reverse, so that the first // node is guaranteed to be in a root SCC. AllocationNode **sorted_nodes = new AllocationNode*[count]; if (!sorted_nodes) { fprintf(stderr, "%s: Out of memory.\n", argv[0]); return 1; } { for (size_t i = 0; i < count; ++i) { sorted_nodes[i] = nodes + i; } NS_QuickSort(sorted_nodes, count, sizeof(AllocationNode*), sort_by_reverse_index, 0); } // Put the nodes into their strongly-connected components. PRUint32 num_sccs = 0; { for (size_t i = 0; i < count; ++i) { nodes[i].reached = PR_FALSE; } nsVoidArray stack; for (AllocationNode **sn = sorted_nodes, **sn_end = sorted_nodes + count; sn != sn_end; ++sn) { if ((*sn)->reached) { continue; } // We found a new strongly connected index. stack.AppendElement(*sn); do { PRUint32 pos = stack.Count() - 1; AllocationNode *n = static_cast<AllocationNode*>(stack[pos]); stack.RemoveElementAt(pos); if (!n->reached) { n->reached = PR_TRUE; n->index = num_sccs; stack.AppendElements(n->pointers_from); } } while (stack.Count() > 0); ++num_sccs; } } // Identify which nodes are leak roots by using DFS, and watching // for component transitions. PRUint32 num_root_nodes = count; { for (size_t i = 0; i < count; ++i) { nodes[i].is_root = PR_TRUE; } nsVoidArray stack; for (AllocationNode *n = nodes, *n_end = nodes+count; n != n_end; ++n) { if (!n->is_root) { continue; } // Loop through pointers_to, and add any that are in a // different SCC to stack: for (int i = n->pointers_to.Count() - 1; i >= 0; --i) { AllocationNode *target = static_cast<AllocationNode*>(n->pointers_to[i]); if (n->index != target->index) { stack.AppendElement(target); } } while (stack.Count() > 0) { PRUint32 pos = stack.Count() - 1; AllocationNode *n = static_cast<AllocationNode*>(stack[pos]); stack.RemoveElementAt(pos); if (n->is_root) { n->is_root = PR_FALSE; --num_root_nodes; stack.AppendElements(n->pointers_to); } } } } // Sort the nodes by their SCC index. NS_QuickSort(sorted_nodes, count, sizeof(AllocationNode*), sort_by_index, 0); // Print output. { printf("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\">\n" "<html>\n" "<head>\n" "<title>Leak analysis</title>\n" "<style type=\"text/css\">\n" " .root { background: white; color: black; }\n" " .nonroot { background: #ccc; color: black; }\n" "</style>\n" "</head>\n"); printf("<body>\n\n" "<p>Generated %d entries (%d in root SCCs) and %d SCCs.</p>\n\n", count, num_root_nodes, num_sccs); for (size_t i = 0; i < count; ++i) { nodes[i].reached = PR_FALSE; } // Loop over the sorted nodes twice, first printing the roots // and then the non-roots. for (PRBool root_type = PR_TRUE; root_type == PR_TRUE || root_type == PR_FALSE; --root_type) { if (root_type) { printf("\n\n" "<div class=\"root\">\n" "<h1 id=\"root\">Root components</h1>\n"); } else { printf("\n\n" "<div class=\"nonroot\">\n" "<h1 id=\"nonroot\">Non-root components</h1>\n"); } PRUint32 component = (PRUint32)-1; PRBool one_object_component; for (const AllocationNode *const* sn = sorted_nodes, *const* sn_end = sorted_nodes + count; sn != sn_end; ++sn) { const AllocationNode *n = *sn; if (n->is_root != root_type) continue; const ADLog::Entry *e = n->entry; if (n->index != component) { component = n->index; one_object_component = sn + 1 == sn_end || (*(sn+1))->index != component; if (!one_object_component) printf("\n\n<h2 id=\"c%d\">Component %d</h2>\n", component, component); } if (one_object_component) { printf("\n\n<div id=\"c%d\">\n", component); printf("<h2 id=\"o%d\">Object %d " "(single-object component %d)</h2>\n", n-nodes, n-nodes, component); } else { printf("\n\n<h3 id=\"o%d\">Object %d</h3>\n", n-nodes, n-nodes); } printf("<pre>\n"); printf("%p <%s> (%d)\n", e->address, e->type, e->datasize); for (size_t d = 0; d < e->datasize; d += sizeof(ADLog::Pointer)) { AllocationNode *target = (AllocationNode*) PL_HashTableLookup(memory_map, *(void**)(e->data + d)); if (target) { printf(" <a href=\"#o%d\">0x%08X</a> <%s>", target - nodes, *(unsigned int*)(e->data + d), target->entry->type); if (target->index != n->index) { printf(", component %d", target->index); } printf("\n"); } else { printf(" 0x%08X\n", *(unsigned int*)(e->data + d)); } } if (n->pointers_from.Count()) { printf("\nPointers from:\n"); for (PRUint32 i = 0, i_end = n->pointers_from.Count(); i != i_end; ++i) { AllocationNode *t = static_cast<AllocationNode*> (n->pointers_from[i]); const ADLog::Entry *te = t->entry; printf(" <a href=\"#o%d\">%s</a> (Object %d, ", t - nodes, te->type, t - nodes); if (t->index != n->index) { printf("component %d, ", t->index); } if (t == n) { printf("self)\n"); } else { printf("%p)\n", te->address); } } } print_escaped(stdout, e->allocation_stack); printf("</pre>\n"); if (one_object_component) { printf("</div>\n"); } } printf("</div>\n"); } printf("</body>\n" "</html>\n"); } delete [] sorted_nodes; delete [] nodes; NS_ShutdownXPCOM(NULL); return 0; }
static void InitTraceLog() { if (gInitialized) { return; } gInitialized = true; bool defined = InitLog("XPCOM_MEM_BLOAT_LOG", "bloat/leaks", &gBloatLog); if (!defined) { gLogLeaksOnly = InitLog("XPCOM_MEM_LEAK_LOG", "leaks", &gBloatLog); } if (defined || gLogLeaksOnly) { RecreateBloatView(); if (!gBloatView) { NS_WARNING("out of memory"); maybeUnregisterAndCloseFile(gBloatLog); gLogLeaksOnly = false; } } InitLog("XPCOM_MEM_REFCNT_LOG", "refcounts", &gRefcntsLog); InitLog("XPCOM_MEM_ALLOC_LOG", "new/delete", &gAllocLog); const char* classes = getenv("XPCOM_MEM_LOG_CLASSES"); #ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR if (classes) { InitLog("XPCOM_MEM_COMPTR_LOG", "nsCOMPtr", &gCOMPtrLog); } else { if (getenv("XPCOM_MEM_COMPTR_LOG")) { fprintf(stdout, "### XPCOM_MEM_COMPTR_LOG defined -- but XPCOM_MEM_LOG_CLASSES is not defined\n"); } } #else const char* 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 if (classes) { // if XPCOM_MEM_LOG_CLASSES was set to some value, the value is interpreted // as a list of class names to track gTypesToLog = PL_NewHashTable(256, PL_HashString, PL_CompareStrings, PL_CompareValues, &typesToLogHashAllocOps, nullptr); if (!gTypesToLog) { NS_WARNING("out of memory"); fprintf(stdout, "### XPCOM_MEM_LOG_CLASSES defined -- unable to log specific classes\n"); } else { fprintf(stdout, "### XPCOM_MEM_LOG_CLASSES defined -- only logging these classes: "); const char* cp = classes; for (;;) { char* cm = (char*)strchr(cp, ','); if (cm) { *cm = '\0'; } PL_HashTableAdd(gTypesToLog, strdup(cp), (void*)1); fprintf(stdout, "%s ", cp); if (!cm) { break; } *cm = ','; cp = cm + 1; } fprintf(stdout, "\n"); } gSerialNumbers = PL_NewHashTable(256, HashNumber, PL_CompareValues, PL_CompareValues, &serialNumberHashAllocOps, nullptr); } const char* objects = getenv("XPCOM_MEM_LOG_OBJECTS"); if (objects) { gObjectsToLog = PL_NewHashTable(256, HashNumber, PL_CompareValues, PL_CompareValues, nullptr, nullptr); if (!gObjectsToLog) { NS_WARNING("out of memory"); fprintf(stdout, "### XPCOM_MEM_LOG_OBJECTS defined -- unable to log specific objects\n"); } else 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: "); const char* 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++) { PL_HashTableAdd(gObjectsToLog, (const void*)serialno, (void*)1); fprintf(stdout, "%" PRIdPTR " ", serialno); } if (!cm) { break; } *cm = ','; cp = cm + 1; } fprintf(stdout, "\n"); } } if (gBloatLog) { gLogging = OnlyBloatLogging; } if (gRefcntsLog || gAllocLog || gCOMPtrLog) { gLogging = FullLogging; } }