【问题标题】:Prohibit unaligned memory accesses on x86/x86_64禁止 x86/x86_64 上未对齐的内存访问
【发布时间】:2012-08-03 23:18:33
【问题描述】:

我想在 x86/x86_64 上模拟具有禁止未对齐内存访问的系统。 是否有一些调试工具或特殊模式可以做到这一点?

在使用专为 SPARC 或其他类似 CPU 设计的软件 (C/C++) 时,我想在多台 x86/x86_64 PC 上运行许多(CPU 密集型)测试。但我对 Sparc 的访问受到限制。

据我所知,Sparc 总是检查内存读取和写入中的对齐是否自然(从任何地址读取一个字节,但仅当地址可被 4 整除时才允许读取 4 字节字)。

可能是 Valgrind 或 PIN 有这种模式?还是编译器的特殊模式? 我正在寻找 Linux 非商业工具,但也允许使用 windows 工具。

或者可能在 EFLAGS 中有秘密的 CPU 标志?

【问题讨论】:

标签: linux debugging x86 memory-alignment


【解决方案1】:

我刚刚阅读了链接到维基百科文章Segmentation Fault 的问题Does unaligned memory access always cause bus errors?

在文章中,有一个非常罕见的提醒 Intel processor flags AC aka Alignment Check。

这是启用它的方法(来自维基百科的Bus Error examplered-zone clobber bug 固定用于 x86-64 System V,因此这在 Linux 和 MacOS 上是安全的,converted from Basic asm 在函数内部从来都不是一个好主意:您希望对 AC 的更改按内存访问顺序进行。

#if defined(__GNUC__)
# if defined(__i386__)
    /* Enable Alignment Checking on x86 */
    __asm__("pushf\n orl $0x40000,(%%esp)\n popf" ::: "memory");
# elif defined(__x86_64__) 
     /* Enable Alignment Checking on x86_64 */
    __asm__("add $-128, %%rsp \n"    // skip past the red-zone, in case there is one and the compiler has local vars there.
            "pushf\n"
            "orl $0x40000,(%%rsp)\n"
            "popf \n"
            "sub $-128, %%rsp"       // and restore the stack pointer.
           ::: "memory");       // ordered wrt. other mem access
# endif
#endif

启用后,它的工作原理与 /proc/cpu/alignment 中的 ARM 对齐设置非常相似,请参阅答案 How to trap unaligned memory access? 以获取示例。

另外,如果您使用 GCC,我建议您启用 -Wcast-align 警告。在为具有严格对齐要求的目标(例如 ARM)构建时,GCC 将报告可能导致内存访问未对齐的位置。

但请注意,libc 的 memcpy 和其他功能的手写 asm 仍然会进行未对齐的访问,因此在 x86(包括 x86-64)上设置 AC 通常不实用。 GCC 有时会发出 asm,即使您的源代码没有,也会进行未对齐的访问,例如作为一次复制或归零两个相邻数组元素或结构成员的优化。

【讨论】:

  • 在最近的 Linux 上使用它的任何人的注意事项:C 库将在 strcmp 中崩溃,它用于动态加载程序。所以在运行之前执行export LD_BIND_NOW=1,这样 ld.so 将在启动时解析所有库符号,而不是按需解析。
  • 还有“STAC”指令“0F 01 CB”-felixcloutier.com/x86/STAC.html“设置 EFLAGS 寄存器中的 AC 标志位。这可以启用用户模式数据访问的对齐检查。”
  • @osgx: stac 是新的具有 SMAP(监督模式访问保护)功能(Broadwell?),在权限级别 > 0 是非法的。即它在用户空间出现故障。用户空间必须继续使用 pushf/popf 为自己设置 AC。 IDK 为什么他们决定不让 stac/clac 在用户空间中解码,因为这是用户空间可以使用堆栈执行的操作。
  • 警告:在 x86_64 上启用它的代码将破坏 gcc 鼻子下的红色区域。
  • @JosephSible-ReinstateMonica:好点,我在这里修好了。维基百科上的代码现在在 Bus Error 文章中;我也可能会设法更新该页面。
【解决方案2】:

这很棘手,我没有亲自做过,但我认为你可以通过以下方式做到:

x86_64 CPU(特别是我检查过 Intel Corei7,但我猜其他 CPU 也有)有一个性能计数器 MISALIGN_MEM_REF,它可以计算未对齐的内存引用。

首先,您可以运行您的程序并在 Linux 下使用“perf”工具来计算您的代码已完成的未对齐访问次数。

更棘手和有趣的 hack 是编写一个内核模块,对性能计数器进行编程以在溢出时生成中断并让它溢出第一个未对齐的加载/存储。在内核模块中响应此中断,但向您的进程发送信号。

这实际上会将 x86_64 变成不支持非对齐访问的内核。

但这并不简单 - 除了您的代码之外,系统库还使用未对齐的访问,因此将它们与您自己的代码分开会很棘手。

【讨论】:

  • “对性能计数器进行编程以产生中断的内核模块”——当我们进行分析时,这不是一种 perf/oprofile 模式吗? (perf record -e MISALIGN_MEM_REF:u -c 1.) perf 已经有代码来分离库和用户代码。 perf 的中断不会停止程序;但 perf 将记录未对齐访问的位置。我认为这种模式比杀死程序并做一个一个修复更有帮助。
  • @osgx 你是对的。如果以与不支持未对齐加载/存储的 CPU 相同的方式生成类似异常的中断并不重要,则可以使用“perf record -e MISALIGN_MEM_REF:u -c 1”来查找执行它们的程序,我同意。
  • @osgx,您的上述命令适用于哪个版本的 perf?我必须在我的 perf_3.13(Ubuntu 14.04)上使用-e alighment-faults,但它从不记录我的测试代码的任何实际错误,其中包含明确的错误。
  • Nathan Kidd,不要使用 perf 的高级事件“alignment-faults”(它没有映射到 x86 上的任何东西),找到 CPU 的原始硬件 perf 事件。并非每个 Intel CPU 都有事件 MISALIGN_MEM_REF。
【解决方案3】:

GCC 和 Clang 都内置了 UndefinedBehaviorSanitizer。其中一项检查,alignment,可以通过 -fsanitize=alignment 启用。它会发出代码在运行时检查指针对齐情况,如果取消引用未对齐的指针,则中止。

【讨论】:

  • 很好,在为具有快速已知安全未对齐访问的平台(如 x86)优化窄对齐访问时,它应该捕获 C 源代码级别未对齐的指针,而不会绊倒编译器故意生成的潜在未对齐访问.此外,memcpy 和其他 libc 函数在手写 asm 中使用未对齐的访问(例如,对于 glibc 中的小型非 2 次幂大小的副本)。所以启用 x86 的 AC 标志一般是不可用的
【解决方案4】:

也许您可以通过所有对齐的动作编译成 SSE。带有movaps 的未对齐访问是非法的,并且可能会在其他架构上表现为非法未对齐访问。

【讨论】:

  • 我认为,并非我的代码中的每个操作都是可矢量化的。任务是找到所有未对齐的访问。
  • 使用SSE不需要向量化代码,它可以做标量运算。
  • @JensBjörnhager。是的,但只有 16 字节的加载和存储具有需要对齐的版本,例如 movapsmovdqa。像movss(单标量)、movsd(双标量)和movd/movq 这样的窄指令就像普通的GP 整数mov,不需要任何对齐。 (除非您启用 AC 标志。)当然,如果 GCC 知道 指针可能未按 16 对齐,它将自动使用 movups 进行矢量化。即使已知它是按 4 或 8 对齐的。
猜你喜欢
  • 2010-11-07
  • 2013-10-21
  • 2011-07-31
  • 1970-01-01
  • 1970-01-01
  • 2014-06-25
  • 2013-05-09
  • 1970-01-01
相关资源
最近更新 更多