我曾经认为,x86-64支持未alignment的内存访问和无效的内存访问总是导致分段错误(可能除SIMD指令,如movdqa
或movaps
)。 不过最近我用普通的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
错误,但只是与0x706a2e3630332d60
alignment仍然是总线错误(我的猜测是它与x86-64上的地址空间是48位有关)。
问题是:哪些地址导致总线错误(SIGBUS)? 是由架构决定还是由操作系统内核configuration(即在页表,控制寄存器或类似的东西)?
SIGBUS
处于悲伤状态。 在不同的操作系统之间没有一致的意见,它的产生时间在操作系统,CPU架构,配置和月相之间差异很大。 除非你使用一个非常具体的配置,否则你应该像“ SIGSEGV
,但是不同”那样对待它。
我怀疑这本来应该是“你尝试了一个内存访问,不管内核怎么做都不可能成功”,所以换句话说,你在地址中确切的位模式永远不会成为有效的内存访问。 这通常意味着在严格的对齐体系结构上不对齐访问。 然后一些系统开始使用它来访问不存在的虚拟地址空间(比如在你的例子中,你所拥有的地址不能存在)。 那么意外的是,一些系统也意味着userland试图触及内核内存(因为至少在技术上它是从用户空间角度看不存在的虚拟地址空间)。 然后它变得随机。
除此之外,我已经看到了SIGBUS:
一般来说,一个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,都无法访问内存,或根本没有任何信号,完全取决于实施。