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. 参考文档

相关推荐
IT小白杨2 天前
2026年短视频平台风控技术解析:设备指纹体系、行为建模与环境隔离的边界在哪里
chrome·经验分享·架构·音视频·安全架构·指纹浏览器
Ai拆代码的曹操2 天前
Nginx upstream keepalive 连接池中毒排查实录
chrome·nginx·php
IT小白杨2 天前
防关联用指纹浏览器还是虚拟机好?2026年技术架构深度对比与选型决策
chrome·经验分享·microsoft·架构·安全架构·指纹浏览器
不可能掉发4 天前
Env Guard:让浏览器一眼分清生产、测试和开发环境
前端·javascript·chrome·测试工具·html·开源软件·个人开发
小龙飞刀4 天前
Chrome插件自动化测试:单元测试、集成测试
chrome·单元测试·集成测试
东方护航数据恢复(深圳)6 天前
服务器硬盘黄灯/红灯故障排查与处理命令全指南_东方护航数据恢复深圳店
运维·服务器·chrome
赵广陆6 天前
FastAPI零基础完整实战
前端·chrome·fastapi
Nuanyt7 天前
Linux系统的 Shell 常见指令和常见知识点(以bash为主)
linux·运维·chrome·bash
lpfasd1238 天前
MediaCrawler 项目深度分析
chrome·python·chrome devtools
水瓶夜之9 天前
.NET 中的新增功能系列文章——.NET SDK中的新增功能
前端·chrome·.net