一、简介
    今天是《Net 高级调试》的第十篇文章。说起来,高级调试,调试的内容还是挺多的,技巧也不少,但是,要想做一个合格的高级调试人员,还需要掌握如何调试动态生成的IL代码。今天要探讨的高级调试的技巧是如何调试通过 Emit 动态生成 IL 代码。可能有人会问,我们不是编写 C# 代码,或者是 VB.Net 代码吗?怎么还要动态生成 IL 代码,这些工作不是编译器做的吗?当然,一般情况是这样的,但是,当我们编写一些高性能的框架的时候,使用 IL 代码编写也是常事。既然也可以直接使用 IL 编写代码,那对它的调试也是少不了的,调试机会虽然很少,具有这个本领,等遇到这样的问题,就不至于慌乱了,俗话说的好:艺多不压身。当然了,第一次看视频或者看书,是很迷糊的,不知道如何操作,还是那句老话,一遍不行,那就再来一遍,还不行,那就再来一遍,俗话说的好,书读千遍,其意自现。
     如果在没有说明的情况下,所有代码的测试环境都是 Net Framewok 4.8,但是,有时候为了查看源码,可能需要使用 Net Core 的项目,我会在项目章节里进行说明。好了,废话不多说,开始我们今天的调试工作。

       调试环境我需要进行说明,以防大家不清楚,具体情况我已经罗列出来。
          操作系统:Windows Professional 10
          调试工具:Windbg Preview(可以去Microsoft Store 去下载)
          开发工具:Visual Studio 2022
          Net 版本:Net Framework 4.8
          CoreCLR源码:源码下载

二、基础知识

    1、动态代码调试
        动态代码调试的机会虽然不多,但是掌握动态代码调试的技巧还是很有必要的,俗话说的好,艺多不压身。当我们遇到有这样写代码,由此引发的问题的时候,我们也可以做到遇事不惊。
        今天我们就讨论三种调试的技巧。

    2、三种调试策略

        2.1、捕获 JIT 的 CompileMethod 方法。
            C# 代码的编译过程分为两个阶段,第一个阶段就是编译器的阶段,这个阶段编译器将我们编写的C#源代码编译成 IL 代码,第二阶段,就是在运行的时候,JIT 将 IL 代码编译为机器代码,我们的程序就运行了。现在是动态生成的 IL 代码,没有第一个阶段,直接就是第二个 JIT 编译的阶段,我们可以尝试在 JIT 的某个方法中设置断点进行拦截,拿到方法的描述符,也就是 MD,有了 MD,我们就可以使用 bp md 命令,为这个方法下断点,这样就可以对动态生成的代码进行调试了。

        2.2、从代码中获取委托的函数指针(我的测试没有实现)。
            这种方法比较简单,但需要在方法中加一行代码,具有一定的破坏性。代码:Marshal.GetFunctionPointerForDelegate(委托实例).ToInt64()。

        2.3、在动态的代码中注入 Debugger.Break()。
            我们在很多的调试中都会用到 Debugger.Break() 函数,让程序中断到调试器,如果这个动态代码,我们具有完整的控制权,我们就可以通过注入这个方法:Debugger.Break(),实现 int 3 中断,进而获取想要的数据。代码如下:IL.Emit(OpCodes.Call,typeof(Debugger).GetMethod("Break"))。

    3、程序集
        3.1、程序集崩溃
            程序集泄露是指什么呢?就是程序的内存暴涨,产生大量的程序集。

三、调试过程
    废话不多说,这一节是具体的调试操作的过程,又可以说是眼见为实的过程,在开始之前,我还是要啰嗦两句,这一节分为两个部分,第一部分是测试的源码部分,没有代码,当然就谈不上测试了,调试必须有载体。第二部分就是根据具体的代码来证实我们学到的知识,是具体的眼见为实。

    1、测试源码
        以下项目代码就是在眼见为实用到的测试用例。
        1.1、Example_10_1_1
Net 高级调试之十:轻量级代码生成的调试-LMLPHPNet 高级调试之十:轻量级代码生成的调试-LMLPHP
 1 namespace Example_10_1_1
 2 {
 3     internal class Program
 4     {
 5         private delegate int AddDelegate(int a, int b);
 6         static void Main(string[] args)
 7         {
 8             var dynamicAdd = new DynamicMethod("Add", typeof(int), new Type[] { typeof(int), typeof(int) });
 9             var il = dynamicAdd.GetILGenerator();
10             il.Emit(OpCodes.Ldarg_0);
11             il.Emit(OpCodes.Ldarg_1);
12             il.Emit(OpCodes.Add);
13             il.Emit(OpCodes.Ret);
14 
15             var addDelegate = (AddDelegate)dynamicAdd.CreateDelegate(typeof(AddDelegate));
16             Console.WriteLine(addDelegate(10, 20));
17         }
18     }
19 }
11-21 17:47