C语言 内存加载何时导致 x86-64 Linux 上的总线错误



我曾经认为x86-64支持未对齐的内存访问,无效的内存访问总是会导致分段错误(也许除了像movdqamovaps这样的SIMD指令)。然而,最近我观察到正常的mov指令的总线错误。这是一个复制器:

void test(void *a)
{
    asm("mov %0, %%rbpnt"
        "mov 0(%%rbp), %%rdxnt"
        : : "r"(a) : "rbp", "rdx");
}
int main()
{
    test((void *)0x706a2e3630332d69);
    return 0;
}

(必须使用省略帧指针进行编译,例如 gcc -O test.c && ./a.out )。

mov 0(%rbp), %rdx指令和地址0x706a2e3630332d69是从错误程序的核心转储中复制的。将其更改为 0 会导致段错误,但仅对齐0x706a2e3630332d60仍然是总线错误(我的猜测是这与 x88-64 上的地址空间为 48 位这一事实有关)。

问题是:哪些地址会导致总线错误(SIGBUS)?它是由架构决定还是由操作系统内核配置(即在页表中、控制寄存器或类似的东西中)?

SIGBUS处于

悲伤状态。不同的操作系统之间没有达成共识,它应该意味着什么,何时生成它,在操作系统、CPU 架构、配置和月相之间差异很大。除非您使用非常具体的配置,否则您应该将其"就像SIGSEGV一样,但不同"。

我怀疑最初它应该意味着"您尝试了无论内核做什么都不可能成功的内存访问",因此换句话说,您在地址中的确切位模式永远不可能是有效的内存访问。最常见的是,这意味着在严格对齐架构上进行未对齐的访问。然后一些系统开始使用它来访问不存在的虚拟地址空间(就像在你的例子中,你拥有的地址不能存在)。然后偶然地,一些系统也意味着用户空间试图接触内核内存(因为至少从技术上讲,从用户空间的角度来看,它是不存在的虚拟地址空间)。然后它变得只是随机的。

除此之外,我还从以下位置看到了SIGBUS:

  • 从 mmap:ed 硬件访问不存在的物理地址。
  • 非执行映射的执行
  • 访问完全有效的映射,但目前无法出错(我在这里看到了SIGSEGV,SIGKILL和SIGBUS,至少有一个操作系统根据您使用的架构而有所不同)。
  • 内存管理死锁(以及其他"出了点可怕的错误,但我们不知道是什么"内存管理错误)。
  • 堆栈红色区域访问
  • 硬件错误(ECC 内存、PCI 总线奇偶校验错误等)
  • 访问文件内容不存在的 mmap:ed 文件(超过文件末尾或孔)。
  • 访问 mmap:ed 文件,其中文件内容应存在,但不存在(I/O 错误)。
  • 无法访问已换出和换入的正常内存(I/O 错误)。

通常,可以在未对齐的内存访问上发送SIGBUS,即当将 64 位整数写入未对齐的地址时,该地址不是 8 字节对齐的。但是,在最近的系统中。硬件本身正确处理它(尽管比对齐的访问慢一点),或者操作系统在异常处理程序中模拟访问它(具有 2 个或更多单独的内存访问)。

在这种情况下,问题是指定了允许的虚拟地址空间之外的地址。尽管指针具有 64 位,但只有 0-(2^48-1) (0x0-0xffffffffffff) 的地址空间在当前的 64 位英特尔处理器上有效。Linux 为其进程提供的地址空间更少,从 0-(2^47-1)(即 0-0x7fffffffffff),其余的 (0x800000000000-0xffffffffffff) 由内核使用。

这意味着,内核发送SIGBUS是因为访问无效地址(每个地址>= 0x800000000000),而不是SIGSEGV,这意味着发生了对有效地址的访问错误(缺少页面条目、错误的访问权限等)。

POSIX 特别需要生成 SIGBUS 的唯一情况是,当您创建一个文件支持的mmap区域,该区域超出支持文件的末尾超过整个页面,然后访问足够远的地址。 (确切的单词是"地址范围内的引用从 pa 开始,并在对象结束后继续从 len 字节到整个页面,将导致 SIGBUS 信号的传递mmap

在所有其他情况下,您是否获得 SIGSEGV 或 SIGBUS 以进行无效内存访问,或者根本没有信号,完全取决于实现。

最新更新