一:背景

1. 讲故事

最近在分析一个崩溃dump时,发现祸首和AssemblyLoadContext有关,说实话这东西我也比较陌生,后来查了下大模型,它主要奔着替代 .NetFrameWork 时代的 AppDomain 的,都是用来做晚期加卸载,实现对宿主程序的可插拔,AppDomain.Create 是在AppDomain级别上,后者是在 Assembly 级别上。

二:Assembly 插拔分析

1. 一个简单的案例

简单来说这东西可以实现 Assembly 的可插拔,这个小案例有三个基本元素。

  1. IPlugin 组件接口

这块比较简单,新建一个类库,里面主要就是组件需要实现的接口。


namespace MyClassLibrary.Interfaces
{public interface IPlugin{string Name { get; }string Version { get; }void Execute();string GetResult();}
}
  1. SamplePlugin 组件实现

新建一个组件,完成这些接口方法的实现。

public class SamplePlugin : IPlugin{public string Name => "Sample Plugin";public string Version => "1.0.0";public void Execute(){Console.WriteLine("SamplePlugin is executing...");}public string GetResult(){return "Hello from SamplePlugin!";}}
  1. 自定义的 CustomAssemblyLoadContext 上下文

最后就是在调用处自定义下 AssemblyLoadContext 以及简单调用,参考代码如下:


namespace Example_1_6
{internal class Program{static void Main(string[] args){Console.WriteLine("=== 插件系统启动 ===");// 设置插件目录string pluginsPath = @"D:\sources\woodpecker\Test\MyClassLibrary\bin\Debug\net8.0\";Console.WriteLine($"插件路径: {pluginsPath}");var dllFile = Directory.GetFiles(pluginsPath, "MyClassLibrary.dll").FirstOrDefault();var _loadContext = new CustomAssemblyLoadContext("MyPluginContext", pluginsPath);var assembly = _loadContext.LoadAssembly(dllFile);var type = assembly.GetType("MyClassLibrary.SamplePlugin");IPlugin plugin = (IPlugin)Activator.CreateInstance(type);Console.WriteLine($"- {plugin.Name} v{plugin.Version}");Console.WriteLine($"\n执行插件: {plugin.Name} v{plugin.Version}");plugin.Execute();string result = plugin.GetResult();Console.WriteLine($"插件返回: {result}");Console.ReadKey();}}public class CustomAssemblyLoadContext : AssemblyLoadContext{private readonly string _dependenciesPath;public CustomAssemblyLoadContext(string name, string dependenciesPath): base(name, isCollectible: true){_dependenciesPath = dependenciesPath;}public Assembly LoadAssembly(string assemblyPath){return LoadFromAssemblyPath(assemblyPath);}public new void Unload(){base.Unload();}}
}

将代码运行起来,可以看到插件代码得到执行。

2. 组件已经插上了吗

plugin中的方法都已经执行了,那 MyClassLibrary.dll 自然就插上去了,接下来如何验证呢?可以使用 windbg 的 !dumpdomain 命令即可。


0:015> !dumpdomain
--------------------------------------
System Domain:      00007ff8e9d4b150
LowFrequencyHeap:   00007FF8E9D4B628
HighFrequencyHeap:  00007FF8E9D4B6B8
StubHeap:           00007FF8E9D4B748
Stage:              OPEN
Name:               None
--------------------------------------
Domain 1:           00000211d617dc80
LowFrequencyHeap:   00007FF8E9D4B628
HighFrequencyHeap:  00007FF8E9D4B6B8
StubHeap:           00007FF8E9D4B748
Stage:              OPEN
Name:               clrhost
Assembly:           00000211d613e560 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.16\System.Private.CoreLib.dll]
ClassLoader:        00000211D613E5F0Module00007ff889d54000    C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.16\System.Private.CoreLib.dll...Assembly:           000002118052b0d0 [D:\sources\woodpecker\Test\MyClassLibrary\bin\Debug\net8.0\MyClassLibrary.dll]
ClassLoader:        000002118052B160Module00007ff88a11c060    D:\sources\woodpecker\Test\MyClassLibrary\bin\Debug\net8.0\MyClassLibrary.dll

从卦中可以清晰的看到 MyClassLibrary.dll 已经成功的送入。

3. 组件如何卸载掉

能不能卸载掉,其实取决于你在 new AssemblyLoadContext() 时塞入的 isCollectible 字段决定的,如果为true就是一个可卸载的程序集,参考代码如下:

public CustomAssemblyLoadContext(string name, string dependenciesPath): base(name, isCollectible: true){_dependenciesPath = dependenciesPath;}

其次要知道的是卸载程序集是一个异步操作,不要以为调用了 UnLoad() 就会立即卸载,它只是起到了一个标记删除的作用,只有程序集中的实例无引用根了,即垃圾对象的时候,再后续由 GC 来实现卸载。

这一块我们可以写段代码来验证下,我故意将逻辑包装到 DoWork() 方法中,然后处理完之后再次触发GC,修改后的代码如下:

internal class Program{static void Main(string[] args){DoWork();GC.Collect();GC.WaitForPendingFinalizers();Console.WriteLine("GC已触发,请再次观察 Assembly 是否被卸载...");Console.ReadLine();}static void DoWork(){Console.WriteLine("=== 插件系统启动 ===");// 设置插件目录string pluginsPath = @"D:\sources\woodpecker\Test\MyClassLibrary\bin\Debug\net8.0\";Console.WriteLine($"插件路径: {pluginsPath}");var dllFile = Directory.GetFiles(pluginsPath, "MyClassLibrary.dll").FirstOrDefault();var _loadContext = new CustomAssemblyLoadContext("MyPluginContext", pluginsPath);var assembly = _loadContext.LoadAssembly(dllFile);var type = assembly.GetType("MyClassLibrary.SamplePlugin");IPlugin plugin = (IPlugin)Activator.CreateInstance(type);Console.WriteLine($"- {plugin.Name} v{plugin.Version}");Console.WriteLine($"\n执行插件: {plugin.Name} v{plugin.Version}");plugin.Execute();string result = plugin.GetResult();Console.WriteLine($"插件返回: {result}");_loadContext.Unload();Console.WriteLine("程序集已标记为卸载... 请观察 Assembly 是否被卸载...");Console.ReadKey();}}

从卦中可以看到确实已经不再有 MyClassLibrary.dll 程序集了,但托管堆上还有 CustomAssemblyLoadContext 死对象,当后续GC触发时再回收,用windbg验证如下:


0:014> !dumpobj /d 238e9c464c8
Name:        Example_1_6.CustomAssemblyLoadContext
MethodTable: 00007ff88a06f098
EEClass:     00007ff88a079008
Tracked Type: false
Size:        88(0x58) bytes
File:        D:\sources\woodpecker\Test\Example_1_6\bin\Debug\net8.0\Example_1_6.dll
Fields:MT    Field   Offset                 Type VT     Attr            Value Name
00007ff889e870a0  4001116       30        System.IntPtr  1 instance 000002388042A8F0 _nativeAssemblyLoadContext
00007ff889dd5fa8  4001117        8        System.Object  0 instance 00000238e9c46520 _unloadLock
0000000000000000  4001118       10                       0 instance 0000000000000000 _resolvingUnmanagedDll
0000000000000000  4001119       18                       0 instance 0000000000000000 _resolving
0000000000000000  400111a       20                       0 instance 0000000000000000 _unloading
00007ff889e8ec08  400111b       28        System.String  0 instance 0000023880006a30 _name
00007ff889e3a5f0  400111c       38         System.Int64  1 instance                0 _id
00007ff889f2f108  400111d       40         System.Int32  1 instance                1 _state
00007ff889ddd070  400111e       44       System.Boolean  1 instance                1 _isCollectible
00007ff88a0ed120  4001114      a00 ...Private.CoreLib]]  0   static 00000238e9c46550 s_allContexts
00007ff889e3a5f0  4001115      bc0         System.Int64  1   static                1 s_nextId
0000000000000000  400111f      a08 ...yLoadEventHandler  0   static 0000000000000000 AssemblyLoad
0000000000000000  4001120      a10 ...solveEventHandler  0   static 0000000000000000 TypeResolve
0000000000000000  4001121      a18 ...solveEventHandler  0   static 0000000000000000 ResourceResolve
0000000000000000  4001122      a20 ...solveEventHandler  0   static 0000000000000000 AssemblyResolve
0000000000000000  4001123      a28                       0   static 0000000000000000 s_asyncLocalCurrent
00007ff889e8ec08  4000001       48        System.String  0 instance 0000023880006938 _dependenciesPath0:014> !gcroot 238e9c464c8
Caching GC roots, this may take a while.
Subsequent runs of this command will be faster.Found 0 unique roots.

三:总结

有时候感叹 知识无涯人有涯,在 dump分析中不断的螺旋式提升,理论指导实践,实践反哺理论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/94817.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/94817.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/94817.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java中使用Spring Boot+Ollama实现本地AI的MCP接入

目录结构完善spring bootpom.xml添加依赖application.ymlMCP 工具配置 mcp-servers.json配置类编写API在我的上一篇文章搭建好本地的聊天机器人后,准备接入MCP进一步增强AI的能力,以实现类似手机AI的功能 参考的是第二篇文章链接其内容比较精炼&#x…

C#正则表达式与用法

🌟 C# 常用正则表达式与用法C# 使用正则需要引用命名空间:using System.Text.RegularExpressions; 常用方法:Regex.IsMatch(input, pattern) → 返回 bool,用于验证Regex.Match(input, pattern) → 返回 Match 对象,可…

从0开始学习Java+AI知识点总结-27.web实战(Maven高级)

一、分模块设计与开发:让项目结构更清晰1.1 为什么需要分模块?单模块开发的痛点在小型项目中,单模块(所有代码放在一个工程)或许能满足需求,但项目规模扩大后会出现两大核心问题:维护成本高&…

Ferris Wheel (贪心 | 双指针)

题目:思路:本题注意题目的条件即可,题意说一个摩天轮可以坐一个人或者两个人,那么显然我们就可以贪心一下具体的,我们可以让最小的去匹配最大的,如果此时大于 x,那么显然我们根本无法使得 最大的…

课程视频怎么加密?在线教育机构常用的6个课程加密方法

知识付费时代,课程视频是教育机构的核心资产。但是不难发现,课程视频的安全却得不到保障。各大购物平台搜索课程名称,便出现了许多盗版课程。如何有效防止课程被翻录和二次传播,成为急需解决的关键问题。今天这期分享点干货&#…

SOME/IP-SD中”服务器服务组播端点”、“客户端服务组播端点”与“IPv4组播选项的区分

<摘要> AUTOSIP-SD协议中组播端点&#xff08;Multicast Endpoint&#xff09;在不同上下文中的角色与表述差异。准确理解“服务器服务组播端点”、“客户端服务组播端点”与“IPv4组播选项”中配置的端点之间的关系&#xff0c;是正确实现组播事件分发机制的关键。这涉及…

计算机是如何运行的

目录 一&#xff0c;计算机是如何组成的 1.1&#xff0c;CPU中央处理单元 1.1.1&#xff0c;CPU的构成和属性 1.1.2&#xff0c;如何判断cpu的好坏 1.1.3&#xff0c;指令 1.1.4&#xff0c;CPU的缓存 1.2&#xff0c;操作系统 1.2.1&#xff0c;进程 1.2.2&#xff0…

JavaScript性能优化:实战技巧与高效策略

JavaScript性能优化实战技术文章大纲性能优化的重要性解释为什么性能优化对用户体验和业务指标至关重要列举常见性能问题的影响&#xff08;如跳出率、转化率下降&#xff09;代码层面的优化减少全局变量使用&#xff0c;避免内存泄漏使用事件委托减少事件监听器的数量避免频繁…

解决.env.production 写死 IP 的问题:Vue + config.json 运行时加载方案

背景&#xff1a;前端常用 .env.production 在构建时写死 API 地址 场景&#xff1a;运维部署时经常不知道目标主机 IP/域名 问题&#xff1a;每次 IP 变动都要重新编译 → 增加运维成本 引出需求&#xff1a;只修改 IP 就能完成部署&#xff0c;不需要重新打包 目录一、解决方…

如何从三星手机转移到另一部三星手机

三星Galaxy S系列因其出色的设计、令人惊叹的显示屏、惊艳的摄像头、更好的扬声器以及创新的指纹传感器而受到大多数用户的欢迎&#xff0c;获得了良好的声誉。让用户感到满意的是&#xff0c;三星Galaxy S10拥有更美观的设计、令人惊叹的显示屏、令人惊叹的摄像头、更好的扬声…

聚焦建筑能源革新!安科瑞 “光储直柔” 方案护航碳中和目标实现

1、背景在 “双碳” 目标引领下&#xff0c;能源结构转型与建筑能效提升成为重要课题。清华大学江亿院士提出的 “光储直柔” 新型配电系统&#xff0c;为建筑领域绿色发展提供了创新方向。光储直柔得到了业界广泛认同和积极响应&#xff0c;国家、各部委、地区陆续出台相关政策…

Shell 中 ()、(())、[]、{} 的用法详解

文章目录Shell 中 ()、(())、[]、{} 的用法详解一、先明确&#xff1a;四类符号的核心功能定位二、逐个拆解&#xff1a;用法、示例与避坑点1. ()&#xff1a;子 Shell 执行&#xff0c;隔离环境核心用法1&#xff1a;子 Shell 执行命令&#xff0c;隔离变量核心用法2&#xff…

开发避坑指南(41):Vue3 提示框proxy.$modal.msgSuccess()提示文本换行解决方案

需求 由于接口返回的提示信息过长&#xff0c;接口已经在返回提示中加入换行标签了&#xff0c;但是使用proxy.modal.msgSuccess(res.msg)提示没有换行&#xff0c;那么Vue3中proxy.modal.msgSuccess(res.msg)提示没有换行&#xff0c;那么Vue3 中 proxy.modal.msgSuccess(res.…

[Sync_ai_vid] 唇形同步推理流程 | Whisper架构

链接&#xff1a;https://github.com/bytedance/LatentSync/blob/main/docs/syncnet_arch.md docs&#xff1a;LatentSync LatentSync是一个端到端唇语同步项目&#xff0c;能够生成语音与唇形完美匹配的逼真视频。 该项目通过使用*音频条件化3D U-Net*&#xff08;一种生成式…

uniapp中 ios端 scroll-view 组件内部子元素z-index失效问题

发现子组件中的弹窗在ios手机上会被限制在scroll-view里面&#xff0c;安卓手机上不受限制&#xff0c;网上找了好久原因 scroll-view组件内部设置了 -webkit-overflow-scrolling: touch 样式&#xff0c;导致z-index失效&#xff08;safari 3D变换会忽略z-index的层级问题&…

PyTorch图像预处理完全指南:从基础操作到GPU加速实战

引言 图像预处理是模型性能的"隐形基石"&#xff0c;在计算机视觉任务中直接决定模型能否提取有效特征。科学的预处理流程能让基础模型性能提升15%以上&#xff0c;而GPU加速预处理可使数据准备阶段耗时降低60%以上。本文将聚焦PyTorch预处理核心技术&#xff0c;从基…

【前端教程】 CSS浮动布局解析与优化:从基础实现到工程化改进

浮动(float)是CSS中实现页面布局的经典技术,虽然现代布局更多使用Flexbox和Grid,但理解浮动的工作原理仍是前端开发者的基础素养。本文以一个三栏浮动布局的代码为例,从布局原理解析、潜在问题诊断、工程化优化三个维度,带你深入理解浮动布局的精髓与优化思路。 一、原代…

DVWA靶场通关笔记-暴力破解(Impossible级别)

目录 一、查看源码 二、功能分析 三、SQL注入分析 1、使用PDO预处理语句和参数绑定 2、mysqli_real_escape_string转义 3、stripslashes去除反斜杠 四、暴力破解分析 1、token防止暴力破解机制 2、登录失败随机延迟机制 3、登陆失败报错信息相同 4、登陆失败的账户…

IAR工程如何生成compile_commands.json文件(能生成但是clangd不能生成“.cache文件”)

最近一直在使用vscodeclangd的方式编写代码&#xff0c;感觉使用clangd查找函数调用、函数声明、类型定义等等都比使用vscode自带的c/c插件好用太多了。现在我有一个功能是IAR版本的&#xff0c;那么有没有办法生成clangd使用的compile_commands.json文件呢&#xff1f;答案是&…

QT5.14.2、CMake 扩展openCV

一、准备工具Qt5.14.2c11cmake3.24.0opencv3.4.16二、使用cmake可扩展opencv 首先解压cmake、opencv 两个下载的压缩包&#xff0c;如下&#xff1a;运行cmake-gui.exe打开后有弹窗选择&#xff0c;然后进入QT的安装路径下找 mingw73_64文件下的 C和C的执行文件这个截图是我扩展…