diff options
27 files changed, 1000 insertions, 366 deletions
diff --git a/configure.ac b/configure.ac index 92458681819..d0e1e5253e0 100644 --- a/configure.ac +++ b/configure.ac @@ -46,7 +46,7 @@ MONO_VERSION_BUILD=`echo $VERSION | cut -d . -f 3` # There is no ordering of corlib versions, no old or new, # the runtime expects an exact match. # -MONO_CORLIB_VERSION=E06DCE2D-7852-4BBA-AD9F-54D67EEF1FF9 +MONO_CORLIB_VERSION=5F2CEB7B-13B1-4694-A0D5-3DD7EDE81388 # # Put a quoted #define in config.h. @@ -2425,7 +2425,7 @@ if test x$host_win32 = xno; then AC_CHECK_HEADERS(pthread.h) AC_CHECK_HEADERS(pthread_np.h) AC_CHECK_FUNCS(pthread_mutex_timedlock) - AC_CHECK_FUNCS(pthread_getattr_np pthread_attr_get_np pthread_setname_np pthread_cond_timedwait_relative_np) + AC_CHECK_FUNCS(pthread_getattr_np pthread_attr_get_np pthread_getname_np pthread_setname_np pthread_cond_timedwait_relative_np) AC_CHECK_FUNCS(pthread_kill) AC_MSG_CHECKING(for PTHREAD_MUTEX_RECURSIVE) AC_TRY_COMPILE([ #include <pthread.h>], [ @@ -5766,7 +5766,7 @@ echo " zlib: $zlib_msg BTLS: $enable_btls$btls_platform_string jemalloc: $with_jemalloc (always use: $with_jemalloc_always) - crash reporting: $crash_reporting + crash reporting: $crash_reporting (private crashes: $with_crash_privacy) $disabled " if test x$with_static_mono = xno -a "x$host_win32" != "xyes"; then diff --git a/mcs/class/corlib/Mono/Runtime.cs b/mcs/class/corlib/Mono/Runtime.cs index 133e824eb38..34d0669088f 100644 --- a/mcs/class/corlib/Mono/Runtime.cs +++ b/mcs/class/corlib/Mono/Runtime.cs @@ -106,17 +106,17 @@ namespace Mono { static extern void SendMicrosoftTelemetry_internal (IntPtr payload, ulong portable_hash, ulong unportable_hash); [MethodImplAttribute (MethodImplOptions.InternalCall)] - static extern void WriteStateToDisk_internal (IntPtr payload, ulong portable_hash, ulong unportable_hash); + static extern void WriteStateToFile_internal (IntPtr payload, ulong portable_hash, ulong unportable_hash); static void - WriteStateToDisk (Exception exc) + WriteStateToFile (Exception exc) { ulong portable_hash; ulong unportable_hash; string payload_str = ExceptionToState_internal (exc, out portable_hash, out unportable_hash); using (var payload_chars = RuntimeMarshal.MarshalString (payload_str)) { - WriteStateToDisk_internal (payload_chars.Value, portable_hash, unportable_hash); + WriteStateToFile_internal (payload_chars.Value, portable_hash, unportable_hash); } } @@ -165,5 +165,43 @@ namespace Mono { } #endif + [MethodImplAttribute (MethodImplOptions.InternalCall)] + static extern string DumpStateSingle_internal (out ulong portable_hash, out ulong unportable_hash); + + [MethodImplAttribute (MethodImplOptions.InternalCall)] + static extern string DumpStateTotal_internal (out ulong portable_hash, out ulong unportable_hash); + + static Tuple<String, ulong, ulong> + DumpStateSingle () + { + ulong portable_hash; + ulong unportable_hash; + string payload_str = DumpStateSingle_internal (out portable_hash, out unportable_hash); + + return new Tuple<String, ulong, ulong> (payload_str, portable_hash, unportable_hash); + } + + static Tuple<String, ulong, ulong> + DumpStateTotal () + { + ulong portable_hash; + ulong unportable_hash; + string payload_str = DumpStateTotal_internal (out portable_hash, out unportable_hash); + + return new Tuple<String, ulong, ulong> (payload_str, portable_hash, unportable_hash); + } + + [MethodImplAttribute (MethodImplOptions.InternalCall)] + static extern void RegisterReportingForNativeLib_internal (IntPtr modulePathSuffix, IntPtr moduleName); + + static void RegisterReportingForNativeLib (string modulePathSuffix_str, string moduleName_str) + { + using (var modulePathSuffix_chars = RuntimeMarshal.MarshalString (modulePathSuffix_str)) + using (var moduleName_chars = RuntimeMarshal.MarshalString (moduleName_str)) + { + RegisterReportingForNativeLib_internal (modulePathSuffix_chars.Value, moduleName_chars.Value); + } + } + } } diff --git a/mcs/class/corlib/Test/System/ExceptionTest.cs b/mcs/class/corlib/Test/System/ExceptionTest.cs index 3563d9a4033..97123e9ab25 100644 --- a/mcs/class/corlib/Test/System/ExceptionTest.cs +++ b/mcs/class/corlib/Test/System/ExceptionTest.cs @@ -17,6 +17,8 @@ using System.Reflection; using NUnit.Framework;
+using System.Threading.Tasks;
+
namespace MonoTests.System
{
[TestFixture]
@@ -473,6 +475,102 @@ namespace MonoTests.System // }
}
}
+
+ void DumpSingle ()
+ {
+ var monoType = Type.GetType ("Mono.Runtime", false);
+ var convert = monoType.GetMethod("DumpStateSingle", BindingFlags.NonPublic | BindingFlags.Static);
+ var output = (Tuple<String, ulong, ulong>) convert.Invoke(null, Array.Empty<object> ());
+
+ var dump = output.Item1;
+ var portable_hash = output.Item2;
+ var unportable_hash = output.Item3;
+
+ Assert.IsTrue (portable_hash != 0, "#1");
+ Assert.IsTrue (unportable_hash != 0, "#2");
+ Assert.IsTrue (dump.Length > 0, "#3");
+ }
+
+ void DumpTotal ()
+ {
+ var monoType = Type.GetType ("Mono.Runtime", false);
+ var convert = monoType.GetMethod("DumpStateTotal", BindingFlags.NonPublic | BindingFlags.Static);
+ var output = (Tuple<String, ulong, ulong>) convert.Invoke(null, Array.Empty<object> ());
+
+ var dump = output.Item1;
+ var portable_hash = output.Item2;
+ var unportable_hash = output.Item3;
+
+ Assert.IsTrue (portable_hash != 0, "#1");
+ Assert.IsTrue (unportable_hash != 0, "#2");
+ Assert.IsTrue (dump.Length > 0, "#3");
+ }
+
+ [Test]
+ [Category("NotOnWindows")]
+ public void DumpICallSingleOnce ()
+ {
+ DumpSingle ();
+ }
+
+ [Test]
+ [Category("NotOnWindows")]
+ public void DumpICallSingleConcurrent ()
+ {
+ // checks that self-dumping works in parallel, locklessly
+ int amt = 20;
+ var tasks = new Task [amt];
+ for (int i=0; i < amt; i++)
+ tasks [i] = Task.Run(() => DumpSingle ());
+ Task.WaitAll (tasks);
+ }
+
+ [Test]
+ [Category("NotOnWindows")]
+ public void DumpICallSingleAsync ()
+ {
+ // checks that dumping works in an async context
+ var t = Task.Run(() => DumpSingle ());
+ t.Wait ();
+ }
+
+ [Test]
+ [Category("NotOnWindows")]
+ public void DumpICallTotalOnce ()
+ {
+ DumpTotal ();
+ }
+
+ [Test]
+ [Category("NotOnWindows")]
+ public void DumpICallTotalRepeated ()
+ {
+ // checks that the state doesn't get broken with repeated use
+ DumpTotal ();
+ DumpTotal ();
+ DumpTotal ();
+ }
+
+ [Test]
+ [Category("NotOnWindows")]
+ public void DumpICallTotalAsync ()
+ {
+ // checks that dumping works in an async context
+ var t = Task.Run(() => DumpTotal ());
+ t.Wait ();
+ }
+
+ [Test]
+ [Category("NotOnWindows")]
+ public void DumpICallTotalConcurrent ()
+ {
+ int amt = 3;
+ var tasks = new Task [amt];
+ for (int i=0; i < amt; i++)
+ tasks [i] = Task.Run(() => DumpTotal ());
+ Task.WaitAll (tasks);
+ }
+
#endif
[Test]
diff --git a/mono/metadata/class-internals.h b/mono/metadata/class-internals.h index 3e5636a1cbf..fe46bea70a2 100644 --- a/mono/metadata/class-internals.h +++ b/mono/metadata/class-internals.h @@ -1109,6 +1109,9 @@ mono_method_get_name_full (MonoMethod *method, gboolean signature, gboolean ret, char * mono_method_get_full_name (MonoMethod *method); +const char* +mono_wrapper_type_to_str (guint32 wrapper_type); + MonoArrayType *mono_dup_array_type (MonoImage *image, MonoArrayType *a); MonoMethodSignature *mono_metadata_signature_deep_dup (MonoImage *image, MonoMethodSignature *sig); diff --git a/mono/metadata/debug-helpers.c b/mono/metadata/debug-helpers.c index 06d7b76b016..3d3c6cfb403 100644 --- a/mono/metadata/debug-helpers.c +++ b/mono/metadata/debug-helpers.c @@ -59,8 +59,8 @@ static const gint16 opidx [] = { #undef WRAPPER }; -static const char* -wrapper_type_to_str (guint32 wrapper_type) +const char* +mono_wrapper_type_to_str (guint32 wrapper_type) { g_assert (wrapper_type < MONO_WRAPPER_NUM); @@ -902,7 +902,7 @@ mono_method_get_name_full (MonoMethod *method, gboolean signature, gboolean ret, } if (method->wrapper_type != MONO_WRAPPER_NONE) - sprintf (wrapper, "(wrapper %s) ", wrapper_type_to_str (method->wrapper_type)); + sprintf (wrapper, "(wrapper %s) ", mono_wrapper_type_to_str (method->wrapper_type)); else strcpy (wrapper, ""); @@ -918,7 +918,7 @@ mono_method_get_name_full (MonoMethod *method, gboolean signature, gboolean ret, } if (method->wrapper_type != MONO_WRAPPER_NONE) - sprintf (wrapper, "(wrapper %s) ", wrapper_type_to_str (method->wrapper_type)); + sprintf (wrapper, "(wrapper %s) ", mono_wrapper_type_to_str (method->wrapper_type)); else strcpy (wrapper, ""); if (ret && sig) { diff --git a/mono/metadata/domain-internals.h b/mono/metadata/domain-internals.h index b3b51497a87..375e955aa23 100644 --- a/mono/metadata/domain-internals.h +++ b/mono/metadata/domain-internals.h @@ -253,6 +253,8 @@ struct _MonoJitInfo { /* FIXME: Embed this after the structure later*/ gpointer gc_info; /* Currently only used by SGen */ + + gpointer seq_points; MonoJitExceptionInfo clauses [MONO_ZERO_LEN_ARRAY]; /* There is an optional MonoGenericJitInfo after the clauses */ diff --git a/mono/metadata/icall-decl.h b/mono/metadata/icall-decl.h index 10d7ae276f5..560f3f7ca05 100644 --- a/mono/metadata/icall-decl.h +++ b/mono/metadata/icall-decl.h @@ -379,5 +379,8 @@ ICALL_EXPORT void ves_icall_System_Runtime_RuntimeImports_ZeroMemory (guint8*, g ICALL_EXPORT void ves_icall_System_Runtime_RuntimeImports_ecvt_s(char*, size_t, double, int, int*, int*); ICALL_EXPORT void ves_icall_get_method_info (MonoMethod*, MonoMethodInfo*, MonoError*); ICALL_EXPORT void* ves_icall_System_Reflection_Assembly_GetManifestResourceInternal (MonoReflectionAssemblyHandle assembly_h, MonoStringHandle name, gint32* size, MonoReflectionModuleHandleOut ref_module, MonoError*); +ICALL_EXPORT MonoStringHandle ves_icall_Mono_Runtime_DumpStateSingle (guint64 *portable_hash, guint64 *unportable_hash, MonoError *error); +ICALL_EXPORT MonoStringHandle ves_icall_Mono_Runtime_DumpStateTotal (guint64 *portable_hash, guint64 *unportable_hash, MonoError *error); +ICALL_EXPORT void ves_icall_Mono_Runtime_RegisterReportingForNativeLib (const char *path_suffix, const char *module_name); #endif // __MONO_METADATA_ICALL_H__ diff --git a/mono/metadata/icall-def.h b/mono/metadata/icall-def.h index 42a1907cfba..0c0c42c0756 100644 --- a/mono/metadata/icall-def.h +++ b/mono/metadata/icall-def.h @@ -91,10 +91,13 @@ HANDLES(ICALL(TLS_PROVIDER_FACTORY_1, "IsBtlsSupported", ves_icall_Mono_TlsProvi ICALL_TYPE(RUNTIME, "Mono.Runtime", RUNTIME_1) HANDLES(ICALL(RUNTIME_1, "DisableMicrosoftTelemetry", ves_icall_Mono_Runtime_DisableMicrosoftTelemetry)) +HANDLES(ICALL(RUNTIME_15, "DumpStateSingle_internal", ves_icall_Mono_Runtime_DumpStateSingle)) +HANDLES(ICALL(RUNTIME_16, "DumpStateTotal_internal", ves_icall_Mono_Runtime_DumpStateTotal)) HANDLES(ICALL(RUNTIME_2, "EnableMicrosoftTelemetry_internal", ves_icall_Mono_Runtime_EnableMicrosoftTelemetry)) HANDLES(ICALL(RUNTIME_3, "ExceptionToState_internal", ves_icall_Mono_Runtime_ExceptionToState)) HANDLES(ICALL(RUNTIME_4, "GetDisplayName", ves_icall_Mono_Runtime_GetDisplayName)) HANDLES(ICALL(RUNTIME_12, "GetNativeStackTrace", ves_icall_Mono_Runtime_GetNativeStackTrace)) +HANDLES(ICALL(RUNTIME_17, "RegisterReportingForNativeLib_internal", ves_icall_Mono_Runtime_RegisterReportingForNativeLib)) HANDLES(ICALL(RUNTIME_13, "SendMicrosoftTelemetry_internal", ves_icall_Mono_Runtime_SendMicrosoftTelemetry)) HANDLES(ICALL(RUNTIME_14, "WriteStateToFile_internal", ves_icall_Mono_Runtime_DumpTelemetry)) diff --git a/mono/metadata/icall.c b/mono/metadata/icall.c index 2be713caed9..30a57cacb08 100644 --- a/mono/metadata/icall.c +++ b/mono/metadata/icall.c @@ -5866,6 +5866,93 @@ ves_icall_Mono_Runtime_DumpTelemetry (char *payload, guint64 portable_hash, guin #endif } +ICALL_EXPORT MonoStringHandle +ves_icall_Mono_Runtime_DumpStateSingle (guint64 *portable_hash, guint64 *unportable_hash, MonoError *error) +{ + MonoStringHandle result; + +#ifndef DISABLE_CRASH_REPORTING + MonoStackHash hashes; + memset (&hashes, 0, sizeof (MonoStackHash)); + MonoContext *ctx = NULL; + + MonoThreadSummary this_thread; + if (!mono_threads_summarize_one (&this_thread, ctx)) + return mono_string_new_handle (mono_domain_get (), "", error); + + *portable_hash = (guint64) this_thread.hashes.offset_free_hash; + *unportable_hash = (guint64) this_thread.hashes.offset_rich_hash; + + JsonWriter writer; + mono_json_writer_init (&writer); + mono_native_state_init (&writer); + gboolean first_thread_added = TRUE; + mono_native_state_add_thread (&writer, &this_thread, NULL, first_thread_added); + char *output = mono_native_state_free (&writer, FALSE); + result = mono_string_new_handle (mono_domain_get (), output, error); + g_free (output); +#else + *portable_hash = 0; + *unportable_hash = 0; + result = mono_string_new_handle (mono_domain_get (), "", error); +#endif + + return result; +} + + +ICALL_EXPORT void +ves_icall_Mono_Runtime_RegisterReportingForNativeLib (const char *path_suffix, const char *module_name) +{ +#ifndef DISABLE_CRASH_REPORTING + mono_get_eh_callbacks ()->mono_register_native_library (path_suffix, module_name); +#endif +} + +// Number derived from trials on relevant hardware. +// If it seems large, please confirm it's safe to shrink +// before doing so. +#define MONO_MAX_SUMMARY_LEN_ICALL 500000 + +ICALL_EXPORT MonoStringHandle +ves_icall_Mono_Runtime_DumpStateTotal (guint64 *portable_hash, guint64 *unportable_hash, MonoError *error) +{ + MonoStringHandle result; + +#ifndef DISABLE_CRASH_REPORTING + char *scratch = g_new0 (gchar, MONO_MAX_SUMMARY_LEN_ICALL); + + char *out; + MonoStackHash hashes; + memset (&hashes, 0, sizeof (MonoStackHash)); + MonoContext *ctx = NULL; + + mono_get_runtime_callbacks ()->install_state_summarizer (); + + // if this works + // - give back loader lock during stop + mono_thread_info_set_is_async_context (TRUE); + gboolean success = mono_threads_summarize (ctx, &out, &hashes, TRUE, FALSE, scratch, MONO_MAX_SUMMARY_LEN_ICALL); + mono_thread_info_set_is_async_context (FALSE); + + if (!success) + return mono_string_new_handle (mono_domain_get (), "", error); + + *portable_hash = (guint64) hashes.offset_free_hash; + *unportable_hash = (guint64) hashes.offset_rich_hash; + result = mono_string_new_handle (mono_domain_get (), out, error); + + // out is now a pointer into garbage memory + g_free (scratch); +#else + *portable_hash = 0; + *unportable_hash = 0; + result = mono_string_new_handle (mono_domain_get (), "", error); +#endif + + return result; +} + ICALL_EXPORT MonoBoolean ves_icall_System_Reflection_AssemblyName_ParseAssemblyName (const char *name, MonoAssemblyName *aname, MonoBoolean *is_version_defined_arg, MonoBoolean *is_token_defined_arg) { diff --git a/mono/metadata/mono-debug.c b/mono/metadata/mono-debug.c index 7da5a60e7dd..93203b52f1d 100644 --- a/mono/metadata/mono-debug.c +++ b/mono/metadata/mono-debug.c @@ -610,8 +610,8 @@ read_variable (MonoDebugVarInfo *var, guint8 *ptr, guint8 **rptr) *rptr = ptr; } -void -mono_debug_free_method_jit_info (MonoDebugMethodJitInfo *jit) +static void +mono_debug_free_method_jit_info_full (MonoDebugMethodJitInfo *jit, gboolean stack) { if (!jit) return; @@ -621,17 +621,23 @@ mono_debug_free_method_jit_info (MonoDebugMethodJitInfo *jit) g_free (jit->locals); g_free (jit->gsharedvt_info_var); g_free (jit->gsharedvt_locals_var); - g_free (jit); + if (!stack) + g_free (jit); +} + +void +mono_debug_free_method_jit_info (MonoDebugMethodJitInfo *jit) +{ + return mono_debug_free_method_jit_info_full (jit, FALSE); } static MonoDebugMethodJitInfo * -mono_debug_read_method (MonoDebugMethodAddress *address) +mono_debug_read_method (MonoDebugMethodAddress *address, MonoDebugMethodJitInfo *jit) { - MonoDebugMethodJitInfo *jit; guint32 i; guint8 *ptr; - jit = g_new0 (MonoDebugMethodJitInfo, 1); + memset (jit, 0, sizeof (*jit)); jit->code_start = address->code_start; jit->code_size = address->code_size; @@ -677,7 +683,7 @@ mono_debug_read_method (MonoDebugMethodAddress *address) } static MonoDebugMethodJitInfo * -find_method (MonoMethod *method, MonoDomain *domain) +find_method (MonoMethod *method, MonoDomain *domain, MonoDebugMethodJitInfo *jit) { MonoDebugDataTable *table; MonoDebugMethodAddress *address; @@ -688,19 +694,19 @@ find_method (MonoMethod *method, MonoDomain *domain) if (!address) return NULL; - return mono_debug_read_method (address); + return mono_debug_read_method (address, jit); } MonoDebugMethodJitInfo * mono_debug_find_method (MonoMethod *method, MonoDomain *domain) { - MonoDebugMethodJitInfo *res; + MonoDebugMethodJitInfo *res = g_new0 (MonoDebugMethodJitInfo, 1); if (mono_debug_format == MONO_DEBUG_FORMAT_NONE) return NULL; mono_debugger_lock (); - res = find_method (method, domain); + find_method (method, domain, res); mono_debugger_unlock (); return res; } @@ -715,10 +721,10 @@ mono_debug_lookup_method_addresses (MonoMethod *method) static gint32 il_offset_from_address (MonoMethod *method, MonoDomain *domain, guint32 native_offset) { - MonoDebugMethodJitInfo *jit; + MonoDebugMethodJitInfo mem; int i; - jit = find_method (method, domain); + MonoDebugMethodJitInfo *jit = find_method (method, domain, &mem); if (!jit || !jit->line_numbers) goto cleanup_and_fail; @@ -726,13 +732,13 @@ il_offset_from_address (MonoMethod *method, MonoDomain *domain, guint32 native_o MonoDebugLineNumberEntry lne = jit->line_numbers [i]; if (lne.native_offset <= native_offset) { - mono_debug_free_method_jit_info (jit); + mono_debug_free_method_jit_info_full (jit, TRUE); return lne.il_offset; } } cleanup_and_fail: - mono_debug_free_method_jit_info (jit); + mono_debug_free_method_jit_info_full (jit, TRUE); return -1; } diff --git a/mono/metadata/object-internals.h b/mono/metadata/object-internals.h index d2a6448e341..188aad2783d 100644 --- a/mono/metadata/object-internals.h +++ b/mono/metadata/object-internals.h @@ -701,8 +701,10 @@ typedef struct { gboolean (*mono_above_abort_threshold) (void); void (*mono_clear_abort_threshold) (void); void (*mono_reraise_exception) (MonoException *ex); - void (*mono_summarize_stack) (MonoDomain *domain, MonoThreadSummary *out, MonoContext *crash_ctx); + void (*mono_summarize_managed_stack) (MonoThreadSummary *out); + void (*mono_summarize_unmanaged_stack) (MonoThreadSummary *out); void (*mono_summarize_exception) (MonoException *exc, MonoThreadSummary *out); + void (*mono_register_native_library) (const char *module_path, const char *module_name); } MonoRuntimeExceptionHandlingCallbacks; MONO_COLD void mono_set_pending_exception (MonoException *exc); diff --git a/mono/metadata/threads-types.h b/mono/metadata/threads-types.h index 02c001495d8..50b685555e6 100644 --- a/mono/metadata/threads-types.h +++ b/mono/metadata/threads-types.h @@ -539,8 +539,9 @@ MONO_COLD void mono_set_pending_exception_handle (MonoExceptionHandle exc); #define MONO_MAX_SUMMARY_NAME_LEN 140 +#define MONO_MAX_THREAD_NAME_LEN 140 #define MONO_MAX_SUMMARY_THREADS 32 -#define MONO_MAX_SUMMARY_FRAMES 40 +#define MONO_MAX_SUMMARY_FRAMES 80 typedef struct { gboolean is_managed; @@ -550,9 +551,17 @@ typedef struct { int il_offset; int native_offset; const char *guid; + +#ifndef MONO_PRIVATE_CRASHES + // We use ifdef to make it a compile-time error to store this + // symbolicated string on release builds + const char *name; +#endif + } managed_data; struct { intptr_t ip; + const char *module; gboolean is_trampoline; gboolean has_name; } unmanaged_data; @@ -564,13 +573,29 @@ typedef struct { } MonoStackHash; typedef struct { + gboolean done; // Needed because cond wait can have spurious wakeups + MonoSemType done_wait; // Readers are finished with this + + // For managed stack walking + + MonoDomain *domain; + MonoJitTlsData *jit_tls; + MonoLMF *lmf; + + // Emitted attributes + gboolean is_managed; - const char *name; + char name [MONO_MAX_THREAD_NAME_LEN]; + intptr_t managed_thread_ptr; intptr_t info_addr; intptr_t native_thread_id; + // Print reason we don't have a complete + // managed trace + const char *error_msg; + int num_managed_frames; MonoFrameSummary managed_frames [MONO_MAX_SUMMARY_FRAMES]; @@ -578,9 +603,18 @@ typedef struct { MonoFrameSummary unmanaged_frames [MONO_MAX_SUMMARY_FRAMES]; MonoStackHash hashes; + + MonoContext *ctx; + MonoContext ctx_mem; } MonoThreadSummary; gboolean -mono_threads_summarize (MonoContext *ctx, gchar **out, MonoStackHash *hashes); +mono_threads_summarize (MonoContext *ctx, gchar **out, MonoStackHash *hashes, gboolean silent, gboolean signal_handler_controller, gchar *mem, size_t provided_size); + +gboolean +mono_threads_summarize_execute (MonoContext *ctx, gchar **out, MonoStackHash *hashes, gboolean silent, gchar *mem, size_t provided_size); + +gboolean +mono_threads_summarize_one (MonoThreadSummary *out, MonoContext *ctx); #endif /* _MONO_METADATA_THREADS_TYPES_H_ */ diff --git a/mono/metadata/threads.c b/mono/metadata/threads.c index 30b8807eedd..dd096922457 100644 --- a/mono/metadata/threads.c +++ b/mono/metadata/threads.c @@ -3809,9 +3809,15 @@ get_thread_dump (MonoThreadInfo *info, gpointer ud) typedef struct { int nthreads, max_threads; + guint32 *threads; } CollectThreadsUserData; +typedef struct { + int nthreads, max_threads; + MonoNativeThreadId *threads; +} CollectThreadIdsUserData; + static void collect_thread (gpointer key, gpointer value, gpointer user) { @@ -3822,6 +3828,16 @@ collect_thread (gpointer key, gpointer value, gpointer user) ud->threads [ud->nthreads ++] = mono_gchandle_new (&thread->obj, TRUE); } +static void +collect_thread_id (gpointer key, gpointer value, gpointer user) +{ + CollectThreadIdsUserData *ud = (CollectThreadIdsUserData *)user; + MonoInternalThread *thread = (MonoInternalThread *)value; + + if (ud->nthreads < ud->max_threads) + ud->threads [ud->nthreads ++] = thread_get_tid (thread); +} + /* * Collect running threads into the THREADS array. * THREADS should be an array allocated on the stack. @@ -3847,6 +3863,27 @@ collect_threads (guint32 *thread_handles, int max_threads) return ud.nthreads; } +static int +collect_thread_ids (MonoNativeThreadId *thread_ids, int max_threads) +{ + CollectThreadIdsUserData ud; + + mono_memory_barrier (); + if (!threads) + return 0; + + memset (&ud, 0, sizeof (ud)); + /* This array contains refs, but its on the stack, so its ok */ + ud.threads = thread_ids; + ud.max_threads = max_threads; + + mono_threads_lock (); + mono_g_hash_table_foreach (threads, collect_thread_id, &ud); + mono_threads_unlock (); + + return ud.nthreads; +} + static void dump_thread (MonoInternalThread *thread, ThreadDumpUserData *ud, FILE* output_file) { @@ -6021,291 +6058,324 @@ mono_set_thread_dump_dir (gchar* dir) { #ifdef DISABLE_CRASH_REPORTING gboolean -mono_threads_summarize (MonoContext *ctx, gchar **out, MonoStackHash *hashes) +mono_threads_summarize (MonoContext *ctx, gchar **out, MonoStackHash *hashes, gboolean silent, gboolean signal_handler_controller, gchar *mem, size_t provided_size) { return FALSE; } -#else +gboolean +mono_threads_summarize_one (MonoThreadSummary *out, MonoContext *ctx) +{ + return FALSE; +} -static size_t num_threads_summarized = 0; +#else -// mono_thread_internal_current doesn't always work in signal -// handler contexts. This does. static gboolean -find_missing_thread (MonoNativeThreadId id, guint32 *out) +mono_threads_summarize_native_self (MonoThreadSummary *out, MonoContext *ctx) { - guint32 thread_array [128]; - int nthreads = collect_threads (thread_array, 128); - gboolean success = FALSE; + memset (out, 0, sizeof (MonoThreadSummary)); + out->ctx = ctx; - for (int i=0; i < nthreads; i++) { - guint32 handle = thread_array [i]; - MonoInternalThread *thread = (MonoInternalThread *) mono_gchandle_get_target (handle); - MonoNativeThreadId tid = thread_get_tid (thread); - if (tid == id) { - *out = handle; - success = TRUE; - } else { - mono_gchandle_free (handle); - } - } + MonoNativeThreadId current = mono_native_thread_id_get(); + out->native_thread_id = (intptr_t) current; - return success; + mono_get_eh_callbacks ()->mono_summarize_unmanaged_stack (out); + + mono_native_thread_get_name (current, out->name, MONO_MAX_SUMMARY_NAME_LEN); + + // FIXME: Figure out how to store and look these up? + /*MonoDomain *domain = thread->obj.vtable->domain;*/ + /*out->managed_thread_ptr = (intptr_t) get_current_thread_ptr_for_domain (domain, thread);*/ + /*out->info_addr = (intptr_t) thread->thread_info;*/ + return TRUE; } -static gboolean +// Not safe to call from signal handler +gboolean mono_threads_summarize_one (MonoThreadSummary *out, MonoContext *ctx) { - MonoDomain *domain; - guint32 handle; + gboolean success = mono_threads_summarize_native_self (out, ctx); - MonoNativeThreadId current = mono_native_thread_id_get(); + // Finish this on the same thread - // Not one of ours - if (!find_missing_thread (current, &handle)) - return FALSE; + if (success) + mono_get_eh_callbacks ()->mono_summarize_managed_stack (out); - MonoInternalThread *thread = (MonoInternalThread *) mono_gchandle_get_target (handle); + return success; +} - memset (out, 0, sizeof (MonoThreadSummary)); - domain = thread->obj.vtable->domain; - out->native_thread_id = (intptr_t) thread_get_tid (thread); - out->managed_thread_ptr = (intptr_t) get_current_thread_ptr_for_domain (domain, thread); - out->info_addr = (intptr_t) thread->thread_info; - if (thread->name) { - char *name = g_utf16_to_utf8 (thread->name, thread->name_len, NULL, NULL, NULL); - out->name = name; - } - mono_get_eh_callbacks ()->mono_summarize_stack (domain, out, ctx); +#define MAX_NUM_THREADS 128 +typedef struct { + gint32 has_owner; // state of this memory - // FIXME: handle failure gracefully - // Enable when doing unmanaged - /*g_assert (out->num_frames > 0);*/ - /*if (out->num_frames == 0)*/ - /*return FALSE;*/ + MonoSemType update; // notify of addition of threads - mono_gchandle_free (handle); + int nthreads; + MonoNativeThreadId thread_array [MAX_NUM_THREADS]; // ids of threads we're dumping - return TRUE; -} + int nthreads_attached; // Number of threads self-registered + MonoThreadSummary *all_threads [MAX_NUM_THREADS]; -typedef enum { - MONO_SUMMARY_EMPTY = 0x0, - MONO_SUMMARY_EXPECT = 0x1, - MONO_SUMMARY_IN_PROGRESS = 0x2, - MONO_SUMMARY_EXAMINE = 0x3, - MONO_SUMMARY_MUTATE_SHARED = 0x4 -} MonoSummaryState; + gboolean silent; // print to stdout +} SummarizerGlobalState; -static const char * -thread_summary_state_to_str (MonoSummaryState state) +static gboolean +summarizer_state_init (SummarizerGlobalState *state, MonoNativeThreadId current, int *my_index) { - switch (state) { - case MONO_SUMMARY_EMPTY: - return "MONO_SUMMARY_EMPTY"; - case MONO_SUMMARY_EXPECT: - return "MONO_SUMMARY_EXPECT"; - case MONO_SUMMARY_IN_PROGRESS: - return "MONO_SUMMARY_IN_PROGRESS"; - case MONO_SUMMARY_EXAMINE: - return "MONO_SUMMARY_EXAMINE"; - case MONO_SUMMARY_MUTATE_SHARED: - return "MONO_SUMMARY_MUTATE_SHARED"; - default: - g_assert_not_reached (); + gint32 started_state = mono_atomic_cas_i32 (&state->has_owner, 1 /* set */, 0 /* compare */); + gboolean not_started = started_state == 0; + if (not_started) { + state->nthreads = collect_thread_ids (state->thread_array, MAX_NUM_THREADS); + mono_os_sem_init (&state->update, 0); } -} -static gint32 summary_started; -static gint32 summarizing_thread_state; -static MonoNativeThreadId summarizing_thread; + for (int i = 0; i < state->nthreads; i++) { + if (state->thread_array [i] == current) { + *my_index = i; + break; + } + } -static inline MonoNativeThreadId -mono_atomic_cas_native_thread_id (volatile MonoNativeThreadId *dest, MonoNativeThreadId exch, MonoNativeThreadId comp) + return not_started; +} + +static void +summarizer_signal_other_threads (SummarizerGlobalState *state, MonoNativeThreadId current, int current_idx) { - // FIXME static_assert - g_assert (sizeof (MonoNativeThreadId) == 4 || sizeof (MonoNativeThreadId) == sizeof (gpointer)); + sigset_t sigset, old_sigset; + sigemptyset(&sigset); + sigaddset(&sigset, SIGTERM); - // Extra casts are needed to avoid warnings. MonoNativeThreadId can be an integer or a pointer. + for (int i=0; i < state->nthreads; i++) { + sigprocmask (SIG_UNBLOCK, &sigset, &old_sigset); - if (sizeof (MonoNativeThreadId) == 4) - return (MonoNativeThreadId)(gsize)mono_atomic_cas_i32 ((gint32*)dest, (gint32)(gsize)exch, (gint32)(gsize)comp); - return (MonoNativeThreadId)(gsize)mono_atomic_cas_ptr ((gpointer*)dest, (gpointer)(gsize)exch, (gpointer)(gsize)comp); + if (i == current_idx) + continue; + + #ifdef HAVE_PTHREAD_KILL + pthread_kill (state->thread_array [i], SIGTERM); + + if (!state->silent) + MOSTLY_ASYNC_SAFE_PRINTF("Pkilling 0x%zx from 0x%zx\n", state->thread_array [i], current); + #else + g_error ("pthread_kill () is not supported by this platform"); + #endif + } } -gboolean -mono_threads_summarize (MonoContext *ctx, gchar **out, MonoStackHash *hashes) +// Returns true when there are shared global references to "this_thread" +static gboolean +summarizer_post_dump (SummarizerGlobalState *state, MonoThreadSummary *this_thread, int current_idx) { - gint32 started_state = mono_atomic_cas_i32 (&summary_started, 1 /* set */, 0 /* compare */); - gboolean not_started = started_state == 0; + mono_memory_barrier (); - MonoNativeThreadId current = mono_native_thread_id_get (); + gpointer old = mono_atomic_cas_ptr ((volatile gpointer *)&state->all_threads [current_idx], this_thread, NULL); + + if (old == GINT_TO_POINTER (-1)) { + MOSTLY_ASYNC_SAFE_PRINTF ("Trying to register response after dumping period ended"); + return FALSE; + } else if (old != NULL) { + MOSTLY_ASYNC_SAFE_PRINTF ("Thread dump raced for thread slot."); + return FALSE; + } - MOSTLY_ASYNC_SAFE_PRINTF("Entering thread summarizer from %zx\n", current); + // We added our pointer + gint32 count = mono_atomic_inc_i32 ((volatile gint32 *) &state->nthreads_attached); + if (count == state->nthreads) + mono_os_sem_post (&state->update); - if (not_started) { - // Setup state - mono_summarize_native_state_begin (); - - if (!current) - g_error ("Can't get native thread ID"); - - // Note: this list can get out of date. We can have - // threads disappear from existince between us getting - // this list and us dumping them. We have to do our best. - // - // Thankfully the memory behind these pointers is always leaked, - // we never have dangling pointers. - guint32 thread_array [128]; - int nthreads = collect_threads (thread_array, 128); - - if (nthreads == 0) - MOSTLY_ASYNC_SAFE_PRINTF("No managed threads detected, error occured before thread init\n"); - - sigset_t sigset, old_sigset; - sigemptyset(&sigset); - sigaddset(&sigset, SIGTERM); - - for (int i=0; i < nthreads; i++) { - guint32 handle = thread_array [i]; - MonoInternalThread *thread = (MonoInternalThread *) mono_gchandle_get_target (handle); - - MonoNativeThreadId tid = thread_get_tid (thread); - if (current == tid) - continue; + return TRUE; +} - // Request every other thread dumps themselves before us - MonoThreadInfo *info = thread->thread_info; +// A lockless spinwait with a timeout +// Used in environments where locks are unsafe +// +// If set_pos is true, we wait until the expected number of threads have +// responded and then count that the expected number are set. If it is not true, +// then we wait for them to be unset. +static void +summary_timedwait (SummarizerGlobalState *state, int timeout_seconds) +{ + gint64 milliseconds_in_second = 1000; + gint64 timeout_total = milliseconds_in_second * timeout_seconds; - mono_memory_barrier (); - size_t old_num_summarized = num_threads_summarized; + gint64 end = mono_msec_ticks () + timeout_total; - sigprocmask (SIG_UNBLOCK, &sigset, &old_sigset); + while (TRUE) { + if (mono_atomic_load_i32 ((volatile gint32 *) &state->nthreads_attached) == state->nthreads) + break; - gint32 prev_state = mono_atomic_cas_i32(&summarizing_thread_state, MONO_SUMMARY_EXPECT /* set */, MONO_SUMMARY_EMPTY /* compare */); - g_assertf(prev_state == MONO_SUMMARY_EMPTY, "Summary memory was not in a clean state prior to entry", NULL); + gint64 now = mono_msec_ticks (); + gint64 remaining = end - now; + if (remaining <= 0) + break; - if (summarizing_thread_state != MONO_SUMMARY_EXPECT) { - const char *name = thread_summary_state_to_str ((MonoSummaryState)summarizing_thread_state); - g_error ("Status after init wrong: %s\n", name); - } + mono_os_sem_timedwait (&state->update, remaining, MONO_SEM_FLAGS_NONE); + } - summarizing_thread = tid; - mono_threads_pthread_kill (info, SIGTERM); + return; +} - // Number of rounds of 40 milliseconds seconds to give each dumping thread - int count = 400; +static void +summarizer_state_term (SummarizerGlobalState *state, gchar **out, gchar *mem, size_t provided_size) +{ + // See the array writes + mono_memory_barrier (); - while (old_num_summarized == num_threads_summarized && count > 0) { - if (thread->state & (ThreadState_Unstarted | ThreadState_Aborted | ThreadState_Stopped)) - break; + mono_summarize_native_state_begin (mem, provided_size); + for (int i=0; i < state->nthreads; i++) { + gpointer old_value = NULL; + while (TRUE) { + // Lock array slot with sentinel and get value set previously + // This lets late dumpers know that they're late. + gpointer new_old_value = mono_atomic_cas_ptr ((volatile gpointer *) &state->all_threads [i], GINT_TO_POINTER(-1), old_value); - usleep (10); - mono_memory_barrier (); - const char *name = thread_summary_state_to_str ((MonoSummaryState)summarizing_thread_state); - MOSTLY_ASYNC_SAFE_PRINTF("Waiting for signalled thread %zx to collect stacktrace. Status: %s\n. Thread state: 0x%x\n", tid, name, thread->state); - count--; - } + if (new_old_value != old_value) + old_value = new_old_value; + else + break; + } - // Free the gchandle; we're done waiting on this one, no matter how the waiting went. - mono_gchandle_free (handle); + MonoThreadSummary *thread = (MonoThreadSummary *) old_value; + if (!thread) + continue; - if (count == 0) { - // After timeout - // Thread may have been in lock or something, may have died - // Either way, didn't respond. Rather than failing, we have to - // skip it. - mono_memory_barrier (); - switch (summarizing_thread_state) { - case MONO_SUMMARY_MUTATE_SHARED: - g_error ("Timed out when writing json log!"); - break; + // We are doing this dump on the controlling thread because this isn't + // an async context. There's still some reliance on malloc here, but it's + // much more stable to do it all from the controlling thread. + mono_get_eh_callbacks ()->mono_summarize_managed_stack (thread); - case MONO_SUMMARY_EMPTY: { - g_assert (summarizing_thread == (MonoNativeThreadId) NULL); - break; + mono_summarize_native_state_add_thread (thread, thread->ctx); - case MONO_SUMMARY_EXAMINE: - MOSTLY_ASYNC_SAFE_PRINTF("Timed out, thread did not finish dumping\n"); + // Set non-shared state to notify the waiting thread to clean up + // without having to keep our shared state alive + mono_atomic_store_i32 (&thread->done, 0x1); + mono_os_sem_post (&thread->done_wait); + } + *out = mono_summarize_native_state_end (); - MonoNativeThreadId old_val = mono_atomic_cas_native_thread_id (&summarizing_thread, tid /* set */, 0 /* compare */); - g_assertf (old_val == NULL, "Attempting to abandon dumping of thread, and thread changed.", NULL); + mono_os_sem_destroy (&state->update); - gint32 timeout_abort_state = mono_atomic_cas_i32(&summarizing_thread_state, MONO_SUMMARY_EMPTY /* set */, MONO_SUMMARY_EXAMINE /* compare */); - g_assertf (timeout_abort_state == MONO_SUMMARY_EXAMINE, "Thread state changed during timeout abort", NULL); + memset (state, 0, sizeof (*state)); + mono_atomic_store_i32 ((volatile gint32 *)&state->has_owner, 0); +} - break; - } - case MONO_SUMMARY_EXPECT: { - MOSTLY_ASYNC_SAFE_PRINTF("Timed out, thread did not respond to signal\n"); +static void +summarizer_state_wait (MonoThreadSummary *thread) +{ + gint64 milliseconds_in_second = 1000; - MonoNativeThreadId old_val = mono_atomic_cas_native_thread_id (&summarizing_thread, 0 /* set */, tid /* compare */); - g_assertf (tid == old_val, "Attempting to abandon dumping of thread %zx, and thread changed to %zx.", tid, old_val); + // cond_wait can spuriously wake up, so we need to check + // done + while (!mono_atomic_load_i32 (&thread->done)) + mono_os_sem_timedwait (&thread->done_wait, milliseconds_in_second, MONO_SEM_FLAGS_NONE); +} - gint32 timeout_abort_state = mono_atomic_cas_i32(&summarizing_thread_state, MONO_SUMMARY_EMPTY /* set */, MONO_SUMMARY_EXPECT /* compare */); - g_assertf (timeout_abort_state == MONO_SUMMARY_EXPECT, "Thread state changed during timeout abort", NULL); +gboolean +mono_threads_summarize_execute (MonoContext *ctx, gchar **out, MonoStackHash *hashes, gboolean silent, gchar *mem, size_t provided_size) +{ + static SummarizerGlobalState state; - break; - } - default: - g_assert_not_reached (); - } - } - } + int current_idx; + MonoNativeThreadId current = mono_native_thread_id_get (); + gboolean this_thread_controls = summarizer_state_init (&state, current, ¤t_idx); - // After dumping all the other threads, we dump our own. - summarizing_thread = current; + if (this_thread_controls) { + state.silent = silent; + summarizer_signal_other_threads (&state, current, current_idx); } - mono_memory_barrier (); + MonoThreadSummary this_thread; - MOSTLY_ASYNC_SAFE_PRINTF("Self-reporting for thread %zx. Registered summarizing thread right now is %zx\n", current, summarizing_thread); - g_assert (current == summarizing_thread); + if (mono_threads_summarize_native_self (&this_thread, ctx)) { + // Init the synchronization between the controlling thread and the + // providing thread + mono_os_sem_init (&this_thread.done_wait, 0); - if (!not_started) { - gint32 restart_state = mono_atomic_cas_i32 (&summarizing_thread_state, MONO_SUMMARY_EXAMINE /* set */, MONO_SUMMARY_EXPECT /* compare */); - if (restart_state != MONO_SUMMARY_EXPECT) { - const char *name = thread_summary_state_to_str ((MonoSummaryState)summarizing_thread_state); - MOSTLY_ASYNC_SAFE_PRINTF ("Dumping thread could not obtain ownership of dumping memory. Timeout? Enum was %s", name); - goto fail; - } - } else { - summarizing_thread_state = MONO_SUMMARY_EXAMINE; - mono_memory_barrier (); + // Store a reference to our stack memory into global state + gboolean success = summarizer_post_dump (&state, &this_thread, current_idx); + if (!success && !state.silent) + MOSTLY_ASYNC_SAFE_PRINTF("Thread 0x%zx reported itself.\n", current); + } else if (!state.silent) { + MOSTLY_ASYNC_SAFE_PRINTF("Thread 0x%zx couldn't report itself.\n", current); } - // Dump ourselves - MonoThreadSummary this_thread; - if (mono_threads_summarize_one (&this_thread, ctx)) { - gint32 write_state = mono_atomic_cas_i32 (&summarizing_thread_state, MONO_SUMMARY_MUTATE_SHARED /* set */, MONO_SUMMARY_EXAMINE /* compare */); - if (write_state != MONO_SUMMARY_EXAMINE) { - MOSTLY_ASYNC_SAFE_PRINTF ("Terminated when walking stack, thread dump lost!\n"); - goto fail; - } + // From summarizer, wait and dump. + if (this_thread_controls) { + if (!state.silent) + MOSTLY_ASYNC_SAFE_PRINTF("Entering thread summarizer pause from 0x%zx\n", current); - mono_summarize_native_state_add_thread (&this_thread, ctx); + // Wait up to 2 seconds for all of the other threads to catch up + summary_timedwait (&state, 2); - gint32 write_done_state = mono_atomic_cas_i32 (&summarizing_thread_state, MONO_SUMMARY_EMPTY /* set */, MONO_SUMMARY_MUTATE_SHARED /* compare */); - g_assertf (write_done_state == MONO_SUMMARY_MUTATE_SHARED, "Memory unsafety: dumping thread ownership of shared memory ignored!", NULL); + if (!state.silent) + MOSTLY_ASYNC_SAFE_PRINTF("Finished thread summarizer pause from 0x%zx.\n", current); + + // Dump and cleanup all the stack memory + summarizer_state_term (&state, out, mem, provided_size); + } else { + // Wait here, keeping our stack memory alive + // for the dumper + summarizer_state_wait (&this_thread); } - mono_memory_barrier (); - num_threads_summarized++; - mono_memory_barrier (); + // FIXME: How many threads should be counted? + if (hashes) + *hashes = this_thread.hashes; - if (not_started) { - // We are the dumper - *out = mono_summarize_native_state_end (); - if (hashes) - *hashes = this_thread.hashes; - return TRUE; + return TRUE; +} + +gboolean +mono_threads_summarize (MonoContext *ctx, gchar **out, MonoStackHash *hashes, gboolean silent, gboolean signal_handler_controller, gchar *mem, size_t provided_size) +{ + // The staggered values are due to the need to use inc_i64 for the first value + static gint64 next_pending_request_id = 0; + static gint64 request_available_to_run = 1; + gint64 this_request_id = mono_atomic_inc_i64 ((volatile gint64 *) &next_pending_request_id); + + // This is a global queue of summary requests. + // It's not safe to signal a thread while they're in the + // middle of a dump. Dladdr is not reentrant. It's the one lock + // we rely on being able to take. + // + // We don't use it in almost any other place in managed code, so + // our problem is in the stack dumping code racing with the signalling code. + // + // A dump is wait-free to the degree that it's not going to loop indefinitely. + // If we're running from a crash handler block, we're not in any position to + // wait for an in-flight dump to finish. If we crashed while dumping, we cannot dump. + // We should simply return so we can die cleanly. + // + // signal_handler_controller should be set only from a handler that expects itself to be the only + // entry point, where the runtime already being dumping means we should just give up + + gboolean success = FALSE; + + while (TRUE) { + gint64 next_request_id = mono_atomic_load_i64 ((volatile gint64 *) &request_available_to_run); + + if (next_request_id == this_request_id) { + success = mono_threads_summarize_execute (ctx, out, hashes, silent, mem, provided_size); + + // Only the thread that gets the ticket can unblock future dumpers. + mono_atomic_inc_i64 ((volatile gint64 *) &request_available_to_run); + break; + } else if (signal_handler_controller) { + // We're done. We can't do anything. + MOSTLY_ASYNC_SAFE_PRINTF ("Attempted to dump for critical failure when already in dump. Error reporting crashed?"); + break; + } else { + if (!silent) + MOSTLY_ASYNC_SAFE_PRINTF ("Waiting for in-flight dump to complete."); + sleep (2); + } } -fail: - while (1) - sleep (10); + return success; } + #endif diff --git a/mono/mini/aot-runtime.c b/mono/mini/aot-runtime.c index d14f2f881ad..c3180a732fa 100644 --- a/mono/mini/aot-runtime.c +++ b/mono/mini/aot-runtime.c @@ -3307,6 +3307,8 @@ decode_exception_debug_info (MonoAotModule *amodule, MonoDomain *domain, else mono_seq_point_info_free (seq_points); mono_domain_unlock (domain); + + jinfo->seq_points = seq_points; } /* Load debug info */ diff --git a/mono/mini/debugger-engine.c b/mono/mini/debugger-engine.c index 588fa6fae2b..4260fff6703 100644 --- a/mono/mini/debugger-engine.c +++ b/mono/mini/debugger-engine.c @@ -302,7 +302,6 @@ mono_de_add_pending_breakpoints (MonoMethod *method, MonoJitInfo *ji) int i, j; MonoSeqPointInfo *seq_points; MonoDomain *domain; - MonoMethod *jmethod; if (!breakpoints) return; @@ -326,20 +325,18 @@ mono_de_add_pending_breakpoints (MonoMethod *method, MonoJitInfo *ji) } if (!found) { - MonoMethod *declaring = NULL; - - jmethod = jinfo_get_method (ji); - if (jmethod->is_inflated) - declaring = mono_method_get_declaring_generic_method (jmethod); - - mono_domain_lock (domain); - seq_points = (MonoSeqPointInfo *)g_hash_table_lookup (domain_jit_info (domain)->seq_points, jmethod); - if (!seq_points && declaring) - seq_points = (MonoSeqPointInfo *)g_hash_table_lookup (domain_jit_info (domain)->seq_points, declaring); - mono_domain_unlock (domain); - if (!seq_points) - /* Could be AOT code */ - continue; + seq_points = (MonoSeqPointInfo *) ji->seq_points; + + if (!seq_points) { + MonoMethod *jmethod = jinfo_get_method (ji); + if (jmethod->is_inflated) { + MonoJitInfo *seq_ji; + MonoMethod *declaring = mono_method_get_declaring_generic_method (jmethod); + mono_jit_search_all_backends_for_jit_info (domain, declaring, &seq_ji); + seq_points = (MonoSeqPointInfo *) seq_ji->seq_points; + } + } + g_assert (seq_points); insert_breakpoint (seq_points, domain, ji, bp, NULL); @@ -358,22 +355,8 @@ set_bp_in_method (MonoDomain *domain, MonoMethod *method, MonoSeqPointInfo *seq_ if (error) error_init (error); - code = mono_jit_find_compiled_method_with_jit_info (domain, method, &ji); - if (!code) { - ERROR_DECL_VALUE (oerror); - - /* Might be AOTed code */ - mono_class_init (method->klass); - code = mono_aot_get_method (domain, method, &oerror); - if (code) { - mono_error_assert_ok (&oerror); - ji = mono_jit_info_table_find (domain, code); - } else { - /* Might be interpreted */ - ji = mini_get_interp_callbacks ()->find_jit_info (domain, method); - } - g_assert (ji); - } + code = mono_jit_search_all_backends_for_jit_info (domain, method, &ji); + g_assert (ji); insert_breakpoint (seq_points, domain, ji, bp, error); } @@ -389,8 +372,8 @@ static void collect_domain_bp (gpointer key, gpointer value, gpointer user_data) { GHashTableIter iter; - MonoDomain *domain = (MonoDomain*)key; MonoSeqPointInfo *seq_points; + MonoDomain *domain = (MonoDomain*)key; CollectDomainData *ud = (CollectDomainData*)user_data; MonoMethod *m; diff --git a/mono/mini/interp/transform.c b/mono/mini/interp/transform.c index f6272c3570e..2a256ebd3a0 100644 --- a/mono/mini/interp/transform.c +++ b/mono/mini/interp/transform.c @@ -1932,7 +1932,7 @@ collect_pred_seq_points (TransformData *td, InterpBasicBlock *bb, SeqPoint *seqp } static void -save_seq_points (TransformData *td) +save_seq_points (TransformData *td, MonoJitInfo *jinfo) { InterpMethod *rtm = td->rtm; GByteArray *array; @@ -2019,6 +2019,8 @@ save_seq_points (TransformData *td) mono_domain_lock (domain); g_hash_table_insert (domain_jit_info (domain)->seq_points, rtm->method, info); mono_domain_unlock (domain); + + jinfo->seq_points = info; } static void @@ -5017,7 +5019,7 @@ generate (MonoMethod *method, MonoMethodHeader *header, InterpMethod *rtm, unsig } } - save_seq_points (td); + save_seq_points (td, jinfo); exit: mono_basic_block_free (original_bb); diff --git a/mono/mini/mini-exceptions.c b/mono/mini/mini-exceptions.c index fcd1cf8bd20..9b27b342df9 100644 --- a/mono/mini/mini-exceptions.c +++ b/mono/mini/mini-exceptions.c @@ -119,7 +119,7 @@ static gpointer throw_corlib_exception_func; static MonoFtnPtrEHCallback ftnptr_eh_callback; -static void mono_walk_stack_full (MonoJitStackWalk func, MonoContext *start_ctx, MonoDomain *domain, MonoJitTlsData *jit_tls, MonoLMF *lmf, MonoUnwindOptions unwind_options, gpointer user_data); +static void mono_walk_stack_full (MonoJitStackWalk func, MonoContext *start_ctx, MonoDomain *domain, MonoJitTlsData *jit_tls, MonoLMF *lmf, MonoUnwindOptions unwind_options, gpointer user_data, gboolean crash_context); static void mono_raise_exception_with_ctx (MonoException *exc, MonoContext *ctx); static void mono_runtime_walk_stack_with_ctx (MonoJitStackWalk func, MonoContext *start_ctx, MonoUnwindOptions unwind_options, void *user_data); static gboolean mono_current_thread_has_handle_block_guard (void); @@ -127,8 +127,10 @@ static gboolean mono_install_handler_block_guard (MonoThreadUnwindState *ctx); static void mono_uninstall_current_handler_block_guard (void); static gboolean mono_exception_walk_trace_internal (MonoException *ex, MonoExceptionFrameWalk func, gpointer user_data); -static void mono_summarize_stack (MonoDomain *domain, MonoThreadSummary *out, MonoContext *crash_ctx); +static void mono_summarize_managed_stack (MonoThreadSummary *out); +static void mono_summarize_unmanaged_stack (MonoThreadSummary *out); static void mono_summarize_exception (MonoException *exc, MonoThreadSummary *out); +static void mono_crash_reporting_register_native_library (const char *module_path, const char *module_name); static gboolean first_managed (MonoStackFrameInfo *frame, MonoContext *ctx, gpointer addr) @@ -236,8 +238,10 @@ mono_exceptions_init (void) cbs.mono_walk_stack_with_ctx = mono_runtime_walk_stack_with_ctx; cbs.mono_walk_stack_with_state = mono_walk_stack_with_state; - cbs.mono_summarize_stack = mono_summarize_stack; + cbs.mono_summarize_managed_stack = mono_summarize_managed_stack; + cbs.mono_summarize_unmanaged_stack = mono_summarize_unmanaged_stack; cbs.mono_summarize_exception = mono_summarize_exception; + cbs.mono_register_native_library = mono_crash_reporting_register_native_library; if (mono_llvm_only) { cbs.mono_raise_exception = mono_llvm_raise_exception; @@ -1110,7 +1114,7 @@ mono_walk_stack_with_ctx (MonoJitStackWalk func, MonoContext *start_ctx, MonoUnw start_ctx = &extra_ctx; } - mono_walk_stack_full (func, start_ctx, mono_domain_get (), thread->jit_data, mono_get_lmf (), unwind_options, user_data); + mono_walk_stack_full (func, start_ctx, mono_domain_get (), thread->jit_data, mono_get_lmf (), unwind_options, user_data, FALSE); } /** @@ -1145,7 +1149,7 @@ mono_walk_stack_with_state (MonoJitStackWalk func, MonoThreadUnwindState *state, (MonoDomain *)state->unwind_data [MONO_UNWIND_DATA_DOMAIN], (MonoJitTlsData *)state->unwind_data [MONO_UNWIND_DATA_JIT_TLS], (MonoLMF *)state->unwind_data [MONO_UNWIND_DATA_LMF], - unwind_options, user_data); + unwind_options, user_data, FALSE); } void @@ -1166,15 +1170,16 @@ mono_walk_stack (MonoJitStackWalk func, MonoUnwindOptions options, void *user_da * \param thread the thread whose stack to walk, can be NULL to use the current thread * \param lmf the LMF of \p thread, can be NULL to use the LMF of the current thread * \param user_data data passed to the callback + * \param crash_context tells us that we're in a context where it's not safe to lock or allocate * This function walks the stack of a thread, starting from the state * represented by \p start_ctx. For each frame the callback * function is called with the relevant info. The walk ends when no more * managed stack frames are found or when the callback returns a TRUE value. */ static void -mono_walk_stack_full (MonoJitStackWalk func, MonoContext *start_ctx, MonoDomain *domain, MonoJitTlsData *jit_tls, MonoLMF *lmf, MonoUnwindOptions unwind_options, gpointer user_data) +mono_walk_stack_full (MonoJitStackWalk func, MonoContext *start_ctx, MonoDomain *domain, MonoJitTlsData *jit_tls, MonoLMF *lmf, MonoUnwindOptions unwind_options, gpointer user_data, gboolean crash_context) { - gint il_offset, i; + gint il_offset; MonoContext ctx, new_ctx; StackFrameInfo frame; gboolean res; @@ -1217,14 +1222,27 @@ mono_walk_stack_full (MonoJitStackWalk func, MonoContext *start_ctx, MonoDomain } #endif - g_assert (start_ctx); - g_assert (domain); - g_assert (jit_tls); + if (!start_ctx) { + g_warning ("start_ctx required for stack walk"); + return; + } + + if (!domain) { + g_warning ("domain required for stack walk"); + return; + } + + if (!jit_tls) { + g_warning ("jit_tls required for stack walk"); + return; + } + /*The LMF will be null if the target have no managed frames.*/ /* g_assert (lmf); */ - - if (async) - g_assert (unwind_options == MONO_UNWIND_NONE); + if (async && (unwind_options & MONO_UNWIND_LOOKUP_ACTUAL_METHOD)) { + g_warning ("async && (unwind_options & MONO_UNWIND_LOOKUP_ACTUAL_METHOD) not legal"); + return; + } memcpy (&ctx, start_ctx, sizeof (MonoContext)); memset (reg_locations, 0, sizeof (reg_locations)); @@ -1241,14 +1259,24 @@ mono_walk_stack_full (MonoJitStackWalk func, MonoContext *start_ctx, MonoDomain goto next; if ((unwind_options & MONO_UNWIND_LOOKUP_IL_OFFSET) && frame.ji) { - MonoDebugSourceLocation *source; + MonoDebugSourceLocation *source = NULL; - source = mono_debug_lookup_source_location (jinfo_get_method (frame.ji), frame.native_offset, domain); + // Don't do this when we can be in a signal handler + if (!crash_context) + source = mono_debug_lookup_source_location (jinfo_get_method (frame.ji), frame.native_offset, domain); if (source) { il_offset = source->il_offset; } else { + MonoSeqPointInfo *seq_points = NULL; + + // It's more reliable to look into the global cache if possible + if (crash_context) + seq_points = (MonoSeqPointInfo *) frame.ji->seq_points; + else + seq_points = mono_get_seq_points (domain, jinfo_get_method (frame.ji)); + SeqPoint sp; - if (mono_find_prev_seq_point_for_native_offset (domain, jinfo_get_method (frame.ji), frame.native_offset, NULL, &sp)) + if (seq_points && mono_seq_point_find_prev_by_native_offset (seq_points, frame.native_offset, &sp)) il_offset = sp.il_offset; else il_offset = -1; @@ -1273,7 +1301,7 @@ mono_walk_stack_full (MonoJitStackWalk func, MonoContext *start_ctx, MonoDomain next: if (get_reg_locations) { - for (i = 0; i < MONO_MAX_IREGS; ++i) + for (int i = 0; i < MONO_MAX_IREGS; ++i) if (new_reg_locations [i]) reg_locations [i] = new_reg_locations [i]; } @@ -1283,8 +1311,15 @@ next: } #ifdef DISABLE_CRASH_REPORTING -static void -mono_summarize_stack (MonoDomain *domain, MonoThreadSummary *out, MonoContext *crash_ctx) + +static void +mono_summarize_managed_stack (MonoThreadSummary *out) +{ + return; +} + +static void +mono_summarize_unmanaged_stack (MonoThreadSummary *out) { return; } @@ -1295,6 +1330,13 @@ mono_summarize_exception (MonoException *exc, MonoThreadSummary *out) return; } +static void +mono_crash_reporting_register_native_library (const char *module_path, const char *module_name) +{ + return; +} + + #else typedef struct { @@ -1302,6 +1344,7 @@ typedef struct { int num_frames; int max_frames; MonoStackHash *hashes; + const char *error; } MonoSummarizeUserData; static void @@ -1318,32 +1361,87 @@ copy_summary_string_safe (char *in, const char *out) return; } -static gboolean -mono_get_portable_ip (intptr_t in_ip, intptr_t *out_ip, char *out_name) +static GHashTable *native_library_whitelist; + +static void +mono_crash_reporting_register_native_library (const char *module_path, const char *module_name) { - // We're only able to get reliable info about pointers in this assembly Dl_info info; - int success = dladdr ((void*) mono_get_portable_ip, &info); - intptr_t this_module = (intptr_t) info.dli_fbase; - g_assert (success); + if (!native_library_whitelist) { + native_library_whitelist = g_hash_table_new_full (NULL, NULL, NULL, g_free); + + dladdr ((void*) mono_crash_reporting_register_native_library, &info); + + if (info.dli_fname && strlen(info.dli_fname) > 0) + g_hash_table_insert (native_library_whitelist, g_strdup (info.dli_fname), g_strdup ("mono")); + } + + // Examples: libsystem_pthread.dylib -> "pthread" + // Examples: libsystem_platform.dylib -> "platform" + // Examples: mono-sgen -> "mono" from above line + g_hash_table_insert (native_library_whitelist, g_strdup (module_path), g_strdup (module_name)); +} + +static gboolean +mono_make_portable_ip (intptr_t in_ip, intptr_t module_base) +{ + // FIXME: Make generalize away from llvm tools? + // So lldb starts the pointer base at 0x100000000 + // and expects to get pointers as (offset + constant) + // + // Quirk shared by: + // /usr/bin/symbols -- symbols version: @(#)PROGRAM:symbols PROJECT:SamplingTools-63501 + // *CoreSymbolicationDT.framework version: 63750*/ + intptr_t offset = in_ip - module_base; + intptr_t magic_value = offset + 0x100000000; + return magic_value; +} + +static gboolean +check_whitelisted_module (const char *in_name, const char **out_module) +{ + if (!native_library_whitelist) { + if (g_str_has_suffix (in_name, "mono-sgen")) { + if (out_module) + *out_module = "mono"; + return TRUE; + } + + return FALSE; + } + + GHashTableIter iter; + char *file_suffix; + char *module_suffix; + g_hash_table_iter_init (&iter, native_library_whitelist); + while (g_hash_table_iter_next (&iter, (gpointer *) &file_suffix, (gpointer *) &module_suffix)) { + if (!g_str_has_suffix (in_name, file_suffix)) + continue; + if (out_module) + *out_module = module_suffix; + return TRUE; + } + + /*fprintf (stderr, "%s == %s\n", info.dli_fname, *out_module);*/ - success = dladdr ((void*)in_ip, &info); + return FALSE; +} + +static gboolean +mono_get_portable_ip (intptr_t in_ip, intptr_t *out_ip, const char **out_module, char *out_name) +{ + // Note: it's not safe for us to be interrupted while inside of dl_addr, because if we + // try to call dl_addr while interrupted while inside the lock, we will try to take a + // non-recursive lock twice on this thread, and will deadlock. + Dl_info info; + gboolean success = dladdr ((void*)in_ip, &info); if (!success) return FALSE; - if ((intptr_t) info.dli_fbase == this_module) { - // FIXME: Make generalize away from llvm tools? - // So lldb starts the pointer base at 0x100000000 - // and expects to get pointers as (offset + constant) - // - // Quirk shared by: - // /usr/bin/symbols -- symbols version: @(#)PROGRAM:symbols PROJECT:SamplingTools-63501 - // *CoreSymbolicationDT.framework version: 63750*/ - intptr_t offset = in_ip - this_module; - intptr_t magic_value = offset + 0x100000000; + if (!check_whitelisted_module (info.dli_fname, out_module)) + return FALSE; - *out_ip = magic_value; - } + *out_ip = mono_make_portable_ip (in_ip, (intptr_t) info.dli_fbase); #ifndef MONO_PRIVATE_CRASHES if (info.dli_saddr && out_name) @@ -1386,27 +1484,38 @@ summarize_offset_rich_hash (guint64 accum, MonoFrameSummary *frame) } static gboolean -summarize_frame_internal (MonoMethod *method, gpointer ip, size_t native_offset, gboolean managed, gpointer user_data) +summarize_frame_internal (MonoMethod *method, gpointer ip, size_t native_offset, int il_offset, gboolean managed, gpointer user_data) { MonoSummarizeUserData *ud = (MonoSummarizeUserData *) user_data; - g_assert (ud->num_frames + 1 < ud->max_frames); + + gboolean valid_state = ud->num_frames + 1 < ud->max_frames; + if (!valid_state) { + ud->error = "Exceeded the maximum number of frames"; + return TRUE; + } + MonoFrameSummary *dest = &ud->frames [ud->num_frames]; - ud->num_frames++; dest->unmanaged_data.ip = (intptr_t) ip; dest->is_managed = managed; - if (method && method->wrapper_type != MONO_WRAPPER_NONE) { + if (method && method->wrapper_type != MONO_WRAPPER_NONE && method->wrapper_type < MONO_WRAPPER_NUM) { dest->is_managed = FALSE; dest->unmanaged_data.has_name = TRUE; - - copy_summary_string_safe (dest->str_descr, mono_method_full_name (method, TRUE)); + copy_summary_string_safe (dest->str_descr, mono_wrapper_type_to_str (method->wrapper_type)); } - - MonoDebugSourceLocation *location = NULL; + +#ifndef MONO_PRIVATE_CRASHES + if (method) + dest->managed_data.name = (char *) method->name; +#endif if (managed) { - g_assert (method); + if (!method) { + ud->error = "Managed method frame, but no provided managed method"; + return TRUE; + } + MonoImage *image = mono_class_get_image (method->klass); // Used for hashing, more stable across rebuilds than using GUID copy_summary_string_safe (dest->str_descr, image->assembly_name); @@ -1415,40 +1524,62 @@ summarize_frame_internal (MonoMethod *method, gpointer ip, size_t native_offset, dest->managed_data.native_offset = native_offset; dest->managed_data.token = method->token; - location = mono_debug_lookup_source_location (method, native_offset, mono_domain_get ()); + dest->managed_data.il_offset = il_offset; } else { dest->managed_data.token = -1; } - if (location) { - dest->managed_data.il_offset = location->il_offset; - - mono_debug_free_source_location (location); - } ud->hashes->offset_free_hash = summarize_offset_free_hash (ud->hashes->offset_free_hash, dest); ud->hashes->offset_rich_hash = summarize_offset_rich_hash (ud->hashes->offset_rich_hash, dest); + // We return FALSE, so we're continuing walking + // And we increment the pointer because we're done with this cell in the array + ud->num_frames++; return FALSE; } static gboolean +summarize_frame_managed_walk (MonoMethod *method, gpointer ip, size_t native_offset, gboolean managed, gpointer user_data) +{ + int il_offset = -1; + + if (managed && method) { + MonoDebugSourceLocation *location = mono_debug_lookup_source_location (method, native_offset, mono_domain_get ()); + if (location) { + il_offset = location->il_offset; + mono_debug_free_source_location (location); + } + } + + return summarize_frame_internal (method, ip, native_offset, il_offset, managed, user_data); +} + + +static gboolean summarize_frame (StackFrameInfo *frame, MonoContext *ctx, gpointer data) { + MonoSummarizeUserData *ud = (MonoSummarizeUserData *) data; // Don't record trampolines between managed frames if (frame->ji && frame->ji->is_trampoline) return TRUE; - MonoMethod *method = NULL; + if (frame->ji && (frame->ji->is_trampoline || frame->ji->async)) + return FALSE; // Keep unwinding + intptr_t ip = 0x0; - mono_get_portable_ip ((intptr_t) MONO_CONTEXT_GET_IP (ctx), &ip, NULL); + mono_get_portable_ip ((intptr_t) MONO_CONTEXT_GET_IP (ctx), &ip, NULL, NULL); + // Don't need to handle return status "success" because this ip is stored below only, NULL is okay + gboolean is_managed = (frame->type == FRAME_TYPE_MANAGED || frame->type == FRAME_TYPE_INTERP); + MonoMethod *method = NULL; if (frame && frame->ji && frame->type != FRAME_TYPE_TRAMPOLINE) method = jinfo_get_method (frame->ji); - gboolean is_managed = (method != NULL); + if (is_managed) + method = jinfo_get_method (frame->ji); - return summarize_frame_internal (method, (gpointer) ip, frame->native_offset, is_managed, data); + return summarize_frame_internal (method, (gpointer) ip, frame->native_offset, frame->il_offset, is_managed, data); } static void @@ -1463,13 +1594,13 @@ mono_summarize_exception (MonoException *exc, MonoThreadSummary *out) data.frames = out->managed_frames; data.hashes = &out->hashes; - mono_exception_walk_trace (exc, summarize_frame_internal, &data); + mono_exception_walk_trace (exc, summarize_frame_managed_walk, &data); out->num_managed_frames = data.num_frames; } static void -mono_summarize_stack (MonoDomain *domain, MonoThreadSummary *out, MonoContext *crash_ctx) +mono_summarize_managed_stack (MonoThreadSummary *out) { MonoSummarizeUserData data; memset (&data, 0, sizeof (MonoSummarizeUserData)); @@ -1484,9 +1615,18 @@ mono_summarize_stack (MonoDomain *domain, MonoThreadSummary *out, MonoContext *c // // Summarize managed stack // - mono_walk_stack_with_ctx (summarize_frame, crash_ctx, MONO_UNWIND_LOOKUP_IL_OFFSET, &data); + mono_walk_stack_full (summarize_frame, out->ctx, out->domain, out->jit_tls, out->lmf, MONO_UNWIND_LOOKUP_IL_OFFSET, &data, TRUE); out->num_managed_frames = data.num_frames; + if (data.error != NULL) + out->error_msg = data.error; +} + +// Always runs on the dumped thread +static void +mono_summarize_unmanaged_stack (MonoThreadSummary *out) +{ + MONO_ARCH_CONTEXT_DEF // // Summarize unmanaged stack // @@ -1497,8 +1637,9 @@ mono_summarize_stack (MonoDomain *domain, MonoThreadSummary *out, MonoContext *c for (int i =0; i < out->num_unmanaged_frames; ++i) { intptr_t ip = frame_ips [i]; + MonoFrameSummary *frame = &out->unmanaged_frames [i]; - int success = mono_get_portable_ip (ip, &out->unmanaged_frames [i].unmanaged_data.ip, (char *) &out->unmanaged_frames [i].str_descr); + int success = mono_get_portable_ip (ip, &frame->unmanaged_data.ip, &frame->unmanaged_data.module, (char *) frame->str_descr); if (!success) continue; @@ -1507,6 +1648,18 @@ mono_summarize_stack (MonoDomain *domain, MonoThreadSummary *out, MonoContext *c } #endif + out->lmf = mono_get_lmf (); + + MonoThreadInfo *thread = mono_thread_info_current_unchecked (); + out->jit_tls = thread->jit_data; + out->domain = mono_domain_get (); + + if (!out->ctx) { + out->ctx = &out->ctx_mem; + mono_arch_flush_register_windows (); + MONO_INIT_CONTEXT_FROM_FUNC (out->ctx, mono_summarize_unmanaged_stack); + } + return; } #endif diff --git a/mono/mini/mini-posix.c b/mono/mini/mini-posix.c index 5340d46fec4..132163d478d 100644 --- a/mono/mini/mini-posix.c +++ b/mono/mini/mini-posix.c @@ -229,15 +229,12 @@ MONO_SIG_HANDLER_FUNC (static, sigterm_signal_handler) MONO_SIG_HANDLER_GET_CONTEXT; #ifndef DISABLE_CRASH_REPORTING - // Note: this function only returns for a single thread - // When it's invoked on other threads once the dump begins, - // those threads perform their dumps and then sleep until we - // die. The dump ends with the exit(1) below + // Note: this is only run from the non-controlling thread MonoContext mctx; gchar *output = NULL; MonoStackHash hashes; mono_sigctx_to_monoctx (ctx, &mctx); - if (!mono_threads_summarize (&mctx, &output, &hashes)) + if (!mono_threads_summarize_execute (&mctx, &output, &hashes, FALSE, NULL, 0)) g_assert_not_reached (); #ifdef TARGET_OSX @@ -247,14 +244,13 @@ MONO_SIG_HANDLER_FUNC (static, sigterm_signal_handler) } else #endif { - // Only the dumping-supervisor thread exits mono_thread_summarize - MOSTLY_ASYNC_SAFE_PRINTF("Unhandled exception dump: \n######\n%s\n######\n", output); - sleep (3); + // Controlling thread gets the dump + if (output) + MOSTLY_ASYNC_SAFE_PRINTF("Unhandled exception dump: \n######\n%s\n######\n", output); } #endif mono_chain_signal (MONO_SIG_HANDLER_PARAMS); - exit (1); } #if (defined (USE_POSIX_BACKEND) && defined (SIGRTMIN)) || defined (SIGPROF) @@ -414,8 +410,14 @@ void mini_register_sigterm_handler (void) { #ifndef DISABLE_CRASH_REPORTING - /* always catch SIGTERM, conditionals inside of handler */ - add_signal_handler (SIGTERM, sigterm_signal_handler, 0); + static gboolean enabled; + + if (!enabled) { + enabled = TRUE; + + /* always catch SIGTERM, conditionals inside of handler */ + add_signal_handler (SIGTERM, sigterm_signal_handler, 0); + } #endif } @@ -1022,8 +1024,14 @@ dump_native_stacktrace (const char *signal, void *ctx) if (!leave) { mono_sigctx_to_monoctx (ctx, &mctx); // Do before forking - if (!mono_threads_summarize (&mctx, &output, &hashes)) + if (!mono_threads_summarize (&mctx, &output, &hashes, FALSE, TRUE, NULL, 0)) g_assert_not_reached (); + + // Wait for the other threads to clean up and exit their handlers + // We can't lock / wait indefinitely, in case one of these threads got stuck somehow + // while dumping. + mono_runtime_printf_err ("\nWaiting for dumping threads to resume\n"); + sleep (1); } // We want our crash, and don't have telemetry diff --git a/mono/mini/mini-runtime.c b/mono/mini/mini-runtime.c index 15e43cd0349..0aca3667cd1 100644 --- a/mono/mini/mini-runtime.c +++ b/mono/mini/mini-runtime.c @@ -2544,6 +2544,8 @@ mono_jit_free_method (MonoDomain *domain, MonoMethod *method) g_hash_table_remove (info->jump_trampoline_hash, method); g_hash_table_remove (info->seq_points, method); + ji->ji->seq_points = NULL; + /* requires the domain lock - took above */ mono_conc_hashtable_remove (info->runtime_invoke_hash, method); @@ -2594,6 +2596,36 @@ mono_jit_free_method (MonoDomain *domain, MonoMethod *method) } gpointer +mono_jit_search_all_backends_for_jit_info (MonoDomain *domain, MonoMethod *method, MonoJitInfo **out_ji) +{ + gpointer code; + MonoJitInfo *ji; + + code = mono_jit_find_compiled_method_with_jit_info (domain, method, &ji); + if (!code) { + ERROR_DECL_VALUE (oerror); + + /* Might be AOTed code */ + mono_class_init (method->klass); + code = mono_aot_get_method (domain, method, &oerror); + if (code) { + mono_error_assert_ok (&oerror); + ji = mono_jit_info_table_find (domain, code); + } else { + if (!is_ok (&oerror)) + mono_error_cleanup (&oerror); + + /* Might be interpreted */ + ji = mini_get_interp_callbacks ()->find_jit_info (domain, method); + } + } + + *out_ji = ji; + + return code; +} + +gpointer mono_jit_find_compiled_method_with_jit_info (MonoDomain *domain, MonoMethod *method, MonoJitInfo **ji) { MonoDomain *target_domain; @@ -4319,7 +4351,7 @@ mini_init (const char *filename, const char *runtime_version) #endif callbacks.get_weak_field_indexes = mono_aot_get_weak_field_indexes; -#ifdef TARGET_OSX +#ifndef DISABLE_CRASH_REPORTING callbacks.install_state_summarizer = mini_register_sigterm_handler; #endif diff --git a/mono/mini/mini-runtime.h b/mono/mini/mini-runtime.h index 915b0dedd53..ce49a570ee6 100644 --- a/mono/mini/mini-runtime.h +++ b/mono/mini/mini-runtime.h @@ -451,6 +451,7 @@ MonoJumpInfoToken* mono_jump_info_token_new2 (MonoMemPool *mp, MonoImage *image, gpointer mono_resolve_patch_target (MonoMethod *method, MonoDomain *domain, guint8 *code, MonoJumpInfo *patch_info, gboolean run_cctors, MonoError *error) MONO_LLVM_INTERNAL; void mini_register_jump_site (MonoDomain *domain, MonoMethod *method, gpointer ip); void mini_patch_jump_sites (MonoDomain *domain, MonoMethod *method, gpointer addr); +gpointer mono_jit_search_all_backends_for_jit_info (MonoDomain *domain, MonoMethod *method, MonoJitInfo **ji); gpointer mono_jit_find_compiled_method_with_jit_info (MonoDomain *domain, MonoMethod *method, MonoJitInfo **ji); gpointer mono_jit_find_compiled_method (MonoDomain *domain, MonoMethod *method); gpointer mono_jit_compile_method (MonoMethod *method, MonoError *error); diff --git a/mono/mini/mini.c b/mono/mini/mini.c index c102737b2f4..d3e013c0ebc 100644 --- a/mono/mini/mini.c +++ b/mono/mini/mini.c @@ -3874,7 +3874,7 @@ mini_method_compile (MonoMethod *method, guint32 opts, MonoDomain *domain, JitFl } MONO_TIME_TRACK (mono_jit_stats.jit_gc_create_gc_map, mini_gc_create_gc_map (cfg)); - MONO_TIME_TRACK (mono_jit_stats.jit_save_seq_point_info, mono_save_seq_point_info (cfg)); + MONO_TIME_TRACK (mono_jit_stats.jit_save_seq_point_info, mono_save_seq_point_info (cfg, cfg->jit_info)); if (!cfg->compile_aot) { mono_save_xdebug_info (cfg); diff --git a/mono/mini/seq-points.c b/mono/mini/seq-points.c index 603f22d5e08..5081f2bab1f 100644 --- a/mono/mini/seq-points.c +++ b/mono/mini/seq-points.c @@ -99,7 +99,7 @@ collect_pred_seq_points (MonoCompile *cfg, MonoBasicBlock *bb, MonoInst *ins, GS } void -mono_save_seq_point_info (MonoCompile *cfg) +mono_save_seq_point_info (MonoCompile *cfg, MonoJitInfo *jinfo) { MonoBasicBlock *bb; GSList *bb_seq_points, *l; @@ -244,6 +244,9 @@ mono_save_seq_point_info (MonoCompile *cfg) else mono_seq_point_info_free (cfg->seq_point_info); mono_domain_unlock (domain); + + g_assert (jinfo); + jinfo->seq_points = cfg->seq_point_info; } g_ptr_array_free (cfg->seq_points, TRUE); diff --git a/mono/mini/seq-points.h b/mono/mini/seq-points.h index e2a98696ef6..05ebaf63f45 100644 --- a/mono/mini/seq-points.h +++ b/mono/mini/seq-points.h @@ -10,7 +10,7 @@ #include <mono/metadata/seq-points-data.h> void -mono_save_seq_point_info (MonoCompile *cfg); +mono_save_seq_point_info (MonoCompile *cfg, MonoJitInfo *jinfo); MonoSeqPointInfo* mono_get_seq_points (MonoDomain *domain, MonoMethod *method); diff --git a/mono/utils/mono-state.c b/mono/utils/mono-state.c index 736c667990e..c981664abdb 100644 --- a/mono/utils/mono-state.c +++ b/mono/utils/mono-state.c @@ -26,21 +26,44 @@ extern GCStats mono_gc_stats; #include <mach/task_info.h> #endif -#define MONO_MAX_SUMMARY_LEN 900 +#define MONO_MAX_SUMMARY_LEN 2500 +static gchar output_dump_str [MONO_MAX_SUMMARY_LEN]; + static JsonWriter writer; static GString static_gstr; -static char output_dump_str [MONO_MAX_SUMMARY_LEN]; -static void mono_json_writer_init_static (void) { +static void mono_json_writer_init_memory (gchar *output_dump_str, int len) +{ + memset (&static_gstr, 0, sizeof (static_gstr)); + memset (&writer, 0, sizeof (writer)); + memset (output_dump_str, 0, len * sizeof (gchar)); + static_gstr.len = 0; - static_gstr.allocated_len = MONO_MAX_SUMMARY_LEN; + static_gstr.allocated_len = len; static_gstr.str = output_dump_str; - memset (output_dump_str, 0, sizeof (output_dump_str)); writer.indent = 0; writer.text = &static_gstr; } +static void mono_json_writer_init_with_static (void) +{ + return mono_json_writer_init_memory (output_dump_str, MONO_MAX_SUMMARY_LEN); +} + +static void assert_has_space (void) +{ + // Each individual key/value append should be roughly less than this many characters + const int margin = 35; + + // Not using static, exit + if (static_gstr.allocated_len == 0) + return; + + if (static_gstr.allocated_len - static_gstr.len < margin) + g_error ("Ran out of space to create crash dump json blob."); +} + static void mono_native_state_add_ctx (JsonWriter *writer, MonoContext *ctx) { @@ -49,14 +72,17 @@ mono_native_state_add_ctx (JsonWriter *writer, MonoContext *ctx) mono_json_writer_object_key(writer, "ctx"); mono_json_writer_object_begin(writer); + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "IP"); mono_json_writer_printf (writer, "\"%p\",\n", (gpointer) MONO_CONTEXT_GET_IP (ctx)); + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "SP"); mono_json_writer_printf (writer, "\"%p\",\n", (gpointer) MONO_CONTEXT_GET_SP (ctx)); + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "BP"); mono_json_writer_printf (writer, "\"%p\"\n", (gpointer) MONO_CONTEXT_GET_BP (ctx)); @@ -74,37 +100,53 @@ mono_native_state_add_frame (JsonWriter *writer, MonoFrameSummary *frame) mono_json_writer_object_begin(writer); if (frame->is_managed) { + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "is_managed"); mono_json_writer_printf (writer, "\"%s\",\n", frame->is_managed ? "true" : "false"); } if (frame->unmanaged_data.is_trampoline) { + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "is_trampoline"); mono_json_writer_printf (writer, "\"true\","); } if (frame->is_managed) { + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "guid"); mono_json_writer_printf (writer, "\"%s\",\n", frame->managed_data.guid); + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "token"); mono_json_writer_printf (writer, "\"0x%05x\",\n", frame->managed_data.token); + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "native_offset"); mono_json_writer_printf (writer, "\"0x%x\",\n", frame->managed_data.native_offset); +#ifndef MONO_PRIVATE_CRASHES + if (frame->managed_data.name != NULL) { + assert_has_space (); + mono_json_writer_indent (writer); + mono_json_writer_object_key(writer, "method_name"); + mono_json_writer_printf (writer, "\"%s\",\n", frame->managed_data.name); + } +#endif + + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "il_offset"); mono_json_writer_printf (writer, "\"0x%05x\"\n", frame->managed_data.il_offset); } else { + assert_has_space (); mono_json_writer_indent (writer); - mono_json_writer_object_key(writer, "native_address"); + mono_json_writer_object_key (writer, "native_address"); if (frame->unmanaged_data.ip) mono_json_writer_printf (writer, "\"%p\"", (void *) frame->unmanaged_data.ip); else @@ -113,6 +155,7 @@ mono_native_state_add_frame (JsonWriter *writer, MonoFrameSummary *frame) if (frame->unmanaged_data.has_name) { mono_json_writer_printf (writer, ",\n"); + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "unmanaged_name"); mono_json_writer_printf (writer, "\"%s\"\n", frame->str_descr); @@ -149,6 +192,8 @@ mono_native_state_add_frames (JsonWriter *writer, int num_frames, MonoFrameSumma void mono_native_state_add_thread (JsonWriter *writer, MonoThreadSummary *thread, MonoContext *ctx, gboolean first_thread) { + assert_has_space (); + if (!first_thread) { mono_json_writer_printf (writer, ",\n"); } @@ -156,24 +201,36 @@ mono_native_state_add_thread (JsonWriter *writer, MonoThreadSummary *thread, Mon mono_json_writer_indent (writer); mono_json_writer_object_begin(writer); + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "is_managed"); mono_json_writer_printf (writer, "%s,\n", thread->is_managed ? "true" : "false"); + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "managed_thread_ptr"); mono_json_writer_printf (writer, "\"0x%x\",\n", (gpointer) thread->managed_thread_ptr); + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "thread_info_addr"); mono_json_writer_printf (writer, "\"0x%x\",\n", (gpointer) thread->info_addr); - if (thread->name) { + if (thread->error_msg != NULL) { + assert_has_space (); + mono_json_writer_indent (writer); + mono_json_writer_object_key(writer, "dumping_error"); + mono_json_writer_printf (writer, "\"%s\",\n", thread->error_msg); + } + + if (thread->name [0] != '\0') { + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "thread_name"); mono_json_writer_printf (writer, "\"%s\",\n", thread->name); } + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "native_thread_id"); mono_json_writer_printf (writer, "\"0x%x\",\n", (gpointer) thread->native_thread_id); @@ -227,6 +284,7 @@ mono_native_state_add_ee_info (JsonWriter *writer) /*aot_mode = "error";*/ /*}*/ + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "execution_context"); mono_json_writer_object_begin(writer); @@ -239,6 +297,7 @@ mono_native_state_add_ee_info (JsonWriter *writer) /*mono_json_writer_object_key(writer, "mono_use_llvm");*/ /*mono_json_writer_printf (writer, "\"%s\",\n", mono_use_llvm ? "true" : "false");*/ + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "coop-enabled"); mono_json_writer_printf (writer, "\"%s\"\n", mono_threads_is_cooperative_suspension_enabled () ? "true" : "false"); @@ -259,10 +318,12 @@ mono_native_state_add_ee_info (JsonWriter *writer) static void mono_native_state_add_version (JsonWriter *writer) { + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "configuration"); mono_json_writer_object_begin(writer); + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "version"); @@ -270,6 +331,7 @@ mono_native_state_add_version (JsonWriter *writer) mono_json_writer_printf (writer, "\"%s\",\n", build); g_free (build); + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "tlc"); #ifdef MONO_KEYWORD_THREAD @@ -278,6 +340,7 @@ mono_native_state_add_version (JsonWriter *writer) mono_json_writer_printf (writer, "\"normal\",\n"); #endif /* MONO_KEYWORD_THREAD */ + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "sigsgev"); #ifdef MONO_ARCH_SIGSEGV_ON_ALTSTACK @@ -286,6 +349,7 @@ mono_native_state_add_version (JsonWriter *writer) mono_json_writer_printf (writer, "\"normal\",\n"); #endif + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "notifications"); #ifdef HAVE_EPOLL @@ -296,14 +360,17 @@ mono_native_state_add_version (JsonWriter *writer) mono_json_writer_printf (writer, "\"thread+polling\",\n"); #endif + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "architecture"); mono_json_writer_printf (writer, "\"%s\",\n", MONO_ARCHITECTURE); + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "disabled_features"); mono_json_writer_printf (writer, "\"%s\",\n", DISABLED_FEATURES); + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "smallconfig"); #ifdef MONO_SMALL_CONFIG @@ -312,6 +379,7 @@ mono_native_state_add_version (JsonWriter *writer) mono_json_writer_printf (writer, "\"disabled\",\n"); #endif + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "bigarrays"); #ifdef MONO_BIG_ARRAYS @@ -320,6 +388,7 @@ mono_native_state_add_version (JsonWriter *writer) mono_json_writer_printf (writer, "\"disabled\",\n"); #endif + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "softdebug"); #if !defined(DISABLE_SDB) @@ -328,6 +397,7 @@ mono_native_state_add_version (JsonWriter *writer) mono_json_writer_printf (writer, "\"disabled\",\n"); #endif + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "interpreter"); #ifndef DISABLE_INTERPRETER @@ -336,6 +406,7 @@ mono_native_state_add_version (JsonWriter *writer) mono_json_writer_printf (writer, "\"disabled\",\n"); #endif + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "llvm_support"); #ifdef MONO_ARCH_LLVM_SUPPORTED @@ -347,11 +418,13 @@ mono_native_state_add_version (JsonWriter *writer) #endif const char *susp_policy = mono_threads_suspend_policy_name (); + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key (writer, "suspend"); mono_json_writer_printf (writer, "\"%s\"\n", susp_policy); + assert_has_space (); mono_json_writer_indent_pop (writer); mono_json_writer_indent (writer); mono_json_writer_object_end (writer); @@ -361,6 +434,7 @@ mono_native_state_add_version (JsonWriter *writer) static void mono_native_state_add_memory (JsonWriter *writer) { + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "memory"); mono_json_writer_object_begin(writer); @@ -372,10 +446,12 @@ mono_native_state_add_memory (JsonWriter *writer) task_name_t task = mach_task_self (); task_info(task, TASK_BASIC_INFO, (task_info_t) &t_info, &t_info_count); + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "Resident Size"); mono_json_writer_printf (writer, "\"%lu\",\n", t_info.resident_size); + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "Virtual Size"); mono_json_writer_printf (writer, "\"%lu\",\n", t_info.virtual_size); @@ -384,22 +460,27 @@ mono_native_state_add_memory (JsonWriter *writer) GCStats stats; memcpy (&stats, &mono_gc_stats, sizeof (GCStats)); + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "minor_gc_time"); mono_json_writer_printf (writer, "\"%lu\",\n", stats.minor_gc_time); + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "major_gc_time"); mono_json_writer_printf (writer, "\"%lu\",\n", stats.major_gc_time); + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "minor_gc_count"); mono_json_writer_printf (writer, "\"%lu\",\n", stats.minor_gc_count); + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "major_gc_count"); mono_json_writer_printf (writer, "\"%lu\",\n", stats.major_gc_count); + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "major_gc_time_concurrent"); mono_json_writer_printf (writer, "\"%lu\"\n", stats.major_gc_time_concurrent); @@ -413,9 +494,9 @@ mono_native_state_add_memory (JsonWriter *writer) static void mono_native_state_add_prologue (JsonWriter *writer) { - mono_json_writer_init (writer); mono_json_writer_object_begin(writer); + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "protocol_version"); mono_json_writer_printf (writer, "\"%s\",\n", MONO_NATIVE_STATE_PROTOCOL_VERSION); @@ -430,6 +511,7 @@ mono_native_state_add_prologue (JsonWriter *writer) const char *assertion_msg = g_get_assertion_message (); if (assertion_msg != NULL) { + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "assertion_message"); @@ -445,6 +527,7 @@ mono_native_state_add_prologue (JsonWriter *writer) } // Start threads array + assert_has_space (); mono_json_writer_indent (writer); mono_json_writer_object_key(writer, "threads"); mono_json_writer_array_begin (writer); @@ -491,9 +574,14 @@ mono_native_state_free (JsonWriter *writer, gboolean free_data) } void -mono_summarize_native_state_begin (void) +mono_summarize_native_state_begin (gchar *mem, int size) { - mono_json_writer_init_static (); + // Shared global mutable memory, only use when VM crashing + if (!mem) + mono_json_writer_init_with_static (); + else + mono_json_writer_init_memory (mem, size); + mono_native_state_init (&writer); } @@ -522,7 +610,7 @@ mono_crash_dump (const char *jsonFile, MonoStackHash *hashes) // Save up to 100 dump files for a given stacktrace hash for (int increment = 0; increment < 100; increment++) { FILE* fp; - char *name = g_strdup_printf ("mono_crash.%" PRIx64 " .%d.json", hashes->offset_free_hash, increment); + char *name = g_strdup_printf ("mono_crash.%" PRIx64 ".%d.json", hashes->offset_free_hash, increment); if ((fp = fopen (name, "ab"))) { if (ftell (fp) == 0) { diff --git a/mono/utils/mono-state.h b/mono/utils/mono-state.h index 0c646ca0006..d69dbf4ab3f 100644 --- a/mono/utils/mono-state.h +++ b/mono/utils/mono-state.h @@ -27,7 +27,7 @@ MONO_BEGIN_DECLS */ void -mono_summarize_native_state_begin (void); +mono_summarize_native_state_begin (char *mem, int size); char * mono_summarize_native_state_end (void); diff --git a/mono/utils/mono-threads-posix.c b/mono/utils/mono-threads-posix.c index fa2169ffd45..2aabb05debe 100644 --- a/mono/utils/mono-threads-posix.c +++ b/mono/utils/mono-threads-posix.c @@ -211,6 +211,19 @@ mono_native_thread_create (MonoNativeThreadId *tid, gpointer func, gpointer arg) return pthread_create (tid, NULL, (void *(*)(void *)) func, arg) == 0; } +size_t +mono_native_thread_get_name (MonoNativeThreadId tid, char *name_out, size_t max_len) +{ +#ifdef HAVE_PTHREAD_GETNAME_NP + int error = pthread_getname_np(tid, name_out, max_len); + if (error != 0) + return 0; + return strlen(name_out); +#else + return 0; +#endif +} + void mono_native_thread_set_name (MonoNativeThreadId tid, const char *name) { diff --git a/mono/utils/mono-threads.h b/mono/utils/mono-threads.h index f02437e1c71..7a94b5b3423 100644 --- a/mono/utils/mono-threads.h +++ b/mono/utils/mono-threads.h @@ -620,6 +620,9 @@ mono_native_thread_create (MonoNativeThreadId *tid, T func, gpointer arg) MONO_API void mono_native_thread_set_name (MonoNativeThreadId tid, const char *name); +size_t +mono_native_thread_get_name (MonoNativeThreadId tid, char *name_out, size_t max_len); + MONO_API gboolean mono_native_thread_join (MonoNativeThreadId tid); |