Skip to content

Commit

Permalink
Update clrmd to 3.1
Browse files Browse the repository at this point in the history
  • Loading branch information
timcassell committed Dec 28, 2023
1 parent 91f3f7e commit 288da74
Show file tree
Hide file tree
Showing 5 changed files with 30 additions and 74 deletions.
2 changes: 1 addition & 1 deletion src/BenchmarkDotNet/BenchmarkDotNet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Gee.External.Capstone" Version="2.3.0" />
<PackageReference Include="Iced" Version="1.17.0" />
<PackageReference Include="Microsoft.Diagnostics.Runtime" Version="2.2.332302" />
<PackageReference Include="Microsoft.Diagnostics.Runtime" Version="3.1.456101" />
<PackageReference Include="Perfolizer" Version="[0.2.1]" />
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.0.2" PrivateAssets="contentfiles;analyzers" />
<PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
Expand Down
2 changes: 1 addition & 1 deletion src/BenchmarkDotNet/Disassemblers/Arm64Disassembler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

namespace BenchmarkDotNet.Disassemblers
{
// This Disassembler uses ClrMd v2x. Please keep it in sync with ClrMdV1Disassembler (if possible).
internal abstract class ClrMdV2Disassembler
internal abstract class ClrMdV3Disassembler

{
private static readonly ulong MinValidAddress = GetMinValidAddress();

Expand Down Expand Up @@ -64,7 +64,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));
}

Expand Down Expand Up @@ -148,9 +148,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.");
Expand Down Expand Up @@ -213,60 +214,30 @@ private IEnumerable<Asm> 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))
Expand All @@ -282,18 +253,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)
Expand All @@ -312,7 +275,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}");
Expand Down Expand Up @@ -348,13 +311,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<Sharp>
{
public bool Equals(Sharp x, Sharp y)
Expand Down
2 changes: 1 addition & 1 deletion src/BenchmarkDotNet/Disassemblers/IntelDisassembler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace BenchmarkDotNet.Disassemblers
{
internal class IntelDisassembler : ClrMdV2Disassembler
internal class IntelDisassembler : ClrMdV3Disassembler
{
internal sealed class RuntimeSpecificData
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down

0 comments on commit 288da74

Please sign in to comment.