汇编网首页登录博客注册
parse的学习博客
博客首页博客互动【做检测题】论坛求助

我的博客

个人首页 |  我的文章 |  我的相册 |  我的好友 |  最新访客 |  文章收藏 |  论坛提问 |  友情链接 |  给我留言  
图片载入中
学习动态
最新留言
友情链接

[2012-03-06 21:53] IA-32和64双兼容编程

Intel的64位扩展技术简介
杨全胜翻译
【译者注】Intel公司在IDF2004上展示了他们的IA-32e架构新的处理器,这是一个利用兼容IA-32架构的64位扩展技术开发的64位微处理器,为了让大家尽早了解该项技术,本人特根据Intel公司提供的64-Bit Extension Technology Software Developer's Guide的部分内容翻译编辑了本文。
 一、操作模式
    具有64位扩展技术的处理器能运行在传统IA-32模式或IA-32e模式。传统的IA-32模式允许处理器运行在保护模式、实地址模式或虚拟8086模式。
    IA-32E模式是处理器在运行64位操作系统的时候使用的一种模式。带有64位扩展技术的处理器将初始进入传统的、页式地址、保护模式,然后,当IA32-EFER寄存器中的某位被设置并且PAE(Physical Address Extensions,物理地址扩展)模式被使能。下表显示了64位扩展技术所支持的操作模式和他们之间的区别。
 
1.IA-32e模式
   IA-32e模式有两个子模式:64位模式和兼容模式。IA-32e模式只能在装载64位操作系统的情况下进入。
2.64位模式
    64位模式用于运行在64位操作系统中的64位应用程序它支持以下的特性:
•        支持64位线性地址结构;然而支持64位扩展技术的IA-32处理器将用少于64位地址来实现
•        寄存器扩展后,可以使用新的操作码前缀来访问(REX)
•        现有的通用寄存器被加宽到64位(RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP)
•        8个新的通用寄存器(R8–R15)
•        8个新的128位流SIMD扩展(SSE)寄存器(XMM8–XMM15)
•        一个64位的指令指针(RIP)
•        一个新的RIP相关数据寻址模式
•        对单一的代码、数据和栈空间能用平板地址空间
•        扩展的和新的指令
•        支持大于64GB的物理地址;然而支持64位扩展技术的IA-32处理器的实际物理地址要特殊实现
•        新的中断优先级控制机制
     64位模式能够在以代码段为基础的操作系统中被使用。它的缺省地址大小是64位;它的缺省操作宽度大小是32位。注意这些缺省设置能够在使用新的REX操作码前缀的指令-指令对中被超越。当操作在64位模式下时,REX前缀允许指定一个64位操作数。利用这个机制,很多现有的指令被修改或重新定义来允许使用64位寄存器和64位地址。
3.兼容模式
    兼容模式允许传统的16位和32位应用程序无需重新编译就可以运行在64位操作系统下(然而运行在虚拟8086模式下或使用硬件任务管理中的传统应用程序将无法工作)。就像64位模式那样,操作系统在一个专门的代码段使能兼容模式。这意味着64位应用程序能运行在处理器中(64位模式)的同时,32位应用程序(没有为64位重编译的)运行再兼容模式。
    兼容模式像传统的保护模式。应用程序只能存取线性地址空间中的第一个4GB,处理标准IA-32指令前缀和寄存器。在兼容模式下不提供REX前缀。(REX前缀编码已经处理成传统IA-32指令)兼容模式也必须使用16位和32位地址和操作数。和传统保护模式一样,兼容模式也允许应用程序使用PAE(物理地址扩展)处理64GB的物理存储。
    下列传统保护模式下的项目,在兼容模式下不支持。
•        虚拟8086模式,任务切换和栈参数拷贝特性在兼容模式下不可用
•        从操作系统的角度看:使用64位机制替代32位机制来处理系统数据结构,地址变换,中断和异常处理等结构和事务。
4.传统模式为什么Intel现在做这件事情?
   传统模式包括保护模式,实地址模式和虚拟8086模式。现有的为这些模式中的任何一种模式而编写的软件都完全能兼容地运行在具有64为扩展技术的IA-32处理器中。
5.系统管理模式
    系统管理模式(SMM)提供与传统IA-32架构中的系统管理中断(SMI)处理程序相同的执行环境。SMM支持从一个模式到另一个操作模式(包括IA-32e和传统模式)的转换。一个SMI处理程序能够通过PSE机制处理任何的物理存储页。然而由于不支持PAE,SMM环境不支持64位线性地址。提交给SMI的事务,处理器将转换到SMM,并根据SMM存储映射(save map)将存储器的状态存储到SM RAM中。因此,一个SMI处理程序将执行在和传统IA-32架构中一样的环境中。
二、寄存器组的改变
    下表比较了运行在64位模式的应用程序和运行在传统的IA-32环境的应用程序中寄存器数据结构的不同。传统环境包括那些存在于现有IA-32处理器、支持64位扩展技术的处理器中的传统模式以及IA-32e兼容模式中的环境。兼容模式应用程序不能在64位模式或64位操作系统中运行,因此应用程序需要运行在传统IA-32保护模式环境中。
 
1.通用寄存器(General-Purpose Registers,GPRs)
    IA-32结构运行在传统或兼容模式时,有8个通用寄存器。AX, BX, CX, DX, DI, SI, BP, SP对16位操作数有效,EAX, EBX, ECX, EDX, EDI, ESI, EBP, ESP对32位操作数有效。
    在64位模式,缺省的操作数是32位的,然而GPRs可以针对32位和64位操作数。如果是32位操作数 EAX, EBX, ECX, EDX, EDI, ESI, EBP, ESP, R8D - R15D可用,如果是64位操作数RAX, RBX, RCX, RDX, RDI, RSI, RBP, RSP, R8-R15可用,R8-R15是8个新的GPRs。所有的这些寄存器能够具有字节、字、双字和四字四个级别。这些级别的划分主要是看REX前缀。
   在64位模式,将限制指令存取字节寄存器,指令不能同时使用传统的高字节(比如AH, BH. CH, DH)和新的字节寄存器(比如RAX寄存器的低字节)。然而指令将可同时用传统低字节(比如AL,BL,CL或DL)和新的字节寄存器(比如R8寄存器或RBP)。这种结构将强迫大家遵守以上的限制,并将任何代REX前缀的指令对高字节(AH, BH, CH, DH)的使用转换到低字节(BPL, SPL, DIL, SIL; 这些是RBP,R SP, RDI 和 RSI的低8位) 的使用。
    在64位模式下,操作数的大小决定了目标GPR的有效位数:
•        64位操作数产生一个64位的结果到目标通用寄存器
•        32位操作数产生32位的结果,采用0-扩充的方法将64位结果写到目标通用寄存器
•        8位和16位操作数产生一个8位或者16位的结果。目标通用寄存器的高56位或48位在操作中不会被修改。如果一个8位或16位操作数的结果被用作64位地址计算,则会对其进行符号扩展,扩展到64位。
    因为64位通用寄存器的高32位在32位模式中没有定义,所以当从64位模式转换到任何一种32位模式(比如传统模式或兼容模式)时,高32位的数据将不被保留。同样,在64位转换到32位模式之后,软件也不必要用这些没有定义的高字节位来存放数据。这些值回从一个硬件实现转换到下一个,或从一个周期转换到下一个。
2.流SIMD扩展(SSE)寄存器
    在兼容和传统模式下,SSE寄存器组由8个128位的传统寄存器XMM0-XMM7组成。在64位模式,有了8个附加的128位SSE寄存器,XMM8-XMM15。通过使用REX指令前缀访问这些存储器。XMM寄存器能够在任何模式下,在SSE, SSE2, SSE3指令中使用。
3.系统寄存器
    64位引入新的寄存器也改变了现有的系统寄存器。他们是:
•        MSRs.扩展特性允许MSR(IA-32_EFER)包含控制,允许与禁止64位扩展技术特性的那些位
•        控制寄存器。所有的控制寄存器扩充到64位,增加了一个新的控制寄存器(任务优先级寄存器CR8或TPR)
•        描述符表寄存器。全局描述符表寄存器(GDTR)和中断描述符表寄存器(IDTR)被扩展到10字节,以便他们能够包含全部64位地址。局部描述符表寄存器(LDTR)和任务寄存器也别扩展来包含64位地址。
•        调试寄存器。调试寄存器扩展到64位。
    1)扩展特性允许寄存器(IA-32_EFER)
    扩展特性语序寄存器(IA-32_EFER)包含处理器扩展特性控制位。该寄存器在地址C0000080H。下表是该寄存器的各位细节。
 
•        LMA(IA-32e模式激活,位10):该位是只读状态位,任何对该位的写入操作都将会被忽略。当IA-32e模式和页式管理被允许后,处理器将该位置1,这表明处理器运行在兼容模式或64位模式,具体在那个模式就要看代码段描述符的L位和D位的值。LMA=0时,处理器运行在传统模式,在这个模式下,处理器处理器的行为如同标准32位的IA-32处理器。
•        LME(IA-32e模式允许,位8):设置该位为1可以使处理器的能力转换到IA-32e模式,但是IA-32e模式并没有真正被激活,只有当软件使能PAE模式进行页式管理。当PAE页式管理被允许,并且LME被设置为1,处理器将设置LMA位为1,这指明IA-32e模式不仅被允许,同时被激活。IA32_EFER的其他所有保留位必须位0。
•        SCE(Syscall/Sysret允许,位0):这位设置为1将支持Syscall/Sysret。Syscall/Sysret只在64为模式下被支持。操作系统负责为64位操作来使能它。
    2)控制寄存器
     控制机存器CR0-CR4在64位扩展模式下被扩展到64位。在64位模式,MOV CRn指令读或写这些寄存器的全部64位。操作数宽度前缀被忽略。兼容和传统模式,控制寄存器的高32位被全部填0,读控制寄存器也只返回低32位。
    在64位模式,CR0和CR4的高32位被保留并且必须被写0。对高32位的任何一位进行写的结果是引起一般性保护异常,#GP(0)。CR2的所有64位都可通过软件来写。CR3的位[51:40]被保留,必须为0。然而MOV CRn指令不检查写到CR2或CR3的地址是否在线性地址或物理地址的实现界限内。
    64为扩展结构引入了一个新控制寄存器—CR8,它被定义为任务优先级寄存器(TPR)。操作系统能够基于中断的优先级别,使用TPR来控制是否允许外部中断来中断处理器。
    3)描述符表寄存器
    四个系统描述符表寄存器(GDTR, IDTR, LDTR和TR) 被扩展到能容下64位基地址。这允许运行在IA-32e模式的操作系统能够将描述符表定位在可用的线性地址空间的任何地方。下表给出了这四个寄存器。在所有的情况下,基地址必须符合范式。线性和物理地址位数能够用执行CPUID通过EAX设置80000008H来决定。
 
    4)调试寄存器
    在64位模式下,调试寄存器DR0-DR7是64位的。MOV DRn指令读或写所有的64个寄存器位。操作数宽度前缀被忽略。
     在IA-32e平台上所有16位模式或32位模式(传统模式或兼容模式)写调试寄存器时高32位全部填0,读调试寄存器的时候只返回低32位。在64位模式下,DR6和DR7的高32位保留并必须是0,对高32位的任何一位写1都会引发#GP(0)异常。
    DR0-DR3的所有64位都是软件能写的。然而MOV DRn指令不检查写到DR0-DR3的地址在线性地址的限制内。只在处理器产生有效地址的时候支持地址匹配。
 三、指令集变化
1.地址宽度和操作数宽度前缀
    64位模式中,缺省的地址宽度是64位,缺省的操作数宽度是32位。地址宽度和操作数宽度前缀允许32位和64位数据和地址在指令序列中混用。下表(1-7)显示了在IA-32e模式下需要指令前缀地址宽度。注意,在64位模式下不支持16位地址。在敬爱内容和传统模式下,地址宽度函数的功能和在IA-32传动架构中一样。
 
   下表(1-8)显示了66H指令前缀和REX.W前缀的有效组合来指定IA-32e操作模式下的操作数宽度问题。
    在64位模式下, 缺省的操作数宽度是32位,REX前缀包括4位域来指定16个不同的值。REX前缀的W位域指定为REX.W。REX.W=1时前缀表明操作数位64为操作数。注意,软件依然能使用操作数宽度66H前缀来切换到16位操作宽度。然而如果同时用REX.W和66H前缀,REX.W的优先权要高。
    在SSE/SSE2/SSE3 SIMD指令的情况下,66H, F2H和F3H前缀作为操作码扩展,并被认为是指令的一部分。在这些情况下,有效的REX.W前缀和66H代码扩展前缀之间没有相互关系。
 
2.REX前缀
   REX前缀是64位模式下引入的新的指令前缀字节,他作以下工作:
•        指定新的GPRs和SSE寄存器
•        指定64位代码宽度
•        指定扩展的控制寄存器(只给系统软件使用)
    不是所有的指令都需要REX前缀。这个前缀只在指令引用扩展的寄存器或使用64位操作数的时候才有必要。如果该前缀放在不需要的地方将会被忽略。
一个指令只能有一个REX前缀。这个前缀一旦使用,就必须直接放在操作码字节或两字节操作码扩展前缀之前。 其他位置的REX前缀将被忽略。
    包含有REX前缀的指令依然要遵循传统的15字节的指令宽度的限制。下图描述了REX前缀如何符合指令的字节次序的。
 
3.控制和调试寄存器的新编码
     在64位模式下,有为控制机存器和调试寄存器指定的附加的编码。当ModRM寄存器的域编码一个控制或调试寄存器的时候,REX.R位被用来修改这些域。这些编码允许处理器访问CR8-CR15和DR8-DR15。
     在64位模式中附加了一个控制寄存器(CR8)。CR8成为任务优先级寄存器(TPR)。在IA-32e技术的首次实现的时候,CR9-CR15和DR8-DR15都没有实现,对它们的访问将引起无效代码异常(#UD)。
4.新的指令
    下面的新指令在带有64位扩展的64位模式下被引入。
•        SWAPGS 指令
•        SYSCALL and SYSRET 指令
•        CDQE 指令
•        CMPSQ 指令
•        CMPXCHG16B 指令
•        LODSQ 指令
•        MOVSQ 指令
•        MOVZX(64-bits) 指令
•        STOSQ 指令
5.堆栈指针
    在64位模式,堆栈指针为64位。堆栈大小不是像兼容模式或传统模式中那样靠SS段描述符中的某位来控制,也不通过指令前缀来指示。
    对隐式堆栈引用将忽略地址大小的指示。除远分支以外,所有隐式引用RSP的指令在64位模式下缺省为64位操作数。影响到的指令包括:PUSH, POP, PUSHF, POPF, ENTER, 和LEAVE。使用这些指令在64位模式下将不可能产生32位堆栈值的压栈和退栈。如果使用66H操作数前缀,将支持16位的压栈和退栈。
     当寄存器RAX-RSP被用作操作数的时候,64位模式缺省的操作尺寸无需REX前缀作为这些指令的先导。如果式R8-R15作为操作数,则REX依然是需要的。这是因为前缀在访问新扩展寄存器中是需要的。
6.分支转移
    64位扩展技术扩充2个分支机制来适应64位线性地址空间的分支。他们是:
•        64位模式下近分支转移被重新定义
•        在64位模式和兼容模式下,64位调用门描述符定义成远调用
    64位模式下,所有近分支转移(CALL, RET, JCC, JCXZ, JMP 和 LOOP)被强迫为64位。这些指令被更新为提供64位的RIP值而无需REX前缀。下面的近转移被有效的操作数宽度所控制:
•        指令指针的宽度的截断
•        由于CALL或RET引起的退栈压栈或退栈的大小
•        由于CALL或RET而引起的堆栈指针增加或减少的大小
•        间接转移操作数大小
    在64位模式下,以上的所有操作都被强制为64位而不管操作数前缀(操作数大小的前缀被忽略)。然而相对转移的位移区域依然受到32位的限制;近转移的地址大小没有被强制为64位。
    地址大小影响到JCXZ和LOOP中RCX的大小;他们也影响到内存间接转移的地址计算。这样的地址缺省是64位,但是他们可以通过地址宽度前缀转换到32位宽度。
    软件会用远转移来改变优先级。传统IA-32结构提供调用门机制来允许软件去从一个优先级转到另一个优先级,尽管调用门也可以不改变优先级而只是做转移。当调用门使用的时候,直接或间接的选择器指针会指向一个门描述符(指令重的便宜被忽略)目的代码段的偏移可以从调用门描述符中获得。IA-32e模式重新定义了32位调用门描述符的类型值,使其成为64位调用门描述符,并扩展64位描述符使其能够容纳64位的偏移。64位模式调用门描述符允许远转移访问有效的线性地址空间的任何地方。这些调用门也控制代码段选择器(CS),允许转换到特权级和缺省尺寸并作为门转换的结果。
    因为通常情况下是指定32位的,唯一在64位模式下指定完全64位绝对RIP的是间接分支转移,由于这个原因,直接远分支转移被从64位模式的指令集中删除了。
    IA-32e模式扩充了SYSENTER和SYSEXIT指令的语义,以便他们操作在64位存储空间。IA-32e也引入了两个新的指令:SYSCALL和SYSRET,他们只在64位模式有效。

四、存储组织
1.64位模式下的地址计算
    在64位模式(如果没有地址大小的转变),有效地址计算的大小是64位的。一个有效地址计算使用一个64位的基和索引寄存器以及符号扩展变换成64位。
    对于64位模式下平面地址空间,线性地址等同于有效地址。在使用FS和GS段的非0为基的事务中,这个规则不被使用。在64位模式下,有效地址成分被加进来,并且有效地址在加64位基地址之前被缩短。地址映射模式在64位模式时,基地址从不会被缩短。
    在IA-32e模式下,指令指针被扩展到64位来支持64位代码偏移。64位指令指针在调用中将值赋给RIP。下表描述了RIP、EIP和IP之间的不同。
 
    通常,替换和直接在64位模式下不被扩展到64位。他们在有效地址计算中依然被限制在32位和符号扩展。然而,在64位模式提供了MOV指令的64位替换和直接形式的支持。
    所有的在IA-32e模式下的16位和32位地址计算用0扩展来形成64位地址。地址计算搜现是缩短到当前模式的有效地址宽度,就像地址宽度前缀的指定那样。其结果是用0扩展得到完全的64位地址宽度。因为这个,16位和32位应用程序运行在兼容模式只能存取64位模式有效地址的低4GB。同样,在64位模式产生一个32位地址只能访问64位模式有效地址的低4GB。
2.规范的寻址
    一个规范形式的地址有地址位63直到更有效的实现位,宏结构设置其为全1或全0。
    IA-32e模式定义一个64位的线性地址,但实现的时候支持的位数要少些。第一个具有64位扩展技术的IA-32e结构的处理器将支持48位线性地址。这意味着规范的地址必须将位63到位48全填0或全填1,填0还是填1要看位47是0还是1。
    尽管实现并不用先行地址的全部64位,他们需要检查位63知道更有效的实现位来看是否地址是规范形式。如果一个线性存储引用不是规范形式,该实现将会产生一个异常。在很多情况下,会产生一个一般保护异常(#GP)。然而,在显示或隐式对战应用的情况下,会产生一个堆栈错(#SS)。隐式堆栈引用指令包括PUSH/POP指令和使用RSP/RBP寄存器来作为缺省堆栈段寄存器的指令。在这些情况下,一个规范错误式#SF,如果一个指令使用RSP/RBP作为基寄存器并且有段超越给出一个非SS段,将引起一个一般保护错误(#GP)的规范错误。隐式堆栈引用包括所有PUSH/POP类型指令和任何使用RSP或RBP作为一个基寄存器。规范地址形式的检查将在特权检查之后页面和边界检查之前完成。
开始进行 64 位 Windows 系统编程之前需要了解的所有信息

发布日期: 2006-5-30 |更新日期: 2006-5-30
本文讨论:
•        64 位版本 Windows 的背景信息
•        适当地利用 x64 体系结构
•        使用 Visual C++ 2005 进行 x64 开发
•        针对 x64 版本的调试技术
本文使用以下技术:
Windows、Win64、Visual Studio 2005
 
本页内容
 
x64 操作系统

 
适当利用 x64

 
使用 Visual C++ 进行 x64 开发

 
使代码与 Win64 兼容

 
调试

 
关于托管代码

 
小结

使用 Windows® 先锋产品的乐趣之一是能够探究新技术以了解它的工作方式。实际上,我不太喜欢使用操作系统,直到对其内部结构有了一点深入了解之后。因此,当 Windows XP 64 位版本和 Windows Server® 2003 出现时,我简直快完蛋了。
Win64 和 x64 CPU 体系结构的优点是:它们与其前任完全不同,但不需要很长的学习过程。尽管开发人员认为迁移到 x64 只是一个重新编译的过程,但事实是我们仍然要在调试器中花费很多时间。拥有 OS 和 CPU 的应用知识十分宝贵。
本文,我将本人在 Win64 和 x64 体系结构方面的经验归结为一个高手 Win32® 程序员迁移到 x64 必备的几个要点。我假设您了解基本的 Win32 概念、基本的 x86 概念以及为什么代码应该在 Win64 上运行。这使我可以将关注的重点放在更重要的内容上。通过本概述,您可以在已经理解的 Win32 和 x86 体系结构基础上了解到一些重要差异。
有关 x64 系统的一个优点是:与基于 Itanium 的系统不同,您可以在同一台计算机上使用 Win32 或 Win64,而不会导致严重的性能损失。此外,除了 Intel 和 AMD x64 实现之间的几个模糊差异,与 x64 兼容的同一个 Windows 版本应该能够在这两个系统上运行。您不需要在 AMD x64 系统上使用一个 Windows 版本,在 Intel x64 系统上使用另一个版本。
我将讨论分为三大领域:OS 实现细节、适当地利用 x64 CPU 体系结构以及使用 Visual C++® 进行 x64 开发。
 
x64 操作系统
在 Windows 体系结构的所有概述中,我一般喜欢从内存和地址空间开始。尽管 64 位处理器在理论上寻址 16 EB 的内存 (264),但 Win64 目前支持 16 TB(由 44 位表示)。为什么不能在计算机中加载到 16 EB 以使用全部 64 位呢?原因有很多。
对初级用户而言,当前的 x64 CPU 通常只允许访问 40 位(1 TB)的物理内存。体系结构(不包括当前硬件)可以将其扩展到 52 位(4 PB)。即使没有该限制,映射如此大内存的页表大小也是巨大的。
与 Win32 中一样,可寻址范围分为用户模式区和内核模式区。每个进程都在底部获得其唯一的 8 TB,而内核模式的代码存在于顶部的 8 TB 中,并由所有进程共享。不同版本的 64 位 Windows 具有不同的物理内存限制,如图 1 和图 2 所示。
同样,与 Win32 中一样,x64 页大小为 4 KB。前 64 KB 的地址空间始终不映射,因此您看到的最低有效地址应该是 0x10000。与在 Win32 中不同,系统 DLL 在用户模式的地址范围顶部附近没有默认的加载地址。相反,它们在 4 GB 内存以上加载,通常在 0x7FF00000000 附近的地址上加载。
许多较新的 x64 处理器的一个出色功能是:支持 Windows 用于实现硬件数据执行保护 (DEP) 的 CPU No Execute 位。x86 平台上存在许多错误和病毒,这是因为 CPU 可以将数据当作合法代码字节执行。CPU 在供数据存储使用的内存中执行从而可终止缓冲区溢出(有意或无意)。通过 DEP,OS 可以在有效代码区域周围设置更清晰的边界,从而使 CPU 在执行超出这些预期边界时捕获到该事件。这推动着为使 Windows 减少受到的攻击而付出的不懈努力。
在为捕获错误而设计的活动中,x64 链接器将可执行文件默认的加载地址指定为在 32 位 (4 GB) 之上。这可以帮助在代码迁移到 Win64 之后能够在现有代码中快速找到这些区域。具体说,如果将指针存储为一个 32 位大小的值(如 DWORD),那么在 Win64 版本中运行时,它将被有效地截断,从而导致指针无效,进而触发访问冲突。该技巧使查找这些令人讨厌的指针错误变得非常简单。
有关指针和 DWORD 的主题将在 Win64 类型系统中继续讨论。指针有多大?LONG 怎么样?那么句柄(如 HWND)呢?幸好,Microsoft 在进行从 Win16 到 Win32 的复杂转换时,使新的类型模型能够轻松地进一步扩展到 64 位。一般地,除了个别几种情况外,新的 64 位环境中的所有类型(除了指针和 size_t)均与 Win32 中的完全相同。也就是说,64 位指针是 8 字节,而 int、long、DWORD 和 HANDLE 仍然是 4 字节。在随后讨论进行 Win64 开发时,我将讨论更多有关类型的内容。
Win64 的文件格式称为 PE32+。几乎从每个角度看,该格式在结构上都与 Win32 PE 文件完全相同。只是扩展了少数几个字段(例如,头结构中的 ImageBase),删除了一个字段,并更改了一个字段以反映不同的 CPU 类型。图 3 显示已更改的字段。
除 PE 头之外,没有太多的更改。有几个结构(例如,IMAGE_LOAD_CONFIG 和 IMAGE_THUNK_DATA)只是将某些字段扩展到 64 位。添加的 PDATA 区段很有趣,因为它突出了 Win32 和 Win64 实现之间的一个主要差异:异常处理。
在 x86 环境中,异常处理是基于堆栈的。如果 Win32 函数包含 try/catch 或 try/finally 代码,则编译器将发出在堆栈上创建小型数据块的指令。此外,每个 try 数据块指向先前的 try 数据结构,从而形成了一个链表,其中最新添加的结构位于表头。随着函数的调用和退出,该链表头会不断更新。如果发生异常,OS 将遍历堆栈上的数据块链表,以查找相应的处理程序。我在 1997 年 1 月的 MSJ 文章中非常详细地描述了该过程,因此这里只做简要说明。
与 Win32 异常处理相比,Win64(包括 x64 和 Itanium 版本)使用了基于表的异常处理。它不会在堆栈上生成任何 try 数据块链表。相反,每个 Win64 可执行文件都包含一个运行时函数表。每个函数表项都包含函数的起始和终结地址,以及一组丰富数据(有关函数中异常处理代码)的位置和函数的堆栈帧布局。请参见 WINNT.H 和 x64 SDK 中的 IMAGE_RUNTIME_FUNCTION_ENTRY 结构,了解这些结构的实质。
当异常发生时,OS 会遍历常规的线程堆栈。当堆栈审核遇到每个帧和保存的指令指针时,OS 会确定该指令指针属于哪一个可执行的模块。随后,OS 会在该模块中搜索运行时函数表,查找相应的运行时函数项,并根据这些数据制定适当的异常处理决策。
如果您是一位火箭科学家,并直接在内存中生成了代码而没有使用基本的 PE32+ 模块,该怎么办呢?这种情况也包含在内。Win64 有一个 RtlAddFunctionTable API,它可让您告诉 OS 有关动态生成的代码的信息。
基于表的异常处理的缺点(相对于基于堆栈的 x86 模型)是:在代码地址中查找函数表项所需的时间比遍历链表的时间要长。但优点是:函数没有在每次执行时设置 try 数据块的开销。
请记住,这只是一个简要介绍,而不是 x64 异常处理的完整描述,但是很令人激动,不是吗?有关 x64 异常模型的进一步概述,请参阅 Kevin Frei 的网络日记项。
与 x64 兼容的 Windows 版本不包含最新 API 的具体数量;大部分新的 Win64 API 都添加到针对 Itanium 处理器的 Windows 版本。简言之,现有的两个重要的 API,分别是 IsWow64Process 和 GetNativeSystemInfo。它们允许 Win32 应用程序确定是否在 Win64 上运行,如果是,则可以看到系统的真正功能。否则,调用 GetSystemInfo 的 32 位进程只能看到 32 位系统的系统功能。例如,GetSystemInfo 只会报告 32 位进程的地址范围。图 4 显示的 API 以前在 x86 上不可用,但可用于 x64。
尽管运行完全的 64 位 Windows 系统听起来很不错,但事实是,在某些情况下,您很可能需要运行 Win32 代码。为此,x64 版本的 Windows 包含 WOW64 子系统,以允许 Win32 和 Win64 进程在同一个系统上并行运行。但是,将 32 位 DLL 载入 64 位进程(反之亦然)则不受支持。(相信我,这是件好事。)您终于可以向 16 位旧式代码吻别了!
在 x64 版本的 Windows 中,从 64 位可执行文件启动的进程(如 Explorer.exe)只能加载 Win64 DLL,而从 32 位可执行文件启动的进程只能加载 Win32 DLL。当 Win32 进程调用内核模式(例如,读取文件)时,WOW64 代码会安静地截断该调用,并在适当的位置调用正确的 x64 等效代码。
当然,不同系统(32 位与 64 位)的进程需要能够互相通信。幸运的是,Win32 中您知道并喜爱的所有常规进程间通信机制也可以在 Win64 中工作,包括共享内存、命名管道以及命名同步对象。
您可能在想,"那么系统目录呢?同一个目录不能同时保存 32 位和 64 位版本的系统 DLL(例如,KERNEL32 或 USER32),不是吗"?通过执行可选择的文件系统重定向,WOW64 魔法般地为您解决了这个问题。来自 Win32 进程的文件活动通常转到 System32 目录,而不是在名为 SysWow64 的目录中。在内部,WOW64 会默默地更改这些请求以指向 SysWow64 目录。Win64 系统实际上有两个 \Windows\System32 目录 - 一个用于 x64 二进制文件,另一个用于 Win32 等效文件。
这看上去没什么,但会令人混淆。例如,我在某一点上使用了 32 位命令行提示(我自己并不知道)。当我针对 System32 目录中的 Kernel32.dll 运行 DIR 时,所得到的结果与我在 SysWow64 目录中执行相同操作后所得到的结果完全相同。我绞尽脑汁后才发现,文件系统重定向的工作方式就是这样。也就是说,即使我认为是在 \Windows\System32 目录中工作,但 WOW64 实际上已将调用重定向到 SysWow64 目录。顺便说一下,如果您确实希望从 x64 应用程序访问 32 位 \Windows\System32 目录,则 GetSystemWow64Directory API 会提供正确的路径。请一定阅读 MSDN® 文档,了解完整的信息。
除了文件系统重定向之外,WOW64 施加的另一个小魔法是注册表重定向。请考虑我前面提到的 Win32 DLL 不能载入 Win64 进程的内容,然后再考虑一下 COM 及其使用注册表加载进程内服务器 DLL 的情况。如果 64 位应用程序要使用 CoCreateInstance 创建一个在 Win32 DLL 中实现的对象,该怎么办呢?该 DLL 不能加载,对吗?WOW64 通过将来自 32 位应用程序的访问重定向到 \Software\Classes(以及相关的)注册节点,再一次节省了时间。实际结果是,Win32 应用程序的注册表视图与 x64 应用程序的不同(但大部分是相同的)。如您所料,OS 通过在调用 RegOpenKey 及友元时指定新的标记值,为 32 位应用程序提供了一个读取实际 64 位注册表值的应急方法。
更进一步说,后几个正中我下怀的 OS 差异涉及线程的局部数据。在 x86 版本的 Windows 中,FS 寄存器用于指向每个线程的内存区域,包括"最后一个错误"和线程的本地存储(分别是 GetLastError 和 TlsGetValue)。在 x64 版本的 Windows 中,FS 寄存器由 GS 寄存器取代。另外,它们的工作方式几乎完全相同。
虽然本文主要从用户模式角度讨论 x64,但有一项重要的内核模式体系结构附加内容需要说明。针对 x64 的 Windows 中有一项称为 PatchGuard 的新技术,该技术主要针对安全性和健壮性。简言之,能够更改关键内核数据结构(例如,系统调用表和中断调度表 (IDT))的用户模式程序或驱动程序会导致安全漏洞和潜在的稳定性问题。对于 x64 体系结构而言,Windows 家族决定不允许以不受支持的方式修改核心内存。强制该操作的技术是 PatchGuard。它使用内核模式线程监视对关键核心内存位置的更改。如果该内存被更改,则错误检测时系统将停止。
总之,如果您熟悉 Win32 体系结构,并且了解如何编写在它上面运行的本机代码,那么在迁移到 Win64 的过程中您就不会感到很惊奇了。您可以在很大程度上将其视为一个更广阔的环境。
 
适当利用 x64
现在,我们看一下 CPU 体系结构本身,因为对 CPU 指令集有一个基本的了解可以使开发(特别是调试)工作更轻松。在编译器生成的 x64 代码中,您将注意到的第一件事是,它与您了解并喜爱的 x86 代码是多么地相似。这对于了解 Intel IA64 编码的人们则完全不同。
随后您将注意到的第二件事是,注册名称与您所熟悉的略有不同,并且有很多名称。通用 x64 寄存器的名称以 R 开头,如 RAX、RBX 等等。这是针对 32 位 x86 寄存器的基于 E 的旧命名方案的发展演化。就像过去一样,16 位 AX 寄存器变为 32 位 EAX,16 位 BX 变为 32 位 EBX,以此类推。如果从 32 位版本转换,所有 E 寄存器都会变为其 64 位形态的 R 寄存器。因此,RAX 是 EAX 的继承者,RBX 超越 EBX,RSI 取代 ESI,以此类推。
此外,还添加了 8 个新的通用寄存器 (R8-R15)。主要的 64 位通用寄存器清单如图 5 所示。
此外,32 位 EIP 寄存器也会变为 RIP 寄存器。当然,32 位指令必须继续执行,以便这些寄存器(EAX、AX、AL、AH 等)的原始、较小类型的版本仍然可用。
为了照顾到图形和科学编程人员,x64 CPU 还有 16 个 128 位 SSE2 寄存器,分别以 XMM0 到 XMM15 命名。由 Windows 保存的 x64 寄存器的完整集合位于 WINNT.H 中定义的相应 #ifdef'ed _CONTEXT 结构中。
在任何时候,x64 CPU 不是以旧式的 32 位模式操作,就是以 64 位模式操作。在 32 位模式中,CPU 与任何其他 x86 类别的 CPU 一样对指令进行解码和操作。在 64 位模式中,CPU 对某些指令编码进行了少量调整,以支持新的寄存器和指令。
如果您熟悉 CPU 操作码编码模型,就会记得为新的指令编码提供的空间会很快消失,并且在 8 个新寄存器中挤出空间也不是一项轻松的任务。为此,一种方法是删除一些极少使用的指令。到目前为止,我留下的指令只有 64 位版本的 PUSHAD 和 POPAD,它们用于在堆栈上保存和恢复所有通用寄存器。释放指令编码空间的另一种方法是,在 64 位模式中完全消除区段。这样,CS、DS、ES、SS、FS 和 GS 的生命周期就结束了。没有太多人会想念它们的。
由于地址是 64 位的,您可能会担心代码大小。例如,下面是一个常见的 32 位指令:
CALL DWORD PTR [XXXXXXXX]

这里,用 X 表示的部分是一个 32 位地址。在 64 位模式中,这会变为 64 位地址,从而将 5 字节的指令变为 9 字节吗?幸运的是,答案是"否"。指令大小保持不变。在 64 位模式中,指令的 32 位操作数部分被视为相对于当前指令的数据偏移。一个示例可以更清楚地说明这一点。在 32 位模式中,以下是调用地址 00020000h 中存储的 32 位指针值的指令:
00401000: CALL DWORD PTR [00020000h]

在 64 位模式中,相同的操作码字节调用地址 00421000h (4010000h + 20000h) 中存储的 64 位指针值。这可以使您联想到,如果是自己生成代码,则这种相对寻址模式会造成重大分歧。您不能仅在指令中指定 8 字节的指针值,而是需要为实际 64 位目标地址驻留的内存位置指定一个 32 位相对地址。因而,有一个未提出的假设是:64 位目标指针必须在使用它的指令的 2GB 空间中。对大多数人而言,这并不是一个大问题,但如果您要生成动态代码或者修改内存中的现有代码,就会出现问题!
所有 x64 寄存器的一个主要优势是,编译器能够最终生成在寄存器中(而非堆栈上)传递大部分参数的代码。将参数推入堆栈会引发内存访问。我们都需要牢记,在 CPU 缓存中找不到的内存访问会导致 CPU 延迟许多个周期,以等待可用的常规 RAM 内存。
在设计调用约定时,x64 体系结构利用机会清除了现有 Win32 调用约定(如 __stdcall、__cdecl、__fastcall、_thiscall 等)的混乱。在 Win64 中,只有一个本机调用约定和 __cdecl 之类的修饰符被编译器忽略。除此之外,减少调用约定行为还为可调试性带来了好处。
您需要了解的有关 x64 调用约定的主要内容是:它与 x86 fastcall 约定的相似之处。使用 x64 约定,会将前 4 个整数参数(从左至右)传入指定的 64 位寄存器:
RCX: 1st integer argument
RDX: 2nd integer argument
R8: 3rd integer argument
R9: 4th integer argument

前 4 个以外的整数参数将传递到堆栈。该指针被视为整数参数,因此始终位于 RCX 寄存器内。对于浮点参数,前 4 个参数将传入 XMM0 到 XMM3 的寄存器,后续的浮点参数将放置到线程堆栈上。
更进一步探究调用约定,即使参数可以传入寄存器,编译器仍然可以通过消耗 RSP 寄存器在堆栈上为其预留空间。至少,每个函数必须在堆栈上预留 32 个字节(4 个 64 位值)。该空间允许将传入函数的寄存器轻松地复制到已知的堆栈位置。不要求被调用函数将输入寄存器参数溢出至堆栈,但需要时,堆栈空间预留确保它可以这样做。当然,如果要传递 4 个以上的整数参数,则必须预留相应的额外堆栈空间。
让我们看一个示例。请考虑一个将两个整数参数传递给子函数的函数。编译器不仅会将值赋给 RCX 和 RDX,还会从 RSP 堆栈指针寄存器中减去 32 个字节。在被调用函数中,可以在寄存器(RCX 和 RDX)中访问参数。如果被调用代码因其他目的而需要寄存器,可将寄存器复制到预留的 32 字节堆栈区域中。图 6 显示在传递 6 个整数参数之后的寄存器和堆栈。
 
图 6 传递整数

x64 系统上的参数堆栈清除比较有趣。从技术上说,调用方(而非被调用方)负责清除堆栈。但是,您很少看到在起始代码和结束代码之外的位置调整 RSP。与通过 PUSH 和 POP 指令在堆栈中显式添加和移除参数的 x86 编译器不同,x64 代码生成器会预留足够的堆栈空间,以调用最大目标函数(参数方法)所使用的任何内容。随后,在调用子函数时,它重复使用相同的堆栈区域来设置参数。
另一方面,RSP 很少更改。这与 x86 代码大不相同,在 x86 代码中,ESP 值随着参数在堆栈中的添加和移除而不断变化。
有一个示例可帮助说明这一点。请考虑一个调用三个其他函数的 x64 函数。第一个函数接受 4 个参数(0x20 个字节),第二个接受 12 个参数(0x60 个字节),第三个接受 8 个参数(0x40 个字节)。在起始代码中,生成的代码只需在堆栈上预留 0x60 个字节,并将参数值复制到 0x60 字节中的适当位置,以便目标函数能够找到它们。
您可以在 Raymond Chen 的网络日记中看到一个有关 x64 调用约定更详细的描述。我不会过多地讨论所有细节,仅在这里强调一些要点。首先,小于 64 位的整数参数进行了符号扩展,然后仍然通过相应的寄存器传递(如果在前 4 个整数参数内)。其次,任何参数所处的堆栈位置都应该是 8 字节的倍数,从而保持 64 位对齐。不是 1、2、4 或 8 字节的任何参数(包括结构)都是通过引用传递的。最后,8、16、32 或 64 位的结构和联合作为相同长度的整数传递。
函数的返回值存储在 RAX 寄存器中。如果返回到 XMM0 中的是浮点类型,就会引发异常。在所有调用中,以下寄存器必须保留:RBX、RBP、RDI、RSI、R12、R13、R14 和 R15。以下寄存器不稳定,可能会被毁坏:RAX、RCX、RDX、R8、R9、R10 和 R11。
我在前面提到过,作为异常处理机制的一部分,OS 会遍历堆栈帧。如果您曾经编写过堆栈遍历代码,就会知道 Win32 帧布局的这一特性可巧妙处理该过程。这种情况在 x64 系统上要好得多。如果某个函数需要分配堆栈空间,调用其他函数,保留任何寄存器或者使用异常处理,则该函数必须使用一组定义良好的指令来生成标准的起始代码和结束代码。
实行创建函数堆栈帧的标准方法是 OS 确保(在理论上)能够始终遍历堆栈的一种方法。除了一致、标准的起始代码,编译器和链接器还必须创建关联的函数表数据项。奇怪的是,所有这些函数项都在 IMAGE_FUNCTION_ENTRY64 的数组表(在 winnt.h 中定义)中结束。如何找到这个表呢?它由 PE 头的 DataDirectory 字段中的 IMAGE_DIRECTORY_ENTRY_EXCEPTION 项指出。
我在短短的一段中讨论了许多体系结构内容。但是,通过大体了解这些概念以及 32 位程序集语言的现有知识,您应该能够在一段较短的时间内了解调试器中的 x64 指令。总是实践出真知。
 
使用 Visual C++ 进行 x64 开发
尽管可以使用 Visual Studio® 2005 之前的 Microsoft® C++ 编译器编写 x64 代码,但这在 IDE 中是一项沉闷的体验。因此,在本文中,我假定您使用的是 Visual Studio 2005,并选择在默认安装中未启用的 x64 工具。我还假定您在 C++ 中拥有要为 x86 和 x64 平台构建的现有 Win32 用户模式项目。
针对 x64 构建的第一步是创建 64 位生成配置。作为一个优秀的 Visual Studio 用户,您应该已经知道项目在默认情况下有两种配置:Debug 和 Retail。这里,您只需创建另外两个配置:x64 形态下的 Debug 和 Retail。
首先,加载现有项目/解决方案。在 Build 菜单上,选择 Configuration Manager。在 Configuration Manager 对话框中,从 Active solution platform 下拉菜单中选择 New(参见图 7)。现在,您应该看到另一个标题为 New Solution Platform 的对话框。
 
图 7 创建新的生成配置

选择 x64 作为您的新平台(参见图 8),并将另一个配置保留为默认状态;然后单击 OK。就这么简单!现在,您应该拥有四个可能的生成配置:Win32 Debug、Win32 Retail、x64 Debug 和 x64 Retail。使用 Configuration Manager,您可以轻松地在它们之间切换。
现在,我们看一下您的代码与 x64 的兼容性。将 x64 Debug 配置设为默认值,然后生成项目。除非代码不重要,否则可能会收到一些不会在 Win32 配置中发生的编译器错误。除非您已经完全摒弃了编写可移植 C++ 代码的所有原则,否则修正这些问题以使代码能够随时用于 Win32 和 x64 相对比较轻松,而无需大量的条件编译代码。
 
图 8 选择生成平台

 
使代码与 Win64 兼容
将 Win32 代码转换为 x64,所需的最重要的工作可能是确保类型定义正确。还记得先前讨论的 Win64 类型系统吗?通过使用 Windows typedef 类型而非 C++ 编译器的本机类型(int、long 等),Windows 头使得编写干净的 Win32 x64 代码很轻松。您应该在自己的代码中继续保持这一点。例如,如果 Windows 将一个 HWND 传递给您,请不要仅仅为了方便就将其存储在 FARPROC 中。
升级完许多代码之后,我看到的最常见而简单的错误可能就是:假定指针值可以存储或传递到 32 位类型(如 int 和 long)甚至 DWORD 中。Win32 和 Win64 中的指针长度视需要而不同,而整数类型长度保持不变。但是,让编译器不允许指针存储在整数类型中也是不现实的。这是一个根深蒂固的 C++ 习惯。
解救方法是 Windows 头中定义的 _PTR 类型。DWORD_PTR、INT_PTR 和 LONG_PTR 之类的类型可让您声明整数类型的变量,并且这些变量始终足够长以便在目标平台上存储指针。例如,定义为 DWORD_PTR 类型的变量在针对 Win32 编译时是 32 位整数,在针对 Win64 编译时是 64 位整数。经过实践,我已经习惯了声明类型以询问"这里是否需要 DWORD 或者实际是指 DWORD_PTR 吗?"。
正如您期望的,可能有机会明确指定整数类型需要多少字节。定义 DWORD_PTR 及其友元的同一头文件 (Basetsd.h) 还可以定义特定长度的整数,如 INT32、INT64、INT16、UINT32 和 DWORD64。
与类型大小差异相关的另一个问题是 printf 和 sprintf 格式化。我对于在过去使用 %X 或 %08X 格式化指针值感到懊悔万分,并且在 x64 系统上运行该代码时还遇到了阻碍。正确的方法是使用 %p,%p 可以在目标平台上自动考虑指针大小。此外,对于与大小相关的类型,printf 和 sprintf 还具有 I 前缀。例如,您可能使用 %Iu 来打印 UINT_PTR 变量。同样,如果您知道该变量始终是 64 位标记值,则可以使用 %I64d。
在清除了无法用于 Win64 的类型定义所导致的错误之后,可能还有只能在 x86 模式下运行的代码。或者,您可能需要编写函数的两个版本,一个用于 Win32,另一个用于 x64。这就是一组预处理器宏的用武之地:
_M_IX86
_M_AMD64
_WIN64

正确使用预处理器宏对于编写正确的跨平台代码而言至关重要。_M_IX86 和 _M_AMD64 仅在针对特定处理器编译时进行定义。_WIN64 在针对任何 64 位版本的 Windows(包括 Itanium 版)编译时定义。
在使用预处理器宏时,请仔细考虑您的需要。例如,只需要代码真正特定于 x64 处理器,没有别的需要了吗?然后,使用与以下类似的代码:
#ifdef _M_AMD64

另一方面,如果同一代码既可以在 x64 又可以在 Itanium 上工作,则使用如下所示的代码可能更好:
#ifdef _WIN64

我发现一个有用的习惯是:只要使用其中一个宏,就始终显式创建 #else 情况,以便提前知道是否忘记了某些情况。请考虑以下编写错误的代码:
#ifdef _M_AMD64
// My x64 code here
#else
// My x86 code here
#endif

如果现在针对第三个 CPU 体系结构编译该代码,会发生什么情况?系统将无意识地编译我的 x86 代码。上面代码的一个更好的表达方式如下:
#ifdef _M_AMD64
// My x64 code here
#elif defined (_M_IX86)
// My x86 code here
#else
#error !!! Need to write code for this architecture
#endif

在我的 Win32 代码中无法轻松移植到 x64 的一部分代码是内联汇编,Visual C++ 不支持它的 x64 目标。不要害怕,汇编有办法。它提供了一个 64 位 MASM (ML64.exe),这在 MSDN 中有所说明。ML64.exe 和其他 x64 工具(包括 CL.EXE 和 LINK.EXE)可以从命令行调用。您可以只运行 VCVARS64.BAT 文件,该文件可以将它们添加到您的路径中。
 
调试
最后,您需要在 Win32 和 x64 版本上干净地编译代码。最后一个难题是运行和调试代码。无论是否在 x64 盒上生成 x64 版本,您都需要使用 Visual Studio 远程调试功能在 x64 模式下进行调试。幸运的是,如果您在 64 位计算机上运行 Visual Studio IDE,则 IDE 将为您执行以下所有步骤。如果您出于某些原因无法使用远程调试,则另一个选项是使用 x64 版本的 WinDbg。但是,您会失去 Visual Studio 调试器提供的许多调试优势。
如果您从未使用过远程调试,也不需要过于担心。一旦设置好,远程调试就可以像在本地一样无缝使用。
第一步是在目标计算机上安装 64 位 MSVSMON。这通常是通过运行 Visual Studio 随附的 RdbgSetup 程序来完成的。一旦 MSVSMON 运行,请使用 Tools 菜单为 32 位 Visual Studio 和 MSVSMON 实例之间的连接配置适当的安全设置(或者缺失)。
接下来,您需要在 Visual Studio 中将项目配置为针对 x64 代码使用远程调试,而不是尝试进行本地调试。您可以从调试项目的属性开始启动这个过程(参见图 9)。
 
图 9 调试属性

确定 64 位配置是当前配置,然后选择 Configuration Properties 下面的 Debugging。靠近顶端是标题为 Debugger to launch 的下拉菜单。通常,它设置为 Local Windows Debugger。将其更改为 Remote Windows Debugger。在下面,您可以指定在启动调试时要执行的远程命令(例如,程序名),以及远程计算机名和连接类型。
如果您正确设置了所有内容,就可以使用与启动 Win32 应用程序相同的方式开始调试 x64 目标应用程序。您可以知道是否已经成功连接到 MSVSMON,因为每次调试器成功连接后,MSVSMON 的跟踪窗口都会显示一个"connected"字符串。在这里,通常都是您知道并喜爱的同一个 Visual Studio 调试器。确保屏幕显示寄存器窗口,并查看所有这些出色的 64 位寄存器,然后转到反汇编窗口以查看"非常熟悉但略有不同的"x64 程序集代码。
请注意,不能将 64 位小型转储直接加载到 Visual Studio 之类的 32 位转储中,而是需要使用远程调试。此外,Visual Studio 2005 目前不支持本机 64 位代码和托管 64 位代码之间的互操作调试。
 
关于托管代码
使用 Microsoft .NET Framework 进行编码的一个优势是,大部分基础操作系统都归纳为通用代码。此外,IL 指令格式是 CPU 不可知的。因此,从理论上说,在 Win32 系统上生成的基于 .NET 的程序二进制文件应该无需修改就可以在 x64 系统上运行。但实际情况却有一点复杂。
.NET Framework 2.0 提供了 x64 版本。在 x64 计算机上安装 .NET Framework 2.0 之后,我能够运行先前在 Win32 环境中运行的 .NET 可执行文件。这真棒!当然,虽然不能保证每个基于 .NET 的程序无需重新编译就可以在 Win32 和 x64 上都运行良好,但它确实在一段合理的时间内"很有用"。
如果您的托管代码显式调用本机代码(例如,通过 C# 或 Visual Basic® 中的平台调用),则在尝试针对 64 位 CLR 运行时可能会遇到问题。但是,有一个编译器开关 (/platform) 可让您更清楚地了解代码应该在哪个平台上运行。例如,您可能希望托管代码在 WOW64 中运行,即使可以使用 64 位 CLR。
 
小结
总之,对于我而言,迁移到 x64 版本的 Windows 是一个相对比较轻松的经历。一旦您很好地掌握了 OS 体系结构和工具中相对较小的差异,就可以轻松地使一个代码基在这两个平台上运行。Visual Studio 2005 可从根本上使这些工作更加轻松。此外,由于每天都会出现更多特定于 x64 版本的设备驱动程序和工具(如 SysInternals.com 提供的 Process Explorer),因此没有理由不进行讨论!
Matt Pietrek 与他人合著有几本关于 Windows 系统级编程的书籍,以及 MSDN Magazine 的 Under the Hood 专栏。他以前曾是 NuMega/Compuware BoundsChecker 系列产品的首席架构师。现在,他是 Microsoft Visual Studio 小组的一员。
评论次数(1)  |  浏览次数(791)  |  类型(默认类型) |  收藏此文  | 

[  chinatree   发表于  2012-03-07 07:04  ]

收藏先。

 
 请输入验证码  (提示:点击验证码输入框,以获取验证码