From cef42d01225ae78a0ff21f56233bc3a04a271cab Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 27 Dec 2023 20:28:32 -0500 Subject: [PATCH] Update clrmd to 3.1 --- src/BenchmarkDotNet/BenchmarkDotNet.csproj | 2 +- .../Disassemblers/Arm64Disassembler.cs | 2 +- ...Disassembler.cs => ClrMdV3Disassembler.cs} | 93 +++++-------------- .../Disassemblers/IntelDisassembler.cs | 2 +- .../SameArchitectureDisassembler.cs | 6 +- 5 files changed, 31 insertions(+), 74 deletions(-) rename src/BenchmarkDotNet/Disassemblers/{ClrMdV2Disassembler.cs => ClrMdV3Disassembler.cs} (77%) diff --git a/src/BenchmarkDotNet/BenchmarkDotNet.csproj b/src/BenchmarkDotNet/BenchmarkDotNet.csproj index 12eeb1651a..e126626397 100644 --- a/src/BenchmarkDotNet/BenchmarkDotNet.csproj +++ b/src/BenchmarkDotNet/BenchmarkDotNet.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/BenchmarkDotNet/Disassemblers/Arm64Disassembler.cs b/src/BenchmarkDotNet/Disassemblers/Arm64Disassembler.cs index 16ebbde219..ce5176c4ca 100644 --- a/src/BenchmarkDotNet/Disassemblers/Arm64Disassembler.cs +++ b/src/BenchmarkDotNet/Disassemblers/Arm64Disassembler.cs @@ -139,7 +139,7 @@ public void Feed(Arm64Instruction instruction) public Arm64RegisterId RegisterId { get { return _registerId; } } } - internal class Arm64Disassembler : ClrMdV2Disassembler + internal class Arm64Disassembler : ClrMdV3Disassembler { internal sealed class RuntimeSpecificData { diff --git a/src/BenchmarkDotNet/Disassemblers/ClrMdV2Disassembler.cs b/src/BenchmarkDotNet/Disassemblers/ClrMdV3Disassembler.cs similarity index 77% rename from src/BenchmarkDotNet/Disassemblers/ClrMdV2Disassembler.cs rename to src/BenchmarkDotNet/Disassemblers/ClrMdV3Disassembler.cs index 16b3891d24..193de9ed07 100644 --- a/src/BenchmarkDotNet/Disassemblers/ClrMdV2Disassembler.cs +++ b/src/BenchmarkDotNet/Disassemblers/ClrMdV3Disassembler.cs @@ -11,8 +11,9 @@ namespace BenchmarkDotNet.Disassemblers { - // This Disassembler uses ClrMd v2x. Please keep it in sync with ClrMdV1Disassembler (if possible). - internal abstract class ClrMdV2Disassembler + // This Disassembler uses ClrMd v3x. Please keep it in sync with ClrMdV1Disassembler (if possible). + internal abstract class ClrMdV3Disassembler + { private static readonly ulong MinValidAddress = GetMinValidAddress(); @@ -64,7 +65,7 @@ internal DisassemblyResult AttachAndDisassemble(Settings settings) state.Todo.Enqueue( new MethodInfo( // the Disassembler Entry Method is always parameterless, so check by name is enough - typeWithBenchmark.Methods.Single(method => method.IsPublic && method.Name == settings.MethodName), + typeWithBenchmark.Methods.Single(method => method.Attributes.HasFlag(System.Reflection.MethodAttributes.Public) && method.Name == settings.MethodName), 0)); } @@ -149,9 +150,10 @@ private DisassembledMethod DisassembleMethod(MethodInfo methodInfo, State state, if (!CanBeDisassembled(method)) { - if (method.IsPInvoke) + if (method.Attributes.HasFlag(System.Reflection.MethodAttributes.PinvokeImpl)) return CreateEmpty(method, "PInvoke method"); - if (method.IL is null || method.IL.Length == 0) + var ilInfo = method.GetILInfo(); + if (ilInfo is null || ilInfo.Length == 0) return CreateEmpty(method, "Extern method"); if (method.CompilationType == MethodCompilationType.None) return CreateEmpty(method, "Method was not JITted yet."); @@ -214,60 +216,30 @@ private IEnumerable Decode(ILToNativeMap map, State state, int depth, ClrMe private static ILToNativeMap[] GetCompleteNativeMap(ClrMethod method, ClrRuntime runtime) { - if (!TryReadNativeCodeAddresses(runtime, method, out ulong startAddress, out ulong endAddress)) + // it's better to use one single map rather than few small ones + // it's simply easier to get next instruction when decoding ;) + + var hotColdInfo = method.HotColdInfo; + if (hotColdInfo.HotSize > 0 && hotColdInfo.HotStart > 0) { - startAddress = method.NativeCode; - endAddress = ulong.MaxValue; + return hotColdInfo.ColdSize <= 0 + ? new[] { new ILToNativeMap() { StartAddress = hotColdInfo.HotStart, EndAddress = hotColdInfo.HotStart + hotColdInfo.HotSize, ILOffset = -1 } } + : new[] + { + new ILToNativeMap() { StartAddress = hotColdInfo.HotStart, EndAddress = hotColdInfo.HotStart + hotColdInfo.HotSize, ILOffset = -1 }, + new ILToNativeMap() { StartAddress = hotColdInfo.ColdStart, EndAddress = hotColdInfo.ColdStart + hotColdInfo.ColdSize, ILOffset = -1 } + }; } - ILToNativeMap[] sortedMaps = method.ILOffsetMap // CanBeDisassembled ensures that there is at least one map in ILOffsetMap - .Where(map => map.StartAddress >= startAddress && map.StartAddress < endAddress) // can be false for Tier 0 maps, EndAddress is not checked on purpose here - .Where(map => map.StartAddress < map.EndAddress) // some maps have 0 length (they don't have corresponding assembly code?) + return method.ILOffsetMap + .Where(map => map.StartAddress < map.EndAddress) // some maps have 0 length? .OrderBy(map => map.StartAddress) // we need to print in the machine code order, not IL! #536 - .Select(map => new ILToNativeMap() - { - StartAddress = map.StartAddress, - // some maps have EndAddress > codeHeaderData.MethodStart + codeHeaderData.MethodSize and contain garbage (#2074). They need to be fixed! - EndAddress = Math.Min(map.EndAddress, endAddress), - ILOffset = map.ILOffset - }) .ToArray(); - - if (sortedMaps.Length == 0) - { - // In such situation ILOffsetMap most likely describes Tier 0, while CodeHeaderData Tier 1. - // Since we care about Tier 1 (if it's present), we "fake" a Tier 1 map. - return new[] { new ILToNativeMap() { StartAddress = startAddress, EndAddress = endAddress } }; - } - else if (sortedMaps[0].StartAddress != startAddress || (sortedMaps[sortedMaps.Length - 1].EndAddress != endAddress && endAddress != ulong.MaxValue)) - { - // In such situation ILOffsetMap most likely is missing few bytes. We just "extend" it to avoid producing "bad" instructions. - return new[] { new ILToNativeMap() { StartAddress = startAddress, EndAddress = endAddress } }; - } - - return sortedMaps; } private static DisassembledMethod CreateEmpty(ClrMethod method, string reason) => DisassembledMethod.Empty(method.Signature, method.NativeCode, reason); - protected static bool TryReadNativeCodeAddresses(ClrRuntime runtime, ClrMethod method, out ulong startAddress, out ulong endAddress) - { - if (method is not null - && runtime.DacLibrary.SOSDacInterface.GetCodeHeaderData(method.NativeCode, out var codeHeaderData) == HResult.S_OK - && codeHeaderData.MethodSize > 0) // false for extern methods! - { - // HotSize can be missing or be invalid (https://github.com/microsoft/clrmd/issues/1036). - // So we fetch the method size on our own. - startAddress = codeHeaderData.MethodStart; - endAddress = codeHeaderData.MethodStart + codeHeaderData.MethodSize; - return true; - } - - startAddress = endAddress = 0; - return false; - } - protected void TryTranslateAddressToName(ulong address, bool isAddressPrecodeMD, State state, int depth, ClrMethod currentMethod) { if (!IsValidAddress(address) || state.AddressToNameMapping.ContainsKey(address)) @@ -283,18 +255,10 @@ protected void TryTranslateAddressToName(ulong address, bool isAddressPrecodeMD, } var method = runtime.GetMethodByInstructionPointer(address); - if (method is null && (address & ((uint) runtime.DataTarget.DataReader.PointerSize - 1)) == 0) - { - if (runtime.DataTarget.DataReader.ReadPointer(address, out ulong newAddress) && IsValidAddress(newAddress)) - { - method = runtime.GetMethodByInstructionPointer(newAddress); - - method = WorkaroundGetMethodByInstructionPointerBug(runtime, method, newAddress); - } - } - else + if (method is null && (address & ((uint) runtime.DataTarget.DataReader.PointerSize - 1)) == 0 + && runtime.DataTarget.DataReader.ReadPointer(address, out ulong newAddress) && IsValidAddress(newAddress)) { - method = WorkaroundGetMethodByInstructionPointerBug(runtime, method, address); + method = runtime.GetMethodByInstructionPointer(newAddress); } if (method is null) @@ -313,7 +277,7 @@ protected void TryTranslateAddressToName(ulong address, bool isAddressPrecodeMD, return; } - var methodTableName = runtime.DacLibrary.SOSDacInterface.GetMethodTableName(address); + var methodTableName = runtime.GetTypeByMethodTable(address)?.Name; if (!string.IsNullOrEmpty(methodTableName)) { state.AddressToNameMapping.Add(address, $"MT_{methodTableName}"); @@ -349,13 +313,6 @@ protected void FlushCachedDataIfNeeded(IDataReader dataTargetDataReader, ulong a } } - // GetMethodByInstructionPointer sometimes returns wrong methods. - // In case given address does not belong to the methods range, null is returned. - private static ClrMethod WorkaroundGetMethodByInstructionPointerBug(ClrRuntime runtime, ClrMethod method, ulong newAddress) - => TryReadNativeCodeAddresses(runtime, method, out ulong startAddress, out ulong endAddress) && !(startAddress >= newAddress && newAddress <= endAddress) - ? null - : method; - private class SharpComparer : IEqualityComparer { public bool Equals(Sharp x, Sharp y) diff --git a/src/BenchmarkDotNet/Disassemblers/IntelDisassembler.cs b/src/BenchmarkDotNet/Disassemblers/IntelDisassembler.cs index f29faae037..3fd541528a 100644 --- a/src/BenchmarkDotNet/Disassemblers/IntelDisassembler.cs +++ b/src/BenchmarkDotNet/Disassemblers/IntelDisassembler.cs @@ -7,7 +7,7 @@ namespace BenchmarkDotNet.Disassemblers { - internal class IntelDisassembler : ClrMdV2Disassembler + internal class IntelDisassembler : ClrMdV3Disassembler { internal sealed class RuntimeSpecificData { diff --git a/src/BenchmarkDotNet/Disassemblers/SameArchitectureDisassembler.cs b/src/BenchmarkDotNet/Disassemblers/SameArchitectureDisassembler.cs index cf810cdd86..8ce026a5dd 100644 --- a/src/BenchmarkDotNet/Disassemblers/SameArchitectureDisassembler.cs +++ b/src/BenchmarkDotNet/Disassemblers/SameArchitectureDisassembler.cs @@ -8,16 +8,16 @@ namespace BenchmarkDotNet.Disassemblers internal class SameArchitectureDisassembler { private readonly DisassemblyDiagnoserConfig config; - private ClrMdV2Disassembler? clrMdV2Disassembler; + private ClrMdV3Disassembler? clrMdV3Disassembler; internal SameArchitectureDisassembler(DisassemblyDiagnoserConfig config) => this.config = config; internal DisassemblyResult Disassemble(DiagnoserActionParameters parameters) // delay the creation to avoid exceptions at DisassemblyDiagnoser ctor - => (clrMdV2Disassembler ??= CreateDisassemblerForCurrentArchitecture()) + => (clrMdV3Disassembler ??= CreateDisassemblerForCurrentArchitecture()) .AttachAndDisassemble(BuildDisassemblerSettings(parameters)); - private static ClrMdV2Disassembler CreateDisassemblerForCurrentArchitecture() + private static ClrMdV3Disassembler CreateDisassemblerForCurrentArchitecture() => RuntimeInformation.GetCurrentPlatform() switch { Platform.X86 or Platform.X64 => new IntelDisassembler(),