调试 .NET 5 中的程序集可卸载性

发布于 2025-01-14 15:04:29 字数 4667 浏览 2 评论 0原文

我有一个需要动态卸载的程序集。我已按照 https://learn.microsoft.com/ 中的步骤操作en-us/dotnet/standard/ assembly/unloadability 用于我的加载程序的开发以及使用 WinDbg 的调试。我有“内存泄漏”,因为我的卸载命令不起作用。我的问题是如何使卸载工作正常进行?

最初,我注意到使用卸载命令后内存并没有减少,因此我创建了一个指向程序集的弱引用字典。每次执行操作时,我都会动态加载和卸载程序集,这会在内存中创建程序集的多个副本。我可以使用 Dictionary查看组件是否已按照上述指南中的方式卸载。无论我等多久,或者垃圾被收集多少次,它们都不会被释放。这对我来说有一些出色的参考资料,但我一生都找不到它。

我没有犯他们在示例代码中犯的任何错误 - 我保留的唯一参考是弱引用。一旦我完成了程序集和程序集加载器,我什至将它们分配为空,尽管它们无论如何都会立即超出范围。我没有任何回调,或者据我所知没有跨界类型,因为我跨界进行的唯一通信是通过文件。大会正在顺利结束,没有出现任何问题。所以我启动了 WinDbg 并尝试按照他们的指示进行操作。一切都很顺利,直到我尝试获取 AssemblyLoader 的 GC 根:

0:000> !gcroot -all 0x00007ff7b943f9c8
Found 0 roots.

当我单步执行应用程序中的线程时,我没有看到任何可疑的东西,尽管老实说,我没有足够的直觉这个领域意义重大。输出如下。

0:021> ~*e !clrstack
OS Thread Id: 0x3510 (0)
        Child SP               IP Call Site
000000FEA5D7E6E0 00007ff8813111c4 [InlinedCallFrame: 000000fea5d7e6e0] Interop+User32.WaitMessage()
000000FEA5D7E6E0 00007ff7b8f38a15 [InlinedCallFrame: 000000fea5d7e6e0] Interop+User32.WaitMessage()
000000FEA5D7E6B0 00007ff7b8f38a15 ILStubClass.IL_STUB_PInvoke()
000000FEA5D7E770 00007ff813db015e System.Windows.Forms.Application+ComponentManager.Interop.Mso.IMsoComponentManager.FPushMessageLoop(UIntPtr, msoloop, Void*) [/_/src/System.Windows.Forms/src/System/Windows/Forms/Application.ComponentManager.cs @ 398]
000000FEA5D7E860 00007ff813db28f5 System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(msoloop, System.Windows.Forms.ApplicationContext) [/_/src/System.Windows.Forms/src/System/Windows/Forms/Application.ThreadContext.cs @ 1111]
000000FEA5D7E8F0 00007ff813db2568 System.Windows.Forms.Application+ThreadContext.RunMessageLoop(msoloop, System.Windows.Forms.ApplicationContext) [/_/src/System.Windows.Forms/src/System/Windows/Forms/Application.ThreadContext.cs @ 975]
000000FEA5D7E950 00007ff813a89c86 System.Windows.Forms.Application.Run(System.Windows.Forms.Form) [/_/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs @ 1198]
000000FEA5D7E990 00007ff7b8bd9f4a ConfigTool.Program.Main() [C:\Users\isherman\Documents\Projects\twmp\FormsConfigTool\Program.cs @ 42]
<snipping out unmanaged threads>
OS Thread Id: 0xa2dc (6)
        Child SP               IP Call Site
000000FEA667FC30 00007ff88338d8c4 [DebuggerU2MCatchHandlerFrame: 000000fea667fc30] 
OS Thread Id: 0xc5fc (7)
        Child SP               IP Call Site
<snip>
OS Thread Id: 0x900c (12)
        Child SP               IP Call Site
<snip>
OS Thread Id: 0x9b88 (16)
        Child SP               IP Call Site
<snip>
OS Thread Id: 0xa358 (18)
        Child SP               IP Call Site
OS Thread Id: 0x9a8c (19)
        Child SP               IP Call Site
<snip>

这是我用来执行和卸载程序集加载上下文的代码:

        private static Dictionary<int, WeakReference> references;
        private static int refCount =0;
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static int ExecuteAndUnload(string assemblyPath, 
                           XmlDocument doc, out WeakReference alcWeakRef)
        {
            references ??= new Dictionary<int, WeakReference>();
            assemblyPath = Path.GetFullPath(assemblyPath);
            var alc = new SequesteredTagBrowser(assemblyPath);
            Assembly a = alc.LoadFromAssemblyPath(assemblyPath);
            Debug.Assert(a.EntryPoint !=null);
            alcWeakRef = new WeakReference(alc, true);
            references.Add(refCount++, alcWeakRef);
            //I pass some xml in as an argument to the Program.Main function
            var args = new object[] {new[] {doc.OuterXml}};
            a.EntryPoint.Invoke(null, args);
            a = null;

            alc.Unload();
            alc = null;
            GC.Collect();

            return 0;
        }

这是 SequesteredTagBrowser 类的代码:

    public class SequesteredTagBrowser:AssemblyLoadContext
    {
        private AssemblyDependencyResolver _resolver;

        public SequesteredTagBrowser(string mainAssemblyToLoadPath) : base(isCollectible: true)
        {
            
            _resolver = new AssemblyDependencyResolver(mainAssemblyToLoadPath);
        }

        protected override Assembly Load(AssemblyName name)
        {
            string assemblyPath = _resolver.ResolveAssemblyToPath(name);
            if (assemblyPath != null)
            {
                return LoadFromAssemblyPath(assemblyPath);
            }

            return null;
        }

    }

I have an assembly which I need to unload dynamically. I have followed the steps in https://learn.microsoft.com/en-us/dotnet/standard/assembly/unloadability for both the development of my loader as well as the debugging using WinDbg. I have a "memory leak" because my unload command is not working. My question is how do I make my unload work?

Initially, I noticed that memory was not going down after using the unload command, so I created a dictionary of weak references which point at the assemblies. I dynamically load and unload the assembly every time I perform an operation, which is creating multiple copies of the assembly in memory. I can use the Dictionary<int, WeakReference> to see if the assemblies have been unloaded or not as they do in the guide above. They aren't getting released, no matter how long I wait, or how many times the garbage gets collected. This says to me that there's some outstanding reference, but for the life of me I can't find it.

I'm not making either of the mistakes they made in the example code- the only reference I hold onto is the weak reference. I even assign the assembly and the assembly loader to null once I'm done with them, though they go out of scope immediately anyway. I don't have any callbacks or, to the best of my knowledge Types across the border because the only communication I do across the boundary is via a file. The assembly is ending appropriately without issues. So I fired up WinDbg and tried to follow their instructions. Everything goes fine until I try to get the GC roots of the AssemblyLoader:

0:000> !gcroot -all 0x00007ff7b943f9c8
Found 0 roots.

And when I step through the threads in my application, I don't see anything that jumps out to me as suspicious, though honestly I don't have enough intuition in this area that that means much. The output is below.

0:021> ~*e !clrstack
OS Thread Id: 0x3510 (0)
        Child SP               IP Call Site
000000FEA5D7E6E0 00007ff8813111c4 [InlinedCallFrame: 000000fea5d7e6e0] Interop+User32.WaitMessage()
000000FEA5D7E6E0 00007ff7b8f38a15 [InlinedCallFrame: 000000fea5d7e6e0] Interop+User32.WaitMessage()
000000FEA5D7E6B0 00007ff7b8f38a15 ILStubClass.IL_STUB_PInvoke()
000000FEA5D7E770 00007ff813db015e System.Windows.Forms.Application+ComponentManager.Interop.Mso.IMsoComponentManager.FPushMessageLoop(UIntPtr, msoloop, Void*) [/_/src/System.Windows.Forms/src/System/Windows/Forms/Application.ComponentManager.cs @ 398]
000000FEA5D7E860 00007ff813db28f5 System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(msoloop, System.Windows.Forms.ApplicationContext) [/_/src/System.Windows.Forms/src/System/Windows/Forms/Application.ThreadContext.cs @ 1111]
000000FEA5D7E8F0 00007ff813db2568 System.Windows.Forms.Application+ThreadContext.RunMessageLoop(msoloop, System.Windows.Forms.ApplicationContext) [/_/src/System.Windows.Forms/src/System/Windows/Forms/Application.ThreadContext.cs @ 975]
000000FEA5D7E950 00007ff813a89c86 System.Windows.Forms.Application.Run(System.Windows.Forms.Form) [/_/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs @ 1198]
000000FEA5D7E990 00007ff7b8bd9f4a ConfigTool.Program.Main() [C:\Users\isherman\Documents\Projects\twmp\FormsConfigTool\Program.cs @ 42]
<snipping out unmanaged threads>
OS Thread Id: 0xa2dc (6)
        Child SP               IP Call Site
000000FEA667FC30 00007ff88338d8c4 [DebuggerU2MCatchHandlerFrame: 000000fea667fc30] 
OS Thread Id: 0xc5fc (7)
        Child SP               IP Call Site
<snip>
OS Thread Id: 0x900c (12)
        Child SP               IP Call Site
<snip>
OS Thread Id: 0x9b88 (16)
        Child SP               IP Call Site
<snip>
OS Thread Id: 0xa358 (18)
        Child SP               IP Call Site
OS Thread Id: 0x9a8c (19)
        Child SP               IP Call Site
<snip>

And here's the code that I use to execute and unload the Assembly Load Context:

        private static Dictionary<int, WeakReference> references;
        private static int refCount =0;
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static int ExecuteAndUnload(string assemblyPath, 
                           XmlDocument doc, out WeakReference alcWeakRef)
        {
            references ??= new Dictionary<int, WeakReference>();
            assemblyPath = Path.GetFullPath(assemblyPath);
            var alc = new SequesteredTagBrowser(assemblyPath);
            Assembly a = alc.LoadFromAssemblyPath(assemblyPath);
            Debug.Assert(a.EntryPoint !=null);
            alcWeakRef = new WeakReference(alc, true);
            references.Add(refCount++, alcWeakRef);
            //I pass some xml in as an argument to the Program.Main function
            var args = new object[] {new[] {doc.OuterXml}};
            a.EntryPoint.Invoke(null, args);
            a = null;

            alc.Unload();
            alc = null;
            GC.Collect();

            return 0;
        }

And here's the code for the SequesteredTagBrowser class:

    public class SequesteredTagBrowser:AssemblyLoadContext
    {
        private AssemblyDependencyResolver _resolver;

        public SequesteredTagBrowser(string mainAssemblyToLoadPath) : base(isCollectible: true)
        {
            
            _resolver = new AssemblyDependencyResolver(mainAssemblyToLoadPath);
        }

        protected override Assembly Load(AssemblyName name)
        {
            string assemblyPath = _resolver.ResolveAssemblyToPath(name);
            if (assemblyPath != null)
            {
                return LoadFromAssemblyPath(assemblyPath);
            }

            return null;
        }

    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文