Skip to content

Instantly share code, notes, and snippets.

@ig-sinicyn
Created December 16, 2016 18:35
Show Gist options
  • Save ig-sinicyn/d33bfcfe30ad0cf49383acb8b416222c to your computer and use it in GitHub Desktop.
Save ig-sinicyn/d33bfcfe30ad0cf49383acb8b416222c to your computer and use it in GitHub Desktop.
PdbInfoExample
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
}
}
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