内存负载何时会导致x86-64 linux上的总线错误?

我曾经认为,x86-64支持未alignment的内存访问和无效的内存访问总是导致分段错误(可能除SIMD指令,如movdqamovaps )。 不过最近我用普通的mov指令观察了总线错误。 这里是一个复制者:

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

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

mov 0(%rbp), %rdx指令和地址0x706a2e3630332d69是从越野车程序的coredump中复制的。 将其更改为0会导致0x706a2e3630332d60错误,但只是与0x706a2e3630332d60alignment仍然是总线错误(我的猜测是它与x86-64上的地址空间是48位有关)。

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

SIGBUS处于悲伤状态。 在不同的操作系统之间没有一致的意见,它的产生时间在操作系统,CPU架构,配置和月相之间差异很大。 除非你使用一个非常具体的配置,否则你应该像“ SIGSEGV ,但是不同”那样对待它。

我怀疑这本来应该是“你尝试了一个内存访问,不管内核怎么做都不可能成功”,所以换句话说,你在地址中确切的位模式永远不会成为有效的内存访问。 这通常意味着在严格的对齐体系结构上不对齐访问。 然后一些系统开始使用它来访问不存在的虚拟地址空间(比如在你的例子中,你所拥有的地址不能存在)。 那么意外的是,一些系统也意味着userland试图触及内核内存(因为至少在技术上它是从用户空间角度看不存在的虚拟地址空间)。 然后它变得随机。

除此之外,我已经看到了SIGBUS:

  • 从mmap:ed硬件访问不存在的物理地址。
  • 非exec映射的exec
  • 访问完全有效的映射,但过度承诺的内存不能在这个时候出现错误(我已经看到SIGSEGV,SIGKILL和SIGBUS在这里,至少有一个操作系统做不同的这取决于你在哪个体系结构)。
  • 内存管理僵局(和其他“有些可怕的错误,但是我们不知道什么是内存管理错误)。
  • 堆栈红色区域访问
  • 硬件错误(ECC内存,PCI总线奇偶校验错误等)
  • 访问mmap:ed文件,文件内容不存在(超过文件的末尾或一个洞)。
  • 访问文件内容应存在的mmap:ed文件,但不要(I / O错误)。
  • 访问正常内存换出和交换不能执行(I / O错误)。

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

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

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

POSIX专门要求生成SIGBUS的唯一情况是,当您创建一个文件支持的mmap区域,该区域延伸超出整个页面的支持文件末尾,然后访问足够远的地址。 (确切的单词是“从pa开始的地址范围内的引用,并且在对象结束之后继续len字节到整个页面应该导致传送SIGBUS信号”,来自mmap的规范 )。

在所有其他情况下,无论您是获得SIGSEGV还是SIGBUS,都无法访问内存,或根本没有任何信号,完全取决于实施。