拥抱64位Windows

3180阅读 0评论2013-02-27 yjd333
分类:Windows平台

http://blog.csdn.net/xieqidong/article/details/2215704


还记得640K内存就足够的好日子吗?那记得是什么时候,它显得捉襟见肘了吗?这是一个日新月异的时代,我们发明了各种方法,以在有限的寻址空间内,映射更多的内存:首先,是“扩充内存”(EMM),起初它只是一张硬件“卡”,以64K或128K HASH(0x80b9d4)为界,切换64K的HASH(0x80b9d4)页到DOS寻址空间内;然后,我们又看到了“扩展内存”(XMS),它使保护模式映射到更多的64K区域成为了可能。而所有这些,都只是给了程序一个权宜之计,我们真正需要的是一个更大的寻址空间,这时,32位寻址就成了大众欢迎的、暂时的“止痛片”。

如果640K对任何人都足够,那么2GB(Windows实际上可寻址到4GB)对大多数应用程序来说都绰绰有余,但是,仍有一些程序要求越来越大的寻址空间,这样,人们又发明了寻址窗口扩展(Address Windowing Extension AWE)和实体地址延伸(Physical Address Extension PAE)等等映射技术。今天,64位处理器和两个新版本的64位Windows来到了我们身边,与16位到32位的变化不同的是,大多数今天我们使用的程序,都没有突破2GB的寻址空间,那么新处理器有什么用呢?我们何必在意它呢。

诚然,在可预见的未来,我们将继续编写32位程序,但不管怎么说,在这两年里,64位CPU进入桌面电脑已成了不争的事实。AMD Athlon64和Opteron处理器随处可见,而主板生产商也推出了相应的主板,价格上与同级别32位主板相近;Intel,一开始掉在了安腾处理器(Itanium)的钱眼里,为了赶上老对手,匆匆于2004年底发布了“32位扩展架构”的至强(Xeon)处理器,技术名称为IA32E或EM64T。由于Intel的安腾处理器缺乏向后兼容,现有的二进制代码运行效率不高,一如当年Win16到Win32的转变,导致OS/2的陨落和Windows在桌面的崛起一样,AMD此时看到了也抓住了这个机会。虽然Intel的处理器可高效运行本地(native)64位代码,但从经验来说,32位程序在AMD64和EM64T架构上效率都不是很高,而Microsoft微软,由于吸取了操作系统源代码兼容性的教训,所以它从现今的Win32源代码从发,构建一个64位版本的系统应该不是件难事。

现在,我们都在平静等待“64位扩展架构”的Windows发布,实际上,许多的电脑厂商已用EM64T或AMD64的芯片搭配32位的Windows,只是你可能没注意到而已,也许这些芯片的最令人吸引之处,就是可以同时运行耗费大量资源的32位程序。你可能此时会想,是不是8TB内存最终对每个人来说都足够了呢?尽管在理论上,64位可达到16000PB的寻址,而实际中,应用程序的寻址空间限制在7至8TB,原因是操作系统的内核,都在地址0x8000000000附近映射到每个进程之中。

当然,64位的变化不只在寻址上,现今,我们已经不再追求处理器频率每年翻一番,处理器频率徘徊在3GHz至4GHz之间,所以,有着64位架构的双倍总线带宽处理器(或多核心处理器),在性能上会更好一些。
不可否认,随着越来越多的64位电脑出现,64位的Windows最终也会走入寻常百姓家,现时窘境却是:是把现有程序移植到64位还是继续开发32位程序呢?大约三年前,我们的一个客户要求把Win32平台上的Unix API层移植到基于安腾的64位Windows之上,在移植过程中,发现了一些有趣的问题;本文中,主要围绕从32位移植到64位的一些相关话题,在此与大家分享。


在Windows之上的Windows(Windows on Windows)
相对于当年运行在32位Windows之下的16位程序,WoW32(Windows on Windows 32)从表面上来看,已经表现得非常不错了,关于WoW32的主要问题不是性能,而是兼容性和稳定性。在16位Windows中,由于其运行在DOS之上,所以程序间不得不以共享内存的方式来实现多任务,随着时间推移,程序逐渐运行在一个抢占式多任务环境的系统中,而且有各自的内存空间。在64位Windows中,32位进程被当作了一个在特殊转换层中的64位进程,这个转换层被称作“Win32 on Windows 64”,简写为“Wow64”。
在32位Windows中,总共4GB的寻址空间被划分为操作系统2GB,程序2GB,如果在boot.ini文件中使用了/3GB的启动选项(或者/Userva=3030,但如果系统超出了进程表,将不能登录)和/LARGEADDRESSAWARE链接器标志,可把内核共享系统空间移至1GB,而给应用程序3GB的内存空间。但得到一大块连续的内存空间并不像看起来那么容易,有如下一个简单的测试程序,可显示最大的可用内存块和最高位程序地址,分别用32位和64位编译器编译,并在可执行文件中设置或取消/LARGEADDRESSAWARE位,然后在不同的平台上运行它们,结果如下图所示,具体数字会因Windows版本的不同而有所变化,HKLM/Software/Microsoft/Windows NT/CurrentVersion/Windows/AppInit_DLLs中所加载的DLL对结果也有所影响。估计其最初的设计目的是为了优化页表的大小,并以减小内核可用空间和牺牲性能为代价,调整寻址空间的大小。

#include
#include
#include
#pragma comment(lib, "kernel32.lib")

bool Walk(HANDLE hProcess, SIZE_T &free_bytes, SIZE_T &max_free, SIZE_T &reserved_bytes, void * &system_base)
{
MEMORY_BASIC_INFORMATION mbi;
char *addr;
void *last_base;
SIZE_T current_region_size = 0;
char *top = (char *) 0 + (SIZE_T) -1;
SYSTEM_INFO si;
DWORD last_state = 0;

GetSystemInfo(&si);
free_bytes = 0;
max_free = 0;
system_base = (void *) (((char *) si.lpMaximumApplicationAddress) + 1);
reserved_bytes = top - (char *) system_base + 1;
addr = (char *) (last_base = si.lpMinimumApplicationAddress);

while (addr < si.lpMaximumApplicationAddress)
{
if (0 == VirtualQueryEx(hProcess, addr, &mbi, sizeof(mbi)))
{return false;}
#if defined(DEBUG)
char name[MAX_PATH];
name[0] = '/0';
GetModuleFileName((HMODULE) mbi.BaseAddress, name, sizeof(name));
printf("AllocationBase: %x, Base: %x Size: %x, State: %x, Dll: %s/n", mbi.AllocationBase, mbi.BaseAddress, mbi.RegionSize, mbi.State, name);
#endif
if (last_state == mbi.State)
{
current_region_size += mbi.RegionSize;
}
else
{
if (MEM_FREE == last_state)
{
if (current_region_size > max_free)
{
max_free = current_region_size;
}
}
current_region_size = mbi.RegionSize;
}
if (MEM_FREE == mbi.State)
{
free_bytes += mbi.RegionSize;
}
last_base = mbi.AllocationBase;
addr += mbi.RegionSize;
last_state = mbi.State;
}
return true;
}

int main(int argc, char **argv)
{
SIZE_T free_bytes;
SIZE_T max_free;
SIZE_T reserved;
void *system_base;
bool ok = false;

if (1 == argc)
{
ok = Walk(GetCurrentProcess(), free_bytes, max_free, reserved, system_base);
}
else
{
DWORD pid = atoi(argv[1]);
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
if (0 != hProcess)
{
ok = Walk(hProcess, free_bytes, max_free, reserved, system_base);
CloseHandle(hProcess);
}
}
if (ok)
{
printf("总共可用字节:0x%I64x/n", (long long) free_bytes);
printf("系统保留字节:0x%I64x/n", (long long) reserved);
printf("系统基地址:0x%p/n", system_base);
printf("最大连续可用块:0x%I64x/n", (long long) max_free);
}
}

最让人惊奇的事情是,用/LARGEADDRESSAWARE选项编译的32位Windows程序,在x64上,实际可访问4GB内存空间。由此可见,在Windows x64上,只需一小点努力,寻址空间就会翻一倍。
还有一个意料之外的结果,在Windows XP SP2上,最大可用块比其他任何一个平台上的都要小。通过在每个基地址处调用GetModuleFileName(),导出DLL在内存中的位置,最终发现UXTheme.DLL在最大可用块的中间(0x5AD70000)有一个隐含的载入地址,你可能会当它当成一个程序错误,但XP x64似乎没有这个问题(基地址:7DF50000),所以重定位XP SP2中的uxtheme.dll可能是安全的。

对运行在x64之上的32位程序,AWE和PAE也是适用的,但IA64版的Windows,对运行在Wow64中的32位程序,不提供AWE和PAE支持。
Wow64环境还有其他的让人惊喜之处,并且带有一组新的API。通常32位程序对%WINDIR%/System32的请求,会被重定向到%WINDIR%/Syswow64中,而且注册表也被虚拟化了,HKLM/Software现在实质上是HKLM/Wow6432Node,这是与HKCU虚拟化的不同之处。例如,Internet Explorer的设置就同时被32位和64位的Internet Explorer共享。(32位应用程序被默认安装于C:/Program Files (x86))。
有一些新的API,可用于32位程序取消虚拟化时通知64位系统,或者检测是否运行在仿真层中;表2中的API来自最新的Platform SDK,要注意的是,它们只在Windows Server 2003之后的kernel32中实现,“link /delayload:kernel32.dll”此时已是一个必选项,它通过LoadLibrary()和GetProcAddress(),可使二进制代码同时运行在老版本和新版本的Windows上。在包含函数GetSystemWow64Directory()的winbase.h头文件中,也可找到一个GetProcAddress()的函数原型。如下的程序演示了重定向功能:


#define _WIN32_WINNT 0x0501
#include
#include
#pragma comment(lib, "kernel32.lib")

typedef BOOL (__stdcall *ISWOW64PROCESS)(HANDLE hProcess, PBOOL Wow64Process);
typedef BOOL (__stdcall *WOW64ENABLEWOW64FSREDIRECTION)(BOOL Wow64FsEnableRedirection);

PGET_SYSTEM_WOW64_DIRECTORY_A pGetSystemWow64DirectoryA = 0;
ISWOW64PROCESS pIsWow64Process = 0;
WOW64ENABLEWOW64FSREDIRECTION pWow64EnableWow64FsRedirection = 0;

void ResolveWow64References(void)
{
HMODULE hKernel32 = GetModuleHandle("kernel32.dll");
if (hKernel32)
{
pGetSystemWow64DirectoryA = (PGET_SYSTEM_WOW64_DIRECTORY_A) GetProcAddress(hKernel32, GET_SYSTEM_WOW64_DIRECTORY_NAME_A_A);
pIsWow64Process = (ISWOW64PROCESS) GetProcAddress(hKernel32, "IsWow64Process");
pWow64EnableWow64FsRedirection = (WOW64ENABLEWOW64FSREDIRECTION) GetProcAddress(hKernel32, "Wow64EnableWow64FsRedirection");
}
}

void WhatIs(const char *cmd)
{
DWORD type;
if (GetBinaryType(cmd, &type))
{
switch (type)
{
case SCS_32BIT_BINARY:
printf("%s是一个32位二进制文件/n", cmd);
break;
case SCS_64BIT_BINARY:
printf("%s是一个64位二进制文件/n", cmd);
break;
default:
fprintf(stderr, "%s -未知的二进制类型-%d/n", cmd, type);
break;
}
}
else
{fprintf(stderr, "%s - error %d/n", cmd, GetLastError());}
}

void main(void)
{
char windir[MAX_PATH];
char cmd[MAX_PATH];
char syswow64[MAX_PATH];
BOOL bWow64Process = FALSE;

ResolveWow64References();
if (0 == pWow64EnableWow64FsRedirection)
{
fprintf(stderr, "需运行在支持这些API的平台上/n");
return;
}
if (pIsWow64Process)
{
pIsWow64Process(GetCurrentProcess(), &bWow64Process);
}

printf("这%s是一个Wow64进程/n", (bWow64Process ? "" : "不"));
GetWindowsDirectory(windir, sizeof(windir));
sprintf(cmd, "%s//system32//cmd.exe", windir);
printf("启用重定向Wow64FsRedirection/n");
pWow64EnableWow64FsRedirection(TRUE);
if (pGetSystemWow64DirectoryA)
{
if (pGetSystemWow64DirectoryA(syswow64, sizeof(syswow64)))
{
sprintf(cmd, "%s//cmd.exe", syswow64);
WhatIs(cmd);
sprintf(cmd, "%s//system32//cmd.exe", windir);
}
}
WhatIs(cmd);
printf("取消重定向Wow64FsRedirection/n");
pWow64EnableWow64FsRedirection(FALSE);
WhatIs(cmd);
}

先来看一下在Wow64进程当中,是如何包装32位寻址空间的。
在Windows检测到32位二进制代码中的CreateProcess()调用时,会启用一个Wow64进程来接手,这个Wow64进程实际上是一个有着全部64位寻址空间的64位程序,它加载一个64位的ntdll.dll(在所有64位进程中都如此),也会加载几个转换DLL,这些DLL的任务是从32位堆栈中取出信息,重定向32位的函数调用,且使本地64位函数调用转到64位的ntdll.dll中。
wow64.dll
wow64cpu.dll
wow64mib.dll
wow64win.dll
除了以上这些DLL,不会有其他的64位DLL加载到Wow64的寻址空间中。一旦这个Wow64程序被加载,会在64位寻址空间中为32位进程设置一个区域——你可能会想,肯定在低位的4GB中——并且加载32位的ntdll.dll,接下来就是32位程序正常加载到内存中运行了。的确,对一个32位进程使用64位调试器(如Windbg),是有先见性的;当跟踪指令到jmp 33:xxxxxxxx处时,32位调试器只会简单地停下来,而64位的调试器就会跳过这一段,从64位ntdll.dll中进入保护模式继续跟踪。
在很大程度上,%WINDIR%/syswow64目录中的32位DLL,与它们在32位Windows上的副本是相同的,两者之间只有一点性能上的差异,例如32位的ntdll.dll,它看起来不只是对32位程序的本地接口层,还设置好相应的转换工作以便转到64位层,并且自身还带有一些Wow64相关的API,这是在64位版本的ntdll.dll中所没有的。在命令行中键入dumpbin -exports | grep Wow64 | grep Nt之后,显示了一个API列表,让我们来看看它们究竟能做什么:
NtWow64GetNativeSystemInformation
NtWow64QueryInformationProcess64
NtWow64QueryVirtualMemory64
NtWow64ReadVirtualMemory64

如果你在Wow64层中运行一个32位程序,并且想访问64位程序中的进程空间,Win32 API ReadProcessMemory()这时可起不了作用,只是读取了寻址空间最开始的4GB。如果你需要的东西是在4GB以外呢?或者想要读取从地址0x7fffffde000开始的进程环境块(PEB)或线程环境块(TEB)呢?
NtQueryInformationProcess()这个函数可返回大多数种类的信息,但返回的数据不能放入一个32位的寻址空间内,转换机制显然会把它替换成零。那么我们怎样收集信息呢?可以创建一些数据结构,并小心地把它们扩展到64位,然后传递给Wow64版本的函数。如下的一个程序演示了如何使用包括GetParentProcessId()和GetCurrentDirectoryExW()在内的这些API。实际上,NtQueryInformationProcess()也可查询到父进程ID,演示程序读取了远程32位或64位进程的进程环境块(PEB),并打印出当前工作目录;由此可看出,一个Wow64进程似乎有一个32位和64位的进程环境块。

头文件
nativeinterface.h

#pragma once
#include
#include

class NTProcessInformation
{
public:
NTProcessInformation(HANDLE hProcess = GetCurrentProcess());
NTProcessInformation(DWORD pid);
bool _declspec(property(get=getok)) ok;
__w64 ULONG_PTR __declspec(property(get=getpid)) pid;
__w64 ULONG_PTR __declspec(property(get=getppid)) ppid;
HANDLE __declspec(property(get=gethprocess)) hProcess;
bool __declspec(property(get=getusewow64apis)) UseWow64Apis;
VOID * __ptr64 __declspec(property(get=getpa)) PebBaseAddress;
inline ULONG_PTR getpid(void) {return x_Pid;}
inline ULONG_PTR getppid(void) {return x_PPid;}
inline VOID * __ptr64 getpa(void) {return x_PebAddress;}
inline bool getok(void) {return x_OK;}
inline bool getusewow64apis(void) {return x_UseWow64Apis;}
inline HANDLE gethprocess(void) {return x_hProcess;}
bool IsWow64Process(HANDLE hProcess = 0);
protected:
void CommonConstruct(void);
__w64 ULONG_PTR x_Pid;
__w64 ULONG_PTR x_PPid;
DWORD x_ExitStatus;
VOID * __ptr64 x_PebAddress;
HANDLE x_hProcess;
bool x_OK;
bool x_UseWow64Apis;
};

class NTPEB
{
public:
NTPEB(HANDLE hProcess = GetCurrentProcess());
NTPEB(DWORD pid);
NTPEB(NTProcessInformation &ntpi);
bool _declspec(property(get=getok)) ok;
bool __declspec(property(get=getusewow64apis)) UseWow64Apis;
HANDLE __declspec(property(get=gethprocess)) hProcess;
VOID * __ptr64 __declspec(property(get=getpp)) ProcessParameters;
inline VOID * __ptr64 getpp(void) {return x_ProcessParameters;}
inline bool getok(void) {return x_OK;}
inline bool getusewow64apis(void) {return x_UseWow64Apis;}
inline HANDLE gethprocess(void) {return x_hProcess;}
protected:
void CommonConstruct(NTProcessInformation &ntpi);
VOID * __ptr64 x_ProcessParameters;
bool x_OK;
bool x_UseWow64Apis;
HANDLE x_hProcess;
};

class NTProcessParameters
{
public:
NTProcessParameters(HANDLE hProcess = GetCurrentProcess());
NTProcessParameters(DWORD pid);
NTProcessParameters(NTPEB &peb);
bool _declspec(property(get=getok)) ok;
std::wstring _declspec(property(get=getcwd)) cwd;
inline std::wstring getcwd(void) {return x_pwd;}
inline bool getok(void) {return x_OK;}
protected:
void CommonConstruct(NTPEB &peb);
std::wstring x_pwd;
bool x_OK;
};


头文件实现文件
nativeinterface.cpp

#include
#include
#include "nativeinterface.h"
#include "ntdll_lite.h"

typedef BOOL (__stdcall *ISWOW64PROCESS)(HANDLE hProcess, PBOOL Wow64Process);
typedef NTSTATUS (__stdcall *NTQUERYINFORMATIONPROCESS)(IN HANDLE ProcessHandle, IN PROCESSINFOCLASS ProcessInformationClass, OUT PVOID ProcessInformation, IN ULONG ProcessInformationLength, OUT PULONG ReturnLength OPTIONAL);
#if !defined(_WIN64)
typedef NTSTATUS (__stdcall *NTWOW64QUERYINFORMATIONPROCESS64)(IN HANDLE ProcessHandle, IN PROCESSINFOCLASS ProcessInformationClass, OUT PVOID ProcessInformation, IN ULONG ProcessInformationLength, OUT PSIZE_T64 ReturnLength OPTIONAL);
typedef NTSTATUS (__stdcall *NTWOW64READVIRTUALMEMORY64)( IN HANDLE ProcessHandle, IN PVOID64_ BaseAddress, OUT PVOID Buffer, IN SIZE_T64 BufferSize, OUT PSIZE_T64 NumberOfBytesRead OPTIONAL);
#endif

ISWOW64PROCESS pIsWow64Process = 0;
NTQUERYINFORMATIONPROCESS pNtQueryInformationProcess = 0;
#if !defined(_WIN64)
NTWOW64QUERYINFORMATIONPROCESS64 pNtWow64QueryInformationProcess64 = 0;
NTWOW64READVIRTUALMEMORY64 pNtWow64ReadVirtualMemory64 = 0;
#endif

static BOOL bLoaded = FALSE;

static BOOL LoadDlls(void)
{
HMODULE hKernel32 = GetModuleHandle("kernel32.dll");
HMODULE hNtDll = GetModuleHandle("ntdll.dll");
if ((0 == hKernel32 ) || (0 == hNtDll))
{return FALSE;}
pIsWow64Process = (ISWOW64PROCESS) GetProcAddress(hKernel32, "IsWow64Process");
pNtQueryInformationProcess = (NTQUERYINFORMATIONPROCESS) GetProcAddress(hNtDll, "NtQueryInformationProcess");
#if !defined(_WIN64)
pNtWow64QueryInformationProcess64 = (NTWOW64QUERYINFORMATIONPROCESS64) GetProcAddress(hNtDll, "NtWow64QueryInformationProcess64");
pNtWow64ReadVirtualMemory64 = (NTWOW64READVIRTUALMEMORY64) GetProcAddress(hNtDll, "NtWow64ReadVirtualMemory64");
#endif
bLoaded = TRUE;
return (0 != pNtQueryInformationProcess);
}

NTProcessInformation::NTProcessInformation(HANDLE hProcess)
: x_Pid(-1)
, x_PPid(-1)
, x_ExitStatus(STILL_ACTIVE)
, x_PebAddress(0)
, x_hProcess(hProcess)
, x_OK(false)
, x_UseWow64Apis(false)
{
CommonConstruct();
}

NTProcessInformation::NTProcessInformation(DWORD pid)
: x_Pid(-1)
, x_PPid(-1)
, x_ExitStatus(STILL_ACTIVE)
, x_PebAddress(0)
, x_hProcess(0)
, x_OK(false)
, x_UseWow64Apis(false)
{
x_hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
CommonConstruct();
}

void NTProcessInformation::CommonConstruct(void)
{
if (!bLoaded)
{
if (!LoadDlls())
{return;}
}
DWORD err;
PROCESS_BASIC_INFORMATION info;
ULONG realsize;

if (ERROR_SUCCESS != (err = pNtQueryInformationProcess(x_hProcess, ProcessBasicInformation, (PVOID) &info, sizeof(info), &realsize)))
{return;}
if (0 == info.PebBaseAddress)
{
#if defined(_WIN64)
return;
#else
PROCESS_BASIC_INFORMATION64 info64;
SIZE_T64 realsize64;

if (!IsWow64Process(GetCurrentProcess()))
{return;}
if (0 == pNtWow64QueryInformationProcess64)
{return;}
if (ERROR_SUCCESS != (err = pNtWow64QueryInformationProcess64(x_hProcess, ProcessBasicInformation, (PVOID) &info64, sizeof(info64), &realsize64)))
{return;}
x_Pid = (ULONG_PTR) info64.UniqueProcessId;
x_PPid = (ULONG_PTR) info64.InheritedFromUniqueProcessId;
x_ExitStatus = info64.ExitStatus;
x_PebAddress = info64.PebBaseAddress;
x_OK = true;
x_UseWow64Apis = true;
#endif
}
else
{
x_Pid = info.UniqueProcessId;
x_PPid = info.InheritedFromUniqueProcessId;
x_ExitStatus = info.ExitStatus;
x_PebAddress = info.PebBaseAddress;
x_OK = true;
}
}

bool NTProcessInformation::IsWow64Process(HANDLE hProcess)
{
if (pIsWow64Process)
{
BOOL IsWow64 = FALSE;

if (pIsWow64Process(hProcess ? hProcess : x_hProcess, &IsWow64))
{return IsWow64 ? true : false;}
}
return false;
}

NTPEB::NTPEB(HANDLE hProcess)
: x_ProcessParameters(0)
, x_OK(false)
, x_UseWow64Apis(false)
, x_hProcess(0)
{
NTProcessInformation info(hProcess);
CommonConstruct(info);
}

NTPEB::NTPEB(DWORD pid)
: x_ProcessParameters(0)
, x_OK(false)
, x_UseWow64Apis(false)
, x_hProcess(0)
{
NTProcessInformation info(pid);
CommonConstruct(info);
}

NTPEB::NTPEB(NTProcessInformation &ntpi)
: x_ProcessParameters(0)
, x_OK(false)
, x_UseWow64Apis(false)
, x_hProcess(0)
{
CommonConstruct(ntpi);
}

void NTPEB::CommonConstruct(NTProcessInformation &ntpi)
{
if (!bLoaded)
{
if (!LoadDlls())
{return;}
}
x_hProcess = ntpi.hProcess;
if (ntpi.UseWow64Apis)
{
#if defined(_WIN64)
return;
#else
PEB64 peb64;
SIZE_T64 realsize64;
NTSTATUS err;
if (ERROR_SUCCESS != (err = pNtWow64ReadVirtualMemory64(x_hProcess, ntpi.getpa(), &peb64, sizeof(PEB64), &realsize64)))
{return;}
x_UseWow64Apis = true;
x_ProcessParameters = peb64.ProcessParameters;
#endif
}
else
{
PEB peb;
SIZE_T realsize;
if (FALSE == ReadProcessMemory(x_hProcess, Ptr64ToPtr(ntpi.getpa()), &peb, sizeof(PEB), &realsize))
{return;}
x_ProcessParameters = peb.ProcessParameters;
}
x_OK = true;
}

NTProcessParameters::NTProcessParameters(HANDLE hProcess)
: x_OK(false)
{
NTPEB peb(hProcess);
CommonConstruct(peb);
}

NTProcessParameters::NTProcessParameters(DWORD pid)
: x_OK(false)
{
NTPEB peb(pid);
CommonConstruct(peb);
}

NTProcessParameters::NTProcessParameters(NTPEB &peb)
: x_OK(false)
{
CommonConstruct(peb);
}

void NTProcessParameters::CommonConstruct(NTPEB &peb)
{
if (!bLoaded)
{
if (!LoadDlls())
{return;}
}
if (peb.UseWow64Apis)
{
#if defined(_WIN64)
return;
#else
PROCESS_PARAMETERS64 ProcParams64;
SIZE_T64 realsize64;
NTSTATUS err;

if (ERROR_SUCCESS != (err = pNtWow64ReadVirtualMemory64(peb.hProcess, peb.getpp(), &ProcParams64, sizeof(PROCESS_PARAMETERS64), &realsize64)))
{return;}
SIZE_T64 len = ProcParams64.CurrentDirectory.DosPath.Length;
wchar_t *buffer = new wchar_t[(SIZE_T) (len+1)];
if (ERROR_SUCCESS != (err = pNtWow64ReadVirtualMemory64(peb.hProcess, ProcParams64.CurrentDirectory.DosPath.Buffer, buffer, len * sizeof(wchar_t), &realsize64)))
{return;}
buffer[realsize64/2] = '/0';
x_pwd = buffer;
delete [] buffer;
#endif
}
else
{
PROCESS_PARAMETERS ProcParams;
SIZE_T realsize;

if (FALSE == ReadProcessMemory(peb.hProcess, Ptr64ToPtr(peb.getpp()), &ProcParams, sizeof(PROCESS_PARAMETERS), &realsize))
{return;}
SIZE_T len = ProcParams.CurrentDirectory.DosPath.Length;
wchar_t *buffer = new wchar_t[len+1];
if (FALSE == ReadProcessMemory(peb.hProcess, ProcParams.CurrentDirectory.DosPath.Buffer, buffer, len*sizeof(wchar_t), &realsize))
{return;}
buffer[realsize/2] = '/0';
x_pwd = buffer;
delete [] buffer;
}
x_OK = true;
}


相关头文件
ntdll_lite.h

#pragma once
typedef LONG NTSTATUS;
typedef LONG KPRIORITY;

#if (_MSC_VER < 1300)
typedef ULONG *ULONG_PTR;
#endif

typedef enum _PROCESSINFOCLASS
{
ProcessBasicInformation,
ProcessQuotaLimits,
ProcessIoCounters,
ProcessVmCounters,
ProcessTimes,
ProcessBasePriority,
ProcessRaisePriority,
ProcessDebugPort,
ProcessExceptionPort,
ProcessAccessToken,
ProcessLdtInformation,
ProcessLdtSize,
ProcessDefaultHardErrorMode,
ProcessIoPortHandlers,
ProcessPooledUsageAndLimits,
ProcessWorkingSetWatch,
ProcessUserModeIOPL,
MaxProcessInfoClass
} PROCESSINFOCLASS;

typedef PVOID PPEB_LDR_DATA;
typedef PVOID PPEB_FREE_BLOCK;

#if !defined(_WIN64)
#if (_MSC_VER < 1300)
typedef unsigned __int64 PCHAR64;
typedef unsigned __int64 HANDLE64;
typedef unsigned __int64 PVOID64_;
#else
typedef char * __ptr64 PCHAR64;
typedef void * __ptr64 HANDLE64;
typedef void * __ptr64 PVOID64_;
#endif
typedef PVOID64_ PPEB_LDR_DATA64;
typedef PVOID64_ PPEB_FREE_BLOCK64;
typedef unsigned __int64 SIZE_T64, *PSIZE_T64;
#endif

typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PCHAR Buffer;
} UNICODE_STRING;
typedef UNICODE_STRING *PUNICODE_STRING;

#if !defined(_WIN64)
typedef struct _UNICODE_STRING64 {
USHORT Length;
USHORT MaximumLength;
PCHAR64 Buffer;
} UNICODE_STRING64;
typedef UNICODE_STRING64 *PUNICODE_STRING64;
#endif

typedef struct _CURRENT_DIRECTORY {
UNICODE_STRING DosPath;
HANDLE Handle;
} CURRENT_DIRECTORY, *PCURRENT_DIRECTORY;

#if !defined(_WIN64)
typedef struct _CURRENT_DIRECTORY64 {
UNICODE_STRING64 DosPath;
HANDLE64 Handle;
} CURRENT_DIRECTORY64, *PCURRENT_DIRECTORY64;
#endif

typedef struct _PROCESS_PARAMETERS {
ULONG MaximumLength;
ULONG Length;
ULONG Flags;
ULONG DebugFlags;
HANDLE ConsoleHandle;
ULONG ConsoleFlags;
HANDLE StandardInput;
HANDLE StandardOutput;
HANDLE StandardError;
CURRENT_DIRECTORY CurrentDirectory;
UNICODE_STRING DllPath;
UNICODE_STRING ImagePathName;
UNICODE_STRING CommandLine;
PVOID Environment;
ULONG StartingX;
ULONG StartingY;
ULONG CountX;
ULONG CountY;
ULONG CountCharsX;
ULONG CountCharsY;
ULONG FillAttribute;
ULONG WindowFlags;
ULONG ShowWindowFlags;
UNICODE_STRING WindowTitle;
UNICODE_STRING DesktopInfo;
UNICODE_STRING ShellInfo;
UNICODE_STRING RuntimeData;
} PROCESS_PARAMETERS, *PPROCESS_PARAMETERS;

#if !defined(_WIN64)
typedef struct _PROCESS_PARAMETERS64 {
ULONG MaximumLength;
ULONG Length;
ULONG Flags;
ULONG DebugFlags;
HANDLE64 ConsoleHandle;
ULONG ConsoleFlags;
HANDLE64 StandardInput;
HANDLE64 StandardOutput;
HANDLE64 StandardError;
CURRENT_DIRECTORY64 CurrentDirectory;
UNICODE_STRING64 DllPath;
UNICODE_STRING64 ImagePathName;
UNICODE_STRING64 CommandLine;
PVOID64_ Environment;
ULONG StartingX;
ULONG StartingY;
ULONG CountX;
ULONG CountY;
ULONG CountCharsX;
ULONG CountCharsY;
ULONG FillAttribute;
ULONG WindowFlags;
ULONG ShowWindowFlags;
UNICODE_STRING64 WindowTitle;
UNICODE_STRING64 DesktopInfo;
UNICODE_STRING64 ShellInfo;
UNICODE_STRING64 RuntimeData;
} PROCESS_PARAMETERS64
#if (_MSC_VER < 1300)
;
typedef unsigned __int64 PPROCESS_PARAMETERS64;
#else
, *PPROCESS_PARAMETERS64;
#endif
#endif

typedef struct _PEB {
BOOLEAN InheritedAddressSpace;
HANDLE Mutant;
PVOID ImageBaseAddress;
PPEB_LDR_DATA Ldr;
PPROCESS_PARAMETERS ProcessParameters;
PVOID SubSystemData;
PVOID ProcessHeap;
PVOID FastPebLock;
PVOID FastPebLockRoutine;
PVOID FastPebUnlockRoutine;
PVOID Spare[4];
PPEB_FREE_BLOCK FreeList;
ULONG TlsExpansionCounter;
PVOID TlsBitmap;
ULONG TlsBitmapBits[2];
PVOID ReadOnlySharedMemoryBase;
PVOID ReadOnlySharedMemoryHeap;
PVOID *ReadOnlyStaticServerData;
PVOID AnsiCodePageData;
PVOID OemCodePageData;
PVOID UnicodeCaseTableData;
LARGE_INTEGER CriticalSectionTimeout;
} PEB, *PPEB;

#if !defined(_WIN64)
typedef struct _PEB64 {
BOOLEAN InheritedAddressSpace;
HANDLE64 Mutant;
PVOID64_ ImageBaseAddress;
PPEB_LDR_DATA64 Ldr;
#if (_MSC_VER < 1300)
PPROCESS_PARAMETERS64 ProcessParameters;
#else
PROCESS_PARAMETERS64 *__ptr64 ProcessParameters;
#endif
PVOID64_ SubSystemData;
PVOID64_ ProcessHeap;
PVOID64_ FastPebLock;
PVOID64_ FastPebLockRoutine;
PVOID64_ FastPebUnlockRoutine;
PVOID64_ Spare[4];
PPEB_FREE_BLOCK64 FreeList;
ULONG TlsExpansionCounter;
PVOID64_ TlsBitmap;
ULONG TlsBitmapBits[2];
PVOID64_ ReadOnlySharedMemoryBase;
PVOID64_ ReadOnlySharedMemoryHeap;
PVOID64_ *ReadOnlyStaticServerData;
PVOID64_ AnsiCodePageData;
PVOID64_ OemCodePageData;
PVOID64_ UnicodeCaseTableData;
LARGE_INTEGER CriticalSectionTimeout;
} PEB64
#if (_MSC_VER < 1300)
;
typedef unsigned __int64 PPEB64;
#else
, * __ptr64 PPEB64;
#endif
#endif

typedef struct _PROCESS_BASIC_INFORMATION {
NTSTATUS ExitStatus;
PPEB PebBaseAddress;
ULONG_PTR AffinityMask;
KPRIORITY BasePriority;
ULONG_PTR UniqueProcessId;
ULONG_PTR InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION;
typedef PROCESS_BASIC_INFORMATION *PPROCESS_BASIC_INFORMATION;

#if !defined(_WIN64)
typedef struct _PROCESS_BASIC_INFORMATION64 {
NTSTATUS ExitStatus;
PPEB64 PebBaseAddress;
ULONG64 AffinityMask;
KPRIORITY BasePriority;
ULONG64 UniqueProcessId;
ULONG64 InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION64;
typedef PROCESS_BASIC_INFORMATION64 *PPROCESS_BASIC_INFORMATION64;
#endif


主程序
#include
#include "nativeinterface.h"
#define DEBUG

BOOL GetCurrentDirectoryExW(HANDLE hProcess, wchar_t *buffer, SIZE_T buflen)
{
NTProcessParameters params(hProcess);
if (!params.ok)
{return false;}

std::wstring result = params.cwd;
SIZE_T len = min(buflen-1, result.length());
memcpy(buffer, result.c_str(), sizeof(wchar_t) * len);
buffer[len] = L'/0';
return TRUE;
}

ULONG_PTR GetParentProcessIdEx(HANDLE hProcess)
{
NTProcessInformation info(hProcess);
if (!info.ok)
{return -1;}
return info.ppid;
}

BOOL MyIsWow64Process(HANDLE hProcess)
{
NTProcessInformation info(hProcess);
if (!info.ok)
{return FALSE;}
return info.IsWow64Process() ? TRUE : FALSE;
}

#if defined(DEBUG)
void main(int argc, char **argv)
{
HANDLE hProcess;
if (1 == argc)
{hProcess = GetCurrentProcess();}
else
{
DWORD pid = atoi(argv[1]);
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
if (0 == hProcess)
{return;}
}
wchar_t path[4096];
printf("ppid = %d/n", GetParentProcessIdEx(hProcess));
printf("这%s 是一个Wow64进程/n", (MyIsWow64Process(hProcess) ? "" : "不"));
GetCurrentDirectoryExW(hProcess, path, sizeof(path));
printf("当前工作目前 = %ls/n", path);
}
#endif

可读取64位寻址空间——这意味着不但能读取其他64位进程的进程环境块(PEB),而且还能观察到Wow64进程的64位部分。如NtWow64WriteVirtualMemory64()这些分配和修改虚拟内存的函数一样,可加载一个64位DLL到Wow64进程的64位部分。
Wow64程序面对的是虚拟化后的64位文件系统和注册表,但从一个32位系统发起的网络应用,如//server/admin$和ConnectRegistry(),可能面对的是一个真实的64位文件系统和注册表,所以,基于这些原因,copy 1234.dll //server/admin$/system32这行命令可能会达不到预期的目的。


本地64位移植
在Windows平台上,需要多于4GB内存的程序越来越多,这些程序要么在AWE或PAE上苦苦挣扎,要么转而栖身到其他的64位平台,如Solaris。以下列出的一些原因,可能会使你选择重新编译生成本地64位二进制文件。

无论是静态还是动态的32位库,都不能链接到64位的可执行文件,反之亦然,但是如果提供中间件,或者计划部署到64位Windows上,那就需要移植了——最初我们是移植到IA64上,现在是x64。基于COM的DLL在此是个例外,它们在32位的宿主中已经被代理了,即使通过网络连接时也是如此,但最终你会发现由此带来的进程内性能损失得不偿失,最终还是要把组件移植到64位Windows上。
如果你要发布一个Internet Explorer的插件,将会发现,32位版本的插件在64位Internet Explorer上无法使用。64位的Windows同时带有32位和64位的Internet Explorer,并且是按照每个窗口一个进程这样配置的,所以在64位平台上两者都能运行。因此,对64位Windows,必须同时发布32位和64位的插件。

如果有一个16位的Windows程序,因为没有Windows on Windows on Windows (WoWoW?)诸如的方案,所以16位程序不可能运行在x64上(会有诸如提示:映像文件有效,但不适用于此计算机类型)。
设备驱动程序也亦然,32位的设备驱动程序无法工作在64位的Windows上。
也许移植的目标是IA64,但会发现,程序并没有预期中的性能,或者是因为程序使用了AWE和PAE,两者在IA64平台上均不被支持,但看起来似乎本地编译的IA64二进制文件比32位效率要高得多。此现象主要针对第一代安腾处理器,对于第二代安腾处理器,情况要好多了。或许Windows使用的是32位软件二进制仿真器,而不是本地硬件仿真器。

移植到新平台上,工作量非常巨大,一项普通的移植都需要严格的计划和时间上巨大的开销。为了找到问题的范围,无论如何都要挑出一些有代表性的源代码,试编译运行。听起来好像很简单,只是敲入make(或者nmake、devenv /build)就行了,实际上,要做的工作远远不只这些。
那些没有源代码的程序,有库可用吗?在移植中要使用什么平台呢?是使用目标平台还是类似x86的桌面平台?对新平台来说,源代码配置管理系统(SCM)可用吗?从哪可得到编译器和链接器?所有的工具都适合64位开发吗?构建环境是否支持在同一源代码中生成多平台应用,还是需要多做一份拷贝?实际上,敲入make需要更多的前端工作,但最简单的解决方案是,拷贝一份源代码,安装好Microsoft Platform SDK,使用x86 to x64跨平台交叉编译器,而把其他诸如开发环境、生成工具、SCM等等,统统扔在32位Windows中不管。这也许是最大的一个惊喜了,在链接可执行文件前,甚至都不需要一台64位电脑。
正因为不能把32位的静态或动态库,链接到64位程序,所以,如果没有全部库的源代码,或者64位版本的库还不存在,那么移植方案就得暂时搁下。你需要在64位目标平台上找到一些东西,替换掉这些库,要么说服生产商提供这些库,或者干脆不移植。

接下来一个最大的问题是开发环境——包括编译器、链接器、调试器、源代码管理系统、make和集成开发环境(IDE)。
从哪可以得到一个编译器呢?在本文写作时,GNU C/C++还不行。Intel已经发布了商业版本的编译器,分别对应于IA64(本地或跨平台)、IA32E(Intel称其为EM64T x64平台)和IA32;微软也有一对版本号为14.0的编译器(VC.NET 2005),分别对应于x86 to IA64和x64,而通过Platform SDK,提供了跨平台交叉编译功能。实际上在Visual Studio.NET 2005中,微软已经提供了本地x64和IA64编译器。
集成开发环境(IDE)不需要是64位的,你能在32位系统上跨平台交叉编译,或者在64位平台上运行32的IDE。
从哪可以得到一个调试器呢?Windbg有针对以上三个平台的版本,但对普通人来说,它还是有点不太好用,除非是黑客。Visual Studio.NET 2005有一个远程调试器服务端可用于以上两个64位平台。它仍是一个32位程序,所以你能得到一个混合的实现,在当前x86桌面系统上开发,而在64位平台上远程调试。
那配置管理系统呢?也许你的SCM已经支持相应64位平台开发了,如果没有,那只有在32位系统上跨平台交叉编译,或者拷贝源代码到64位平台上。

现在我们可以准备生成程序了,所有为移植做的准备就是为了现在享受胜利的果实,但代码有多具移植性呢?
我们都知道sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long),但也要准备接受sizeof(long) != sizeof(void *)或者sizeof(int) != sizeof(void *)。大多数64位Unix系统定义sizeof(long) == sizeof(void *),这被称作LP64模型,此时long与指针都是64位的。Windows选择的是LLP64模型,此时对移植32位Windows源代码来说,sizeof(long long) == sizeof(void *)。这意味着,如果代码中把指针赋值给int(这是不可移植的,因为它不严谨且假定sizeof(int) == sizeof(long) == sizeof(void *)),或者把指针赋值给long,那就要在代码移植之前,把它们全部改正过来。记住,程序看起来也许运行得很正常,只要它位于64位寻址空间的低4GB位置,但malloc()和HeapAlloc()返回的值可能会在此之外,MapViewOfFile()也同样。

在很大程度上,编写良好的Win32代码只需稍作改动,便可通过编译。你可能会惊奇地发现,在很多地方,DWORD与PVOID几乎是同一样东西,只要认真遵循前面所提到的方法,源代码通过编译不是件难事,但是,记住在生成64位程序时使用-W3选项,对编译器的警告多加留意。

就以往的经验来说,需要为每一个COM组件生成一个新的GUID,但在Windows x64上,同一个组件的32位和64位版本(如Internet Explorer 工具栏)可以共享一个GUID,并可在同一台电脑上,同时为它们的客户程序提供服务。事实上,COM的实现是高度可伸缩性的,可以允许32位的组件被64位进程使用(反之亦然),或同时注册32位和64位的组件。换句话来说,如果你选择移植组件,可以保留GUID和接口定义;如果不移植(那些充当进程内服务的IE工具栏和插件必须要移植),多数情况下,它们都能正常运行,此时性能可能会受影响。

一些更棘手的问题都是围绕与32位程序共享二进制数据上,也许是通过文件,或者是共享内存、网络等等。在此有很多技术都可以用得上,但微软已经在它的编译器和头文件中内置了一些实用的工具。
微软编译器cl版本13.0或者更高(VS.NET 2003或后续版本),Platform SDK中的14.0版本,这些编译器都支持/Wp64选项。对每个源程序,只需简单地设置/Wp64选项,就可以看到5个(至少到目前为止)有用的警告;参见图1;以下的程序示范了这些警告,并且提供了一些有关移植的解决方法。


#include
#include
#include
#include
#include
#pragma comment(lib, "user32.lib")

void main(void)
{
void *ptr = 0;
int val;
intptr_t intptr;
size_t sizet;
HWND hWnd = GetDesktopWindow();
printf("%x/n", ptr);
printf("%p/n", ptr);
val = (int) ptr;
intptr = (intptr_t) ptr;
val = strlen("abc");
sizet = strlen("abc");
val = (int) hWnd;
val = HandleToUlong(hWnd);
hWnd = (HWND) UlongToHandle(val);
}

在头文件BaseTsd.h中,有一些可用的新内联函数和宏,如DWORD32、DWORD64、UINT_PTR、PointerToUlong()、Ptr64ToPtr()等。HWND在64位Windows上已是一个64位的变量,如果你想把它传递给一个32位进程,它的值会被截断,HandleToUlong()此时正好帮得上忙,但需注意的是,尽量不要让截断发生。*__ptr64强制一个指针变量占用64位,哪怕是在32位平台上,void * __ptr64 ptr就可强制在32位和64位平台上得到同样大小的变量,以保证二进制兼容;还有一个相应的__ptr32。显然,在我们发现8TB寻址空间不够用之前,是不会出现__ptr128的。

在把指针赋值给int时,尽量使用intptr_t,而不是int或long long。大多数编译器都提供了一个typedef,以保证对你的目标平台,变量有一个正确的大小。

删除源代码中的内联汇编语句,或者用一些“C”代码代替它,在生成程序时,也不要链接外部的汇编文件,因为__asm在IA64和x64编译器中会产生错误。为了确保二进制的兼容,请明智地决定联合体(Union)中变量的位置,以便结构数据对齐保持一致。参见以下程序:

#include
#include
#include

struct abc
{
HWND AWindow;
void * __ptr32 ThirtyTwoBitPointer;
void * __ptr64 SixtyFourBitPointer;
long ALong;
};

struct def
{
union
{
HWND AWindow;
long long reserved;
};
union
{
void * __ptr32 ThirtyTwoBitPointer;
long long reserved2;
};
void * __ptr64 SixtyFourBitPointer;
union
{
long ALong;
long long reserved3;
};
};

void main(void)
{
printf("%d/n", sizeof(abc));
printf("/tAWindow %p/n", offsetof(abc, AWindow));
printf("/t32bit pointer %p/n", offsetof(abc, ThirtyTwoBitPointer));
printf("/t64bit pointer %p/n", offsetof(abc, SixtyFourBitPointer));
printf("/tAlong %p/n", offsetof(abc, ALong));

printf("%d/n", sizeof(def));
printf("/tAWindow %p/n", offsetof(def, AWindow));
printf("/t32bit pointer %p/n", offsetof(def, ThirtyTwoBitPointer));
printf("/t64bit pointer %p/n", offsetof(def, SixtyFourBitPointer));
printf("/tAlong %p/n", offsetof(def, ALong));
}

IA64平台,像多数RISC处理器一样,对数据对齐过分吹毛求疵,有以下代码:
unsigned char *ptr;
DWORD dword = (DWORD) (*(unsigned long long *) ptr);
此时使用一个memcpy(),也许是比赋值更好的选择。

也许你经常要为32位和64位生成不同代码,为此,微软的64位编译器定义了一个预处理变量_WIN64。微软的编译器同时也提供_M_IX86、_M_IA64、_M_AMD64,以便应付那些需要针对特定处理器生成代码的极端场合。

64位移植中最困难的事,恐怕就是代码生成环境了。如果你之前移植到其他的架构上(如Alpha、MIPS或PowerPC),可能会觉得很容易,平台提供的示例代码中,常含有一个调用cl和nmake的makefile,以便演示在不同平台上针对同一源代码如何编译,其思想是把平台与输出分离。但即使是把vc?.pdb文件放在正确位置,并且也把编译器临时目录分离,makefile在多平台同时生成程序时,还是会有问题。
如果你正在使用Visual Studio IDE,可以很容易地导出配置文件、指定输出目录$(OUTDIR)、按需调整好编译器,然后在目标平台上打开一个Platform SDK命令行窗口,用如下命令devenv /build /useenv myproject.sln "Win32 - Debug(X64)"来构建一个工程。可以考虑一下最新的VS.NET 2005和它的跨平台交叉编译器,只要在每一个配置中指定目标平台,就可以很容易地生成应用(或工程)。在AMD的官方网站上,有一个示例,演示了如何使用Visual Studio和Platform SDK,从现有的工程文件中,生成64位二进制程序。

最后当然还要说一下软件安装的问题,如果有一个面向32位、64位(IA64和x64)的混合安装程序,在一开始就需要仔细地计划。如果使用Windows Installer,那么针对每一个平台架构,都会有一个MSI文件。有趣的是,在64位的安装中,可使用32位的用户定制(Custom Action)DLL,这暗示着在此之下,有一对协同工作的32位和64位进程。但这不在本文讨论范畴之内。


结论
不是每个人都需要把他们的程序移植到本地64位平台上,事实上,如果依然作为运行在64位处理器上的32位程序,可能会更加小巧,方便部署——当然除了安腾处理器。目前唯一清楚的是,微软为它的32位仿真层做了大量卓越的工作,而64位处理器和主板,价格便宜量又足,它们会越来越受到欢迎,因此,不管是运行现有32位程序、或是新游戏、还是复杂的商业程序,我们都没有理由——不拥抱64位Windows!!

上一篇:CreateProcess IE 独立进程
下一篇:vMwaer vSphere Client无法连接vCenter Server问题