在GC终结器线程中崩溃,“DestroyScout”有什么问题?

8

我正在处理一个 .Net 服务器应用程序,它几乎每周都会在“GC Finalizer Thread”上出现问题而崩溃,更确切地说是在“mscorlib.dll ...~DestroyScout ()”的第798行,根据 Visual Studio 的提示。

Visual Studio 还试图打开文件“DynamicILGenerator.gs”。我没有这个文件,但我找到了一个版本的文件,在这个文件中,第798行确实位于 DestroyScout 的析构函数中(无论这是什么意思)。

在我的 Visual Studio 环境中,我有以下信息:

线程:

Not Flagged >   5892    0   Worker Thread   GC Finalizer Thread mscorlib.dll!System.Reflection.Emit.DynamicResolver.DestroyScout.~DestroyScout

调用栈:

    [Managed to Native Transition]  
>   mscorlib.dll!System.Reflection.Emit.DynamicResolver.DestroyScout.~DestroyScout() Line 798   C#
[Native to Managed Transition]  
kernel32.dll!@BaseThreadInitThunk@12()  Unknown
ntdll.dll!__RtlUserThreadStart()    Unknown
ntdll.dll!__RtlUserThreadStart@8()  Unknown

局部变量(无法确定$exception对象是否正确):

+       $exception  {"Exception of type 'System.ExecutionEngineException' was thrown."} System.ExecutionEngineException
    this    Cannot obtain value of the local variable or argument because it is not available at this instruction pointer,
            possibly because it has been optimized away.    System.Reflection.Emit.DynamicResolver.DestroyScout
    Stack objects   No CLR objects were found in the stack memory range of the current frame.   

“DynamicILGenerator.cs”源代码,提到了DestroyScout类(在注释中提到了第798行):

    private class DestroyScout
    {
        internal RuntimeMethodHandleInternal m_methodHandle;

        [System.Security.SecuritySafeCritical]  // auto-generated
        ~DestroyScout()
        {
            if (m_methodHandle.IsNullHandle())
                return;

            // It is not safe to destroy the method if the managed resolver is alive.
            if (RuntimeMethodHandle.GetResolver(m_methodHandle) != null)
            {
                if (!Environment.HasShutdownStarted &&
                    !AppDomain.CurrentDomain.IsFinalizingForUnload())
                {
                    // Somebody might have been holding a reference on us via weak handle.
                    // We will keep trying. It will be hopefully released eventually.
                    GC.ReRegisterForFinalize(this);
                }
                return;
            }

            RuntimeMethodHandle.Destroy(m_methodHandle); // <===== line 798
        }
    }

观察窗口(m_methodHandle):

m_methodHandle  Cannot obtain value of the local variable or argument because 
                it is not available at this instruction pointer,
                possibly because it has been optimized away.
                System.RuntimeMethodHandleInternal

常规转储模块信息:

Dump Summary
------------
Dump File:  Application_Server2.0.exe.5296.dmp : C:\Temp_Folder\Application_Server2.0.exe.5296.dmp
Last Write Time:    14/06/2022 19:08:30
Process Name:   Application_Server2.0.exe : C:\Runtime\Application_Server2.0.exe
Process Architecture:   x86
Exception Code: 0xC0000005
Exception Information:  The thread tried to read from or write to a virtual address
                        for which it does not have the appropriate access.
Heap Information:   Present

System Information
------------------
OS Version: 10.0.14393
CLR Version(s): 4.7.3920.0

Modules
-------
Module Name                                           Module Path   Module Version
-----------                                           -----------   --------------
...
clr.dll     C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll       4.7.3920.0
...

注意: 这个dump文件是在一个Windows-Server 2016计算机上生成的,我正在我的Windows 10环境中对这个dump文件进行调查(不要被dump摘要中的OS版本所误导)!

编辑

Destroyscout试图摧毁什么?这可能非常有趣。


重新看一遍,我认为这与多线程无关,但显然似乎是一个bug。在析构函数中重新注册this以进行finalize可能会导致GC再次调用析构函数..这是什么?这是C#中销毁对象的奇怪逻辑,等待所有弱引用放弃其句柄..我不知道,抱歉评论了。 - Michael Schönbauer
你尝试过使用更新版本的.NET Framework吗?ExecutionEngineException对我来说表明可能是某种损坏的内存,这种情况只会在最终化时显现。你是否正在使用unsafe或PInvoke? - Charlieface
2
@MichaelSchönbauer:不要因为尝试而感到抱歉 :-) - Dominique
@Charlieface:我刚刚检查了所有的源代码。我发现了三个unsafe实例,但它们都在这里没有使用的一段代码中。PInvoke从未被使用过。你提到升级我的.NET框架。假设我这样做了,我怎么知道哪个.NET框架可以解决这个问题? - Dominique
这里没有使用 unsafe 并不意味着它没有 bug,它可能会覆盖不应该被覆盖的内存,但是这种影响只在这里出现。我不知道这个 bug(如果它是一个 bug),也找不到任何关于它的文档,只是建议您尝试升级框架。 - Charlieface
显示剩余22条评论
1个回答

1
我不知道是什么导致了这个崩溃,但我可以告诉你 DestroyScout 是做什么的。
它与创建动态方法有关。类 DynamicResolver 需要清理相关的非托管内存,这些内存不受 GC 跟踪。但是,只有在绝对没有对该方法的引用后,它才能被清理。
然而,因为恶意(或极端奇怪的)代码可能使用长时间存在的 WeakReference,它可以在 GC 后复活对动态方法的引用,即使其终结器已经运行。因此,DestroyScout 出现,并带有奇怪的 GC.ReRegisterForFinalize 代码,以确保它是最后一个被销毁的引用。
这在源代码的注释中有解释 链接
// We can destroy the unmanaged part of dynamic method only after the managed part is definitely gone and thus
// nobody can call the dynamic method anymore. A call to finalizer alone does not guarantee that the managed 
// part is gone. A malicious code can keep a reference to DynamicMethod in long weak reference that survives finalization,
// or we can be running during shutdown where everything is finalized.
//
// The unmanaged resolver keeps a reference to the managed resolver in long weak handle. If the long weak handle 
// is null, we can be sure that the managed part of the dynamic method is definitely gone and that it is safe to 
// destroy the unmanaged part. (Note that the managed finalizer has to be on the same object that the long weak handle 
// points to in order for this to work.) Unfortunately, we can not perform the above check when out finalizer 
// is called - the long weak handle won't be cleared yet. Instead, we create a helper scout object that will attempt 
// to do the destruction after next GC.

关于您的崩溃,这是在内部代码中发生的,并导致了ExecutionEngineException。这很可能是由于内存损坏造成的,当内存被以不应该的方式使用时会发生这种情况。
内存损坏可能出现的原因有很多。按可能性排序如下:
  • 不正确地使用PInvoke到本机Win32函数(DllImport和相关的marshalling)。
  • 不正确地使用unsafe(包括库类,如UnsafeBuffer,它们执行相同的操作)。
  • 在Runtime不希望被多线程使用的对象上发生多线程竞争条件。这可能导致读取错误和内存屏障违规等问题。
  • .NET本身的一个错误。这可能是最容易排除的:只需升级到最新版本即可。
考虑将崩溃报告提交给Microsoft进行调查。

来自作者的编辑:
为了向Microsoft提交崩溃报告,可以使用以下URL:https://www.microsoft.com/en-us/unifiedsupport。请注意,这是一个付费服务,您可能需要将整个源代码交付给Microsoft,以便获得完整的崩溃转储分析。


我特别喜欢你提出的将崩溃转储发送给微软的想法。也许他们会看到我没发现的问题。 - Dominique

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接