全解析:hostpolicy 的探测顺序与版本裁决机制)
.NET 程序集冲突解析Assembly Conflict Resolution全解析hostpolicy 的探测顺序与版本裁决机制【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime导读本文基于 dotnet/runtime 仓库中的设计文档 assembly-conflict-resolution.md深入剖析 .NETCoreCLR应用启动时如何解决同名程序集在多个物理位置同时存在的冲突问题。文章覆盖层layer与 deps.json 的处理顺序、探测路径probing path的完整优先级、以及基于 Assembly Version / File Version 的裁决算法并结合 hostpolicy 的真实 C 实现逐行印证。读完本文你将能够准确预判任意 .NET 应用框架依赖型或自包含型启动时哪个程序集最终胜出并能在排查程序集版本被意外替换的疑难问题时快速定位根因。背景为什么需要程序集冲突解析一个 .NET 应用在运行时可能通过多种渠道接触到同名但不同版本的程序集应用目录app 目录应用自身发布或拷贝的程序集共享框架目录Microsoft.NETCore.App以及 2.1 引入的中间层框架如Microsoft.AspNetCore.App各自目录下的程序集共享存储Shared StoreDOTNET_SHARED_STORE或 dotnet 安装目录store下按arch/tfm组织的包服务目录Servicing用于服务更新servicing的程序集额外探测路径--additionalprobingpath参数与runtimeconfig.dev.json中的additionalProbingPaths。当同一个程序集名在这些位置出现多次时主机host必须裁决加载哪一个。这个裁决逻辑位于Microsoft.NETCore.App目录下的hostpolicy.dll中其源码在 src/native/corehost/hostpolicy核心实现为 deps_resolver.cpp。启动时程序集解析的整体流程在 CoreCLR 应用启动时主机依次完成以下工作构建探测位置列表详见下文探测顺序逐个解析各层的.deps.json文件对 deps.json 中的每个程序集条目按探测位置顺序找到其物理文件路径将所有解析出的完整路径列表即 TPA 列表Trusted Platform Assemblies传给 CLRCLR 在程序集被访问时依据该列表加载程序集。其中第 24 步的层概念是理解冲突解析的关键。层Layer的概念一个运行中的应用由若干层叠加而成最上层是应用本身app中间是可选的、一个或多个框架层自 2.1 起支持2.0 及更早只有 app 与Microsoft.NETCore.App两层最底层是Microsoft.NETCore.App。每一层都有自己独立的.deps.json层与层之间可能存在同名程序集。hostpolicy 需要决定同一程序集名最终映射到哪一个物理路径。探测顺序Probe Ordering探测位置的完整优先级如下数字越小优先级越高Servicing Location服务位置应用目录App directory框架目录Framework directory(s)从高层框架到低层框架共享存储Shared Store额外位置runtimeconfig.dev.json中指定的位置或通过--additionalprobingpath参数传入的位置更细粒度的探测路径规则含 Windows/Linux/macOS 差异、共享存储的环境变量展开、|arch|/|tfm|占位符替换等详见同目录设计文档 host-probing.md。其要点包括Servicing仅当 deps.json 中该库记录标记serviceable: true时才会被探测基路径在 Windows x64 为%ProgramFiles(x86)%\coreservicingWindows x86 为%ProgramFiles%\coreservicingLinux/macOS 为$CORE_SERVICING共享存储DOTNET_SHARED_STORE可包含多个路径每个路径追加|arch|/|tfm|若通过dotnet.exe执行还会使用dotnet.exe 目录/store/|arch|/|tfm|额外路径--additionalprobingpath与additionalProbingPaths中的路径同样支持|arch|/|tfm|占位符替换。关键语义一旦某个 deps.json 条目在某探测位置命中就不再对该条目继续探测后续位置——即先到先得。在 hostpolicy 源码中探测配置的构建位于 deps_resolver.cpp 的setup_probe_config依次压入 servicing NI 探测、servicing 普通探测、已发布 deps 目录、从高到低的框架目录、共享存储、以及额外路径顺序与设计文档完全一致。而probe_deps_entrydeps_resolver.cpp则实现了逐配置探测非 serviceable 资产跳过 servicing、非 runtime 资产跳过 NI 探测、条目所属层级高于框架时跳过该框架探测等。deps.json 的处理顺序Deps.json Ordering各层 deps.json 的处理顺序为应用程序的.deps.json可选--additional-deps参数指定的 deps 文件框架的 deps.json从高层到低层这一顺序与探测顺序共同决定了冲突的裁决结果。冲突裁决算法Algorithm设计文档给出的完整算法如下确定探测路径遍历应用.deps.json中的每个条目若该资产名已存在已解析条目比较新旧条目的 Assembly Version 与 File Version新条目版本等于或更低→ 跳过保留旧的新条目版本更高→ 移除旧条目转入下面的else分支重新探测否则新资产或被更高版本替换为该资产实际探测文件逐一遍历探测路径除层级更高的框架外若探测路径是框架检查其.deps.json是否包含精确匹配的包包名版本。命中则使用该位置并结束探测若探测路径不是框架直接使用该位置并结束探测读取--additional-deps的 deps 文件重复步骤 3-7从高到低读取每个框架的 deps.json重复步骤 3-7将程序集集合及其路径传给 CLR。应用通常胜出的原因由于应用的.deps.json先于框架的.deps.json处理且应用目录先于框架目录被探测应用通常占据先手优势。同时应用引用的 OOBOut-Of-Band包框架尤其Microsoft.NETCore.App有自己的元包且不引用 OOB 包通常并不引用因此第 6 步中框架探测路径对应用 deps.json 中的包/程序集条目不会命中应用胜出成为常态。为什么只探测同级或更低级框架算法第 5 步限定除层级更高的框架外的原因有二一个给定的框架永远不应该依赖更高层级的框架其 deps 资产应在其自身层级或更低层级找到该限定允许给定框架在自身层级找到资产并替换掉高层级框架的资产当自身版本更新时。版本比较的元数据前提为了比较 Assembly Version 与 File Version每个 deps.json 文件需要写入额外的版本元数据assemblyVersion与fileVersion字段。若这些元数据缺失——例如 2.1 之前发布的应用——则该程序集会被视为更旧older不会替换该程序集此前已解析到的位置。源码级印证版本裁决与替换逻辑冲突裁决在 hostpolicy 中的实际实现位于 deps_resolver.cpp。关键逻辑process_entry中的冲突分支如下首先用资产名在已解析映射表name_to_resolved_asset_map_t中查找L436-L437扩展名一致性校验新条目的文件名如System.Text.Json.dll必须与既有解析路径的文件名一致否则报DuplicateAssemblyWithDifferentExtensionMessage错误L457-L468版本裁决条件L473-L474新条目 Assembly Version 更高或 Assembly Version 相同且 File Version 更高时才触发替换流程路径相同则免替换若重新探测得到的路径与既有路径一致则无需替换L480替换动作从映射表中erase旧条目再重新add_tpa_asset加入新路径L488-L493。版本元数据的来源在 deps_format.cpp解析 deps.json 时读取每个 runtime 资产对象的可选属性assemblyVersion与fileVersion解析失败或缺失时使用version_t::empty()空版本即视为最旧。数据结构定义于 deps_entry.h每个deps_asset_t携带assembly_version与file_version两个version_t。各层处理顺序的源码印证在resolve_tpa_list中处理顺序与设计文档一致deps_resolver.cpp应用自身的 runtime 条目get_app_deps()fx_level0--additional-deps指定的各 deps 文件m_additional_deps同样以 fx_level0 处理框架依赖的 runtime 条目m_fx_deps[i]从i1开始即从高层框架到低层框架。值得注意的细节libhost如 comhost场景不支持 additional deps也不支持自包含应用场景下的 additional deps框架依赖型应用才具备 SharedFX 版本与 RID 回退图信息见 resolve_additional_deps 的注释deps 文件缺失时会回退为直接枚举应用目录中的程序集get_dir_assemblies见 L527-L531缺失资产的容错应用的资产缺失时不报错仅记录而框架层若缺少更新的包则视为错误L497-L503这与设计文档app 缺失可忽略的说明一致。常见冲突场景与排查建议场景一应用引用了比框架更新的 OOB 包应用 deps.json 先处理、应用目录先探测且框架 deps.json 通常不包含该 OOB 包 → 应用的版本胜出。这也是app wins最典型的场景。场景二框架想替换更高层框架的同名程序集例如中间框架发现Microsoft.NETCore.App中的某个程序集版本更旧且该框架自身目录中存在更高版本 → 由于只探测同级或更低级框架该框架可以解析到自身资产并替换高层框架条目。实现上即 L473-L474 的版本更高判定触发 erase 重新 add。场景三2.1 之前发布的应用其 deps.json 缺少assemblyVersion/fileVersion元数据 → 视为空版本最旧永远不替换已解析位置。因此旧应用的资产即使版本号更高在编译层面也不会覆盖框架解析结果。排查建议设置COREHOST_TRACE1环境变量后运行应用可输出 hostpolicy 的详细探测日志其中包含Adding tpa entry、Replacing deps entry [旧路径, AssemblyVersion:..., FileVersion:...] with [新路径, ...]等关键信息见 deps_resolver.cpp 与 L482-L486结合 host-probing.md 中描述的路径模板核对DOTNET_SHARED_STORE、additionalProbingPaths、servicing 目录是否引入了意外的高优先级资产检查目标 deps.json 中对应资产的assemblyVersion/fileVersion是否齐全判断其是否具备替换资格。相关阅读host-probing.md探测路径的完整细节servicing、共享存储、|arch|/|tfm|占位符等共享存储的路径构造实现shared_store.cppDOTNET_SHARED_STORE环境变量处理--additionalprobingpath命令行参数定义command_line.cppadditionalProbingPaths运行时配置解析runtime_config.cppdeps.json 中assemblyVersion/fileVersion的解析deps_format.cpp【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考