我目前正在调试公司的CLR分析器(通过ASP.NET 4.7.3282.0,.NET Framework 4.7.2),并看到CLR卸载通用类但使用ClassUnloadStarted回调的情况。不被调用。
简而言之,我们的探查器会根据 ClassLoadStarted , ClassLoadFinished 和 ClassUnloadStarted 回调跟踪基于ClassID加载的类。在某个时候,该类(及其相关模块)将被卸载,但是相关类ID不会调用 ClassUnloadStarted 回调。因此,我们剩下一个停滞的ClassID,以为该类仍在加载。稍后,当我们尝试查询该ClassID时,CLR意外崩溃(因为它现在指向垃圾内存)。我的问题,考虑以下详细情况:
我找不到关于此行为的任何文档或理由,尤其是未调用 ClassUnloadStarted 的行为。我也没有在CoreCLR代码中找到任何提示。预先感谢您的帮助!
详细方案:
这是有问题的课程(IComparable(T)
和T=ClassFromModuleFoo
):
System/IComparable`1<ClassFromModuleFoo>
在应用程序运行时,问题在某些模块卸载后显现出来。
这是基于添加的调试打印的确切的加载/卸载回调流程:
System/IComparable'1(ClassFromModuleFoo)
。ClassFromModuleFoo
装入程序集#1。IComparable
和ClassFromModuleFoo
再次被加载,这次是在程序集2中。现在每个类有两个实例:一个在Foo中加载到程序集#1中,一个在Foo中加载到程序集#2中。ClassUnloadStarted
调用了ClassFromModuleFoo
回调。ClassUnloadStarted
在以后的任何时候都不会被召集{1} {1}}(即使其模块已卸载且其ClassID指向现在已损坏的内存)。一些其他信息:
System/IComparable'1(ClassFromModuleFoo)
来禁用本机映像,以为它可能会影响ClassLoad *回调,但并没有任何区别。我确认确实加载了COR_PRF_DISABLE_ALL_NGEN_IMAGES
而不是其本机映像。编辑:
由于我非常聪明的同事,我能够用一个小的示例项目来重现该问题,该示例项目通过加载和卸载AppDomains来模拟这种情况。在这里:
https://github.com/shaharv/dotnet/tree/master/testers/module-load-unload
崩溃发生在测试中的此类上,该类已被卸载,并且CLR没有为此调用回调:
mscorlib.dll
这是相关的代码,它被加载和卸载了几次:
Loop/MyGenList`1<System/String>
在某些时候,探查器在尝试查询该类的ClassID时崩溃-认为它仍然有效,因为未调用卸载回调。
另一方面,我尝试将该示例移植到.NET Core进行进一步的研究,但无法弄清楚怎么做,因为.NET Core不支持辅助AppDomain(而且我不确定它是否支持一般按需组装)。
答案 0 :(得分:1)
在.Net Core中使其成为可能(3.0之前不支持卸载)之后,我们设法对其进行了复制(感谢valiano!)。已被coreclr团队(https://github.com/dotnet/coreclr/issues/26126)确认为错误。
根据戴夫马森的解释:
涉及三种独立的类型,每个回调仅 给你两个(但两个不同)。
Plugin.MyGenList1:未绑定的通用类型Plugin.MyGenList1 :绑定到规范类型的通用类型(已使用 供普通参考)Plugin.MyGenList1:通用 类型绑定到System.String。对于ClassLoadStarted,我们有逻辑 具体排除了未绑定的通用类型(即 在显示给探查器中显示为Plugin.MyGenList1) ClassLoader :: Notify
这意味着您ClassLoadStarted仅给您回调 规范和字符串实例。这似乎是正确的做法, 因为作为探查器,您只关心绑定的泛型类型和 没有约束的人没有兴趣。
问题是我们针对 ClassUnloadStarted。该回调发生在EEClass :: Destruct内部,并且 仅在非通用类型,未绑定通用类型, 和规范的泛型类型。非规范的通用类型(即 Plugin.MyGenList1)被跳过。