You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解析IL字节中的方法调用?查找调用指定方法的程序集方法

解决方案

一、用现成库快速实现(推荐)

不用自己手动解析IL,直接用ILSpy背后的ICSharpCode.Decompiler库,它已经封装了IL解析逻辑,能轻松检测方法调用。

步骤:

  1. 安装NuGet包ICSharpCode.Decompiler
  2. 遍历程序集中的所有方法,对每个有方法体的方法,用库分析其IL中的调用指令:

示例代码:

using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.IL;
using System.Reflection;

public static IEnumerable<MethodInfo> FindCallers(MethodInfo targetMethod, Assembly assembly)
{
    var decompiler = new CSharpDecompiler(assembly.Location, new DecompilerSettings());
    
    foreach (var type in assembly.GetTypes())
    {
        foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
        {
            if (!method.HasMethodBody) continue;
            
            try
            {
                var ilMethod = decompiler.DecompileMethod(method);
                // 查找所有调用指令,包括call、callvirt、newobj(构造函数调用)
                var calls = ilMethod.Descendants.OfType<Call>()
                    .Concat(ilMethod.Descendants.OfType<NewObj>());
                
                foreach (var call in calls)
                {
                    var calledMethod = call.Target.MetadataToken.Resolve(method.Module) as MethodInfo;
                    if (calledMethod == targetMethod)
                    {
                        yield return method;
                        break; // 找到一个调用就停止检查当前方法
                    }
                }
            }
            catch { /* 跳过无法反编译的方法 */ }
        }
    }
}

这个方法能直接定位所有调用目标方法的函数,包括构造函数的newobj调用。

二、手动解析IL的实现思路

如果一定要自己写解析逻辑,核心是识别IL中的调用指令并解析元数据令牌:

关键IL调用指令

  • call(操作码0x28):调用静态方法、非虚实例方法
  • callvirt(操作码0x6F):调用虚方法、接口方法
  • newobj(操作码0x73):调用构造函数(创建实例时)

这些指令的结构都是:操作码 + 4字节元数据令牌,令牌指向被调用方法的元数据信息。

算法大纲

  1. 遍历目标程序集中的所有类型,再遍历每个类型的所有方法(注意包含非公开、静态方法)
  2. 对每个方法,跳过无方法体的(抽象、接口方法等)
  3. 获取方法体的IL字节数组:method.GetMethodBody()?.GetILAsByteArray()
  4. 遍历IL字节数组,逐个解析指令:
    • 读取当前字节作为操作码,判断是否是call/callvirt/newobj
    • 如果是,读取后续4字节(小端序)作为元数据令牌
    • 用module.ResolveMethod(token)将令牌转为MethodInfo
    • 对比该MethodInfo是否等于目标方法,是则记录当前方法为调用者
  5. 继续遍历直到IL数组结束

示例代码片段(核心解析部分):

public static IEnumerable<MethodInfo> FindCallersManual(MethodInfo targetMethod, Assembly assembly)
{
    var targetToken = targetMethod.MetadataToken;
    var targetModule = targetMethod.Module;
    
    foreach (var type in assembly.GetTypes())
    {
        foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
        {
            var body = method.GetMethodBody();
            if (body == null) continue;
            
            var ilBytes = body.GetILAsByteArray();
            int offset = 0;
            while (offset < ilBytes.Length)
            {
                byte opcode = ilBytes[offset];
                offset++;
                
                // 检查是否是调用类指令
                if (opcode == 0x28 || opcode == 0x6F || opcode == 0x73)
                {
                    // 读取4字节元数据令牌(小端)
                    int token = BitConverter.ToInt32(ilBytes, offset);
                    offset += 4;
                    
                    try
                    {
                        var calledMethod = targetModule.ResolveMethod(token);
                        if (calledMethod.MetadataToken == targetToken && calledMethod.Module == targetModule)
                        {
                            yield return method;
                            break;
                        }
                    }
                    catch { /* 跳过无效令牌 */ }
                }
                else
                {
                    // 跳过非调用指令,根据操作码长度移动偏移
                    offset += GetOperandSize(opcode);
                }
            }
        }
    }
}

// 辅助函数:获取操作码的操作数长度(简化版)
private static int GetOperandSize(byte opcode)
{
    // 大部分单字节操作码无操作数或操作数长度固定,这里只处理常见情况
    switch (opcode)
    {
        case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07:
        case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0E: case 0x0F:
        case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
        case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: case 0x1F:
            return 0; // 无操作数
        case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27:
        case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F: case 0x30:
            return 4; // 4字节操作数
        default:
            return 1;
    }
}

三、入门资料推荐

  1. 《CLR via C#》:书中有专门章节讲解IL指令和方法体结构,比CLI文档更通俗易懂,适合快速上手。
  2. CLI Partition III:CIL Instruction Set:虽然CLI文档厚,但只需要看第三部分的IL指令集章节,重点关注call、callvirt、newobj这几个指令的格式,不用通读全文。
  3. 微软官方IL指令参考:直接搜索“IL指令集”即可找到官方文档,每个指令的格式和用途都有明确说明。

内容的提问来源于stack exchange,提问作者ChrisW

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 08:35:03