diff -r 000000000000 -r 6474c204b198 tools/trace-malloc/lib/nsTraceMalloc.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/trace-malloc/lib/nsTraceMalloc.c Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,2063 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim:cindent:ts=8:et:sw=4: + * + * 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/. */ +#ifdef NS_TRACE_MALLOC + /* + * TODO: + * - FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=392008 + * - extend logfile so 'F' record tells free stack + */ +#include +#include +#include +#include +#ifdef XP_UNIX +#include +#include +#include +#endif +#include "plhash.h" +#include "pratom.h" +#include "prlog.h" +#include "prlock.h" +#include "prmon.h" +#include "prprf.h" +#include "prenv.h" +#include "prnetdb.h" +#include "nsTraceMalloc.h" +#include "nscore.h" +#include "prinit.h" +#include "prthread.h" +#include "plstr.h" +#include "nsStackWalk.h" +#include "nsTraceMallocCallbacks.h" +#include "nsTypeInfo.h" +#include "mozilla/PoisonIOInterposer.h" + +#if defined(XP_MACOSX) + +#include + +#define WRITE_FLAGS "w" + +#define __libc_malloc(x) malloc(x) +#define __libc_realloc(x, y) realloc(x, y) +#define __libc_free(x) free(x) + +#elif defined(XP_UNIX) + +#include + +#define WRITE_FLAGS "w" + +#ifdef WRAP_SYSTEM_INCLUDES +#pragma GCC visibility push(default) +#endif +extern __ptr_t __libc_malloc(size_t); +extern __ptr_t __libc_calloc(size_t, size_t); +extern __ptr_t __libc_realloc(__ptr_t, size_t); +extern void __libc_free(__ptr_t); +extern __ptr_t __libc_memalign(size_t, size_t); +extern __ptr_t __libc_valloc(size_t); +#ifdef WRAP_SYSTEM_INCLUDES +#pragma GCC visibility pop +#endif + +#elif defined(XP_WIN32) + +#include /* for timeb */ +#include /* for fstat */ + +#include /*for write*/ + +#define WRITE_FLAGS "w" + +#define __libc_malloc(x) dhw_orig_malloc(x) +#define __libc_realloc(x, y) dhw_orig_realloc(x,y) +#define __libc_free(x) dhw_orig_free(x) + +#else /* not XP_MACOSX, XP_UNIX, or XP_WIN32 */ + +# error "Unknown build configuration!" + +#endif + +typedef struct logfile logfile; + +#define STARTUP_TMBUFSIZE (64 * 1024) +#define LOGFILE_TMBUFSIZE (16 * 1024) + +struct logfile { + int fd; + int lfd; /* logical fd, dense among all logfiles */ + char *buf; + int bufsize; + int pos; + uint32_t size; + uint32_t simsize; + logfile *next; + logfile **prevp; +}; + +static char default_buf[STARTUP_TMBUFSIZE]; +static logfile default_logfile = + {-1, 0, default_buf, STARTUP_TMBUFSIZE, 0, 0, 0, NULL, NULL}; +static logfile *logfile_list = NULL; +static logfile **logfile_tail = &logfile_list; +static logfile *logfp = &default_logfile; +static PRLock *tmlock = NULL; +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif +static char sdlogname[PATH_MAX] = ""; /* filename for shutdown leak log */ + +/* + * This enables/disables trace-malloc logging. + * + * It is separate from suppress_tracing so that we do not have to pay + * the performance cost of repeated TM_TLS_GET_DATA calls when + * trace-malloc is disabled (which is not as bad as the locking we used + * to have). + * + * It must default to zero, since it can be tested by the Linux malloc + * hooks before NS_TraceMallocStartup sets it. + */ +static uint32_t tracing_enabled = 0; + +/* + * Control whether we should log stacks + */ +static uint32_t stacks_enabled = 1; + +/* + * This lock must be held while manipulating the calltree, the + * allocations table, the log, or the tmstats. + * + * Callers should not *enter* the lock without checking suppress_tracing + * first; otherwise they risk trying to re-enter on the same thread. + */ +#define TM_ENTER_LOCK(t) \ + PR_BEGIN_MACRO \ + PR_ASSERT(t->suppress_tracing != 0); \ + if (tmlock) \ + PR_Lock(tmlock); \ + PR_END_MACRO + +#define TM_EXIT_LOCK(t) \ + PR_BEGIN_MACRO \ + PR_ASSERT(t->suppress_tracing != 0); \ + if (tmlock) \ + PR_Unlock(tmlock); \ + PR_END_MACRO + +#define TM_SUPPRESS_TRACING_AND_ENTER_LOCK(t) \ + PR_BEGIN_MACRO \ + t->suppress_tracing++; \ + TM_ENTER_LOCK(t); \ + PR_END_MACRO + +#define TM_EXIT_LOCK_AND_UNSUPPRESS_TRACING(t) \ + PR_BEGIN_MACRO \ + TM_EXIT_LOCK(t); \ + t->suppress_tracing--; \ + PR_END_MACRO + + +/* + * Thread-local storage. + * + * We can't use NSPR thread-local storage for this because it mallocs + * within PR_GetThreadPrivate (the first time) and PR_SetThreadPrivate + * (which can be worked around by protecting all uses of those functions + * with a monitor, ugh) and because it calls malloc/free when the + * thread-local storage is in an inconsistent state within + * PR_SetThreadPrivate (when expanding the thread-local storage array) + * and _PRI_DetachThread (when and after deleting the thread-local + * storage array). + */ + +#ifdef XP_WIN32 + +#include + +#define TM_TLS_INDEX_TYPE DWORD +#define TM_CREATE_TLS_INDEX(i_) PR_BEGIN_MACRO \ + (i_) = TlsAlloc(); \ + PR_END_MACRO +#define TM_DESTROY_TLS_INDEX(i_) TlsFree((i_)) +#define TM_GET_TLS_DATA(i_) TlsGetValue((i_)) +#define TM_SET_TLS_DATA(i_, v_) TlsSetValue((i_), (v_)) + +#else + +#include + +#define TM_TLS_INDEX_TYPE pthread_key_t +#define TM_CREATE_TLS_INDEX(i_) pthread_key_create(&(i_), NULL) +#define TM_DESTROY_TLS_INDEX(i_) pthread_key_delete((i_)) +#define TM_GET_TLS_DATA(i_) pthread_getspecific((i_)) +#define TM_SET_TLS_DATA(i_, v_) pthread_setspecific((i_), (v_)) + +#endif + +static TM_TLS_INDEX_TYPE tls_index; +static PRBool tls_index_initialized = PR_FALSE; + +/* FIXME (maybe): This is currently unused; we leak the thread-local data. */ +#if 0 +static void +free_tm_thread(void *priv) +{ + tm_thread *t = (tm_thread*) priv; + + PR_ASSERT(t->suppress_tracing == 0); + + if (t->in_heap) { + t->suppress_tracing = 1; + if (t->backtrace_buf.buffer) + __libc_free(t->backtrace_buf.buffer); + + __libc_free(t); + } +} +#endif + +tm_thread * +tm_get_thread(void) +{ + tm_thread *t; + tm_thread stack_tm_thread; + + if (!tls_index_initialized) { + /** + * Assume that the first call to |malloc| will occur before + * there are multiple threads. (If that's not the case, we + * probably need to do the necessary synchronization without + * using NSPR primitives. See discussion in + * https://bugzilla.mozilla.org/show_bug.cgi?id=442192 + */ + TM_CREATE_TLS_INDEX(tls_index); + tls_index_initialized = PR_TRUE; + } + + t = TM_GET_TLS_DATA(tls_index); + + if (!t) { + /* + * First, store a tm_thread on the stack to suppress for the + * malloc below + */ + stack_tm_thread.suppress_tracing = 1; + stack_tm_thread.backtrace_buf.buffer = NULL; + stack_tm_thread.backtrace_buf.size = 0; + stack_tm_thread.backtrace_buf.entries = 0; + TM_SET_TLS_DATA(tls_index, &stack_tm_thread); + + t = (tm_thread*) __libc_malloc(sizeof(tm_thread)); + t->suppress_tracing = 0; + t->backtrace_buf = stack_tm_thread.backtrace_buf; + TM_SET_TLS_DATA(tls_index, t); + + PR_ASSERT(stack_tm_thread.suppress_tracing == 1); /* balanced */ + } + + return t; +} + +/* We don't want more than 32 logfiles open at once, ok? */ +typedef uint32_t lfd_set; + +#define LFD_SET_STATIC_INITIALIZER 0 +#define LFD_SET_SIZE 32 + +#define LFD_ZERO(s) (*(s) = 0) +#define LFD_BIT(i) ((uint32_t)1 << (i)) +#define LFD_TEST(i,s) (LFD_BIT(i) & *(s)) +#define LFD_SET(i,s) (*(s) |= LFD_BIT(i)) +#define LFD_CLR(i,s) (*(s) &= ~LFD_BIT(i)) + +static logfile *get_logfile(int fd) +{ + logfile *fp; + int lfd; + + for (fp = logfile_list; fp; fp = fp->next) { + if (fp->fd == fd) + return fp; + } + lfd = 0; +retry: + for (fp = logfile_list; fp; fp = fp->next) { + if (fp->fd == lfd) { + if (++lfd >= LFD_SET_SIZE) + return NULL; + goto retry; + } + } + fp = __libc_malloc(sizeof(logfile) + LOGFILE_TMBUFSIZE); + if (!fp) + return NULL; + fp->fd = fd; + fp->lfd = lfd; + fp->buf = (char*) (fp + 1); + fp->bufsize = LOGFILE_TMBUFSIZE; + fp->pos = 0; + fp->size = fp->simsize = 0; + fp->next = NULL; + fp->prevp = logfile_tail; + *logfile_tail = fp; + logfile_tail = &fp->next; + return fp; +} + +static void flush_logfile(logfile *fp) +{ + int len, cnt, fd; + char *bp; + + len = fp->pos; + if (len == 0) + return; + fp->pos = 0; + fd = fp->fd; + if (fd >= 0) { + fp->size += len; + bp = fp->buf; + do { + cnt = write(fd, bp, len); + if (cnt <= 0) { + printf("### nsTraceMalloc: write failed or wrote 0 bytes!\n"); + return; + } + bp += cnt; + len -= cnt; + } while (len > 0); + } + fp->simsize += len; +} + +static void log_byte(logfile *fp, char byte) +{ + if (fp->pos == fp->bufsize) + flush_logfile(fp); + fp->buf[fp->pos++] = byte; +} + +static void log_string(logfile *fp, const char *str) +{ + int len, rem, cnt; + + len = strlen(str) + 1; /* include null terminator */ + while ((rem = fp->pos + len - fp->bufsize) > 0) { + cnt = len - rem; + memcpy(&fp->buf[fp->pos], str, cnt); + str += cnt; + fp->pos += cnt; + flush_logfile(fp); + len = rem; + } + memcpy(&fp->buf[fp->pos], str, len); + fp->pos += len; +} + +static void log_filename(logfile* fp, const char* filename) +{ + if (strlen(filename) < 512) { + char *bp, *cp, buf[512]; + + bp = strstr(strcpy(buf, filename), "mozilla"); + if (!bp) + bp = buf; + + for (cp = bp; *cp; cp++) { + if (*cp == '\\') + *cp = '/'; + } + + filename = bp; + } + log_string(fp, filename); +} + +static void log_uint32(logfile *fp, uint32_t ival) +{ + if (ival < 0x80) { + /* 0xxx xxxx */ + log_byte(fp, (char) ival); + } else if (ival < 0x4000) { + /* 10xx xxxx xxxx xxxx */ + log_byte(fp, (char) ((ival >> 8) | 0x80)); + log_byte(fp, (char) (ival & 0xff)); + } else if (ival < 0x200000) { + /* 110x xxxx xxxx xxxx xxxx xxxx */ + log_byte(fp, (char) ((ival >> 16) | 0xc0)); + log_byte(fp, (char) ((ival >> 8) & 0xff)); + log_byte(fp, (char) (ival & 0xff)); + } else if (ival < 0x10000000) { + /* 1110 xxxx xxxx xxxx xxxx xxxx xxxx xxxx */ + log_byte(fp, (char) ((ival >> 24) | 0xe0)); + log_byte(fp, (char) ((ival >> 16) & 0xff)); + log_byte(fp, (char) ((ival >> 8) & 0xff)); + log_byte(fp, (char) (ival & 0xff)); + } else { + /* 1111 0000 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx */ + log_byte(fp, (char) 0xf0); + log_byte(fp, (char) ((ival >> 24) & 0xff)); + log_byte(fp, (char) ((ival >> 16) & 0xff)); + log_byte(fp, (char) ((ival >> 8) & 0xff)); + log_byte(fp, (char) (ival & 0xff)); + } +} + +static void log_event1(logfile *fp, char event, uint32_t serial) +{ + log_byte(fp, event); + log_uint32(fp, (uint32_t) serial); +} + +static void log_event2(logfile *fp, char event, uint32_t serial, size_t size) +{ + log_event1(fp, event, serial); + log_uint32(fp, (uint32_t) size); +} + +static void log_event3(logfile *fp, char event, uint32_t serial, size_t oldsize, + size_t size) +{ + log_event2(fp, event, serial, oldsize); + log_uint32(fp, (uint32_t) size); +} + +static void log_event4(logfile *fp, char event, uint32_t serial, uint32_t ui2, + uint32_t ui3, uint32_t ui4) +{ + log_event3(fp, event, serial, ui2, ui3); + log_uint32(fp, ui4); +} + +static void log_event5(logfile *fp, char event, uint32_t serial, uint32_t ui2, + uint32_t ui3, uint32_t ui4, uint32_t ui5) +{ + log_event4(fp, event, serial, ui2, ui3, ui4); + log_uint32(fp, ui5); +} + +static void log_event6(logfile *fp, char event, uint32_t serial, uint32_t ui2, + uint32_t ui3, uint32_t ui4, uint32_t ui5, uint32_t ui6) +{ + log_event5(fp, event, serial, ui2, ui3, ui4, ui5); + log_uint32(fp, ui6); +} + +static void log_event7(logfile *fp, char event, uint32_t serial, uint32_t ui2, + uint32_t ui3, uint32_t ui4, uint32_t ui5, uint32_t ui6, + uint32_t ui7) +{ + log_event6(fp, event, serial, ui2, ui3, ui4, ui5, ui6); + log_uint32(fp, ui7); +} + +static void log_event8(logfile *fp, char event, uint32_t serial, uint32_t ui2, + uint32_t ui3, uint32_t ui4, uint32_t ui5, uint32_t ui6, + uint32_t ui7, uint32_t ui8) +{ + log_event7(fp, event, serial, ui2, ui3, ui4, ui5, ui6, ui7); + log_uint32(fp, ui8); +} + +typedef struct callsite callsite; + +struct callsite { + void* pc; + uint32_t serial; + lfd_set lfdset; + const char *name; /* pointer to string owned by methods table */ + const char *library; /* pointer to string owned by libraries table */ + int offset; + callsite *parent; + callsite *siblings; + callsite *kids; +}; + +/* NB: these counters are incremented and decremented only within tmlock. */ +static uint32_t library_serial_generator = 0; +static uint32_t method_serial_generator = 0; +static uint32_t callsite_serial_generator = 0; +static uint32_t tmstats_serial_generator = 0; +static uint32_t filename_serial_generator = 0; + +/* Root of the tree of callsites, the sum of all (cycle-compressed) stacks. */ +static callsite calltree_root = + {0, 0, LFD_SET_STATIC_INITIALIZER, NULL, NULL, 0, NULL, NULL, NULL}; + +/* a fake pc for when stacks are disabled; must be different from the + pc in calltree_root */ +#define STACK_DISABLED_PC ((void*)1) + +/* Basic instrumentation. */ +static nsTMStats tmstats = NS_TMSTATS_STATIC_INITIALIZER; + +/* Parent with the most kids (tmstats.calltree_maxkids). */ +static callsite *calltree_maxkids_parent; + +/* Calltree leaf for path with deepest stack backtrace. */ +static callsite *calltree_maxstack_top; + +/* Last site (i.e., calling pc) that recurred during a backtrace. */ +static callsite *last_callsite_recurrence; + +static void log_tmstats(logfile *fp) +{ + log_event1(fp, TM_EVENT_STATS, ++tmstats_serial_generator); + log_uint32(fp, tmstats.calltree_maxstack); + log_uint32(fp, tmstats.calltree_maxdepth); + log_uint32(fp, tmstats.calltree_parents); + log_uint32(fp, tmstats.calltree_maxkids); + log_uint32(fp, tmstats.calltree_kidhits); + log_uint32(fp, tmstats.calltree_kidmisses); + log_uint32(fp, tmstats.calltree_kidsteps); + log_uint32(fp, tmstats.callsite_recurrences); + log_uint32(fp, tmstats.backtrace_calls); + log_uint32(fp, tmstats.backtrace_failures); + log_uint32(fp, tmstats.btmalloc_failures); + log_uint32(fp, tmstats.dladdr_failures); + log_uint32(fp, tmstats.malloc_calls); + log_uint32(fp, tmstats.malloc_failures); + log_uint32(fp, tmstats.calloc_calls); + log_uint32(fp, tmstats.calloc_failures); + log_uint32(fp, tmstats.realloc_calls); + log_uint32(fp, tmstats.realloc_failures); + log_uint32(fp, tmstats.free_calls); + log_uint32(fp, tmstats.null_free_calls); + log_uint32(fp, calltree_maxkids_parent ? calltree_maxkids_parent->serial + : 0); + log_uint32(fp, calltree_maxstack_top ? calltree_maxstack_top->serial : 0); +} + +static void *generic_alloctable(void *pool, size_t size) +{ + return __libc_malloc(size); +} + +static void generic_freetable(void *pool, void *item) +{ + __libc_free(item); +} + +typedef struct lfdset_entry { + PLHashEntry base; + lfd_set lfdset; +} lfdset_entry; + +static PLHashEntry *lfdset_allocentry(void *pool, const void *key) +{ + lfdset_entry *le = __libc_malloc(sizeof *le); + if (le) + LFD_ZERO(&le->lfdset); + return &le->base; +} + +static void lfdset_freeentry(void *pool, PLHashEntry *he, unsigned flag) +{ + lfdset_entry *le; + + if (flag != HT_FREE_ENTRY) + return; + le = (lfdset_entry*) he; + __libc_free((void*) le); +} + +static PLHashAllocOps lfdset_hashallocops = { + generic_alloctable, generic_freetable, + lfdset_allocentry, lfdset_freeentry +}; + +/* Table of library pathnames mapped to to logged 'L' record serial numbers. */ +static PLHashTable *libraries = NULL; + +/* Table of filename pathnames mapped to logged 'G' record serial numbers. */ +static PLHashTable *filenames = NULL; + +/* Table mapping method names to logged 'N' record serial numbers. */ +static PLHashTable *methods = NULL; + +/* + * Presumes that its caller is holding tmlock, but may temporarily exit + * the lock. + */ +static callsite * +calltree(void **stack, size_t num_stack_entries, tm_thread *t) +{ + logfile *fp = logfp; + void *pc; + uint32_t nkids; + callsite *parent, *site, **csp, *tmp; + int maxstack; + uint32_t library_serial, method_serial, filename_serial; + const char *library, *method, *filename; + char *slash; + PLHashNumber hash; + PLHashEntry **hep, *he; + lfdset_entry *le; + size_t stack_index; + nsCodeAddressDetails details; + nsresult rv; + + maxstack = (num_stack_entries > tmstats.calltree_maxstack); + if (maxstack) { + /* these two are the same, although that used to be less clear */ + tmstats.calltree_maxstack = num_stack_entries; + tmstats.calltree_maxdepth = num_stack_entries; + } + + /* Reverse the stack again, finding and building a path in the tree. */ + parent = &calltree_root; + stack_index = num_stack_entries; + do { + --stack_index; + pc = stack[stack_index]; + + csp = &parent->kids; + while ((site = *csp) != NULL) { + if (site->pc == pc) { + tmstats.calltree_kidhits++; + + /* Put the most recently used site at the front of siblings. */ + *csp = site->siblings; + site->siblings = parent->kids; + parent->kids = site; + + /* Check whether we've logged for this site and logfile yet. */ + if (!LFD_TEST(fp->lfd, &site->lfdset)) { + /* + * Some other logfile put this site in the calltree. We + * must log an event for site, and possibly first for its + * method and/or library. Note the code after the while + * loop that tests if (!site). + */ + break; + } + + /* Site already built and logged to fp -- go up the stack. */ + goto upward; + } + tmstats.calltree_kidsteps++; + csp = &site->siblings; + } + + if (!site) { + tmstats.calltree_kidmisses++; + + /* Check for recursion: see if pc is on our ancestor line. */ + for (site = parent; site; site = site->parent) { + if (site->pc == pc) { + tmstats.callsite_recurrences++; + last_callsite_recurrence = site; + goto upward; + } + } + } + + /* + * Not in tree at all, or not logged to fp: let's find our symbolic + * callsite info. + */ + + if (!stacks_enabled) { + /* + * Fake the necessary information for our single fake stack + * frame. + */ + PL_strncpyz(details.library, "stacks_disabled", + sizeof(details.library)); + details.loffset = 0; + details.filename[0] = '\0'; + details.lineno = 0; + details.function[0] = '\0'; + details.foffset = 0; + } else { + /* + * NS_DescribeCodeAddress can (on Linux) acquire a lock inside + * the shared library loader. Another thread might call malloc + * while holding that lock (when loading a shared library). So + * we have to exit tmlock around this call. For details, see + * https://bugzilla.mozilla.org/show_bug.cgi?id=363334#c3 + * + * We could be more efficient by building the nodes in the + * calltree, exiting the monitor once to describe all of them, + * and then filling in the descriptions for any that hadn't been + * described already. But this is easier for now. + */ + TM_EXIT_LOCK(t); + rv = NS_DescribeCodeAddress(pc, &details); + TM_ENTER_LOCK(t); + if (NS_FAILED(rv)) { + tmstats.dladdr_failures++; + goto fail; + } + } + + /* Check whether we need to emit a library trace record. */ + library_serial = 0; + library = NULL; + if (details.library[0]) { + if (!libraries) { + libraries = PL_NewHashTable(100, PL_HashString, + PL_CompareStrings, PL_CompareValues, + &lfdset_hashallocops, NULL); + if (!libraries) { + tmstats.btmalloc_failures++; + goto fail; + } + } + hash = PL_HashString(details.library); + hep = PL_HashTableRawLookup(libraries, hash, details.library); + he = *hep; + if (he) { + library = (char*) he->key; + library_serial = (uint32_t) NS_PTR_TO_INT32(he->value); + le = (lfdset_entry *) he; + if (LFD_TEST(fp->lfd, &le->lfdset)) { + /* We already logged an event on fp for this library. */ + le = NULL; + } + } else { + library = strdup(details.library); + if (library) { + library_serial = ++library_serial_generator; + he = PL_HashTableRawAdd(libraries, hep, hash, library, + NS_INT32_TO_PTR(library_serial)); + } + if (!he) { + tmstats.btmalloc_failures++; + goto fail; + } + le = (lfdset_entry *) he; + } + if (le) { + /* Need to log an event to fp for this lib. */ + slash = strrchr(library, '/'); + log_event1(fp, TM_EVENT_LIBRARY, library_serial); + log_string(fp, slash ? slash + 1 : library); + LFD_SET(fp->lfd, &le->lfdset); + } + } + + /* For compatibility with current log format, always emit a + * filename trace record, using "noname" / 0 when no file name + * is available. */ + filename_serial = 0; + filename = details.filename[0] ? details.filename : "noname"; + if (!filenames) { + filenames = PL_NewHashTable(100, PL_HashString, + PL_CompareStrings, PL_CompareValues, + &lfdset_hashallocops, NULL); + if (!filenames) { + tmstats.btmalloc_failures++; + return NULL; + } + } + hash = PL_HashString(filename); + hep = PL_HashTableRawLookup(filenames, hash, filename); + he = *hep; + if (he) { + filename = (char*) he->key; + filename_serial = (uint32_t) NS_PTR_TO_INT32(he->value); + le = (lfdset_entry *) he; + if (LFD_TEST(fp->lfd, &le->lfdset)) { + /* We already logged an event on fp for this filename. */ + le = NULL; + } + } else { + filename = strdup(filename); + if (filename) { + filename_serial = ++filename_serial_generator; + he = PL_HashTableRawAdd(filenames, hep, hash, filename, + NS_INT32_TO_PTR(filename_serial)); + } + if (!he) { + tmstats.btmalloc_failures++; + return NULL; + } + le = (lfdset_entry *) he; + } + if (le) { + /* Need to log an event to fp for this filename. */ + log_event1(fp, TM_EVENT_FILENAME, filename_serial); + log_filename(fp, filename); + LFD_SET(fp->lfd, &le->lfdset); + } + + if (!details.function[0]) { + PR_snprintf(details.function, sizeof(details.function), + "%s+%X", library ? library : "main", details.loffset); + } + + /* Emit an 'N' (for New method, 'M' is for malloc!) event if needed. */ + method_serial = 0; + if (!methods) { + methods = PL_NewHashTable(10000, PL_HashString, + PL_CompareStrings, PL_CompareValues, + &lfdset_hashallocops, NULL); + if (!methods) { + tmstats.btmalloc_failures++; + goto fail; + } + } + hash = PL_HashString(details.function); + hep = PL_HashTableRawLookup(methods, hash, details.function); + he = *hep; + if (he) { + method = (char*) he->key; + method_serial = (uint32_t) NS_PTR_TO_INT32(he->value); + le = (lfdset_entry *) he; + if (LFD_TEST(fp->lfd, &le->lfdset)) { + /* We already logged an event on fp for this method. */ + le = NULL; + } + } else { + method = strdup(details.function); + if (method) { + method_serial = ++method_serial_generator; + he = PL_HashTableRawAdd(methods, hep, hash, method, + NS_INT32_TO_PTR(method_serial)); + } + if (!he) { + tmstats.btmalloc_failures++; + return NULL; + } + le = (lfdset_entry *) he; + } + if (le) { + log_event4(fp, TM_EVENT_METHOD, method_serial, library_serial, + filename_serial, details.lineno); + log_string(fp, method); + LFD_SET(fp->lfd, &le->lfdset); + } + + /* Create a new callsite record. */ + if (!site) { + site = __libc_malloc(sizeof(callsite)); + if (!site) { + tmstats.btmalloc_failures++; + goto fail; + } + + /* Update parent and max-kids-per-parent stats. */ + if (!parent->kids) + tmstats.calltree_parents++; + nkids = 1; + for (tmp = parent->kids; tmp; tmp = tmp->siblings) + nkids++; + if (nkids > tmstats.calltree_maxkids) { + tmstats.calltree_maxkids = nkids; + calltree_maxkids_parent = parent; + } + + /* Insert the new site into the tree. */ + site->pc = pc; + site->serial = ++callsite_serial_generator; + LFD_ZERO(&site->lfdset); + site->name = method; + site->library = library; + site->offset = details.loffset; + site->parent = parent; + site->siblings = parent->kids; + parent->kids = site; + site->kids = NULL; + } + + /* Log the site with its parent, method, and offset. */ + log_event4(fp, TM_EVENT_CALLSITE, site->serial, parent->serial, + method_serial, details.foffset); + LFD_SET(fp->lfd, &site->lfdset); + + upward: + parent = site; + } while (stack_index > 0); + + if (maxstack) + calltree_maxstack_top = site; + + return site; + + fail: + return NULL; +} + +/* + * Buffer the stack from top at low index to bottom at high, so that we can + * reverse it in calltree. + */ +static void +stack_callback(void *pc, void *sp, void *closure) +{ + stack_buffer_info *info = (stack_buffer_info*) closure; + + /* + * If we run out of buffer, keep incrementing entries so that + * backtrace can call us again with a bigger buffer. + */ + if (info->entries < info->size) + info->buffer[info->entries] = pc; + ++info->entries; +} + +/* + * The caller MUST NOT be holding tmlock when calling backtrace. + * On return, if *immediate_abort is set, then the return value is NULL + * and the thread is in a very dangerous situation (e.g. holding + * sem_pool_lock in Mac OS X pthreads); the caller should bail out + * without doing anything (such as acquiring locks). + */ +static callsite * +backtrace(tm_thread *t, int skipFrames, int *immediate_abort) +{ + callsite *site; + stack_buffer_info *info = &t->backtrace_buf; + void ** new_stack_buffer; + size_t new_stack_buffer_size; + nsresult rv; + + t->suppress_tracing++; + + if (!stacks_enabled) { +#if defined(XP_MACOSX) + /* Walk the stack, even if stacks_enabled is false. We do this to + check if we must set immediate_abort. */ + info->entries = 0; + rv = NS_StackWalk(stack_callback, skipFrames, /* maxFrames */ 0, info, + 0, NULL); + *immediate_abort = rv == NS_ERROR_UNEXPECTED; + if (rv == NS_ERROR_UNEXPECTED || info->entries == 0) { + t->suppress_tracing--; + return NULL; + } +#endif + + /* + * Create a single fake stack frame so that all the tools get + * data in the correct format. + */ + *immediate_abort = 0; + if (info->size < 1) { + PR_ASSERT(!info->buffer); /* !info->size == !info->buffer */ + info->buffer = __libc_malloc(1 * sizeof(void*)); + if (!info->buffer) + return NULL; + info->size = 1; + } + + info->entries = 1; + info->buffer[0] = STACK_DISABLED_PC; + } else { + /* + * NS_StackWalk can (on Windows) acquire a lock the shared library + * loader. Another thread might call malloc while holding that lock + * (when loading a shared library). So we can't be in tmlock during + * this call. For details, see + * https://bugzilla.mozilla.org/show_bug.cgi?id=374829#c8 + */ + + /* + * skipFrames == 0 means |backtrace| should show up, so don't use + * skipFrames + 1. + * NB: this call is repeated below if the buffer is too small. + */ + info->entries = 0; + rv = NS_StackWalk(stack_callback, skipFrames, /* maxFrames */ 0, info, + 0, NULL); + *immediate_abort = rv == NS_ERROR_UNEXPECTED; + if (rv == NS_ERROR_UNEXPECTED || info->entries == 0) { + t->suppress_tracing--; + return NULL; + } + + /* + * To avoid allocating in stack_callback (which, on Windows, is + * called on a different thread from the one we're running on here), + * reallocate here if it didn't have a big enough buffer (which + * includes the first call on any thread), and call it again. + */ + if (info->entries > info->size) { + new_stack_buffer_size = 2 * info->entries; + new_stack_buffer = __libc_realloc(info->buffer, + new_stack_buffer_size * sizeof(void*)); + if (!new_stack_buffer) + return NULL; + info->buffer = new_stack_buffer; + info->size = new_stack_buffer_size; + + /* and call NS_StackWalk again */ + info->entries = 0; + NS_StackWalk(stack_callback, skipFrames, /* maxFrames */ 0, info, + 0, NULL); + + /* same stack */ + PR_ASSERT(info->entries * 2 == new_stack_buffer_size); + } + } + + TM_ENTER_LOCK(t); + + site = calltree(info->buffer, info->entries, t); + + tmstats.backtrace_calls++; + if (!site) { + tmstats.backtrace_failures++; + PR_ASSERT(tmstats.backtrace_failures < 100); + } + TM_EXIT_LOCK(t); + + t->suppress_tracing--; + return site; +} + +typedef struct allocation { + PLHashEntry entry; + size_t size; + FILE *trackfp; /* for allocation tracking */ +} allocation; + +#define ALLOC_HEAP_SIZE 150000 + +static allocation alloc_heap[ALLOC_HEAP_SIZE]; +static allocation *alloc_freelist = NULL; +static int alloc_heap_initialized = 0; + +static PLHashEntry *alloc_allocentry(void *pool, const void *key) +{ + allocation **listp, *alloc; + int n; + + if (!alloc_heap_initialized) { + n = ALLOC_HEAP_SIZE; + listp = &alloc_freelist; + for (alloc = alloc_heap; --n >= 0; alloc++) { + *listp = alloc; + listp = (allocation**) &alloc->entry.next; + } + *listp = NULL; + alloc_heap_initialized = 1; + } + + listp = &alloc_freelist; + alloc = *listp; + if (!alloc) + return __libc_malloc(sizeof(allocation)); + *listp = (allocation*) alloc->entry.next; + return &alloc->entry; +} + +static void alloc_freeentry(void *pool, PLHashEntry *he, unsigned flag) +{ + allocation *alloc; + + if (flag != HT_FREE_ENTRY) + return; + alloc = (allocation*) he; + if ((ptrdiff_t)(alloc - alloc_heap) < (ptrdiff_t)ALLOC_HEAP_SIZE) { + alloc->entry.next = &alloc_freelist->entry; + alloc_freelist = alloc; + } else { + __libc_free((void*) alloc); + } +} + +static PLHashAllocOps alloc_hashallocops = { + generic_alloctable, generic_freetable, + alloc_allocentry, alloc_freeentry +}; + +static PLHashNumber hash_pointer(const void *key) +{ + return (PLHashNumber) key; +} + +static PLHashTable *allocations = NULL; + +static PLHashTable *new_allocations(void) +{ + allocations = PL_NewHashTable(200000, hash_pointer, + PL_CompareValues, PL_CompareValues, + &alloc_hashallocops, NULL); + return allocations; +} + +#define get_allocations() (allocations ? allocations : new_allocations()) + +#if defined(XP_MACOSX) + +/* from malloc.c in Libc */ +typedef void +malloc_logger_t(uint32_t type, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, + uintptr_t result, uint32_t num_hot_frames_to_skip); + +extern malloc_logger_t *malloc_logger; + +#define MALLOC_LOG_TYPE_ALLOCATE 2 +#define MALLOC_LOG_TYPE_DEALLOCATE 4 +#define MALLOC_LOG_TYPE_HAS_ZONE 8 +#define MALLOC_LOG_TYPE_CLEARED 64 + +static void +my_malloc_logger(uint32_t type, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, + uintptr_t result, uint32_t num_hot_frames_to_skip) +{ + uintptr_t all_args[3] = { arg1, arg2, arg3 }; + uintptr_t *args = all_args + ((type & MALLOC_LOG_TYPE_HAS_ZONE) ? 1 : 0); + + uint32_t alloc_type = + type & (MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_DEALLOCATE); + tm_thread *t = tm_get_thread(); + + if (alloc_type == (MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_DEALLOCATE)) { + ReallocCallback((void*)args[0], (void*)result, args[1], 0, 0, t); + } else if (alloc_type == MALLOC_LOG_TYPE_ALLOCATE) { + /* + * We don't get size/count information for calloc, so just use + * MallocCallback. + */ + MallocCallback((void*)result, args[0], 0, 0, t); + } else if (alloc_type == MALLOC_LOG_TYPE_DEALLOCATE) { + FreeCallback((void*)args[0], 0, 0, t); + } +} + +static void +StartupHooker(void) +{ + PR_ASSERT(!malloc_logger); + malloc_logger = my_malloc_logger; +} + +static void +ShutdownHooker(void) +{ + PR_ASSERT(malloc_logger == my_malloc_logger); + malloc_logger = NULL; +} + +#elif defined(XP_UNIX) + +/* + * We can't use glibc's malloc hooks because they can't be used in a + * threadsafe manner. They require unsetting the hooks to call into the + * original malloc implementation, and then resetting them when the + * original implementation returns. If another thread calls the same + * allocation function while the hooks are unset, we have no chance to + * intercept the call. + */ + +NS_EXTERNAL_VIS_(__ptr_t) +malloc(size_t size) +{ + uint32_t start, end; + __ptr_t ptr; + tm_thread *t; + + if (!tracing_enabled || !PR_Initialized() || + (t = tm_get_thread())->suppress_tracing != 0) { + return __libc_malloc(size); + } + + t->suppress_tracing++; + start = PR_IntervalNow(); + ptr = __libc_malloc(size); + end = PR_IntervalNow(); + t->suppress_tracing--; + + MallocCallback(ptr, size, start, end, t); + + return ptr; +} + +NS_EXTERNAL_VIS_(__ptr_t) +calloc(size_t count, size_t size) +{ + uint32_t start, end; + __ptr_t ptr; + tm_thread *t; + + if (!tracing_enabled || !PR_Initialized() || + (t = tm_get_thread())->suppress_tracing != 0) { + return __libc_calloc(count, size); + } + + t->suppress_tracing++; + start = PR_IntervalNow(); + ptr = __libc_calloc(count, size); + end = PR_IntervalNow(); + t->suppress_tracing--; + + CallocCallback(ptr, count, size, start, end, t); + + return ptr; +} + +NS_EXTERNAL_VIS_(__ptr_t) +realloc(__ptr_t oldptr, size_t size) +{ + uint32_t start, end; + __ptr_t ptr; + tm_thread *t; + + if (!tracing_enabled || !PR_Initialized() || + (t = tm_get_thread())->suppress_tracing != 0) { + return __libc_realloc(oldptr, size); + } + + t->suppress_tracing++; + start = PR_IntervalNow(); + ptr = __libc_realloc(oldptr, size); + end = PR_IntervalNow(); + t->suppress_tracing--; + + /* FIXME bug 392008: We could race with reallocation of oldptr. */ + ReallocCallback(oldptr, ptr, size, start, end, t); + + return ptr; +} + +NS_EXTERNAL_VIS_(void*) +valloc(size_t size) +{ + uint32_t start, end; + __ptr_t ptr; + tm_thread *t; + + if (!tracing_enabled || !PR_Initialized() || + (t = tm_get_thread())->suppress_tracing != 0) { + return __libc_valloc(size); + } + + t->suppress_tracing++; + start = PR_IntervalNow(); + ptr = __libc_valloc(size); + end = PR_IntervalNow(); + t->suppress_tracing--; + + MallocCallback(ptr, size, start, end, t); + + return ptr; +} + +NS_EXTERNAL_VIS_(void*) +memalign(size_t boundary, size_t size) +{ + uint32_t start, end; + __ptr_t ptr; + tm_thread *t; + + if (!tracing_enabled || !PR_Initialized() || + (t = tm_get_thread())->suppress_tracing != 0) { + return __libc_memalign(boundary, size); + } + + t->suppress_tracing++; + start = PR_IntervalNow(); + ptr = __libc_memalign(boundary, size); + end = PR_IntervalNow(); + t->suppress_tracing--; + + MallocCallback(ptr, size, start, end, t); + + return ptr; +} + +NS_EXTERNAL_VIS_(int) +posix_memalign(void **memptr, size_t alignment, size_t size) +{ + __ptr_t ptr = memalign(alignment, size); + if (!ptr) + return ENOMEM; + *memptr = ptr; + return 0; +} + +NS_EXTERNAL_VIS_(void) +free(__ptr_t ptr) +{ + uint32_t start, end; + tm_thread *t; + + if (!tracing_enabled || !PR_Initialized() || + (t = tm_get_thread())->suppress_tracing != 0) { + __libc_free(ptr); + return; + } + + t->suppress_tracing++; + start = PR_IntervalNow(); + __libc_free(ptr); + end = PR_IntervalNow(); + t->suppress_tracing--; + + /* FIXME bug 392008: We could race with reallocation of ptr. */ + + FreeCallback(ptr, start, end, t); +} + +NS_EXTERNAL_VIS_(void) +cfree(void *ptr) +{ + free(ptr); +} + +#define StartupHooker() PR_BEGIN_MACRO PR_END_MACRO +#define ShutdownHooker() PR_BEGIN_MACRO PR_END_MACRO + +#elif defined(XP_WIN32) + +/* See nsWinTraceMalloc.cpp. */ + +#endif + +static const char magic[] = NS_TRACE_MALLOC_MAGIC; + +static void +log_header(int logfd) +{ + uint32_t ticksPerSec = PR_htonl(PR_TicksPerSecond()); + (void) write(logfd, magic, NS_TRACE_MALLOC_MAGIC_SIZE); + (void) write(logfd, &ticksPerSec, sizeof ticksPerSec); +} + +PR_IMPLEMENT(void) +NS_TraceMallocStartup(int logfd) +{ + const char* stack_disable_env; + + /* We must be running on the primordial thread. */ + PR_ASSERT(tracing_enabled == 0); + PR_ASSERT(logfp == &default_logfile); + tracing_enabled = (logfd >= 0); + + if (logfd >= 3) + MozillaRegisterDebugFD(logfd); + + /* stacks are disabled if this env var is set to a non-empty value */ + stack_disable_env = PR_GetEnv("NS_TRACE_MALLOC_DISABLE_STACKS"); + stacks_enabled = !stack_disable_env || !*stack_disable_env; + + if (tracing_enabled) { + PR_ASSERT(logfp->simsize == 0); /* didn't overflow startup buffer */ + + /* Log everything in logfp (aka default_logfile)'s buffer to logfd. */ + logfp->fd = logfd; + logfile_list = &default_logfile; + logfp->prevp = &logfile_list; + logfile_tail = &logfp->next; + log_header(logfd); + } + + RegisterTraceMallocShutdown(); + + tmlock = PR_NewLock(); + (void) tm_get_thread(); /* ensure index initialization while it's easy */ + + if (tracing_enabled) + StartupHooker(); +} + +/* + * Options for log files, with the log file name either as the next option + * or separated by '=' (e.g. "./mozilla --trace-malloc * malloc.log" or + * "./mozilla --trace-malloc=malloc.log"). + */ +static const char TMLOG_OPTION[] = "--trace-malloc"; +static const char SDLOG_OPTION[] = "--shutdown-leaks"; + +#define SHOULD_PARSE_ARG(name_, log_, arg_) \ + (0 == strncmp(arg_, name_, sizeof(name_) - 1)) + +#define PARSE_ARG(name_, log_, argv_, i_, consumed_) \ + PR_BEGIN_MACRO \ + char _nextchar = argv_[i_][sizeof(name_) - 1]; \ + if (_nextchar == '=') { \ + log_ = argv_[i_] + sizeof(name_); \ + consumed_ = 1; \ + } else if (_nextchar == '\0') { \ + log_ = argv_[i_+1]; \ + consumed_ = 2; \ + } \ + PR_END_MACRO + +PR_IMPLEMENT(int) +NS_TraceMallocStartupArgs(int argc, char **argv) +{ + int i, logfd = -1, consumed, logflags; + char *tmlogname = NULL, *sdlogname_local = NULL; + + /* + * Look for the --trace-malloc option early, to avoid missing + * early mallocs (we miss static constructors whose output overflows the + * log file's static 16K output buffer). + */ + for (i = 1; i < argc; i += consumed) { + consumed = 0; + if (SHOULD_PARSE_ARG(TMLOG_OPTION, tmlogname, argv[i])) + PARSE_ARG(TMLOG_OPTION, tmlogname, argv, i, consumed); + else if (SHOULD_PARSE_ARG(SDLOG_OPTION, sdlogname_local, argv[i])) + PARSE_ARG(SDLOG_OPTION, sdlogname_local, argv, i, consumed); + + if (consumed) { +#ifndef XP_WIN32 /* If we don't comment this out, it will crash Windows. */ + int j; + /* Now remove --trace-malloc and its argument from argv. */ + argc -= consumed; + for (j = i; j < argc; ++j) + argv[j] = argv[j+consumed]; + argv[argc] = NULL; + consumed = 0; /* don't advance next iteration */ +#endif + } else { + consumed = 1; + } + } + + if (tmlogname) { +#ifdef XP_UNIX + int pipefds[2]; +#endif + + switch (*tmlogname) { +#ifdef XP_UNIX + case '|': + if (pipe(pipefds) == 0) { + pid_t pid = fork(); + if (pid == 0) { + /* In child: set up stdin, parse args, and exec. */ + int maxargc, nargc; + char **nargv, *token; + + if (pipefds[0] != 0) { + dup2(pipefds[0], 0); + close(pipefds[0]); + } + close(pipefds[1]); + + tmlogname = strtok(tmlogname + 1, " \t"); + maxargc = 3; + nargv = (char **) malloc((maxargc+1) * sizeof(char *)); + if (!nargv) exit(1); + nargc = 0; + nargv[nargc++] = tmlogname; + while ((token = strtok(NULL, " \t")) != NULL) { + if (nargc == maxargc) { + maxargc *= 2; + nargv = (char**) + realloc(nargv, (maxargc+1) * sizeof(char*)); + if (!nargv) exit(1); + } + nargv[nargc++] = token; + } + nargv[nargc] = NULL; + + (void) setsid(); + execvp(tmlogname, nargv); + exit(127); + } + + if (pid > 0) { + /* In parent: set logfd to the pipe's write side. */ + close(pipefds[0]); + logfd = pipefds[1]; + } + } + if (logfd < 0) { + fprintf(stderr, + "%s: can't pipe to trace-malloc child process %s: %s\n", + argv[0], tmlogname, strerror(errno)); + exit(1); + } + break; +#endif /*XP_UNIX*/ + case '-': + /* Don't log from startup, but do prepare to log later. */ + /* XXX traditional meaning of '-' as option argument is "stdin" or "stdout" */ + if (tmlogname[1] == '\0') + break; + /* FALL THROUGH */ + + default: + logflags = O_CREAT | O_WRONLY | O_TRUNC; +#if defined(XP_WIN32) + /* + * Avoid translations on WIN32. + */ + logflags |= O_BINARY; +#endif + logfd = open(tmlogname, logflags, 0644); + if (logfd < 0) { + fprintf(stderr, + "%s: can't create trace-malloc log named %s: %s\n", + argv[0], tmlogname, strerror(errno)); + exit(1); + } + break; + } + } + + if (sdlogname_local) { + strncpy(sdlogname, sdlogname_local, sizeof(sdlogname)); + sdlogname[sizeof(sdlogname) - 1] = '\0'; + } + + NS_TraceMallocStartup(logfd); + return argc; +} + +PR_IMPLEMENT(PRBool) +NS_TraceMallocHasStarted(void) +{ + return tmlock ? PR_TRUE : PR_FALSE; +} + +PR_IMPLEMENT(void) +NS_TraceMallocShutdown(void) +{ + logfile *fp; + + if (sdlogname[0]) + NS_TraceMallocDumpAllocations(sdlogname); + + if (tmstats.backtrace_failures) { + fprintf(stderr, + "TraceMalloc backtrace failures: %lu (malloc %lu dladdr %lu)\n", + (unsigned long) tmstats.backtrace_failures, + (unsigned long) tmstats.btmalloc_failures, + (unsigned long) tmstats.dladdr_failures); + } + while ((fp = logfile_list) != NULL) { + logfile_list = fp->next; + log_tmstats(fp); + flush_logfile(fp); + if (fp->fd >= 0) { + MozillaUnRegisterDebugFD(fp->fd); + close(fp->fd); + fp->fd = -1; + } + if (fp != &default_logfile) { + if (fp == logfp) + logfp = &default_logfile; + free((void*) fp); + } + } + if (tmlock) { + PRLock *lock = tmlock; + tmlock = NULL; + PR_DestroyLock(lock); + } + if (tracing_enabled) { + tracing_enabled = 0; + ShutdownHooker(); + } +} + +PR_IMPLEMENT(void) +NS_TraceMallocDisable(void) +{ + tm_thread *t = tm_get_thread(); + logfile *fp; + uint32_t sample; + + /* Robustify in case of duplicate call. */ + PR_ASSERT(tracing_enabled); + if (tracing_enabled == 0) + return; + + TM_SUPPRESS_TRACING_AND_ENTER_LOCK(t); + for (fp = logfile_list; fp; fp = fp->next) + flush_logfile(fp); + sample = --tracing_enabled; + TM_EXIT_LOCK_AND_UNSUPPRESS_TRACING(t); + if (sample == 0) + ShutdownHooker(); +} + +PR_IMPLEMENT(void) +NS_TraceMallocEnable(void) +{ + tm_thread *t = tm_get_thread(); + uint32_t sample; + + TM_SUPPRESS_TRACING_AND_ENTER_LOCK(t); + sample = ++tracing_enabled; + TM_EXIT_LOCK_AND_UNSUPPRESS_TRACING(t); + if (sample == 1) + StartupHooker(); +} + +PR_IMPLEMENT(int) +NS_TraceMallocChangeLogFD(int fd) +{ + logfile *oldfp, *fp; + struct stat sb; + tm_thread *t = tm_get_thread(); + + TM_SUPPRESS_TRACING_AND_ENTER_LOCK(t); + oldfp = logfp; + if (oldfp->fd != fd) { + flush_logfile(oldfp); + fp = get_logfile(fd); + if (!fp) { + TM_EXIT_LOCK_AND_UNSUPPRESS_TRACING(t); + return -2; + } + if (fd >= 0 && fstat(fd, &sb) == 0 && sb.st_size == 0) + log_header(fd); + logfp = fp; + } + TM_EXIT_LOCK_AND_UNSUPPRESS_TRACING(t); + return oldfp->fd; +} + +static int +lfd_clr_enumerator(PLHashEntry *he, int i, void *arg) +{ + lfdset_entry *le = (lfdset_entry*) he; + logfile *fp = (logfile*) arg; + + LFD_CLR(fp->lfd, &le->lfdset); + return HT_ENUMERATE_NEXT; +} + +static void +lfd_clr_walk(callsite *site, logfile *fp) +{ + callsite *kid; + + LFD_CLR(fp->lfd, &site->lfdset); + for (kid = site->kids; kid; kid = kid->siblings) + lfd_clr_walk(kid, fp); +} + +PR_IMPLEMENT(void) +NS_TraceMallocCloseLogFD(int fd) +{ + logfile *fp; + tm_thread *t = tm_get_thread(); + + TM_SUPPRESS_TRACING_AND_ENTER_LOCK(t); + + fp = get_logfile(fd); + if (fp) { + flush_logfile(fp); + if (fp == &default_logfile) { + /* Leave default_logfile in logfile_list with an fd of -1. */ + fp->fd = -1; + + /* NB: we can never free lfd 0, it belongs to default_logfile. */ + PR_ASSERT(fp->lfd == 0); + } else { + /* Clear fp->lfd in all possible lfdsets. */ + PL_HashTableEnumerateEntries(libraries, lfd_clr_enumerator, fp); + PL_HashTableEnumerateEntries(methods, lfd_clr_enumerator, fp); + lfd_clr_walk(&calltree_root, fp); + + /* Unlink fp from logfile_list, freeing lfd for reallocation. */ + *fp->prevp = fp->next; + if (!fp->next) { + PR_ASSERT(logfile_tail == &fp->next); + logfile_tail = fp->prevp; + } + + /* Reset logfp if we must, then free fp. */ + if (fp == logfp) + logfp = &default_logfile; + free((void*) fp); + } + } + + TM_EXIT_LOCK_AND_UNSUPPRESS_TRACING(t); + MozillaUnRegisterDebugFD(fd); + close(fd); +} + +PR_IMPLEMENT(void) +NS_TraceMallocLogTimestamp(const char *caption) +{ + logfile *fp; +#ifdef XP_UNIX + struct timeval tv; +#endif +#ifdef XP_WIN32 + struct _timeb tb; +#endif + tm_thread *t = tm_get_thread(); + + TM_SUPPRESS_TRACING_AND_ENTER_LOCK(t); + + fp = logfp; + log_byte(fp, TM_EVENT_TIMESTAMP); + +#ifdef XP_UNIX + gettimeofday(&tv, NULL); + log_uint32(fp, (uint32_t) tv.tv_sec); + log_uint32(fp, (uint32_t) tv.tv_usec); +#endif +#ifdef XP_WIN32 + _ftime(&tb); + log_uint32(fp, (uint32_t) tb.time); + log_uint32(fp, (uint32_t) tb.millitm); +#endif + log_string(fp, caption); + + TM_EXIT_LOCK_AND_UNSUPPRESS_TRACING(t); +} + +static void +print_stack(FILE *ofp, callsite *site) +{ + while (site) { + if (site->name || site->parent) { + fprintf(ofp, "%s[%s +0x%X]\n", + site->name, site->library, site->offset); + } + site = site->parent; + } +} + +static const char *allocation_format = + (sizeof(void*) == 4) ? "\t0x%08zX\n" : + (sizeof(void*) == 8) ? "\t0x%016zX\n" : + "UNEXPECTED sizeof(void*)"; + +static int +allocation_enumerator(PLHashEntry *he, int i, void *arg) +{ + allocation *alloc = (allocation*) he; + FILE *ofp = (FILE*) arg; + callsite *site = (callsite*) he->value; + + size_t *p, *end; + + fprintf(ofp, "%p <%s> (%lu)\n", + he->key, + nsGetTypeName(he->key), + (unsigned long) alloc->size); + + for (p = (size_t*) he->key, + end = (size_t*) ((char*)he->key + alloc->size); + p < end; ++p) { + fprintf(ofp, allocation_format, *p); + } + + print_stack(ofp, site); + fputc('\n', ofp); + return HT_ENUMERATE_NEXT; +} + +PR_IMPLEMENT(void) +NS_TraceStack(int skip, FILE *ofp) +{ + callsite *site; + tm_thread *t = tm_get_thread(); + int immediate_abort; + + site = backtrace(t, skip + 1, &immediate_abort); + while (site) { + if (site->name || site->parent) { + fprintf(ofp, "%s[%s +0x%X]\n", + site->name, site->library, site->offset); + } + site = site->parent; + } +} + +PR_IMPLEMENT(int) +NS_TraceMallocDumpAllocations(const char *pathname) +{ + FILE *ofp; + int rv; + int fd; + + tm_thread *t = tm_get_thread(); + + TM_SUPPRESS_TRACING_AND_ENTER_LOCK(t); + + ofp = fopen(pathname, WRITE_FLAGS); + if (ofp) { + MozillaRegisterDebugFD(fileno(ofp)); + if (allocations) { + PL_HashTableEnumerateEntries(allocations, allocation_enumerator, + ofp); + } + rv = ferror(ofp) ? -1 : 0; + MozillaUnRegisterDebugFILE(ofp); + fclose(ofp); + } else { + rv = -1; + } + + TM_EXIT_LOCK_AND_UNSUPPRESS_TRACING(t); + + return rv; +} + +PR_IMPLEMENT(void) +NS_TraceMallocFlushLogfiles(void) +{ + logfile *fp; + tm_thread *t = tm_get_thread(); + + TM_SUPPRESS_TRACING_AND_ENTER_LOCK(t); + + for (fp = logfile_list; fp; fp = fp->next) + flush_logfile(fp); + + TM_EXIT_LOCK_AND_UNSUPPRESS_TRACING(t); +} + +PR_IMPLEMENT(void) +NS_TrackAllocation(void* ptr, FILE *ofp) +{ + allocation *alloc; + tm_thread *t = tm_get_thread(); + + fprintf(ofp, "Trying to track %p\n", (void*) ptr); + setlinebuf(ofp); + + TM_SUPPRESS_TRACING_AND_ENTER_LOCK(t); + if (get_allocations()) { + alloc = (allocation*) + *PL_HashTableRawLookup(allocations, hash_pointer(ptr), ptr); + if (alloc) { + fprintf(ofp, "Tracking %p\n", (void*) ptr); + alloc->trackfp = ofp; + } else { + fprintf(ofp, "Not tracking %p\n", (void*) ptr); + } + } + TM_EXIT_LOCK_AND_UNSUPPRESS_TRACING(t); +} + +PR_IMPLEMENT(void) +MallocCallback(void *ptr, size_t size, uint32_t start, uint32_t end, tm_thread *t) +{ + callsite *site; + PLHashEntry *he; + allocation *alloc; + int immediate_abort; + + if (!tracing_enabled || t->suppress_tracing != 0) + return; + + site = backtrace(t, 2, &immediate_abort); + if (immediate_abort) + return; + + TM_SUPPRESS_TRACING_AND_ENTER_LOCK(t); + tmstats.malloc_calls++; + if (!ptr) { + tmstats.malloc_failures++; + } else { + if (site) { + log_event5(logfp, TM_EVENT_MALLOC, + site->serial, start, end - start, + (uint32_t)NS_PTR_TO_INT32(ptr), size); + } + if (get_allocations()) { + he = PL_HashTableAdd(allocations, ptr, site); + if (he) { + alloc = (allocation*) he; + alloc->size = size; + alloc->trackfp = NULL; + } + } + } + TM_EXIT_LOCK_AND_UNSUPPRESS_TRACING(t); +} + +PR_IMPLEMENT(void) +CallocCallback(void *ptr, size_t count, size_t size, uint32_t start, uint32_t end, tm_thread *t) +{ + callsite *site; + PLHashEntry *he; + allocation *alloc; + int immediate_abort; + + if (!tracing_enabled || t->suppress_tracing != 0) + return; + + site = backtrace(t, 2, &immediate_abort); + if (immediate_abort) + return; + + TM_SUPPRESS_TRACING_AND_ENTER_LOCK(t); + tmstats.calloc_calls++; + if (!ptr) { + tmstats.calloc_failures++; + } else { + size *= count; + if (site) { + log_event5(logfp, TM_EVENT_CALLOC, + site->serial, start, end - start, + (uint32_t)NS_PTR_TO_INT32(ptr), size); + } + if (get_allocations()) { + he = PL_HashTableAdd(allocations, ptr, site); + if (he) { + alloc = (allocation*) he; + alloc->size = size; + alloc->trackfp = NULL; + } + } + } + TM_EXIT_LOCK_AND_UNSUPPRESS_TRACING(t); +} + +PR_IMPLEMENT(void) +ReallocCallback(void * oldptr, void *ptr, size_t size, + uint32_t start, uint32_t end, tm_thread *t) +{ + callsite *oldsite, *site; + size_t oldsize; + PLHashNumber hash; + PLHashEntry **hep, *he; + allocation *alloc; + FILE *trackfp = NULL; + int immediate_abort; + + if (!tracing_enabled || t->suppress_tracing != 0) + return; + + site = backtrace(t, 2, &immediate_abort); + if (immediate_abort) + return; + + TM_SUPPRESS_TRACING_AND_ENTER_LOCK(t); + tmstats.realloc_calls++; + oldsite = NULL; + oldsize = 0; + hep = NULL; + he = NULL; + if (oldptr && get_allocations()) { + hash = hash_pointer(oldptr); + hep = PL_HashTableRawLookup(allocations, hash, oldptr); + he = *hep; + if (he) { + oldsite = (callsite*) he->value; + alloc = (allocation*) he; + oldsize = alloc->size; + trackfp = alloc->trackfp; + if (trackfp) { + fprintf(alloc->trackfp, + "\nrealloc(%p, %lu), oldsize %lu, alloc site %p\n", + (void*) ptr, (unsigned long) size, + (unsigned long) oldsize, (void*) oldsite); + NS_TraceStack(1, trackfp); + } + } + } + if (!ptr && size) { + /* + * When realloc() fails, the original block is not freed or moved, so + * we'll leave the allocation entry untouched. + */ + tmstats.realloc_failures++; + } else { + if (site) { + log_event8(logfp, TM_EVENT_REALLOC, + site->serial, start, end - start, + (uint32_t)NS_PTR_TO_INT32(ptr), size, + oldsite ? oldsite->serial : 0, + (uint32_t)NS_PTR_TO_INT32(oldptr), oldsize); + } + if (ptr && allocations) { + if (ptr != oldptr) { + /* + * If we're reallocating (not allocating new space by passing + * null to realloc) and realloc moved the block, free oldptr. + */ + if (he) + PL_HashTableRawRemove(allocations, hep, he); + + /* Record the new allocation now, setting he. */ + he = PL_HashTableAdd(allocations, ptr, site); + } else { + /* + * If we haven't yet recorded an allocation (possibly due to a + * temporary memory shortage), do it now. + */ + if (!he) + he = PL_HashTableAdd(allocations, ptr, site); + } + if (he) { + alloc = (allocation*) he; + alloc->size = size; + alloc->trackfp = trackfp; + } + } + } + TM_EXIT_LOCK_AND_UNSUPPRESS_TRACING(t); +} + +PR_IMPLEMENT(void) +FreeCallback(void * ptr, uint32_t start, uint32_t end, tm_thread *t) +{ + PLHashEntry **hep, *he; + callsite *site; + allocation *alloc; + + if (!tracing_enabled || t->suppress_tracing != 0) + return; + + /* + * FIXME: Perhaps we should call backtrace() so we can check for + * immediate_abort. However, the only current contexts where + * immediate_abort will be true do not call free(), so for now, + * let's avoid the cost of backtrace(). See bug 478195. + */ + TM_SUPPRESS_TRACING_AND_ENTER_LOCK(t); + tmstats.free_calls++; + if (!ptr) { + tmstats.null_free_calls++; + } else { + if (get_allocations()) { + hep = PL_HashTableRawLookup(allocations, hash_pointer(ptr), ptr); + he = *hep; + if (he) { + site = (callsite*) he->value; + if (site) { + alloc = (allocation*) he; + if (alloc->trackfp) { + fprintf(alloc->trackfp, "\nfree(%p), alloc site %p\n", + (void*) ptr, (void*) site); + NS_TraceStack(1, alloc->trackfp); + } + log_event5(logfp, TM_EVENT_FREE, + site->serial, start, end - start, + (uint32_t)NS_PTR_TO_INT32(ptr), alloc->size); + } + PL_HashTableRawRemove(allocations, hep, he); + } + } + } + TM_EXIT_LOCK_AND_UNSUPPRESS_TRACING(t); +} + +PR_IMPLEMENT(nsTMStackTraceID) +NS_TraceMallocGetStackTrace(void) +{ + callsite *site; + int dummy; + tm_thread *t = tm_get_thread(); + + PR_ASSERT(t->suppress_tracing == 0); + + site = backtrace(t, 2, &dummy); + return (nsTMStackTraceID) site; +} + +PR_IMPLEMENT(void) +NS_TraceMallocPrintStackTrace(FILE *ofp, nsTMStackTraceID id) +{ + print_stack(ofp, (callsite *)id); +} + +#endif /* NS_TRACE_MALLOC */