Chrome V49 SxS 加载问题分析与修复

Chrome V49 SxS 加载问题分析与修复

概述

本文详细记录了 Chrome V49(49.0.2623.112)在 ReactOS 上因 Side-by-Side (SxS) 程序集机制不完善导致加载失败的问题,从根因分析到代码修复的完整过程。

运行环境

项目 说明
操作系统 ReactOS (Debug 构建)
虚拟机 VirtualBox (VCI 镜像: ReactOS-Test.vdi)
目标程序 Chrome V49 (49.0.2623.112)
目录结构 C:\Chrome_V49\ --- Chrome.exe、Chrome.exe.manifest
C:\Chrome_V49\49.0.2623.112\ --- chrome.dll、chrome_elf.dll、其他 DLL

问题现象

Chrome.exe 启动后立即崩溃,调试日志显示:

复制代码
LoadLibraryExW(chrome.dll) failing with status c0000135

STATUS_DLL_NOT_FOUND --- Chrome 无法加载其核心模块 chrome.dll

随后发生 NULL 指针解引用(c0000005,地址 0x00000000),进程崩溃。

第一阶段:SxS 清单分析

1.1 提取嵌入清单

使用 Windows API(LoadLibraryExW + FindResourceExW)从 Chrome.exe 中提取 RT_MANIFEST 资源。

Chrome.exe 嵌入清单(资源 ID 1):

xml 复制代码
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <!-- 依赖 1: Common Controls 6.0(有 publicKeyToken,走 WinSxS) -->
  <dependency>
    <dependentAssembly>
      <assemblyIdentity type="Win32" name="Microsoft.Windows.Common-Controls"
                        version="6.0.0.0" processorArchitecture="*"
                        publicKeyToken="6595b64144ccf1df" language="*"/>
    </dependentAssembly>
  </dependency>
  <!-- 依赖 2: 49.0.2623.112 私有程序集(无 publicKeyToken,走私有路径) -->
  <dependency>
    <dependentAssembly>
      <assemblyIdentity type="win32" name="49.0.2623.112"
                        version="49.0.2623.112" language="*"/>
    </dependentAssembly>
  </dependency>
  <trustInfo>...</trustInfo>
  <compatibility>...</compatibility>
</assembly>

外部 Chrome.exe.manifest

与嵌入清单类似,但缺少 49.0.2623.112 的依赖声明。

49.0.2623.112.manifest(位于子目录中):

xml 复制代码
<assembly manifestVersion="1.0">
  <assemblyIdentity name='49.0.2623.112' version='49.0.2623.112' type='win32'/>
  <file name='chrome_elf.dll'/>
  <file name='kasko.dll'/>
</assembly>

关键发现:chrome.dll 不在 SxS 清单中。 只有 chrome_elf.dllkasko.dll 在 SxS 的 DLL 重定向清单中。这意味着 SxS 的 DLL 重定向机制对 chrome.dll 无效。

1.2 其他模块清单

模块 资源 ID 依赖
chrome.dll RT_MANIFEST #2 Common Controls 6.0
chrome_child.dll RT_MANIFEST #2 仅有 trustInfo

1.3 SxS 安全项验证

分析 ReactOS 的 SxS 实现代码 sdk/lib/rtl/actctx.c

潜在问题 验证结果
type 属性大小写敏感 安全 --- 解析器原样存储不做比较
缺少 publicKeyToken 安全 --- 所有属性都是可选的,lookup_winsxs 正确返回 STATUS_NO_SUCH_FILE
私有程序集路径搜索 安全 --- lookup_assembly 正确搜索 appdir\name\name.manifest
DLL 重定向路径解析 安全 --- find_actctx_dll 正确处理非 WinSxS 路径

第二阶段:调试日志分析

2.1 首次运行日志

复制代码
LdrpInitializeProcessCompat: Found guid for winver 0x600 in manifest

→ 激活上下文创建成功,清单解析正常,兼容性垫片设置 Vista 模式。

复制代码
LoadLibraryExW(chrome.dll) failing with status c0000135

搜索路径:

复制代码
C:\Chrome_V49;C:\ReactOS\System32;C:\ReactOS\system;C:\ReactOS;.;
C:\ReactOS\bin;C:\ReactOS\System32;C:\ReactOS;C:\ReactOS\System32\Wbem

49.0.2623.112\ 子目录不在搜索路径中!

2.2 关键发现:SetDllDirectory 未被调用

复制代码
LdrpDllDirectory.Length = 0, Buffer = 'NULL'

Chrome 在 ReactOS 上没有调用 SetDllDirectory ,导致版本子目录未被加入 DLL 搜索路径。而在 Windows 7 上,Chrome 会通过此机制将 49.0.2623.112\ 添加到搜索路径。

2.3 Chrome 的两次尝试

尝试 调用 结果
1 LoadLibraryExW("chrome.dll") ❌ Length=0,找不到
2 LoadLibraryExW("49.0.2623.112/chrome.dll") ✅ Length=76,找到
3 LoadLibraryExW("chrome.dll") ❌ 再次失败,随后崩溃

第二次使用的相对路径 49.0.2623.112/chrome.dll 通过 RtlDosSearchPath_U 相对于 CWD 解析成功,但 Chrome 继续尝试第三次裸名调用并最终崩溃。

第三阶段:根因分析

3.1 搜索路径计算代码

BaseComputeProcessDllPathdll/win32/kernel32/client/path.c)构建搜索路径:

SetDllDirectory 已被调用BaseDllDirectory.Buffer != NULL):

复制代码
AppDir → SetDllDirectory → System32 → Windows → PATH

SetDllDirectory 未被调用

复制代码
AppDir → System32 → Windows → CWD → PATH  (安全模式)
AppDir → CWD → System32 → Windows → PATH  (非安全模式)

Chrome 的情况属于后者,搜索路径只有 C:\Chrome_V49,不包含子目录。

3.2 LdrpBuildSearchPath 双重路径

LdrpBuildSearchPathdll/ntdll/ldr/ldrutils.c)会在 kernel32 传来的搜索路径前额外添加 LdrpDllDirectory。但由于 SetDllDirectory 未被调用,LdrpDllDirectory.Length = 0,不产生任何效果。

3.3 核心结论

ReactOS 缺少 Windows 7 的行为:当进程激活上下文有私有程序集依赖时,程序集子目录应被隐式添加到 DLL 搜索路径。

在 Windows 7 上,LoadLibrary("chrome.dll") 的搜索顺序包括 DLL 搜索顺序

  1. DLL 重定向
  2. API Sets
  3. SxS 清单重定向 ← ReactOS 缺少此步骤的目录扩展
  4. 已加载模块列表
  5. Known DLLs
  6. 文件系统搜索

第四阶段:修复实现

4.1 修复策略

LdrpResolveDllName(ntdll)中添加 Windows 7 风格的 fallback:当标准 DLL 搜索失败时,查询激活上下文中的私有程序集清单路径,提取其目录并加入搜索路径重新搜索。

4.2 修改代码

文件: dll/ntdll/ldr/ldrutils.c

函数: LdrpResolveDllName

c 复制代码
/*
 * Windows 7 behavior: if the DLL was not found in the standard search path,
 * try searching in private assembly subdirectories of the application directory.
 * The activation context stores assembly manifest paths; we extract their
 * directories and retry the search with those directories prepended.
 */
if (DllName && !wcschr(DllName, L'\\') && !wcschr(DllName, L'/'))
{
    NTSTATUS ActCtxStatus;
    SIZE_T RetLen;
    ACTIVATION_CONTEXT_DETAILED_INFORMATION DetInfo;
    DWORD AsmIdx;

    /* 查询激活上下文详细信息,获取程序集数量 */
    RtlZeroMemory(&DetInfo, sizeof(DetInfo));
    ActCtxStatus = RtlQueryInformationActivationContext(
        0, NULL, NULL,
        ActivationContextDetailedInformation,
        &DetInfo, sizeof(DetInfo), &RetLen);

    /* 处理缓冲区过小的情况 */
    if (ActCtxStatus == STATUS_BUFFER_TOO_SMALL && RetLen > sizeof(DetInfo))
    {
        PACTIVATION_CONTEXT_DETAILED_INFORMATION pDet;
        pDet = RtlAllocateHeap(LdrpHeap, 0, RetLen);
        if (pDet)
        {
            RtlZeroMemory(pDet, RetLen);
            ActCtxStatus = RtlQueryInformationActivationContext(
                0, NULL, NULL,
                ActivationContextDetailedInformation,
                pDet, (ULONG)RetLen, &RetLen);
            if (NT_SUCCESS(ActCtxStatus))
                DetInfo = *pDet;
            RtlFreeHeap(LdrpHeap, 0, pDet);
        }
    }

    if (NT_SUCCESS(ActCtxStatus) && DetInfo.ulAssemblyCount > 1)
    {
        /* 跳过索引 0(主程序),检查依赖程序集 */
        for (AsmIdx = 1; AsmIdx <= DetInfo.ulAssemblyCount; AsmIdx++)
        {
            BYTE AsmBuf[4096];
            PACTIVATION_CONTEXT_ASSEMBLY_DETAILED_INFORMATION AsmInfo = (PVOID)AsmBuf;
            WCHAR *LastSlash, NewSearchPath[MAX_PATH];

            RtlZeroMemory(AsmBuf, sizeof(AsmBuf));
            ActCtxStatus = RtlQueryInformationActivationContext(
                0, NULL, &AsmIdx,
                AssemblyDetailedInformationInActivationContext,
                AsmInfo, sizeof(AsmBuf), &RetLen);
            if (!NT_SUCCESS(ActCtxStatus)) continue;
            if (!AsmInfo->lpAssemblyManifestPath) continue;

            /* 从清单路径中提取目录部分 */
            LastSlash = wcsrchr(AsmInfo->lpAssemblyManifestPath, L'\\');
            if (!LastSlash) continue;

            /* 构建: "manifest_dir;original_search_path" */
            RtlCopyMemory(NewSearchPath, AsmInfo->lpAssemblyManifestPath,
                          (LastSlash - AsmInfo->lpAssemblyManifestPath + 1) * sizeof(WCHAR));
            NewSearchPath[LastSlash - AsmInfo->lpAssemblyManifestPath + 1] = L'\0';
            wcscat(NewSearchPath, L";");
            wcscat(NewSearchPath, DefaultPath);

            /* 用增强后的搜索路径重试 */
            Length = RtlDosSearchPath_U(NewSearchPath, DllName, NULL,
                                        BufSize, FullDllName->Buffer,
                                        &BaseDllName->Buffer);
            if (Length && Length <= BufSize)
            {
                DPRINT1("LDR: Found %ws in assembly dir %ws\n",
                        DllName, NewSearchPath);
                break;
            }
        }
    }
}

4.3 关键 API

API 用途 所在文件
RtlQueryInformationActivationContext 查询激活上下文信息 sdk/lib/rtl/actctx.c
ActivationContextDetailedInformation 获取程序集数量和 app 目录 sdk/include/xdk/winnt_old.h
AssemblyDetailedInformationInActivationContext 获取单个程序集的清单路径 同上
RtlDosSearchPath_U 按搜索路径搜索文件 sdk/lib/rtl/path.c

4.4 数据结构

c 复制代码
typedef struct _ACTIVATION_CONTEXT_DETAILED_INFORMATION {
    DWORD dwFlags;
    DWORD ulFormatVersion;
    DWORD ulAssemblyCount;           // 程序集总数(含主程序)
    DWORD ulRootManifestPathType;
    DWORD ulRootManifestPathChars;
    // ... 更多字段
    PCWSTR lpRootManifestPath;
    PCWSTR lpAppDirPath;
} ACTIVATION_CONTEXT_DETAILED_INFORMATION;

typedef struct _ACTIVATION_CONTEXT_ASSEMBLY_DETAILED_INFORMATION {
    DWORD ulFlags;
    DWORD ulEncodedAssemblyIdentityLength;
    DWORD ulManifestPathType;
    DWORD ulManifestPathLength;
    // ...
    PCWSTR lpAssemblyEncodedAssemblyIdentity;
    PCWSTR lpAssemblyManifestPath;   // 清单文件的完整路径
    PCWSTR lpAssemblyDirectoryName;  // WinSxS 风格目录名
    DWORD ulFileCount;
} ACTIVATION_CONTEXT_ASSEMBLY_DETAILED_INFORMATION;

第五阶段:测试结果

5.1 修复后日志

复制代码
LDR: Found chrome.dll in assembly dir C:\Chrome_V49\49.0.2623.112\;

修复成功! chrome.dll 通过 SxS 程序集目录回退机制被正确找到。

5.2 修复后的问题

chrome.dll 加载成功后,Chrome 继续执行并触发了另一个独立的问题:

复制代码
Assertion failed at ../ntoskrnl/fsrtl/filelock.c(1190):
  Result == NT_SUCCESS(IoStatusBlock.Status)

调用链:

复制代码
Chrome.exe → NtLockFile → FatCommonLockControl → FsRtlProcessFileLock

这是 ReactOS FAT32 文件系统驱动(fastfat.sys) 的文件锁(NtLockFile)实现存在缺陷,属于独立的内核层问题,不影响 SxS 修复本身的有效性。

附录

A. 涉及的 ReactOS 源代码文件

文件 修改状态 说明
dll/ntdll/ldr/ldrutils.c ✅ 已修改 添加 SxS 程序集目录回退搜索
dll/win32/kernel32/client/loader.c ✅ 已修改 添加调试日志(可移除)
sdk/lib/rtl/actctx.c ❌ 未修改 SxS 基础设施(分析用)
dll/ntdll/rtl/libsupp.c ❌ 未修改 现有 find_actctx_dll 函数(分析用)
dll/win32/kernel32/client/path.c ❌ 未修改 BaseComputeProcessDllPath(分析用)

B. SxS 清单提取脚本

使用 Python + ctypes 调用 Windows API FindResourceExWLoadResource 从 PE 文件中提取 RT_MANIFEST 资源。

C. 参考文档

相关推荐
caimouse21 小时前
mm学习笔记_04:VAD树算法与地址空间分配
笔记·学习·算法·reactos
caimouse1 天前
WINLOGON 初始化与作用分析
reactos
caimouse2 天前
KPRCB 结构分析与调度流程
reactos
hj2862512 天前
Shell 脚本完整学习笔记
chrome·笔记·学习
caimouse2 天前
ReactOS.exe 安装程序分析
reactos
caimouse3 天前
Phase1Initialization 函数分析
单片机·嵌入式硬件·reactos
caimouse4 天前
ReactOS bootcd.iso 引导到桌面显示过程分析
reactos
caimouse5 天前
ReactOS BootCD 光盘布局完整深度分析
单片机·reactos
caimouse5 天前
KiSwitchToBootStack 函数分析
reactos