在JIT编译生成的函数上设置断点
在前面已经介绍过,JIT编译器编译了一个函数并将其放在内存中。如果我们知道JIT编译器保存机器代码的位置,就可以使用调试器的bp命令来设置 断点。我们再次使用在前面介绍过的03breakpoint.exe程序,并且试验能否在AddAndPrint函数上设置一个断点。具体来说,我们希望 在第二次调用这个函数时设置一个断点,以便分析其中潜在的错误。在调试器下启动03breakpoint.exe,并继续执行直到程序提示按任意键。按下 任意键,等待并直到第二次提示按下任意键。此时,按下CTRL-C进入调试器。这是在第二个AddAndPrint函数中设置断点的起始位置。第一个任务 就是判断这个函数是否已经被JIT编译器编译,这可以通过SOS命令name2ee来进行判断。命令name2ee的形式如下所示:
![]() |
参数module name是需要分析的模块,而type or method name则表示需要获得信息的类型名或者方法名。在这里,module name是03breakpoint.exe,而type name为Advanced.NET.Debugging.Chapter3.Breakpoint.AddAndPrint。
![]() |
可以看到,在最后一行输出中,这个方法的状态为JITTED(表示已经被JIT编译器编译过了),并且编译之后的地址为003e0178。我们可以通过命令U对这段代码做一个简单的完整性检查(sanity check,在后面将进一步讨论),如清单3-9所示。
清单3-9通过反编译JIT生成的代码对JIT地址进行完整性检查
![]() |
在反汇编代码的第一部分很清楚地表明了传递给命令U的地址参数是正确的。命令U告诉我们,位
于这个地址上的代码是JIT生成代码。此外,下一行则告诉我们与这段代码相对应的方法名(这里是
Advanced.NET.Debugging.Chapter3.Breakpoint.AddAndPrint)。最后,第三行是起始地址
(003e0178)和生成代码的大小(80)。在这些初始信息之后是反汇编后的指令。现在,我们可以找到动态生成代码的正确地址,并且在这个地址上设置
一个断点。如果恢复程序执行(不要忘记按下任意键来启动第二次调用),可以很快地触发断点,如下所示:
![]() |
在触发断点后,可以使用ClrStack命令来确保触发的是正确的代码位置。
之前,我们希望在第二次调用AddAndPrint时设置一个断点,因为这次的调用中存在一个错误。而现在,我相信你已经知道了为什么要在第二次调 用时设置一个断点,就是因为在一个由JIT编译的函数上设置断点是很容易的(在前一次调用该函数时会使这个函数被JIT编译)。我们可以通过 name2ee命令来找出JIT编译后代码的地址,然后在这个地址上使用bp命令。
这是在设置断点时可以使用的另一种方法。SOS调试器扩展包含了一个命令bpmd,这个命令能够极大地简化工作,它能自动找出被JIT编译后代码的正确地址,并且可以仅根据完整的方法名来设置断点。
接下来,我们来看看如何在还没有被JIT编译的函数上设置断点。毕竟,我们不能总在被JIT编译后的函数上设置断点,有时候还需要在第一次调用函数时就设置断点,而此时函数通常还没有被编译。