-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add some tests that validate debug info through ETW events (#61962)
Validate the debug mappings generated by the JIT using the MethodILToNative event. Unfortunately we can not use EventListener as the event there does not contain the actual mappings (#12678) so this reuses some of the facilities from the tracing tests to use EventPipe and TraceEvent. This only adds the infrastructure and a small number of tests, but at least this should make it easier to add more tests in this area in the future. There are some more limitations, for example we cannot validate the CALL_INSTRUCTION mappings generated for the managed return value feature because the debugger filters them out of the table reported. I am hoping we can change these mappings to be included as normal in the future. The tests themselves are added by adding a method to tests.il with an ExpectedILMappings attribute that allows specifying a subset of IL offsets that we expect mappings to be generated for, with separate subsets under Debug and when optimizing. This was the best way I could think of to be able to refer to the right IL offsets.
- Loading branch information
1 parent
90773ac
commit a46358c
Showing
11 changed files
with
492 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
This directory contains tests for debugging information generated by the JIT. | ||
The tests are written in IL inside the tests.il file by creating a method in the | ||
DebugInfoMethods class and marking them with the ExpectedILMappings attribute. | ||
In that attribute, the IL offsets at which mappings are expected to be generated | ||
can be specified for both debug (DebuggableAttribute with | ||
DisableOptimizations) and optimized builds. | ||
|
||
To debug these tests, run the 'tester' project, which will JIT all methods in | ||
tests.il in both debug and release (you may need to turn off tiered | ||
compilation). | ||
|
||
* attribute.cs/csproj: Project containing ExpectedILMappingsAttribute, to avoid | ||
circular dependencies | ||
|
||
* tests.il: File containing the tests marked with ExpectedILMappings | ||
|
||
* tests_d.ilproj/tests_r.ilproj: Both these projects just add tests.il, the only | ||
difference is that the former has DebuggableAttribute with | ||
DisableOptimizations and the latter does not. | ||
|
||
* tester.cs/csproj: The orchestrator of the tests, references tests_d and | ||
tests_r and jits the test methods using RuntimeHelpers.PrepareMethod, collects | ||
the IL mappings emitted using runtime events, and validates that the mappings | ||
match the data in ExpectedILMappings. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
|
||
[AttributeUsage(AttributeTargets.Method)] | ||
public class ExpectedILMappings : Attribute | ||
{ | ||
public int[] Debug { get; set; } | ||
public int[] Opts { get; set; } | ||
} |
10 changes: 10 additions & 0 deletions
10
src/tests/JIT/Directed/debugging/debuginfo/attribute.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<OutputType>Library</OutputType> | ||
<Optimize>false</Optimize> | ||
<CLRTestKind>BuildOnly</CLRTestKind> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<Compile Include="$(MSBuildProjectName).cs" /> | ||
</ItemGroup> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
#define DEBUG |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
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<Provider> | ||
{ | ||
new Provider("Microsoft-Windows-DotNETRuntime", eventLevel: EventLevel.Verbose, keywords: (ulong)keywords) | ||
}; | ||
|
||
var config = new SessionConfiguration(1024, EventPipeSerializationFormat.NetTrace, dotnetRuntimeProvider); | ||
|
||
return | ||
IpcTraceTest.RunAndValidateEventCounts( | ||
new Dictionary<string, ExpectedEventCount>(), | ||
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<ExpectedILMappings>() != null) | ||
{ | ||
RuntimeHelpers.PrepareMethod(mi.MethodHandle); | ||
} | ||
} | ||
} | ||
|
||
private static Func<int> ValidateMappings(EventPipeEventSource source) | ||
{ | ||
List<(long MethodID, OptimizationTier Tier, (int ILOffset, int NativeOffset)[] Mappings)> methodsWithMappings = new(); | ||
Dictionary<long, OptimizationTier> methodTier = new(); | ||
|
||
source.Clr.MethodLoad += e => methodTier[e.MethodID] = e.OptimizationTier; | ||
source.Clr.MethodLoadVerbose += e => methodTier[e.MethodID] = e.OptimizationTier; | ||
source.Clr.MethodILToNativeMap += e => | ||
{ | ||
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<ExpectedILMappings>(); | ||
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<DebuggableAttribute>(); | ||
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*<object, IntPtr, MethodBase>)getMethodBaseMethod.MethodHandle .GetFunctionPointer(); | ||
} | ||
|
||
// Needed to go from MethodID -> MethodBase | ||
private static readonly delegate*<object, IntPtr, MethodBase> s_getMethodBaseByHandle; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<DebugType>PdbOnly</DebugType> | ||
<Optimize>True</Optimize> | ||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<ProjectReference Include="tests_d.ilproj" Aliases="tests_d" /> | ||
<ProjectReference Include="tests_r.ilproj" Aliases="tests_r" /> | ||
<ProjectReference Include="attribute.csproj" /> | ||
<ProjectReference Include="../../../../tracing/eventpipe/common/common.csproj" /> | ||
<Compile Include="$(MSBuildProjectName).cs" /> | ||
</ItemGroup> | ||
</Project> |
Oops, something went wrong.