[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:
Rolf Bjarne Kvinge 2021-05-25 08:19:27 +02:00 коммит произвёл GitHub
Родитель 295533c26f
Коммит 5a0600491c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 209 добавлений и 0 удалений

Просмотреть файл

@ -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)));