mcpField
C
clr-activation-debugging

clr-activation-debugging

github.com/dotnet开发5.8k3.5k 次安装1 天前更新
代码仓库

关于 clr-activation-debugging

使用 mscoree.dll shim 生成的 CLR 激活日志,诊断 .NET Framework 运行时激活问题。帮助开发者追踪运行时选择、激活失败、按需功能行为,以及相关的 COM 或旧版托管问题。

主要功能

  1. 01分析 CLRLoad 激活日志
  2. 02追踪 CLR 版本选择过程
  3. 03解读 shim 错误和激活结果
  4. 04检查按需功能及 COM 激活路径

适用场景

  1. 01诊断进程无法加载 CLR
  2. 02调查意外选择的 CLR 版本
  3. 03追踪 .NET 3.5 按需功能行为
  4. 04排查 COM 运行时激活失败

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dotnet/skills --skill clr-activation-debugging

安装后在 Claude Code 中输入/clr-activation-debugging确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
activation-flow.md
com-activation.md
log-format.md

详细介绍

CLR 激活调试

通过分析 shim(mscoree.dll)生成的 CLR 激活日志(CLRLoad 日志),诊断 .NET Framework 运行时激活问题。这些日志会记录 shim 在选择和加载 CLR 版本时做出的每项决策。

适用场景

  • 进程完全无法加载 CLR(“Unable to find a version of the runtime to use”)
  • shim 选择了错误的 CLR 版本(例如,选择 v2.0 而不是 v4.0)
  • 意外出现 .NET 3.5 按需功能(FOD)安装对话框
  • 预计会出现 FOD 对话框,但却没有出现
  • CLR v2 和 CLR v4 同时加载到同一进程,导致故障
  • COM 对象激活失败,因为 shim 无法解析运行时
  • 旧版托管 API(CorBindToRuntime)绑定到了非预期版本

不适用场景

  • 现代 .NET(CoreCLR / .NET 5+) — 本技能仅涵盖 .NET Framework(mscoree.dll shim)
  • 程序集绑定失败 — 请使用 Fusion 日志(fuslogvw.exe),而不是 CLR 激活日志
  • CLR 加载后发生的运行时崩溃 — 激活已成功,问题出在其他地方

背景

Shim 架构

.NET Framework shim 分为两层:

  • mscoree.dll(“shell shim”)— 面向用户的 DLL,是 CLR 托管 COM 对象的已注册 InprocServer32,也是 _CorExeMain、旧版 API 等的入口点。
  • mscoreei.dll — 实际的 shim 实现,运行时选择逻辑、日志记录和激活决策都在此执行。mscoree.dll 会将请求转发到 mscoreei.dll。

阅读日志时,FOD 命令行中的 caller-name:mscoreei.dll 就体现了这一点 — 实际执行操作的是 mscoreei.dll。

.NET 3.5 / v2.0.50727 版本对应关系

.NET 2.0、3.0 和 3.5 共用同一 CLR 运行时版本:v2.0.50727。“3.0”和“3.5”版本是在 CLR v2.0 基础上增加了库。就激活而言,它们都属于“v2.0.50727”。当 shim 解析到 v2.0.50727,或 FOD 提示安装“NetFx3”时,安装的是 CLR v2.0 运行时(以及 3.0/3.5 库)。同样,CLR v4.0(v4.0.30319)涵盖了从 4.0 到 4.8.x 的所有 .NET Framework 版本。

最新版 Windows 上的 .NET 3.5 可用性

在较新的 Windows 版本(Windows 11 Insider Preview Build 27965 及未来平台版本)上,.NET Framework 3.5 不再作为 Windows 可选组件(按需功能)提供。必须通过独立 MSI 安装。这意味着即使触发了 FOD 对话框(fondue.exe /enable-feature:NetFx3),也无法在这些系统上完成安装。在 Windows 10 和不高于 25H2 的 Windows 11 上,FOD 仍然可用。.NET Framework 3.5 将于 2029 年 1 月 9 日终止支持。

Shim HRESULT 代码

shim 失败时,会返回 0x8013xxxx 范围内的特定 HRESULT。这些是调用方会看到的错误(激活日志本身不会记录这些代码,而是记录易于理解的消息):

HRESULT符号含义
0x80131700CLR_E_SHIM_RUNTIMELOAD找不到或无法加载合适的运行时版本。这是最常见的 shim 错误 — 在仅有 v4 的计算机上,受版本限制的旧版激活失败时,调用方会看到此错误。
0x80131701CLR_E_SHIM_RUNTIMEEXPORT找到了运行时,但无法从中获取所需的导出项或接口。
0x80131702CLR_E_SHIM_INSTALLROOT注册表中缺少 .NET Framework 安装根目录,或该目录无效。
0x80131703CLR_E_SHIM_INSTALLCOMP安装缺少所需组件。
0x80131704CLR_E_SHIM_LEGACYRUNTIMEALREADYBOUND另一个运行时已绑定为旧版运行时。旧版 API 试图绑定到与已选版本冲突的版本。
0x80131705CLR_E_SHIM_SHUTDOWNINPROGRESSshim 正在关闭,无法处理请求。

如果用户报告了其中某个 HRESULT(尤其是 0x80131700),CLR 激活日志就是合适的诊断工具。

先决条件

必须启用 CLR 激活日志记录才能生成日志文件。如果用户还没有日志,请指导他们启用日志记录:

通过环境变量(推荐——仅作用于当前会话):

set COMPLUS_CLRLoadLogDir=C:\CLRLoadLogs

通过注册表(全局生效——影响所有 .NET Framework 进程):

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework
  CLRLoadLogDir = "C:\CLRLoadLogs" (REG_SZ)

在 64 位系统上,如果涉及 32 位进程,还需在 Wow6432Node 下设置。

⚠️ 日志目录必须已存在。 此 shim 不会创建该目录。如果目录不存在,则不会写入日志,也不会出现错误或失败提示。

日志将写入为 {ProcessName}.CLRLoad{NN}.log(NN = 00–99,每个进程实例一个)。进程退出前无法读取日志——文件会一直保持打开状态。

捕获日志后,移除环境变量或注册表项以停止记录。

输入

输入必填描述
CLR 激活日志文件是一个或多个 .CLRLoad*.log 文件
症状描述建议提供用户观察到的情况(FOD 对话框、运行时错误、失败等)
预期行为建议提供用户期望发生的情况

工作流

步骤 1:加载参考资料

从包含此 SKILL.md 的目录解析捆绑文件路径,而不是从用户的工作区解析。按以下顺序加载参考文件——其中包含详细的日志格式、决策流程和 CLSID 注册表文档:

  1. references/log-format.md — 日志行格式、字段和所有已知日志消息类型
  2. references/activation-flow.md — shim 选择运行时的决策树
  3. references/com-activation.md — COM(DllGetClassObject)激活详情、CLSID 注册表布局

如果直接读取失败,只列出一次此技能的 references/ 目录,并且仅当列表中显示预期文件时才重试。不要使用工作区文件或文本搜索来定位技能安装目录。

如果一个或多个预期参考文件仍不可用,请尽可能使用已加载的参考资料,并使用下方的内联知识补充缺失内容。在最终诊断中包含 Reference coverage: reduced; unavailable: <paths>; used inline guidance for missing references.,并将 <paths> 替换为缺失的相对路径。

步骤 2:检查日志文件

先了解整体情况,再深入查看任何单个日志:

  1. 列出所有日志文件,并按进程名称分组——这可以显示哪些可执行文件触发了 CLR 激活
  2. 针对每个进程,扫描结果行:
    • Decided on runtime: vX.Y.Z — 成功解析
    • ERROR: — 解析失败
    • Launching feature-on-demand — 已显示 FOD 对话框
    • Could have launched feature-on-demand — 本应触发 FOD,但被抑制
    • V2.0 Capping is preventing consideration — 因 capping 而跳过了 v4+
grep -l "ERROR:\|Launching feature-on-demand\|Could have launched" *.log
grep -c "Launching feature-on-demand" *.log
  1. 构建汇总表:
进程日志文件结果选定的运行时FOD?
...............
步骤 3:分析有问题的日志

对于结果异常的每个日志文件,跟踪完整的激活流程。从上到下阅读日志并识别:

⚠️ 嵌套日志条目: shim 自身的内部调用可能会在已记录的激活序列中触发额外的日志条目。例如,DllGetClassObject 调用可能在内部调用 ComputeVersionString,而后者又会调用 FindLatestVersion,每一步都会生成日志行。当运行 FOD 检查(“Checking if feature-on-demand installation would help”)时,它会重新运行整个版本计算过程——在同一次激活中生成第二个 ComputeVersionString 块。不要将这些嵌套或重入条目误认为是单独的激活尝试。

3a. 入口点

第一条 FunctionCall: 或 MethodCall: 行会告诉你激活是如何触发的:

入口点含义
_CorExeMain托管 EXE 启动 — 该二进制文件本身就是一个 .NET 程序集
DllGetClassObject. Clsid: {guid}COM 激活 — 某个对象通过 mscoree.dll 路由,对一个 COM 类调用了 CoCreate
ClrCreateInstance新式(v4+)托管 API
CorBindToRuntimeEx旧式(v1/v2)托管 API — 将进程绑定到一个运行时
ICLRMetaHostPolicy::GetRequestedRuntime基于策略的托管 API(通常在其他入口点之后由内部调用)
LoadLibraryShim旧式 API,通过名称加载框架 DLL
3b. 输入参数

入口点之后会立即输出版本计算所用的输入参数:

  • IsLegacyBind:这是否为旧式(v4 之前)激活路径?如果为 1,shim 会采用单运行时的“旧式”视图。旧式 API(CorBindToRuntimeEx、用于旧式 COM 的 DllGetClassObject、LoadLibraryShim 等)会设置此项。
  • IsCapped:如果为 1,shim 的向前滚动语义会限制在 Whidbey(v2.0.50727)版本 — 枚举已安装运行时时不会考虑 v4.0+。这正是让 v4 安装不产生影响的机制:旧式代码路径会继续表现得仿佛 v4 不存在。在仅安装 v4 且未安装 .NET 3.5 的计算机上,受限枚举会发现没有任何运行时。如果显式提供了特定的 v4 版本字符串(例如,通过 CorBindToRuntimeEx("v4.0.30319", ...) 或通过配置中的 useLegacyV2RuntimeActivationPolicy),限制并不会阻止加载 v4+。
  • SkuCheckFlags:控制 SKU(版本)兼容性检查。
  • ShouldEmulateExeLaunch:是否应为了策略判断而将其视作 EXE 启动。
  • LegacyBindRequired:是否严格要求旧式绑定。
3c. 配置文件处理

查找配置文件解析结果:

  • Parsing config file: {path} — shim 正在查找 .config 文件
  • Config File (Open). Result:00000000 — 已找到并成功打开配置文件
  • Config File (Open). Result:80070002 — 未找到配置文件(ERROR_FILE_NOT_FOUND 对应的 HRESULT)
  • Found config file: {path} — 已成功读取配置文件
  • UseLegacyV2RuntimeActivationPolicy is set to {0|1} — 是否存在 <startup useLegacyV2RuntimeActivationPolicy="true">。如果为 1,所有运行时都会被视为旧式代码路径的候选项 — 这意味着旧式 shim API 可以枚举并选择 v4+。此设置可与多个 <supportedRuntime> 条目、其他配置选项配合使用,也可以在完全没有 <supportedRuntime> 条目的情况下使用(此时旧式 API 可以直接枚举 v4)。**副作用:**会关闭与 v4 之前运行时的进程内 SxS — 将它们排除在进程之外。
  • Config file includes SupportedRuntime entry. Version: vX.Y.Z, SKU: {sku} — 配置中找到的每个 <supportedRuntime> 条目

**关键要点:**如果进程没有配置文件,并且正在进行受限的旧式绑定,shim 就没有任何信息可以将其引导到 v4.0。它会枚举已安装的运行时(限制为 ≤v2.0),如果未安装 3.5,就会一无所获并失败。这是有意设计的 — v4 对这些代码路径不可见,以确保安装 v4 不产生影响。

3d. 版本解析
  • Installed Runtime: vX.Y.Z. VERSION_ARCHITECTURE: N — 计算机上已安装的运行时
  • {exe} was built with version: vX.Y.Z — 来自二进制文件 PE 标头的版本(仅适用于托管程序集;本机 EXE 不会包含此信息)
  • Using supportedRuntime: vX.Y.Z — shim 从配置中的 <supportedRuntime> 列表选择的版本
  • FindLatestVersion is returning the following version: vX.Y.Z ... V2.0 Capped: {0|1} — 基于策略的最新版本搜索结果
  • Default version of the runtime on the machine: vX.Y.Z 或 (null) — shim 最终确定的版本;(null) 表示未找到任何版本
  • Decided on runtime: vX.Y.Z — 最终决定 — 将要加载的版本
3e. 失败和 FOD 路径

如果版本解析失败:

  1. ERROR: Unable to find a version of the runtime to use — shim 未找到合适的运行时
  2. SEM_FAILCRITICALERRORS is set to {value} — 检查进程错误模式:
    • 值为 0:允许显示错误对话框和 FOD
    • 非零(设置了任意位,通常为 0x8001):禁止显示错误对话框和 FOD。SEM_FAILCRITICALERRORS 标志(0x0001)由父进程继承。
  3. Checking if feature-on-demand installation would help — shim 重新运行版本计算,以查看安装 .NET 3.5 是否能解决此请求
  4. 然后会出现以下其中一种情况:
    • Launching feature-on-demand installation. CmdLine: "...\fondue.exe" /enable-feature:NetFx3 — 显示 FOD 对话框
    • Could have launched feature-on-demand installation if was not opted out. — 由于设置了 SEM_FAILCRITICALERRORS,FOD 被禁止
3f. 单个进程中的多次激活

单个日志可能包含多个激活序列。每个序列都以新的 FunctionCall: 或 MethodCall: 条目开头。常见模式如下:

  1. 首次通过 ClrCreateInstance / GetRequestedRuntime 激活 → 成功(通过配置加载 v4.0)
  2. 第二次通过 DllGetClassObject(COM)激活 → 旧版绑定,版本上限生效 → 失败

当本机 EXE(如 link.exe 或 mt.exe)为主要任务成功加载 CLR 后,后续的 COM 激活请求(例如针对 diasymreader 的请求)会触发单独的旧版解析,而该解析无法找到 v2.0 时,就会出现这种情况。

步骤 4:检查系统状态(如有需要)

如果日志分析表明存在注册或配置问题,请检查:

CLSID 注册(针对 COM 激活问题):

# Check the CLSID entry
Get-ItemProperty 'Registry::HKCR\CLSID\{guid}'
Get-ItemProperty 'Registry::HKCR\CLSID\{guid}\InprocServer32'
Get-ChildItem 'Registry::HKCR\CLSID\{guid}\InprocServer32' | ForEach-Object {
    Write-Output "--- $($_.PSChildName) ---"
    Get-ItemProperty "Registry::$($_.Name)"
}

InprocServer32 下的关键值:

  • (Default) 对于由 CLR 托管的 COM 对象,应为 mscoree.dll
  • 版本子项(例如 2.0.50727、4.0.30319)表示哪些运行时版本注册了此 CLSID
  • 版本子项下的 ImplementedInThisVersion 表示该运行时版本原生实现了此 COM 类(而非通过托管互操作实现)
  • 版本子项下的 Assembly 和 Class 表示托管 COM 互操作注册
  • 版本子项下的 RuntimeVersion 指定应由哪个 CLR 版本托管此对象

已安装的运行时:

Get-ChildItem 'Registry::HKLM\SOFTWARE\Microsoft\.NETFramework\policy'

进程错误模式(解释 FOD 为何启动或未启动): SEM_FAILCRITICALERRORS 标志由父进程继承。如果构建系统或脚本设置了该标志(或调用了 SetErrorMode),所有子进程都会继承它。

步骤 5:诊断并报告

给出清晰的诊断,涵盖以下内容:

  1. 发生了什么 — 哪些进程遇到了激活问题,具体症状是什么
  2. 为何发生 — 追踪 shim 中导致该结果的具体决策路径
  3. 哪些因素控制了行为 — 指出决定结果的具体输入(配置文件是否存在、错误模式、CLSID 注册、版本上限状态)
  4. 发生了哪些变化(如适用)— 如果用户表示行为发生了变化,指出哪些输入可能发生了变化(父进程的错误模式、配置文件、CLSID 注册、已安装的运行时)

常见场景

意外的 FOD 对话框

模式: DllGetClassObject → IsCapped: 1 → 没有配置文件 → (null) → SEM_FAILCRITICALERRORS: 0 → 启动 FOD

根本原因: 原生 EXE 正在对一个注册在 mscoree.dll 下的 CLSID 执行 COM 激活。这会走旧版代码路径,其运行时版本上限为 v2.0。没有配置文件(且没有 useLegacyV2RuntimeActivationPolicy)时,这条代码路径无法识别 v4。在未安装 .NET 3.5 的计算机上,没有可见的运行时;而且未设置 SEM_FAILCRITICALERRORS 时,就会弹出 FOD 对话框。

关键问题: SEM_FAILCRITICALERRORS 为什么会改变?它从父进程继承。不同的启动方式(脚本与直接调用、不同的构建系统)会产生不同的错误模式。仅有 v4 的计算机上,受限的旧版绑定始终会失败,只是 SEM_FAILCRITICALERRORS 决定了它表现为可见对话框还是静默失败。

选择了错误的运行时

模式: 配置中的 supportedRuntime 条目列出了多个版本;shim 会选择第一个已安装的版本。如果 v2.0 排在首位且已安装 .NET 3.5,即使 v4.0 也可用,仍会优先选择 v2.0。

关键要点: 配置中的 <supportedRuntime> 条目按顺序评估。第一个已安装的匹配版本会胜出。

v2 和 v4 均已加载

模式: 同一进程日志中出现多个激活序列——一个绑定到 v4,另一个绑定到 v2(或反过来)。在同一进程中并行加载 CLR v2 和 v4 是受支持的,但可能会导致共享状态问题。

关键要点: 查找同一日志文件中版本不同的多个 Decided on runtime 行。

旧版运行时已绑定

模式: 进程较早阶段成功执行了旧版代码路径(例如,使用显式 v4 版本调用 CorBindToRuntimeEx,或配置了 useLegacyV2RuntimeActivationPolicy)。这会将旧版运行时设为 v4.0。此后所有旧版激活(包括原本会失败的受限 COM 激活)都会复用已绑定的旧版运行时,从而静默成功。

关键要点: 进程内激活的顺序很重要。如果先将 v4.0 绑定为旧版运行时,受限 COM 激活就能正常工作。如果受限 COM 激活先发生(在任何旧版运行时绑定之前),它就会失败。这意味着具体行为可能取决于哪个组件先执行激活——并发代码中的竞争条件可能改变结果。

常见误区

误区正确做法
认为 IsCapped: 1 表示 v4.0 永远无法加载限制仅会影响向前滚动枚举。以下情况仍可加载 v4.0:显式传入特定版本字符串;配置中包含 useLegacyV2RuntimeActivationPolicy="true" 和 <supportedRuntime version="v4.0"/>;或者旧版运行时已绑定到 v4+。
认为限制功能失效或存在 Bug限制是有意设计的,可避免安装 v4 产生影响。在仅安装 v4 的计算机上,旧版代码路径会正确地检测不到任何运行时。这是预期行为。
认为 FOD 是按进程控制的SEM_FAILCRITICALERRORS 会从父进程继承。父进程(构建系统、脚本、shell)的设置变化会改变所有子进程的行为。
只查看日志中的首次激活单个日志可能包含多个独立的激活序列。有问题的激活通常是后续的 COM 激活,而非初始 CLR 加载。
认为缺少配置文件无关紧要对于使用旧版/受限绑定执行 COM 激活的本机 EXE,配置文件(包含 useLegacyV2RuntimeActivationPolicy)是让旧版代码路径检测到 v4.0 的主要方式。没有配置文件 = 受限 = v4 不可见。
添加 <supportedRuntime>,却未设置 useLegacyV2RuntimeActivationPolicy若未设置 useLegacyV2RuntimeActivationPolicy="true",通过配置向前滚动到 v4 对主 EXE 加载有效,但旧版代码路径(COM 激活、对 mscoree.h API 的 P/Invoke)仍受限于 v2.0。旧版代码路径需要同时具备这两项设置。
未了解权衡取舍就设置 useLegacyV2RuntimeActivationPolicy此属性会关闭进程内 SxS,即阻止 v4 之前的运行时进入进程。对于构建工具,这通常没有问题;但对于需要同时托管 v2 和 v4 的应用,应谨慎考虑。

验证

提交诊断结果前,请确认:

  • 已分析所有包含错误或触发 FOD 的日志文件(而不只是第一个)
  • 已确定每个有问题的激活的入口点
  • 已记录每个激活序列的限制和旧版绑定状态
  • 已检查配置文件是否存在
  • 已记录与 FOD 相关问题中的 SEM_FAILCRITICALERRORS 状态
  • 已分别追踪单个日志中的多个激活
  • 诊断说明了具体的决策路径,而不只是结果

评论

支持 Markdown