project structure changed
This commit is contained in:
145
Internal/ILReader.cs
Normal file
145
Internal/ILReader.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace DTLib.Ben.Demystifier.Internal;
|
||||
|
||||
internal class ILReader
|
||||
{
|
||||
private static readonly OpCode[] singleByteOpCode;
|
||||
private static readonly OpCode[] doubleByteOpCode;
|
||||
|
||||
private readonly byte[] _cil;
|
||||
private int ptr;
|
||||
|
||||
static ILReader()
|
||||
{
|
||||
singleByteOpCode = new OpCode[225];
|
||||
doubleByteOpCode = new OpCode[31];
|
||||
|
||||
var fields = GetOpCodeFields();
|
||||
|
||||
for (var i = 0; i < fields.Length; i++)
|
||||
{
|
||||
var code = (OpCode)fields[i].GetValue(null)!;
|
||||
if (code.OpCodeType == OpCodeType.Nternal)
|
||||
continue;
|
||||
|
||||
if (code.Size == 1)
|
||||
singleByteOpCode[code.Value] = code;
|
||||
else
|
||||
doubleByteOpCode[code.Value & 0xff] = code;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ILReader(byte[] cil)
|
||||
{
|
||||
_cil = cil;
|
||||
}
|
||||
|
||||
public OpCode OpCode { get; private set; }
|
||||
public int MetadataToken { get; private set; }
|
||||
public MemberInfo? Operand { get; private set; }
|
||||
|
||||
public bool Read(MethodBase methodInfo)
|
||||
{
|
||||
if (ptr < _cil.Length)
|
||||
{
|
||||
OpCode = ReadOpCode();
|
||||
Operand = ReadOperand(OpCode, methodInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private OpCode ReadOpCode()
|
||||
{
|
||||
var instruction = ReadByte();
|
||||
if (instruction < 254)
|
||||
return singleByteOpCode[instruction];
|
||||
return doubleByteOpCode[ReadByte()];
|
||||
}
|
||||
|
||||
private MemberInfo? ReadOperand(OpCode code, MethodBase methodInfo)
|
||||
{
|
||||
MetadataToken = 0;
|
||||
int inlineLength;
|
||||
switch (code.OperandType)
|
||||
{
|
||||
case OperandType.InlineMethod:
|
||||
MetadataToken = ReadInt();
|
||||
Type[]? methodArgs = null;
|
||||
if (methodInfo.GetType() != typeof(ConstructorInfo) &&
|
||||
!methodInfo.GetType().IsSubclassOf(typeof(ConstructorInfo)))
|
||||
methodArgs = methodInfo.GetGenericArguments();
|
||||
Type[]? typeArgs = null;
|
||||
if (methodInfo.DeclaringType is not null) typeArgs = methodInfo.DeclaringType.GetGenericArguments();
|
||||
try
|
||||
{
|
||||
return methodInfo.Module.ResolveMember(MetadataToken, typeArgs, methodArgs);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Can return System.ArgumentException : Token xxx is not a valid MemberInfo token in the scope of module xxx.dll
|
||||
return null;
|
||||
}
|
||||
|
||||
case OperandType.InlineNone:
|
||||
inlineLength = 0;
|
||||
break;
|
||||
|
||||
case OperandType.ShortInlineBrTarget:
|
||||
case OperandType.ShortInlineVar:
|
||||
case OperandType.ShortInlineI:
|
||||
inlineLength = 1;
|
||||
break;
|
||||
|
||||
case OperandType.InlineVar:
|
||||
inlineLength = 2;
|
||||
break;
|
||||
|
||||
case OperandType.InlineBrTarget:
|
||||
case OperandType.InlineField:
|
||||
case OperandType.InlineI:
|
||||
case OperandType.InlineString:
|
||||
case OperandType.InlineSig:
|
||||
case OperandType.InlineSwitch:
|
||||
case OperandType.InlineTok:
|
||||
case OperandType.InlineType:
|
||||
case OperandType.ShortInlineR:
|
||||
inlineLength = 4;
|
||||
break;
|
||||
|
||||
case OperandType.InlineI8:
|
||||
case OperandType.InlineR:
|
||||
inlineLength = 8;
|
||||
break;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
for (var i = 0; i < inlineLength; i++) ReadByte();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private byte ReadByte()
|
||||
{
|
||||
return _cil[ptr++];
|
||||
}
|
||||
|
||||
private int ReadInt()
|
||||
{
|
||||
var b1 = ReadByte();
|
||||
var b2 = ReadByte();
|
||||
var b3 = ReadByte();
|
||||
var b4 = ReadByte();
|
||||
return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24);
|
||||
}
|
||||
|
||||
private static FieldInfo[] GetOpCodeFields()
|
||||
{
|
||||
return typeof(OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static);
|
||||
}
|
||||
}
|
||||
113
Internal/PortablePdbReader.cs
Normal file
113
Internal/PortablePdbReader.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.IO;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
using System.Reflection.PortableExecutable;
|
||||
|
||||
namespace DTLib.Ben.Demystifier.Internal;
|
||||
|
||||
// Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.StackTrace.Sources/StackFrame/PortablePdbReader.cs
|
||||
internal class PortablePdbReader : IDisposable
|
||||
{
|
||||
private readonly Dictionary<string, MetadataReaderProvider> _cache = new(StringComparer.Ordinal);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var entry in _cache) entry.Value.Dispose();
|
||||
|
||||
_cache.Clear();
|
||||
}
|
||||
|
||||
public void PopulateStackFrame(StackFrame frameInfo, MethodBase method, int IlOffset, out string fileName,
|
||||
out int row, out int column)
|
||||
{
|
||||
fileName = "";
|
||||
row = 0;
|
||||
column = 0;
|
||||
|
||||
if (method.Module.Assembly.IsDynamic) return;
|
||||
|
||||
var metadataReader = GetMetadataReader(method.Module.Assembly.Location);
|
||||
|
||||
if (metadataReader is null) return;
|
||||
|
||||
var methodToken = MetadataTokens.Handle(method.MetadataToken);
|
||||
|
||||
Debug.Assert(methodToken.Kind == HandleKind.MethodDefinition);
|
||||
|
||||
var handle = ((MethodDefinitionHandle)methodToken).ToDebugInformationHandle();
|
||||
|
||||
if (!handle.IsNil)
|
||||
{
|
||||
var methodDebugInfo = metadataReader.GetMethodDebugInformation(handle);
|
||||
var sequencePoints = methodDebugInfo.GetSequencePoints();
|
||||
SequencePoint? bestPointSoFar = null;
|
||||
|
||||
foreach (var point in sequencePoints)
|
||||
{
|
||||
if (point.Offset > IlOffset) break;
|
||||
|
||||
if (point.StartLine != SequencePoint.HiddenLine) bestPointSoFar = point;
|
||||
}
|
||||
|
||||
if (bestPointSoFar.HasValue)
|
||||
{
|
||||
row = bestPointSoFar.Value.StartLine;
|
||||
column = bestPointSoFar.Value.StartColumn;
|
||||
fileName = metadataReader.GetString(metadataReader.GetDocument(bestPointSoFar.Value.Document).Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MetadataReader? GetMetadataReader(string assemblyPath)
|
||||
{
|
||||
if (!_cache.TryGetValue(assemblyPath, out var provider) && provider is not null)
|
||||
{
|
||||
var pdbPath = GetPdbPath(assemblyPath);
|
||||
|
||||
if (!string.IsNullOrEmpty(pdbPath) && File.Exists(pdbPath) && IsPortable(pdbPath!))
|
||||
{
|
||||
var pdbStream = File.OpenRead(pdbPath);
|
||||
provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream);
|
||||
}
|
||||
|
||||
_cache[assemblyPath] = provider;
|
||||
}
|
||||
|
||||
return provider?.GetMetadataReader();
|
||||
}
|
||||
|
||||
private static string? GetPdbPath(string assemblyPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(assemblyPath)) return null;
|
||||
|
||||
if (File.Exists(assemblyPath))
|
||||
{
|
||||
var peStream = File.OpenRead(assemblyPath);
|
||||
|
||||
using var peReader = new PEReader(peStream);
|
||||
foreach (var entry in peReader.ReadDebugDirectory())
|
||||
if (entry.Type == DebugDirectoryEntryType.CodeView)
|
||||
{
|
||||
var codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry);
|
||||
var peDirectory = Path.GetDirectoryName(assemblyPath);
|
||||
return peDirectory is null
|
||||
? null
|
||||
: Path.Combine(peDirectory, Path.GetFileName(codeViewData.Path));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool IsPortable(string pdbPath)
|
||||
{
|
||||
using var pdbStream = File.OpenRead(pdbPath);
|
||||
return pdbStream.ReadByte() == 'B' &&
|
||||
pdbStream.ReadByte() == 'S' &&
|
||||
pdbStream.ReadByte() == 'J' &&
|
||||
pdbStream.ReadByte() == 'B';
|
||||
}
|
||||
}
|
||||
60
Internal/ReflectionHelper.cs
Normal file
60
Internal/ReflectionHelper.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) Ben A Adams. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading;
|
||||
|
||||
namespace DTLib.Ben.Demystifier.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// A helper class that contains utilities methods for dealing with reflection.
|
||||
/// </summary>
|
||||
public static class ReflectionHelper
|
||||
{
|
||||
private static PropertyInfo? transformerNamesLazyPropertyInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the <paramref name="type" /> is a value tuple type.
|
||||
/// </summary>
|
||||
public static bool IsValueTuple(this Type type)
|
||||
{
|
||||
return type.Namespace == "System" && type.Name.Contains("ValueTuple`");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given <paramref name="attribute" /> is of type <code>TupleElementNameAttribute</code>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To avoid compile-time dependency hell with System.ValueTuple, this method uses reflection and not checks statically
|
||||
/// that
|
||||
/// the given <paramref name="attribute" /> is <code>TupleElementNameAttribute</code>.
|
||||
/// </remarks>
|
||||
public static bool IsTupleElementNameAttribute(this Attribute attribute)
|
||||
{
|
||||
var attributeType = attribute.GetType();
|
||||
return attributeType.Namespace == "System.Runtime.CompilerServices" &&
|
||||
attributeType.Name == "TupleElementNamesAttribute";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns 'TransformNames' property value from a given <paramref name="attribute" />.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To avoid compile-time dependency hell with System.ValueTuple, this method uses reflection
|
||||
/// instead of casting the attribute to a specific type.
|
||||
/// </remarks>
|
||||
public static IList<string>? GetTransformerNames(this Attribute attribute)
|
||||
{
|
||||
Debug.Assert(attribute.IsTupleElementNameAttribute());
|
||||
|
||||
var propertyInfo = GetTransformNamesPropertyInfo(attribute.GetType())!;
|
||||
return propertyInfo.GetValue(attribute) as IList<string>;
|
||||
}
|
||||
|
||||
private static PropertyInfo GetTransformNamesPropertyInfo(Type attributeType)
|
||||
{
|
||||
#pragma warning disable 8634
|
||||
return LazyInitializer.EnsureInitialized(ref transformerNamesLazyPropertyInfo,
|
||||
#pragma warning restore 8634
|
||||
() => attributeType.GetProperty("TransformNames", BindingFlags.Instance | BindingFlags.Public)!)!;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user