什么是总线错误?


254

“总线错误”消息是什么意思,它与段错误有什么区别?


5
我想对两者都添加一个简单的解释:分段错误表示您正在尝试访问不允许访问的内存(例如,它不属于程序的一部分)。但是,在发生总线错误时,通常意味着您正在尝试访问不存在的内存(例如,尝试访问12G的地址,但您只有8G的内存),或者超出了可用内存的限制。
xdevs23 '17

您在哪个平台上看到了这个?电脑?苹果电脑?x86?32/64?
Peter Mortensen

Answers:


243

如今,总线错误在x86上很少见,并在处理器甚至无法尝试请求的内存访问时发生,通常是:

  • 使用地址不满足其对齐要求的处理器指令。

当访问不属于您的进程的内存时,会发生分段错误,它们很常见,通常是由于以下原因导致的:

  • 使用指向已释放对象的指针。
  • 使用未初始化的虚假指针。
  • 使用空指针。
  • 缓冲区溢出。

PS:更准确地说,这不是操纵会导致问题的指针本身,而是访问它指向的内存(取消引用)。


105
它们并不罕见。我只是从“如何艰难地学习C”开始的练习9中,已经遇到了一个问题
3684年

24
总线错误的另一原因(无论如何在Linux上)是操作系统无法使用物理内存来备份虚拟页面时(例如,低内存条件或使用大页面内存时无法使用大页面。)通常,mmap(和malloc)只是保留虚拟地址空间,然后内核按需分配物理内存(即所谓的软页面错误。)创建足够大的malloc,然后对其进行足够的写入,您将得到总线错误。
Eloff,2015年

1
对我来说,包含的分区/var/cache只是完整的askubuntu.com/a/915520/493379
c33s

2
就我而言,方法为存储回调的对象提供static_cast了一个void *参数(一个属性指向该对象,另一个属性指向该方法)。然后调用该回调。但是,传递的内容void *完全不同,因此方法调用导致总线错误。
Christopher K.

@bltxd您知道总线错误的性质吗?也就是说,环形总线上的消息是否具有某种机制,即环形站点上的站点也接受它发送的消息,但是该消息发送到了目的地,因为这表明它已经绕过了整个环形通道并且未被接受。我猜想行填充缓冲区返回错误状态,并且当它退出时会刷新管道并调用正确的异常微例程。这基本上要求内存控制器接受其范围内的所有地址,这表明当BAR等发生更改时,它必须在内部进行
Lewis Kelsey,

84

段故障正在访问您不允许访问的内存。它是只读的,您没有权限,等等。

总线错误正在尝试访问可能不存在的内存。您使用了对系统无意义的地址,或者该操作使用了错误的地址。


14

mmap 最小POSIX 7示例

内核发送SIGBUS到进程时发生“总线错误” 。

一个由于ftruncate遗忘而产生它的最小示例:

#include <fcntl.h> /* O_ constants */
#include <unistd.h> /* ftruncate */
#include <sys/mman.h> /* mmap */

int main() {
    int fd;
    int *map;
    int size = sizeof(int);
    char *name = "/a";

    shm_unlink(name);
    fd = shm_open(name, O_RDWR | O_CREAT, (mode_t)0600);
    /* THIS is the cause of the problem. */
    /*ftruncate(fd, size);*/
    map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    /* This is what generates the SIGBUS. */
    *map = 0;
}

运行:

gcc -std=c99 main.c -lrt
./a.out

在Ubuntu 14.04中测试。

POSIX 描述 SIGBUS为:

访问内存对象的未定义部分。

MMAP规范说:

在地址范围内从pa开始并在整个对象的末尾连续len个字节到整个页面的引用,将导致SIGBUS信号的传递。

shm_open 说它生成大小为0的对象:

共享内存对象的大小为零。

因此,*map = 0我们正在触及分配对象的末尾。

ARMv8 aarch64中未对齐的堆栈内存访问

在以下位置提到此问题:什么是总线错误?SPARC,但在这里我将提供一个更可重现的示例。

您需要的是一个独立的aarch64程序:

.global _start
_start:
asm_main_after_prologue:
    /* misalign the stack out of 16-bit boundary */
    add sp, sp, #-4
    /* access the stack */
    ldr w0, [sp]

    /* exit syscall in case SIGBUS does not happen */
    mov x0, 0
    mov x8, 93
    svc 0

然后,该程序在ThunderX2服务器计算机上的Ubuntu 18.04 aarch64,Linux内核4.15.0上引发SIGBUS 。

不幸的是,我无法在QEMU v4.0.0用户模式下重现它,我不确定为什么。

故障似乎是可选的,由SCTLR_ELx.SASCTLR_EL1.SA0字段控制,我在这里对相关文档进行了进一步的总结。


11

我相信,当应用程序在数据总线上显示数据未对齐时,内核会提高SIGBUS。我认为,由于大多数处理器的大多数现代编译器都会为程序员填充/对齐数据,因此(至少)减轻了以往的对齐麻烦,因此,最近几天人们很少看到SIGBUS(AFAIK)。

来自:这里


1
取决于您对代码执行的棘手技巧。如果您做一些愚蠢的事情(例如做指针数学),然后进行类型转换以访问问题模式,则可以触发BUS错误/对齐陷阱(即,设置uint8_t数组,在该数组的指针上添加一,二或三,然后进行类型转换X86系统几乎可以让您执行此操作,尽管这确实会降低性能。 某些 ARMv7系统将允许您执行此操作-但大多数ARM,MIPS,Power等将在您身上苦苦挣扎。
斯瓦尔塔夫(Svartalf)2014年

6

当由于某种原因而无法分页代码页时,您也可以获得SIGBUS。


7
当我在运行过程中更新.so文件时,通常会发生这种情况
baddeveloper 2015年

发生的另一个原因是,如果您尝试mmap的文件大小大于/dev/shm
ilija139

3

我在OS X上进行C编程时遇到的总线错误的特定示例:

#include <string.h>
#include <stdio.h>

int main(void)
{
    char buffer[120];
    fgets(buffer, sizeof buffer, stdin);
    strcat("foo", buffer);
    return 0;
}

以防万一您不记得文档strcat通过更改第一个参数将第二个参数附加到第一个参数(翻转参数并可以正常工作)。在Linux上,这会产生分段错误(如预期的那样),但在OS X上,则会出现总线错误。为什么?我真的不知道


堆栈溢出保护可能会引起总线错误。
2015年

1
"foo"存储在内存的只读段中,因此无法对其进行写入。它不会是堆栈溢出保护,而不会是内存写保护(如果程序可以自行重写,这是一个安全漏洞)。
Mark Lakata

3

总线错误的一个经典实例是在某些架构上,例如SPARC(至少某些SPARC,也许已更改)是在您进行错误对齐的访问时。例如:

unsigned char data[6];
(unsigned int *) (data + 2) = 0xdeadf00d;

此代码段尝试将32位整数值写入0xdeadf00d(最有可能)未正确对齐的地址,并且会在这方面“挑剔”的体系结构上产生总线错误。顺便说一下,Intel x86 不是这样的体系结构,它将允许访问(尽管执行速度较慢)。


1
万一我有数据[8];现在,它是32位体系结构中4的倍数。因此,它是对齐的。我现在仍然会收到错误消息吗?另外,请解释一下,对指针进行数据类型转换是否不是一个好主意。它会在脆弱的体系结构上引起未对准错误。请详细说明,它将对我有帮助。
灵巧的2013年

嘿。与其说类型转换,不如说是您对指针数学进行了指针转换。仔细一下上面的代码。编译器已经仔细地用双字对齐了您的数据指针,然后通过将引用偏移两次并类型转换为非常需要双字对齐的非双字边界访问,从而将编译器上的所有内容搞砸了。
Svartalf 2014年

“脆弱”不是我要用于所有这些词的词。X86机器和代码使人们在一段时间内做了相当愚蠢的事情,这就是其中之一。如果遇到这种问题,请重新考虑您的代码-首先,它在X86上的性能不是很好。
Svartalf 2014年

@Svartalf:在x86上,对未对齐指针的字访问肯定比对对齐指针的字访问慢,但至少从历史上看,它们比简单代码要快,后者无条件地将内容组装成字节,并且比尝试尝试的代码更简单使用各种大小操作的最佳组合。我希望C标准将包括将较大的整数类型打包到较小的整数/字符序列中/从较小的整数/字符序列中解压缩的方法,以便让编译器使用给定平台上最佳的方法。
supercat 2015年

@Supercat:事情就是这样-您在X86上摆脱了它。您可以在ARM,MIPS,Power等设备上进行尝试,然后您会遇到麻烦的事情。在低于Arch V7的ARM上,您的代码将出现对齐失败-在V7上,如果为其设置了运行时,则可以对性能造成严重影响。您只是根本不想这样做。直言不讳,这是不好的做法。:D
Svartalf

2

这取决于您的操作系统,CPU,编译器以及其他可能的因素。

通常,这意味着CPU总线无法完成命令或发生冲突,但这可能意味着各种情况,具体取决于运行的环境和代码。

-亚当


2

通常,这意味着未对齐的访问。

尝试访问物理上不存在的内存也会导致总线错误,但是如果您使用的处理器带有MMU和没有错误的操作系统,则不会看到此错误,因为您不会遇到任何错误。 -已存在的内存映射到您进程的地址空间。


2
我的i7当然有一个MMU,但是在OS X上学习C时(传递未初始化的指针scanf),我仍然遇到此错误。这是否意味着OS X Mavericks有故障?在非越野车操作系统上的行为将是什么?
Calvin Huang


1

在Mac OS X上出现总线错误的原因是,我试图在堆栈上分配大约1Mb。这在一个线程中效果很好,但是当使用openMP时,这会导致总线错误,因为Mac OS X 对于非主线程的堆栈大小非常有限。


1

我同意以上所有答案。这是我关于BUS错误的2美分:

程序代码中的指令不必引起BUS错误。当您运行二进制文件并且在执行过程中,二进制文件被修改(被构建覆盖或删除等)时,可能会发生这种情况。

验证是否存在这种情况: 检查是否是这种原因的一种简单方法是启动运行相同二进制文件的实例并运行构建。SIGBUS构建完成并替换二进制文件(两个实例当前正在运行的二进制文件)后不久,两个正在运行的实例都会因错误而崩溃。

根本原因: 这是因为OS交换内存页面,在某些情况下,二进制文件可能未完全加载到内存中,并且当OS尝试从同一二进制文件中获取下一页时,会发生这些崩溃,但是二进制文件自上次使用以来已发生更改阅读。


同意,这是我经验中最常见的总线错误原因。
itaych

0

为了补充上面的答案,当您的进程无法尝试访问特定“变量”的内存时,也会发生总线错误。

for (j = 0; i < n; j++) {
    for (i =0; i < m; i++) {
        a[n+1][j] += a[i][j];
    }
}

注意“ 无意的”使用变量“I”第一个“for循环”?这就是在这种情况下导致总线错误的原因。


如果m> = n,则外循环将执行一次或完全不执行,具体取决于i的既有值。如果m <n,它将无限期地随着j索引的增加而运行,直到您将超出数组的范围,并且极有可能导致分段错误,而不是总线错误。如果这段代码可以编译,那么访问变量“ i”本身的内存就没有问题。抱歉,这个答案是错误的。
itaych

0

我刚刚发现很难在ARMv7处理器上编写一些代码,这些代码在未优化时会给您带来分段错误,但在使用-O2进行编译时会给您带来总线错误(优化更多信息)。

我正在使用来自Ubuntu 64位的GCC ARM gnueabihf交叉编译器。


这如何回答这个问题?
Peter Mortensen

-1

导致总线错误的典型缓冲区溢出是:

{
    char buf[255];
    sprintf(buf,"%s:%s\n", ifname, message);
}

如果双引号(“”)中字符串的大小大于buf大小,则会出现总线错误。


1
呵呵...如果是这种情况,您将有BUS错误问题,而不是一直在Windows和其他计算机上阅读的堆栈粉碎漏洞。BUS错误是由于尝试访问“内存”而导致的,而该内存由于地址无效而无法访问。(因此,术语“ BUS”错误。)这可能是由于许多故障,包括无效的对齐等而导致的,只要处理器无法将地址放在总线上即可。
Svartalf 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.