[runtime] Add support for tracking created and destroyed MonoObject* instances for CoreCLR. (#11657)
* [runtime] Add support for tracking created and destroyed MonoObject* instances for CoreCLR. Implement a rudimentary way of tracking created and destroyed MonoObject* instances, so that it's easy to find leaks. * Add a xamarin_bridge_shutdown method that's called just before returning from xamarin_main. And use it to dump the leaked MonoObject*s.
This commit is contained in:
Родитель
295533c26f
Коммит
5a0600491c
|
@ -11,6 +11,7 @@
|
|||
#include <inttypes.h>
|
||||
|
||||
#include "product.h"
|
||||
#include "runtime-internal.h"
|
||||
#include "xamarin/xamarin.h"
|
||||
#include "xamarin/coreclr-bridge.h"
|
||||
|
||||
|
@ -19,6 +20,132 @@
|
|||
unsigned int coreclr_domainId = 0;
|
||||
void *coreclr_handle = NULL;
|
||||
|
||||
#if defined (TRACK_MONOOBJECTS)
|
||||
|
||||
// To enable tracking of MonoObject* instances, uncomment the TRACK_MONOOBJECTS define in:
|
||||
// * runtime/runtime-internal.h
|
||||
// * src/ObjCRuntime/Runtime.CoreCLR.cs
|
||||
// Both defines must be uncommented for tracking to work. Once enabled, you can opt-in to
|
||||
// capturing the stack trace of when the MonoObject* was created, by setting the
|
||||
// MONOOBJECT_TRACKING_WITH_STACKTRACES environment variable.
|
||||
|
||||
#include <execinfo.h>
|
||||
#include <pthread.h>
|
||||
|
||||
static int _Atomic monoobject_created = 0;
|
||||
static int _Atomic monoobject_destroyed = 0;
|
||||
static CFMutableDictionaryRef monoobject_dict = NULL;
|
||||
static pthread_mutex_t monoobject_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
struct monoobject_tracked_entry {
|
||||
char *managed;
|
||||
void *addresses [128];
|
||||
int frames;
|
||||
char *native;
|
||||
};
|
||||
|
||||
static char *
|
||||
get_stacktrace (void **addresses, int frames)
|
||||
{
|
||||
// get the symbols for the addresses
|
||||
char** strs = backtrace_symbols (addresses, frames);
|
||||
|
||||
// compute the total length of all the symbols, adding 1 for every line (for the newline)
|
||||
size_t length = 0;
|
||||
int i;
|
||||
for (i = 0; i < frames; i++)
|
||||
length += strlen (strs [i]) + 1;
|
||||
length++;
|
||||
|
||||
// format the symbols as one long string with newlines
|
||||
char *rv = (char *) calloc (1, length);
|
||||
char *buffer = rv;
|
||||
size_t left = length;
|
||||
for (i = 0; i < frames; i++) {
|
||||
snprintf (buffer, left, "%s\n", strs [i]);
|
||||
size_t slen = strlen (strs [i]) + 1;
|
||||
left -= slen;
|
||||
buffer += slen;
|
||||
}
|
||||
free (strs);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
void
|
||||
xamarin_bridge_log_monoobject (MonoObject *mobj, const char *stacktrace)
|
||||
{
|
||||
// add stack traces if we have them / they've been been requested
|
||||
if (monoobject_dict != NULL) {
|
||||
// create a new entry
|
||||
struct monoobject_tracked_entry *value = (struct monoobject_tracked_entry *) calloc (1, sizeof (struct monoobject_tracked_entry));
|
||||
|
||||
value->managed = stacktrace ? xamarin_strdup_printf ("%s", stacktrace) : NULL;
|
||||
value->frames = backtrace ((void **) &value->addresses, sizeof (value->addresses) / sizeof (&value->addresses [0]));
|
||||
|
||||
// insert into our dictionary of monoobjects
|
||||
pthread_mutex_lock (&monoobject_lock);
|
||||
CFDictionarySetValue (monoobject_dict, mobj, value);
|
||||
pthread_mutex_unlock (&monoobject_lock);
|
||||
}
|
||||
|
||||
atomic_fetch_add (&monoobject_created, 1);
|
||||
}
|
||||
|
||||
void
|
||||
xamarin_bridge_dump_monoobjects ()
|
||||
{
|
||||
if (monoobject_dict != NULL) {
|
||||
// dump the monoobject's that haven't been freed (max 10 entries).
|
||||
pthread_mutex_lock (&monoobject_lock);
|
||||
|
||||
// get the keys and values
|
||||
unsigned int length = (unsigned int) CFDictionaryGetCount (monoobject_dict);
|
||||
MonoObject** keys = (MonoObject **) calloc (1, sizeof (void*) * length);
|
||||
char** values = (char **) calloc (1, sizeof (char*) * length);
|
||||
CFDictionaryGetKeysAndValues (monoobject_dict, (const void **) keys, (const void **) values);
|
||||
|
||||
// is there anything left in the dictionary? if so, show that
|
||||
unsigned int items_to_show = length > 10 ? 10 : length;
|
||||
if (items_to_show > 0) {
|
||||
fprintf (stderr, "⚠️ There were %i MonoObjects created, %i MonoObjects freed, so %i were not freed.\n", (int) monoobject_created, (int) monoobject_destroyed, (int) (monoobject_created - monoobject_destroyed));
|
||||
fprintf (stderr, "Showing the first %i (of %i) MonoObjects:\n", items_to_show, length);
|
||||
for (unsigned int i = 0; i < items_to_show; i++) {
|
||||
MonoObject *obj = keys [i];
|
||||
struct monoobject_tracked_entry *value = (struct monoobject_tracked_entry *) values [i];
|
||||
fprintf (stderr, "Object %i/%i %p RC: %i\n", i + 1, (int) length, obj, (int) obj->reference_count);
|
||||
if (value->managed && *value->managed)
|
||||
fprintf (stderr, "\tManaged stack trace:\n%s\n", value->managed);
|
||||
if (value->native == NULL && value->frames > 0)
|
||||
value->native = get_stacktrace (value->addresses, value->frames);
|
||||
if (value->native && *value->native)
|
||||
fprintf (stderr, "\tNative stack trace:\n%s\n", value->native);
|
||||
}
|
||||
fprintf (stderr, "⚠️ There were %i MonoObjects created, %i MonoObjects freed, so %i were not freed.\n", (int) monoobject_created, (int) monoobject_destroyed, (int) (monoobject_created - monoobject_destroyed));
|
||||
} else {
|
||||
fprintf (stderr, "✅ There were %i MonoObjects created, %i MonoObjects freed, so no leaked MonoObjects.\n", (int) monoobject_created, (int) monoobject_destroyed);
|
||||
}
|
||||
pthread_mutex_unlock (&monoobject_lock);
|
||||
|
||||
free (keys);
|
||||
free (values);
|
||||
} else {
|
||||
fprintf (stderr, "There were %i MonoObjects created, %i MonoObjects freed, so %i were not freed.\n", (int) monoobject_created, (int) monoobject_destroyed, (int) (monoobject_created - monoobject_destroyed));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
monoobject_dict_free_value (CFAllocatorRef allocator, const void *value)
|
||||
{
|
||||
struct monoobject_tracked_entry* v = (struct monoobject_tracked_entry *) value;
|
||||
xamarin_free (v->managed);
|
||||
if (v->native)
|
||||
free (v->native);
|
||||
free (v);
|
||||
}
|
||||
|
||||
#endif // defined (TRACK_MONOOBJECTS)
|
||||
|
||||
void
|
||||
xamarin_bridge_setup ()
|
||||
{
|
||||
|
@ -27,6 +154,26 @@ xamarin_bridge_setup ()
|
|||
void
|
||||
xamarin_bridge_initialize ()
|
||||
{
|
||||
#if defined (TRACK_MONOOBJECTS)
|
||||
// Only capture the stack trace if requested explicitly, it has a very significant perf hit (monotouch-test is 3x slower).
|
||||
const char *with_stacktraces = getenv ("MONOOBJECT_TRACKING_WITH_STACKTRACES");
|
||||
if (with_stacktraces && *with_stacktraces) {
|
||||
// Create a dictionary to store the stack traces
|
||||
CFDictionaryValueCallBacks value_callbacks = { 0 };
|
||||
value_callbacks.release = monoobject_dict_free_value;
|
||||
monoobject_dict = CFDictionaryCreateMutable (kCFAllocatorDefault, 0, NULL, &value_callbacks);
|
||||
|
||||
fprintf (stderr, "Stack traces enabled for MonoObject tracking.\n");
|
||||
}
|
||||
#endif // defined (TRACK_MONOOBJECTS)
|
||||
}
|
||||
|
||||
void
|
||||
xamarin_bridge_shutdown ()
|
||||
{
|
||||
#if defined (TRACK_MONOOBJECTS)
|
||||
xamarin_bridge_dump_monoobjects ();
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -158,6 +305,15 @@ xamarin_mono_object_release (MonoObject **mobj_ref)
|
|||
xamarin_free (mobj->struct_value); // allocated using Marshal.AllocHGlobal.
|
||||
|
||||
xamarin_free (mobj); // allocated using Marshal.AllocHGlobal.
|
||||
|
||||
#if defined (TRACK_MONOOBJECTS)
|
||||
if (monoobject_dict != NULL) {
|
||||
pthread_mutex_lock (&monoobject_lock);
|
||||
CFDictionaryRemoveValue (monoobject_dict, mobj);
|
||||
pthread_mutex_unlock (&monoobject_lock);
|
||||
}
|
||||
atomic_fetch_add (&monoobject_destroyed, 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
*mobj_ref = NULL;
|
||||
|
|
|
@ -484,5 +484,7 @@ xamarin_main (int argc, char *argv[], enum XamarinLaunchMode launch_mode)
|
|||
|
||||
xamarin_mono_object_release (&assembly);
|
||||
|
||||
xamarin_bridge_shutdown ();
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
|
|
@ -89,6 +89,11 @@ xamarin_bridge_initialize ()
|
|||
mono_install_assembly_preload_hook (xamarin_assembly_preload_hook, NULL);
|
||||
DEBUG_LAUNCH_TIME_PRINT ("\tJIT init time");
|
||||
}
|
||||
|
||||
void
|
||||
xamarin_bridge_shutdown ()
|
||||
{
|
||||
}
|
||||
#endif // !LEGACY_XAMARIN_MAC
|
||||
|
||||
static MonoClass *
|
||||
|
@ -417,6 +422,19 @@ xamarin_mono_object_retain (MonoObject *mobj)
|
|||
// Nothing to do here
|
||||
}
|
||||
|
||||
#if defined (TRACK_MONOOBJECTS)
|
||||
// This function is needed for the corresponding managed P/Invoke to not make
|
||||
// the native linker fail due to an unresolved symbol. This method should
|
||||
// never end up being called (it'll be linked away by the native linker if the
|
||||
// managed linker removes the P/Invoke, and never called from managed code
|
||||
// otherwise).
|
||||
void
|
||||
xamarin_bridge_log_monoobject (MonoObject *mobj, const char *stacktrace)
|
||||
{
|
||||
xamarin_assertion_message ("%s is not available on MonoVM", __func__);
|
||||
}
|
||||
#endif // defined (TRACK_MONOOBJECTS)
|
||||
|
||||
#endif // DOTNET
|
||||
|
||||
/*
|
||||
|
|
|
@ -25,6 +25,13 @@
|
|||
#define DEBUG_LAUNCH_TIME_PRINT(...)
|
||||
#endif
|
||||
|
||||
// Uncomment the TRACK_MONOOBJECTS define to show a summary at process exit of
|
||||
// the MonoObjects that were created, and if any were not freed. If there are
|
||||
// leaked MonoObjects, a list of them will be printed.
|
||||
// This has an equivalent variable in src/ObjCRuntime/Runtime.CoreCLR.cs,
|
||||
// which must be set for tracking to work.
|
||||
//#define TRACK_MONOOBJECTS
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
|
|
@ -201,6 +201,7 @@ uint32_t xamarin_find_protocol_wrapper_type (uint32_t token_ref);
|
|||
void xamarin_release_block_on_main_thread (void *obj);
|
||||
void xamarin_bridge_setup (); // this is called very early, before parsing the command line arguments
|
||||
void xamarin_bridge_initialize (); // this is called a bit later, after parsing the command line arguments.
|
||||
void xamarin_bridge_shutdown (); // this is called just before returning from xamarin_main
|
||||
unsigned char * xamarin_load_aot_data (MonoAssembly *assembly, int size, gpointer user_data, void **out_handle);
|
||||
void xamarin_free_aot_data (MonoAssembly *assembly, int size, gpointer user_data, void *handle);
|
||||
MonoAssembly* xamarin_assembly_preload_hook (MonoAssemblyName *aname, char **assemblies_path, void* user_data);
|
||||
|
@ -291,6 +292,7 @@ typedef id (*xamarin_get_handle_func) (MonoObject *info);
|
|||
MonoToggleRefStatus xamarin_gc_toggleref_callback (uint8_t flags, id handle, xamarin_get_handle_func get_handle, MonoObject *info);
|
||||
void xamarin_gc_event (MonoGCEvent event);
|
||||
|
||||
void xamarin_bridge_log_monoobject (MonoObject *obj, const char *stacktrace);
|
||||
/*
|
||||
* In MonoVM MonoObjects are tracked in memory/the stack directly by the GC, but that doesn't
|
||||
* work for CoreCLR, so we make it ref-counted. All code must use the functions below to retain/release
|
||||
|
|
|
@ -6,6 +6,14 @@
|
|||
//
|
||||
// Copyright 2021 Microsoft Corp.
|
||||
|
||||
|
||||
// Uncomment the TRACK_MONOOBJECTS define to show a summary at process exit of
|
||||
// the MonoObjects that were created, and if any were not freed. If there are
|
||||
// leaked MonoObjects, a list of them will be printed.
|
||||
// This has an equivalent variable in runtime/runtime-internal.m
|
||||
// which must be set for tracking to work.
|
||||
//#define TRACK_MONOOBJECTS
|
||||
|
||||
#if NET && !COREBUILD
|
||||
|
||||
using System;
|
||||
|
@ -62,6 +70,10 @@ namespace ObjCRuntime {
|
|||
public MonoObject* Parameters;
|
||||
}
|
||||
|
||||
#if TRACK_MONOOBJECTS
|
||||
static bool? track_monoobject_with_stacktraces;
|
||||
#endif
|
||||
|
||||
// Comment out the attribute to get all printfs
|
||||
[System.Diagnostics.Conditional ("UNDEFINED")]
|
||||
static void log_coreclr (string message)
|
||||
|
@ -259,6 +271,13 @@ namespace ObjCRuntime {
|
|||
|
||||
log_coreclr ($"GetMonoObjectImpl ({obj.GetType ()}) => 0x{rv.ToString ("x")} => GCHandle=0x{handle.ToString ("x")}");
|
||||
|
||||
#if TRACK_MONOOBJECTS
|
||||
// Only capture the stack trace if requested explicitly, it has a very significant perf hit (monotouch-test is 3x slower).
|
||||
if (!track_monoobject_with_stacktraces.HasValue)
|
||||
track_monoobject_with_stacktraces = !string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("MONOOBJECT_TRACKING_WITH_STACKTRACES"));
|
||||
xamarin_bridge_log_monoobject (rv, track_monoobject_with_stacktraces.Value ? Environment.StackTrace : null);
|
||||
#endif
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
@ -278,6 +297,11 @@ namespace ObjCRuntime {
|
|||
}
|
||||
}
|
||||
|
||||
#if TRACK_MONOOBJECTS
|
||||
[DllImport ("__Internal", CharSet = CharSet.Auto)]
|
||||
static extern void xamarin_bridge_log_monoobject (IntPtr mono_object, string stack_trace);
|
||||
#endif
|
||||
|
||||
static IntPtr MarshalStructure<T> (T value) where T: struct
|
||||
{
|
||||
var rv = Marshal.AllocHGlobal (Marshal.SizeOf (typeof (T)));
|
||||
|
|
Загрузка…
Ссылка в новой задаче