65.9K
CodeProject 正在变化。 阅读更多。
Home

x64 / IA64 上的注册表重定向器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (21投票s)

2006年5月23日

11分钟阅读

viewsIcon

146865

downloadIcon

790

本文将更深入地介绍 x64 / IA64 系统上的注册表重定向器

Registry with Wow6432Node

引言

在 x64/IA64 操作系统中添加了 注册表重定向器,以支持新 64 位平台上的 win32 (x86) 应用程序。它的设计目的是在不修改应用程序的情况下,平滑地支持“旧式”x86 应用程序。由于 x86 和 x64/IA64 是完全不同的环境,因此他们决定为旧 (x86) 和新 (x64/IA64) 应用程序创建两个不同的注册表视图。但完全独立的注册表视图并不总是应用程序想要的。例如,COM 服务器 (OutProc) 的注册表项应该在两个环境中都可见。为了解决这些问题,他们引入了“注册表重定向器”。该重定向器由以下三个部分组成:

本文旨在为您提供注册表重定向器及其发生的副作用的概述和深入知识。

除了注册表重定向器,还有一个 文件系统重定向器(本文不包含此内容)。

两个注册表视图

注册表的 32 位视图位于 64 位视图的特殊子节点中。此节点称为 Wow6432Node。以下节点包含 32 位视图(及其所有子节点):

  • HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Wow6432Node
  • HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node
  • HKEY_USERS\*\SOFTWARE\Classes\Wow6432Node

完整的注册表重定向在用户模式下完成。键名在用户模式下转换为相应的“内核模式”名称。因此,一般来说,您可以说:Windows x64 中只有一个注册表,但重定向器有时会转换传递的名称。

示例

以下是注册表重定向器的一个小示例:
如果从 64 位应用程序访问键 HKEY_LOCAL_MACHINE\SOFTWARE\MyApp\Settings,您实际上打开的是该节点。如果从 32 位应用程序打开此节点,您将被透明地重定向到 HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\MyApp\Setting

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#pragma comment(lib, "Advapi32.lib")
int _tmain()
{
  LONG lRet;
  HKEY hKey;
  // Open the key in the default-view
  lRet = RegCreateKeyEx(HKEY_LOCAL_MACHINE, 

_T("SOFTWARE\\MyApp\\MySettings"),
    0, NULL, 0, KEY_ALL_ACCESS, NULL, &hKey, NULL);
  if(lRet == ERROR_SUCCESS)
  {
#if _M_IX86
    TCHAR szValue[] = _T("x86");
    RegSetValueEx(hKey, _T("AppType"), 0, REG_SZ,
      (const BYTE*) szValue,
      (DWORD) (_tcslen(szValue)+sizeof(TCHAR))*sizeof(TCHAR));
#else
    TCHAR szValue[] = _T("x64 / IA64");
    RegSetValueEx(hKey, _T("AppType"), 0, REG_SZ,
      (const BYTE*) szValue,
      (DWORD) (_tcslen(szValue)+sizeof(TCHAR))*sizeof(TCHAR));
#endif
    RegCloseKey(hKey);
  }
  return 0;
}

执行此示例作为 32 位“和”64 位应用程序后,您将创建两个单独的注册表项。一个包含“x86”值,另一个包含“x64 / IA64”值。

共享注册表项

有几个硬编码的注册表项将由 x86 和 x64/IA64 应用程序共享。这些项仅存在于注册表的 64 位视图中,但您可以在 x86 应用程序中打开它们。这些项的列表可通过 KB 文章 896459此处 获得(顺便说一句,KB 文章中有一个拼写错误的条目)。仅为完整起见,这是当前列表:

  • HKEY_LOCAL_MACHINE\Software\Microsoft\SystemCertificates
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\Services
  • HKEY_LOCAL_MACHINE\Software\Classes\HCP
  • HKEY_LOCAL_MACHINE\Software\Microsoft\EnterpriseCertificates
  • HKEY_LOCAL_MACHINE\Software\Microsoft\MSMQ
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\NetworkCards
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\ProfileList
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Perflib
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Print
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Ports
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Control Panel\Cursors\Schemes
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Telephony\Loc ations
  • HKEY_LOCAL_MACHINE\Software\Policies
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Group Policy
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Setup\OC Mmanager
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Software\Microsoft\Shared Tools\MSInfo
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Setup
  • HKEY_LOCAL_MACHINE\Software\Microsoft\CTF\TIP
  • HKEY_LOCAL_MACHINE\Software\Microsoft\CTF\SystemShared
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Fonts
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontSubstitutes
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontDpi
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontMapper
  • HKEY_LOCAL_MACHINE\Software\Microsoft\RAS
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Driver Signing
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Non-Driver Signing
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\Calais\Current
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\Calais\Readers
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Time Zones
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Transaction Server
  • HKEY_LOCAL_MACHINE\Software\Microsoft\DFS
  • HKEY_LOCAL_MACHINE\Software\Microsoft\TermServLicensing

内部

您还可以通过使用 windbg 在 ADVAPI32!ExemptRedirectedKey 中找到此列表。

每次在 x86 应用程序中打开一个项时(通过 Wow64RegOpenKeyExWow64pRegCreateKeyEx 完成),都会在 ADVAPI32!IsExemptRedirectedKey 中检查该项是否应在默认视图或 x64 视图中打开。

共享注册表项的副作用

由于这些项仅存在于注册表的 64 位视图中,因此目前无法通过枚举 (RegEnumKeyEx) 在 x86 应用程序中找到这些项。您可以通过 %systemroot%\SysWOW64\regedit.exe -m(-m 允许 regedit.exe 运行多个实例)打开 32 位版本的 *regedit.exe* 并尝试查找这些项来验证这一点。

请参阅我关于此行为的 博客文章

您可能会认为您可以使用 KEY_WOW64_64KEY 标志从 x86 应用程序枚举整个 64 位注册表。但由于其他“功能”,这也无法实现,请参阅

由于这些项的处理方式不同(通过 IsExemptRedirectedKey),因此使用 KEY_WOW64_64KEYKEY_WOW64_32KEY 打开这些项没有意义。这些标志将被忽略。

注册表反射

注册表反射是注册表重定向器的一个非常特殊的组成部分。与共享注册表项一样,也有一组硬编码的项将被反射。反射意味着单个项存在两个物理副本(在 x86 视图和 x64/IA64 视图中),但当内容更改并关闭该项时,这些项将被“反射”。由于反射仅发生在关闭项时,因此最后关闭的将生效。以下是反射项的列表:

  • HKEY_LOCAL_MACHINE\Software\Classes
  • HKEY_LOCAL_MACHINE\Software\COM3
  • HKEY_LOCAL_MACHINE\Software\EventSystem
  • HKEY_LOCAL_MACHINE\Software\Ole
  • HKEY_LOCAL_MACHINE\Software\Rpc
  • HKEY_USERS\*\Software\Classes
  • HKEY_USERS\*_Classes
    注意:* 表示匹配所有用户安全标识符 (SID)。

对 HKEY_LOCAL_MACHINE\Software\Classes\CLSID 的特殊处理

此节点包含所有 COM 组件。COM 组件的激活可以通过两种方式完成:进程内或进程外。这在注册表中 COM 组件的 CLSID 下进行指定。要么有一个子项 InProcServer32,要么有一个 LocalServer32。注册表反射器现在是“智能”的,并且只反射与进程外(“LocalServer32”)服务器绑定的项。只有进程外反射项才有意义,因为进程内 COM 服务器可以是 32 位或 64 位。无法将 64 位 DLL 加载到 32 位进程中,反之亦然。因此,如果您注册一个进程内组件,它将绑定到 DLL 的架构(32 位或 64 位)。因此,受影响的注册表项不应在另一个环境中反射。

为特定项禁用/启用反射

默认情况下,为硬编码反射列表中的所有(子项)启用反射。使用新的 API RegDisableReflection KeyRegEnableReflectionK ey,可以更改这些特定项的反射状态。当然,可以使用 RegQueryReflectionKe y 函数查询项的当前状态。但您应该注意,输出参数名为 bIsReflectionDisabled!这意味着您必须跳出常规思维。为了简单起见,我为您提供了一个列表:

  • bIsReflectionDisabled == FALSE => 反射已启用!
  • bIsReflectionDisabled != FALSE => 反射已禁用!

限制

看起来此函数可用于为任何特定项启用反射。但这不正确!默认情况下,所有硬编码在反射列表中的项都会被反射!您无法将项添加到此硬编码列表中!您唯一能做的就是禁用通常会被反射的项的反射,因为它在硬编码的反射列表中。

函数 RegDisableReflectionKey 只是为指定项设置一个特殊的 TagRegEnableReflectionKey 会移除此 Tag。因此,这些函数将在“所有”项上成功,但只会影响硬编码反射列表中的项。

您还应该注意,此函数不会影响子项!仅影响指定的项。

示例

在以下示例中,我禁用了一个永不会被反射的项的反射(仅为显示函数即使没有效果也会成功,因为它永远不会被反射)。

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#pragma comment(lib, "Advapi32.lib")

void ShowReflectionState(HKEY hKey)
{
  BOOL bReflectedDisabled = TRUE;
  LONG lResult = RegQueryReflectionKey(hKey, &bReflectedDisabled);
  if (lResult == ERROR_SUCCESS)
  {
    if (bReflectedDisabled != FALSE)
      _tprintf(_T("DISABLED!\n"));
    else
      _tprintf(_T("ENABLED (means: Tag is not set)!\n"));
  }
  else
    _tprintf(_T("Error (RegQueryReflectionKey): 0x%8.8x\n"), lResult);
}
int _tmain()
{
  HKEY hKey;
  if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\MyApp"),
    0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS)
  {
    _tprintf(_T("Current reflection state: "));
    ShowReflectionState(hKey);

    _tprintf(_T("Disabling reflection: "));
    LONG lRes = RegDisableReflectionKey(hKey);
    if (lRes != ERROR_SUCCESS)
      _tprintf(_T("Error: 0x%8.8x\n"), lRes);
    else
      _tprintf(_T("Ok\n"));
    _tprintf(_T("New reflection state: "));
    ShowReflectionState(hKey);

    _tprintf(_T("Enabling reflection: "));
    lRes = RegEnableReflectionKey(hKey);
    if (lRes != ERROR_SUCCESS)
      _tprintf(_T("Error: 0x%8.8x\n"), lRes);
    else
      _tprintf(_T("Ok\n"));
    _tprintf(_T("New reflection state: "));
    ShowReflectionState(hKey);

    RegCloseKey(hKey);
  }
  return 0;
}

使用此示例,如果您创建了一个新的 MyApp 节点,您将得到以下输出:

Current reflection state: ENABLED (means: Tag is not set)!
Disabling reflection: Ok
New reflection state: DISABLED!
Enabling reflection: Ok
New reflection state: ENABLED (means: Tag is not set)!

内部

总的来说,反射仅在 x64/IA64 模式下进行。因此,所有函数都在 ADVAPI32.DLL 的 x64 版本中。反射仅在用户模式下进行。没有内核模式组件需要了解反射,因为每个内核模式组件都是 x64/IA64,因此不需要反射。

更改值时会发生什么

如果值被更改,将发生以下情况:

  1. 测试反射是否激活 (BOOL ADVAPI32!bReflectorStatusOn)
    这很有趣……似乎反射可以通过某些方式禁用,或者并非总是启用(可能在系统启动时)。
  2. 检查注册表项是否在“ReflectionList”中 (ADVAPI32!IsOnReflectionListByToken)
    此列表是硬编码并存储在 x64-"ADVAPI32!ReflectList" 中的。除了已列出的节点外,它还包含以下节点:
    • \REGISTRY\MACHINE\SOFTWARE\Wow6432Node\Microsoft\ Windows\CurrentVersion\Run
    • \REGISTRY\MACHINE\SOFTWARE\Wow6432Node\Microsoft\ Windows\CurrentVersion\RunOnce
    • \REGISTRY\MACHINE\SOFTWARE\Wow6432Node\Microsoft\ Windows\CurrentVersion\RunOnceEx

    但我真的看不到这些项被反射……也许有人能让它工作(似乎缺少不带 Wow6432Node 的相应项……)

  • 如果值已更改,“脏”标志将被设置。
  • 关闭 hKey 时,将调用函数 Wow64RegCloseKey,如果设置了脏标志,它将执行 NtSyncKey。在 x64 应用程序中,函数 Wow64RegCloseKey 直接在 RegCloseKey 内部调用;在 x86 应用程序中,该函数通过 WOW64 中的 CpuSimulation 间接调用。

脏项的句柄列表

包含“脏”标志的打开注册表项的句柄列表限制为 512 个条目?因此,如果您打开超过 512 个注册表项(并保持打开状态),并且所有项都需要反射,则由于此限制,最新的项将不会被反射。当然,您永远不应该同时打开 512 个注册表项,所以这应该不是实际的限制。

使用 REG_EXPAND_SZ 写入时值的透明更改

从 x86 应用程序(在 WOW64 下运行)向注册表写入 REG_EXPAND_SZ 字符串有些特殊。因为使用 REG_EXPAND_SZ,您可以在字符串中嵌入环境变量。并且可能会有一个更改,某些 x64 应用程序可能会读取此字符串。因此,MS 决定在“即时”更改此类字符串的值,如果它们包含可能导致错误目录的环境变量。这些环境变量是:

  • %ProgramFiles%
  • %commonprogramfiles%

如果您写入此类字符串,它将被替换为相应的 x86 值(%ProgramFiles(x86)%, %commonprogramfiles(x86)%)。

#include <windows.h>
#include <tchar.h>
int _tmain()
{
  HKEY hKey;
  RegCreateKeyEx(HKEY_CURRENT_USER, _T("SOFTWARE\\TEST"), 0, NULL, 0,
    KEY_ALL_ACCESS, NULL, &hKey, NULL);

  LPCTSTR szTest1 = _T("%ProgramFiles%\\Test");
  RegSetValueEx(hKey, _T("Test1"), 0, REG_EXPAND_SZ, (LPBYTE) szTest1,
    (DWORD) (_tcslen(szTest1)+1)*sizeof(TCHAR));

  RegCloseKey(hKey);
  return 0;
}

但似乎所有 x64 操作系统都存在此功能的错误。因为它们目前是区分大小写地比较这些字符串!

访问备用注册表视图 (KEY_WOW64_64KEY 和 KEY_WOW64_32KEY)

有新的标志可用于访问 32 位或 64 位应用程序中的备用注册表视图。使用此标志,您可以强制 RegCreateKeyEx, RegDeleteKeyExRegOpenKeyEx 显式访问注册表的 64 位视图 (KEY_WOW64_64KEY) 或 32 位视图 (KEY_WOW64_32KEY),而与您进程的默认视图无关。当然,这仅在您访问将被重定向的某些项时才有意义。

此标志非常特殊,不与返回的句柄相关联!

如果您想删除一个项,并且您使用 KEY_WOW64_64KEY 打开了节点,那么您期望 RegDeleteKeyEx 函数将在 64 位视图上执行。但事实并非如此!它将在默认进程视图上执行!如果您想从 32 位应用程序删除 64 位项,您需要显式地将 KEY_WOW64_64KEY 标志传递给 RegDeleteKeyEx。您不需要使用 KEY_WOW64_64KEY 打开节点(如果节点在两个视图中都存在)。

这是一个简短的示例:

#include <windows.h>
#include <tchar.h>
#pragma comment(lib, "Advapi32.lib")
void _tmain()
{
  LONG lRet;
  HKEY hKey;
  // Open the key on the default-view
  lRet = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\MyTest"),
    0L, KEY_ALL_ACCESS, &hKey);
  if(lRet == ERROR_SUCCESS)
  {
    // delete it on the other view...
#if _M_IX86
    // The key will be deleted on the 64-bit view, even if the app is 32-bit
    // and the key was opend on the 32-bit view!
    lRet = RegDeleteKeyEx(hKey, _T("1"), KEY_WOW64_64KEY, 0);
#else
    // The key will be deleted on the 32-bit view, even if the app is 64-bit
    // and the key was opend on the 64-bit view!
    lRet = RegDeleteKeyEx(hKey, _T("1"), KEY_WOW64_32KEY, 0);
#endif
    RegCloseKey(hKey);
  }
}

另一种方式也是可能的:您可以使用 KEY_WOW64_xxKEY 打开该项,如果您在 RegDeleteKeyEx 上未指定此标志,则该项将在默认视图中被删除。

KEY_WOW64_64KEY 和特殊节点 Wow6432Node 的副作用

您不应该使用 KEY_WOW64_64KEY 打开以下项,因为它们将返回不带 Wow6432Node 的键节点。我不知道这是设计如此还是错误……

  • SOFTWARE\Classes\Wow6432Node
  • SOFTWARE\Wow6432Node

因此,如果您尝试使用指定的 KEY_WOW64_64KEY 枚举注册表,您将陷入无限循环!此外,如果您在 x86 应用程序中打开 Wow6432Node 的子项,例如 SOFTWARE\Wow6432Node\SubKey,则会忽略 Wow6432Node 部分。因此,如果您指定 KEY_WOW64_64KEY,将打开 x64 节点 SOFTWARE\SubKey。如果您使用默认视图,也将打开节点 SOFTWARE\SubKey(在相应的视图中!)。

您也不应该创建名称为 Wow6432Node 的自定义子节点,因为这在打开键时可能会造成混淆。例如:打开 SOFTWARE\Wow6432Node\SubKey\Wow6432Node 将在默认视图中(如果未传递特殊标志)打开 SOFTWARE 节点,或者如果传递了 KEY_WOW64_64KEY,则在 x64 视图中打开。

在 x86 应用程序中,似乎节点 Wow6432Node 会被从路径中删除,并且根据标志,将使用 x64 或 x86 注册表视图。
在 x64 应用程序中,如果您指定 KEY_WOW64_64KEY 标志,节点 Wow6432Node 将被删除。

在 x64 应用程序中,如果您尝试使用 KEY_WOW64_32KEY 打开一个项,并且路径包含一个名为 Wow6432Node 的节点,它总是返回根节点!至少我认为这是一个 bug。

Vista 中的更改

新 API

Windows Vista 中,他们引入了一个新的 API 函数来更改注册表重定向器对特殊项的行为。这些函数称为 RegSetKeyFlags 和 RegQueryKeyFlags。它有三个可能的标志可以设置或清除:

  • KEY_FLAG_DISABLE_REDIRECTION
    我尚未测试此标志;因此,我将仅引用原始文档:
    禁用使用此句柄的操作的注册表重定向。(默认情况下,WOW64 会将对在 64 位注册表视图中打开的句柄的操作重定向到 32 位注册表视图。)
  • KEY_FLAG_EXEMPT_REFLECTION
    此标志的效果与使用 RegDisableReflectionKey, RegEnableReflectionKeyRegQueryReflectionKey 相同。
  • KEY_FLAG_OWNERSHIP_REFLECTION
    我尚未测试此标志;因此,我将仅引用原始文档:
    如果项存在于备用注册表视图中且不是由调用者创建的,则使该项免于反射。

它提供了对重定向和反射的更多控制,但它也增加了一个更复杂的机制来确定设置这些标志时会发生什么。

内部

新 API 使用与当前可用 API(RegDisableReflectionKey, RegEnableReflectionKey)相同的机制来存储设置。设置是使用键标签存储的(通过 ADVAPI32!QueryKeyTagADVAPI32!UpdateKeyTag)。因此,使用 RegDisableReflectionKey 或带有 EY_FLAG_EXEMPT_REFLECTIONRegSetKeyFlags 具有相同的效果。设置存储在相同的标签中,因此您也可以使用 RegQueryKeyFlagsRegQueryReflectionKey 来查询反射状态。

共享注册表项

似乎 build 5365 在 ADVAPI32!ExemptRedirectedKey 共享项列表中存在一个 bug。项 HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Time Zones 拼写错误,末尾少了一个“s”(两份文档中关于共享项列表的混乱情况相同,请参见上文)。

链接

历史

  • 2006 年 5 月 23 日
    • 初始版本。
  • 2006 年 10 月 7 日
    • 添加了“使用 REG_EXPAND_SZ 写入时值的透明更改”
© . All rights reserved.