我曾经认为x86-64支持未对齐的内存访问,无效的内存访问总是会导致分段错误(可能除了movdqa
或movaps
等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内核配置(即在页表,控制寄存器或类似的东西中)?
答案 0 :(得分:10)
SIGBUS
处于悲伤状态。在不同的操作系统之间,它应该意味着什么以及何时生成它们在操作系统,cpu架构,配置和月亮阶段之间的差异很大。除非您使用非常具体的配置,否则您应该只是对待它,而不是SIGSEGV
,但不同的是#34;。
我怀疑它本来应该意味着"你试过一个内存访问,无论内核做什么都不可能成功",换句话说,你在地址中的确切位模式永远不能成为有效的内存访问。最常见的是,这意味着严格对齐架构上的未对齐访问。然后,一些系统开始使用它来访问不存在的虚拟地址空间(例如,在您的示例中,您所拥有的地址可能不存在)。然后意外的是,一些系统也意味着userland试图触摸内核内存(因为至少在技术上它是从用户区的角度来看不存在的虚拟地址空间)。然后它变得随机。
除此之外我还见过SIGBUS:
答案 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,或者根本没有信号,都完全取决于实现。