Retrieve Local variable name from PDB files

巧了我就是萌 提交于 2020-01-28 09:56:27

问题


i'm currently trying to retrieve source code from IL bytecodes and PDB files, i'm arrived to the point where i can generate source code from IL and reflection i know the name of local variable names is included in the pdb file. My question is how can i find it back ? what libs should i use to handle the pdb files (if any) or should i write the code myself ? where can i find information about pdb file format ? currently in the generated sourcecode i'm using auto generated values for local variables but i want to change that as i believe it is possible to find that information back if you have pdb files at your disposal. I tried to look on google but i didnt find any usefull informations.

Thanks in advance for you replies ;)


回答1:


Here's how to read local variable names from a MethodInfo using the types in System.Diagnostics.SymbolStore:

public class LocalVariableNameReader
{
    Dictionary<int, string> _names = new Dictionary<int, string> ();

    public string this [int index]
    {
        get
        {
            if (!_names.ContainsKey (index)) return null;
            return _names [index];
        }
    }

    public LocalVariableNameReader (MethodInfo m)
    {
        ISymbolReader symReader = SymUtil.GetSymbolReaderForFile (m.DeclaringType.Assembly.Location, null);
        ISymbolMethod met = symReader.GetMethod (new SymbolToken (m.MetadataToken));
        VisitLocals (met.RootScope);
    }

    void VisitLocals (ISymbolScope iSymbolScope)
    {
        foreach (var s in iSymbolScope.GetLocals ()) _names [s.AddressField1] = s.Name;
        foreach (var c in iSymbolScope.GetChildren ()) VisitLocals (c);
    }
}

The SymUtil class comes from this example.

Edit: The above link is broken. From a google cache:

A long time ago I was looking on how to process pdb files in order to get additional information that reflection simply could not provide. Now I stumped over a 5 year old post of Mike Stall (here) and suddenly all became clear. I’ve put together a small example which uses .net 4.0 to read the method body code as it is in the source code from where the assembly was compiled. Mind you that in order for this to work you have to have the pdb of the assembly available. Also please make sure you add a refference to ISymWrapper and your project is targeting .Net 4.0 framework, not .Net 4.0 Client.

using System;
using System.Diagnostics.SymbolStore;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;

namespace PdbTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Assembly ass = Assembly.GetExecutingAssembly();
            ISymbolReader symreader = SymUtil.GetSymbolReaderForFile(ass.Location, null);

            MethodInfo m = ass.GetType("PdbTest.TestClass").GetMethod("GetStringRepresentation");
            ISymbolMethod met = symreader.GetMethod(new SymbolToken(m.MetadataToken));

            int count = met.SequencePointCount;

            ISymbolDocument[] docs = new ISymbolDocument[count];
            int[] offsets = new int[count];
            int[] lines = new int[count];
            int[] columns = new int[count];
            int[] endlines = new int[count];
            int[] endcolumns = new int[count];

            met.GetSequencePoints(offsets, docs, lines, columns, endlines, endcolumns);

            StreamReader reader = new StreamReader(docs[0].URL);
            string[] linesOfCode = reader.ReadToEnd().Split('n');
            reader.Close();

            Console.WriteLine("The content of method PdbTest.TestClass.GetStringRepresentation");
            for (int i = lines[0]; i < endlines[count - 1] - 1; i++)
            {
                Console.WriteLine(linesOfCode[i]);
            }
        }
    }


    #region test class

    public enum MyEnum
    {
        Apples,
        Oranges
    }

    public partial class TestClass
    {
        public string GetStringRepresentation(MyEnum e)
        {
            MyEnum e2 = MyEnum.Apples;
            return e.ToString() + e2.ToString();
        }
    }

    #endregion test class

    #region Get a symbol reader for the given module

    // Encapsulate a set of helper classes to get a symbol reader from a file.
    // The symbol interfaces require an unmanaged metadata interface.
    static class SymUtil
    {
        static class NativeMethods
        {
            [DllImport("ole32.dll")]
            public static extern int CoCreateInstance(
                [In] ref Guid rclsid,
                [In, MarshalAs(UnmanagedType.IUnknown)] Object pUnkOuter,
                [In] uint dwClsContext,
                [In] ref Guid riid,
                [Out, MarshalAs(UnmanagedType.Interface)] out Object ppv);
        }

        // Wrapper.
        public static ISymbolReader GetSymbolReaderForFile(string pathModule, string searchPath)
        {
            return SymUtil.GetSymbolReaderForFile(
                new System.Diagnostics.SymbolStore.SymBinder(), pathModule, searchPath);
        }

        // We demand Unmanaged code permissions because we're reading from the file 
        // system and calling out to the Symbol Reader
        // @TODO - make this more specific.
        [System.Security.Permissions.SecurityPermission(
            System.Security.Permissions.SecurityAction.Demand,
            Flags = System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)]
        public static ISymbolReader GetSymbolReaderForFile(
           System.Diagnostics.SymbolStore.SymBinder binder, string pathModule, string searchPath)
        {
            // Guids for imported metadata interfaces.
            Guid dispenserClassID = new Guid(0xe5cb7a31, 0x7512, 0x11d2, 0x89, 
                0xce, 0x00, 0x80, 0xc7, 0x92, 0xe5, 0xd8); // CLSID_CorMetaDataDispenser
            Guid dispenserIID = new Guid(0x809c652e, 0x7396, 0x11d2, 0x97, 0x71, 
                0x00, 0xa0, 0xc9, 0xb4, 0xd5, 0x0c); // IID_IMetaDataDispenser
            Guid importerIID = new Guid(0x7dac8207, 0xd3ae, 0x4c75, 0x9b, 0x67, 
                0x92, 0x80, 0x1a, 0x49, 0x7d, 0x44); // IID_IMetaDataImport

            // First create the Metadata dispenser.
            object objDispenser;
            NativeMethods.CoCreateInstance(ref dispenserClassID, null, 1, 
                ref dispenserIID, out objDispenser);

            // Now open an Importer on the given filename. We'll end up passing this importer 
            // straight through to the Binder.
            object objImporter;
            IMetaDataDispenser dispenser = (IMetaDataDispenser)objDispenser;
            dispenser.OpenScope(pathModule, 0, ref importerIID, out objImporter);

            IntPtr importerPtr = IntPtr.Zero;
            ISymbolReader reader;
            try
            {
                // This will manually AddRef the underlying object, so we need to 
                // be very careful to Release it.
                importerPtr = Marshal.GetComInterfaceForObject(objImporter, 
                    typeof(IMetadataImport));

                reader = binder.GetReader(importerPtr, pathModule, searchPath);
            }
            finally
            {
                if (importerPtr != IntPtr.Zero)
                {
                    Marshal.Release(importerPtr);
                }
            }
            return reader;
        }
    }
    #region Metadata Imports

    // We can use reflection-only load context to use reflection to query for 
    // metadata information rather
    // than painfully import the com-classic metadata interfaces.
    [Guid("809c652e-7396-11d2-9771-00a0c9b4d50c"), 
        InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [ComVisible(true)]
    interface IMetaDataDispenser
    {
        // We need to be able to call OpenScope, which is the 2nd vtable slot.
        // Thus we need this one placeholder here to occupy the first slot..
        void DefineScope_Placeholder();

        //STDMETHOD(OpenScope)(                   // Return code.
        //LPCWSTR     szScope,                // [in] The scope to open.
        //  DWORD       dwOpenFlags,            // [in] Open mode flags.
        //  REFIID      riid,                   // [in] The interface desired.
        //  IUnknown    **ppIUnk) PURE;         // [out] Return interface on success.
        void OpenScope([In, MarshalAs(UnmanagedType.LPWStr)] String szScope, 
            [In] Int32 dwOpenFlags, [In] ref Guid riid, 
            [Out, MarshalAs(UnmanagedType.IUnknown)] out Object punk);

        // Don't need any other methods.
    }

    // Since we're just blindly passing this interface through managed code to the Symbinder, 
    // we don't care about actually importing the specific methods.
    // This needs to be public so that we can call Marshal.GetComInterfaceForObject() on 
    // it to get the underlying metadata pointer.
    [Guid("7DAC8207-D3AE-4c75-9B67-92801A497D44"), 
        InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [ComVisible(true)]
    [CLSCompliant(true)]
    public interface IMetadataImport
    {
        // Just need a single placeholder method so that it doesn't complain
        // about an empty interface.
        void Placeholder();
    }
    #endregion

    #endregion Get a symbol reader for the given module
}



回答2:


I see the question was asked in 2011. Now it's 2019 and there are 2 options to retrieve local variabls from a method.

First let's define VariableInfo to keep local variable parameters:

public class VariableInfo : IEquatable<VariableInfo>
{
    public int Index { get; }
    public string Name { get; }
    public Type Type { get; }

    public VariableInfo(int index, Type type, string name) =>
        (Index, Type, Name) = (index, type, name);

    public override bool Equals(object obj) =>
        Equals(obj as VariableInfo);

    public bool Equals(VariableInfo info) =>
        info != null &&
        Index.Equals(info.Index) &&
        Name.Equals(info.Name) &&
        Type.Equals(info.Type);

    public override int GetHashCode()
    {
        unchecked
        {
            var hash = 17;
            hash = 23 * hash + Index.GetHashCode();
            hash = 23 * hash + Name.GetHashCode();
            hash = 23 * hash + Type.GetHashCode();
            return hash;
        }
    }

    public override string ToString() =>
        $"Index {Index}, Type {Type}, Name {Name}";
}

Then let's define ILocalsReader interface as folows:

public interface ILocalsReader
{
    VariableInfo[] Read(MethodBase info);
}

Now one can implement it with Microsoft.Samples.Debugging.CorApi like this:

public class MicrosoftDebuggingReader : ILocalsReader
{
    public VariableInfo[] Read(MethodBase info)
    {
        var il = info.GetMethodBody().LocalVariables.ToArray();

        return SymbolAccess
            .GetReaderForFile(info.DeclaringType.Assembly.Location)
            .GetMethod(new SymbolToken(info.MetadataToken))
            .RootScope
            .GetInnerScopesRecursive()
            .SelectMany(scope => scope.GetLocals())
            .Select(local =>
                new VariableInfo(local.AddressField1,
                                 il[local.AddressField1].LocalType,
                                 local.Name))
           .ToArray();
    }
}

Where GetInnerScopesRecursive is an extension method:

internal static class SymbolScopeExtensions
{
    public static IEnumerable<ISymbolScope> GetInnerScopesRecursive(this ISymbolScope scope)
    {
        yield return scope;
        foreach (var innerScope in scope.GetChildren()
            .SelectMany(innerScope => innerScope.GetInnerScopesRecursive()))
            yield return innerScope;
    }
}

Remember to build against x64.

Another option is using Mono.Cecil like this:

public class MonoCecilReader : ILocalsReader
{
    public VariableInfo[] Read(MethodBase info)
    {
        var method = info.GetMethodDefinition();
        method.Module.ReadSymbols();

        var pdb = Path.ChangeExtension(info.DeclaringType.Assembly.Location, "pdb");
        new PdbReaderProvider().GetSymbolReader(method.Module, pdb)
                               .Read(method);

        var il = info.GetMethodBody().LocalVariables;
        return Read(method, il);
    }

    public VariableInfo[] Read(MethodDefinition method, IList<LocalVariableInfo> il)
    {
        return method
               .DebugInformation
               .Scope
               .GetInnerScopesRecursive()
               .SelectMany(scope => scope.Variables)
               .Select(local =>
                   new VariableInfo(local.Index,
                                    il[local.Index].LocalType,
                                    local.Name))
               .ToArray();
    }
}

Where GetMethodDefinition is an extension method:

public static class MethodDefinitionExtensions
{
    public static MethodDefinition GetMethodDefinition(this MethodBase info) =>
        AssemblyDefinition
        .ReadAssembly(info.DeclaringType.Assembly.Location)
        .Modules
        .SelectMany(module => module.GetTypes())
        .Single(type => type.FullNameMatches(info.DeclaringType))
        .Methods
        .FirstOrDefault(method =>
            method.Name.Equals(info.Name) &&
            method.ReturnType.FullName.Equals(info.GetReturnType().FullName) &&
            method.Parameters.Select(parameter => parameter.ParameterType.FullName)
                  .SequenceEqual(info.GetParameters().Select(parameter => parameter.ParameterType.FullName)));
}

And GetReturnType is an extension method:

public static class MethodBaseExtensions
{
    public static Type GetReturnType(this MethodBase method)
    {
        if (method is MethodInfo info)
            return info.ReturnType;

        if (method is ConstructorInfo ctor)
            return typeof(void);

        throw new ArgumentException($"Argument {nameof(method)} has unsupported type {method.GetType()}.");
    }
}

And FullNameMatches is an extension method:

internal static class TypeDefinitionExtensions
{
    public static bool FullNameMatches(this TypeDefinition typeDefinition, Type type) =>
        typeDefinition.FullName.Replace("/", "").Equals(type.FullName.Replace("+", ""));
}

And GetInnerScopesRecursive is an extension method:

internal static class ScopeDebugInformationExtensions
{
    public static IEnumerable<ScopeDebugInformation> GetInnerScopesRecursive(this ScopeDebugInformation scope)
    {
        yield return scope;
        foreach (var innerScope in scope.Scopes
            .SelectMany(innerScope => innerScope.GetInnerScopesRecursive()))
            yield return innerScope;
    }
}

Usage:

class Program
{
    static void Main(string[] args)
    {
        var info = new Action<string>(Foo).GetMethodInfo();

        Console.WriteLine("\tMicrosoft.Samples.Debugging.CorSymbolStore");
        foreach (var v in new MicrosoftDebuggingReader().Read(info))
            Console.WriteLine(v);

        Console.WriteLine("\tMono.Cecil");
        foreach (var v in new MonoCecilReader().Read(info))
            Console.WriteLine(v);
    }

    public static void Foo(string s)
    {
        for (int i; ;)
            for (double j; ;)
                for (bool k; ;)
                    for (object m = 0; ;)
                        for (DateTime n; ;) { }
    }
}

Gives:

        Microsoft.Samples.Debugging.CorSymbolStore
Index 0, Type System.Int32, Name i
Index 1, Type System.Double, Name j
Index 2, Type System.Boolean, Name k
Index 3, Type System.Object, Name m
Index 4, Type System.DateTime, Name n
        Mono.Cecil
Index 0, Type System.Int32, Name i
Index 1, Type System.Double, Name j
Index 2, Type System.Boolean, Name k
Index 3, Type System.Object, Name m
Index 4, Type System.DateTime, Name n

Note:

  • Microsoft.Samples.Debugging.CorApi has ~9k downloads and was last updated on 10.12.2011
  • Mono.Cecil has ~3415k downloads and latest commit on 05.08.2019



回答3:


Look at the CCI project on Codeplex. It has a PDBReader project.



来源:https://stackoverflow.com/questions/6166767/retrieve-local-variable-name-from-pdb-files

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!