Retrieve Local variable name from PDB files

后端 未结 3 476
陌清茗
陌清茗 2020-12-19 15:48

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 n

3条回答
  •  梦毁少年i
    2020-12-19 16:50

    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
    {
        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 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 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 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(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

提交回复
热议问题