当前位置: 首页 > 知识库问答 >
问题:

为什么对MMAP内存的非对齐访问有时会在AMD64上分段?

孟振
2023-03-14
#include <inttypes.h>
#include <stdlib.h>

#include <sys/mman.h>

int main()
{
  uint32_t sum = 0;
  uint8_t *buffer = mmap(NULL, 1<<18, PROT_READ,
                         MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
  uint16_t *p = (buffer + 1);
  int i;

  for (i=0;i<14;++i) {
    //printf("%d\n", i);
    sum += p[i];
  }

  return sum;
}

如果我将循环的迭代次数减少到小于14次,它将不再分段故障。如果我从循环中打印数组索引,它也不再分段错误。

为什么在能够访问未对齐地址的CPU上,未对齐内存访问会出现分段故障,为什么只有在这种特定的情况下才会出现这种情况?

共有1个答案

吕昀
2023-03-14

相关:Pascal Cuoq的博客文章显示了GCC假设对齐指针的情况(两个int*不部分重叠):GCC总是假设对齐指针访问。他还链接到2016年的一篇博客文章(一个错误故事:x86上的数据对齐),该文章与这个问题有完全相同的错误:指针不对齐的自动向量化->SegFault。

GCC4.8做了一个循环序言,试图达到一个对齐边界,但它假设uint16_t*p是2字节对齐的,即一些标量迭代将使指针16字节对齐。

我不认为gcc打算在x86上支持不对齐的指针,它只是碰巧适用于没有自动向量化的非原子类型。在ISO C中,使用指向uint16_t的指针以小于alignof(uint16_t)=2的对齐方式绝对是未定义的行为。当GCC看到您在编译时违反规则时,它不会发出警告,实际上它会生成工作代码(对于malloc,它知道返回值的最小对齐方式),但这可能只是GCC内部的意外,不应该被视为“支持”的指示。

14是GCC的启发式决定在此函数中自动向量化循环的最小阈值,使用-O3和no-march-mtune选项。

我将您的代码放在Godbolt上,这是main的相关部分:

    call    mmap    #
    lea     rdi, [rax+1]      # p,
    mov     rdx, rax  # buffer,
    mov     rax, rdi  # D.2507, p
    and     eax, 15   # D.2507,
    shr     rax        ##### rax>>=1 discards the low byte, assuming it's zero
    neg     rax       # D.2507
    mov     esi, eax  # prolog_loop_niters.7, D.2507
    and     esi, 7    # prolog_loop_niters.7,
    je      .L2
    # .L2 leads directly to a MOVDQA xmm2, [rdx+1]

它计算出(通过这段代码)在到达MOVDQA之前要做多少个标量迭代,但没有一个代码路径导致MOVDQU循环。也就是说,gcc没有代码路径来处理p为奇数的情况。

    call    malloc  #
    movzx   edx, WORD PTR [rax+17]        # D.2497, MEM[(uint16_t *)buffer_5 + 17B]
    movzx   ecx, WORD PTR [rax+27]        # D.2497, MEM[(uint16_t *)buffer_5 + 27B]
    movdqu  xmm2, XMMWORD PTR [rax+1]   # tmp91, MEM[(uint16_t *)buffer_5 + 1B]
#include <string.h>

int sum(int *p) {
    int sum=0;
    for (int i=0 ; i<10001 ; i++) {
        // sum += p[i];
        int tmp;
#ifdef USE_ALIGNED
        tmp = p[i];     // normal dereference
#else
        memcpy(&tmp, &p[i], sizeof(tmp));  // unaligned load
#endif
        sum += tmp;
    }
    return sum;
}
.L4:    # gcc7.2 normal dereference
    add     eax, 1
    paddd   xmm0, XMMWORD PTR [rdx]
    add     rdx, 16
    cmp     ecx, eax
    ja      .L4
.L2:   # gcc7.2 memcpy for an unaligned pointer
    movdqu  xmm2, XMMWORD PTR [rdi]
    add     rdi, 16
    cmp     rax, rdi      # end_pointer != pointer
    paddd   xmm0, xmm2
    jne     .L2           # -mtune=generic still doesn't optimize for macro-fusion of cmp/jcc :(

    # hsum into EAX, then the final odd scalar element:
    add     eax, DWORD PTR [rdi+40000]   # this is how memcpy compiles for normal scalar code, too.

但有时这不是一个选择。memcpy在复制一个原语类型的所有字节时,完全使用现代gcc/clang进行了相当可靠的优化。即只加载或存储,不调用函数,也不跳转到额外的内存位置。即使在-O0中,这个简单的memcpy内联,没有函数调用,当然tmp也不会被优化。

无论如何,如果您担心编译器生成的asm在更复杂的情况下或使用不同的编译器时可能无法优化,请检查它。例如,ICC18不使用memcpy自动向量化版本。

uint64_t tmp=0;,然后在低3字节上的memcpy编译为实际的内存副本并重新加载,因此这不是表示奇数大小类型的零扩展的好方法。

typedef int __attribute__((aligned(1), may_alias)) unaligned_aliasing_int;

typedef unsigned long __attribute__((may_alias, aligned(1))) unaligned_aliasing_ulong;
unaligned_aliasing_int *p = something;
int tmp = *p++;
int tmp2 = *p++;
 类似资料:
  • 问题内容: 我想用x86 / x86_64上禁止的未对齐内存访问来模拟系统。有一些调试工具或特殊模式可以做到这一点吗? 当使用为SPARC或其他类似CPU设计的软件(C / C ++)时,我想在几台x86 / x86_64 PC上运行许多(CPU密集型)测试。但是我对Sparc的访问受到限制。 据我所知,Sparc总是检查内存读写的对齐是否自然(从任何地址读取一个字节,但仅当地址被4整除时才读取4

  • 也许我误解了什么,但x86中的未对齐访问似乎带来了安全问题,例如返回地址完整性问题。 > 为什么x86设计器首先允许未对齐的访问?(性能是我能想到的唯一好处。) 如果x86设计人员允许这种未对齐的访问问题,他们应该知道如何解决它,不是吗?是否可以使用静态技术或清理技术检测未对齐的访问?

  • 问题内容: 我正在开发一个宠物的开源项目,该项目实现了一些流密码算法,并且只有在ARM处理器上运行该bug时,我才遇到问题。我什至尝试在qemu下的x86中运行ARM二进制文件,但该错误并未在那里触发。 该错误的具体机制仍然难以捉摸,但是我最好的选择是相信它是由程序中未对齐的内存访问尝试引起的,这是qemu实现的,但被开发板中的真正ARM处理器默默忽略了。 因此,由于该问题很难诊断,所以我想知道是

  • 问题内容: 我尝试测量NUMA的非对称内存访问效果,但失败了。 本实验 在2.93GHz,2个CPU,8核的Intel Xeon X5570上执行。 在固定到核心0的线程上,我使用numa_alloc_local在核心0的NUMA节点上分配了大小为10,000,000字节的数组 x 。然后我遍历数组 x 50次,并读取和写入数组中的每个字节。测量经过的时间以执行50次迭代。 然后,在服务器中的每个

  • 我想模拟x86/x86_64上禁止未对齐内存访问的系统。是否有一些调试工具或特殊模式来执行此操作? 当使用为SPARC或其他类似CPU设计的软件(C/C)时,我想在几台x86/x86_64PC上运行许多(CPU密集型)测试。但是我对Sparc的访问是有限的。 正如我所知,Sparc总是检查内存读写的对齐是否正常(从任何地址读取一个字节,但仅当地址可被4整除时才允许读取一个4字节的字)。 可能是Va

  • 当目标指令集为x86/x64时,未对齐的内存读写不会导致错误的结果;而在Emscripten环境下,编译目标为asm.js与WebAssembly时,情况又各有不同。 info 这里“未对齐”的含义是:欲访问的内存地址不是欲访问的数据类型大小的整数倍。 4.2.1 asm.js C代码如下: //unaligned.cc struct ST { uint8_t c[4]; float f; }