/* * gobject-list: a LD_PRELOAD library for tracking the lifetime of GObjects * * Copyright (C) 2011 Collabora Ltd. * Copyright (C) 2011 Morten Welinder * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA * * Authors: * Danielle Madeley * Morten Welinder */ /* Minimal Makefile: CFLAGS=`pkg-config --cflags glib-2.0` LIBS=`pkg-config --libs glib-2.0` libgobject-list.so: gobject-list.c gcc -fPIC -rdynamic -g -c -Wall ${CFLAGS} $< gcc -shared -Wl,-soname,$@ -o $@ gobject-list.o -lc -ldl ${LIBS} */ /* We need this to get RTLD_NEXT. */ #define _GNU_SOURCE 1 #include #include #include #include #include #include #include #ifdef HAVE_LIBUNWIND #define UNW_LOCAL_ONLY #include #endif G_LOCK_DEFINE_STATIC (objects_lock); static GHashTable *objects = NULL; static guint display_flags; static GString *qstr; static GObject * (*real_g_object_constructor) (GType type, guint n_construct_properties, GObjectConstructParam *construct_params); static void (*real_g_object_finalize) (GObject *object); static gpointer (*real_g_object_ref) (gpointer); static gpointer (*real_g_object_ref_sink) (gpointer); static void (*real_g_object_unref) (gpointer); static gpointer (*real_gdk_window_get_user_data) (gpointer, gpointer *); static gpointer (*real_gdk_display_manager_get) (void); static void (*real_gdk_flush) (void); static GSList * (*real_gdk_display_manager_list_displays) (gpointer); static void (*real_gdk_display_close) (gpointer); static gboolean seen_gdk_window = FALSE; static GType (*real_gdk_window_object_get_type) (void); static const char * (*real_gtk_label_get_text) (gpointer); static const char * (*real_gtk_printer_get_name) (gpointer); static gpointer (*real_gtk_widget_get_parent) (gpointer); static const char * (*real_gtk_widget_get_name) (gpointer); static GSList * (*real_gtk_size_group_get_widgets) (gpointer); static GType (*real_gtk_action_group_get_type) (void); static GType (*real_gtk_action_get_type) (void); static GType (*real_gtk_label_get_type) (void); static GType (*real_gtk_printer_get_type) (void); static GType (*real_gtk_printer_option_get_type) (void); static GType (*real_gtk_table_get_type) (void); static GType (*real_gtk_widget_get_type) (void); static GType (*real_gtk_window_get_type) (void); static GType (*real_gtk_size_group_get_type) (void); static GType (*real_gtk_tree_model_get_type) (void); static gint (*real_gtk_tree_model_get_n_columns) (gpointer); static GType (*real_gtk_tree_model_get_column_type) (gpointer, int); static GType (*real_pango_font_family_get_type) (void); static const char * (*real_pango_font_family_get_name) (gpointer); static void initialize (void); typedef enum { DISPLAY_FLAG_NONE = 0, DISPLAY_FLAG_CREATE = 1, DISPLAY_FLAG_REFS = 1 << 2, DISPLAY_FLAG_BACKTRACE = 1 << 3, DISPLAY_FLAG_ALL = DISPLAY_FLAG_CREATE | DISPLAY_FLAG_REFS | DISPLAY_FLAG_BACKTRACE, DISPLAY_FLAG_DEFAULT = DISPLAY_FLAG_CREATE, } DisplayFlags; static gboolean display_filter (DisplayFlags flags) { return (display_flags & flags) != 0; } static const char * quoted_string (const char *s) { char *es; if (!s) return "(null)"; es = g_strescape (s, NULL); g_string_truncate (qstr, 0); g_string_append_c (qstr, '\"'); g_string_append (qstr, es); g_string_append_c (qstr, '\"'); g_free (es); return qstr->str; } static const char * fake_gtk_printer_option_get_name (gpointer obj) { /* We have no properties or getter function, so... */ struct _GtkPrinterOption { GObject parent_instance; char *name; char *display_text; enum { OINK } type; char *value; int num_choices; char **choices; char **choices_display; gboolean activates_default; gboolean has_conflict; char *group; }; return ((struct _GtkPrinterOption *)obj)->name; } static gboolean object_filter (const char *obj_name) { static const char *filter; static gboolean inited = FALSE; if (!inited) { inited = TRUE; filter = g_getenv ("GOBJECT_LIST_FILTER"); } if (filter == NULL) return TRUE; else return (strncmp (filter, obj_name, strlen (filter)) == 0); } static void print_trace (void) { #ifdef HAVE_LIBUNWIND unw_context_t uc; unw_cursor_t cursor; guint stack_num = 0; if (!display_filter (DISPLAY_FLAG_BACKTRACE)) return; unw_getcontext (&uc); unw_init_local (&cursor, &uc); while (unw_step (&cursor) > 0) { gchar name[65]; unw_word_t off; if (unw_get_proc_name (&cursor, name, sizeof (name) - 1, &off) < 0) { g_printerr ("Error getting proc name\n"); break; } g_printerr ("#%d %s + [0x%08x]\n", stack_num++, name, (unsigned int)off); } #endif } static void dump_object_list (void) { GList *keys, *l; /* * We get all the keys at once so we can release the lock right away. * Holding the lock across the loop feels risky. */ G_LOCK (objects_lock); keys = g_hash_table_get_keys (objects); G_UNLOCK (objects_lock); for (l = keys; l; l = l->next) { GObject *obj = l->data; g_printerr (" - %p, %s: refs=%u", obj, G_OBJECT_TYPE_NAME (obj), obj->ref_count); if (real_gtk_action_group_get_type && G_TYPE_CHECK_INSTANCE_TYPE (obj, real_gtk_action_group_get_type ())) { gchar *name = NULL; g_object_get (obj, "name", &name, NULL); if (name) g_printerr (" name=%s", quoted_string (name)); g_free (name); } if (real_gtk_action_get_type && G_TYPE_CHECK_INSTANCE_TYPE (obj, real_gtk_action_get_type ())) { gchar *name = NULL; g_object_get (obj, "name", &name, NULL); if (name) g_printerr (" name=%s", quoted_string (name)); g_free (name); } if (real_gtk_label_get_type && G_TYPE_CHECK_INSTANCE_TYPE (obj, real_gtk_label_get_type ())) { const char *label = real_gtk_label_get_text (obj); g_printerr (" label=%s", quoted_string (label)); } if (real_gtk_printer_get_type && G_TYPE_CHECK_INSTANCE_TYPE (obj, real_gtk_printer_get_type ())) { const char *name = real_gtk_printer_get_name (obj); g_printerr (" name=%s", name); } if (real_gtk_printer_option_get_type && G_TYPE_CHECK_INSTANCE_TYPE (obj, real_gtk_printer_option_get_type ())) { const char *name = fake_gtk_printer_option_get_name (obj); if (name) g_printerr (" name=%s", quoted_string (name)); } if (real_gtk_table_get_type && G_TYPE_CHECK_INSTANCE_TYPE (obj, real_gtk_table_get_type ())) { int rows, cols; g_object_get (obj, "n-rows", &rows, "n-columns", &cols, NULL); g_printerr (" size=%dc%dr", cols, rows); } if (real_gtk_widget_get_type && G_TYPE_CHECK_INSTANCE_TYPE (obj, real_gtk_widget_get_type ())) { GObject *parent = real_gtk_widget_get_parent (obj); const char *name = real_gtk_widget_get_name (obj); if (parent) g_printerr (" parent=%p", parent); if (name && *name) g_printerr (" name=%s", quoted_string (name)); } if (real_gtk_window_get_type && G_TYPE_CHECK_INSTANCE_TYPE (obj, real_gtk_window_get_type ())) { gchar *title = NULL; g_object_get (obj, "title", &title, NULL); if (title) g_printerr (" title=%s", quoted_string (title)); g_free (title); } if (real_gtk_size_group_get_type && G_TYPE_CHECK_INSTANCE_TYPE (obj, real_gtk_size_group_get_type ())) { GSList *l = real_gtk_size_group_get_widgets (obj); if (l) { g_printerr (" widgets=["); while (l) { g_printerr ("%p", l->data); l = l->next; if (l) g_printerr (","); } g_printerr ("]"); } } if (real_gtk_tree_model_get_type && G_TYPE_CHECK_INSTANCE_TYPE (obj, real_gtk_tree_model_get_type ())) { int i, n = real_gtk_tree_model_get_n_columns (obj); g_printerr (" cols=["); for (i = 0; i < n; i++) { GType t = real_gtk_tree_model_get_column_type (obj, i); if (i) g_printerr (","); g_printerr ("%s", g_type_name (t)); } g_printerr ("]"); } if (real_gdk_window_object_get_type && G_TYPE_CHECK_INSTANCE_TYPE (obj, real_gdk_window_object_get_type ())) { gpointer user; real_gdk_window_get_user_data (obj, &user); if (user) g_printerr (" user=%p", user); } if (G_IS_TYPE_MODULE (obj)) { const char *name = G_TYPE_MODULE (obj)->name; if (name) g_printerr (" name=%s", quoted_string (name)); } if (G_IS_FILE_INFO (obj)) { const char *name = g_file_info_get_name (G_FILE_INFO (obj)); if (name) g_printerr (" name=%s", quoted_string (name)); } if (G_IS_FILE (obj)) { char *uri = g_file_get_uri (G_FILE (obj)); g_printerr (" uri=%s", quoted_string (uri)); g_free (uri); } if (real_pango_font_family_get_type && G_TYPE_CHECK_INSTANCE_TYPE (obj, real_pango_font_family_get_type ())) { const char *name = real_pango_font_family_get_name (obj); if (name) g_printerr (" name=%s", quoted_string (name)); } g_printerr ("\n"); } g_list_free (keys); } static void sig_usr1_handler (int signal) { g_printerr ("\nLiving Objects:\n"); dump_object_list (); } static void exiting (void) { if (seen_gdk_window) { GSList *displays; real_gdk_flush (); while (g_main_context_iteration (NULL, FALSE)) ;/* nothing */ displays = real_gdk_display_manager_list_displays (real_gdk_display_manager_get ()); g_slist_foreach (displays, (GFunc)real_gdk_display_close, NULL); g_slist_free (displays); } g_printerr ("\nStill Alive:\n"); dump_object_list (); } static void * get_func (const char *func_name) { return dlsym (RTLD_NEXT, func_name); } static GObject* hook_g_object_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_params) { GObject *obj = real_g_object_constructor (type, n_construct_properties, construct_params); const char *obj_name; if (!obj) return NULL; obj_name = G_OBJECT_TYPE_NAME (obj); if (object_filter (obj_name)) { guint ref_count = obj->ref_count; g_printerr (" ++ Created object %p, %s ref_count=%d\n", obj, obj_name, ref_count); G_LOCK (objects_lock); g_hash_table_insert (objects, obj, GUINT_TO_POINTER (TRUE)); G_UNLOCK (objects_lock); print_trace (); } return obj; } static void hook_g_object_finalize (GObject *obj) { char *typ = g_strdup (G_OBJECT_TYPE_NAME (obj)); G_LOCK (objects_lock); g_hash_table_remove (objects, obj); G_UNLOCK (objects_lock); real_g_object_finalize (obj); if (object_filter (typ)) { g_printerr (" -- Finalized object %p, %s\n", obj, typ); print_trace (); } g_free (typ); } /* A copy of gobject's function in order to ensure we catch the ref. */ static void g_value_object_copy_value (const GValue *src_value, GValue *dest_value) { if (src_value->data[0].v_pointer) dest_value->data[0].v_pointer = g_object_ref (src_value->data[0].v_pointer); else dest_value->data[0].v_pointer = NULL; } /* A copy of gobject's function in order to ensure we catch the unref. */ static void g_value_object_free_value (GValue *value) { if (value->data[0].v_pointer) g_object_unref (value->data[0].v_pointer); } /* A copy of gobject's function in order to ensure we catch the ref. */ static gchar* g_value_object_collect_value (GValue *value, guint n_collect_values, GTypeCValue *collect_values, guint collect_flags) { if (collect_values[0].v_pointer) { GObject *object = collect_values[0].v_pointer; if (object->g_type_instance.g_class == NULL) return g_strconcat ("invalid unclassed object pointer for value type `", G_VALUE_TYPE_NAME (value), "'", NULL); else if (!g_value_type_compatible (G_OBJECT_TYPE (object), G_VALUE_TYPE (value))) return g_strconcat ("invalid object type `", G_OBJECT_TYPE_NAME (object), "' for value type `", G_VALUE_TYPE_NAME (value), "'", NULL); /* never honour G_VALUE_NOCOPY_CONTENTS for ref-counted types */ value->data[0].v_pointer = g_object_ref (object); } else value->data[0].v_pointer = NULL; return NULL; } /* A copy of gobject's function in order to ensure we catch the ref. */ static gchar * g_value_object_lcopy_value (const GValue *value, guint n_collect_values, GTypeCValue *collect_values, guint collect_flags) { GObject **object_p = collect_values[0].v_pointer; if (!object_p) return g_strdup_printf ("value location for `%s' passed as NULL", G_VALUE_TYPE_NAME (value)); if (!value->data[0].v_pointer) *object_p = NULL; else if (collect_flags & G_VALUE_NOCOPY_CONTENTS) *object_p = value->data[0].v_pointer; else *object_p = g_object_ref (value->data[0].v_pointer); return NULL; } GType gdk_window_object_get_type (void) { seen_gdk_window = TRUE; return real_gdk_window_object_get_type (); } void g_type_init (void) { initialize (); } static void initialize (void) { if (G_LIKELY (objects != NULL)) return; /* Note: we are not in a sane state until we call the real g_type_init(). */ { void (*real_g_type_init) (void) = get_func ("g_type_init"); real_g_type_init (); } { const char *s = g_getenv ("GOBJECT_LIST_DISPLAY"); GDebugKey keys[] = { { "none", DISPLAY_FLAG_NONE }, { "create", DISPLAY_FLAG_CREATE }, { "refs", DISPLAY_FLAG_REFS }, { "backtrace", DISPLAY_FLAG_BACKTRACE }, { "all", DISPLAY_FLAG_ALL }, }; display_flags = s ? g_parse_debug_string (s, keys, G_N_ELEMENTS (keys)) : DISPLAY_FLAG_DEFAULT; } /* set up signal handler */ signal (SIGUSR1, sig_usr1_handler); /* set up objects map */ objects = g_hash_table_new (NULL, NULL); qstr = g_string_new (NULL); /* Prime things while we're still single-threaded. */ object_filter ("oink"); /* Set up exit handler */ atexit (exiting); { GObjectClass *klass = g_type_class_ref (G_TYPE_OBJECT); GTypeValueTable *vt = g_type_value_table_peek (G_TYPE_OBJECT); /* Take over GObject::constructor */ real_g_object_constructor = klass->constructor; klass->constructor = hook_g_object_constructor; /* Take over GObject::finalize */ real_g_object_finalize = klass->finalize; klass->finalize = hook_g_object_finalize; /* Take over value operations that change ref or unref. */ vt->value_free = g_value_object_free_value; vt->value_copy = g_value_object_copy_value; vt->collect_value = g_value_object_collect_value; vt->lcopy_value = g_value_object_lcopy_value; g_type_class_unref (klass); } real_g_object_ref = get_func ("g_object_ref"); real_g_object_ref_sink = get_func ("g_object_ref_sink"); real_g_object_unref = get_func ("g_object_unref"); real_gdk_window_object_get_type = get_func ("gdk_window_object_get_type"); real_gdk_window_get_user_data = get_func ("gdk_window_get_user_data"); real_gdk_display_manager_get = get_func ("gdk_display_manager_get"); real_gdk_display_manager_list_displays = get_func ("gdk_display_manager_list_displays"); real_gdk_display_close = get_func ("gdk_display_close"); real_gdk_flush = get_func ("gdk_flush"); real_gtk_action_get_type = get_func ("gtk_action_get_type"); real_gtk_action_group_get_type = get_func ("gtk_action_group_get_type"); real_gtk_label_get_type = get_func ("gtk_label_get_type"); real_gtk_printer_get_type = get_func ("gtk_printer_get_type"); real_gtk_printer_option_get_type = get_func ("gtk_printer_option_get_type"); real_gtk_table_get_type = get_func ("gtk_table_get_type"); real_gtk_widget_get_type = get_func ("gtk_widget_get_type"); real_gtk_window_get_type = get_func ("gtk_window_get_type"); real_gtk_size_group_get_type = get_func ("gtk_size_group_get_type"); real_gtk_tree_model_get_type = get_func ("gtk_tree_model_get_type"); real_gtk_tree_model_get_n_columns = get_func ("gtk_tree_model_get_n_columns"); real_gtk_tree_model_get_column_type = get_func ("gtk_tree_model_get_column_type"); real_gtk_label_get_text = get_func ("gtk_label_get_text"); real_gtk_printer_get_name = get_func ("gtk_printer_get_name"); real_gtk_widget_get_parent = get_func ("gtk_widget_get_parent"); real_gtk_widget_get_name = get_func ("gtk_widget_get_name"); real_gtk_size_group_get_widgets = get_func ("gtk_size_group_get_widgets"); real_pango_font_family_get_type = get_func ("pango_font_family_get_type"); real_pango_font_family_get_name = get_func ("pango_font_family_get_name"); } gpointer g_object_ref (gpointer object) { GObject *obj = G_OBJECT (object); const char *obj_name; guint ref_count; GObject *ret; obj_name = G_OBJECT_TYPE_NAME (obj); ref_count = obj->ref_count; ret = real_g_object_ref (object); if (object_filter (obj_name) && display_filter (DISPLAY_FLAG_REFS)) { g_printerr (" + Reffed object %p, %s; ref_count: %d -> %d\n", obj, obj_name, ref_count, obj->ref_count); print_trace (); } return ret; } gpointer g_object_ref_sink (gpointer object) { GObject *obj = G_OBJECT (object); const char *obj_name; guint ref_count; gboolean was_floating; GObject *ret; obj_name = G_OBJECT_TYPE_NAME (obj); ref_count = obj->ref_count; was_floating = g_object_is_floating (obj); ret = real_g_object_ref_sink (object); if (object_filter (obj_name) && display_filter (DISPLAY_FLAG_REFS)) { g_printerr (" + Reffed %sobject %p, %s; ref_count: %d -> %d\n", was_floating ? "and sank " : "", obj, obj_name, ref_count, obj->ref_count); print_trace (); } return ret; } void g_object_unref (gpointer object) { GObject *obj = G_OBJECT (object); const char *obj_name; obj_name = G_OBJECT_TYPE_NAME (obj); if (object_filter (obj_name) && display_filter (DISPLAY_FLAG_REFS)) { g_printerr (" - Unreffed object %p, %s; ref_count: %d -> %d\n", obj, obj_name, obj->ref_count, obj->ref_count - 1); print_trace (); } real_g_object_unref (object); }