clr-activation-debugging

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

CLR Activation Debugging

CLR激活调试

Diagnose .NET Framework runtime activation issues by analyzing CLR activation logs (CLRLoad logs) produced by the shim (mscoree.dll). These logs record every decision the shim makes when selecting and loading a CLR version.
通过分析垫片程序(mscoree.dll)生成的CLR激活日志(CLRLoad日志)诊断.NET Framework运行时激活问题。这些日志记录了垫片程序在选择和加载CLR版本时做出的每一个决策。

When to Use

适用场景

  • A process fails to load the CLR at all ("Unable to find a version of the runtime to use")
  • The shim picks the wrong CLR version (e.g., v2.0 instead of v4.0)
  • Unexpected .NET 3.5 Feature-on-Demand (FOD) install dialogs appear
  • FOD dialogs are expected but do NOT appear
  • Both CLR v2 and CLR v4 load into the same process, causing failures
  • A COM object fails to activate because the shim can't resolve the runtime
  • Legacy hosting APIs (CorBindToRuntime) bind to an unexpected version
  • 进程完全无法加载CLR(提示“找不到要使用的运行时版本”)
  • 垫片程序选择了错误的CLR版本(例如选择v2.0而非v4.0)
  • 意外弹出.NET 3.5按需功能(FOD)安装对话框
  • 本应弹出FOD对话框却未出现
  • CLR v2和CLR v4同时加载到同一进程导致故障
  • COM对象因垫片程序无法解析运行时激活失败
  • 旧版托管API(CorBindToRuntime)绑定到了意外的版本

When Not to Use

不适用场景

  • Modern .NET (CoreCLR / .NET 5+) — this skill covers .NET Framework only (the mscoree.dll shim)
  • Assembly binding failures — use Fusion logs (fuslogvw.exe), not CLR activation logs
  • Runtime crashes after the CLR has loaded — activation succeeded; the problem is elsewhere
  • 现代.NET(CoreCLR / .NET 5+) —— 本指南仅适用于.NET Framework(mscoree.dll垫片)
  • 程序集绑定失败 —— 请使用Fusion日志(fuslogvw.exe),而非CLR激活日志
  • CLR加载完成后的运行时崩溃 —— 此时激活已成功,问题出在其他环节

Background

背景

The Shim Architecture

垫片架构

The .NET Framework shim has two layers:
  • mscoree.dll (the "shell shim") — the public-facing DLL that is the registered
    InprocServer32
    for CLR-hosted COM objects and the entry point for
    _CorExeMain
    , legacy APIs, etc.
  • mscoreei.dll — the actual shim implementation where the runtime selection logic, logging, and activation decisions live. mscoree.dll forwards into mscoreei.dll.
When reading logs, the
caller-name:mscoreei.dll
in FOD command lines reflects this — it's mscoreei.dll doing the work.
.NET Framework垫片分为两层:
  • mscoree.dll(“外壳垫片”)—— 对外暴露的DLL,是CLR托管COM对象的注册
    InprocServer32
    ,也是
    _CorExeMain
    、旧版API等的入口点
  • mscoreei.dll —— 垫片的实际实现,包含运行时选择逻辑、日志记录和激活决策逻辑,mscoree.dll会将请求转发到mscoreei.dll
读取日志时,FOD命令行中的
caller-name:mscoreei.dll
就反映了这一架构 —— 实际执行工作的是mscoreei.dll。

.NET 3.5 / v2.0.50727 Version Mapping

.NET 3.5 / v2.0.50727版本映射

.NET 2.0, 3.0, and 3.5 all share the same CLR runtime version: v2.0.50727. The "3.0" and "3.5" releases were library additions on top of CLR v2.0. For activation purposes, they are all "v2.0.50727." When the shim resolves to v2.0.50727 or FOD offers to install "NetFx3", it's installing the CLR v2.0 runtime (plus the 3.0/3.5 libraries). Similarly, CLR v4.0 (v4.0.30319) covers all .NET Framework versions from 4.0 through 4.8.x.
.NET 2.0、3.0和3.5都共享同一个CLR运行时版本:v2.0.50727。“3.0”和“3.5”版本是在CLR v2.0基础上新增的类库。从激活的角度来看,它们都属于“v2.0.50727”。当垫片解析到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版本。

.NET 3.5 Availability on Recent Windows

近期Windows版本上的.NET 3.5可用性

On recent Windows versions (Windows 11 Insider Preview Build 27965 and future platform releases), .NET Framework 3.5 is no longer available as a Windows optional component (Feature-on-Demand). It must be installed from a standalone MSI. This means the FOD dialog (
fondue.exe /enable-feature:NetFx3
) will not succeed on these systems even if it fires. On Windows 10 and Windows 11 through 25H2, FOD remains available. .NET Framework 3.5 reaches end of support on January 9, 2029.
在近期的Windows版本(Windows 11预览体验版本27965及未来的平台版本)中,.NET Framework 3.5不再作为Windows可选组件(按需功能)提供,必须通过独立MSI安装。这意味着即便触发了FOD对话框(
fondue.exe /enable-feature:NetFx3
),在这些系统上也无法安装成功。在Windows 10和Windows 11 25H2及更早版本中,FOD仍然可用。.NET Framework 3.5的支持将于2029年1月9日终止。

Shim HRESULT Codes

垫片HRESULT码

When the shim fails, it returns specific HRESULTs in the
0x8013xxxx
range. These are the errors you'll see from callers (not in the activation logs themselves, which log human-readable messages):
HRESULTSymbolMeaning
0x80131700
CLR_E_SHIM_RUNTIMELOAD
Cannot find or load a suitable runtime version. This is the most common shim error — it's what callers see when capped legacy activation fails on a v4-only machine.
0x80131701
CLR_E_SHIM_RUNTIMEEXPORT
Found a runtime but failed to get a required export or interface from it.
0x80131702
CLR_E_SHIM_INSTALLROOT
The .NET Framework install root is missing or invalid in the registry.
0x80131703
CLR_E_SHIM_INSTALLCOMP
A required component of the installation is missing.
0x80131704
CLR_E_SHIM_LEGACYRUNTIMEALREADYBOUND
A different runtime is already bound as the legacy runtime. A legacy API tried to bind to a version that conflicts with the one already chosen.
0x80131705
CLR_E_SHIM_SHUTDOWNINPROGRESS
The shim is shutting down and cannot service the request.
If a user reports one of these HRESULTs (especially
0x80131700
), CLR activation logs are the right diagnostic tool.
当垫片运行失败时,会返回
0x8013xxxx
范围内的特定HRESULT。这些是调用方会收到的错误(不会出现在激活日志中,日志中记录的是人类可读的消息):
HRESULT符号含义
0x80131700
CLR_E_SHIM_RUNTIMELOAD
找不到或无法加载合适的运行时版本。这是最常见的垫片错误 —— 在仅安装了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
垫片正在关闭,无法处理请求。
如果用户反馈出现了这些HRESULT(尤其是
0x80131700
),CLR激活日志是合适的诊断工具。

Prerequisites

前置要求

CLR activation logging must be enabled to produce log files. If the user doesn't have logs yet, instruct them to enable logging:
Via environment variable (recommended — scoped to current session):
set COMPLUS_CLRLoadLogDir=C:\CLRLoadLogs
Via registry (machine-wide — affects all .NET Framework processes):
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework
  CLRLoadLogDir = "C:\CLRLoadLogs" (REG_SZ)
On 64-bit systems, also set under
Wow6432Node
if 32-bit processes are involved.
⚠️ The log directory must already exist. The shim will not create it. If it doesn't exist, no logs will be written and there will be no error or indication of failure.
Logs are written as
{ProcessName}.CLRLoad{NN}.log
(NN = 00–99, one per process instance). Logs cannot be read until the process exits — the file is held open.
After capturing, remove the env var or registry key to stop logging.
必须先启用CLR激活日志记录才能生成日志文件。如果用户还没有日志,指导他们开启日志记录:
通过环境变量开启(推荐 —— 仅作用于当前会话):
set COMPLUS_CLRLoadLogDir=C:\\CLRLoadLogs
通过注册表开启(机器级别 —— 影响所有.NET Framework进程):
HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\.NETFramework
  CLRLoadLogDir = "C:\\CLRLoadLogs" (REG_SZ)
在64位系统上,如果涉及32位进程,还需要在
Wow6432Node
下设置相同的配置。
⚠️ 日志目录必须已提前创建,垫片不会自动创建目录。如果目录不存在,不会生成任何日志,也不会有错误提示或失败通知。
日志文件以
{ProcessName}.CLRLoad{NN}.log
格式生成(NN = 00–99,每个进程实例对应一个日志文件)。进程退出前无法读取日志,文件会处于被占用状态。
捕获日志后,删除环境变量或注册表项以停止日志记录。

Inputs

输入项

InputRequiredDescription
CLR activation log filesYesOne or more
.CLRLoad*.log
files
Symptom descriptionRecommendedWhat the user observed (FOD dialog, wrong runtime, failure, etc.)
Expected behaviorRecommendedWhat the user expected to happen
输入项是否必填描述
CLR激活日志文件一个或多个
.CLRLoad*.log
文件
症状描述推荐用户观察到的现象(FOD对话框、运行时错误、故障等)
预期行为推荐用户期望的运行结果

Workflow

工作流程

Step 1: Load Reference Material

步骤1:加载参考材料

Try to load the reference files in this order — they contain the detailed log format, decision flow, and CLSID registry documentation:
  1. references/log-format.md
    — Log line format, fields, and all known log message types
  2. references/activation-flow.md
    — The shim's decision tree for runtime selection
  3. references/com-activation.md
    — COM (DllGetClassObject) activation specifics, CLSID registry layout
If reference files are not available, proceed using the inline knowledge below.
按以下顺序加载参考文件,它们包含详细的日志格式、决策流程和CLSID注册表文档:
  1. references/log-format.md
    —— 日志行格式、字段和所有已知的日志消息类型
  2. references/activation-flow.md
    —— 垫片选择运行时的决策树
  3. references/com-activation.md
    —— COM(DllGetClassObject)激活细节、CLSID注册表结构
如果没有参考文件,可使用下文的内嵌知识继续操作。

Step 2: Survey the Log Files

步骤2:概览日志文件

Get the big picture before diving into any single log:
  1. List all log files and group by process name — this shows which executables triggered CLR activation
  2. For each process, scan for outcome lines:
    • Decided on runtime: vX.Y.Z
      — successful resolution
    • ERROR:
      — failed resolution
    • Launching feature-on-demand
      — FOD dialog was shown
    • Could have launched feature-on-demand
      — FOD would have fired but was suppressed
    • V2.0 Capping is preventing consideration
      — v4+ was skipped due to capping
grep -l "ERROR:\|Launching feature-on-demand\|Could have launched" *.log
grep -c "Launching feature-on-demand" *.log
  1. Build a summary table:
ProcessLog FilesOutcomeRuntime SelectedFOD?
...............
在深入分析单个日志前先了解整体情况:
  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
      —— 由于版本限制,跳过了v4+版本
grep -l "ERROR:\\|Launching feature-on-demand\\|Could have launched" *.log
grep -c "Launching feature-on-demand" *.log
  1. 构建汇总表:
进程日志文件结果选中的运行时是否触发FOD?
...............

Step 3: Analyze Problematic Logs

步骤3:分析问题日志

For each log file with an unexpected outcome, trace the full activation flow. Read the log top-to-bottom and identify:
⚠️ Nested log entries: The shim's own internal calls can trigger additional log entries within an activation sequence that is already being logged. For example, a
DllGetClassObject
call may internally call
ComputeVersionString
, which calls
FindLatestVersion
, each generating log lines. When the FOD check runs ("Checking if feature-on-demand installation would help"), it re-runs the entire version computation — producing a second
ComputeVersionString
block within the same activation. Don't mistake these nested/re-entrant entries for separate activation attempts.
针对每个结果不符合预期的日志文件,追踪完整的激活流程。从上到下阅读日志,识别以下内容:
⚠️ 嵌套日志条目: 垫片自身的内部调用可能会在正在记录的激活序列中触发额外的日志条目。例如,
DllGetClassObject
调用可能在内部调用
ComputeVersionString
,后者又调用
FindLatestVersion
,每个调用都会生成日志行。当运行FOD检查时(“Checking if feature-on-demand installation would help”),会重新运行完整的版本计算 —— 在同一个激活流程中生成第二个
ComputeVersionString
块。不要将这些嵌套/重入的条目误认为是独立的激活尝试。

3a. Entry Point

3a. 入口点

The first
FunctionCall:
or
MethodCall:
line tells you how activation was triggered:
Entry PointMeaning
_CorExeMain
Managed EXE launch — the binary IS a .NET assembly
DllGetClassObject. Clsid: {guid}
COM activation — something CoCreated a COM class routed through mscoree.dll
ClrCreateInstance
Modern (v4+) hosting API
CorBindToRuntimeEx
Legacy (v1/v2) hosting API — binds the process to one runtime
ICLRMetaHostPolicy::GetRequestedRuntime
Policy-based hosting API (often called internally after other entry points)
LoadLibraryShim
Legacy API to load a framework DLL by name
第一行
FunctionCall:
MethodCall:
会告诉你激活是如何触发的:
入口点含义
_CorExeMain
托管EXE启动 —— 该二进制文件本身就是.NET程序集
DllGetClassObject. Clsid: {guid}
COM激活 —— 某个程序通过CoCreate创建了经由mscoree.dll路由的COM类
ClrCreateInstance
现代(v4+)托管API
CorBindToRuntimeEx
旧版(v1/v2)托管API —— 将进程绑定到单个运行时
ICLRMetaHostPolicy::GetRequestedRuntime
基于策略的托管API(通常在其他入口点之后被内部调用)
LoadLibraryShim
按名称加载框架DLL的旧版API

3b. Input Parameters

3b. 输入参数

Immediately after the entry point, the log dumps the version computation inputs:
  • IsLegacyBind
    : Is this a legacy (pre-v4) activation path? If 1, the shim uses the single-runtime "legacy" view of the world. Legacy APIs (
    CorBindToRuntimeEx
    ,
    DllGetClassObject
    for legacy COM,
    LoadLibraryShim
    , etc.) set this.
  • IsCapped
    : If 1, the shim's roll-forward semantics are capped at Whidbey (v2.0.50727) — it will NOT consider v4.0+ when enumerating installed runtimes. This is the mechanism that makes v4 installation non-impactful: legacy codepaths continue to behave as if v4 doesn't exist. On a v4-only machine with no .NET 3.5, a capped enumeration sees no runtimes at all. Capping does NOT prevent loading v4+ if a specific v4 version string is explicitly provided (e.g., via
    CorBindToRuntimeEx("v4.0.30319", ...)
    or via config with
    useLegacyV2RuntimeActivationPolicy
    ).
  • SkuCheckFlags
    : Controls SKU (edition) compatibility checking.
  • ShouldEmulateExeLaunch
    : Whether to pretend this is an EXE launch for policy purposes.
  • LegacyBindRequired
    : Whether a legacy bind is strictly required.
在入口点之后,日志会立即输出版本计算的输入参数:
  • IsLegacyBind
    : 是否为旧版(v4之前)激活路径?如果为1,垫片会使用单运行时的“旧版”逻辑。旧版API(
    CorBindToRuntimeEx
    、旧版COM的
    DllGetClassObject
    LoadLibraryShim
    等)会设置这个参数。
  • IsCapped
    : 如果为1,垫片的前向滚动语义被限制在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. Config File Processing

3c. 配置文件处理

Look for config file parsing results:
  • Parsing config file: {path}
    — the shim is looking for a
    .config
    file
  • Config File (Open). Result:00000000
    — config file found and opened successfully
  • Config File (Open). Result:80070002
    config file not found (HRESULT for ERROR_FILE_NOT_FOUND)
  • Found config file: {path}
    — config was successfully read
  • UseLegacyV2RuntimeActivationPolicy is set to {0|1}
    — whether
    <startup useLegacyV2RuntimeActivationPolicy="true">
    is present. When 1, all runtimes are treated as candidates for legacy codepaths — meaning legacy shim APIs can enumerate and choose v4+. This can be used with multiple
    <supportedRuntime>
    entries, with other config options, or even with no
    <supportedRuntime>
    entries at all (in which case legacy APIs can simply enumerate v4). Side effect: turns off in-proc SxS with pre-v4 runtimes — locks them out of the process.
  • Config file includes SupportedRuntime entry. Version: vX.Y.Z, SKU: {sku}
    — each
    <supportedRuntime>
    found in config
Key insight: If a process has no config file AND is doing a capped legacy bind, the shim has nothing to direct it to v4.0. It will enumerate installed runtimes (capped to ≤v2.0), find nothing if 3.5 isn't installed, and fail. This is by design — v4 is intentionally invisible to these codepaths to keep v4 installation non-impactful.
查找配置文件解析结果:
  • Parsing config file: {path}
    —— 垫片正在查找
    .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时,所有运行时都会被视为旧代码路径的候选 —— 意味着旧版垫片API可以枚举并选择v4+版本。可以配合多个
    <supportedRuntime>
    条目、其他配置选项使用,甚至可以不搭配
    <supportedRuntime>
    条目(这种情况下旧版API可以直接枚举v4)。副作用: 关闭进程内v4之前版本运行时的SxS支持 —— 禁止这些版本加载到进程中。
  • Config file includes SupportedRuntime entry. Version: vX.Y.Z, SKU: {sku}
    —— 在配置中找到的每个
    <supportedRuntime>
    条目
关键要点: 如果进程没有配置文件,并且正在执行受限制的旧版绑定,垫片没有任何引导指向v4.0。它会枚举已安装的运行时(限制为≤v2.0),如果未安装3.5则找不到任何运行时,最终失败。这是设计使然 —— v4对这些代码路径故意不可见,以保证v4安装不会影响旧系统。

3d. Version Resolution

3d. 版本解析

  • Installed Runtime: vX.Y.Z. VERSION_ARCHITECTURE: N
    — what's installed on the machine
  • {exe} was built with version: vX.Y.Z
    — version from the binary's PE header (managed assemblies only; native EXEs won't have this)
  • Using supportedRuntime: vX.Y.Z
    — the shim picked a version from the config's
    <supportedRuntime>
    list
  • FindLatestVersion is returning the following version: vX.Y.Z ... V2.0 Capped: {0|1}
    — result of policy-based latest-version search
  • Default version of the runtime on the machine: vX.Y.Z
    or
    (null)
    — what the shim settled on;
    (null)
    means nothing was found
  • Decided on runtime: vX.Y.Z
    final decision — this is the version that will be loaded
  • Installed Runtime: vX.Y.Z. VERSION_ARCHITECTURE: N
    —— 机器上安装的运行时
  • {exe} was built with version: vX.Y.Z
    —— 二进制文件PE头中的版本(仅托管程序集有,原生EXE没有)
  • Using supportedRuntime: vX.Y.Z
    —— 垫片从配置的
    <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)
    —— 垫片最终选定的版本;
    (null)
    表示未找到任何版本
  • Decided on runtime: vX.Y.Z
    —— 最终决策 —— 即将加载的版本

3e. Failure and FOD Path

3e. 失败和FOD路径

If version resolution fails:
  1. ERROR: Unable to find a version of the runtime to use
    — the shim found no suitable runtime
  2. SEM_FAILCRITICALERRORS is set to {value}
    — checks the process error mode:
    • Value 0: Error dialogs and FOD are ALLOWED
    • Nonzero (any bit set, commonly 0x8001): Error dialogs and FOD are SUPPRESSED. The
      SEM_FAILCRITICALERRORS
      flag (0x0001) is inherited from the parent process.
  3. Checking if feature-on-demand installation would help
    — the shim re-runs version computation to see if installing .NET 3.5 would resolve the request
  4. Then either:
    • Launching feature-on-demand installation. CmdLine: "...\fondue.exe" /enable-feature:NetFx3
      FOD dialog shown
    • Could have launched feature-on-demand installation if was not opted out.
      FOD suppressed because
      SEM_FAILCRITICALERRORS
      was set
如果版本解析失败:
  1. ERROR: Unable to find a version of the runtime to use
    —— 垫片没有找到合适的运行时
  2. SEM_FAILCRITICALERRORS is set to {value}
    —— 检查进程错误模式:
    • 值为0: 允许弹出错误对话框和FOD
    • 非0(任意位被设置,通常为0x8001): 禁止弹出错误对话框和FOD。
      SEM_FAILCRITICALERRORS
      标志(0x0001)会从父进程继承。
  3. Checking if feature-on-demand installation would help
    —— 垫片重新运行版本计算,判断安装.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.
      —— FOD被抑制,因为设置了
      SEM_FAILCRITICALERRORS

3f. Multiple Activations in One Process

3f. 单个进程中的多次激活

A single log can contain multiple activation sequences. Each begins with a new
FunctionCall:
or
MethodCall:
entry. A common pattern:
  1. First activation via
    ClrCreateInstance
    /
    GetRequestedRuntime
    → succeeds (loads v4.0 via config)
  2. Second activation via
    DllGetClassObject
    (COM) → legacy bind, capped → fails
This happens when a native EXE (like link.exe or mt.exe) loads the CLR successfully for its primary work, then a secondary COM activation request (e.g., for diasymreader) triggers a separate legacy resolution that can't find v2.0.
单个日志可能包含多个激活序列,每个序列都以新的
FunctionCall:
MethodCall:
条目开头。常见模式:
  1. 第一次通过
    ClrCreateInstance
    /
    GetRequestedRuntime
    激活 → 成功(通过配置加载v4.0)
  2. 第二次通过
    DllGetClassObject
    (COM)激活 → 旧版绑定,受版本限制 → 失败
当原生EXE(例如link.exe或mt.exe)为了主要工作成功加载CLR后,后续的COM激活请求(例如diasymreader的请求)会触发独立的旧版解析,无法找到v2.0时就会出现这种情况。

Step 4: Check System State (if needed)

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

When log analysis points to a registration or configuration issue, check:
CLSID Registration (for COM activation issues):
powershell
undefined
当日志分析指向注册或配置问题时,检查以下内容:
CLSID注册(针对COM激活问题):
powershell
undefined

Check the CLSID entry

检查CLSID条目

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)" }

Key values under `InprocServer32`:
- `(Default)` should be `mscoree.dll` for CLR-hosted COM objects
- **Version subkeys** (e.g., `2.0.50727`, `4.0.30319`) indicate which runtime versions registered this CLSID
- **`ImplementedInThisVersion`** under a version subkey means that runtime version natively implements the COM class (not via managed interop)
- **`Assembly`** and **`Class`** under a version subkey indicate a managed COM interop registration
- **`RuntimeVersion`** under a version subkey specifies which CLR version should host this object

**Installed runtimes:**
```powershell
Get-ChildItem 'Registry::HKLM\SOFTWARE\Microsoft\.NETFramework\policy'
Process error mode (why FOD did/didn't fire): The
SEM_FAILCRITICALERRORS
flag is inherited from the parent process. If a build system or script sets it (or calls
SetErrorMode
), all child processes inherit it.
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`下的关键值:
- `(默认)`对于CLR托管的COM对象应该为`mscoree.dll`
- **版本子键**(例如`2.0.50727`、`4.0.30319`)表示哪个运行时版本注册了这个CLSID
- 版本子键下的**`ImplementedInThisVersion`**表示该运行时版本原生实现了这个COM类(不是通过托管互操作)
- 版本子键下的**`Assembly`**和**`Class`**表示托管COM互操作注册
- 版本子键下的**`RuntimeVersion`**指定应该托管该对象的CLR版本

**已安装的运行时:**
```powershell
Get-ChildItem 'Registry::HKLM\\SOFTWARE\\Microsoft\\.NETFramework\\policy'
进程错误模式(FOD是否触发的原因):
SEM_FAILCRITICALERRORS
标志从父进程继承。如果构建系统或脚本设置了该标志(或调用了
SetErrorMode
),所有子进程都会继承这个设置。

Step 5: Diagnose and Report

步骤5:诊断并报告

Produce a clear diagnosis covering:
  1. What happened — which process(es) had activation issues and what the symptom was
  2. Why it happened — trace through the specific decision path in the shim that led to the outcome
  3. What controls the behavior — identify the specific inputs (config file presence, error mode, CLSID registration, capping state) that determined the outcome
  4. What changed (if applicable) — if the user says behavior changed, identify which input could have changed (error mode from parent process, config file, CLSID registration, installed runtimes)
生成清晰的诊断结果,包含以下内容:
  1. 发生了什么 —— 哪些进程出现了激活问题,症状是什么
  2. 为什么会发生 —— 追溯垫片中导致该结果的具体决策路径
  3. 行为的控制因素 —— 确定决定结果的具体输入(配置文件是否存在、错误模式、CLSID注册、版本限制状态)
  4. 发生了什么变化(如适用)—— 如果用户反馈行为发生了变化,找出可能变化的输入(父进程的错误模式、配置文件、CLSID注册、已安装的运行时)

Common Scenarios

常见场景

Unexpected FOD Dialogs

意外的FOD对话框

Pattern:
DllGetClassObject
IsCapped: 1
→ no config file →
(null)
SEM_FAILCRITICALERRORS: 0
→ FOD launched
Root cause: A native EXE is doing COM activation of a CLSID registered under mscoree.dll. This takes the legacy codepath, which is capped at v2.0. With no config file (and no
useLegacyV2RuntimeActivationPolicy
), v4 is invisible to this codepath. On a machine without .NET 3.5, there are no runtimes visible, and with
SEM_FAILCRITICALERRORS
not set, the FOD dialog fires.
Key question: Why did
SEM_FAILCRITICALERRORS
change? It's inherited from the parent. Different launch methods (script vs. direct invocation, different build systems) produce different error modes. The underlying capped-legacy-bind-on-v4-only-machine failure is always there — it's just that
SEM_FAILCRITICALERRORS
controls whether it manifests as a visible dialog or a silent failure.
模式:
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
发生了变化?它是从父进程继承的。不同的启动方式(脚本 vs 直接调用、不同的构建系统)会产生不同的错误模式。底层的“仅v4机器上受限制的旧版绑定失败”问题一直存在 —— 只是
SEM_FAILCRITICALERRORS
控制了它是表现为可见对话框还是静默失败。

Wrong Runtime Selected

选择了错误的运行时

Pattern:
supportedRuntime
entries in config list multiple versions; the shim picks the first one that's installed. If v2.0 is listed first and .NET 3.5 is installed, v2.0 wins even though v4.0 is also available.
Key insight: Config
<supportedRuntime>
entries are evaluated in order. First installed match wins.
模式: 配置中的
supportedRuntime
条目列出了多个版本;垫片会选择第一个已安装的版本。如果v2.0排在第一位且已安装.NET 3.5,即使v4.0也可用,也会选择v2.0。
关键要点: 配置中的
<supportedRuntime>
条目按顺序评估,第一个匹配的已安装版本会被选中。

Both v2 and v4 Loaded

同时加载v2和v4

Pattern: Multiple activation sequences in the same process log — one binds v4, another binds v2 (or vice versa). Side-by-side loading of CLR v2 and v4 in the same process IS supported but can cause issues with shared state.
Key insight: Look for separate
Decided on runtime
lines with different versions in the same log file.
模式: 同一个进程日志中有多个激活序列 —— 一个绑定了v4,另一个绑定了v2(反之亦然)。同一进程中并行加载CLR v2和v4是支持的,但可能会导致共享状态相关的问题。
关键要点: 在同一个日志文件中查找不同版本的
Decided on runtime
行。

Legacy Runtime Already Bound

旧版运行时已绑定

Pattern: A legacy codepath succeeds early in the process (e.g.,
CorBindToRuntimeEx
with an explicit v4 version, or config with
useLegacyV2RuntimeActivationPolicy
). This sets the legacy runtime to v4.0. All subsequent legacy activations — including capped COM activations that would otherwise fail — silently succeed by reusing the already-bound legacy runtime.
Key insight: The ORDER of activations within a process matters. If v4.0 is bound as the legacy runtime first, capped COM activations work. If the capped COM activation happens first (before any legacy runtime is bound), it fails. This means behavior can depend on which component activates first — a race condition in concurrent code can change the outcome.
模式: 进程早期旧版代码路径执行成功(例如带明确v4版本的
CorBindToRuntimeEx
调用,或者配置了
useLegacyV2RuntimeActivationPolicy
),这会将旧版运行时设置为v4.0。所有后续的旧版激活 —— 包括原本会失败的受限制COM激活 —— 都会复用已绑定的旧版运行时,静默成功。
关键要点: 进程内激活的顺序很重要。如果首先将v4.0绑定为旧版运行时,受限制的COM激活就能正常工作。如果受限制的COM激活先发生(在绑定任何旧版运行时之前),就会失败。这意味着行为取决于哪个组件先激活 —— 并发代码中的竞态条件可能会改变结果。

Common Pitfalls

常见误区

PitfallCorrect Approach
Assuming
IsCapped: 1
means v4.0 can never load
Capping only restricts roll-forward enumeration. v4.0 can still be loaded if: a specific version string is passed explicitly, config has
useLegacyV2RuntimeActivationPolicy="true"
with
<supportedRuntime version="v4.0"/>
, or the legacy runtime is already bound to v4+.
Thinking capping is broken or a bugCapping is intentional — it makes v4 installation non-impactful. On a v4-only machine, legacy codepaths correctly see no runtimes. This is working as designed.
Assuming FOD is controlled per-process
SEM_FAILCRITICALERRORS
is inherited from the parent process. A change in the parent (build system, script, shell) changes behavior for all children.
Looking only at the first activation in a logA single log can contain multiple independent activation sequences. The problematic one is often a secondary COM activation, not the initial CLR load.
Assuming a missing config file is benignFor native EXEs doing COM activation with legacy/capped bind, the config file (with
useLegacyV2RuntimeActivationPolicy
) is the primary way to make legacy codepaths see v4.0. No config = capped = v4 invisible.
Adding
<supportedRuntime>
without
useLegacyV2RuntimeActivationPolicy
Without
useLegacyV2RuntimeActivationPolicy="true"
, rolling forward to v4 via config works for the primary EXE load, but legacy codepaths (COM activation, P/Invoke to mscoree.h APIs) remain capped at v2.0. Both are needed for legacy codepaths.
Setting
useLegacyV2RuntimeActivationPolicy
without understanding the trade-off
This attribute turns off in-proc SxS — it locks pre-v4 runtimes out of the process. This is usually fine for build tools but should be considered for apps that need to host both v2 and v4.
误区正确做法
认为
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激活、P/Invoke调用mscoree.h API)仍然被限制为v2.0。旧代码路径需要同时配置两者。
设置
useLegacyV2RuntimeActivationPolicy
时不了解权衡
这个属性会关闭进程内SxS —— 禁止v4之前的运行时加载到进程中。这对于构建工具通常没问题,但对于需要同时托管v2和v4的应用需要谨慎考虑。

Validation

验证

Before delivering a diagnosis, verify:
  • All log files with errors or FOD triggers were analyzed (not just the first one)
  • The entry point for each problematic activation was identified
  • The capping and legacy bind state was noted for each activation sequence
  • Config file presence/absence was checked
  • SEM_FAILCRITICALERRORS state was noted for FOD-related issues
  • Multiple activations within a single log were individually traced
  • The diagnosis explains the specific decision path, not just the outcome
在输出诊断结果前,确认以下内容:
  • 所有包含错误或FOD触发的日志文件都已分析(不只是第一个)
  • 每个有问题的激活的入口点都已识别
  • 每个激活序列的版本限制和旧版绑定状态都已记录
  • 已检查配置文件是否存在
  • FOD相关问题已记录
    SEM_FAILCRITICALERRORS
    状态
  • 单个日志中的多次激活都已单独追踪
  • 诊断解释了具体的决策路径,而不只是结果",