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.dll 和 kasko.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 搜索路径计算代码
BaseComputeProcessDllPath(dll/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 双重路径
LdrpBuildSearchPath(dll/ntdll/ldr/ldrutils.c)会在 kernel32 传来的搜索路径前额外添加 LdrpDllDirectory。但由于 SetDllDirectory 未被调用,LdrpDllDirectory.Length = 0,不产生任何效果。
3.3 核心结论
ReactOS 缺少 Windows 7 的行为:当进程激活上下文有私有程序集依赖时,程序集子目录应被隐式添加到 DLL 搜索路径。
在 Windows 7 上,LoadLibrary("chrome.dll") 的搜索顺序包括 DLL 搜索顺序:
- DLL 重定向
- API Sets
- SxS 清单重定向 ← ReactOS 缺少此步骤的目录扩展
- 已加载模块列表
- Known DLLs
- 文件系统搜索
第四阶段:修复实现
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 FindResourceExW、LoadResource 从 PE 文件中提取 RT_MANIFEST 资源。
C. 参考文档
- Windows DLL 搜索顺序
- SxS 程序集搜索顺序
- 私有程序集
- ReactOS CORE-10843 --- SxS 激活上下文相关 bug