在.NET Core中反编译匿名方法的IL代码

3
我有这个方法。
public void Add(Action<Cursor> action)
{
    // Decompile `action` to C#
}

我想在该方法内部反编译action中的匿名方法为C#代码或C#代码的AST表示。

我尝试使用Mono.CecilICSharpCode.Decompiler(从ILSpy代码库) ,但我找不到一种方法来反编译匿名方法,因为这两个库需要你从物理位置加载程序集,并通过类型图搜索该方法,这似乎非常复杂,特别是当要查找匿名方法时。

是否有一种方法可以使用该方法的IL字节数组并将其反编译为C#?

public void Add(Action<Cursor> action)
{
    var il = action.Method.GetMethodBody().GetILAsByteArray();
    var ast = decompiler.DecompileIL(il); // Does such decompiler exist?
}
1个回答

2

使用ILSpy存储库中的ICSharpCode.Decompiler库,我能够反编译一个Lambda方法。

像这样

public void Add(Action<Cursor> action)
{
    // Get the assembly in which the lambda resides
    var asm = Assembly.GetCallingAssembly();
    var file = asm.Location;

    // Create a resolver (a callback for resolving referenced assemblies during decompilation)
    var resolver = new CustomAssemblyResolver(asm);
    // Create an instance of decompiler
    var decompiler = new CSharpDecompiler(file, resolver, new DecompilerSettings());

    // Get an "EntityHandle" instance for the lambda's underlying method
    var method = MetadataTokenHelpers.TryAsEntityHandle(action.Method.MetadataToken); 

    var ast = decompiler.Decompile(new List<EntityHandle>() { method.Value });
}

正如我之前提到的,您需要为CSharpDecompiler实例提供一个解析器(IAssemblyResolver),其任务是在反编译过程中加载引用的程序集。 ICSharpCode.Decompiler有一个UniversalAssemblyResolver(source),它通过搜索文件系统中的常见文件夹来完成此任务(它接收一个框架版本以了解在哪里搜索)。我不喜欢它,因为它假设了太多并需要太多信息(我并不总是知道框架或我的运行时的其他部分),所以我创建了自己的解析器,遍历程序集图(从调用程序集开始)。
class CustomAssemblyResolver : IAssemblyResolver
{
    private Dictionary<string, Assembly> _references;

    public CustomAssemblyResolver(Assembly asm)
    {
        _references = new Dictionary<string, Assembly>();
        var stack = new Stack<Assembly>();
        stack.Push(asm);

        while (stack.Count > 0)
        {
            var top = stack.Pop();
            if (_references.ContainsKey(top.FullName)) continue;

            _references[top.FullName] = top;

            var refs = top.GetReferencedAssemblies();
            if (refs != null && refs.Length > 0)
            {
                foreach (var r in refs)
                {
                    stack.Push(Assembly.Load(r));
                }
            }
        }

        ;
    }

    public PEFile Resolve(IAssemblyReference reference)
    {
        var asm = _references[reference.FullName];
        var file = asm.Location;
        return new PEFile(file, new FileStream(file, FileMode.Open, FileAccess.Read), PEStreamOptions.Default, MetadataReaderOptions.Default);
    }

    public PEFile ResolveModule(PEFile mainModule, string moduleName)
    {
        var baseDir = Path.GetDirectoryName(mainModule.FileName);
        var moduleFileName = Path.Combine(baseDir, moduleName);
        if (!File.Exists(moduleFileName))
        {
            throw new Exception($"Module {moduleName} could not be found");
        }
        return new PEFile(moduleFileName, new FileStream(moduleFileName, FileMode.Open, FileAccess.Read), PEStreamOptions.Default, MetadataReaderOptions.Default);
    }
}

我不知道是否适用于所有用例,但它对我来说非常有效,至少到目前为止。


1
这可能不是最好的做法,但当我需要根据元数据令牌从文件中获取任意函数的AST时,这也对我有所帮助。在这种情况下,我不需要实现自定义解析器。 - Plutoberth
在我的情况下,我正在使用外部库和 NuGet 包,并且我需要获取这些包的元数据。也许有更好的方法做到这一点,也许我可以更加有选择性地解析它们。@Plutoberth - areller
如果您要发布C#代码,请遵循C#编码规范。https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions - Michael Jordan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接