MSIL检查

问题描述 投票:0回答:4

我有一些字节格式的 MSIL(反射的 GetMethodBody() 的结果),我想分析一下。我想在 MSIL 中找到使用 new 运算符创建的所有类。关于如何以编程方式执行此操作的任何想法?

.net cil
4个回答
2
投票

我最终在这里使用了 MSIL 解析器:http://blogs.msdn.com/zelmalki/archive/2008/12/11/msil-parser.aspx,对源代码稍作修改以在 ConstructorInfo 以及MethodInfo(从反射器返回的结果)。

它将给出操作列表,以及操作码和参数。操作码是一个枚举,基于该值可以解释参数。参数是二进制形式,需要使用MethodInfo.Module.Resolve*()来获取实际参数值。

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

namespace AspnetReflection
{
    public class MsilReader
    {
        static readonly Dictionary<short, OpCode> _instructionLookup;
        static readonly object _syncObject = new object();
        readonly BinaryReader _methodReader;
        MsilInstruction _current;
        Module _module; // Need to resolve method, type tokens etc


        static MsilReader()
        {
            if (_instructionLookup == null)
            {
                lock (_syncObject)
                {
                    if (_instructionLookup == null)
                    {
                        _instructionLookup = GetLookupTable();
                    }
                }
            }
        }

        public MsilReader(MethodInfo method)
        {
            if (method == null)
            {
                throw new ArgumentException("method");
            }

            _module = method.Module;
            _methodReader = new BinaryReader(new MemoryStream(method.GetMethodBody().GetILAsByteArray()));
        }

        public MsilReader(ConstructorInfo contructor)
        {
            if (contructor == null)
            {
                throw new ArgumentException("contructor");
            }

            _module = contructor.Module;
            _methodReader = new BinaryReader(new MemoryStream(contructor.GetMethodBody().GetILAsByteArray()));
        }

        public MsilInstruction Current
        {
            get { return _current; }
        }


        public bool Read()
        {
            if (_methodReader.BaseStream.Length == _methodReader.BaseStream.Position)
            {
                return false;
            }

            int instructionValue;

            if (_methodReader.BaseStream.Length - 1 == _methodReader.BaseStream.Position)
            {
                instructionValue = _methodReader.ReadByte();
            }
            else
            {
                instructionValue = _methodReader.ReadUInt16();

                if ((instructionValue & OpCodes.Prefix1.Value) != OpCodes.Prefix1.Value)
                {
                    instructionValue &= 0xff;
                    _methodReader.BaseStream.Position--;
                }
                else
                {
                    instructionValue = ((0xFF00 & instructionValue) >> 8) |
                                       ((0xFF & instructionValue) << 8);
                }
            }

            OpCode code;

            if (!_instructionLookup.TryGetValue((short) instructionValue, out code))
            {
                throw new InvalidProgramException();
            }

            int dataSize = GetSize(code.OperandType);

            var data = new byte[dataSize];

            _methodReader.Read(data, 0, dataSize);

            _current = new MsilInstruction(code, data);

            return true;
        }

        static int GetSize(OperandType opType)
        {
            int size = 0;

            switch (opType)
            {
                case OperandType.InlineNone:
                    return 0;
                case OperandType.ShortInlineBrTarget:
                case OperandType.ShortInlineI:
                case OperandType.ShortInlineVar:
                    return 1;

                case OperandType.InlineVar:
                    return 2;

                case OperandType.InlineBrTarget:
                case OperandType.InlineField:
                case OperandType.InlineI:
                case OperandType.InlineMethod:
                case OperandType.InlineSig:
                case OperandType.InlineString:
                case OperandType.InlineSwitch:
                case OperandType.InlineTok:
                case OperandType.InlineType:
                case OperandType.ShortInlineR:
                    return 4;
                case OperandType.InlineI8:

                case OperandType.InlineR:


                    return 8;

                default:

                    return 0;
            }
        }


        static Dictionary<short, OpCode> GetLookupTable()
        {
            var lookupTable = new Dictionary<short, OpCode>();

            FieldInfo[] fields = typeof (OpCodes).GetFields(BindingFlags.Static | BindingFlags.Public);

            foreach (FieldInfo field in fields)
            {
                var code = (OpCode) field.GetValue(null);

                lookupTable.Add(code.Value, code);
            }

            return lookupTable;
        }
    }


    public struct MsilInstruction
    {
        public readonly byte[] Data;
        public readonly OpCode Instruction;

        public MsilInstruction(OpCode code, byte[] data)
        {
            Instruction = code;

            Data = data;
        }


        public override string ToString()
        {
            var builder = new StringBuilder();

            builder.Append(Instruction.Name + " ");

            if (Data != null && Data.Length > 0)
            {
                builder.Append("0x");

                foreach (byte b in Data)
                {
                    builder.Append(b.ToString("x2"));
                }
            }

            return builder.ToString();
        }
    }
}

1
投票

你可以看看像 FxCop 这样的工具背后的引擎。它被命名为CCI。或者查看 Mono 中名为 Cecil 的那个,它是 Gendarme 的基础。它们是为这些(和其他)任务而构建的。


1
投票

查看有关 codeproject 的这篇文章 http://www.codeproject.com/KB/cs/sdilreader.aspx

使用源代码,使您能够将 IL byte[] 放入指令列表中。如果您正在处理 Generic,您可能想要滚动浏览消息并查看我在那篇文章(Bug Fix for Generic)中发布的帖子,该帖子修复了与使用 Generic 相关的一些错误(仅当您想要将 IL 转换为显示文本)。

一旦你有了所有的 IL 指令,你所需要的就是循环遍历它们并在指令的操作码 (instruction.code) 与 OpCodes.Newobj 或 Newarr 匹配时增加计数。

如果你想对MSIL的内部有更深入的了解,强烈推荐John Gough的《Compiling for the .NET CLR》一书。


0
投票

我还发现 Frank 发现的代码非常有用,但它确实有一个问题,即开关操作码未正确处理。

来自 MSDN,操作码后跟一个 int32,其中包含跳转表中的项目数,然后是要跳转到的位置。所以一个有 3 个项目的开关实际上有 16 个数据字节而不是 4 个。

我正在使用 Ziad Elmalki second post 中关于该主题的代码,其中包含一个 GetData 方法来识别诸如方法调用目标之类的东西。

我通过更改 GetData 中的处理来更正开关操作码的处理,使其看起来更像这样:

    case OperandType.InlineSwitch:
        {
            int numberOfCases = BitConverter.ToInt32(rawData, 0);
            int[] caseAddresses = new int[numberOfCases];
            byte[] caseData = new byte[4];
            for (int i = 0; i < numberOfCases; i++)
            {
                _methodReader.Read(caseData, 0, caseData.Length);
                caseAddresses[i] = BitConverter.ToInt32(caseData, 0);
            }
            data = caseAddresses;
        }
        break;
© www.soinside.com 2019 - 2024. All rights reserved.