为什么托管调试与本机调试不同?
人们会问“为什么本机调试器不能调试托管代码?”.
原因是CLR提供了许多在典型的本地C++应用程序中所获得的酷服务,例如:运行在虚拟机/JITEN、动态类布局、类型系统、垃圾收集、反射等等。每一个都对调试器提出了特殊的挑战。换句话说,一个完成了所有这些功能的本地应用程序根本无法与传统的本地调试器进行调试。
1) 本机调试可以在硬件级别抽象,但是托管调试需要在IL级别抽象。托管代码不能仅仅被压缩成C/C++本地调试范例。一个原因是这可能会限制CLR执行IL的选项。例如,尽管目前(从v2.0起)jit-IL,我们还是希望为诸如解释IL、推销很少使用的jitted代码、甚至重新jitting代码等事情敞开大门。如果ICorDebug对所有内容都使用本机代码偏移量,它将无法调试解释的IL。
2) 托管调试需要很多信息,直到运行时才可用。对于托管代码,编译器只生成IL,真正的调试信息直到运行时才得到解析。例如,JIT将在运行时将IL编译为本机代码,加载程序将在运行时动态确定大多数类的布局。类型系统可以在运行时创建新类型. 对于本机代码,这都是在编译时确定的。托管调试器需要某种方法在运行时获取所有这些信息。一些解决方案包括:
a、 让CLR在运行时在信息确定时创建辅助pdb。这可能是一个巨大的性能命中率,如果没有附加调试器,我们不愿意这样做。但是如果我们在没有附加调试器的情况下不这样做,那么如果以后调试器附加了调试器,它可能就不可用了。
b、 让托管调试器检查相关的CLR数据结构(直接从进程外或通过进程内运行的“helper”线程)。这里的一个重要警告是确保当CLR数据结构处于不一致状态时,调试器不会请求此类信息。CLR当前使用帮助线程。
3) 托管调试器需要与垃圾回收器(GC)协调。CLR有一个标记-清除压缩GC。这意味着GC将移动对象来整理堆碎片,并在整个过程中相应地更新所有引用(“GC根”)。这会从几个方面影响调试:
a、 调试对象在GC期间暂时处于不一致的状态。调试器必须与GC协调,以确保在此窗口期间不会检查调试对象。
b、 调试器可以让用户更改变量的值。此更新必须与GC的更新相协调。
c、 没有方便的对象标识。在本机代码中,对象的原始指针值唯一地标识该对象,因为对象不会四处移动。