Created
April 29, 2017 20:32
-
-
Save alanmcgovern/e0e43fe1365acc5169f34b8c4d29b4cd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <glib-object.h> | |
#include <dlfcn.h> | |
#include <signal.h> | |
#include <string.h> | |
#include <stdlib.h> | |
#include <execinfo.h> | |
#define BACKTRACE_DEPTH 50 | |
typedef enum { | |
EVENT_CREATED = 0, | |
EVENT_REF = 1, | |
EVENT_UNREF = 2, | |
} EventType; | |
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 | DISPLAY_FLAG_BACKTRACE, | |
} DisplayFlags; | |
typedef struct { | |
const gchar *name; | |
DisplayFlags flag; | |
} DisplayFlagsMapItem; | |
typedef struct { | |
GHashTable *objects; /* owned */ | |
} ObjectData; | |
typedef struct { | |
EventType type; | |
void **backtrace; | |
int symbols; | |
int old_ref_count; | |
int new_ref_count; | |
} Event; | |
extern char *mono_pmip (void *ip); | |
/* GObject overrides */ | |
gpointer g_object_new (GType type, const char *first, ...); | |
gpointer g_object_ref (gpointer object); | |
void g_object_unref (gpointer object); | |
static void g_object_finalized (G_GNUC_UNUSED gpointer data, GObject *obj); | |
/* Functions to deal with tracking ref/unref/creation events */ | |
Event * event_new (EventType type); | |
Event * event_created_new (); | |
Event * event_ref_new (int old_ref_count, int new_ref_count); | |
Event * event_unref_new (int old_ref_count, int new_ref_count); | |
void event_destroy (Event *event); | |
/* Helper functions to print out our data */ | |
static void print_trace (void **array, int symbols); | |
static void print_event (gpointer data, gpointer user_data); | |
DisplayFlagsMapItem display_flags_map[] = | |
{ | |
{ "none", DISPLAY_FLAG_NONE }, | |
{ "create", DISPLAY_FLAG_CREATE }, | |
{ "refs", DISPLAY_FLAG_REFS }, | |
{ "backtrace", DISPLAY_FLAG_BACKTRACE }, | |
{ "all", DISPLAY_FLAG_ALL }, | |
}; | |
Event * | |
event_new (EventType type) | |
{ | |
Event *event; | |
event = calloc (1, sizeof (Event)); | |
event->type = type; | |
event->backtrace = malloc (sizeof (void*) * BACKTRACE_DEPTH); | |
event->symbols = backtrace (event->backtrace, BACKTRACE_DEPTH); | |
return event; | |
} | |
Event * | |
event_created_new () | |
{ | |
return event_new (EVENT_CREATED); | |
} | |
Event * | |
event_ref_new (int old_ref_count, int new_ref_count) | |
{ | |
Event *event; | |
event = event_new (EVENT_REF); | |
event->old_ref_count = old_ref_count; | |
event->new_ref_count = new_ref_count; | |
return event; | |
} | |
Event * | |
event_unref_new (int old_ref_count, int new_ref_count) | |
{ | |
Event *event; | |
event = event_new (EVENT_UNREF); | |
event->old_ref_count = old_ref_count; | |
event->new_ref_count = new_ref_count; | |
return event; | |
} | |
void | |
event_destroy (Event *event) | |
{ | |
free (event->backtrace); | |
free (event); | |
} | |
static void | |
print_event (gpointer data, G_GNUC_UNUSED gpointer user_data) | |
{ | |
g_print ("\n"); | |
Event *event = (Event *)data; | |
if (event->type == EVENT_CREATED) | |
g_print ("\tCreated\n"); | |
else if (event->type == EVENT_REF) | |
g_print ("\tRef %d -> %d\n", event->old_ref_count, event->new_ref_count); | |
else if (event->type == EVENT_UNREF) | |
g_print ("\tUnref %d -> %d\n", event->old_ref_count, event->new_ref_count); | |
else | |
g_print ("\tUnknown event...\n"); | |
print_trace (event->backtrace, event->symbols); | |
} | |
static void | |
print_trace (void **array, int symbols) | |
{ | |
char **names; | |
char *managed_frame; | |
int i; | |
names = backtrace_symbols (array, symbols); | |
for (i = 1; i < symbols; ++i) { | |
if ((managed_frame = mono_pmip (array [i])) != NULL) | |
g_print ("\t%d %s\n", i, managed_frame); | |
else | |
g_print ("\t%s\n", names [i]); | |
} | |
} | |
/* Global static state, which must be accessed with the @gobject_list mutex | |
* held. */ | |
static volatile ObjectData gobject_list_state = { NULL, }; | |
/* Global lock protecting access to @gobject_list_state, since GObject methods | |
* may be called from multiple threads concurrently. */ | |
G_LOCK_DEFINE_STATIC (gobject_list); | |
static gboolean | |
object_filter (const char *obj_name) | |
{ | |
const char *filter = "GdkWindow";//g_getenv ("GOBJECT_LIST_FILTER"); | |
if (filter == NULL) | |
return TRUE; | |
else | |
return (strncmp (filter, obj_name, strlen (filter)) == 0); | |
} | |
static void | |
_dump_object_list (GHashTable *hash) | |
{ | |
GHashTableIter iter; | |
GObject *obj; | |
GPtrArray *events; | |
g_hash_table_iter_init (&iter, hash); | |
while (g_hash_table_iter_next (&iter, (gpointer) &obj, (gpointer)&events)) | |
{ | |
/* FIXME: Not really sure how we get to this state. */ | |
if (obj == NULL || obj->ref_count == 0 || obj->ref_count > 10) | |
continue; | |
g_print (" - %p, %s (refcount: %d):\n", obj, G_OBJECT_TYPE_NAME (obj), obj->ref_count); | |
g_ptr_array_foreach (events, print_event, NULL); | |
} | |
g_print ("%u objects\n", g_hash_table_size (hash)); | |
} | |
static void | |
print_still_alive (void) | |
{ | |
g_print ("\nStill Alive:\n"); | |
G_LOCK (gobject_list); | |
_dump_object_list (gobject_list_state.objects); | |
G_UNLOCK (gobject_list); | |
} | |
static void | |
_sig_usr1_handler (G_GNUC_UNUSED int signal) | |
{ | |
print_still_alive (); | |
} | |
static void | |
_exiting (void) | |
{ | |
print_still_alive (); | |
} | |
static void * | |
get_func (const char *func_name) | |
{ | |
static void *handle = NULL; | |
void *func; | |
char *error; | |
G_LOCK (gobject_list); | |
if (G_UNLIKELY (g_once_init_enter (&handle))) | |
{ | |
void *_handle; | |
_handle = dlopen("/Library/Frameworks/Mono.framework/Versions/5.2.0/lib/libgobject-2.0.0.dylib", RTLD_LAZY); | |
if (_handle == NULL) | |
g_error ("Failed to open libgobject-2.0.0.dylib: %s", dlerror ()); | |
/* set up objects map */ | |
gobject_list_state.objects = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_ptr_array_unref); | |
/* Set up exit handler */ | |
atexit (_exiting); | |
/* Prevent propagation to child processes. */ | |
if (g_getenv ("GOBJECT_PROPAGATE_LD_PRELOAD") == NULL) | |
{ | |
g_unsetenv ("LD_PRELOAD"); | |
g_unsetenv ("DYLD_INSERT_LIBRARIES"); | |
} | |
g_once_init_leave (&handle, _handle); | |
} | |
func = dlsym (handle, func_name); | |
if ((error = dlerror ()) != NULL) | |
g_error ("Failed to find symbol: %s", error); | |
G_UNLOCK (gobject_list); | |
return func; | |
} | |
gpointer | |
g_object_new (GType type, | |
const char *first, | |
...) | |
{ | |
static gpointer (* real_g_object_new_valist) (GType, const char *, va_list) = NULL; | |
va_list var_args; | |
GObject *obj; | |
const char *obj_name; | |
if (!real_g_object_new_valist) | |
real_g_object_new_valist = get_func ("g_object_new_valist"); | |
va_start (var_args, first); | |
obj = real_g_object_new_valist (type, first, var_args); | |
va_end (var_args); | |
obj_name = G_OBJECT_TYPE_NAME (obj); | |
G_LOCK (gobject_list); | |
if (object_filter (obj_name)) | |
{ | |
g_object_weak_ref (obj, g_object_finalized, NULL); | |
Event *event = event_created_new (); | |
GPtrArray *array = g_ptr_array_new_with_free_func ((GDestroyNotify) event_destroy); | |
g_ptr_array_add (array, event); | |
g_hash_table_insert (gobject_list_state.objects, obj, array); | |
} | |
G_UNLOCK (gobject_list); | |
return obj; | |
} | |
gpointer | |
g_object_ref (gpointer object) | |
{ | |
static gpointer (* real_g_object_ref) (gpointer) = NULL; | |
GObject *obj = G_OBJECT (object); | |
guint ref_count; | |
GObject *ret; | |
if (!real_g_object_ref) | |
real_g_object_ref = get_func ("g_object_ref"); | |
ref_count = obj->ref_count; | |
ret = real_g_object_ref (object); | |
G_LOCK (gobject_list); | |
GPtrArray *events = (GPtrArray *) g_hash_table_lookup (gobject_list_state.objects, object); | |
if (events) | |
g_ptr_array_add (events, event_ref_new (ref_count, obj->ref_count)); | |
G_UNLOCK (gobject_list); | |
return ret; | |
} | |
void | |
g_object_unref (gpointer object) | |
{ | |
static void (* real_g_object_unref) (gpointer) = NULL; | |
GObject *obj = G_OBJECT (object); | |
if (!real_g_object_unref) | |
real_g_object_unref = get_func ("g_object_unref"); | |
G_LOCK (gobject_list); | |
GPtrArray *events = (GPtrArray *) g_hash_table_lookup (gobject_list_state.objects, object); | |
if (events) | |
g_ptr_array_add (events, event_unref_new (obj->ref_count, obj->ref_count - 1)); | |
G_UNLOCK (gobject_list); | |
real_g_object_unref (object); | |
} | |
static void | |
g_object_finalized (G_GNUC_UNUSED gpointer data, GObject *obj) | |
{ | |
G_LOCK (gobject_list); | |
g_hash_table_remove (gobject_list_state.objects, obj); | |
G_UNLOCK (gobject_list); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment