Created
December 16, 2016 18:35
-
-
Save ig-sinicyn/d33bfcfe30ad0cf49383acb8b416222c to your computer and use it in GitHub Desktop.
PdbInfoExample
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
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Reflection; | |
using System.Runtime.CompilerServices; | |
using System.Runtime.InteropServices; | |
using System.Threading.Tasks; | |
using Microsoft.DiaSymReader; | |
namespace PdbInfoExample | |
{ | |
// Package url: https://dotnet.myget.org/Gallery/symreader | |
// 1. Add a nuget feed https://dotnet.myget.org/F/symreader/api/v3/index.json | |
// 2. Add add a reference to latest prerelease version of Microsoft.DiaSymReader from the feed. | |
partial class C { public C(int z) { z = 3; } } | |
partial class Examples // partial at Program.Examples.cs | |
{ | |
public int Normal() => 1; | |
public async Task<int> Async() => await Task.FromResult(1); | |
public IEnumerable<int> Yield() { yield return 1; } | |
partial void Partial(); | |
} | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
PrintAllMethodsSameFile(MethodBase.GetCurrentMethod(), detailed: /*false*/true); | |
} | |
#region Helpers | |
static void PrintAllMethodsSameFile(MethodBase methodInfo, bool detailed) | |
{ | |
var module = methodInfo.DeclaringType.Assembly.ManifestModule; | |
methodInfo = ResolveBestMethodInfo(methodInfo); | |
var reader = TryGetReader(methodInfo.DeclaringType.Assembly); | |
var docs = reader.GetMethod(methodInfo.MetadataToken).GetDocumentsForMethod(); | |
foreach (var doc in docs) | |
{ | |
Console.WriteLine(); | |
Console.WriteLine($" ====== {Path.GetFileName(doc.GetName())} ====== "); | |
foreach (var sameDocMethod in reader.GetMethodsInDocument(doc)) | |
{ | |
if (detailed) | |
PrintSequencePoints(module.ResolveMethod(sameDocMethod.GetToken()), reader); | |
else | |
PrintCore(module.ResolveMethod(sameDocMethod.GetToken()), reader); | |
} | |
} | |
} | |
private static void PrintSequencePoints(MethodBase methodInfo, ISymUnmanagedReader reader) | |
{ | |
var origin = methodInfo; | |
var originGenerated = | |
methodInfo.GetCustomAttribute<CompilerGeneratedAttribute>() != null || | |
methodInfo.DeclaringType.GetCustomAttribute<CompilerGeneratedAttribute>() != null; | |
methodInfo = ResolveBestMethodInfo(methodInfo); | |
var compilerGenerated = | |
methodInfo.GetCustomAttribute<CompilerGeneratedAttribute>() != null || | |
methodInfo.DeclaringType.GetCustomAttribute<CompilerGeneratedAttribute>() != null; | |
Console.WriteLine(); | |
Console.WriteLine($" --- {origin.DeclaringType.Name}.{origin.Name} (generated={originGenerated}/{compilerGenerated})--- "); | |
var method = reader.GetMethod(methodInfo.MetadataToken); | |
var seq = method.GetSequencePoints().ToArray(); // empty! | |
if (seq.Length == 0) | |
Console.WriteLine($"??? (generated={compilerGenerated})"); | |
foreach (var point in seq) | |
{ | |
Console.WriteLine( | |
$" {Path.GetFileName(point.Document.GetName())}, " + | |
$"line {(point.IsHidden ? "hidden" : point.StartLine.ToString())}"); | |
} | |
} | |
private static void PrintCore(MethodBase methodInfo, ISymUnmanagedReader reader) | |
{ | |
var origin = methodInfo; | |
var originGenerated = | |
methodInfo.GetCustomAttribute<CompilerGeneratedAttribute>() != null || | |
methodInfo.DeclaringType.GetCustomAttribute<CompilerGeneratedAttribute>() != null; | |
methodInfo = ResolveBestMethodInfo(methodInfo); | |
var compilerGenerated = | |
methodInfo.GetCustomAttribute<CompilerGeneratedAttribute>() != null || | |
methodInfo.DeclaringType.GetCustomAttribute<CompilerGeneratedAttribute>() != null; | |
Console.WriteLine(); | |
Console.WriteLine($" --- {origin.DeclaringType.Name}.{origin.Name} (generated={originGenerated}/{compilerGenerated})--- "); | |
methodInfo = ResolveBestMethodInfo(methodInfo); | |
var method = reader.GetMethod(methodInfo.MetadataToken); | |
var docs = method.GetDocumentsForMethod(); // empty! | |
if (docs.Length == 0) | |
Console.WriteLine("???"); | |
var methodEnc = (ISymEncUnmanagedMethod)method; | |
foreach (var doc in docs) | |
{ | |
methodEnc.GetSourceExtentInDocument(doc, out var s, out var e); | |
Console.WriteLine($" {Path.GetFileName(doc.GetName())}, lines {s}..{e}"); | |
} | |
} | |
#endregion | |
#region Infrastructure | |
// ReSharper disable once SuggestBaseTypeForParameter | |
private static ISymUnmanagedReader2 TryGetReader(Assembly assembly) | |
{ | |
var assemblyPath = new Uri(assembly.CodeBase).LocalPath; | |
var codeBaseDirectory = Path.GetDirectoryName(assemblyPath); | |
var dispenser = (IMetaDataDispenser)new CorMetaDataDispenser(); | |
var import = dispenser.OpenScope(assemblyPath, 0, typeof(IMetaDataImportStub).GUID); | |
var binder = (ISymUnmanagedBinder)new CorSymBinder(); | |
ISymUnmanagedReader reader; | |
var hr = binder.GetReaderForFile(import, assemblyPath, codeBaseDirectory, out reader); | |
return ((ISymUnmanagedReader2)reader); | |
} | |
// BASEDON: https://github.com/aspnet/dnx/blob/bebc991012fe633ecac69675b2e892f568b927a5/src/Microsoft.Dnx.TestHost/TestAdapter/SourceInformationProvider.cs#L88-L104 | |
private static MethodBase ResolveBestMethodInfo(MethodBase methodBase) | |
{ | |
if (!(methodBase is MethodInfo method)) | |
return methodBase; | |
// If a method has a StateMachineAttribute, then all of the user code will show up | |
// in the symbols associated with the compiler-generated code. So, we need to look | |
// for the 'MoveNext' on the generated type and resolve symbols for that. | |
var attribute = method.GetCustomAttribute<StateMachineAttribute>(); | |
if (attribute?.StateMachineType == null) | |
{ | |
return method; | |
} | |
return attribute.StateMachineType.GetMethod( | |
"MoveNext", | |
BindingFlags.Instance | BindingFlags.NonPublic); | |
} | |
#region CoClasses | |
/// <summary> | |
/// CoClass for getting an ISymUnmanagedBinder | |
/// </summary> | |
[ComImport, Guid("0A29FF9E-7F9C-4437-8B11-F424491E3931")] | |
private class CorSymBinder { } | |
/// <summary> | |
/// CoClass for getting an IMetaDataDispenser | |
/// </summary> | |
[ComImport] | |
[Guid("E5CB7A31-7512-11d2-89CE-0080C792E5D8")] | |
[TypeLibType(TypeLibTypeFlags.FCanCreate)] | |
[ClassInterface(ClassInterfaceType.None)] | |
private class CorMetaDataDispenser { } | |
[ComImport] | |
[Guid("7DAC8207-D3AE-4c75-9B67-92801A497D44")] | |
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | |
[TypeLibType(TypeLibTypeFlags.FRestricted)] | |
private interface IMetaDataImportStub | |
{ | |
// ... | |
} | |
#endregion | |
/// <summary> | |
/// This version of the IMetaDataDispenser interface defines | |
/// the interfaces so that the last parameter from cor.h | |
/// is the return value of the methods. The 'raw' way to | |
/// implement these methods is as follows: | |
/// void OpenScope( | |
/// [In][MarshalAs(UnmanagedType.LPWStr)] string szScope, | |
/// [In] UInt32 dwOpenFlags, | |
/// [In] ref Guid riid, | |
/// [Out] out IntPtr ppIUnk); | |
/// The way to call this other version is as follows | |
/// IntPtr unk; | |
/// dispenser.OpenScope(assemblyName, 0, ref guidIMetaDataImport, out unk); | |
/// importInterface = (IMetaDataImport)Marshal.GetObjectForIUnknown(unk); | |
/// Marshal.Release(unk); | |
/// </summary> | |
[ComImport] | |
[Guid("809C652E-7396-11D2-9771-00A0C9B4D50C")] | |
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown /*0x0001*/)] | |
[TypeLibType(TypeLibTypeFlags.FRestricted /*0x0200*/)] | |
private interface IMetaDataDispenser | |
{ | |
[return: MarshalAs(UnmanagedType.Interface)] | |
object DefineScope( | |
[In] ref Guid rclsid, | |
[In] uint dwCreateFlags, | |
[In] ref Guid riid); | |
[return: MarshalAs(UnmanagedType.Interface)] | |
object OpenScope( | |
[In] [MarshalAs(UnmanagedType.LPWStr)] string szScope, | |
[In] uint dwOpenFlags, | |
[In] ref Guid riid); | |
[return: MarshalAs(UnmanagedType.Interface)] | |
object OpenScopeOnMemory( | |
[In] IntPtr pData, | |
[In] uint cbData, | |
[In] uint dwOpenFlags, | |
[In] ref Guid riid); | |
} | |
#endregion | |
} | |
} |
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
using System.Threading; | |
namespace PdbInfoExample | |
{ | |
partial class C { int X = 1; } | |
partial class C { int Y = 2; } | |
partial class Examples | |
{ | |
partial void Partial() => Thread.Sleep(1); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment