Port the .NET (Core) disassembler to ClrMd v2 (#2040)
* update and sync TraceEvent version * port the Disassembler from ClrMD 1.x to 2.x
This commit is contained in:
Родитель
762b76c368
Коммит
d24ea32447
|
@ -48,7 +48,7 @@ tests/output/*
|
|||
artifacts/*
|
||||
BDN.Generated
|
||||
BenchmarkDotNet.Samples/Properties/launchSettings.json
|
||||
src/BenchmarkDotNet/Disassemblers/net461/*
|
||||
src/BenchmarkDotNet/Disassemblers/net462/*
|
||||
src/BenchmarkDotNet/Disassemblers/BenchmarkDotNet.Disassembler.*.nupkg
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="4.5.1" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.3" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.5" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\BenchmarkDotNet\BenchmarkDotNet.csproj" />
|
||||
|
|
|
@ -12,6 +12,6 @@
|
|||
<ProjectReference Include="..\BenchmarkDotNet\BenchmarkDotNet.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.0.1" PrivateAssets="contentfiles;analyzers" />
|
||||
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.0.2" PrivateAssets="contentfiles;analyzers" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -7,7 +7,8 @@ using System.Linq;
|
|||
|
||||
namespace BenchmarkDotNet.Disassemblers
|
||||
{
|
||||
internal static class ClrMdDisassembler
|
||||
// This Disassembler uses ClrMd v1x. Please keep it in sync with ClrMdV2Disassembler (if possible).
|
||||
internal static class ClrMdV1Disassembler
|
||||
{
|
||||
internal static DisassemblyResult AttachAndDisassemble(Settings settings)
|
||||
{
|
|
@ -24,7 +24,7 @@ namespace BenchmarkDotNet.Disassemblers
|
|||
|
||||
try
|
||||
{
|
||||
var methodsToExport = ClrMdDisassembler.AttachAndDisassemble(options);
|
||||
var methodsToExport = ClrMdV1Disassembler.AttachAndDisassemble(options);
|
||||
|
||||
SaveToFile(methodsToExport, options.ResultsPath);
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\BenchmarkDotNet.Disassembler.x64\DataContracts.cs" Link="DataContracts.cs" />
|
||||
<Compile Include="..\BenchmarkDotNet.Disassembler.x64\ClrMdDisassembler.cs" Link="ClrMdDisassembler.cs" />
|
||||
<Compile Include="..\BenchmarkDotNet.Disassembler.x64\ClrMdV1Disassembler.cs" Link="ClrMdV1Disassembler.cs" />
|
||||
<Compile Include="..\BenchmarkDotNet.Disassembler.x64\SourceCodeProvider.cs" Link="SourceCodeProvider.cs" />
|
||||
<Compile Include="..\BenchmarkDotNet.Disassembler.x64\Program.cs" Link="Program.cs" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -16,15 +16,14 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.4.3" />
|
||||
<PackageReference Include="Iced" Version="1.17.0" />
|
||||
<PackageReference Include="Microsoft.Diagnostics.Runtime" Version="1.1.126102" />
|
||||
<PackageReference Include="Microsoft.Diagnostics.Runtime" Version="2.2.332302" />
|
||||
<PackageReference Include="Perfolizer" Version="0.2.1" />
|
||||
<PackageReference Include="System.Management" Version="6.0.0" />
|
||||
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
|
||||
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.Diagnostics.NETCore.Client" Version="0.2.61701" />
|
||||
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="2.0.61" PrivateAssets="contentfiles;analyzers" />
|
||||
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.0.2" PrivateAssets="contentfiles;analyzers" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
<PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
|
||||
|
@ -47,7 +46,5 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\BenchmarkDotNet.Disassembler.x64\DataContracts.cs" Link="Disassemblers\DataContracts.cs" />
|
||||
<Compile Include="..\BenchmarkDotNet.Disassembler.x64\SourceCodeProvider.cs" Link="Disassemblers\SourceCodeProvider.cs" />
|
||||
<Compile Include="..\BenchmarkDotNet.Disassembler.x64\ClrMdDisassembler.cs" Link="Disassemblers\ClrMdDisassembler.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -0,0 +1,265 @@
|
|||
using Iced.Intel;
|
||||
using Microsoft.Diagnostics.Runtime;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace BenchmarkDotNet.Disassemblers
|
||||
{
|
||||
// This Disassembler uses ClrMd v2x. Please keep it in sync with ClrMdV1Disassembler (if possible).
|
||||
internal static class ClrMdV2Disassembler
|
||||
{
|
||||
internal static DisassemblyResult AttachAndDisassemble(Settings settings)
|
||||
{
|
||||
using (var dataTarget = DataTarget.AttachToProcess(
|
||||
settings.ProcessId,
|
||||
suspend: false))
|
||||
{
|
||||
var runtime = dataTarget.ClrVersions.Single().CreateRuntime();
|
||||
|
||||
ConfigureSymbols(dataTarget);
|
||||
|
||||
var state = new State(runtime);
|
||||
|
||||
var typeWithBenchmark = state.Runtime.EnumerateModules().Select(module => module.GetTypeByName(settings.TypeName)).First(type => type != null);
|
||||
|
||||
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),
|
||||
0));
|
||||
|
||||
var disassembledMethods = Disassemble(settings, state);
|
||||
|
||||
// we don't want to export the disassembler entry point method which is just an artificial method added to get generic types working
|
||||
var filteredMethods = disassembledMethods.Length == 1
|
||||
? disassembledMethods // if there is only one method we want to return it (most probably benchmark got inlined)
|
||||
: disassembledMethods.Where(method => !method.Name.Contains(DisassemblerConstants.DisassemblerEntryMethodName)).ToArray();
|
||||
|
||||
return new DisassemblyResult
|
||||
{
|
||||
Methods = filteredMethods,
|
||||
SerializedAddressToNameMapping = state.AddressToNameMapping.Select(x => new DisassemblyResult.MutablePair { Key = x.Key, Value = x.Value }).ToArray(),
|
||||
PointerSize = (uint)IntPtr.Size
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static void ConfigureSymbols(DataTarget dataTarget)
|
||||
{
|
||||
// code copied from https://github.com/Microsoft/clrmd/issues/34#issuecomment-161926535
|
||||
dataTarget.SetSymbolPath("http://msdl.microsoft.com/download/symbols");
|
||||
}
|
||||
|
||||
private static DisassembledMethod[] Disassemble(Settings settings, State state)
|
||||
{
|
||||
var result = new List<DisassembledMethod>();
|
||||
|
||||
while (state.Todo.Count != 0)
|
||||
{
|
||||
var methodInfo = state.Todo.Dequeue();
|
||||
|
||||
if (!state.HandledMethods.Add(methodInfo.Method)) // add it now to avoid StackOverflow for recursive methods
|
||||
continue; // already handled
|
||||
|
||||
if (settings.MaxDepth >= methodInfo.Depth)
|
||||
result.Add(DisassembleMethod(methodInfo, state, settings));
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
private static DisassembledMethod DisassembleMethod(MethodInfo methodInfo, State state, Settings settings)
|
||||
{
|
||||
var method = methodInfo.Method;
|
||||
|
||||
if (method.ILOffsetMap.Length == 0 && (method.HotColdInfo.HotStart == 0 || method.HotColdInfo.HotSize == 0))
|
||||
{
|
||||
if (method.IsPInvoke)
|
||||
return CreateEmpty(method, "PInvoke method");
|
||||
if (method.IL is null || method.IL.Length == 0)
|
||||
return CreateEmpty(method, "Extern method");
|
||||
if (method.CompilationType == MethodCompilationType.None)
|
||||
return CreateEmpty(method, "Method was not JITted yet.");
|
||||
|
||||
return CreateEmpty(method, $"No valid {nameof(method.ILOffsetMap)} and {nameof(method.HotColdInfo)}");
|
||||
}
|
||||
|
||||
var codes = new List<SourceCode>();
|
||||
if (settings.PrintSource && method.ILOffsetMap.Length > 0)
|
||||
{
|
||||
// we use HashSet to prevent from duplicates
|
||||
var uniqueSourceCodeLines = new HashSet<Sharp>(new SharpComparer());
|
||||
// for getting C# code we always use the original ILOffsetMap
|
||||
foreach (var map in method.ILOffsetMap.Where(map => map.StartAddress < map.EndAddress && map.ILOffset >= 0).OrderBy(map => map.StartAddress))
|
||||
foreach (var sharp in SourceCodeProvider.GetSource(method, map))
|
||||
uniqueSourceCodeLines.Add(sharp);
|
||||
|
||||
codes.AddRange(uniqueSourceCodeLines);
|
||||
}
|
||||
|
||||
// for getting ASM we try to use data from HotColdInfo if available (better for decoding)
|
||||
foreach (var map in GetCompleteNativeMap(method))
|
||||
codes.AddRange(Decode(map.StartAddress, (uint)(map.EndAddress - map.StartAddress), state, methodInfo.Depth, method));
|
||||
|
||||
Map[] maps = settings.PrintSource
|
||||
? codes.GroupBy(code => code.InstructionPointer).OrderBy(group => group.Key).Select(group => new Map() { SourceCodes = group.ToArray() }).ToArray()
|
||||
: new[] { new Map() { SourceCodes = codes.ToArray() } };
|
||||
|
||||
return new DisassembledMethod
|
||||
{
|
||||
Maps = maps,
|
||||
Name = method.Signature,
|
||||
NativeCode = method.NativeCode
|
||||
};
|
||||
}
|
||||
|
||||
private static IEnumerable<Asm> Decode(ulong startAddress, uint size, State state, int depth, ClrMethod currentMethod)
|
||||
{
|
||||
byte[] code = new byte[size];
|
||||
int bytesRead = state.Runtime.DataTarget.DataReader.Read(startAddress, code);
|
||||
if (bytesRead == 0 || bytesRead != size)
|
||||
yield break;
|
||||
|
||||
var reader = new ByteArrayCodeReader(code, 0, bytesRead);
|
||||
var decoder = Decoder.Create(state.Runtime.DataTarget.DataReader.PointerSize * 8, reader);
|
||||
decoder.IP = startAddress;
|
||||
|
||||
while (reader.CanReadByte)
|
||||
{
|
||||
decoder.Decode(out var instruction);
|
||||
|
||||
TryTranslateAddressToName(instruction, state, depth, currentMethod);
|
||||
|
||||
yield return new Asm
|
||||
{
|
||||
InstructionPointer = instruction.IP,
|
||||
Instruction = instruction
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryTranslateAddressToName(Instruction instruction, State state, int depth, ClrMethod currentMethod)
|
||||
{
|
||||
var runtime = state.Runtime;
|
||||
|
||||
if (!TryGetReferencedAddress(instruction, (uint)runtime.DataTarget.DataReader.PointerSize, out ulong address))
|
||||
return;
|
||||
|
||||
if (state.AddressToNameMapping.ContainsKey(address))
|
||||
return;
|
||||
|
||||
var jitHelperFunctionName = runtime.GetJitHelperFunctionName(address);
|
||||
if (!string.IsNullOrEmpty(jitHelperFunctionName))
|
||||
{
|
||||
state.AddressToNameMapping.Add(address, jitHelperFunctionName);
|
||||
return;
|
||||
}
|
||||
|
||||
var methodTableName = runtime.DacLibrary.SOSDacInterface.GetMethodTableName(address);
|
||||
if (!string.IsNullOrEmpty(methodTableName))
|
||||
{
|
||||
state.AddressToNameMapping.Add(address, $"MT_{methodTableName}");
|
||||
return;
|
||||
}
|
||||
|
||||
var methodDescriptor = runtime.GetMethodByHandle(address);
|
||||
if (!(methodDescriptor is null))
|
||||
{
|
||||
state.AddressToNameMapping.Add(address, $"MD_{methodDescriptor.Signature}");
|
||||
return;
|
||||
}
|
||||
|
||||
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) && newAddress > ushort.MaxValue)
|
||||
method = runtime.GetMethodByInstructionPointer(newAddress);
|
||||
}
|
||||
|
||||
if (method is null)
|
||||
return;
|
||||
|
||||
if (method.NativeCode == currentMethod.NativeCode && method.Signature == currentMethod.Signature)
|
||||
return; // in case of a call which is just a jump within the method or a recursive call
|
||||
|
||||
if (!state.HandledMethods.Contains(method))
|
||||
state.Todo.Enqueue(new MethodInfo(method, depth + 1));
|
||||
|
||||
var methodName = method.Signature;
|
||||
if (!methodName.Any(c => c == '.')) // the method name does not contain namespace and type name
|
||||
methodName = $"{method.Type.Name}.{method.Signature}";
|
||||
state.AddressToNameMapping.Add(address, methodName);
|
||||
}
|
||||
|
||||
internal static bool TryGetReferencedAddress(Instruction instruction, uint pointerSize, out ulong referencedAddress)
|
||||
{
|
||||
for (int i = 0; i < instruction.OpCount; i++)
|
||||
{
|
||||
switch (instruction.GetOpKind(i))
|
||||
{
|
||||
case OpKind.NearBranch16:
|
||||
case OpKind.NearBranch32:
|
||||
case OpKind.NearBranch64:
|
||||
referencedAddress = instruction.NearBranchTarget;
|
||||
return referencedAddress > ushort.MaxValue;
|
||||
case OpKind.Immediate16:
|
||||
case OpKind.Immediate8to16:
|
||||
case OpKind.Immediate8to32:
|
||||
case OpKind.Immediate8to64:
|
||||
case OpKind.Immediate32to64:
|
||||
case OpKind.Immediate32 when pointerSize == 4:
|
||||
case OpKind.Immediate64:
|
||||
referencedAddress = instruction.GetImmediate(i);
|
||||
return referencedAddress > ushort.MaxValue;
|
||||
case OpKind.Memory when instruction.IsIPRelativeMemoryOperand:
|
||||
referencedAddress = instruction.IPRelativeMemoryAddress;
|
||||
return referencedAddress > ushort.MaxValue;
|
||||
case OpKind.Memory:
|
||||
referencedAddress = instruction.MemoryDisplacement64;
|
||||
return referencedAddress > ushort.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
referencedAddress = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static ILToNativeMap[] GetCompleteNativeMap(ClrMethod method)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
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 }
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static DisassembledMethod CreateEmpty(ClrMethod method, string reason)
|
||||
=> DisassembledMethod.Empty(method.Signature, method.NativeCode, reason);
|
||||
|
||||
private class SharpComparer : IEqualityComparer<Sharp>
|
||||
{
|
||||
public bool Equals(Sharp x, Sharp y)
|
||||
{
|
||||
// sometimes some C# code lines are duplicated because the same line is the best match for multiple ILToNativeMaps
|
||||
// we don't want to confuse the users, so this must also be removed
|
||||
return x.FilePath == y.FilePath && x.LineNumber == y.LineNumber;
|
||||
}
|
||||
|
||||
public int GetHashCode(Sharp obj) => obj.FilePath.GetHashCode() ^ obj.LineNumber;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -39,7 +39,7 @@ namespace BenchmarkDotNet.Disassemblers.Exporters
|
|||
// first of all, we search of referenced addresses (jump|calls)
|
||||
var referencedAddresses = new HashSet<ulong>();
|
||||
foreach (var asm in asmInstructions)
|
||||
if (ClrMdDisassembler.TryGetReferencedAddress(asm.Instruction, disassemblyResult.PointerSize, out ulong referencedAddress))
|
||||
if (ClrMdV2Disassembler.TryGetReferencedAddress(asm.Instruction, disassemblyResult.PointerSize, out ulong referencedAddress))
|
||||
referencedAddresses.Add(referencedAddress);
|
||||
|
||||
// for every IP that is referenced, we emit a uinque label
|
||||
|
@ -72,7 +72,7 @@ namespace BenchmarkDotNet.Disassemblers.Exporters
|
|||
prettified.Add(new Label(label));
|
||||
}
|
||||
|
||||
if (ClrMdDisassembler.TryGetReferencedAddress(asm.Instruction, disassemblyResult.PointerSize, out ulong referencedAddress))
|
||||
if (ClrMdV2Disassembler.TryGetReferencedAddress(asm.Instruction, disassemblyResult.PointerSize, out ulong referencedAddress))
|
||||
{
|
||||
// jump or a call within same method
|
||||
if (addressesToLabels.TryGetValue(referencedAddress, out string translated))
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace BenchmarkDotNet.Disassemblers
|
|||
internal LinuxDisassembler(DisassemblyDiagnoserConfig config) => this.config = config;
|
||||
|
||||
internal DisassemblyResult Disassemble(DiagnoserActionParameters parameters)
|
||||
=> ClrMdDisassembler.AttachAndDisassemble(BuildDisassemblerSettings(parameters));
|
||||
=> ClrMdV2Disassembler.AttachAndDisassemble(BuildDisassemblerSettings(parameters));
|
||||
|
||||
private Settings BuildDisassemblerSettings(DiagnoserActionParameters parameters)
|
||||
=> new Settings(
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
using Microsoft.Diagnostics.Runtime;
|
||||
using Microsoft.Diagnostics.Symbols;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace BenchmarkDotNet.Disassemblers
|
||||
{
|
||||
internal static class SourceCodeProvider
|
||||
{
|
||||
private static readonly Dictionary<SourceFile, string[]> SourceFileCache = new Dictionary<SourceFile, string[]>();
|
||||
private static readonly Dictionary<SourceFile, string> SourceFilePathsCache = new Dictionary<SourceFile, string>();
|
||||
|
||||
internal static IEnumerable<Sharp> GetSource(ClrMethod method, ILToNativeMap map)
|
||||
{
|
||||
var sourceLocation = method.GetSourceLocation(map.ILOffset);
|
||||
if (sourceLocation == null)
|
||||
yield break;
|
||||
|
||||
for (int line = sourceLocation.LineNumber; line <= sourceLocation.LineNumberEnd; ++line)
|
||||
{
|
||||
var sourceLine = ReadSourceLine(sourceLocation.SourceFile, line);
|
||||
if (sourceLine == null)
|
||||
continue;
|
||||
|
||||
var text = sourceLine + Environment.NewLine
|
||||
+ GetSmartPointer(sourceLine,
|
||||
start: line == sourceLocation.LineNumber ? sourceLocation.ColumnNumber - 1 : default(int?),
|
||||
end: line == sourceLocation.LineNumberEnd ? sourceLocation.ColumnNumberEnd - 1 : default(int?));
|
||||
|
||||
yield return new Sharp
|
||||
{
|
||||
Text = text,
|
||||
InstructionPointer = map.StartAddress,
|
||||
FilePath = GetFilePath(sourceLocation.SourceFile),
|
||||
LineNumber = line
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetFilePath(SourceFile sourceFile)
|
||||
=> SourceFilePathsCache.TryGetValue(sourceFile, out string filePath) ? filePath : sourceFile.Url;
|
||||
|
||||
private static string ReadSourceLine(SourceFile file, int line)
|
||||
{
|
||||
if (!SourceFileCache.TryGetValue(file, out string[] contents))
|
||||
{
|
||||
// GetSourceFile method returns path when file is stored on the same machine
|
||||
// otherwise it downloads it from the Symbol Server and returns the source code ;)
|
||||
string wholeFileOrJustPath = file.GetSourceFile();
|
||||
|
||||
if (string.IsNullOrEmpty(wholeFileOrJustPath))
|
||||
return null;
|
||||
|
||||
if (File.Exists(wholeFileOrJustPath))
|
||||
{
|
||||
contents = File.ReadAllLines(wholeFileOrJustPath);
|
||||
SourceFilePathsCache.Add(file, wholeFileOrJustPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
contents = wholeFileOrJustPath.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
|
||||
}
|
||||
|
||||
SourceFileCache.Add(file, contents);
|
||||
}
|
||||
|
||||
return line - 1 < contents.Length
|
||||
? contents[line - 1]
|
||||
: null; // "nop" can have no corresponding c# code ;)
|
||||
}
|
||||
|
||||
private static string GetSmartPointer(string sourceLine, int? start, int? end)
|
||||
{
|
||||
Debug.Assert(start is null || start < sourceLine.Length);
|
||||
Debug.Assert(end is null || end <= sourceLine.Length);
|
||||
|
||||
var prefix = new char[end ?? sourceLine.Length];
|
||||
var index = 0;
|
||||
|
||||
// write offset using whitespaces
|
||||
while (index < (start ?? prefix.Length))
|
||||
{
|
||||
prefix[index] =
|
||||
sourceLine.Length > index &&
|
||||
sourceLine[index] == '\t'
|
||||
? '\t'
|
||||
: ' ';
|
||||
index++;
|
||||
}
|
||||
|
||||
// write smart pointer
|
||||
while (index < prefix.Length)
|
||||
{
|
||||
prefix[index] = '^';
|
||||
index++;
|
||||
}
|
||||
|
||||
return new string(prefix);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class ClrSourceExtensions
|
||||
{
|
||||
// TODO Not sure we want this to be a shared dictionary, especially without
|
||||
// any synchronization. Probably want to put this hanging off the Context
|
||||
// somewhere, or inside SymbolCache.
|
||||
private static readonly Dictionary<PdbInfo, ManagedSymbolModule> s_pdbReaders = new Dictionary<PdbInfo, ManagedSymbolModule>();
|
||||
private static readonly SymbolReader symbolReader = new SymbolReader(TextWriter.Null) { SymbolPath = SymbolPath.MicrosoftSymbolServerPath };
|
||||
|
||||
internal static SourceLocation GetSourceLocation(this ClrMethod method, int ilOffset)
|
||||
{
|
||||
var reader = GetReaderForMethod(method);
|
||||
if (reader == null)
|
||||
return null;
|
||||
|
||||
return reader.SourceLocationForManagedCode((uint)method.MetadataToken, ilOffset);
|
||||
}
|
||||
|
||||
internal static SourceLocation GetSourceLocation(this ClrStackFrame frame)
|
||||
{
|
||||
var reader = GetReaderForMethod(frame.Method);
|
||||
if (reader == null)
|
||||
return null;
|
||||
|
||||
return reader.SourceLocationForManagedCode((uint)frame.Method.MetadataToken, FindIlOffset(frame));
|
||||
}
|
||||
|
||||
private static int FindIlOffset(ClrStackFrame frame)
|
||||
{
|
||||
ulong ip = frame.InstructionPointer;
|
||||
int last = -1;
|
||||
foreach (ILToNativeMap item in frame.Method.ILOffsetMap)
|
||||
{
|
||||
if (item.StartAddress > ip)
|
||||
return last;
|
||||
|
||||
if (ip <= item.EndAddress)
|
||||
return item.ILOffset;
|
||||
|
||||
last = item.ILOffset;
|
||||
}
|
||||
|
||||
return last;
|
||||
}
|
||||
|
||||
private static ManagedSymbolModule GetReaderForMethod(ClrMethod method)
|
||||
{
|
||||
ClrModule module = method?.Type?.Module;
|
||||
PdbInfo info = module?.Pdb;
|
||||
|
||||
ManagedSymbolModule reader = null;
|
||||
if (info != null)
|
||||
{
|
||||
if (!s_pdbReaders.TryGetValue(info, out reader))
|
||||
{
|
||||
string pdbPath = info.Path;
|
||||
if (pdbPath != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
reader = symbolReader.OpenSymbolFile(pdbPath);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// This will typically happen when trying to load information
|
||||
// from public symbols, or symbol files generated by some weird
|
||||
// compiler. We can ignore this, but there's no need to load
|
||||
// this PDB anymore, so we will put null in the dictionary and
|
||||
// be done with it.
|
||||
reader = null;
|
||||
}
|
||||
}
|
||||
|
||||
s_pdbReaders[info] = reader;
|
||||
}
|
||||
}
|
||||
|
||||
return reader;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,7 +31,7 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.3" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="Mono.Cecil" Version="0.11.1" />
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Memory" Version="4.5.3" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.5" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
|
|
Загрузка…
Ссылка в новой задаче