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

Mik*_*sev 3 c linux x86 assembly

我曾经以为,支持x86-64的未对齐的内存访问和无效的内存访问总是引起分段错误(除非,也许,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;
}
Run Code Online (Sandbox Code Playgroud)

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

mov 0(%rbp), %rdx指令和地址0x706a2e3630332d69是从有缺陷的程序的coredump复制的.将其更改为0会导致段0x706a2e3630332d60错误,但只是对齐仍然是总线错误(我的猜测是它与地址空间在x86-64上的48位相关).

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

Art*_*Art 10

SIGBUS处于悲伤状态.不同的操作系统之间没有达成共识,它的意义和生成时间在操作系统,CPU架构,配置和月相之间变化很大.除非您使用非常具体的配置,否则您应该将其视为"就像SIGSEGV,但不同".

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

除此之外,我见过SIGBUS:

  • 从mmap:ed硬件访问不存在的物理地址.
  • 执行非执行映射
  • 访问完全有效的映射,但此时过度使用的内存不能出错(我在这里看过SIGSEGV,SIGKILL和SIGBUS,至少有一个操作系统根据你所使用的架构有不同的做法).
  • 内存管理死锁(和其他"一些可怕的错误,但我们不知道什么"内存管理错误).
  • 堆栈红区访问
  • 硬件错误(ECC内存,pci总线奇偶校验错误等)
  • 访问mmap:ed文件,其中文件内容不存在(超过文件末尾或空洞).
  • 访问mmap:ed文件,其中文件内容应该存在,但不存在(I/O错误).
  • 无法执行换出和交换的正常内存访问(I/O错误).


Ctx*_*Ctx 5

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

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

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

  • 所有文档都谈到地址被符号扩展。从技术上讲,它被定义为“位 48-63 必须是位 47 的副本”,但这就是符号扩展。将其视为二进制补码,会使地址空间为负值,并且更容易推理。 (3认同)