diff options
Diffstat (limited to 'kmtrace')
-rw-r--r-- | kmtrace/Makefile.am | 50 | ||||
-rw-r--r-- | kmtrace/README | 110 | ||||
-rw-r--r-- | kmtrace/configure.in.in | 18 | ||||
-rw-r--r-- | kmtrace/demangle.cpp | 60 | ||||
-rw-r--r-- | kmtrace/kde.excludes | 49 | ||||
-rwxr-xr-x | kmtrace/kminspector.in | 9 | ||||
-rw-r--r-- | kmtrace/kmtrace.cpp | 721 | ||||
-rw-r--r-- | kmtrace/ksotrace.cpp | 11 | ||||
-rw-r--r-- | kmtrace/ktrace.c | 840 | ||||
-rw-r--r-- | kmtrace/ktrace.h | 10 | ||||
-rw-r--r-- | kmtrace/match.cpp | 73 | ||||
-rw-r--r-- | kmtrace/mtrace.c | 383 |
12 files changed, 2334 insertions, 0 deletions
diff --git a/kmtrace/Makefile.am b/kmtrace/Makefile.am new file mode 100644 index 00000000..68fce88d --- /dev/null +++ b/kmtrace/Makefile.am @@ -0,0 +1,50 @@ +# This file is part of the KDE libraries +# Copyright (C) 1996-1997 Matthias Kalle Dalheimer (kalle@kde.org) +# (C) 1997-1998 Stephan Kulow (coolo@kde.org) + +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. + +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Library General Public License for more details. + +# You should have received a copy of the GNU Library General Public License +# along with this library; see the file COPYING.LIB. If not, write to +# the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +AM_CPPFLAGS = -DQT_NO_ASCII_CAST -UQT_NO_COMPAT -O3 +LDADD = $(LIB_KDECORE) -liberty +INCLUDES = $(all_includes) + +bin_PROGRAMS = kmtrace demangle kmmatch +kmtrace_SOURCES = kmtrace.cpp +kmtrace_LDFLAGS = $(all_libraries) + +demangle_SOURCES = demangle.cpp +demangle_LDFLAGS = $(all_libraries) + +kmmatch_SOURCES = match.cpp +kmmatch_LDFLAGS = $(all_libraries) + +bin_SCRIPTS = kminspector + +lib_LIBRARIES = libktrace_s.a +libktrace_s_a_SOURCES = ktrace_s.c + +ktrace_s.c: $(srcdir)/ktrace.c + $(LN_S) $(srcdir)/ktrace.c $@ + +lib_LTLIBRARIES = libktrace.la +libktrace_la_LDFLAGS = -avoid-version +libktrace_la_SOURCES = ksotrace.cpp ktrace.c +libktrace_la_LIBADD = $(LIBDL) + +exclude_DATA = kde.excludes +excludedir = $(kde_datadir)/kmtrace + +include_HEADERS = ktrace.h diff --git a/kmtrace/README b/kmtrace/README new file mode 100644 index 00000000..63e35228 --- /dev/null +++ b/kmtrace/README @@ -0,0 +1,110 @@ +This is a KDE tool to assist with malloc debugging using glibc's "mtrace" +functionality. Unfortunately the mtrace that is part of current (9/9/2000) +glibc versions only logs the return-address of the malloc/free call. +The file mtrace.c in this directory logs a complete backtrace upon malloc/ +free. + +THIS PROGRAM DEPENDS ON GLIBC! It does not pretend to be portable. + +Howto use: + +Install the libktrace.so shared library, the ktrace.h header file, the +and kde.excludes file and the kmtrace processing tool with: + + make install + +There are two ways to activate memory usage loggings by ktrace : + +1) The LD_PRELOAD way + +This way, you can debug any application without having to recompile it, +but you'll have to debug also the memory allocated by KApplication and +friends. + +You can activate malloc logging by starting yourApplication as: + + MALLOC_TRACE=/tmp/ktrace.out LD_PRELOAD=$KDEDIR/lib/libktrace.so yourApplication + +2) The manual way + +Take the KDE application that you want to investigate and add + + #include <ktrace.h> + +Add as first statement in main(): + + ktrace(); + +Add ktrace_s.a to the LDADD line in your Makefile.am like: + + kicker_LDADD = kicker.la /opt/kde/lib/libktrace_s.a + +Note that the static library is used. +You can now activate malloc logging by starting yourApplication as: + + MALLOC_TRACE=/tmp/ktrace.out yourApplication + +This will generate a huge log in /tmp/ktrace.out. + +You can process this log with: + + kmtrace /tmp/ktrace.out > ktrace.parsed + +By default the trace-output is stored in the current directory +as "ktrace.out". kmtrace also searches it there, so you don't need +to add any commandline options. + +TIPS +==== + +* If you can't be bothered with the stuff that KApplication allocates for you +you might want to put the ktrace() call after the KApplication constructor. +This will lead to a lot of warnings like: + + Freeing unalloacted memory: 0x08056108 + +Which are harmless, it just means that the memory was allocated before you +turned the tracing on. Note that you cannot use this if you're using +LD_PRELOAD to use ktrace. + +* To filter out harmless allocations out of the output file you can specify +with the --exclude option a file with symbols to exclude from output. If a +backtrace contains a symbol that starts with any of the symbols in this file, +this backtrace / leaked block is not shown in the output. + +In the file kde.exclude some example symbols are listed. Usage example: + + kmtrace /tmp/malloc.trace > /tmp/malloc.parsed + +* Be aware that the reported symbols may not be accurate under all +circumstances. E.g. consider the following backtrace: + + 0x405879c1 /lib/libdl.so.2(dlerror+0x1b1) + 0x405873b3 /lib/libdl.so.2(dlopen+0x33) + 0x4053c0b2 /ext/kde2.0/lib/libkdecore.so.3(QXmlSimpleReader::reportParseErro + 0x4053c74b /ext/kde2.0/lib/libkdecore.so.3(lt_dlexit+0x24b) + 0x4053c894 /ext/kde2.0/lib/libkdecore.so.3(lt_dlexit+0x394) + 0x4053dd49 /ext/kde2.0/lib/libkdecore.so.3(lt_dlopen+0x899) + +The QXmlSimpleReader is obviously wrong here. + +* You can use the --tree option to kmtrace to specify a file to print a tree +of the allocations. You can also use --treedepth and --treethreshold options +to hide subtrees that are deeper than the specified depth or allocated less +than the given memory amount. + +* The advantage of using libktrace_s.a (the static library) is that you can +put calls to ktrace() and kuntrace() around a block of code that +interests you. Only allocations and deallocations between the first call +to ktrace() and the first call to kuntrace() will be logged. + + +Have fun. + +Waldo Bastian +bastian@kde.org + +kmtrace.cpp by Waldo Bastian <bastian@kde.org> +ktrace.c by Waldo Bastian <bastian@kde.org> based on mtrace.c +mtrace.c by Mike Haertel <mike@ai.mit.edu> +mtrace.c patched by Andi Kleen <ak@suse.de> diff --git a/kmtrace/configure.in.in b/kmtrace/configure.in.in new file mode 100644 index 00000000..5df3711e --- /dev/null +++ b/kmtrace/configure.in.in @@ -0,0 +1,18 @@ +dnl AC_OUTPUT( kmtrace/kminspector ) + +case "$host" in + *-gnu) + saved_LIBS="$LIBS" + LIBS="$LIBS -Wl,-Bstatic -liberty -Wl,-Bdynamic" + AC_TRY_LINK([], [], [kde_compile_kmtrace=$GCC], [kde_compile_kmtrace=no]) + AC_SUBST(KMTRACE_LIBS, [$LIBS]) + LIBS="$saved_LIBS" + ;; + *) + kde_compile_kmtrace=no + ;; +esac + +if test ! "x$kde_compile_kmtrace" = "xyes"; then + DO_NOT_COMPILE="$DO_NOT_COMPILE kmtrace" +fi diff --git a/kmtrace/demangle.cpp b/kmtrace/demangle.cpp new file mode 100644 index 00000000..81bcff90 --- /dev/null +++ b/kmtrace/demangle.cpp @@ -0,0 +1,60 @@ +#include <qintdict.h> +#include <stdio.h> +#include <qstringlist.h> +#include <qstrlist.h> +#include <qtextstream.h> +#include <qsortedlist.h> +#include <qfile.h> +#include <qtl.h> +#include <qvaluelist.h> +#include <stdlib.h> +#include <ktempfile.h> +#include <kinstance.h> +#include <kstandarddirs.h> +#include <kcmdlineargs.h> + +extern "C" { +/* Options passed to cplus_demangle (in 2nd parameter). */ + +#define DMGL_NO_OPTS 0 /* For readability... */ +#define DMGL_PARAMS (1 << 0) /* Include function args */ +#define DMGL_ANSI (1 << 1) /* Include const, volatile, etc */ +#define DMGL_JAVA (1 << 2) /* Demangle as Java rather than C++. */ + +#define DMGL_AUTO (1 << 8) +#define DMGL_GNU (1 << 9) +#define DMGL_LUCID (1 << 10) +#define DMGL_ARM (1 << 11) +#define DMGL_HP (1 << 12) /* For the HP aCC compiler; same as ARM + except for template arguments, etc. */ +#define DMGL_EDG (1 << 13) +#define DMGL_GNU_V3 (1 << 14) +#define DMGL_GNAT (1 << 15) + + +extern char *cplus_demangle(const char *mangled, int options); +} + + +int main(int argc, char **argv) +{ + char buf[1024]; + + while(!feof(stdin)) + { + fgets(buf, 1024, stdin); + QCString line = buf; + line = line.stripWhiteSpace(); + char *res = cplus_demangle(line.data(), DMGL_PARAMS | DMGL_AUTO | DMGL_ANSI ); + if (res) + { + printf("%s\n", res); + free(res); + } + else + { + printf("%s\n", line.data()); + } + } +} + diff --git a/kmtrace/kde.excludes b/kmtrace/kde.excludes new file mode 100644 index 00000000..9e77eb5a --- /dev/null +++ b/kmtrace/kde.excludes @@ -0,0 +1,49 @@ +# We don't care about initialisation done by X11. +XLoadQueryFont +_XInitKeysymDB +IceRegisterForProtocolSetup +KDE_IceRegisterForProtocolSetup +IceOpenConnection +KDE_IceOpenConnection +KDE_IceProtocolSetup +#and we don't care about Xim memleaks +qt_init_internal +QApplication::create_xim +# workaround for Qt 2.2.0 bug +QFontDatabase::QFontDatabase +QFontDatabase::charSets + +#QRexExp does caching(?) +QRegExpEngine::QRegExpEngine +QRegExpEngine::parseExpression + +QStyleSheet::defaultSheet + +QTimer::singleShot +QTextCodec::codecForLocale +# Yeah, yeah we know that this metaobject stuff is never free'ed. +QMetaObject::new_meta +QObject::initMetaObject +QObject::staticMetaObject +QString::sprintf +QWidget::createTLExtra +# static KDE stuff +kdbgstream::flush +KCmdLineArgs::addCmdLineOptions +k_bindtextdomain +_nl_find_domain +KIconTheme::list +KIconTheme::current +KGlobalSettings::toolBarFont +KGlobalSettings::menuFont +KGlobalSettings::fixedFont +KGlobalSettings::generalFont +KGlobalSettings::toolBarFont +KImageIOFactory::self +objMap +# Some C functions that allocate data for initialisation +getpwuid +adjtime +getservbyname +# nothing created by static objects can be a memory leak +__static_initialization_and_destruction_0 diff --git a/kmtrace/kminspector.in b/kmtrace/kminspector.in new file mode 100755 index 00000000..572ed200 --- /dev/null +++ b/kmtrace/kminspector.in @@ -0,0 +1,9 @@ +#! /bin/sh + +export MALLOC_TREE=kminspector.tree +export MALLOC_THRESHOLD=2000 +export LD_PRELOAD=@kde_libraries@/libktrace.so + +$* + +cat kminspector.tree | less diff --git a/kmtrace/kmtrace.cpp b/kmtrace/kmtrace.cpp new file mode 100644 index 00000000..82055e43 --- /dev/null +++ b/kmtrace/kmtrace.cpp @@ -0,0 +1,721 @@ +#include <qintdict.h> +#include <stdio.h> +#include <qstringlist.h> +#include <qstrlist.h> +#include <qtextstream.h> +#include <qsortedlist.h> +#include <qfile.h> +#include <qtl.h> +#include <qvaluelist.h> +#include <stdlib.h> +#include <ktempfile.h> +#include <kinstance.h> +#include <kstandarddirs.h> +#include <kcmdlineargs.h> +#include <kprocess.h> + +extern "C" { +/* Options passed to cplus_demangle (in 2nd parameter). */ + +#define DMGL_NO_OPTS 0 /* For readability... */ +#define DMGL_PARAMS (1 << 0) /* Include function args */ +#define DMGL_ANSI (1 << 1) /* Include const, volatile, etc */ +#define DMGL_JAVA (1 << 2) /* Demangle as Java rather than C++. */ + +#define DMGL_AUTO (1 << 8) +#define DMGL_GNU (1 << 9) +#define DMGL_LUCID (1 << 10) +#define DMGL_ARM (1 << 11) +#define DMGL_HP (1 << 12) /* For the HP aCC compiler; same as ARM + except for template arguments, etc. */ +#define DMGL_EDG (1 << 13) +#define DMGL_GNU_V3 (1 << 14) +#define DMGL_GNAT (1 << 15) + + +extern char *cplus_demangle(const char *mangled, int options); +} + +struct Entry { + int base; + int size; + int signature; + int count; + int total_size; + int backtrace[1]; + + bool operator==(const Entry &e) { return total_size == e.total_size; } + bool operator<(const Entry &e) { return total_size > e.total_size; } +}; + +QIntDict<Entry> *entryDict = 0; +QIntDict<char> *symbolDict = 0; +QIntDict<char> *formatDict = 0; +QSortedList<Entry> *entryList = 0; +QStrList *excludes = 0; + +const char * const unknown = "<unknown>"; +const char * const excluded = "<excluded>"; +int allocCount = 0; +int leakedCount = 0; +int count = 0; +int maxCount; +int totalBytesAlloced = 0; +int totalBytesLeaked = 0; +int totalBytes = 0; +int maxBytes; + +int fromHex(const char *str); +void parseLine(const QCString &_line, char operation); +void dumpBlocks(); + +int fromHex(const char *str) +{ + if (*str == '[') str++; + str += 2; // SKip "0x" + return strtoll(str, NULL, 16); +} + +// [address0][address1] .... [address] + base size +void parseLine(const QCString &_line, char operation) +{ + char *line= (char *) _line.data(); + const char *cols[200]; + int i = 0; + cols[i++] = line; + while(*line) + { + if (*line == ' ') + { + *line = 0; + line++; + while (*line && (*line==' ')) line++; + if (*line) cols[i++] = line; + } + else line++; + } + int cols_count = i; + if (cols_count > 199) fprintf(stderr, "Error cols_count = %d\n", cols_count); + if (cols_count < 4) return; + switch (operation) + { + case '+': + { + Entry *entry = (Entry *) malloc((cols_count+3) *sizeof(int)); + entry->base = fromHex(cols[cols_count-2]); + entry->size = fromHex(cols[cols_count-1]); + int signature = 0; + for(int i = cols_count-3; i--;) + { + signature += (entry->backtrace[i-1] = fromHex(cols[i])); + } + entry->signature = (signature / 4)+cols_count; + entry->count = 1; + entry->total_size = entry->size; + entry->backtrace[cols_count-4] = 0; + totalBytesAlloced += entry->size; + totalBytes += entry->size; + count++; + if (totalBytes > maxBytes) + maxBytes = totalBytes; + if (count > maxCount) + maxCount = count; + if (entryDict->find(entry->base)) + fprintf(stderr, "\rAllocated twice: 0x%08x \n", entry->base); + entryDict->replace(entry->base, entry); + } break; + case '-': + { + int base = fromHex(cols[cols_count-1]); + Entry *entry = entryDict->take(base); + if (!entry) + { + if (base) + fprintf(stderr, "\rFreeing unalloacted memory: 0x%08x \n", base); + } + else + { + totalBytes -= entry->size; + count--; + free(entry); + } + } break; + default: + break; + } +} + +void sortBlocks() +{ + QIntDictIterator<Entry> it(*entryDict); + for(;it.current(); ++it) + { + Entry *entry = it.current(); + totalBytesLeaked += entry->total_size; + entryList->append(entry); + for(int i = 0; entry->backtrace[i]; i++) + { + if (!symbolDict->find(entry->backtrace[i])) + symbolDict->insert(entry->backtrace[i], unknown); + } + } + entryList->sort(); +} + +void collectDupes() +{ + QIntDict<Entry> dupeDict; + QIntDictIterator<Entry> it(*entryDict); + for(;it.current();) + { + Entry *entry = it.current(); + ++it; + Entry *entry2 = dupeDict.find(entry->signature); + if (entry2) + { + entry2->count++; + entry2->total_size += entry->size; + entryDict->remove(entry->base); + } + else + { + dupeDict.insert(entry->signature, entry); + } + } +} + +int lookupSymbols(FILE *stream) +{ + int i = 0; + int symbols = 0; + char line2[1024]; + while(!feof(stream)) + { + fgets(line2, 1023, stream); + if (line2[0] == '=' ) + { + if(strcmp(line2,"= End") == 0 ) + break; + } + else if (line2[0] == '#') + ; + else if (line2[0] == '@') + ; + else if (line2[0] == '[') + ; + else if (line2[0] == '-') + ; + else if (line2[0] == '<') + ; + else if (line2[0] == '>') + ; + else if (line2[0] == '+') + { + i++; + if (i & 1024) + { + fprintf(stderr, "\rLooking up symbols: %d found %d of %d symbols", i, symbols, symbolDict->count()); + } + } + else + { + char *addr = index(line2, '['); + if (addr) + { + long i_addr = fromHex(addr); + const char* str = symbolDict->find(i_addr); + if (str == unknown) + { + *addr = 0; + char* str; + if( rindex(line2, '/') != NULL ) + str = qstrdup(rindex(line2, '/')+1); + else + str = qstrdup(line2); + symbolDict->replace(i_addr, str); + symbols++; + } + } + } + } + fprintf(stderr, "\rLooking up symbols: %d found %d of %d symbols\n", i, symbols, symbolDict->count()); + return symbolDict->count()-symbols; +} + +void lookupUnknownSymbols(const char *appname) +{ + KTempFile inputFile; + KTempFile outputFile; + inputFile.setAutoDelete(true); + outputFile.setAutoDelete(true); + FILE *fInputFile = inputFile.fstream(); + QIntDict<char> oldDict = *symbolDict; + QIntDictIterator<char> it(oldDict); + for(;it.current(); ++it) + { + fprintf(fInputFile, "%08lx\n", it.currentKey()); + } + inputFile.close(); + QCString command; + command.sprintf("addr2line -e %s -f -C -s < %s > %s", appname, + QFile::encodeName(KProcess::quote(inputFile.name())).data(), + QFile::encodeName(KProcess::quote(outputFile.name())).data()); + system(command.data()); + fInputFile = fopen(QFile::encodeName(outputFile.name()), "r"); + if (!fInputFile) + { + fprintf(stderr, "Error opening temp file.\n"); + return; + } + QIntDictIterator<char> it2(oldDict); + char buffer1[1024]; + char buffer2[1024]; + for(;it2.current(); ++it2) + { + if (feof(fInputFile)) + { + fprintf(stderr, "Premature end of symbol output.\n"); + fclose(fInputFile); + return; + } + if (!fgets(buffer1, 1023, fInputFile)) continue; + if (!fgets(buffer2, 1023, fInputFile)) continue; + buffer1[strlen(buffer1)-1]=0; + buffer2[strlen(buffer2)-1]=0; + QCString symbol; + symbol.sprintf("%s(%s)", buffer2, buffer1); + if(*buffer1 != '?') + symbolDict->replace(it2.currentKey(),qstrdup(symbol.data())); + } + fclose(fInputFile); +} + +int match(const char *s1, const char *s2) +{ + register int result; + while(true) + { + result = *s1 - *s2; + if (result) + return result; + s1++; + s2++; + if (!*s2) return 0; + if (!*s1) return -1; + } + return 0; +} + +const char *lookupAddress(int addr) +{ + char *str = formatDict->find(addr); + if (str) return str; + QCString s = symbolDict->find(addr); + if (s.isEmpty()) + { +fprintf(stderr, "Error!\n"); + exit(1); + } + else + { + int start = s.find('('); + int end = s.findRev('+'); + if (end < 0) + end = s.findRev(')'); + if ((start > 0) && (end > start)) + { + QCString symbol = s.mid(start+1, end-start-1); + char *res = 0; + if (symbol.find(')') == -1) + res = cplus_demangle(symbol.data(), DMGL_PARAMS | DMGL_AUTO | DMGL_ANSI ); + + if (res) + { + symbol = res; + free(res); + } + res = (char *) symbol.data(); + for(const char *it = excludes->first();it;it = excludes->next()) + { + int i = match(res, it); + if (i == 0) + { + formatDict->insert(addr,excluded); + return excluded; + } + } + s.replace(start+1, end-start-1, symbol); + } + } + str = qstrdup(s.data()); + formatDict->insert(addr,str); + return str; +} + +void dumpBlocks() +{ + int filterBytes = 0; + int filterCount = 0; + for(Entry *entry = entryList->first();entry; entry = entryList->next()) + { + for(int i = 0; entry->backtrace[i]; i++) + { + const char *str = lookupAddress(entry->backtrace[i]); + if (str == excluded) + { + entry->total_size = 0; + continue; + } + } + if (!entry->total_size) continue; + filterBytes += entry->total_size; + filterCount++; + } + printf("Leaked memory after filtering: %d bytes in %d blocks.\n", filterBytes, filterCount); + for(Entry *entry = entryList->first();entry; entry = entryList->next()) + { + if (!entry->total_size) continue; + printf("[%d bytes in %d blocks, 1st. block is %d bytes at 0x%08x] ", entry->total_size, entry->count, entry->size, entry->base); + printf("\n"); + for(int i = 0; entry->backtrace[i]; i++) + { + const char *str = lookupAddress(entry->backtrace[i]); + printf(" 0x%08x %s\n", entry->backtrace[i], str); + } + } +} + +struct TreeEntry +{ + int address; // backtrace + int total_size; + int total_count; + typedef QValueList < TreeEntry > TreeList; + TreeList *subentries () const; + mutable TreeList *_subentries; + TreeEntry (int adr = 0, int size = 0, int count = 0, TreeList * sub = NULL ); + bool operator == (const TreeEntry &) const; + bool operator < (const TreeEntry &) const; +}; + +typedef QValueList < TreeEntry > TreeList; + +inline TreeEntry::TreeEntry (int adr, int size, int count, TreeList * sub) + : address (adr), total_size (size), total_count (count), _subentries (sub) +{ +} + +inline bool TreeEntry::operator == (const TreeEntry & r) const +{ // this one is for QValueList + return address == r.address; +} + +inline +bool TreeEntry::operator < (const TreeEntry & r) const +{ // this one is for qBubbleSort() ... yes, ugly hack + // the result is also reversed to get descending order + return total_size > r.total_size; +} + +inline TreeList * TreeEntry::subentries () const +{ // must be allocated only on-demand + if (_subentries == NULL) + _subentries = new TreeList; // this leaks memory, but oh well + return _subentries; +} + +TreeList * treeList = 0; + +void buildTree () +{ + for (Entry * entry = entryList->first (); + entry != NULL; entry = entryList->next ()) + { + if (!entry->total_size) + continue; + TreeList * list = treeList; + int i; + for (i = 0; entry->backtrace[i]; ++i) + ; // find last (topmost) backtrace entry + for (--i; i >= 0; --i) + { + TreeList::Iterator pos = list->find (entry->backtrace[i]); + if (pos == list->end ()) + { + list->prepend (TreeEntry (entry->backtrace[i], entry->total_size, + entry->count)); + pos = list->find (entry->backtrace[i]); + } + else + *pos = TreeEntry (entry->backtrace[i], + entry->total_size + (*pos).total_size, + entry->count + (*pos).total_count, + (*pos)._subentries); + list = (*pos).subentries (); + } + } +} + +void processTree (TreeList * list, int threshold, int maxdepth, int depth) +{ + if (++depth > maxdepth && maxdepth > 0) // maxdepth <= 0 means no limit + return; + for (TreeList::Iterator it = list->begin (); it != list->end ();) + { + if ((*it).subentries ()->count () > 0) + processTree ((*it).subentries (), threshold, maxdepth, depth); + if ((*it).total_size < threshold || (depth > maxdepth && maxdepth > 0)) + { + it = list->remove (it); + continue; + } + ++it; + } + qBubbleSort (*list); +} + +void +dumpTree (const TreeEntry & entry, int level, char *indent, FILE * file) +{ + bool extra_ind = (entry.subentries ()->count () > 0); + if(extra_ind) + indent[level++] = '+'; + indent[level] = '\0'; + char savindent[2]; + const char * str = lookupAddress (entry.address); + fprintf (file, "%s- %d/%d %s[0x%08x]\n", indent, + entry.total_size, entry.total_count, str, entry.address); + if (level > 1) + { + savindent[0] = indent[level - 2]; + savindent[1] = indent[level - 1]; + if (indent[level - 2] == '+') + indent[level - 2] = '|'; + else if (indent[level - 2] == '\\') + indent[level - 2] = ' '; + } + int pos = 0; + int last = entry.subentries ()->count() - 1; + for (TreeList::ConstIterator it = entry.subentries ()->begin (); + it != entry.subentries ()->end (); ++it) + { + if (pos == last) + indent[level - 1] = '\\'; + dumpTree ((*it), level, indent, file); + ++pos; + } + if (level > 1) + { + indent[level - 2] = savindent[0]; + indent[level - 1] = savindent[1]; + } + if (extra_ind) + --level; + indent[level] = '\0'; +} + +void dumpTree (FILE * file) +{ + char indent[1024]; + indent[0] = '\0'; + for (TreeList::ConstIterator it = treeList->begin (); + it != treeList->end (); ++it) + dumpTree (*it, 0, indent, file); +} + +void createTree (const QCString & treefile, int threshold, int maxdepth) +{ + FILE * file = fopen (treefile, "w"); + if (file == NULL) + { + fprintf (stderr, "Can't write tree file.\n"); + return; + } + treeList = new TreeList; + buildTree (); + processTree (treeList, threshold, maxdepth, 0); + dumpTree (file); + fclose (file); +} + +void readExcludeFile(const char *name) +{ + FILE *stream = fopen(name, "r"); + if (!stream) + { + fprintf(stderr, "Error: Can't open %s.\n", name); + exit(1); + } + char line[1024]; + while(!feof(stream)) + { + if (!fgets(line, 1023, stream)) break; + if ((line[0] == 0) || (line[0] == '#')) continue; + line[strlen(line)-1] = 0; + excludes->append(line); + } + fclose(stream); + excludes->sort(); +} + +static KCmdLineOptions options[] = +{ + { "x", 0, 0 }, + { "exclude <file>", "File containing symbols to exclude from output", 0}, + { "e", 0, 0 }, + { "exe <file>", "Executable to use for looking up unknown symbols", 0}, + { "+<trace-log>", "Log file to investigate", 0}, + {"t", 0, 0}, + {"tree <file>", "File to write allocations tree", 0}, + {"th", 0, 0}, + {"treethreshold <value>", + "Don't print subtrees which allocated less than <value> memory", 0}, + {"td", 0, 0}, + {"treedepth <value>", + "Don't print subtrees that are deeper than <value>", 0}, + KCmdLineLastOption +}; + +int main(int argc, char *argv[]) +{ + KInstance instance("kmtrace"); + + KCmdLineArgs::init(argc, argv, "kmtrace", "KDE Memory leak tracer", "v1.0"); + + KCmdLineArgs::addCmdLineOptions(options); + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + (void) args->count(); + const char *logfile; + if(args->count()) + logfile = args->arg(0); + else + logfile = "ktrace.out"; + + QCString exe = args->getOption("exe"); + QCString exclude; + + excludes = new QStrList; + + exclude = QFile::encodeName(locate("data", "kmtrace/kde.excludes")); + if(!exclude.isEmpty()) + readExcludeFile(exclude); + + exclude = args->getOption("exclude"); + if (!exclude.isEmpty()) + { + fprintf(stderr, "Reading %s\n", exclude.data()); + readExcludeFile(exclude); + } + + FILE *stream = fopen(logfile, "r"); + if (!stream) + { + fprintf(stderr, "Can't open %s\n", logfile); + exit(1); + } + + entryDict = new QIntDict<Entry>(9973); + symbolDict = new QIntDict<char>(9973); + formatDict = new QIntDict<char>(9973); + entryList = new QSortedList<Entry>; + + fprintf(stderr, "Running\n"); + QCString line; + char line2[1024]; + while(!feof(stream)) + { + fgets(line2, 1023, stream); + line2[strlen(line2)-1] = 0; + if (line2[0] == '=') + { + printf("%s\n", line2); + if( strcmp( line2, "= End" ) == 0 ) + break; + } + else if (line2[0] == '#') + { + QCString app(line2+1); + if(exe.isEmpty()) + { + exe = app.stripWhiteSpace(); + fprintf(stderr, "ktrace.out: malloc trace of %s\n", exe.data()); + } + else if(!app.contains(exe.data())) + { + fprintf(stderr, "trace file was for application '%s', not '%s'\n", app.data(), exe.data()); + exit(1); + } + } + else if (line2[0] == '@') + line = 0; + else if (line2[0] == '[') + line = line + ' ' + line2; + else if (line2[0] == '+') + { + allocCount++; + line = line + ' ' + line2; + parseLine(line, '+'); + line = 0; + if (allocCount & 128) + { + fprintf(stderr, "\rTotal long term allocs: %d still allocated: %d ", allocCount, entryDict->count()); + } + } + else if (line2[0] == '-') + { + line = line + ' ' + line2; + parseLine(line, '-'); + line = 0; + } + else if (line2[0] == '<') + { + line2[0] = '-'; + // First part of realloc (free) + QCString reline = line + ' ' + line2; + parseLine(reline, '-'); + } + else if (line2[0] == '>') + { + line2[0] = '+'; + // Second part of realloc (alloc) + line = line + ' ' + line2; + parseLine(line, '+'); + line = 0; + } + else + { + char *addr = index(line2,'['); + if (addr) + { + line = line + ' ' + addr; + } + } + } + leakedCount = count; + fprintf(stderr, "\rTotal long term allocs: %d still allocated: %d(%d) \n", allocCount, leakedCount, entryDict->count()); + printf("Totals allocated: %d bytes in %d blocks.\n", totalBytesAlloced, allocCount); + printf("Maximum allocated: %d bytes / %d blocks.\n", maxBytes, maxCount); + fprintf(stderr, "Collecting duplicates...\n"); + collectDupes(); + fprintf(stderr, "Sorting...\n"); + sortBlocks(); + printf("Totals leaked: %d bytes in %d blocks.\n", totalBytesLeaked, leakedCount); + fprintf(stderr, "Looking up symbols...\n"); + rewind(stream); + lookupSymbols(stream); + fprintf(stderr, "Looking up unknown symbols...\n"); + lookupUnknownSymbols(exe); + fprintf(stderr, "Printing...\n"); + dumpBlocks(); + QCString treeFile = args->getOption ("tree"); + if (!treeFile.isEmpty ()) + { + fprintf (stderr, "Creating allocation tree...\n"); + createTree (treeFile, args->getOption ("treethreshold").toInt (), + args->getOption ("treedepth").toInt ()); + } + fprintf(stderr, "Done.\n"); + return 0; +} diff --git a/kmtrace/ksotrace.cpp b/kmtrace/ksotrace.cpp new file mode 100644 index 00000000..9f379414 --- /dev/null +++ b/kmtrace/ksotrace.cpp @@ -0,0 +1,11 @@ +#include "ktrace.h" +#include <stdlib.h> + +class KTraceActivate +{ +public: + KTraceActivate() { setenv("LD_PRELOAD","",1); ktrace(); } + ~KTraceActivate() { kuntrace(); } +} kTraceActivateInstance; + + diff --git a/kmtrace/ktrace.c b/kmtrace/ktrace.c new file mode 100644 index 00000000..044a1d24 --- /dev/null +++ b/kmtrace/ktrace.c @@ -0,0 +1,840 @@ +/* More debugging hooks for `malloc'. + Copyright (C) 1991,92,93,94,96,97,98,99,2000 Free Software Foundation, Inc. + Written April 2, 1991 by John Gilmore of Cygnus Support. + Based on mcheck.c by Mike Haertel. + Hacked by AK + Cleanup and performance improvements by + Chris Schlaeger <cs@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + The author may be reached (Email) at the address mike@ai.mit.edu, + or (US mail) as Mike Haertel c/o Free Software Foundation. +*/ + +#define MALLOC_HOOKS +#define _GNU_SOURCE + +#include <pthread.h> +#include <malloc.h> + +#include <dlfcn.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <execinfo.h> +#include <unistd.h> + +#ifdef USE_IN_LIBIO +# include <libio/iolibio.h> +# define setvbuf(s, b, f, l) _IO_setvbuf (s, b, f, l) +#endif + +/* This is the most important parameter. It should be set to two times + * the maximum number of mallocs the application uses at a time. Prime + * numbers are very good candidates for this value. I added a list of + * some prime numbers for conveniance. + * + * 10007, 20011, 30013, 40031, 50033, 60037, 70039, 80051, 90053, + * 100057, 110059, 120067, 130069, 140071, 150077, 160079, 170081, + * 180097, 190121, 200131, 210139, 220141, 230143, 240151, 250153, + * 260171, 270191, 280199, 290201, 300221, 310223, 320237, 330241, + * 340261, 350281, 360287, 370373, 380377, 390389 */ +#define TR_CACHE_SIZE 100057 + +/* The DELTA value is also a value for the maximum + * number of iterations during a positive free/realloc + * search. It must NOT divide TR_CACHE_SIZE without + * remainder! */ +#define DELTA 23 + +/* The high and low mark control the flushing algorithm. Whenever the + * hashtable reaches the high mark every DELTAth entry is written to + * disk until the low filling mark is reached. A hash table becomes + * very inefficient when it becomes filled to 50% or more. */ +#define TR_HIGH_MARK ((int) (TR_CACHE_SIZE * 0.5)) +#define TR_LOW_MARK ((int) (TR_HIGH_MARK - (TR_CACHE_SIZE / DELTA))) + +/* Maximum call stack depth. No checking for overflows + * is done. Adjust this value with care! */ +#define TR_BT_SIZE 100 + +#define PROFILE 1 + +/* The hash function. Since the smallest allocated block is probably + * not smaller than 8 bytes we ignore the last 3 LSBs. */ +#define HASHFUNC(a) (((((unsigned long) a) << 1) ^ \ + (((unsigned long) a) >> 3)) % \ + TR_CACHE_SIZE) + +#define TR_HASHTABLE_SIZE 9973 + +#define TR_NONE 0 +#define TR_MALLOC 1 +#define TR_REALLOC 2 +#define TR_FREE 3 + +#define TRACE_BUFFER_SIZE 512 + +typedef struct +{ + void* ptr; + size_t size; + int bt_size; + void** bt; +} tr_entry; + +typedef struct CallerNode +{ + void* funcAdr; + unsigned long mallocs; + unsigned long mallocsSum; + unsigned int noCallees; + unsigned int maxCallees; + struct CallerNode** callees; +} CallerNode; + +void ktrace(void); +void kuntrace(void); + +static void addAllocationToTree(void); + +static void tr_freehook __P ((void*, const void*)); +static void* tr_reallochook __P ((void*, size_t, + const void*)); +static void* tr_mallochook __P ((size_t, const void*)); +/* Old hook values. */ +static void (*tr_old_free_hook) __P ((void* ptr, const void*)); +static void* (*tr_old_malloc_hook) __P ((size_t size, + const void*)); +static void* (*tr_old_realloc_hook) __P ((void* ptr, + size_t size, + const void*)); + +static FILE* mallstream; +static char malloc_trace_buffer[TRACE_BUFFER_SIZE]; + + +/* Address to breakpoint on accesses to... */ +void* mallwatch; + +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + + +static int bt_size; +static void *bt[TR_BT_SIZE + 1]; +static char tr_offsetbuf[20]; +static tr_entry tr_cache[TR_CACHE_SIZE]; +static int tr_cache_level; +static int tr_cache_pos; +static int tr_max_offset = 0; +static void *tr_hashtable[TR_HASHTABLE_SIZE]; +#ifdef PROFILE +static unsigned long tr_mallocs = 0; +static unsigned long tr_logged_mallocs = 0; +static unsigned long tr_frees = 0; +static unsigned long tr_logged_frees = 0; +static unsigned long tr_current_mallocs = 0; +static unsigned long tr_max_mallocs = 0; +static unsigned long tr_flashes = 0; +static unsigned long tr_failed_free_lookups = 0; +static unsigned long tr_malloc_collisions = 0; +#endif + +static CallerNode* CallTree = NULL; +static char* mallTreeFile = NULL; +static FILE* mallTreeStream = NULL; +static long mallThreshold = 2000; + +/* This function is called when the block being alloc'd, realloc'd, or + * freed has an address matching the variable "mallwatch". In a + * debugger, set "mallwatch" to the address of interest, then put a + * breakpoint on tr_break. */ +void tr_break __P ((void)); +void +tr_break() +{ +} + +__inline__ static void +tr_backtrace(void **_bt, int size) +{ + int i; + Dl_info info; + for (i = 0; i < size; i++) + { + long hash = (((unsigned long)_bt[i]) / 4) % TR_HASHTABLE_SIZE; + if ((tr_hashtable[hash]!= _bt[i]) && dladdr(_bt[i], &info) && + info.dli_fname && *info.dli_fname) + { + if (_bt[i] >= (void *) info.dli_saddr) + sprintf(tr_offsetbuf, "+%#lx", (unsigned long) + (_bt[i] - info.dli_saddr)); + else + sprintf(tr_offsetbuf, "-%#lx", (unsigned long) + (info.dli_saddr - _bt[i])); + fprintf(mallstream, "%s%s%s%s%s[%p]\n", + info.dli_fname ?: "", + info.dli_sname ? "(" : "", + info.dli_sname ?: "", + info.dli_sname ? tr_offsetbuf : "", + info.dli_sname ? ")" : "", + _bt[i]); + tr_hashtable[hash] = _bt[i]; + } + else + { + fprintf(mallstream, "[%p]\n", _bt[i]); + } + } +} + +__inline__ static void +tr_log(const void* caller, void* ptr, void* old, + size_t size, int op) +{ + int i, offset; + + switch (op) + { + case TR_FREE: + i = HASHFUNC(ptr); + if ((offset = (i + tr_max_offset + 1)) >= TR_CACHE_SIZE) + offset -= TR_CACHE_SIZE; + do + { + if (tr_cache[i].ptr == ptr) + { + tr_cache[i].ptr = NULL; + free(tr_cache[i].bt); + tr_cache_level--; + return; + } + if (++i >= TR_CACHE_SIZE) + i = 0; +#ifdef PROFILE + tr_failed_free_lookups++; +#endif + } while (i != offset); + + /* We don't know this allocation, so it has been flushed to disk + * already. So flush free as well. */ + fprintf(mallstream, "@\n"); + bt_size = backtrace(bt, TR_BT_SIZE); + tr_backtrace(&(bt[1]), bt_size - 2); + fprintf(mallstream, "- %p\n", ptr); +#ifdef PROFILE + tr_logged_frees++; +#endif + return; + + case TR_REALLOC: + /* If old is 0 it's actually a malloc. */ + if (old) + { + i = HASHFUNC(old); + if ((offset = (i + tr_max_offset + 1)) >= TR_CACHE_SIZE) + offset -= TR_CACHE_SIZE; + do + { + if (tr_cache[i].ptr == old) + { + int j = HASHFUNC(ptr); + /* We move the entry otherwise the free will be + * fairly expensive due to the wrong place in the + * hash table. */ + tr_cache[i].ptr = NULL; + for ( ; ; ) + { + if (tr_cache[j].ptr == NULL) + break; + + if (++j >= TR_CACHE_SIZE) + i = 0; + } + tr_cache[j].ptr = ptr; + if (ptr) + { + tr_cache[j].size = tr_cache[i].size; + tr_cache[j].bt_size = tr_cache[i].bt_size; + tr_cache[j].bt = tr_cache[i].bt; + } + else + tr_cache_level--; + tr_cache[i].size = size; + return; + } + if (++i >= TR_CACHE_SIZE) + i = 0; + } while (i != offset); + + fprintf(mallstream, "@\n"); + bt_size = backtrace(bt, TR_BT_SIZE); + tr_backtrace(&(bt[1]), bt_size - 2); + fprintf(mallstream, "< %p\n", old); + fprintf(mallstream, "> %p %#lx\n", ptr, + (unsigned long) size); + return; + } + + case TR_MALLOC: + if (tr_cache_level >= TR_HIGH_MARK) + { + /* The hash table becomes ineffective when the high mark has + * been reached. We still need some more experience with + * the low mark. It's unclear what reasonable values are. */ +#ifdef PROFILE + tr_flashes++; +#endif + i = HASHFUNC(ptr); + do + { + if (tr_cache[i].ptr) + { +#ifdef PROFILE + tr_logged_mallocs++; +#endif + fprintf(mallstream, "@\n"); + tr_backtrace(&(tr_cache[i].bt[1]), + tr_cache[i].bt_size - 2); + fprintf(mallstream, "+ %p %#lx\n", + tr_cache[i].ptr, + (unsigned long int) + tr_cache[i].size); + tr_cache[i].ptr = NULL; + tr_cache_level--; + } + if ((i += DELTA) >= TR_CACHE_SIZE) + i -= TR_CACHE_SIZE; + } while (tr_cache_level > TR_LOW_MARK); + } + + i = HASHFUNC(ptr); + for ( ; ; ) + { + if (tr_cache[i].ptr == NULL) + break; + + if (++i >= TR_CACHE_SIZE) + i = 0; +#ifdef PROFILE + tr_malloc_collisions++; +#endif + } + if ((offset = (i - HASHFUNC(ptr))) < 0) + offset += TR_CACHE_SIZE; + if (offset > tr_max_offset) + tr_max_offset = offset; + + tr_cache[i].ptr = ptr; + tr_cache[i].size = size; + tr_cache[i].bt = (void**) malloc(TR_BT_SIZE * sizeof(void*)); + tr_cache[i].bt_size = backtrace( + tr_cache[i].bt, TR_BT_SIZE); + tr_cache[i].bt = realloc(tr_cache[i].bt, tr_cache[i].bt_size * sizeof(void*)); + tr_cache_level++; + + return; + + case TR_NONE: + if (tr_cache[tr_cache_pos].ptr) + { +#ifdef PROFILE + tr_logged_mallocs++; +#endif + fprintf(mallstream, "@\n"); + tr_backtrace(&(tr_cache[tr_cache_pos].bt[1]), + tr_cache[tr_cache_pos].bt_size - 2); + fprintf(mallstream, "+ %p %#lx\n", + tr_cache[tr_cache_pos].ptr, + (unsigned long int) + tr_cache[tr_cache_pos].size); + tr_cache[tr_cache_pos].ptr = NULL; + free(tr_cache[tr_cache_pos].bt); + tr_cache_level--; + } + + if (++tr_cache_pos >= TR_CACHE_SIZE) + tr_cache_pos = 0; + break; + } +} + +static void +tr_freehook (ptr, caller) + void* ptr; + const void* caller; +{ + if (ptr == NULL) + return; + if (ptr == mallwatch) + tr_break (); + + pthread_mutex_lock(&lock); +#ifdef PROFILE + tr_frees++; + tr_current_mallocs--; +#endif + + __free_hook = tr_old_free_hook; + + if (tr_old_free_hook != NULL) + (*tr_old_free_hook) (ptr, caller); + else + free(ptr); + tr_log(caller, ptr, 0, 0, TR_FREE); + + __free_hook = tr_freehook; + pthread_mutex_unlock(&lock); +} + +static void* +tr_mallochook (size, caller) + size_t size; + const void* caller; +{ + void* hdr; + + pthread_mutex_lock(&lock); + + __malloc_hook = tr_old_malloc_hook; + __realloc_hook = tr_old_realloc_hook; + __free_hook = tr_old_free_hook; + + if (tr_old_malloc_hook != NULL) + hdr = (void*) (*tr_old_malloc_hook) (size, caller); + else + hdr = (void*) malloc(size); + tr_log(caller, hdr, 0, size, TR_MALLOC); + /* We only build the allocation tree if mallTreeFile has been set. */ + if (mallTreeFile) + addAllocationToTree(); + + __malloc_hook = tr_mallochook; + __realloc_hook = tr_reallochook; + __free_hook = tr_freehook; + +#ifdef PROFILE + tr_mallocs++; + tr_current_mallocs++; + if (tr_current_mallocs > tr_max_mallocs) + tr_max_mallocs = tr_current_mallocs; +#endif + pthread_mutex_unlock(&lock); + + if (hdr == mallwatch) + tr_break (); + + return hdr; +} + +static void* +tr_reallochook (ptr, size, caller) + void* ptr; + size_t size; + const void* caller; +{ + void* hdr; + + if (ptr == mallwatch) + tr_break (); + + pthread_mutex_lock(&lock); + + __free_hook = tr_old_free_hook; + __malloc_hook = tr_old_malloc_hook; + __realloc_hook = tr_old_realloc_hook; + + if (tr_old_realloc_hook != NULL) + hdr = (void*) (*tr_old_realloc_hook) (ptr, size, caller); + else + hdr = (void*) realloc (ptr, size); + + tr_log(caller, hdr, ptr, size, TR_REALLOC); + + __free_hook = tr_freehook; + __malloc_hook = tr_mallochook; + __realloc_hook = tr_reallochook; + +#ifdef PROFILE + /* If ptr is 0 there was no previos malloc of this location */ + if (ptr == NULL) + { + tr_mallocs++; + tr_current_mallocs++; + if (tr_current_mallocs > tr_max_mallocs) + tr_max_mallocs = tr_current_mallocs; + } +#endif + + pthread_mutex_unlock(&lock); + + if (hdr == mallwatch) + tr_break (); + + return hdr; +} + +void +addAllocationToTree(void) +{ + int bt_size; + int i, j; + void *bt[TR_BT_SIZE + 1]; + CallerNode* cn = CallTree; + CallerNode** parent = &CallTree; + + bt_size = backtrace(bt, TR_BT_SIZE); + for (i = bt_size - 1; i >= 4; i--) + { + if (cn == NULL) + { + *parent = cn = (CallerNode*) malloc(sizeof(CallerNode)); + cn->funcAdr = bt[i]; + cn->mallocs = 0; + cn->noCallees = 0; + cn->maxCallees = 0; + cn->callees = NULL; + } + if (i == 4) + cn->mallocs++; + else + { + int knownCallee = 0; + for (j = 0; j < cn->noCallees; j++) + if (bt[i - 1] == cn->callees[j]->funcAdr) + { + parent = &cn->callees[j]; + cn = cn->callees[j]; + knownCallee = 1; + break; + } + if (!knownCallee) + { + if (cn->noCallees == cn->maxCallees) + { + /* Copy callees into new, larger array. */ + CallerNode** tmp; + int newSize = 2 * cn->maxCallees; + if (newSize == 0) + newSize = 4; + tmp = (CallerNode**) malloc(newSize * sizeof(CallerNode*)); + memcpy(tmp, cn->callees, + cn->maxCallees * sizeof(CallerNode*)); + if (cn->callees) + free(cn->callees); + cn->callees = tmp; + memset(&cn->callees[cn->maxCallees], 0, + (newSize - cn->maxCallees) * sizeof(CallerNode*)); + cn->maxCallees = newSize; + } + parent = &cn->callees[cn->noCallees++]; + cn = 0; + } + } + } +} + +static int +removeBranchesBelowThreshold(CallerNode* root) +{ + int i; + int max; + + if (!root) + return (0); + for (i = 0; i < root->noCallees; i++) + { + if (removeBranchesBelowThreshold(root->callees[i])) + { + free(root->callees[i]); + if (root->noCallees > 1) + { + root->callees[i] = root->callees[root->noCallees - 1]; + root->callees[root->noCallees - 1] = 0; + } + else if (root->noCallees == 1) + root->callees[i] = 0; + + root->noCallees--; + i--; + } + } + if (root->noCallees == 0 && root->mallocs < mallThreshold ) + return (1); + + return (0); +} + +static void +dumpCallTree(CallerNode* root, char* indentStr, int rawMode) +{ + int i; + Dl_info info; + char* newIndentStr; + size_t indDepth; + + if (!root || !mallTreeStream) + return; + + if (rawMode) + { + fprintf(mallTreeStream, "-"); + } + else + { + newIndentStr = (char*) malloc(strlen(indentStr) + 2); + strcpy(newIndentStr, indentStr); + if (root->noCallees > 0) + strcat(newIndentStr, "+"); + indDepth = strlen(newIndentStr); + fprintf(mallTreeStream, "%s- ", newIndentStr); + } + + if (dladdr(root->funcAdr, &info) && info.dli_fname && *info.dli_fname) + { + if (root->funcAdr >= (void *) info.dli_saddr) + sprintf(tr_offsetbuf, "+%#lx", (unsigned long) + (root->funcAdr - info.dli_saddr)); + else + sprintf(tr_offsetbuf, "-%#lx", (unsigned long) + (info.dli_saddr - root->funcAdr)); + fprintf(mallTreeStream, "%s%s%s%s%s[%p]", + info.dli_fname ?: "", + info.dli_sname ? "(" : "", + info.dli_sname ?: "", + info.dli_sname ? tr_offsetbuf : "", + info.dli_sname ? ")" : "", + root->funcAdr); + } + else + { + fprintf(mallTreeStream, "[%p]", root->funcAdr); + } + fprintf(mallTreeStream, ": %lu\n", root->mallocs); + if (indDepth > 1 && !rawMode) + { + if (newIndentStr[indDepth - 2] == '+') + newIndentStr[indDepth - 2] = '|'; + else if (newIndentStr[indDepth - 2] == '\\') + newIndentStr[indDepth - 2] = ' '; + } + + for (i = 0; i < root->noCallees; i++) + { + if (rawMode) + dumpCallTree(root->callees[i], "", 1); + else + { + if (i == root->noCallees - 1) + newIndentStr[indDepth - 1] = '\\'; + dumpCallTree(root->callees[i], newIndentStr, rawMode); + } + } + if (rawMode) + fprintf(mallTreeStream, ".\n"); + else + free(newIndentStr); + +} + +#ifdef _LIBC +extern void __libc_freeres (void); + +/* This function gets called to make sure all memory the library + * allocates get freed and so does not irritate the user when studying + * the mtrace output. */ +static void +release_libc_mem (void) +{ + /* Only call the free function if we still are running in mtrace + * mode. */ + /*if (mallstream != NULL) + __libc_freeres ();*/ + + kuntrace(); + write(2, "kuntrace()\n", 11); +} +#endif + +/* We enable tracing if either the environment variable MALLOC_TRACE + * or the variable MALLOC_TREE are set, or if the variable mallwatch + * has been patched to an address that the debugging user wants us to + * stop on. When patching mallwatch, don't forget to set a breakpoint + * on tr_break! */ +void +ktrace() +{ +#ifdef _LIBC + static int added_atexit_handler; +#endif + char* mallfile; + + /* Don't panic if we're called more than once. */ + if (mallstream != NULL) + return; + +#ifdef _LIBC + /* When compiling the GNU libc we use the secure getenv function + * which prevents the misuse in case of SUID or SGID enabled + * programs. */ + mallfile = __secure_getenv("MALLOC_TRACE"); + mallTreeFile = __secure_getenv("MALLOC_TREE"); + if( __secure_getenv("MALLOC_THRESHOLD") != NULL ) + mallThreshold = atol(__secure_getenv("MALLOC_THRESHOLD")); +#else + mallfile = getenv("MALLOC_TRACE"); + mallTreeFile = getenv("MALLOC_TREE"); + if( getenv("MALLOC_THRESHOLD") != NULL ) + mallThreshold = atol(getenv("MALLOC_THRESHOLD")); +#endif + if (mallfile != NULL || mallTreeFile != NULL || mallwatch != NULL) + { + mallstream = fopen (mallfile != NULL ? mallfile : "/dev/null", "w"); + if (mallstream != NULL) + { + char buf[512]; + + /* Be sure it doesn't malloc its buffer! */ + setvbuf (mallstream, malloc_trace_buffer, _IOFBF, + TRACE_BUFFER_SIZE); + fprintf (mallstream, "= Start\n"); + memset(buf, 0, sizeof(buf)); + readlink("/proc/self/exe", buf, sizeof(buf)); + if(*buf) + fprintf (mallstream, "#%s\n", buf); + + /* Save old hooks and hook in our own functions for all + * malloc, realloc and free calls */ + tr_old_free_hook = __free_hook; + __free_hook = tr_freehook; + tr_old_malloc_hook = __malloc_hook; + __malloc_hook = tr_mallochook; + tr_old_realloc_hook = __realloc_hook; + __realloc_hook = tr_reallochook; + + tr_cache_pos = TR_CACHE_SIZE; + do + { + tr_cache[--tr_cache_pos].ptr = NULL; + } while (tr_cache_pos); + tr_cache_level = 0; + + memset(tr_hashtable, 0, sizeof(void*) * TR_HASHTABLE_SIZE); +#ifdef _LIBC + if (!added_atexit_handler) + { + added_atexit_handler = 1; + atexit (release_libc_mem); + } +#endif + } + } +} + +void +kuntrace() +{ + if (mallstream == NULL) + return; + + /* restore hooks to original values */ + __free_hook = tr_old_free_hook; + __malloc_hook = tr_old_malloc_hook; + __realloc_hook = tr_old_realloc_hook; + + if (removeBranchesBelowThreshold(CallTree)) + CallTree = 0; + if (mallTreeFile) + { + if (mallTreeStream = fopen(mallTreeFile, "w")) + { + dumpCallTree(CallTree, "", 0); + fclose(mallTreeStream); + } + } + + /* Flush cache. */ + while (tr_cache_level) + tr_log(NULL, 0, 0, 0, TR_NONE); + + fprintf (mallstream, "= End\n"); +#ifdef PROFILE + fprintf(mallstream, "\nMax Mallocs: %8ld Cache Size: %8ld" + " Flashes: %8ld\n" + "Mallocs: %8ld Frees: %8ld Leaks: %8ld\n" + "Logged Mallocs: %8ld Logged Frees: %8ld Logged Leaks: %8ld\n" + "Avg. Free lookups: %ld Malloc collisions: %ld Max offset: %ld\n", + tr_max_mallocs, TR_CACHE_SIZE, tr_flashes, + tr_mallocs, tr_frees, tr_current_mallocs, + tr_logged_mallocs, tr_logged_frees, + tr_logged_mallocs - tr_logged_frees, + tr_frees > 0 ? ( tr_failed_free_lookups / tr_frees ) : 0, + tr_malloc_collisions, tr_max_offset); +#endif + fclose (mallstream); + mallstream = NULL; + write(2, "kuntrace()\n", 11); +} + +int fork() +{ + int result; + if (mallstream) + fflush(mallstream); + result = __fork(); + if (result == 0) + { + if (mallstream) + { + close(fileno(mallstream)); + mallstream = NULL; + __free_hook = tr_old_free_hook; + __malloc_hook = tr_old_malloc_hook; + __realloc_hook = tr_old_realloc_hook; + } + } + return result; +} + + +static int my_mcount_lock = 0; +void mcount() +{ + Dl_info info; + int i = 1; + if (my_mcount_lock) return; + my_mcount_lock = 1; + bt_size = backtrace(bt, TR_BT_SIZE); +#if 0 +for(i = 1; (i < 5) && (i < bt_size); i++) +{ +#endif + if (dladdr(bt[i], &info) && info.dli_fname && *info.dli_fname) + { + fprintf(stdout, "%s\n", info.dli_sname ? info.dli_sname : ""); + } + else + { + fprintf(stdout, "[%p]\n", bt[i]); + } +#if 0 +} + fprintf(stdout, "\n"); +#endif + my_mcount_lock = 0; +} + diff --git a/kmtrace/ktrace.h b/kmtrace/ktrace.h new file mode 100644 index 00000000..366f2148 --- /dev/null +++ b/kmtrace/ktrace.h @@ -0,0 +1,10 @@ +#ifndef _KTRACE_H +#define _KTRACE_H + +extern "C" { +/* Activate a standard collection of tracing hooks. */ +extern void ktrace (void); +extern void kuntrace (void); +} + +#endif /* ktrace.h */ diff --git a/kmtrace/match.cpp b/kmtrace/match.cpp new file mode 100644 index 00000000..2a9c74b4 --- /dev/null +++ b/kmtrace/match.cpp @@ -0,0 +1,73 @@ +#include <qintdict.h> +#include <stdio.h> +#include <qstringlist.h> +#include <qstrlist.h> +#include <qtextstream.h> +#include <qsortedlist.h> +#include <qfile.h> +#include <qtl.h> +#include <qvaluelist.h> +#include <stdlib.h> +#include <ktempfile.h> +#include <kinstance.h> +#include <kstandarddirs.h> +#include <kcmdlineargs.h> + +int main(int argc, char **argv) +{ + char buf[1024]; + if (argc != 3) + { + fprintf(stderr, "Usage: kmmatch <map-file> <call-file>\n"); + fprintf(stderr, "\n<map-file> is a file as output by 'nm'.\n"); + fprintf(stderr, "<call-file> is a file that contains symbols, e.g. a list of all\n" + "function calls made by a program.\n"); + fprintf(stderr, "The program will print all symbols from <call-file> that are present\n" + "in <map-file>, in the same order as they appear in <call-file>.\n"); + return 1; + } + + int i = 1; + QDict<int> dict(20011); + + FILE *map_file = fopen(argv[1], "r"); + if (!map_file) + { + fprintf(stderr, "Error opening '%s'\n", argv[1]); + return 1; + } + while(!feof(map_file)) + { + fgets(buf, 1024, map_file); + QString line = QString::fromLatin1(buf).stripWhiteSpace(); + QStringList split = QStringList::split(' ', line); + if (split.count() <= 1) + return 1; + + if (split[1] == "T") + { + dict.insert(split[2], &i); + } + } + fclose(map_file); + + FILE *call_file = fopen(argv[2], "r"); + if (!call_file) + { + fprintf(stderr, "Error opening '%s'\n", argv[2]); + return 1; + } + + while(!feof(call_file)) + { + fgets(buf, 1024, call_file); + QString line = QString::fromLatin1(buf).stripWhiteSpace(); + if (dict.find(line)) + { + qWarning("%s", line.latin1()); + } + } + fclose(call_file); + return 0; +} + diff --git a/kmtrace/mtrace.c b/kmtrace/mtrace.c new file mode 100644 index 00000000..26c08ae6 --- /dev/null +++ b/kmtrace/mtrace.c @@ -0,0 +1,383 @@ +/* More debugging hooks for `malloc'. + Copyright (C) 1991,92,93,94,96,97,98,99,2000 Free Software Foundation, Inc. + Written April 2, 1991 by John Gilmore of Cygnus Support. + Based on mcheck.c by Mike Haertel. + Hacked by AK + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + The author may be reached (Email) at the address mike@ai.mit.edu, + or (US mail) as Mike Haertel c/o Free Software Foundation. */ + +#define _LIBC +#define MALLOC_HOOKS + +#ifndef _MALLOC_INTERNAL +#define _MALLOC_INTERNAL +#include <malloc.h> +#include <mcheck.h> +#include <bits/libc-lock.h> +#endif + +#include <dlfcn.h> + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <stdio-common/_itoa.h> +#include <elf/ldsodefs.h> + +#ifdef USE_IN_LIBIO +# include <libio/iolibio.h> +# define setvbuf(s, b, f, l) _IO_setvbuf (s, b, f, l) +#endif + +#define TRACE_BUFFER_SIZE 512 + +static FILE *mallstream; +static const char mallenv[]= "MALLOC_TRACE"; +static char malloc_trace_buffer[TRACE_BUFFER_SIZE]; + +__libc_lock_define_initialized (static, lock) + +/* Address to breakpoint on accesses to... */ +__ptr_t mallwatch; + +/* File name and line number information, for callers that had + the foresight to call through a macro. */ +char *_mtrace_file; +int _mtrace_line; + +/* Old hook values. */ +static void (*tr_old_free_hook) __P ((__ptr_t ptr, const __ptr_t)); +static __ptr_t (*tr_old_malloc_hook) __P ((__malloc_size_t size, + const __ptr_t)); +static __ptr_t (*tr_old_realloc_hook) __P ((__ptr_t ptr, + __malloc_size_t size, + const __ptr_t)); + +#define TR_PIPELINE_SIZE 16 +#define TR_BT_SIZE 80 +#define TR_HASHTABLE_SIZE 9973 + +#define TR_NONE 0 +#define TR_MALLOC 1 +#define TR_REALLOC 2 +#define TR_FREE 3 + +typedef struct { + int op; + __ptr_t ptr; + __ptr_t old; + __malloc_size_t size; + int bt_size; + void *bt[TR_BT_SIZE+1]; +} tr_entry; + +static tr_entry tr_pipeline[TR_PIPELINE_SIZE]; +static int tr_pipeline_pos; +static void *tr_hashtable[TR_HASHTABLE_SIZE]; + + +/* This function is called when the block being alloc'd, realloc'd, or + freed has an address matching the variable "mallwatch". In a debugger, + set "mallwatch" to the address of interest, then put a breakpoint on + tr_break. */ + +void tr_break __P ((void)); +void +tr_break () +{ +} + +static void +tr_backtrace(void **bt, int size) +{ + char buf[20]; + int i; + Dl_info info; + for(i = 0; i < size; i++) + { + long hash = (((unsigned long)bt[i]) / 4) % TR_HASHTABLE_SIZE; + if ((tr_hashtable[hash]!= bt[i]) && _dl_addr(bt[i], &info) && info.dli_fname && *info.dli_fname) + { + if (bt[i] >= (void *) info.dli_saddr) + sprintf(buf, "+%#lx", (unsigned long)(bt[i] - info.dli_saddr)); + else + sprintf(buf, "-%#lx", (unsigned long)(info.dli_saddr - bt[i])); + fprintf(mallstream, "%s%s%s%s%s[%p]\n", + info.dli_fname ?: "", + info.dli_sname ? "(" : "", + info.dli_sname ?: "", + info.dli_sname ? buf : "", + info.dli_sname ? ")" : "", + bt[i]); + tr_hashtable[hash] = bt[i]; + } + else { + fprintf(mallstream, "[%p]\n", bt[i]); + } + } +} + +static void +inline +tr_log(const __ptr_t caller, __ptr_t ptr, __ptr_t old, __malloc_size_t size, int op) +{ + switch(op) + { + case TR_REALLOC: + break; + case TR_MALLOC: + break; + case TR_FREE: + { + int i = tr_pipeline_pos; + do { + if (i) i--; else i = TR_PIPELINE_SIZE-1; + if (tr_pipeline[i].ptr == ptr) + { + if (tr_pipeline[i].op == TR_MALLOC) + { + tr_pipeline[i].op = TR_NONE; + tr_pipeline[i].ptr = NULL; + return; + } + break; + } + } while (i != tr_pipeline_pos); + } + } + + if (tr_pipeline[tr_pipeline_pos].op) + { + putc('@', mallstream); + putc('\n', mallstream); + /* Generate backtrace... + * We throw out the first frame (tr_mallochook) + * end the last one (_start) + */ + tr_backtrace(&(tr_pipeline[tr_pipeline_pos].bt[1]), + tr_pipeline[tr_pipeline_pos].bt_size-2); + + switch(tr_pipeline[tr_pipeline_pos].op) + { + case TR_MALLOC: + fprintf (mallstream, "+ %p %#lx\n", + tr_pipeline[tr_pipeline_pos].ptr, + (unsigned long int) tr_pipeline[tr_pipeline_pos].size); + break; + case TR_FREE: + fprintf (mallstream, "- %p\n", + tr_pipeline[tr_pipeline_pos].ptr); + break; + case TR_REALLOC: + fprintf (mallstream, "< %p\n", + tr_pipeline[tr_pipeline_pos].old); + fprintf (mallstream, "> %p %#lx\n", + tr_pipeline[tr_pipeline_pos].ptr, + (unsigned long int) tr_pipeline[tr_pipeline_pos].size); + break; + } + } + + tr_pipeline[tr_pipeline_pos].op = op; + tr_pipeline[tr_pipeline_pos].ptr = ptr; + tr_pipeline[tr_pipeline_pos].old = old; + tr_pipeline[tr_pipeline_pos].size = size; + tr_pipeline[tr_pipeline_pos].bt_size = backtrace( + tr_pipeline[tr_pipeline_pos].bt, TR_BT_SIZE); + tr_pipeline_pos++; + if (tr_pipeline_pos == TR_PIPELINE_SIZE) + tr_pipeline_pos = 0; +} + +static void tr_freehook __P ((__ptr_t, const __ptr_t)); +static void +tr_freehook (ptr, caller) + __ptr_t ptr; + const __ptr_t caller; +{ + if (ptr == NULL) + return; + __libc_lock_lock (lock); + tr_log(caller, ptr, 0, 0, TR_FREE ); + __libc_lock_unlock (lock); + if (ptr == mallwatch) + tr_break (); + __libc_lock_lock (lock); + __free_hook = tr_old_free_hook; + if (tr_old_free_hook != NULL) + (*tr_old_free_hook) (ptr, caller); + else + free (ptr); + __free_hook = tr_freehook; + __libc_lock_unlock (lock); +} + +static __ptr_t tr_mallochook __P ((__malloc_size_t, const __ptr_t)); +static __ptr_t +tr_mallochook (size, caller) + __malloc_size_t size; + const __ptr_t caller; +{ + __ptr_t hdr; + + __libc_lock_lock (lock); + + __malloc_hook = tr_old_malloc_hook; + if (tr_old_malloc_hook != NULL) + hdr = (__ptr_t) (*tr_old_malloc_hook) (size, caller); + else + hdr = (__ptr_t) malloc (size); + __malloc_hook = tr_mallochook; + + tr_log(caller, hdr, 0, size, TR_MALLOC); + + __libc_lock_unlock (lock); + + if (hdr == mallwatch) + tr_break (); + + return hdr; +} + +static __ptr_t tr_reallochook __P ((__ptr_t, __malloc_size_t, const __ptr_t)); +static __ptr_t +tr_reallochook (ptr, size, caller) + __ptr_t ptr; + __malloc_size_t size; + const __ptr_t caller; +{ + __ptr_t hdr; + + if (ptr == mallwatch) + tr_break (); + + __libc_lock_lock (lock); + + __free_hook = tr_old_free_hook; + __malloc_hook = tr_old_malloc_hook; + __realloc_hook = tr_old_realloc_hook; + if (tr_old_realloc_hook != NULL) + hdr = (__ptr_t) (*tr_old_realloc_hook) (ptr, size, caller); + else + hdr = (__ptr_t) realloc (ptr, size); + __free_hook = tr_freehook; + __malloc_hook = tr_mallochook; + __realloc_hook = tr_reallochook; + + tr_log(caller, hdr, ptr, size, TR_REALLOC); + + __libc_lock_unlock (lock); + + if (hdr == mallwatch) + tr_break (); + + return hdr; +} + + +#ifdef _LIBC +extern void __libc_freeres (void); + +/* This function gets called to make sure all memory the library + allocates get freed and so does not irritate the user when studying + the mtrace output. */ +static void +release_libc_mem (void) +{ + /* Only call the free function if we still are running in mtrace mode. */ + if (mallstream != NULL) + __libc_freeres (); +} +#endif + + +/* We enable tracing if either the environment variable MALLOC_TRACE + is set, or if the variable mallwatch has been patched to an address + that the debugging user wants us to stop on. When patching mallwatch, + don't forget to set a breakpoint on tr_break! */ + +void +mtrace () +{ +#ifdef _LIBC + static int added_atexit_handler; +#endif + char *mallfile; + + /* Don't panic if we're called more than once. */ + if (mallstream != NULL) + return; + +#ifdef _LIBC + /* When compiling the GNU libc we use the secure getenv function + which prevents the misuse in case of SUID or SGID enabled + programs. */ + mallfile = __secure_getenv (mallenv); +#else + mallfile = getenv (mallenv); +#endif + if (mallfile != NULL || mallwatch != NULL) + { + mallstream = fopen (mallfile != NULL ? mallfile : "/dev/null", "w"); + if (mallstream != NULL) + { + /* Be sure it doesn't malloc its buffer! */ + setvbuf (mallstream, malloc_trace_buffer, _IOFBF, TRACE_BUFFER_SIZE); + fprintf (mallstream, "= Start\n"); + tr_old_free_hook = __free_hook; + __free_hook = tr_freehook; + tr_old_malloc_hook = __malloc_hook; + __malloc_hook = tr_mallochook; + tr_old_realloc_hook = __realloc_hook; + __realloc_hook = tr_reallochook; + + tr_pipeline_pos = TR_PIPELINE_SIZE; + for(;tr_pipeline_pos--;) + { + tr_pipeline[tr_pipeline_pos].op = TR_NONE; + tr_pipeline[tr_pipeline_pos].ptr = NULL; + } + memset(tr_hashtable, 0, sizeof(void *)*TR_HASHTABLE_SIZE); +#ifdef _LIBC + if (!added_atexit_handler) + { + added_atexit_handler = 1; + atexit (release_libc_mem); + } +#endif + } + } +} + +void +muntrace () +{ + if (mallstream == NULL) + return; + + fprintf (mallstream, "= End\n"); + fclose (mallstream); + mallstream = NULL; + __free_hook = tr_old_free_hook; + __malloc_hook = tr_old_malloc_hook; + __realloc_hook = tr_old_realloc_hook; +} + + |