从PDB文件中检索本地变量名

| 我目前正在尝试从IL字节码和PDB文件中检索源代码, 我到了可以从IL和反射生成源代码的地步 我知道局部变量名称的名称包含在pdb文件中。 我的问题是如何找到它?我应该使用哪些库来处理pdb文件(如果有),或者我应该自己编写代码?在哪里可以找到有关pdb文件格式的信息? 目前在生成的源代码中,我正在使用本地变量的自动生成的值,但是我想更改它,因为我相信如果您有pdb文件可供使用,则可以找到该信息。 我试图在Google上查看,但没有找到任何有用的信息。 预先感谢您的答复;)     
已邀请:
        这是使用
System.Diagnostics.SymbolStore
中的类型从MethodInfo读取局部变量名称的方法:
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);
    }
}
SymUtil
类来自此示例。 编辑:上面的链接已损坏。从谷歌缓存:   很久以前,我在研究如何处理pdb文件,以便   获得反射根本无法提供的其他信息。   现在我绊倒了一个5岁的Mike Stall的职位   (这里)   突然之间一切都变得清晰了。我整理了一个小例子   使用.net 4.0读取方法主体代码,就像在源代码中一样   从汇编的位置开始。请注意这一点   要工作,您必须具有程序集的pdb。也   请确保您添加对ISymWrapper和您的项目的引用   针对.Net 4.0框架,而不是.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
}
    
        我看到这个问题是在2011年提出的。现在是2019年,有2种方法可以从一种方法中检索局部变量。 首先让我们定义ѭ4来保留局部变量参数:
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}\";
}
然后让我们将“ 6”接口定义如下:
public interface ILocalsReader
{
    VariableInfo[] Read(MethodBase info);
}
现在可以像这样用
Microsoft.Samples.Debugging.CorApi
实现它:
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();
    }
}
其中“ 10”是扩展方法:
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;
    }
}
记住要反对ѭ12build。 另一个选择是使用
Mono.Cecil
像这样:
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();
    }
}
其中“ 15”是扩展方法:
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)));
}
GetReturnType
是扩展方法:
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()}.\");
    }
}
FullNameMatches
是扩展方法:
internal static class TypeDefinitionExtensions
{
    public static bool FullNameMatches(this TypeDefinition typeDefinition, Type type) =>
        typeDefinition.FullName.Replace(\"/\", \"\").Equals(type.FullName.Replace(\"+\", \"\"));
}
GetInnerScopesRecursive
是扩展方法:
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;
    }
}
用法:
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; ;) { }
    }
}
给出:
        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
注意:
Microsoft.Samples.Debugging.CorApi
下载量约9000,最新更新于2011年10月10日
Mono.Cecil
共有〜3415k下载,最新提交时间为05.08.2019     
        查看Codeplex上的CCI项目。它具有一个PDBReader项目。     

要回复问题请先登录注册