关于 clr-activation-debugging
使用 mscoree.dll shim 生成的 CLR 激活日志,诊断 .NET Framework 运行时激活问题。帮助开发者追踪运行时选择、激活失败、按需功能行为,以及相关的 COM 或旧版托管问题。
主要功能
- 01分析 CLRLoad 激活日志
- 02追踪 CLR 版本选择过程
- 03解读 shim 错误和激活结果
- 04检查按需功能及 COM 激活路径
适用场景
- 01诊断进程无法加载 CLR
- 02调查意外选择的 CLR 版本
- 03追踪 .NET 3.5 按需功能行为
- 04排查 COM 运行时激活失败
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dotnet/ skills --skill clr-activation-debugging安装后在 Claude Code 中输入/clr-activation-debugging确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/activation-flow.mdcom-activation.mdlog-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 | 符号 | 含义 |
|---|---|---|
0x80131700 | CLR_E_SHIM_RUNTIMELOAD | 找不到或无法加载合适的运行时版本。这是最常见的 shim 错误 — 在仅有 v4 的计算机上,受版本限制的旧版激活失败时,调用方会看到此错误。 |
0x80131701 | CLR_E_SHIM_RUNTIMEEXPORT | 找到了运行时,但无法从中获取所需的导出项或接口。 |
0x80131702 | CLR_E_SHIM_INSTALLROOT | 注册表中缺少 .NET Framework 安装根目录,或该目录无效。 |
0x80131703 | CLR_E_SHIM_INSTALLCOMP | 安装缺少所需组件。 |
0x80131704 | CLR_E_SHIM_LEGACYRUNTIMEALREADYBOUND | 另一个运行时已绑定为旧版运行时。旧版 API 试图绑定到与已选版本冲突的版本。 |
0x80131705 | CLR_E_SHIM_SHUTDOWNINPROGRESS | shim 正在关闭,无法处理请求。 |
如果用户报告了其中某个 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 注册表文档:
references/log-format.md— 日志行格式、字段和所有已知日志消息类型references/activation-flow.md— shim 选择运行时的决策树references/com-activation.md— COM(DllGetClassObject)激活详情、CLSID 注册表布局
如果直接读取失败,只列出一次此技能的 references/ 目录,并且仅当列表中显示预期文件时才重试。不要使用工作区文件或文本搜索来定位技能安装目录。
如果一个或多个预期参考文件仍不可用,请尽可能使用已加载的参考资料,并使用下方的内联知识补充缺失内容。在最终诊断中包含 Reference coverage: reduced; unavailable: <paths>; used inline guidance for missing references.,并将 <paths> 替换为缺失的相对路径。
步骤 2:检查日志文件
先了解整体情况,再深入查看任何单个日志:
- 列出所有日志文件,并按进程名称分组——这可以显示哪些可执行文件触发了 CLR 激活
- 针对每个进程,扫描结果行:
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
- 构建汇总表:
| 进程 | 日志文件 | 结果 | 选定的运行时 | 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 路径
如果版本解析失败:
ERROR: Unable to find a version of the runtime to use— shim 未找到合适的运行时SEM_FAILCRITICALERRORS is set to {value}— 检查进程错误模式:- 值为 0:允许显示错误对话框和 FOD
- 非零(设置了任意位,通常为 0x8001):禁止显示错误对话框和 FOD。
SEM_FAILCRITICALERRORS标志(0x0001)由父进程继承。
Checking if feature-on-demand installation would help— shim 重新运行版本计算,以查看安装 .NET 3.5 是否能解决此请求- 然后会出现以下其中一种情况:
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: 条目开头。常见模式如下:
- 首次通过
ClrCreateInstance/GetRequestedRuntime激活 → 成功(通过配置加载 v4.0) - 第二次通过
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:诊断并报告
给出清晰的诊断,涵盖以下内容:
- 发生了什么 — 哪些进程遇到了激活问题,具体症状是什么
- 为何发生 — 追踪 shim 中导致该结果的具体决策路径
- 哪些因素控制了行为 — 指出决定结果的具体输入(配置文件是否存在、错误模式、CLSID 注册、版本上限状态)
- 发生了哪些变化(如适用)— 如果用户表示行为发生了变化,指出哪些输入可能发生了变化(父进程的错误模式、配置文件、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 状态
- 已分别追踪单个日志中的多个激活
- 诊断说明了具体的决策路径,而不只是结果