From 1dc046ab3e5ac9b09dff12df02392f431a4cec95 Mon Sep 17 00:00:00 2001 From: Pavel Cisler Date: Wed, 14 Jun 2000 00:26:58 +0000 Subject: New small leakchecking library. It is a bit simpler than MemProf in that 2000-06-13 Pavel Cisler * test/nautilus-leak-checker-stubs.h: * test/nautilus-leak-checker.c: (nautilus_leak_allocation_record_init), (nautilus_leak_allocation_record_copy), (nautilus_leak_allocation_record_finalize), (nautilus_leak_allocation_record_free), (nautilus_leak_stack_crawl_compare), (nautilus_leak_initialize), (get_stack_trace), (detect_reentry), (nautilus_leak_record_malloc), (nautilus_leak_record_realloc), (nautilus_leak_record_free), (nautilus_leak_initialize_if_needed), (__libc_malloc), (__libc_memalign), (__libc_calloc), (__libc_realloc), (__libc_free), (malloc), (realloc), (memalign), (calloc), (free), (print_one_leak), (nautilus_leak_print_leaks), (nautilus_leak_checker_init), (allocate_lots), (leak_mem2), (leak_mem), (main): * test/nautilus-leak-checker.h: * test/nautilus-leak-hash-table.c: (nautilus_leak_hash_element_finalize), (nautilus_leak_hash_element_hash), (nautilus_leak_hash_element_match), (nautilus_leak_hash_element_vector_inititalize), (nautilus_leak_hash_element_vector_finalize), (nautilus_leak_hash_element_vector_at), (nautilus_leak_hash_element_vector_add), (nautilus_leak_hash_element_vector_remove), (nautilus_leak_hash_table_optimal_size), (nautilus_leak_hash_table_initialize), (nautilus_leak_hash_table_finalize), (nautilus_leak_hash_table_new), (nautilus_leak_hash_table_free), (nautilus_leak_hash_table_hash), (nautilus_leak_hash_table_find), (nautilus_leak_hash_table_add), (nautilus_leak_hash_table_remove_element), (nautilus_leak_hash_table_remove), (nautilus_leak_table_new_entry_at), (nautilus_leak_table_add_entry), (nautilus_leak_table_new), (nautilus_leak_table_free), (sort_by_count), (nautilus_leak_table_sort_by_count), (sort_by_size), (nautilus_leak_table_sort_by_size), (nautilus_leak_table_each_item): * test/nautilus-leak-hash-table.h: * test/nautilus-leak-symbol-lookup.c: (nautilus_leak_find_symbol_in_map), (nautilus_leak_symbol_map_load), (nautilus_leak_symbol_map_load_if_needed), (nautilus_leak_print_symbol_cleanup), (nautilus_leak_find_symbol_address), (nautilus_leak_print_symbol_address): * test/nautilus-leak-symbol-lookup.h: New small leakchecking library. It is a bit simpler than MemProf in that it doesn't try to find leaks, just lists all the outstanding allocations (which if done at application quit is a list of leaks). It borrows heavily from MemProf. * test/Makefile.am: Currently broken makefile changes for the leakchecker library. Needs some libtool wrestling that Ramiro kindly offered to help with. * src/nautilus-main.c: (nautilus_leak_checker_init), (nautilus_leak_print_leaks), (main): Call the leakchecker. Noop unless the leakchecker lib is loaded with Nautilus using the LD_PRELOAD variable (the only way of turning the thing on/off). --- test/Makefile.am | 28 ++ test/nautilus-leak-checker-stubs.h | 78 +++++ test/nautilus-leak-checker.c | 693 +++++++++++++++++++++++++++++++++++++ test/nautilus-leak-checker.h | 54 +++ test/nautilus-leak-hash-table.c | 511 +++++++++++++++++++++++++++ test/nautilus-leak-hash-table.h | 57 +++ test/nautilus-leak-symbol-lookup.c | 223 ++++++++++++ test/nautilus-leak-symbol-lookup.h | 31 ++ 8 files changed, 1675 insertions(+) create mode 100644 test/nautilus-leak-checker-stubs.h create mode 100644 test/nautilus-leak-checker.c create mode 100644 test/nautilus-leak-checker.h create mode 100644 test/nautilus-leak-hash-table.c create mode 100644 test/nautilus-leak-hash-table.h create mode 100644 test/nautilus-leak-symbol-lookup.c create mode 100644 test/nautilus-leak-symbol-lookup.h (limited to 'test') diff --git a/test/Makefile.am b/test/Makefile.am index b6d026869..2f84aa833 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -1,5 +1,7 @@ NULL= +lib_LTLIBRARIES = libleakcheck.la + INCLUDES =\ -I$(top_srcdir) \ -I$(top_builddir) \ @@ -38,6 +40,32 @@ test_nautilus_mime_actions_SOURCES = test-nautilus-mime-actions.c test_nautilus_mime_actions_set_SOURCES = test-nautilus-mime-actions-set.c +libleakcheck_la_SOURCES = \ + nautilus-leak-checker.c \ + nautilus-leak-checker.h \ + nautilus-leak-checker-stubs.h \ + nautilus-leak-hash-table.c \ + nautilus-leak-hash-table.h \ + nautilus-leak-symbol-lookup.c \ + nautilus-leak-symbol-lookup.h \ + $(NULL) + +# we need to link libbfd and liberty statically +# this is currently broken because of libtool dumbness + +# LIBLEAKCHECK_STATIC_LIBS = "-Wl,-Bstatic -lbfd -liberty -Wl,-Bdynamic" + +libleakcheck_la_LDFLAGS = -module -avoid-version $(LIBLEAKCHECK_STATIC_LIBS) +libleakcheck_la_LIBADD = -ldl + EXTRA_DIST = \ test-nautilus-mime-actions.c \ $(NULL) + +# the link rule is here so we can remove -rpath $(libdir) +# however - +# if we leave -rpath $(libdir) here, libtool refuses to link with -liberty +# if we dont, .libs/libleakcheck.lai doesn't get generated and make install fails + +libleakcheck.la: $(libleakcheck_la_OBJECTS) $(libleakcheck_la_DEPENDENCIES) + $(LINK) -rpath $(libdir) $(libleakcheck_la_LDFLAGS) $(libleakcheck_la_OBJECTS) $(libleakcheck_la_LIBADD) $(LIBS) diff --git a/test/nautilus-leak-checker-stubs.h b/test/nautilus-leak-checker-stubs.h new file mode 100644 index 000000000..8f175dfda --- /dev/null +++ b/test/nautilus-leak-checker-stubs.h @@ -0,0 +1,78 @@ +/* nautilus-leak-checker-stubs.h - simple leak checking library + Virtual File System Library + + Copyright (C) 2000 Eazel + + The Gnome 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. + + The Gnome 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 the Gnome 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. + + Author: Pavel Cisler + based on MemProf by Owen Taylor, +*/ + +#ifndef LEAK_CHECKER_STUBS_H +#define LEAK_CHECKER_STUBS_H + + +extern void *(* real_malloc) (size_t size); +extern void *(* real_memalign) (size_t boundary, size_t size); +extern void *(* real_realloc) (void *ptr, size_t size); +extern void *(* real_calloc) (void *ptr, size_t size); +extern void (*real_free) (void *ptr); + + +void *__libc_malloc (size_t size); +void *__libc_memalign (size_t boundary, size_t size); +void *__libc_calloc (size_t count, size_t size); +void *__libc_realloc (void *ptr, size_t size); +void __libc_free (void *ptr); + +/* Records the context of an allocation. + * We could add pid, allocation time, etc. if needed. + */ +typedef struct { + /* pointer returned by malloc/realloc */ + void *block; + + /* allocated size */ + size_t size; + + /* NULL-terminated array of return addresses */ + void **stack_crawl; +} NautilusLeakAllocationRecord; + +void nautilus_leak_allocation_record_finalize + (NautilusLeakAllocationRecord *record); +void nautilus_leak_allocation_record_free (NautilusLeakAllocationRecord *record); +void nautilus_leak_allocation_record_init (NautilusLeakAllocationRecord *record, + void *block, + size_t initial_size, + void **stack_crawl, + int max_depth); +NautilusLeakAllocationRecord *nautilus_leak_allocation_record_copy + (const NautilusLeakAllocationRecord *record); + +int nautilus_leak_stack_crawl_compare (void **stack_crawl1, + void **stack_crawl2, + int levels); + +/* Hash table entry. */ +struct NautilusHashEntry { + int next; + NautilusLeakAllocationRecord data; +}; + + +#endif \ No newline at end of file diff --git a/test/nautilus-leak-checker.c b/test/nautilus-leak-checker.c new file mode 100644 index 000000000..ffc105607 --- /dev/null +++ b/test/nautilus-leak-checker.c @@ -0,0 +1,693 @@ +/* nautilus-leak-checker.c - simple leak checking library + Virtual File System Library + + Copyright (C) 2000 Eazel + + The Gnome 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. + + The Gnome 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 the Gnome 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. + + Author: Pavel Cisler + based on MemProf by Owen Taylor, +*/ + +#include "nautilus-leak-checker.h" +/* included first, defines following switch*/ +#if LEAK_CHECKER + +#include +#include +#include +#include +#include + +#include "nautilus-leak-checker-stubs.h" +#include "nautilus-leak-hash-table.h" +#include "nautilus-leak-symbol-lookup.h" + +/* this is the maximum number of stack crawl levels we can capture */ +enum { + TRACE_ARRAY_MAX = 512 +}; + +static volatile gboolean nautilus_leak_hooks_initialized; +static volatile gboolean nautilus_leak_initializing_hooks; +static volatile gboolean nautilus_leak_check_leaks; + +void *(* real_malloc) (size_t size); +void *(* real_memalign) (size_t boundary, size_t size); +void *(* real_realloc) (void *ptr, size_t size); +void *(* real_calloc) (void *ptr, size_t size); +void (* real_free) (void *ptr); + +const char *app_path; + +void +nautilus_leak_allocation_record_init (NautilusLeakAllocationRecord *record, void *block, size_t initial_size, + void **stack_crawl, int max_depth) +{ + int stack_depth, index; + + record->block = block; + record->size = initial_size; + + for (index = 0; index < max_depth; index++) { + if (stack_crawl[index] == NULL) + break; + } + + stack_depth = index; + + /* call real_malloc to avoid recursion and messing up results */ + record->stack_crawl = real_malloc ((stack_depth + 1) * sizeof(void *)); + memcpy (record->stack_crawl, stack_crawl, stack_depth * sizeof(void *)); + record->stack_crawl[stack_depth] = NULL; +} + +NautilusLeakAllocationRecord * +nautilus_leak_allocation_record_copy (const NautilusLeakAllocationRecord *record) +{ + int stack_depth, index; + NautilusLeakAllocationRecord *result; + + result = real_malloc (sizeof(*result)); + + result->block = record->block; + result->size = record->size; + + for (index = 0; ; index++) { + if (record->stack_crawl[index] == NULL) + break; + } + stack_depth = index; + result->stack_crawl = real_malloc ((stack_depth + 1) * sizeof(void *)); + memcpy (result->stack_crawl, record->stack_crawl, (stack_depth + 1) * sizeof(void *)); + + return result; +} + +void +nautilus_leak_allocation_record_finalize (NautilusLeakAllocationRecord *record) +{ + /* call real_free to avoid recursion and messing up results */ + real_free (record->stack_crawl); +} + +void +nautilus_leak_allocation_record_free (NautilusLeakAllocationRecord *record) +{ + /* call real_free to avoid recursion and messing up results */ + real_free (record->stack_crawl); + real_free (record); +} + +/* return a strcmp-like result to be used in sort funcitons */ +int +nautilus_leak_stack_crawl_compare (void **stack_crawl1, void **stack_crawl2, int levels) +{ + int index; + for (index = 0; index < levels; index++) { + if (stack_crawl1 [index] == NULL && stack_crawl2 [index] == NULL) { + return 0; + } + + if (stack_crawl1 [index] < stack_crawl2 [index]) { + return -1; + } else if (stack_crawl1 [index] > stack_crawl2 [index]) { + return 1; + } + } + + return 0; +} + +static void +nautilus_leak_initialize (void) +{ + /* Locate the original malloc calls. The dlsym calls + * will allocate memory when doing lookups. We use a special + * trick to deal with the fact that real_malloc is not set up + * yet while we are doing the first dlsym -- see malloc. + */ + real_malloc = dlsym (RTLD_NEXT, "__libc_malloc"); + real_realloc = dlsym (RTLD_NEXT, "__libc_realloc"); + real_free = dlsym (RTLD_NEXT, "__libc_free"); + real_memalign = dlsym (RTLD_NEXT, "__libc_memalign"); + real_calloc = dlsym (RTLD_NEXT, "__libc_calloc"); + + nautilus_leak_hooks_initialized = TRUE; + nautilus_leak_check_leaks = TRUE; +} + +typedef struct StackFrame StackFrame; + +struct StackFrame { + StackFrame *next_frame; + void *return_address; + /* first argument is here */ +}; + +static void +get_stack_trace (void **trace_array, int trace_array_max) +{ + int index; + const StackFrame *stack_frame; + + /* point to the stack frame pointer, two words before the + * address of the first argument + */ + stack_frame = (const StackFrame *)((void **)&trace_array - 2); + + + /* Record stack frames; skip first two in the malloc calls. */ + for (index = -2; index < trace_array_max; index++) { + if (index >= 0) { + /* Return address is the next pointer after + * stack frame. + */ + trace_array [index] = stack_frame->return_address; + } + stack_frame = stack_frame->next_frame; + if (stack_frame == NULL) { + break; + } + } + if (index < trace_array_max) { + trace_array [index] = NULL; + } +} + +/* Figure out if one of the malloc calls is reentering itself. + * There seems no cleaner way to handle this -- we want to override + * malloc calls at the __libc_* level. + * The malloc hooks recurse when initializing themselves. + * When this happens we need a reliable way to tell that a recursion is + * underway so as to not record the corresponding malloc call twice + */ +static gboolean +detect_reentry (void *parent_caller) +{ + int count; + const StackFrame *stack_frame; + + stack_frame = (const StackFrame *)((void **)&parent_caller - 2); + stack_frame = stack_frame->next_frame; + + for (count = 0; count < 7; count++) { + /* See if we return address on the stack + * that is near the parent_caller -- the start address + * of the calling function. + * We are using two arbitrary numbers here - 5 should be a "deep enough" + * check to detect the recursion, 512 bytes should be a large enough distance + * from the start of the malloc call to the point where the old malloc call is + * being called. + */ + if (stack_frame->return_address >= parent_caller + && stack_frame->return_address < (void *)((char *)parent_caller + 0x100)) { + /* printf("detected reentry at level %d\n", count); */ + return TRUE; + } + stack_frame = stack_frame->next_frame; + if (stack_frame == NULL) { + break; + } + } + return FALSE; +} + +static pthread_mutex_t nautilus_leak_hash_table_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; +static NautilusLeakHashTable *hash_table; + +static int nautilus_leak_malloc_count; + +static void +nautilus_leak_record_malloc (void *ptr, size_t size) +{ + void *trace_array [TRACE_ARRAY_MAX]; + NautilusHashEntry *element; + +/* printf("new block %p, %d\n", ptr, size); */ + + if (!nautilus_leak_check_leaks) + return; + + ++nautilus_leak_malloc_count; + + get_stack_trace (trace_array, TRACE_ARRAY_MAX); + + pthread_mutex_lock (&nautilus_leak_hash_table_mutex); + + if (hash_table == NULL) { + hash_table = nautilus_leak_hash_table_new (10 * 1024); + } + if (nautilus_leak_hash_table_find (hash_table, (guint)ptr) != NULL) { + printf("*** block %p appears to already be allocated " + "- someone must have sneaked a free past us\n", ptr); + nautilus_leak_hash_table_remove (hash_table, (guint)ptr); + } + /* insert a new item into the hash table, using the block address as the key */ + element = nautilus_leak_hash_table_add (hash_table, (guint)ptr); + + /* fill out the new allocated element */ + nautilus_leak_allocation_record_init (&element->data, ptr, size, trace_array, TRACE_ARRAY_MAX); + + pthread_mutex_unlock (&nautilus_leak_hash_table_mutex); +} + +static void +nautilus_leak_record_realloc (void *old_ptr, void *new_ptr, size_t size) +{ + void *trace_array [TRACE_ARRAY_MAX]; + NautilusHashEntry *element; + +/* printf("reallocing block %p, %d was %p\n", new_ptr, size, old_ptr); */ + + if (!nautilus_leak_check_leaks) + return; + + get_stack_trace (trace_array, TRACE_ARRAY_MAX); + + pthread_mutex_lock (&nautilus_leak_hash_table_mutex); + + /* must have hash table by now */ + g_assert (hash_table != NULL); + /* must have seen the block already */ + if (nautilus_leak_hash_table_find (hash_table, (guint)old_ptr) == NULL) { + printf("*** we haven't seen block %p yet " + "- someone must have sneaked a malloc past us\n", old_ptr); + } else { + nautilus_leak_hash_table_remove (hash_table, (guint)old_ptr); + } + + /* shouldn't have this block yet */ + if (nautilus_leak_hash_table_find (hash_table, (guint)new_ptr) != NULL) { + printf("*** block %p appears to already be allocated " + "- someone must have sneaked a free past us\n", new_ptr); + nautilus_leak_hash_table_remove (hash_table, (guint)new_ptr); + } + + /* insert a new item into the hash table, using the block address as the key */ + element = nautilus_leak_hash_table_add (hash_table, (guint)new_ptr); + + /* Fill out the new allocated element. + * This way the last call to relloc will be the stack crawl that shows up in the + * final balance. + */ + nautilus_leak_allocation_record_init (&element->data, new_ptr, size, trace_array, TRACE_ARRAY_MAX); + + pthread_mutex_unlock (&nautilus_leak_hash_table_mutex); +} + +static void +nautilus_leak_record_free (void *ptr) +{ +/* printf("freeing block %p\n", ptr); */ + if (!nautilus_leak_check_leaks) + return; + + --nautilus_leak_malloc_count; + + pthread_mutex_lock (&nautilus_leak_hash_table_mutex); + + /* must have hash table by now */ + g_assert (hash_table != NULL); + /* must have seen the block already */ + if (nautilus_leak_hash_table_find (hash_table, (guint)ptr) == NULL) { + printf("*** we haven't seen block %p yet " + "- someone must have sneaked a malloc past us\n", ptr); + } else { + nautilus_leak_hash_table_remove (hash_table, (guint)ptr); + } + pthread_mutex_unlock (&nautilus_leak_hash_table_mutex); + +} + +static void +nautilus_leak_initialize_if_needed (void) +{ + if (nautilus_leak_hooks_initialized) + return; + + if (nautilus_leak_initializing_hooks) + /* guard against reentrancy */ + return; + + nautilus_leak_initializing_hooks = TRUE; + nautilus_leak_initialize (); + nautilus_leak_initializing_hooks = FALSE; +} + +/* we are overlaying the original __libc_malloc */ +enum { + STARTUP_FALLBACK_MEMORY_SIZE = 1024 +}; + +static int startup_fallback_memory_index = 0; +static char startup_fallback_memory [STARTUP_FALLBACK_MEMORY_SIZE]; + +void * +__libc_malloc (size_t size) +{ + void *result; + + nautilus_leak_initialize_if_needed (); + if (real_malloc == NULL) { + /* Our malloc hook is not installed yet - we are being + * called from the initialize_if_needed routine. We + * have to fall back on returning a chunk of static + * memory to carry us through the initialization + */ + + /* align to natural word boundary */ + size = (size + sizeof(void *) - 1 ) & ~(sizeof(void *) - 1); + if (size + startup_fallback_memory_index + > STARTUP_FALLBACK_MEMORY_SIZE) { + g_warning ("trying to allocate to much space during startup"); + return NULL; + } + result = &startup_fallback_memory [startup_fallback_memory_index]; + startup_fallback_memory_index += size; + + return result; + } + result = (*real_malloc) (size); + + if (result != NULL) { + if (detect_reentry(&__libc_malloc)) { + printf("avoiding reentry in __libc_malloc\n"); + } else { + nautilus_leak_record_malloc (result, size); + } + } + + return result; +} + +void * +__libc_memalign (size_t boundary, size_t size) +{ + void *result; + + nautilus_leak_initialize_if_needed (); + result = (*real_memalign) (boundary, size); + + if (result != NULL) { + if (detect_reentry(&__libc_memalign)) { + printf("avoiding reentry in __libc_memalign\n"); + } else { + nautilus_leak_record_malloc (result, size); + } + } + + return result; +} + +/* We are implementing __libc_calloc by calling __libc_malloc and memset + * instead of calling real_calloc for a reason. dlsym calls + * calloc and this way we prevent recursion during initialization. + * If we didn't do this, we would have to teach __libc_calloc to use + * fallback startup memory like __libc_malloc does. + */ +void * +__libc_calloc (size_t count, size_t size) +{ + size_t total; + void *result; + + total = count * size; + nautilus_leak_initialize_if_needed (); + result = __libc_malloc (total); + + if (result != NULL) { + memset (result, 0, total); + } + + return result; +} + +void * +__libc_realloc (void *ptr, size_t size) +{ + void *result; + nautilus_leak_initialize_if_needed (); + result = (*real_realloc) (ptr, size); + + if (detect_reentry(&__libc_realloc)) { + printf("avoiding reentry in __libc_realloc\n"); + } else { + if (result != NULL && ptr == NULL) { + /* we are allocating a new block */ + nautilus_leak_record_malloc (result, size); + } else { + nautilus_leak_record_realloc (ptr, result, size); + } + } + return result; +} + +void +__libc_free (void *ptr) +{ + nautilus_leak_initialize_if_needed (); + + if (ptr > (void *)&startup_fallback_memory[0] + && ptr < (void *)&startup_fallback_memory[STARTUP_FALLBACK_MEMORY_SIZE]) { + /* this is a temporary startup fallback block, don't do anything + * with it + */ + return; + } + if (ptr != NULL) { + nautilus_leak_record_free (ptr); + } + + (real_free) (ptr); +} + +void * +malloc (size_t size) +{ + return __libc_malloc (size); +} + +void * +realloc (void *ptr, size_t size) +{ + return __libc_realloc (ptr, size); +} + +void * +memalign (size_t boundary, size_t size) +{ + return __libc_memalign (boundary, size); +} + +void * +calloc (size_t nmemb, size_t size) +{ + return __libc_calloc (nmemb, size); +} + +void +free (void *ptr) +{ + __libc_free (ptr); +} + +typedef struct { + int max_count; + int counter; + int stack_print_depth; +} PrintOneLeakParams; + + +/* we don't care if printf, etc. allocates (as long as it doesn't leak) + * because by now we have a snapshot of the leaks at the time of + * calling nautilus_leak_print_leaks + */ +static gboolean +print_one_leak (NautilusLeakTableEntry *entry, void *context) +{ + int index; + PrintOneLeakParams *params = (PrintOneLeakParams *)context; + + printf("block %p total_size %d count %d\n", entry->sample_allocation->block, + entry->total_size, entry->count); + + for (index = 0; index < params->stack_print_depth; index++) { + /* only print stack_grouping worth of stack crawl - + * beyond that different blocks may have different addresses + * and we would be printing a lie + */ + if (entry->sample_allocation->stack_crawl[index] == NULL) + break; + printf("\t"); + + nautilus_leak_print_symbol_address (app_path, + entry->sample_allocation->stack_crawl[index]); + } + + /* only print max_counter groups */ + return params->counter++ < params->max_count; +} + +void +nautilus_leak_print_leaks (int stack_grouping_depth, int stack_print_depth, + int max_count, gboolean sort_by_count) +{ + NautilusLeakTable *temp_leak_table; + PrintOneLeakParams each_context; + + pthread_mutex_lock (&nautilus_leak_hash_table_mutex); + /* must have hash table by now */ + g_assert (hash_table != NULL); + + /* Build a leak table by grouping blocks with the same + * stackcrawls (stack_grouping_depth levels considered) + * from the allocated block hash table. + */ + temp_leak_table = nautilus_leak_table_new (hash_table, stack_grouping_depth); + pthread_mutex_unlock (&nautilus_leak_hash_table_mutex); + + printf("%d outstanding allocations ============ \n", nautilus_leak_malloc_count); + printf("stack trace match depth %d\n", stack_grouping_depth); + + /* sort the leak table */ + if (sort_by_count) { + nautilus_leak_table_sort_by_count (temp_leak_table); + } else { + nautilus_leak_table_sort_by_size (temp_leak_table); + } + + /* we have a sorted table of all the leakers, we can print it out. */ + each_context.counter = 0; + each_context.max_count = max_count; + each_context.stack_print_depth = stack_print_depth; + nautilus_leak_table_each_item (temp_leak_table, print_one_leak, &each_context); + + /* we are done with it, free the leak table */ + nautilus_leak_table_free (temp_leak_table); + + /* we are done with it, clean up cached up data used by the symbol lookup */ + nautilus_leak_print_symbol_cleanup (); +} + +void +nautilus_leak_checker_init (const char *path) +{ + /* we should get rid of this and find another way to find our + * binary's name + */ + printf("setting up the leakchecker for %s\n", path); + app_path = path; +} + +#endif + +#ifdef LEAK_CHECK_TESTING +/* normally disabled */ + +static void +allocate_lots(int count) +{ + GList *list; + GList *p; + + list = NULL; + for (; count > 0; count--) { + list = g_list_prepend (list, g_malloc (rand() % 256)); + } + for (p = list; p != NULL; p = p->next) { + g_free (p->data); + } + g_list_free (list); +} + + +static void +leak_mem2(void) +{ + int i; + for (i = 0; i < 40; i++) { + g_strdup("bla"); + } + allocate_lots (128); +} + +static void +leak_mem(void) +{ + int i; + for (i = 0; i < 1010; i++) { + malloc(13); + } + leak_mem2(); + allocate_lots (200); +} + + +int +main (int argc, char **argv) +{ + void *non_leak; + void *leak; + int i; + + nautilus_leak_checker_init (*argv); + + non_leak = g_malloc(100); + leak = g_malloc(200); + g_assert(non_leak != NULL); + non_leak = g_realloc(non_leak, 1000); + g_assert(non_leak != NULL); + non_leak = g_realloc(non_leak, 10000); + leak = g_malloc(200); + non_leak = g_realloc(non_leak, 100000); + leak = g_malloc(200); + g_assert(non_leak != NULL); + g_free(non_leak); + + non_leak = calloc(1, 100); + g_assert(non_leak != NULL); + g_free(non_leak); + leak = g_malloc(200); + + non_leak = memalign(16, 100); + g_assert(non_leak != NULL); + g_free(non_leak); + leak = g_malloc(200); + leak = memalign(16, 100); + leak = memalign(16, 100); + leak = memalign(16, 100); + leak = memalign(16, 100); + leak = memalign(16, 100); + leak = memalign(16, 100); + + for (i = 0; i < 13; i++) { + leak = malloc(13); + } + + leak_mem(); + leak_mem2(); + + for (i = 0; i < 100; i++) { + allocate_lots(rand() % 40); + } + printf("done\n"); + nautilus_leak_print_leaks (6, 12, 20, TRUE); + + return 0; +} + +#endif \ No newline at end of file diff --git a/test/nautilus-leak-checker.h b/test/nautilus-leak-checker.h new file mode 100644 index 000000000..b58a633dd --- /dev/null +++ b/test/nautilus-leak-checker.h @@ -0,0 +1,54 @@ +/* nautilus-leak-checker.h - simple leak checking library + Virtual File System Library + + Copyright (C) 2000 Eazel + + The Gnome 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. + + The Gnome 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 the Gnome 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. + + Author: Pavel Cisler + based on MemProf by Owen Taylor, +*/ + +#ifndef LEAK_CHECKER_H +#define LEAK_CHECKER_H + +#define LEAK_CHECKER 1 + +#if LEAK_CHECKER + +#define __USE_GNU +#define _GNU_SOURCE +/* need this for dlsym */ +#include +#include + +/* This is a leakchecker simpler than MemProf - it tracks all the outstanding + * allocations and allows you print a total when your app quits. It doesn't actually + * try to identify leaks like MemProf does. The entire leakchecker machinery runs + * in the same process as the target app and shares the same heap for it's data + * structures. + */ + +extern void nautilus_leak_checker_init (const char *app_path); + +extern void nautilus_leak_print_leaks (int stack_grouping_depth, + int stack_print_depth, + int max_count, + gboolean sort_by_count); + +#endif + +#endif diff --git a/test/nautilus-leak-hash-table.c b/test/nautilus-leak-hash-table.c new file mode 100644 index 000000000..5f5b5b5a9 --- /dev/null +++ b/test/nautilus-leak-hash-table.c @@ -0,0 +1,511 @@ +/* nautilus-leak-hash-table.c - hash table for a leak checking library + Virtual File System Library + + Copyright (C) 2000 Eazel + + The Gnome 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. + + The Gnome 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 the Gnome 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. + + Author: Pavel Cisler +*/ + +#include "nautilus-leak-checker.h" +/* included first, defines following switch*/ +#if LEAK_CHECKER + +#include +#include +#include + +#include "nautilus-leak-checker-stubs.h" +#include "nautilus-leak-hash-table.h" + +/* We have our own hash table here mainly to allow avoiding calls to + * malloc and realloc that would cause reentry + */ + +static void +nautilus_leak_hash_element_finalize (NautilusHashEntry *element) +{ + nautilus_leak_allocation_record_finalize (&element->data); + memset (element, 0, sizeof(NautilusHashEntry)); +} + +static unsigned long +nautilus_leak_hash_element_hash (NautilusHashEntry *element) +{ + return (unsigned long)element->data.block; +} + +static gboolean +nautilus_leak_hash_element_match (NautilusHashEntry *element, unsigned long key) +{ + return (unsigned long)element->data.block == key; +} + +/* NautilusHashEntries are allocated inside a NautilusHashEntryVector. + * NautilusHashEntryVector keeps a linked list of deleted entries. + */ +typedef struct { + NautilusHashEntry *data; + size_t size; + int next_free; + int next_deleted; +} NautilusHashEntryVector; + +static void +nautilus_leak_hash_element_vector_inititalize (NautilusHashEntryVector *vector, size_t initial_size) +{ + int index; + + vector->data = (NautilusHashEntry *)real_malloc(initial_size * sizeof(NautilusHashEntry)); + if (vector->data == NULL) { + g_warning ("leak checker out of memory"); + abort(); + } + memset (vector->data, 0, initial_size * sizeof(NautilusHashEntry)); + for (index = initial_size - 1; index >= 0; index --) { + vector->data[index].next = -1; + } + + vector->size = initial_size; + vector->next_free = 0; + vector->next_deleted = -1; +} + +static void +nautilus_leak_hash_element_vector_finalize (NautilusHashEntryVector *vector) +{ + int index; + for (index = 0; index < vector->size; index++) { + nautilus_leak_hash_element_finalize (&vector->data[index]); + } + real_free (vector->data); +} + +static NautilusHashEntry * +nautilus_leak_hash_element_vector_at (NautilusHashEntryVector *vector, int index) +{ + return &vector->data[index]; +} + +enum { + HASH_ELEMENT_VECTOR_GROW_CHUNK = 1024 +}; + +static int +nautilus_leak_hash_element_vector_add (NautilusHashEntryVector *vector) +{ + int index; + NautilusHashEntry *new_element; + size_t new_size; + NautilusHashEntry *new_data; + + if (vector->next_deleted >= 0) { + /* Reuse a previously deleted item. */ + index = vector->next_deleted; + vector->next_deleted = nautilus_leak_hash_element_vector_at (vector, index)->next; + } else if (vector->next_free >= vector->size - 1) { + /* We need grow the vector because it cannot fit more entries. */ + new_size = vector->size + HASH_ELEMENT_VECTOR_GROW_CHUNK; + new_data = (NautilusHashEntry *)real_malloc(new_size * sizeof(NautilusHashEntry)); + if (new_data == NULL) { + g_warning ("leak checker out of memory"); + abort(); + } + /* FIXME: only clean the unused part */ + memset (new_data, 0, new_size * sizeof(NautilusHashEntry)); + + /* copy all the existing items over*/ + memcpy (new_data, vector->data, vector->size * sizeof(NautilusHashEntry)); + /* delete the old array */ + real_free (vector->data); + vector->data = new_data; + vector->size = new_size; + index = vector->next_free; + ++vector->next_free; + } else { + /* Just take the next free item. */ + index = vector->next_free; + ++vector->next_free; + } + + /* Initialize the new element to an empty state. */ + new_element = nautilus_leak_hash_element_vector_at (vector, index); + memset (new_element, 0, sizeof(NautilusHashEntry)); + new_element->next = -1; + + return index; +} + +static void +nautilus_leak_hash_element_vector_remove (NautilusHashEntryVector *vector, int index) +{ + /* free the data */ + nautilus_leak_hash_element_finalize (&vector->data[index]); + + /* insert item as first into deleted item list */ + nautilus_leak_hash_element_vector_at (vector, index)->next = vector->next_deleted; + vector->next_deleted = index; +} + +struct NautilusLeakHashTable { + size_t array_size; + int *hash_array; + NautilusHashEntryVector element_vector; +}; + +/* These primes are close to 2^n numbers for optimal hashing performance + * and near-2^n size. + */ +long nautilus_leak_hash_table_primes [] = { + 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521, 131071, 262139, + 524287, 1048573, 2097143, 4194301, 8388593, 16777213, 33554393, 67108859, + 134217689, 268435399, 536870909, 1073741789, 2147483647, 0 +}; + + +static size_t +nautilus_leak_hash_table_optimal_size (size_t size) +{ + int index; + for (index = 0; ; index++) { + if (!nautilus_leak_hash_table_primes [index] || nautilus_leak_hash_table_primes [index] >= size) { + return nautilus_leak_hash_table_primes [index]; + } + } + + return 0; +} + +static void +nautilus_leak_hash_table_initialize (NautilusLeakHashTable *table, size_t initial_size) +{ + /* calculate the size of the bucket array */ + table->array_size = nautilus_leak_hash_table_optimal_size (initial_size); + + /* allocate the element array */ + nautilus_leak_hash_element_vector_inititalize (&table->element_vector, table->array_size * 5); + + /* allocate the bucket array */ + table->hash_array = (int *)real_malloc (table->array_size * sizeof(int)); + + /* initialize the to empty state */ + memset (table->hash_array, -1, table->array_size * sizeof(int)); +} + +static void +nautilus_leak_hash_table_finalize (NautilusLeakHashTable *table) +{ + nautilus_leak_hash_element_vector_finalize (&table->element_vector); + real_free (table->hash_array); +} + +NautilusLeakHashTable * +nautilus_leak_hash_table_new (size_t initial_size) +{ + NautilusLeakHashTable *new_table; + new_table = real_malloc (sizeof(NautilusLeakHashTable)); + nautilus_leak_hash_table_initialize (new_table, initial_size); + + return new_table; +} + +void +nautilus_leak_hash_table_free (NautilusLeakHashTable *hash_table) +{ + nautilus_leak_hash_table_finalize (hash_table); + real_free (hash_table); +} + +static unsigned long +nautilus_leak_hash_table_hash (NautilusLeakHashTable *table, unsigned long seed) +{ + return (seed >> 2) % table->array_size; +} + +NautilusHashEntry * +nautilus_leak_hash_table_find (NautilusLeakHashTable *table, unsigned long key) +{ + int index; + NautilusHashEntry *result; + + for (index = table->hash_array [nautilus_leak_hash_table_hash (table, key)]; index >= 0;) { + result = nautilus_leak_hash_element_vector_at (&table->element_vector, index); + if (nautilus_leak_hash_element_match (result, key)) { + return result; + } + index = result->next; + } + + return NULL; +} + +NautilusHashEntry * +nautilus_leak_hash_table_add (NautilusLeakHashTable *table, unsigned long key) +{ + int new_index; + NautilusHashEntry *result; + unsigned long hash; + + /* calculate the index of the bucket */ + hash = nautilus_leak_hash_table_hash (table, key); + + /* allocate space for new item in element vector */ + new_index = nautilus_leak_hash_element_vector_add (&table->element_vector); + result = nautilus_leak_hash_element_vector_at (&table->element_vector, new_index); + + /* insert new item first in the list for bucket */ + result->next = table->hash_array[hash]; + table->hash_array[hash] = new_index; + + return result; +} + +static void +nautilus_leak_hash_table_remove_element (NautilusLeakHashTable *table, NautilusHashEntry *element) +{ + unsigned long hash; + int next; + int index; + NautilusHashEntry *tmp_element; + + /* find the bucket */ + hash = nautilus_leak_hash_table_hash (table, nautilus_leak_hash_element_hash (element)); + next = table->hash_array[hash]; + + g_assert (next >= 0); + + /* try to match bucket list head */ + if (nautilus_leak_hash_element_vector_at (&table->element_vector, next) == element) { + table->hash_array[hash] = element->next; + nautilus_leak_hash_element_vector_remove (&table->element_vector, next); + return; + } + + for (index = next; index >= 0; ) { + /* look for an existing match in table */ + next = nautilus_leak_hash_element_vector_at (&table->element_vector, index)->next; + if (next < 0) { + g_assert (!"should not be here"); + return; + } + + tmp_element = nautilus_leak_hash_element_vector_at (&table->element_vector, index); + if (nautilus_leak_hash_element_vector_at (&table->element_vector, next) == element) { + nautilus_leak_hash_element_vector_at (&table->element_vector, index)->next = element->next; + nautilus_leak_hash_element_vector_remove (&table->element_vector, next); + return; + } + index = next; + } +} + +gboolean +nautilus_leak_hash_table_remove (NautilusLeakHashTable *table, unsigned long key) +{ + NautilusHashEntry *element; + + element = nautilus_leak_hash_table_find (table, key); + if (element != NULL) { + /* FIXME: this could be faster if we just found the element + * here and deleted it. + */ + nautilus_leak_hash_table_remove_element (table, element); + + return TRUE; + } + return FALSE; +} + +struct NautilusLeakTable { + size_t size; + NautilusLeakTableEntry *data; +}; + +static NautilusLeakTableEntry * +nautilus_leak_table_new_entry_at (NautilusLeakTable *table, int index) +{ + /* Allocate a new slot. Avoid using real_realloc here because + * it ends up calling our version of __libc_malloc and messes up + * the leak table + */ + NautilusLeakTableEntry *new_table = (NautilusLeakTableEntry *) real_malloc + ((table->size + 1) * sizeof (NautilusLeakTableEntry)); + + if (new_table == NULL) { + g_warning ("Ran out of memory while allocating leak checker structures"); + abort (); + } + + /* finish what realloc would have done if we could call it */ + memcpy (new_table, table->data, (table->size) * sizeof (NautilusLeakTableEntry)); + real_free (table->data); + table->data = new_table; + + /* move the items over by one to make room for new item */ + if (index < table->size) { + memmove (&table->data[index + 1], + &table->data[index], + (table->size - index) * sizeof (NautilusLeakTableEntry)); + } + + table->size++; + + return &table->data[index]; +} + +static void +nautilus_leak_table_add_entry (NautilusLeakTable *table, NautilusHashEntry *entry, int stack_grouping_depth) +{ + int r, l; + int resulting_index; + int compare_result; + + /* do a binary lookup of the item */ + r = table->size - 1; + resulting_index = 0; + compare_result = 0; + + for (l = 0; l <= r; ) { + resulting_index = (l + r) / 2; + + compare_result = nautilus_leak_stack_crawl_compare ( + table->data[resulting_index].sample_allocation->stack_crawl, + entry->data.stack_crawl, + stack_grouping_depth); + + if (compare_result > 0) { + r = resulting_index - 1; + } else if (compare_result < 0) { + l = resulting_index + 1; + } else { + break; + } + } + + if (compare_result < 0) { + resulting_index++; + } + + if (compare_result == 0 && resulting_index < table->size) { + /* we already have a match, just bump up the count and size */ + table->data[resulting_index].count++; + table->data[resulting_index].total_size += entry->data.size; + return; + } + + nautilus_leak_table_new_entry_at (table, resulting_index); + table->data[resulting_index].count = 1; + table->data[resulting_index].total_size = entry->data.size; + table->data[resulting_index].sample_allocation = nautilus_leak_allocation_record_copy (&entry->data); +} + +NautilusLeakTable * +nautilus_leak_table_new (NautilusLeakHashTable *hash_table, int stack_grouping_depth) +{ + NautilusLeakTable *result; + NautilusHashEntry *nautilus_leak_hash_table_entry; + int index; + + result = real_malloc (sizeof(NautilusLeakTable)); + result->size = 0; + result->data = NULL; + + for (index = 0; index < hash_table->element_vector.size; index++) { + /* traverse the hash table element vector */ + nautilus_leak_hash_table_entry = nautilus_leak_hash_element_vector_at (&hash_table->element_vector, index); + if (nautilus_leak_hash_table_entry->data.stack_crawl != NULL) { + nautilus_leak_table_add_entry (result, nautilus_leak_hash_table_entry, stack_grouping_depth); + } + } + + return result; +} + +void +nautilus_leak_table_free (NautilusLeakTable *leak_table) +{ + int index; + if (leak_table != NULL) { + for (index = 0; index < leak_table->size; index++) { + nautilus_leak_allocation_record_free(leak_table->data[index].sample_allocation); + } + real_free (leak_table->data); + } + + real_free (leak_table); +} + +static int +sort_by_count (const void *entry1, const void *entry2) +{ + int result; + + result = ((NautilusLeakTableEntry *)entry2)->count + - ((NautilusLeakTableEntry *)entry1)->count; + + if (result == 0) { + /* match, secondary sort order by size */ + return ((NautilusLeakTableEntry *)entry2)->total_size + - ((NautilusLeakTableEntry *)entry1)->total_size; + } + return result; +} + +void +nautilus_leak_table_sort_by_count (NautilusLeakTable *leak_table) +{ + qsort (leak_table->data, leak_table->size, + sizeof(NautilusLeakTableEntry), sort_by_count); +} + +static int +sort_by_size (const void *entry1, const void *entry2) +{ + int result; + + result = ((NautilusLeakTableEntry *)entry2)->total_size + - ((NautilusLeakTableEntry *)entry1)->total_size; + + if (result == 0) { + /* match, secondary sort order by count */ + return ((NautilusLeakTableEntry *)entry2)->count + - ((NautilusLeakTableEntry *)entry1)->count; + } + return result; +} + +void +nautilus_leak_table_sort_by_size (NautilusLeakTable *leak_table) +{ + qsort (leak_table->data, leak_table->size, + sizeof (NautilusLeakTableEntry), sort_by_size); +} + +void +nautilus_leak_table_each_item (NautilusLeakTable *leak_table, NautilusEachLeakTableFunction function, + void *context) +{ + int index; + for (index = 0; index < leak_table->size; index++) { + if (!function (&leak_table->data[index], context)) { + /* break early */ + return; + } + } +} + +#endif \ No newline at end of file diff --git a/test/nautilus-leak-hash-table.h b/test/nautilus-leak-hash-table.h new file mode 100644 index 000000000..08e295417 --- /dev/null +++ b/test/nautilus-leak-hash-table.h @@ -0,0 +1,57 @@ +/* nautilus-leak-hash-table.h - hash table for a leak checking library + Virtual File System Library + + Copyright (C) 2000 Eazel + + The Gnome 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. + + The Gnome 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 the Gnome 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. + + Author: Pavel Cisler +*/ + +#ifndef LEAK_HASH_TABLE__ +#define LEAK_HASH_TABLE__ + +typedef struct NautilusLeakHashTable NautilusLeakHashTable; +typedef struct NautilusHashEntry NautilusHashEntry; + +NautilusLeakHashTable *nautilus_leak_hash_table_new (size_t initial_size); +void nautilus_leak_hash_table_free (NautilusLeakHashTable *hash_table); +gboolean nautilus_leak_hash_table_remove (NautilusLeakHashTable *table, + unsigned long key); +NautilusHashEntry *nautilus_leak_hash_table_add (NautilusLeakHashTable *table, + unsigned long key); +NautilusHashEntry *nautilus_leak_hash_table_find (NautilusLeakHashTable *table, + unsigned long key); + + +typedef struct { + int count; + size_t total_size; + NautilusLeakAllocationRecord *sample_allocation; +} NautilusLeakTableEntry; + +typedef struct NautilusLeakTable NautilusLeakTable; +typedef gboolean (* NautilusEachLeakTableFunction) (NautilusLeakTableEntry *entry, void *context); + +NautilusLeakTable *nautilus_leak_table_new (NautilusLeakHashTable *hash_table, + int stack_grouping_depth); +void nautilus_leak_table_free (NautilusLeakTable *leak_table); +void nautilus_leak_table_sort_by_count (NautilusLeakTable *leak_table); +void nautilus_leak_table_sort_by_size (NautilusLeakTable *leak_table); +void nautilus_leak_table_each_item (NautilusLeakTable *leak_table, + NautilusEachLeakTableFunction function, + void *context); +#endif \ No newline at end of file diff --git a/test/nautilus-leak-symbol-lookup.c b/test/nautilus-leak-symbol-lookup.c new file mode 100644 index 000000000..41f00d888 --- /dev/null +++ b/test/nautilus-leak-symbol-lookup.c @@ -0,0 +1,223 @@ +/* nautilus-leak-symbol-lookup.c - symbol lookup for a leak checking library + Virtual File System Library + + Copyright (C) 2000 Eazel + + The Gnome 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. + + The Gnome 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 the Gnome 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. + + Author: Pavel Cisler + based on MemProf by Owen Taylor, +*/ + +#define _GNU_SOURCE + /* need this for dladdr */ + +#include "nautilus-leak-symbol-lookup.h" + +#include +#include +#include +#include +#include +#include +#include + + +static GList *symbol_table_list; + +typedef struct { + char *path; + bfd *abfd; + asymbol **symbol_table; + asection *text_section; +} NautilusLeakSymbolLookupMap; + +static gboolean +nautilus_leak_find_symbol_in_map (const NautilusLeakSymbolLookupMap *map, + unsigned long address, char **function_name, char **source_file_name, + unsigned int *line, unsigned long offset, const char *known_function_name) +{ + const char *file; + const char *function; + + address -= offset; + address -= map->text_section->vma; + + if (address < 0 || address > map->text_section->_cooked_size) { + /* not a valid address range for this binary */ + return FALSE; + } + + if (!bfd_find_nearest_line (map->abfd, map->text_section, map->symbol_table, + address, + &file, &function, line)) { + printf ("error looking up address in binary %s\n", map->path); + return FALSE; + } + + if (known_function_name != NULL && + strcmp (function, known_function_name) != 0) { + return FALSE; + } + *function_name = g_strdup (function); + *source_file_name = g_strdup (file); + + return TRUE; +} + +static NautilusLeakSymbolLookupMap * +nautilus_leak_symbol_map_load (const char *binary_path) +{ + NautilusLeakSymbolLookupMap *map; + char *target = NULL; + size_t storage_needed; + int number_of_symbols; + + map = g_new0 (NautilusLeakSymbolLookupMap, 1); + + map->abfd = bfd_openr (binary_path, target); + + if (map->abfd == NULL) { + fprintf (stderr, "%s: ", binary_path); + bfd_perror (binary_path); + return NULL; + } + + if (!bfd_check_format (map->abfd, bfd_object)) { + fprintf (stderr, "%s is not an object file\n", binary_path); + bfd_close (map->abfd); + return NULL; + } + + /* Use the ".text" section. */ + map->text_section = bfd_get_section_by_name (map->abfd, ".text"); + + /* Read the symbol table. */ + storage_needed = bfd_get_symtab_upper_bound (map->abfd); + if (storage_needed == 0) { + fprintf (stderr, "no symbols\n"); + bfd_close (map->abfd); + return NULL; + } + map->symbol_table = (asymbol **)g_malloc (storage_needed); + if (map->symbol_table == NULL) { + fprintf (stderr, "no memory allocating symbol table\n"); + bfd_close (map->abfd); + return NULL; + } + number_of_symbols = bfd_canonicalize_symtab (map->abfd, map->symbol_table); + map->path = g_strdup (binary_path); + + symbol_table_list = g_list_append (symbol_table_list, map); + + return map; +} + +static NautilusLeakSymbolLookupMap * +nautilus_leak_symbol_map_load_if_needed (const char *binary_path) +{ + GList *p; + NautilusLeakSymbolLookupMap *map; + + for (p = symbol_table_list; p != NULL; p = p->next) { + map = p->data; + if (strcmp (map->path, binary_path) == 0) + /* no need to load the symbols, already got the map */ + return map; + } + return nautilus_leak_symbol_map_load (binary_path); +} + +void +nautilus_leak_print_symbol_cleanup (void) +{ + /* free the cached symbol tables */ + GList *p; + NautilusLeakSymbolLookupMap *map; + + for (p = symbol_table_list; p != NULL; p = p->next) { + map = p->data; + bfd_close (map->abfd); + g_free (map->symbol_table); + g_free (map->path); + + g_free (map); + } + + g_list_free (symbol_table_list); + symbol_table_list = NULL; +} + +static gboolean +nautilus_leak_find_symbol_address (void *address, char **function_name, char **source_file_name, + int *line) +{ + GList *p; + NautilusLeakSymbolLookupMap *map; + Dl_info info; + + if (dladdr (address, &info) != 0) { + /* We know the function name and the binary it lives in, now try to find + * the function and the offset. + */ + map = nautilus_leak_symbol_map_load_if_needed (info.dli_fname); + if (map != NULL + && nautilus_leak_find_symbol_in_map (map, (long)address, + function_name, source_file_name, line, + (unsigned long)info.dli_fbase, info.dli_sname)) { + return TRUE; + } + /* just return the function name and the library binary path */ + *function_name = g_strdup (info.dli_sname); + *source_file_name = g_strdup (info.dli_fname); + *line = -1; + return TRUE; + } else { + /* Usually dladdr will succeed, it seems to only fail for + * address lookups for functions in the main binary. + */ + for (p = symbol_table_list; p != NULL; p = p->next) { + map = p->data; + if (nautilus_leak_find_symbol_in_map (map, (long)address, function_name, + source_file_name, (unsigned int *)line, 0, NULL)) + return TRUE; + } + } + + return FALSE; +} + +void +nautilus_leak_print_symbol_address (const char *app_path, void *address) +{ + char *function_name; + char *source_file_name; + int line; + + nautilus_leak_symbol_map_load_if_needed (app_path); + + if (nautilus_leak_find_symbol_address (address, &function_name, &source_file_name, &line)) { + if (line >= 0) { + printf("%p %s %s:%d\n", address, function_name, source_file_name, line); + } else { + printf("%p %s in library %s\n", address, function_name, source_file_name); + } + g_free (function_name); + g_free (source_file_name); + } else { + printf("%p (unknown function)\n", address); + } +} diff --git a/test/nautilus-leak-symbol-lookup.h b/test/nautilus-leak-symbol-lookup.h new file mode 100644 index 000000000..c56f00a1c --- /dev/null +++ b/test/nautilus-leak-symbol-lookup.h @@ -0,0 +1,31 @@ +/* nautilus-leak-symbol-lookup.h - symbol lookup for a leak checking library + Virtual File System Library + + Copyright (C) 2000 Eazel + + The Gnome 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. + + The Gnome 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 the Gnome 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. + + Author: Pavel Cisler + based on MemProf by Owen Taylor, +*/ + +#ifndef SYMBOL_LOOKUP_H +#define SYMBOL_LOOKUP_ + +void nautilus_leak_print_symbol_address (const char *app_path, void *address); +void nautilus_leak_print_symbol_cleanup (void); + +#endif -- cgit v1.2.1