对待64位windows

还记得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 <stdio.h>
#include <stdlib.h>
#include <windows.h>
#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 <stdio.h>
#include <windows.h>
#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 <windows.h>
#include <string>

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 <windows.h>
#include <basetsd.h>
#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 oncetypedef 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_;#elsetypedef char * __ptr64 PCHAR64;typedef void * __ptr64 HANDLE64;typedef void * __ptr64 PVOID64_;#endiftypedef 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 <windows.h>#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位系统发起的网络应用,如和ConnectRegistry(),可能面对的是一个真实的64位文件系统和注册表,所以,基于这些原因,copy 1234.dll 这行命令可能会达不到预期的目的。 本地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;以下的程序示范了这些警告,并且提供了一些有关移植的解决方法。

文章来自: 本站原创
Tags:
评论: 1 | 查看次数: 10739