diff --git a/tools/trace-malloc/lib/nsTraceMalloc.c b/tools/trace-malloc/lib/nsTraceMalloc.c index e3999410082..6ba44e473c2 100644 --- a/tools/trace-malloc/lib/nsTraceMalloc.c +++ b/tools/trace-malloc/lib/nsTraceMalloc.c @@ -375,6 +375,7 @@ struct callsite { uint32 serial; lfd_set lfdset; char *name; + const char *library; int offset; callsite *parent; callsite *siblings; @@ -389,7 +390,7 @@ static uint32 callsite_serial_generator = 0; static uint32 tmstats_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, 0, NULL, NULL, NULL}; +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; @@ -695,7 +696,8 @@ static callsite *calltree(uint32 *bp) site->serial = ++callsite_serial_generator; LFD_ZERO(&site->lfdset); site->name = method; - site->offset = offset; + site->library = info.dli_fname; + site->offset = (char*)pc - (char*)info.dli_fbase; site->parent = parent; site->siblings = parent->kids; parent->kids = site; @@ -1273,13 +1275,19 @@ allocation_enumerator(PLHashEntry *he, PRIntn i, void *arg) FILE *ofp = (FILE*) arg; callsite *site = (callsite*) he->value; - fprintf(ofp, "%8p %9lu ", he->key, (unsigned long) alloc->size); + extern const char* nsGetTypeName(const void* ptr); + unsigned *p, *end; + + fprintf(ofp, "0x%08X <%s> (%lu)\n", (unsigned) he->key, nsGetTypeName(he->key), (unsigned long) alloc->size); + + end = (unsigned*)(((char*) he->key) + alloc->size); + for (p = (unsigned*)he->key; p < end; ++p) + fprintf(ofp, "\t0x%08X\n", *p); + while (site) { if (site->name || site->parent) - fprintf(ofp, " %s+%d", site->name, site->offset); + fprintf(ofp, "%s[%s +0x%X]\n", site->name, site->library, site->offset); site = site->parent; - if (site) - fputc(';', ofp); } fputc('\n', ofp); return HT_ENUMERATE_NEXT; @@ -1294,7 +1302,6 @@ NS_TraceMallocDumpAllocations(const char *pathname) ofp = fopen(pathname, "w"); if (!ofp) return -1; - fprintf(ofp, "Address size stack\n"); if (allocations) PL_HashTableEnumerateEntries(allocations, allocation_enumerator, ofp); rv = ferror(ofp) ? -1 : 0; diff --git a/tools/trace-malloc/lib/nsTypeInfo.cpp b/tools/trace-malloc/lib/nsTypeInfo.cpp new file mode 100644 index 00000000000..22b72cab43b --- /dev/null +++ b/tools/trace-malloc/lib/nsTypeInfo.cpp @@ -0,0 +1,230 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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 oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is nsTypeInfo.cpp code, released + * November 27, 2000. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Patrick C. Beard + * Chris Waterson + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + */ + +/* + typeinfo.cpp + + Speculatively use RTTI on a random object. If it contains a pointer at offset 0 + that is in the current process' address space, and that so on, then attempt to + use C++ RTTI's typeid operation to obtain the name of the type. + + by Patrick C. Beard. + */ + +#include +#include + +extern "C" const char* nsGetTypeName(void* ptr); + +class IUnknown { +public: + virtual long QueryInterface() = 0; + virtual long AddRef() = 0; + virtual long Release() = 0; +}; + +#if defined(MACOS) + +#include + +class AddressSpace { +public: + AddressSpace(); + Boolean contains(void* ptr); +private: + ProcessInfoRec mInfo; +}; + +AddressSpace::AddressSpace() +{ + ProcessSerialNumber psn = { 0, kCurrentProcess }; + mInfo.processInfoLength = sizeof(mInfo); + ::GetProcessInformation(&psn, &mInfo); +} + +Boolean AddressSpace::contains(void* ptr) +{ + UInt32 start = UInt32(mInfo.processLocation); + return (UInt32(ptr) >= start && UInt32(ptr) < (start + mInfo.processSize)); +} + +const char* nsGetTypeName(void* ptr) +{ + // construct only one of these per process. + static AddressSpace space; + + // sanity check the vtable pointer, before trying to use RTTI on the object. + void** vt = *(void***)ptr; + if (vt && !(unsigned(vt) & 0x3) && space.contains(vt) && space.contains(*vt)) { + IUnknown* u = static_cast(ptr); + const char* type = typeid(*u).name(); + // make sure it looks like a C++ identifier. + if (type && (isalnum(type[0]) || type[0] == '_')) + return type; + } + return "void*"; +} + +#endif + +#if defined(linux) + +#include +#include + +static jmp_buf context; + +static void handler(int signum) +{ + longjmp(context, signum); +} + +#define attempt() setjmp(context) + +class Signaller { +public: + Signaller(int signum); + ~Signaller(); + +private: + typedef void (*handler_t) (int signum); + int mSignal; + handler_t mOldHandler; +}; + +Signaller::Signaller(int signum) + : mSignal(signum), mOldHandler(signal(signum, &handler)) +{ +} + +Signaller::~Signaller() +{ + signal(mSignal, mOldHandler); +} + +// The following are pointers that bamboozle are otherwise feeble +// attempts to "safely" collect type names. +// +// XXX this kind of sucks because it means that anyone trying to use +// this without NSPR will get unresolved symbols when this library +// loads. It's also not very extensable. Oh well: FIX ME! +extern "C" { + // from nsprpub/pr/src/io/priometh.c (libnspr4.so) + extern void* _pr_faulty_methods; + + // from nsprpub/pr/src/io/prlayer.c (libnspr4.so) + extern void* pl_methods; +}; + +static int +is_bamboozler(void* vt) +{ + static void* bamboolzers[] = { + _pr_faulty_methods, + pl_methods, + 0 + }; + + for (void** fn = bamboolzers; *fn; ++fn) { + if (*fn == vt) + return 1; + } + + return 0; +} + + + +static inline int +sanity_check_vtable_i386(void** vt) +{ + // Now that we're "safe" inside the signal handler, we can + // start poking around. If we're really an object with + // RTTI, then the second entry in the vtable should point + // to a function. + // + // Let's see if the second entry: + // + // 1) looks like a 4-byte aligned pointer + // + // 2) points to something that looks like the following + // i386 instructions: + // + // 55 push %ebp + // 89e5 mov %esp,%ebp + // + // (which is the standard function prologue generated + // by egcs). + unsigned** i = reinterpret_cast(vt) + 1; + return !(unsigned(*i) & 3) && ((**i & 0xffffff) == 0xe58955); +} + +static inline int +sanity_check_vtable_ppc(void** vt) +{ + // XXX write me! + return 1; +} + +#if defined(__i386) +# define SANITY_CHECK_VTABLE(vt) (sanity_check_vtable_i386(vt)) +#elif defined(PPC) +# define SANITY_CHECK_VTABLE(vt) (sanity_check_vtable_ppc(vt)) +#else +# define SANITY_CHECK_VTABLE(vt) (1) +#endif + +const char* nsGetTypeName(void* ptr) +{ + // sanity check the vtable pointer, before trying to use RTTI on the object. + void** vt = *(void***)ptr; + if (vt && !(unsigned(vt) & 3) && !is_bamboozler(vt)) { + Signaller s1(SIGSEGV); + if (attempt() == 0) { + if (SANITY_CHECK_VTABLE(vt)) { + // Looks like a function: what the hell, let's call it. + IUnknown* u = static_cast(ptr); + const char* type = typeid(*u).name(); + // EGCS seems to prefix a length string. + while (isdigit(*type)) ++type; + return type; + } + } + } + return "void*"; +} + +#endif diff --git a/xpcom/base/Makefile.in b/xpcom/base/Makefile.in index 9d2b346f2c0..00a1ecd553b 100644 --- a/xpcom/base/Makefile.in +++ b/xpcom/base/Makefile.in @@ -73,6 +73,7 @@ EXPORTS = \ ifdef NS_TRACE_MALLOC CSRCS += nsTraceMalloc.c +CPPSRCS += nsTypeInfo.cpp EXPORTS += nsTraceMalloc.h DEFINES += -DNS_TRACE_MALLOC SIMPLE_PROGRAMS = bloatblame diff --git a/xpcom/base/nsTraceMalloc.c b/xpcom/base/nsTraceMalloc.c index e3999410082..6ba44e473c2 100644 --- a/xpcom/base/nsTraceMalloc.c +++ b/xpcom/base/nsTraceMalloc.c @@ -375,6 +375,7 @@ struct callsite { uint32 serial; lfd_set lfdset; char *name; + const char *library; int offset; callsite *parent; callsite *siblings; @@ -389,7 +390,7 @@ static uint32 callsite_serial_generator = 0; static uint32 tmstats_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, 0, NULL, NULL, NULL}; +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; @@ -695,7 +696,8 @@ static callsite *calltree(uint32 *bp) site->serial = ++callsite_serial_generator; LFD_ZERO(&site->lfdset); site->name = method; - site->offset = offset; + site->library = info.dli_fname; + site->offset = (char*)pc - (char*)info.dli_fbase; site->parent = parent; site->siblings = parent->kids; parent->kids = site; @@ -1273,13 +1275,19 @@ allocation_enumerator(PLHashEntry *he, PRIntn i, void *arg) FILE *ofp = (FILE*) arg; callsite *site = (callsite*) he->value; - fprintf(ofp, "%8p %9lu ", he->key, (unsigned long) alloc->size); + extern const char* nsGetTypeName(const void* ptr); + unsigned *p, *end; + + fprintf(ofp, "0x%08X <%s> (%lu)\n", (unsigned) he->key, nsGetTypeName(he->key), (unsigned long) alloc->size); + + end = (unsigned*)(((char*) he->key) + alloc->size); + for (p = (unsigned*)he->key; p < end; ++p) + fprintf(ofp, "\t0x%08X\n", *p); + while (site) { if (site->name || site->parent) - fprintf(ofp, " %s+%d", site->name, site->offset); + fprintf(ofp, "%s[%s +0x%X]\n", site->name, site->library, site->offset); site = site->parent; - if (site) - fputc(';', ofp); } fputc('\n', ofp); return HT_ENUMERATE_NEXT; @@ -1294,7 +1302,6 @@ NS_TraceMallocDumpAllocations(const char *pathname) ofp = fopen(pathname, "w"); if (!ofp) return -1; - fprintf(ofp, "Address size stack\n"); if (allocations) PL_HashTableEnumerateEntries(allocations, allocation_enumerator, ofp); rv = ferror(ofp) ? -1 : 0; diff --git a/xpcom/base/nsTypeInfo.cpp b/xpcom/base/nsTypeInfo.cpp new file mode 100644 index 00000000000..22b72cab43b --- /dev/null +++ b/xpcom/base/nsTypeInfo.cpp @@ -0,0 +1,230 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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 oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is nsTypeInfo.cpp code, released + * November 27, 2000. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Patrick C. Beard + * Chris Waterson + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + */ + +/* + typeinfo.cpp + + Speculatively use RTTI on a random object. If it contains a pointer at offset 0 + that is in the current process' address space, and that so on, then attempt to + use C++ RTTI's typeid operation to obtain the name of the type. + + by Patrick C. Beard. + */ + +#include +#include + +extern "C" const char* nsGetTypeName(void* ptr); + +class IUnknown { +public: + virtual long QueryInterface() = 0; + virtual long AddRef() = 0; + virtual long Release() = 0; +}; + +#if defined(MACOS) + +#include + +class AddressSpace { +public: + AddressSpace(); + Boolean contains(void* ptr); +private: + ProcessInfoRec mInfo; +}; + +AddressSpace::AddressSpace() +{ + ProcessSerialNumber psn = { 0, kCurrentProcess }; + mInfo.processInfoLength = sizeof(mInfo); + ::GetProcessInformation(&psn, &mInfo); +} + +Boolean AddressSpace::contains(void* ptr) +{ + UInt32 start = UInt32(mInfo.processLocation); + return (UInt32(ptr) >= start && UInt32(ptr) < (start + mInfo.processSize)); +} + +const char* nsGetTypeName(void* ptr) +{ + // construct only one of these per process. + static AddressSpace space; + + // sanity check the vtable pointer, before trying to use RTTI on the object. + void** vt = *(void***)ptr; + if (vt && !(unsigned(vt) & 0x3) && space.contains(vt) && space.contains(*vt)) { + IUnknown* u = static_cast(ptr); + const char* type = typeid(*u).name(); + // make sure it looks like a C++ identifier. + if (type && (isalnum(type[0]) || type[0] == '_')) + return type; + } + return "void*"; +} + +#endif + +#if defined(linux) + +#include +#include + +static jmp_buf context; + +static void handler(int signum) +{ + longjmp(context, signum); +} + +#define attempt() setjmp(context) + +class Signaller { +public: + Signaller(int signum); + ~Signaller(); + +private: + typedef void (*handler_t) (int signum); + int mSignal; + handler_t mOldHandler; +}; + +Signaller::Signaller(int signum) + : mSignal(signum), mOldHandler(signal(signum, &handler)) +{ +} + +Signaller::~Signaller() +{ + signal(mSignal, mOldHandler); +} + +// The following are pointers that bamboozle are otherwise feeble +// attempts to "safely" collect type names. +// +// XXX this kind of sucks because it means that anyone trying to use +// this without NSPR will get unresolved symbols when this library +// loads. It's also not very extensable. Oh well: FIX ME! +extern "C" { + // from nsprpub/pr/src/io/priometh.c (libnspr4.so) + extern void* _pr_faulty_methods; + + // from nsprpub/pr/src/io/prlayer.c (libnspr4.so) + extern void* pl_methods; +}; + +static int +is_bamboozler(void* vt) +{ + static void* bamboolzers[] = { + _pr_faulty_methods, + pl_methods, + 0 + }; + + for (void** fn = bamboolzers; *fn; ++fn) { + if (*fn == vt) + return 1; + } + + return 0; +} + + + +static inline int +sanity_check_vtable_i386(void** vt) +{ + // Now that we're "safe" inside the signal handler, we can + // start poking around. If we're really an object with + // RTTI, then the second entry in the vtable should point + // to a function. + // + // Let's see if the second entry: + // + // 1) looks like a 4-byte aligned pointer + // + // 2) points to something that looks like the following + // i386 instructions: + // + // 55 push %ebp + // 89e5 mov %esp,%ebp + // + // (which is the standard function prologue generated + // by egcs). + unsigned** i = reinterpret_cast(vt) + 1; + return !(unsigned(*i) & 3) && ((**i & 0xffffff) == 0xe58955); +} + +static inline int +sanity_check_vtable_ppc(void** vt) +{ + // XXX write me! + return 1; +} + +#if defined(__i386) +# define SANITY_CHECK_VTABLE(vt) (sanity_check_vtable_i386(vt)) +#elif defined(PPC) +# define SANITY_CHECK_VTABLE(vt) (sanity_check_vtable_ppc(vt)) +#else +# define SANITY_CHECK_VTABLE(vt) (1) +#endif + +const char* nsGetTypeName(void* ptr) +{ + // sanity check the vtable pointer, before trying to use RTTI on the object. + void** vt = *(void***)ptr; + if (vt && !(unsigned(vt) & 3) && !is_bamboozler(vt)) { + Signaller s1(SIGSEGV); + if (attempt() == 0) { + if (SANITY_CHECK_VTABLE(vt)) { + // Looks like a function: what the hell, let's call it. + IUnknown* u = static_cast(ptr); + const char* type = typeid(*u).name(); + // EGCS seems to prefix a length string. + while (isdigit(*type)) ++type; + return type; + } + } + } + return "void*"; +} + +#endif