diff --git a/src/tests/JIT/Directed/debugging/debuginfo/tester.cs b/src/tests/JIT/Directed/debugging/debuginfo/tester.cs new file mode 100644 index 00000000000000..b784fd4a6f56b0 --- /dev/null +++ b/src/tests/JIT/Directed/debugging/debuginfo/tester.cs @@ -0,0 +1,152 @@ +extern alias tests_d; +extern alias tests_r; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Tracing; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using Microsoft.Diagnostics.Tools.RuntimeClient; +using Microsoft.Diagnostics.Tracing; +using Microsoft.Diagnostics.Tracing.Parsers; +using Microsoft.Diagnostics.Tracing.Parsers.Clr; +using Tracing.Tests.Common; +using DebugInfoMethodsD = tests_d::DebugInfoMethods; +using DebugInfoMethodsR = tests_r::DebugInfoMethods; + +public unsafe class DebugInfoTest +{ + public static unsafe int Main() + { + var keywords = + ClrTraceEventParser.Keywords.Jit | ClrTraceEventParser.Keywords.JittedMethodILToNativeMap; + + var dotnetRuntimeProvider = new List + { + new Provider("Microsoft-Windows-DotNETRuntime", eventLevel: EventLevel.Verbose, keywords: (ulong)keywords) + }; + + var config = new SessionConfiguration(1024, EventPipeSerializationFormat.NetTrace, dotnetRuntimeProvider); + + return + IpcTraceTest.RunAndValidateEventCounts( + new Dictionary(), + JitMethods, + config, + ValidateMappings); + } + + private static void JitMethods() + { + ProcessType(typeof(DebugInfoMethodsD)); + ProcessType(typeof(DebugInfoMethodsR)); + } + + private static void ProcessType(Type t) + { + foreach (MethodInfo mi in t.GetMethods()) + { + if (mi.GetCustomAttribute() != null) + { + RuntimeHelpers.PrepareMethod(mi.MethodHandle); + } + } + } + + private static Func ValidateMappings(EventPipeEventSource source) + { + List<(long MethodID, OptimizationTier Tier, (int ILOffset, int NativeOffset)[] Mappings)> methodsWithMappings = new(); + Dictionary methodTier = new(); + + source.Clr.MethodLoad += e => methodTier[e.MethodID] = e.OptimizationTier; + source.Clr.MethodLoadVerbose += e => methodTier[e.MethodID] = e.OptimizationTier; + source.Clr.MethodILToNativeMap += e => + { + if (e.MethodID == 0) + return; + + var mappings = new (int, int)[e.CountOfMapEntries]; + for (int i = 0; i < mappings.Length; i++) + mappings[i] = (e.ILOffset(i), e.NativeOffset(i)); + + if (!methodTier.TryGetValue(e.MethodID, out OptimizationTier tier)) + tier = OptimizationTier.Unknown; + + methodsWithMappings.Add((e.MethodID, tier, mappings)); + }; + + return () => + { + int result = 100; + foreach ((long methodID, OptimizationTier tier, (int ILOffset, int NativeOffset)[] mappings) in methodsWithMappings) + { + MethodBase meth = s_getMethodBaseByHandle(null, (IntPtr)(void*)methodID); + ExpectedILMappings attrib = meth.GetCustomAttribute(); + if (attrib == null) + { + continue; + } + + string name = $"[{meth.DeclaringType.Assembly.GetName().Name}]{meth.DeclaringType.FullName}.{meth.Name}"; + + // If DebuggableAttribute is saying that the assembly must be debuggable, then verify debug mappings. + // Otherwise verify release mappings. + // This may seem a little strange since we do not use the tier at all -- however, we expect debug + // to never tier and in release, we expect the release mappings to be the "least common denominator", + // i.e. tier0 and tier1 mappings should both be a superset. + // Note that tier0 and MinOptJitted differs in mappings generated exactly due to DebuggableAttribute. + DebuggableAttribute debuggableAttrib = meth.DeclaringType.Assembly.GetCustomAttribute(); + bool debuggableMappings = debuggableAttrib != null && debuggableAttrib.IsJITOptimizerDisabled; + + Console.WriteLine("{0}: Validate mappings for {1} codegen (tier: {2})", name, debuggableMappings ? "debuggable" : "optimized", tier); + + int[] expected = debuggableMappings ? attrib.Debug : attrib.Opts; + if (expected == null) + { + continue; + } + + if (!ValidateSingle(expected, mappings)) + { + Console.WriteLine(" Validation failed: expected mappings at IL offsets {0}", string.Join(", ", expected.Select(il => $"{il:x3}"))); + Console.WriteLine(" Actual (IL <-> native):"); + foreach ((int ilOffset, int nativeOffset) in mappings) + { + string ilOffsetName = Enum.IsDefined((SpecialILOffset)ilOffset) ? ((SpecialILOffset)ilOffset).ToString() : $"{ilOffset:x3}"; + Console.WriteLine(" {0:x3} <-> {1:x3}", ilOffsetName, nativeOffset); + } + + result = -1; + } + } + + return result; + }; + } + + // Validate that all IL offsets we expected had mappings generated for them. + private static bool ValidateSingle(int[] expected, (int ILOffset, int NativeOffset)[] mappings) + { + return expected.All(il => mappings.Any(t => t.ILOffset == il)); + } + + private enum SpecialILOffset + { + NoMapping = -1, + Prolog = -2, + Epilog = -3, + } + + static DebugInfoTest() + { + Type runtimeMethodHandleInternalType = typeof(RuntimeMethodHandle).Assembly.GetType("System.RuntimeMethodHandleInternal"); + Type runtimeTypeType = typeof(RuntimeMethodHandle).Assembly.GetType("System.RuntimeType"); + MethodInfo getMethodBaseMethod = runtimeTypeType.GetMethod("GetMethodBase", BindingFlags.NonPublic | BindingFlags.Static, new[] { runtimeTypeType, runtimeMethodHandleInternalType }); + s_getMethodBaseByHandle = (delegate*)getMethodBaseMethod.MethodHandle .GetFunctionPointer(); + } + + // Needed to go from MethodID -> MethodBase + private static readonly delegate* s_getMethodBaseByHandle; +} diff --git a/src/tests/JIT/Directed/debugging/debuginfo/tester.csproj b/src/tests/JIT/Directed/debugging/debuginfo/tester.csproj new file mode 100644 index 00000000000000..2545e6ceb3c2aa --- /dev/null +++ b/src/tests/JIT/Directed/debugging/debuginfo/tester.csproj @@ -0,0 +1,17 @@ + + + Exe + PdbOnly + True + True + true + true + + + + + + + + + diff --git a/src/tests/tracing/eventpipe/gcdump/gcdump.cs b/src/tests/tracing/eventpipe/gcdump/gcdump.cs new file mode 100644 index 00000000000000..91750986d395a4 --- /dev/null +++ b/src/tests/tracing/eventpipe/gcdump/gcdump.cs @@ -0,0 +1,108 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics.Tracing; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Diagnostics.NETCore.Client; +using Microsoft.Diagnostics.Tools.RuntimeClient; +using Microsoft.Diagnostics.Tracing; +using Microsoft.Diagnostics.Tracing.Parsers; +using Tracing.Tests.Common; +using Microsoft.Diagnostics.Tracing.Parsers.Clr; + +namespace Tracing.Tests.EventSourceError +{ + // Regression test for https://github.com/dotnet/runtime/issues/38639 + public class GCDumpTest + { + private static int _bulkTypeCount = 0; + private static int _bulkNodeCount = 0; + private static int _bulkEdgeCount = 0; + private static int _bulkRootEdgeCount = 0; + private static int _bulkRootStaticVarCount = 0; + + private static readonly ulong GC_HeapDump_Keyword = 0x100000UL; + + public static int Main(string[] args) + { + // This test validates that if an EventSource generates an error + // during construction it gets emitted over EventPipe + + List providers = new List + { + new Provider("Microsoft-Windows-DotNETRuntime", eventLevel: EventLevel.Verbose, keywords: (ulong)ClrTraceEventParser.Keywords.GCHeapSnapshot) + }; + + var configuration = new SessionConfiguration(circularBufferSizeMB: 1024, format: EventPipeSerializationFormat.NetTrace, providers: providers); + return IpcTraceTest.RunAndValidateEventCounts(_expectedEventCounts, _eventGeneratingAction, configuration, _DoesRundownContainMethodEvents); + } + + private static Dictionary _expectedEventCounts = new Dictionary() + { + // This space intentionally left blank + }; + + private static Action _eventGeneratingAction = () => + { + // This space intentionally left blank + }; + + private static Func> _DoesRundownContainMethodEvents = (source) => + { + source.Clr.TypeBulkType += (GCBulkTypeTraceData data) => + { + _bulkTypeCount += data.Count; + }; + + source.Clr.GCBulkNode += delegate (GCBulkNodeTraceData data) + { + _bulkNodeCount += data.Count; + }; + + source.Clr.GCBulkEdge += (GCBulkEdgeTraceData data) => + { + _bulkEdgeCount += data.Count; + }; + + source.Clr.GCBulkRootEdge += (GCBulkRootEdgeTraceData data) => + { + _bulkRootEdgeCount += data.Count; + }; + + source.Clr.GCBulkRootStaticVar += (GCBulkRootStaticVarTraceData data) => + { + _bulkRootStaticVarCount += data.Count; + }; + + return () => + { + // Hopefully it is low enough to be resilient to changes in the runtime + // and high enough to catch issues. There should be between hundreds and thousands + // for each, but the number is variable and the point of the test is to verify + // that we get any events at all. + if (_bulkTypeCount > 50 + && _bulkNodeCount > 50 + && _bulkEdgeCount > 50 + && _bulkRootEdgeCount > 50 + && _bulkRootStaticVarCount > 50) + { + return 100; + } + + + Console.WriteLine($"Test failed due to missing GC heap events."); + Console.WriteLine($"_bulkTypeCount = {_bulkTypeCount}"); + Console.WriteLine($"_bulkNodeCount = {_bulkNodeCount}"); + Console.WriteLine($"_bulkEdgeCount = {_bulkEdgeCount}"); + Console.WriteLine($"_bulkRootEdgeCount = {_bulkRootEdgeCount}"); + Console.WriteLine($"_bulkRootStaticVarCount = {_bulkRootStaticVarCount}"); + return -1; + }; + }; + } +} diff --git a/src/tests/tracing/eventpipe/gcdump/gcdump.csproj b/src/tests/tracing/eventpipe/gcdump/gcdump.csproj new file mode 100644 index 00000000000000..26b0e4e38b2a2e --- /dev/null +++ b/src/tests/tracing/eventpipe/gcdump/gcdump.csproj @@ -0,0 +1,14 @@ + + + .NETCoreApp + exe + true + true + + true + + + + + +