From 6ea4cbcee040956cceaa53058cdb7de6529d2db0 Mon Sep 17 00:00:00 2001 From: monojenkins Date: Wed, 17 Jun 2020 17:46:44 -0400 Subject: [PATCH] [2020-02] [merp] Add API methods for getting hashcode/reason of last crash (#19978) * [merp] Add API methods for getting hashcode/reason of last crash * Add test * Review feedback and cleanup Co-authored-by: Alexis Christoforides --- mcs/class/corlib/Mono/Runtime.cs | 38 ++++++++++++++++++++++++++++++++++++++ mono/tests/merp-crash-test.cs | 38 +++++++++++++++++++++++++++++++++++--- 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/mcs/class/corlib/Mono/Runtime.cs b/mcs/class/corlib/Mono/Runtime.cs index f59a7e93891..c85a3a3b1f5 100644 --- a/mcs/class/corlib/Mono/Runtime.cs +++ b/mcs/class/corlib/Mono/Runtime.cs @@ -27,6 +27,7 @@ // using System; +using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -255,6 +256,43 @@ namespace Mono { } } + static string get_breadcrumb_value (string file_prefix, string directory_str, bool clear) + { + var allfiles = Directory.GetFiles (directory_str, $"{file_prefix}_*" ); + if (allfiles.Length == 0) + return string.Empty; + + if (allfiles.Length > 1) { + // it's impossible to tell which breadcrumb is the last one (let's not trust filesystem timestamps) + // delete the multiple files so at least next crash can make sense + try { + Array.ForEach (allfiles, f => File.Delete (f) ); + } catch (Exception) { } + + return string.Empty; + } + + if (clear) + File.Delete (allfiles [0]); + + var filename = Path.GetFileName (allfiles [0]); + return filename.Substring (file_prefix.Length + 1); + } + + static long CheckCrashReportHash (string directory_str, bool clear) + { + var value = get_breadcrumb_value ("crash_hash", directory_str, clear); + if (value == string.Empty) + return 0; + else + return Convert.ToInt64 (value, 16); + } + + static string CheckCrashReportReason (string directory_str, bool clear) + { + return get_breadcrumb_value ("crash_reason", directory_str, clear); + } + [MethodImplAttribute (MethodImplOptions.InternalCall)] static extern void AnnotateMicrosoftTelemetry_internal (IntPtr key, IntPtr val); diff --git a/mono/tests/merp-crash-test.cs b/mono/tests/merp-crash-test.cs index a33010e59cf..d534a64fae5 100644 --- a/mono/tests/merp-crash-test.cs +++ b/mono/tests/merp-crash-test.cs @@ -65,6 +65,7 @@ class C Crashers.Add(new Crasher ("MerpCrashSignalKill", MerpCrashSignalBus)); Crashers.Add(new Crasher ("MerpCrashSignalSegv", MerpCrashSignalSegv)); Crashers.Add(new Crasher ("MerpCrashSignalIll", MerpCrashSignalIll)); + Crashers.Add(new Crasher ("MerpCrashTestBreadcrumbs", MerpCrashTestBreadcrumbs, validator: ValidateBreadcrumbs)); } public static void @@ -88,6 +89,22 @@ class C throw new ValidationException (String.Format ("incorrect fail fast message (expected: {0}, got: {1})", failfastMsg, s)); } + public static void ValidateBreadcrumbs (object json) + { + var monoType = Type.GetType ("Mono.Runtime", false); + var m = monoType.GetMethod ("CheckCrashReportReason", BindingFlags.NonPublic | BindingFlags.Static); + var m_params = new object [] { "./", false }; + string o = (string)m.Invoke(null, m_params); + if (o != "segv") + throw new Exception ("Crash report reason should be 'segv'"); + + m = monoType.GetMethod ("CheckCrashReportHash", BindingFlags.NonPublic | BindingFlags.Static); + long hash = (long)m.Invoke (null, m_params); + + if (hash == 0) + throw new Exception ("Crash hash should not be zero"); + } + [DllImport("libtest")] public static extern void mono_test_MerpCrashSnprintf (); @@ -222,6 +239,12 @@ class C mono_test_MerpCrashUnhandledExceptionHook (); } + public static void + MerpCrashTestBreadcrumbs () + { + mono_test_MerpCrashSignalSegv (); + } + private static object jsonGetKey (object o, string key) => (o as Dictionary)[key]; private static object jsonGetKeys (object o, params string[] keys) { @@ -273,8 +296,6 @@ class C public static void TestValidate (string configDir, bool silent, Action validator = null) { - DumpLogCheck (expected_level: "MerpInvoke"); // we are expecting merp invoke to fail - var xmlFilePath = String.Format("{0}CustomLogsMetadata.xml", configDir); var paramsFilePath = String.Format("{0}MERP.uploadparams.txt", configDir); var crashFilePath = String.Format("{0}lastcrashlog.txt", configDir); @@ -319,7 +340,7 @@ class C } catch (CrasherClass.ValidationException e) { throw new Exception (String.Format ("Validation failed '{0}', json: {1}", e.Message, crashFile)); } catch (Exception e) { - throw new Exception (String.Format ("Invalid json: {0}", crashFile)); + throw new Exception (String.Format ("Invalid json ({0}:{1}): {2}", e.GetType(), e.Message, crashFile)); } File.Delete (crashFilePath); @@ -328,6 +349,8 @@ class C Console.WriteLine ("Crash file {0} missing", crashFilePath); } + DumpLogCheck (expected_level: "MerpInvoke"); // we are expecting merp invoke to fail + if (!xmlFileExists) throw new Exception (String.Format ("Did not produce {0}", xmlFilePath)); @@ -368,6 +391,15 @@ class C if (expected_level != levels [result]) throw new Exception (String.Format ("Crash level {0} does not match expected {1}", levels [result], expected_level)); + + // also clear hash and reason breadcrumbs + convert = monoType.GetMethod("CheckCrashReportHash", BindingFlags.NonPublic | BindingFlags.Static); + var hash_result = (long) convert.Invoke(null, new object[] { "./", true }); + convert = monoType.GetMethod("CheckCrashReportReason", BindingFlags.NonPublic | BindingFlags.Static); + var reason_result = (string) convert.Invoke(null, new object[] { "./", true }); + + if (reason_result == string.Empty) + throw new Exception("Crash reason should not be an empty string"); } -- 2.11.4.GIT