什么内存加载导致x86-64 linux上的总线错误?

时间:2016-01-26 09:32:27

标签: c linux assembly x86

我曾经认为x86-64支持未对齐的内存访问,无效的内存访问总是会导致分段错误(可能除了movdqamovaps等SIMD指令外。不过最近我用正常的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仍然是总线错误(我的猜测是它与地址空间在x86-64上的48位相关)。

问题是:哪些地址导致总线错误(SIGBUS)?它是由体系结构确定还是由OS内核配置(即在页表,控制寄存器或类似的东西中)?

3 个答案:

答案 0 :(得分:10)

SIGBUS处于悲伤状态。在不同的操作系统之间,它应该意味着什么以及何时生成它们在操作系统,cpu架构,配置和月亮阶段之间的差异很大。除非您使用非常具体的配置,否则您应该只是对待它,而不是SIGSEGV,但不同的是#34;。

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

除此之外我还见过SIGBUS:

  • 从mmap:ed hardware访问不存在的物理地址。
  • 非执行映射的执行
  • 访问完全有效的映射,但此时过度使用的内存不会出现故障(我在这里看过SIGSEGV,SIGKILL和SIGBUS,至少有一个操作系统会根据您使用的架构进行不同的操作#39;继续)。
  • 内存管理死锁(以及其他"可怕的错误,但我们不知道"内存管理错误)。
  • 堆叠红区访问
  • 硬件错误(ECC内存,pci总线奇偶校验错误等)
  • 访问mmap:ed文件,其中文件内容不存在(超过文件末尾或漏洞)。
  • 访问mmap:ed文件,其中文件内容应该存在,但不要(I / O错误)。
  • 访问换出的普通内存并且无法执行交换(I / O错误)。

答案 1 :(得分:3)

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

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

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

答案 2 :(得分:1)

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

在所有其他情况下,无论是获得无效内存访问的SIGSEGV还是SIGBUS,或者根本没有信号,都完全取决于实现。