如何在x64上为我的进程启用对齐异常?

时间:2014-11-13 22:02:34

标签: windows assembly x86-64

我很想知道我的64位应用程序是否存在对齐错误。

来自Windows Data Alignment on IPF, x86, and x64

  

在Windows中,生成对齐错误的应用程序将引发异常EXCEPTION_DATATYPE_MISALIGNMENT

     
      
  • 在x64架构上,默认情况下禁用对齐例外,并且修复由硬件完成。 应用程序可以通过设置几个寄存器位来启用对齐异常 ,在这种情况下,除非用户让操作系统屏蔽{{1}的异常,否则将引发异常}}。 (有关详细信息,请参阅 AMD架构程序员手册第2卷:系统编程。
  •   
     

[埃德。强调我的

     
      
  • 在x86架构上,操作系统不会使对齐错误对应用程序可见。在这两个平台上,您还会在对齐故障上遇到性能下降,但是它的严重程度将远远低于Itanium,因为硬件会对内存进行多次访问以检索未对齐的数据。

  •   
  • 在Itanium 上,默认情况下,操作系统(OS)会使应用程序看到此异常,并且终止处理程序在这些情况下可能很有用。如果您没有设置处理程序,那么您的程序将挂起或崩溃。在清单3中,我们提供了一个示例,说明如何捕获EXCEPTION_DATATYPE_MISALIGNMENT异常。

  •   

忽略了参考 AMD架构程序员手册的方向,我将参考Intel 64 and IA-32 Architectures Software Developer’s Manual

  

5.10.5检查对齐

     

当CPL为3时,可以通过设置来检查内存引用的对齐方式   CR0寄存器中的AM标志和EFLAGS寄存器中的AC标志。未对齐的记忆   引用生成对齐异常(#AC)。处理器不生成   在特权级别0,1或2下操作时的对齐异常。有关a,请参阅表6-7   启用对齐检查时对齐要求的描述。

优异。我不确定这意味着什么,但非常好。

然后还有:

  

2.5控制寄存器

     

控制寄存器(CR0,CR1,CR2,CR3和CR4;见图2-6)确定操作   处理器的模式和当前正在执行的任务的特征。   在所有32位模式和兼容模式下,这些寄存器均为32位。

     

在64位模式下,控制寄存器扩展为64位。 MOV CRn指令   用于操作寄存器位。这些指令的操作数大小前缀   被忽略了。

     

控制寄存器总结如下,以及每个架构定义的控件   这些控制寄存器中的字段是单独描述的。在图2-6中,宽度为   64位模式的寄存器用括号表示(CR0除外)。    - CR0 - 包含控制操作模式和状态的系统控制标志   处理器

     

enter image description here

     

AM
  对齐掩码(CR0的第18位) - 启用自动对齐检查   什么时候设定清除时禁用对齐检查。对齐检查是   仅在AM标志置位时执行,EFLAGS寄存器中的AC标志为   设置,CPL为3,处理器在受保护或虚拟的情况下运行   8086模式。

我试过

我实际使用的语言是Delphi,但假装它是与语言无关的伪代码:

SEM_NOALIGNMENTFAULTEXCEPT

第一条指令

void UnmaskAlignmentExceptions()
{
   asm
      mov rax, cr0; //copy CR0 flags into RAX
      or  rax, 0x20000; //set bit 18 (AM)
      mov cr0, rax; //copy flags back
}

因特权指令例外而失败。

如何在x64上为我的进程启用对齐例外?

PUSHF

我发现x86有说明:

  • mov rax, cr0; PUSHF:在堆叠上打开/关闭EFLAGS的第一个16位
  • POPFPUSHFD:在堆栈中打开/关闭所有32位EFLAGS

enter image description here

然后我开始使用x64版本:

  • POPFDPUSHFQ:在堆栈上/下推送/弹出RFLAGS四元组

(在64位世界中,POPFQ被重命名为EFLAGS)。

所以我写道:

RFLAGS

它没有崩溃或触发保护异常。我不知道它是否符合我的要求。

奖金阅读

3 个答案:

答案 0 :(得分:11)

在x64上运行的应用程序可以访问标志寄存器(有时称为EFLAGS)。该寄存器中的位18允许应用程序在发生对齐错误时获得异常。所以从理论上讲,所有程序必须要做的就是修改对齐错误的异常是修改标志寄存器。

然而

为了使其真正起作用,操作系统内核必须设置cr0的位18以允许它。 Windows操作系统不会这样做。为什么不?谁知道?

应用程序无法在控制寄存器中设置值。只有内核才能做到这一点。设备驱动程序在内核中运行,因此他们也可以设置它。

可以通过创建设备驱动程序来查看并尝试使其工作(请参阅http://blogs.msdn.com/b/oldnewthing/archive/2004/07/27/198410.aspx#199239以及后面的注释)。请注意,这篇文章已经有十多年了,所以有些链接已经死了。

您可能还会发现此评论(以及此问题中的其他一些答案)非常有用:

  

Larry Osterman - 07-28-2004 2:22 AM

     

我们实际构建了一个版本的NT,并为x86打开了对齐例外(你可以像Skywing提到的那样)。

     

由于破坏的应用数量,我们很快将其关闭了。

答案 1 :(得分:0)

这适用于64位Intel CPU。某些AMD可能会失败

pushfq
bts qword ptr [rsp], 12h ; reset AC bit of rflags
popfq

它无法立即在32位CPU中工作,这些将首先需要内核驱动程序来更改CR0的AM位然后

pushfd
bts dword ptr [esp], 12h
popfd

答案 2 :(得分:0)

作为AC来查找由于访问不对齐而导致的速度下降的一种替代方法,您可以在mem_inst_retired.split_loadsmem_inst_retired.split_stores的Intel CPU上使用硬件性能计数器事件来查找加载/存储跨越缓存行边界。

perf record -c 10 -e mem_inst_retired.split_stores,mem_inst_retired.split_loads ./a.out 在Linux上应该很有用。 -c 10每10个硬件事件记录一次样本。如果您的程序对未对齐的访问进行了 lot 操作,而您只想查找真正的热点,请将其保留为默认设置。但是-c 10甚至可以在一次调用printf的微小二进制文件上获得有用的数据。像perf这样的其他-g选项可以照常记录每个示例工作上的父函数,并且可能有用。

在Windows上,使用您喜欢的任何工具来查看性能计数器。 VTune很受欢迎。


现代Intel CPU(P6家族及更高版本)在缓存行内未对齐不会受到任何惩罚https://agner.org/optimize/。实际上,在Intel CPU上,此类加载/存储甚至为guaranteed to be atomic(最多8个字节)。因此,AC的要求比必要的严格,但是它将帮助查找可能有风险的访问,这些访问可能是页面拆分或具有不同对齐数据的缓存行拆分。

AMD CPU可能会因跨越64字节缓存行内的16字节边界而受到处罚。我不熟悉那里提供哪些硬件计数器。请注意,如果有问题的访问从不跨越缓存行边界,则在Intel硬件上进行性能分析不一定会发现AMD CPU发生速度下降。

有关罚款的详细信息,请参见How can I accurately benchmark unaligned access speed on x86_64,包括我对Skylake上4k分割延迟和吞吐量的测试。

另请参阅http://blog.stuffedcow.net/2014/01/x86-memory-disambiguation/,以了解因Intel / AMD上未对齐的加载/存储而可能导致的存储转发效率损失。


使用AC集运行常规二进制文件并不总是可行的。编译器生成的代码可能选择使用未对齐的8字节加载或存储来复制多个结构成员,或存储一些文字数据。

gcc -O3 -mtune=generic(即启用优化的默认值)假定高速缓存行拆分足够便宜,足以冒使用未对齐访问而不是源访问多个窄访问的风险。在Skylake中,页面拆分的价格要便宜得多,从Haswell的100个周期降低到150个周期,再到Skylake的10个周期(与CL拆分相同的罚款),因为显然英特尔发现它们比以前想象的要少。

许多优化的库函数(例如memcpy)使用未对齐的整数访问。例如对于6字节的副本,glibc的memcpy将从缓冲区的开始/结尾进行2个重叠的4字节加载,然后进行2个重叠的存储。 (对于6个字节来做dword + word并没有特殊的情况,只是增加2的幂)。 This comment in the source解释了其策略。

因此,即使您的操作系统允许您启用交流功能,您也可能需要特殊版本的库,以免在整个地方触发小型memcpy之类的交流功能。


在数组上顺序循环时的对齐对于AVX512确实很重要,因为矢量与缓存行的宽度相同。如果您的指针未对齐,则每次访问都是高速缓存行拆分,而AVX2不仅如此。对齐总是更好的方法,但是对于许多算法来说,大量的计算与内存访问混合在一起,对AVX512来说只有很大的不同。

如果未按其他方式触摸线,则跨越高速缓存线边界的零散未对齐访问实质上具有两倍于触摸两条线的高速缓存足迹。