gecko/tools/trace-malloc/lib/nsTraceMalloc.c

2449 lines
73 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim:cindent:ts=8:et:sw=4:
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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/MPL/
*
* 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 nsTraceMalloc.c/bloatblame.c code, released
* April 19, 2000.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 2000
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Brendan Eich, 14-April-2000
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifdef NS_TRACE_MALLOC
/*
* TODO:
* - fix my_dladdr so it builds its own symbol tables from bfd
* - extend logfile so 'F' record tells free stack
* - diagnose rusty's SMP realloc oldsize corruption bug
* - #ifdef __linux__/x86 and port to other platforms
* - unify calltree with gc/boehm somehow (common utility lib?)
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef XP_UNIX
#include <unistd.h>
#include <sys/stat.h>
#include <sys/time.h>
#endif
#include "plhash.h"
#include "pratom.h"
#include "prlog.h"
#include "prmon.h"
#include "prprf.h"
#include "prenv.h"
#include "prnetdb.h"
#include "nsTraceMalloc.h"
#include "nscore.h"
#include "prinit.h"
#ifdef XP_WIN32
#include "nsStackFrameWin.h"
#include <sys/timeb.h>/*for timeb*/
#include <sys/stat.h>/*for fstat*/
#include <io.h> /*for write*/
#include "nsTraceMallocCallbacks.h"
#define WRITE_FLAGS "w"
#endif /* WIN32 */
#ifdef XP_UNIX
#define WRITE_FLAGS "w"
#ifdef MOZ_DEMANGLE_SYMBOLS
char *nsDemangle(const char *);
#endif
#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
/* XXX I wish dladdr could find local text symbols (static functions). */
#define __USE_GNU 1
#include <dlfcn.h>
#if 1
#define my_dladdr dladdr
#else
/* XXX this version, which uses libbfd, runs mozilla clean out of memory! */
#include <bfd.h>
#include <elf.h> /* damn dladdr ignores local symbols! */
#include <link.h>
extern struct link_map *_dl_loaded;
static int my_dladdr(const void *address, Dl_info *info)
{
const ElfW(Addr) addr = (ElfW(Addr)) address;
struct link_map *lib, *matchlib;
unsigned int n, size;
bfd *abfd;
PTR minisyms;
long nsyms;
bfd_byte *mini, *endmini;
asymbol *sym, *storage;
bfd_vma target, symaddr;
static const char *sname;
/* Find the highest-addressed object not greater than address. */
matchlib = NULL;
for (lib = _dl_loaded; lib; lib = lib->l_next) {
if (lib->l_addr != 0 && /* 0 means map not set up yet? */
lib->l_addr <= addr &&
(!matchlib || matchlib->l_addr < lib->l_addr)) {
matchlib = lib;
}
}
if (!matchlib)
return 0;
/*
* We know the address lies within matchlib, if it's in any shared object.
* Make sure it isn't past the end of matchlib's segments.
*/
n = (size_t) matchlib->l_phnum;
if (n > 0) {
do {
--n;
} while (matchlib->l_phdr[n].p_type != PT_LOAD);
if (addr >= (matchlib->l_addr +
matchlib->l_phdr[n].p_vaddr +
matchlib->l_phdr[n].p_memsz)) {
/* Off the end of the highest-addressed shared object. */
return 0;
}
}
/*
* Now we know what object the address lies in. Set up info for a file
* match, then find the greatest info->dli_saddr <= addr.
*/
info->dli_fname = matchlib->l_name;
info->dli_fbase = (void*) matchlib->l_addr;
info->dli_sname = NULL;
info->dli_saddr = NULL;
/* Ah, the joys of libbfd.... */
const char target[] =
#if defined(__i386)
"elf32-i386"
#elif defined(__x86_64__)
"elf64-x86-64"
#else
#error Unknown architecture
#endif
;
abfd = bfd_openr(matchlib->l_name, target);
if (!abfd)
return 0;
if (!bfd_check_format(abfd, bfd_object)) {
printf("%s is not an object file, according to libbfd.\n",
matchlib->l_name);
return 0;
}
nsyms = bfd_read_minisymbols(abfd, 0, &minisyms, &size);
if (nsyms < 0) {
bfd_close(abfd);
return 0;
}
if (nsyms > 0) {
storage = bfd_make_empty_symbol(abfd);
if (!storage) {
bfd_close(abfd);
return 0;
}
target = (bfd_vma) addr - (bfd_vma) matchlib->l_addr;
endmini = (bfd_byte*) minisyms + nsyms * size;
for (mini = (bfd_byte*) minisyms; mini < endmini; mini += size) {
sym = bfd_minisymbol_to_symbol(abfd, 0, (const PTR)mini, storage);
if (!sym) {
bfd_close(abfd);
return 0;
}
if (sym->flags & (BSF_GLOBAL | BSF_LOCAL | BSF_WEAK)) {
symaddr = sym->value + sym->section->vma;
if (symaddr == 0 || symaddr > target)
continue;
if (!info->dli_sname || info->dli_saddr < (void*) symaddr) {
info->dli_sname = sym->name;
info->dli_saddr = (void*) symaddr;
}
}
}
/* Emulate dladdr by allocating and owning info->dli_sname's storage. */
if (info->dli_sname) {
if (sname)
__libc_free((void*) sname);
sname = strdup(info->dli_sname);
if (!sname)
return 0;
info->dli_sname = sname;
}
}
bfd_close(abfd);
return 1;
}
#endif /* 0 */
#else /* !XP_UNIX */
#define __libc_malloc(x) malloc(x)
#define __libc_calloc(x, y) calloc(x,y)
#define __libc_realloc(x, y) realloc(x,y)
#define __libc_free(x) free(x)
/*
* Ok we need to load malloc, free, realloc, and calloc from the dll and store
* the function pointers somewhere. All other dlls that link with this library
* should have their malloc overridden to call this one.
*/
typedef void * (__stdcall *MALLOCPROC)(size_t);
typedef void * (__stdcall *REALLOCPROC)(void *, size_t);
typedef void * (__stdcall *CALLOCPROC)(size_t,size_t);
typedef void (__stdcall *FREEPROC)(void *);
/*debug types*/
#ifdef _DEBUG
typedef void * (__stdcall *MALLOCDEBUGPROC) ( size_t, int, const char *, int);
typedef void * (__stdcall *CALLOCDEBUGPROC) ( size_t, size_t, int, const char *, int);
typedef void * (__stdcall *REALLOCDEBUGPROC) ( void *, size_t, int, const char *, int);
typedef void (__stdcall *FREEDEBUGPROC) ( void *, int);
#endif
struct AllocationFuncs
{
MALLOCPROC malloc_proc;
CALLOCPROC calloc_proc;
REALLOCPROC realloc_proc;
FREEPROC free_proc;
#ifdef _DEBUG
MALLOCDEBUGPROC malloc_debug_proc;
CALLOCDEBUGPROC calloc_debug_proc;
REALLOCDEBUGPROC realloc_debug_proc;
FREEDEBUGPROC free_debug_proc;
#endif
int prevent_reentry;
}gAllocFuncs;
#endif /* !XP_UNIX */
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 size;
uint32 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 PRMonitor *tmmon = NULL;
static char *sdlogname = NULL; /* filename for shutdown leak log */
/*
* This counter suppresses tracing, in case any tracing code needs to malloc,
* and it must be tested and manipulated only within tmmon.
*/
static uint32 suppress_tracing = 0;
#define TM_ENTER_MONITOR() \
PR_BEGIN_MACRO \
if (tmmon) \
PR_EnterMonitor(tmmon); \
PR_END_MACRO
#define TM_EXIT_MONITOR() \
PR_BEGIN_MACRO \
if (tmmon) \
PR_ExitMonitor(tmmon); \
PR_END_MACRO
/* We don't want more than 32 logfiles open at once, ok? */
typedef uint32 lfd_set;
#define LFD_SET_STATIC_INITIALIZER 0
#define LFD_SET_SIZE 32
#define LFD_ZERO(s) (*(s) = 0)
#define LFD_BIT(i) ((uint32)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);
while ((rem = fp->pos + len - fp->bufsize) > 0) {
cnt = len - rem;
strncpy(&fp->buf[fp->pos], str, cnt);
str += cnt;
fp->pos += cnt;
flush_logfile(fp);
len = rem;
}
strncpy(&fp->buf[fp->pos], str, len);
fp->pos += len;
/* Terminate the string. */
log_byte(fp, '\0');
}
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 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 serial)
{
log_byte(fp, event);
log_uint32(fp, (uint32) serial);
}
static void log_event2(logfile *fp, char event, uint32 serial, size_t size)
{
log_event1(fp, event, serial);
log_uint32(fp, (uint32) size);
}
static void log_event3(logfile *fp, char event, uint32 serial, size_t oldsize,
size_t size)
{
log_event2(fp, event, serial, oldsize);
log_uint32(fp, (uint32) size);
}
static void log_event4(logfile *fp, char event, uint32 serial, uint32 ui2,
uint32 ui3, uint32 ui4)
{
log_event3(fp, event, serial, ui2, ui3);
log_uint32(fp, ui4);
}
static void log_event5(logfile *fp, char event, uint32 serial, uint32 ui2,
uint32 ui3, uint32 ui4, uint32 ui5)
{
log_event4(fp, event, serial, ui2, ui3, ui4);
log_uint32(fp, ui5);
}
static void log_event6(logfile *fp, char event, uint32 serial, uint32 ui2,
uint32 ui3, uint32 ui4, uint32 ui5, uint32 ui6)
{
log_event5(fp, event, serial, ui2, ui3, ui4, ui5);
log_uint32(fp, ui6);
}
static void log_event7(logfile *fp, char event, uint32 serial, uint32 ui2,
uint32 ui3, uint32 ui4, uint32 ui5, uint32 ui6,
uint32 ui7)
{
log_event6(fp, event, serial, ui2, ui3, ui4, ui5, ui6);
log_uint32(fp, ui7);
}
static void log_event8(logfile *fp, char event, uint32 serial, uint32 ui2,
uint32 ui3, uint32 ui4, uint32 ui5, uint32 ui6,
uint32 ui7, uint32 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 serial;
lfd_set lfdset;
char *name;
const char *library;
int offset;
callsite *parent;
callsite *siblings;
callsite *kids;
};
/* NB: these counters are incremented and decremented only within tmmon. */
static uint32 library_serial_generator = 0;
static uint32 method_serial_generator = 0;
static uint32 callsite_serial_generator = 0;
static uint32 tmstats_serial_generator = 0;
static uint32 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};
/* 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, PRSize 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, PRUintn 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;
#ifdef XP_WIN32
/*
* Realease builds seem to take more stackframes.
*/
#define MAX_STACKFRAMES 512
#define MAX_UNMANGLED_NAME_LEN 256
static callsite *calltree(int skip)
{
logfile *fp = logfp;
HANDLE myProcess;
HANDLE myThread;
CONTEXT context;
int ok, maxstack, offset;
int getSymRes = 0;
STACKFRAME frame[MAX_STACKFRAMES];
uint32 library_serial, method_serial;
int framenum;
void *pc;
uint32 depth, nkids;
callsite *parent, **csp, *tmp;
callsite *site = NULL;
char *demangledname;
const char *library;
IMAGEHLP_MODULE imagehelp;
char buf[sizeof(IMAGEHLP_SYMBOL) + 512];
PIMAGEHLP_SYMBOL symbol;
char *method, *slash;
PLHashNumber hash;
PLHashEntry **hep, *he;
lfdset_entry *le;
char* noname = "noname";
IMAGEHLP_LINE imagehelpLine;
const char* filename = NULL;
uint32 linenumber = 0;
uint32 filename_serial = 0;
imagehelp.SizeOfStruct = sizeof(imagehelp);
framenum = 0;
myProcess = GetCurrentProcess();
myThread = GetCurrentThread();
ok = EnsureSymInitialized();
if (! ok)
return 0;
/*
* Get the context information for this thread. That way we will know
* where our sp, fp, pc, etc. are, and we can fill in the STACKFRAME with
* the initial values.
*/
context.ContextFlags = CONTEXT_FULL;
ok = GetThreadContext(myThread, &context);
if (! ok)
return 0;
/* Setup initial stack frame from which to walk. */
memset(&(frame[0]), 0, sizeof(frame[0]));
frame[0].AddrPC.Offset = context.Eip;
frame[0].AddrPC.Mode = AddrModeFlat;
frame[0].AddrStack.Offset = context.Esp;
frame[0].AddrStack.Mode = AddrModeFlat;
frame[0].AddrFrame.Offset = context.Ebp;
frame[0].AddrFrame.Mode = AddrModeFlat;
for (;framenum < MAX_STACKFRAMES;) {
PIMAGEHLP_SYMBOL symbol = (PIMAGEHLP_SYMBOL) buf;
if (framenum)
memcpy(&(frame[framenum]),&(frame[framenum-1]),sizeof(STACKFRAME));
ok = _StackWalk(IMAGE_FILE_MACHINE_I386,
myProcess,
myThread,
&(frame[framenum]),
&context,
0, /* read process memory hook */
_SymFunctionTableAccess,/* function table access hook */
_SymGetModuleBase, /* module base hook */
0); /* translate address hook */
if (!ok)
break;
if (skip) {
/* skip tells us to skip the first skip amount of stackframes */
skip--;
continue;
}
if (frame[framenum].AddrPC.Offset == 0)
break;
framenum++;
/*
* Time to increase the number of stack frames?
*/
if (framenum >= MAX_STACKFRAMES)
break;
}
depth = framenum;
maxstack = (depth > tmstats.calltree_maxstack);
if (maxstack)
tmstats.calltree_maxstack = depth;
/* Reverse the stack again, finding and building a path in the tree. */
parent = &calltree_root;
while (0 < framenum) {
DWORD displacement;/*used for getsymfromaddr*/
pc = frame[--framenum].AddrPC.Offset;
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. XXX static syms are masked by nearest lower global
* Load up the info for the dll.
*/
memset(&imagehelpLine, 0, sizeof(imagehelpLine));
if (!SymGetModuleInfoEspecial(myProcess,
frame[framenum].AddrPC.Offset,
&imagehelp, &imagehelpLine)) {
library = noname;
filename = noname;
linenumber = 0;
} else {
library = imagehelp.ModuleName;
filename = imagehelpLine.FileName;
linenumber = imagehelpLine.LineNumber;
if ('\0' == filename) {
filename = noname;
linenumber = 0;
}
}
/* Check whether we need to emit a library trace record. */
library_serial = 0;
if (library) {
if (!libraries) {
libraries = PL_NewHashTable(100, PL_HashString,
PL_CompareStrings, PL_CompareValues,
&lfdset_hashallocops, NULL);
if (!libraries) {
tmstats.btmalloc_failures++;
return NULL;
}
}
hash = PL_HashString(library);
hep = PL_HashTableRawLookup(libraries, hash, library);
he = *hep;
library = strdup(library); /* strdup it always? */
if (he) {
library_serial = (uint32) 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(library); */
if (library) {
library_serial = ++library_serial_generator;
he = PL_HashTableRawAdd(libraries, hep, hash, library,
(void*) library_serial);
}
if (!he) {
tmstats.btmalloc_failures++;
return NULL;
}
le = (lfdset_entry *) he;
}
if (le) {
/* Need to log an event to fp for this lib. */
slash = strrchr(library, '/');
if (slash)
library = slash + 1;
log_event1(fp, TM_EVENT_LIBRARY, library_serial);
log_string(fp, library);
LFD_SET(fp->lfd, &le->lfdset);
}
}
/* Check whether we need to emit a filename trace record. */
filename_serial = 0;
if (filename) {
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_serial = (uint32) 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,
(void*) 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);
}
}
symbol = (PIMAGEHLP_SYMBOL) buf;
symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL);
symbol->MaxNameLength = sizeof(buf) - sizeof(IMAGEHLP_SYMBOL);
symbol->Name[symbol->MaxNameLength] = '\0';
getSymRes = _SymGetSymFromAddr(myProcess,
frame[framenum].AddrPC.Offset,
&displacement,
symbol);
/* Now find the demangled method name and pc offset in it. */
if (0 != getSymRes) {
demangledname = (char *)malloc(MAX_UNMANGLED_NAME_LEN);
if (!_SymUnDName(symbol,demangledname,MAX_UNMANGLED_NAME_LEN)) {
free(demangledname);
return 0;
}
method = demangledname;
offset = (char*)pc - (char*)(symbol->Address);
}
else {
method = noname;
offset = pc;
}
/* 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++;
if (method != noname) {
free((void*) method);
}
return NULL;
}
}
hash = PL_HashString(method);
hep = PL_HashTableRawLookup(methods, hash, method);
he = *hep;
if (he) {
method_serial = (uint32) NS_PTR_TO_INT32(he->value);
if (method != noname) {
free((void*) method);
}
method = (char *) he->key;
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_serial = ++method_serial_generator;
he = PL_HashTableRawAdd(methods, hep, hash, method,
(void*) method_serial);
if (!he) {
tmstats.btmalloc_failures++;
if (method != noname) {
free((void*) method);
}
return NULL;
}
le = (lfdset_entry *) he;
}
if (le) {
log_event4(fp, TM_EVENT_METHOD, method_serial, library_serial,
filename_serial, linenumber);
log_string(fp, method);
LFD_SET(fp->lfd, &le->lfdset);
}
/* Create a new callsite record. */
if (!site) {
site = malloc(sizeof(callsite));
if (!site) {
tmstats.btmalloc_failures++;
return NULL;
}
/* 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->offset = offset;
site->parent = parent;
site->siblings = parent->kids;
site->library = library;
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, offset);
LFD_SET(fp->lfd, &site->lfdset);
upward:
parent = site;
}
if (maxstack)
calltree_maxstack_top = site;
depth = 0;
for (tmp = site; tmp; tmp = tmp->parent)
depth++;
if (depth > tmstats.calltree_maxdepth)
tmstats.calltree_maxdepth = depth;
return site;
}
#else /*XP_UNIX*/
static callsite *calltree(void **bp)
{
logfile *fp = logfp;
void **bpup, **bpdown, *pc;
uint32 depth, nkids;
callsite *parent, *site, **csp, *tmp;
Dl_info info;
int ok, len, maxstack, offset;
uint32 library_serial, method_serial;
const char *library, *symbol;
char *method, *slash;
PLHashNumber hash;
PLHashEntry **hep, *he;
lfdset_entry *le;
uint32 filename_serial;
uint32 linenumber;
const char* filename;
/* Reverse the stack frame list to avoid recursion. */
bpup = NULL;
for (depth = 0; ; depth++) {
bpdown = (void**) bp[0];
bp[0] = (void*) bpup;
if ((void**) bpdown[0] < bpdown)
break;
bpup = bp;
bp = bpdown;
}
maxstack = (depth > tmstats.calltree_maxstack);
if (maxstack)
tmstats.calltree_maxstack = depth;
/* Reverse the stack again, finding and building a path in the tree. */
parent = &calltree_root;
do {
bpup = (void**) bp[0];
bp[0] = (void*) bpdown;
pc = bp[1];
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. XXX static syms are masked by nearest lower global
*/
info.dli_fname = info.dli_sname = NULL;
/*
* dladdr can 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 tmmon
* 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_MONITOR();
ok = my_dladdr((void*) pc, &info);
TM_ENTER_MONITOR();
if (ok < 0) {
tmstats.dladdr_failures++;
return NULL;
}
/*
* One day, if someone figures out how to get filename and line
* number info, this is the place to fill it all in.
*/
filename = "noname";
linenumber = 0;
/* Check whether we need to emit a library trace record. */
library_serial = 0;
library = info.dli_fname;
if (library) {
if (!libraries) {
libraries = PL_NewHashTable(100, PL_HashString,
PL_CompareStrings, PL_CompareValues,
&lfdset_hashallocops, NULL);
if (!libraries) {
tmstats.btmalloc_failures++;
return NULL;
}
}
hash = PL_HashString(library);
hep = PL_HashTableRawLookup(libraries, hash, library);
he = *hep;
if (he) {
library_serial = (uint32) 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(library);
if (library) {
library_serial = ++library_serial_generator;
he = PL_HashTableRawAdd(libraries, hep, hash, library,
(void*) library_serial);
}
if (!he) {
tmstats.btmalloc_failures++;
return NULL;
}
le = (lfdset_entry *) he;
}
if (le) {
/* Need to log an event to fp for this lib. */
slash = strrchr(library, '/');
if (slash)
library = slash + 1;
log_event1(fp, TM_EVENT_LIBRARY, library_serial);
log_string(fp, library);
LFD_SET(fp->lfd, &le->lfdset);
}
}
/* Check whether we need to emit a filename trace record. */
filename_serial = 0;
if (filename) {
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_serial = (uint32) 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 {
if (filename) {
filename_serial = ++filename_serial_generator;
he = PL_HashTableRawAdd(filenames, hep, hash, filename,
(void*) 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);
}
}
/* Now find the demangled method name and pc offset in it. */
symbol = info.dli_sname;
offset = (char*)pc - (char*)info.dli_saddr;
method = NULL;
#ifdef MOZ_DEMANGLE_SYMBOLS
if (symbol && (len = strlen(symbol)) != 0) {
method = nsDemangle(symbol);
}
#endif
if (!method) {
method = symbol
? strdup(symbol)
: PR_smprintf("%s+%X",
info.dli_fname ? info.dli_fname : "main",
(char*)pc - (char*)info.dli_fbase);
}
if (!method) {
tmstats.btmalloc_failures++;
return NULL;
}
/* 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++;
free((void*) method);
return NULL;
}
}
hash = PL_HashString(method);
hep = PL_HashTableRawLookup(methods, hash, method);
he = *hep;
if (he) {
method_serial = (uint32) NS_PTR_TO_INT32(he->value);
free((void*) method);
method = (char *) he->key;
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_serial = ++method_serial_generator;
he = PL_HashTableRawAdd(methods, hep, hash, method,
(void*) method_serial);
if (!he) {
tmstats.btmalloc_failures++;
free((void*) method);
return NULL;
}
le = (lfdset_entry *) he;
}
if (le) {
log_event4(fp, TM_EVENT_METHOD, method_serial, library_serial,
filename_serial, linenumber);
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++;
return NULL;
}
/* 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 = info.dli_fname;
site->offset = (char*)pc - (char*)info.dli_fbase;
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, offset);
LFD_SET(fp->lfd, &site->lfdset);
upward:
parent = site;
bpdown = bp;
bp = bpup;
} while (bp);
if (maxstack)
calltree_maxstack_top = site;
depth = 0;
for (tmp = site; tmp; tmp = tmp->parent)
depth++;
if (depth > tmstats.calltree_maxdepth)
tmstats.calltree_maxdepth = depth;
return site;
}
#endif
#ifdef XP_WIN32
callsite *
backtrace(int skip)
{
callsite *site;
tmstats.backtrace_calls++;
suppress_tracing++;
site = calltree(skip);
if (!site) {
tmstats.backtrace_failures++;
/* PR_ASSERT(tmstats.backtrace_failures < 100); */
}
suppress_tracing--;
return site;
}
#else /*XP_UNIX*/
callsite *
backtrace(int skip)
{
void **bp, **bpdown;
callsite *site, **key;
PLHashNumber hash;
PLHashEntry **hep, *he;
int i, n;
tmstats.backtrace_calls++;
suppress_tracing++;
/* Stack walking code adapted from Kipp's "leaky". */
#if defined(__i386)
__asm__( "movl %%ebp, %0" : "=g"(bp));
#elif defined(__x86_64__)
__asm__( "movq %%rbp, %0" : "=g"(bp));
#else
/*
* It would be nice if this worked uniformly, but at least on i386 and
* x86_64, it stopped working with gcc 4.1, because it points to the
* end of the saved registers instead of the start.
*/
bp = (void**) __builtin_frame_address(0);
#endif
while (--skip >= 0) {
bpdown = (void**) bp[0];
if (bpdown < bp)
break;
bp = bpdown;
}
site = calltree(bp);
if (!site) {
tmstats.backtrace_failures++;
PR_ASSERT(tmstats.backtrace_failures < 100);
}
suppress_tracing--;
return site;
}
#endif /* XP_UNIX */
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, PRUintn flag)
{
allocation *alloc;
if (flag != HT_FREE_ENTRY)
return;
alloc = (allocation*) he;
if ((PRUptrdiff)(alloc - alloc_heap) < (PRUptrdiff)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())
#ifdef XP_UNIX
NS_EXTERNAL_VIS_(__ptr_t)
malloc(size_t size)
{
PRUint32 start, end;
__ptr_t ptr;
callsite *site;
PLHashEntry *he;
allocation *alloc;
if (!PR_Initialized()) {
return __libc_malloc(size);
}
start = PR_IntervalNow();
ptr = __libc_malloc(size);
end = PR_IntervalNow();
TM_ENTER_MONITOR();
tmstats.malloc_calls++;
if (!ptr) {
tmstats.malloc_failures++;
} else if (suppress_tracing == 0) {
site = backtrace(1);
if (site)
log_event5(logfp, TM_EVENT_MALLOC,
site->serial, start, end - start,
(uint32)NS_PTR_TO_INT32(ptr), size);
if (get_allocations()) {
suppress_tracing++;
he = PL_HashTableAdd(allocations, ptr, site);
suppress_tracing--;
if (he) {
alloc = (allocation*) he;
alloc->size = size;
alloc->trackfp = NULL;
}
}
}
TM_EXIT_MONITOR();
return ptr;
}
NS_EXTERNAL_VIS_(__ptr_t)
calloc(size_t count, size_t size)
{
PRUint32 start, end;
__ptr_t ptr;
callsite *site;
PLHashEntry *he;
allocation *alloc;
/**
* During the initialization of the glibc/libpthread, and
* before main() is running, ld-linux.so.2 tries to allocate memory
* using calloc (call from _dl_tls_setup).
*
* Thus, our calloc replacement is invoked too early, tries to
* initialize NSPR, which calls dlopen, which calls into the dl
* -> crash.
*
* Delaying NSPR calls until NSPR is initialized helps.
*/
if (!PR_Initialized()) {
return __libc_calloc(count, size);
}
start = PR_IntervalNow();
ptr = __libc_calloc(count, size);
end = PR_IntervalNow();
TM_ENTER_MONITOR();
tmstats.calloc_calls++;
if (!ptr) {
tmstats.calloc_failures++;
} else if (suppress_tracing == 0) {
site = backtrace(1);
size *= count;
if (site) {
log_event5(logfp, TM_EVENT_CALLOC,
site->serial, start, end - start,
(uint32)NS_PTR_TO_INT32(ptr), size);
}
if (get_allocations()) {
suppress_tracing++;
he = PL_HashTableAdd(allocations, ptr, site);
suppress_tracing--;
if (he) {
alloc = (allocation*) he;
alloc->size = size;
alloc->trackfp = NULL;
}
}
}
TM_EXIT_MONITOR();
return ptr;
}
NS_EXTERNAL_VIS_(__ptr_t)
realloc(__ptr_t ptr, size_t size)
{
PRUint32 start, end;
__ptr_t oldptr;
callsite *oldsite, *site;
size_t oldsize;
PLHashNumber hash;
PLHashEntry **hep, *he;
allocation *alloc;
FILE *trackfp = NULL;
if (!PR_Initialized()) {
return __libc_realloc(ptr, size);
}
TM_ENTER_MONITOR();
tmstats.realloc_calls++;
if (suppress_tracing == 0) {
oldptr = ptr;
oldsite = NULL;
oldsize = 0;
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);
}
}
}
}
TM_EXIT_MONITOR();
start = PR_IntervalNow();
ptr = __libc_realloc(ptr, size);
end = PR_IntervalNow();
TM_ENTER_MONITOR();
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 (suppress_tracing == 0) {
site = backtrace(1);
if (site) {
log_event8(logfp, TM_EVENT_REALLOC,
site->serial, start, end - start,
(uint32)NS_PTR_TO_INT32(ptr), size,
oldsite ? oldsite->serial : 0,
(uint32)NS_PTR_TO_INT32(oldptr), oldsize);
}
if (ptr && allocations) {
suppress_tracing++;
if (ptr != oldptr) {
/*
* If we're reallocating (not merely allocating new space by
* passing null to realloc) and realloc has moved the block,
* free oldptr.
*/
if (he)
PL_HashTableRemove(allocations, oldptr);
/* 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);
}
suppress_tracing--;
if (he) {
alloc = (allocation*) he;
alloc->size = size;
alloc->trackfp = trackfp;
}
}
}
TM_EXIT_MONITOR();
return ptr;
}
NS_EXTERNAL_VIS_(void*)
valloc(size_t size)
{
PRUint32 start, end;
__ptr_t ptr;
callsite *site;
PLHashEntry *he;
allocation *alloc;
if (!PR_Initialized()) {
return __libc_valloc(size);
}
start = PR_IntervalNow();
ptr = __libc_valloc(size);
end = PR_IntervalNow();
TM_ENTER_MONITOR();
tmstats.malloc_calls++; /* XXX valloc_calls ? */
if (!ptr) {
tmstats.malloc_failures++; /* XXX valloc_failures ? */
} else if (suppress_tracing == 0) {
site = backtrace(1);
if (site)
log_event5(logfp, TM_EVENT_MALLOC, /* XXX TM_EVENT_VALLOC? */
site->serial, start, end - start,
(uint32)NS_PTR_TO_INT32(ptr), size);
if (get_allocations()) {
suppress_tracing++;
he = PL_HashTableAdd(allocations, ptr, site);
suppress_tracing--;
if (he) {
alloc = (allocation*) he;
alloc->size = size;
alloc->trackfp = NULL;
}
}
}
TM_EXIT_MONITOR();
return ptr;
}
NS_EXTERNAL_VIS_(void*)
memalign(size_t boundary, size_t size)
{
PRUint32 start, end;
__ptr_t ptr;
callsite *site;
PLHashEntry *he;
allocation *alloc;
if (!PR_Initialized()) {
return __libc_memalign(boundary, size);
}
start = PR_IntervalNow();
ptr = __libc_memalign(boundary, size);
end = PR_IntervalNow();
TM_ENTER_MONITOR();
tmstats.malloc_calls++; /* XXX memalign_calls ? */
if (!ptr) {
tmstats.malloc_failures++; /* XXX memalign_failures ? */
} else if (suppress_tracing == 0) {
site = backtrace(1);
if (site) {
log_event5(logfp, TM_EVENT_MALLOC, /* XXX TM_EVENT_MEMALIGN? */
site->serial, start, end - start,
(uint32)NS_PTR_TO_INT32(ptr), size);
}
if (get_allocations()) {
suppress_tracing++;
he = PL_HashTableAdd(allocations, ptr, site);
suppress_tracing--;
if (he) {
alloc = (allocation*) he;
alloc->size = size;
alloc->trackfp = NULL;
}
}
}
TM_EXIT_MONITOR();
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)
{
PLHashEntry **hep, *he;
callsite *site;
allocation *alloc;
uint32 serial = 0, size = 0;
PRUint32 start, end;
if (!PR_Initialized()) {
__libc_free(ptr);
return;
}
TM_ENTER_MONITOR();
tmstats.free_calls++;
if (!ptr) {
tmstats.null_free_calls++;
} else if (suppress_tracing == 0) {
if (get_allocations()) {
hep = PL_HashTableRawLookup(allocations, hash_pointer(ptr), ptr);
he = *hep;
if (he) {
site = (callsite*) he->value;
if (site) {
alloc = (allocation*) he;
serial = site->serial;
size = alloc->size;
if (alloc->trackfp) {
fprintf(alloc->trackfp, "\nfree(%p), alloc site %p\n",
(void*) ptr, (void*) site);
NS_TraceStack(1, alloc->trackfp);
}
}
PL_HashTableRawRemove(allocations, hep, he);
}
}
}
TM_EXIT_MONITOR();
start = PR_IntervalNow();
__libc_free(ptr);
end = PR_IntervalNow();
if (size != 0) {
TM_ENTER_MONITOR();
log_event5(logfp, TM_EVENT_FREE,
serial, start, end - start,
(uint32)NS_PTR_TO_INT32(ptr), size);
TM_EXIT_MONITOR();
}
}
NS_EXTERNAL_VIS_(void)
cfree(void *ptr)
{
free(ptr);
}
#endif /* XP_UNIX */
static const char magic[] = NS_TRACE_MALLOC_MAGIC;
static void
log_header(int logfd)
{
uint32 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)
{
/* We must be running on the primordial thread. */
PR_ASSERT(suppress_tracing == 0);
PR_ASSERT(logfp == &default_logfile);
suppress_tracing = (logfd < 0);
if (suppress_tracing == 0) {
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);
}
atexit(NS_TraceMallocShutdown);
tmmon = PR_NewMonitor();
#ifdef XP_WIN32
/* Register listeners for win32. */
if (suppress_tracing == 0) {
StartupHooker();
}
#endif
}
/*
* 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; /* note global |sdlogname| */
/*
* Look for the --trace-malloc <logfile> 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, argv[i]))
PARSE_ARG(SDLOG_OPTION, sdlogname, 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;
}
}
NS_TraceMallocStartup(logfd);
return argc;
}
PR_IMPLEMENT(void) NS_TraceMallocShutdown()
{
logfile *fp;
if (sdlogname)
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) {
close(fp->fd);
fp->fd = -1;
}
if (fp != &default_logfile) {
if (fp == logfp)
logfp = &default_logfile;
free((void*) fp);
}
}
if (tmmon) {
PRMonitor *mon = tmmon;
tmmon = NULL;
PR_DestroyMonitor(mon);
}
#ifdef XP_WIN32
if (suppress_tracing == 0) {
ShutdownHooker();
}
#endif
}
PR_IMPLEMENT(void) NS_TraceMallocDisable()
{
logfile *fp;
TM_ENTER_MONITOR();
for (fp = logfile_list; fp; fp = fp->next)
flush_logfile(fp);
suppress_tracing++;
TM_EXIT_MONITOR();
}
PR_IMPLEMENT(void) NS_TraceMallocEnable()
{
TM_ENTER_MONITOR();
suppress_tracing--;
TM_EXIT_MONITOR();
}
PR_IMPLEMENT(int) NS_TraceMallocChangeLogFD(int fd)
{
logfile *oldfp, *fp;
struct stat sb;
TM_ENTER_MONITOR();
oldfp = logfp;
if (oldfp->fd != fd) {
flush_logfile(oldfp);
fp = get_logfile(fd);
if (!fp)
return -2;
if (fd >= 0 && fstat(fd, &sb) == 0 && sb.st_size == 0)
log_header(fd);
logfp = fp;
}
TM_EXIT_MONITOR();
return oldfp->fd;
}
static PRIntn
lfd_clr_enumerator(PLHashEntry *he, PRIntn 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_ENTER_MONITOR();
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_MONITOR();
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_ENTER_MONITOR();
fp = logfp;
log_byte(fp, TM_EVENT_TIMESTAMP);
#ifdef XP_UNIX
gettimeofday(&tv, NULL);
log_uint32(fp, (uint32) tv.tv_sec);
log_uint32(fp, (uint32) tv.tv_usec);
#endif
#ifdef XP_WIN32
_ftime(&tb);
log_uint32(fp, (uint32) tb.time);
log_uint32(fp, (uint32) tb.millitm);
#endif
log_string(fp, caption);
TM_EXIT_MONITOR();
}
static PRIntn
allocation_enumerator(PLHashEntry *he, PRIntn i, void *arg)
{
allocation *alloc = (allocation*) he;
FILE *ofp = (FILE*) arg;
callsite *site = (callsite*) he->value;
extern const char* nsGetTypeName(const void* ptr);
unsigned long *p, *end;
fprintf(ofp, "%p <%s> (%lu)\n",
he->key,
nsGetTypeName(he->key),
(unsigned long) alloc->size);
for (p = (unsigned long*) he->key,
end = (unsigned long*) ((char*)he->key + alloc->size);
p < end; ++p)
fprintf(ofp, "\t0x%08lX\n", *p);
while (site) {
if (site->name || site->parent) {
fprintf(ofp, "%s[%s +0x%X]\n",
site->name, site->library, site->offset);
}
site = site->parent;
}
fputc('\n', ofp);
return HT_ENUMERATE_NEXT;
}
PR_IMPLEMENT(void)
NS_TraceStack(int skip, FILE *ofp)
{
callsite *site;
site = backtrace(skip + 1);
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;
ofp = fopen(pathname, WRITE_FLAGS);
if (!ofp)
return -1;
if (allocations)
PL_HashTableEnumerateEntries(allocations, allocation_enumerator, ofp);
rv = ferror(ofp) ? -1 : 0;
fclose(ofp);
return rv;
}
PR_IMPLEMENT(void)
NS_TraceMallocFlushLogfiles()
{
logfile *fp;
TM_ENTER_MONITOR();
for (fp = logfile_list; fp; fp = fp->next)
flush_logfile(fp);
TM_EXIT_MONITOR();
}
PR_IMPLEMENT(void)
NS_TrackAllocation(void* ptr, FILE *ofp)
{
PLHashEntry **hep;
allocation *alloc;
fprintf(ofp, "Trying to track %p\n", (void*) ptr);
setlinebuf(ofp);
TM_ENTER_MONITOR();
if (get_allocations()) {
hep = PL_HashTableRawLookup(allocations, hash_pointer(ptr), ptr);
alloc = (allocation*) *hep;
if (alloc) {
fprintf(ofp, "Tracking %p\n", (void*) ptr);
alloc->trackfp = ofp;
} else {
fprintf(ofp, "Not tracking %p\n", (void*) ptr);
}
}
TM_EXIT_MONITOR();
}
#ifdef XP_WIN32
PR_IMPLEMENT(void)
MallocCallback(void *ptr, size_t size, PRUint32 start, PRUint32 end)
{
callsite *site;
PLHashEntry *he;
allocation *alloc;
TM_ENTER_MONITOR();
tmstats.malloc_calls++;
if (!ptr) {
tmstats.malloc_failures++;
} else if (suppress_tracing == 0) {
site = backtrace(4);
if (site)
log_event5(logfp, TM_EVENT_MALLOC,
site->serial, start, end - start,
(uint32)NS_PTR_TO_INT32(ptr), size);
if (get_allocations()) {
suppress_tracing++;
he = PL_HashTableAdd(allocations, ptr, site);
suppress_tracing--;
if (he) {
alloc = (allocation*) he;
alloc->size = size;
}
}
}
TM_EXIT_MONITOR();
}
PR_IMPLEMENT(void)
CallocCallback(void *ptr, size_t count, size_t size, PRUint32 start, PRUint32 end)
{
callsite *site;
PLHashEntry *he;
allocation *alloc;
TM_ENTER_MONITOR();
tmstats.calloc_calls++;
if (!ptr) {
tmstats.calloc_failures++;
} else if (suppress_tracing == 0) {
site = backtrace(1);
size *= count;
if (site)
log_event5(logfp, TM_EVENT_CALLOC,
site->serial, start, end - start,
(uint32)NS_PTR_TO_INT32(ptr), size);
if (get_allocations()) {
suppress_tracing++;
he = PL_HashTableAdd(allocations, ptr, site);
suppress_tracing--;
if (he) {
alloc = (allocation*) he;
alloc->size = size;
}
}
}
TM_EXIT_MONITOR();
}
PR_IMPLEMENT(void)
ReallocCallback(void * oldptr, void *ptr, size_t size, PRUint32 start, PRUint32 end)
{
callsite *oldsite, *site;
size_t oldsize;
PLHashNumber hash;
PLHashEntry **hep, *he;
allocation *alloc;
TM_ENTER_MONITOR();
tmstats.realloc_calls++;
if (suppress_tracing == 0) {
oldsite = NULL;
oldsize = 0;
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;
}
}
}
if (!ptr && size) {
tmstats.realloc_failures++;
/*
* When realloc() fails, the original block is not freed or moved, so
* we'll leave the allocation entry untouched.
*/
} else if (suppress_tracing == 0) {
site = backtrace(1);
if (site) {
log_event8(logfp, TM_EVENT_REALLOC,
site->serial, start, end - start,
(uint32)NS_PTR_TO_INT32(ptr), size,
oldsite ? oldsite->serial : 0,
(uint32)NS_PTR_TO_INT32(oldptr), oldsize);
}
if (ptr && allocations) {
suppress_tracing++;
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);
}
suppress_tracing--;
if (he) {
alloc = (allocation*) he;
alloc->size = size;
}
}
}
TM_EXIT_MONITOR();
}
PR_IMPLEMENT(void)
FreeCallback(void * ptr, PRUint32 start, PRUint32 end)
{
PLHashEntry **hep, *he;
callsite *site;
allocation *alloc;
TM_ENTER_MONITOR();
tmstats.free_calls++;
if (!ptr) {
tmstats.null_free_calls++;
} else if (suppress_tracing == 0) {
if (get_allocations()) {
hep = PL_HashTableRawLookup(allocations, hash_pointer(ptr), ptr);
he = *hep;
if (he) {
site = (callsite*) he->value;
if (site) {
alloc = (allocation*) he;
log_event5(logfp, TM_EVENT_FREE,
site->serial, start, end - start,
(uint32)NS_PTR_TO_INT32(ptr), alloc->size);
}
PL_HashTableRawRemove(allocations, hep, he);
}
}
}
TM_EXIT_MONITOR();
}
#endif /*XP_WIN32*/
#endif /* NS_TRACE_MALLOC */