“总线错误”消息是什么意思,它与分段错误有何不同?
18 回答
总线错误现在在 x86 上很少见,并且当您的处理器甚至无法尝试请求的内存访问时发生,通常:
- 使用地址不满足对齐要求的处理器指令。
访问不属于您的进程的内存时会发生分段错误。它们很常见,通常是以下原因的结果:
- 使用指向已释放对象的指针。
- 使用未初始化的因此伪造的指针。
- 使用空指针。
- 溢出缓冲区。
PS:更准确地说,不是操纵指针本身会导致问题。它正在访问它指向的内存(取消引用)。
段错误正在访问您无权访问的内存。它是只读的,您没有权限等...
总线错误正在尝试访问不可能存在的内存。您使用了对系统没有意义的地址,或者用于该操作的地址类型错误。
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.SA
andSCTLR_EL1.SA0
字段控制,我在这里进一步总结了相关文档。
我相信当应用程序在数据总线上出现数据错位时,内核会引发 SIGBUS。我认为,由于大多数处理器的大多数[?]现代编译器为程序员填充/对齐数据,因此(至少)过去的对齐问题得到了缓解,因此这些天人们不会经常看到 SIGBUS(AFAIK)。
来自:这里
我同意以上所有答案。这是我关于 BUS 错误的 2 美分:
程序代码中的指令不一定会出现 BUS 错误。当您运行二进制文件并且在执行期间,二进制文件被修改(被构建覆盖或删除等)时,可能会发生这种情况。
验证是否是这种情况
检查这是否是原因的一种简单方法是从构建输出目录启动相同二进制文件的几个实例,并在它们启动后运行构建。SIGBUS
在构建完成并替换二进制文件(两个实例当前正在运行的那个)后不久,两个正在运行的实例都会因错误而崩溃。
根本原因
这是因为操作系统交换内存页面,在某些情况下,二进制文件可能没有完全加载到内存中。当操作系统试图从同一个二进制文件中获取下一页时,就会发生这些崩溃,但该二进制文件自上次读取后发生了变化。
总线错误的一个典型实例是在某些架构上,例如SPARC(至少是一些 SPARC,也许这已经改变了),就是当您执行未对齐的访问时。例如:
unsigned char data[6];
(unsigned int *) (data + 2) = 0xdeadf00d;
此代码段尝试将 32 位整数值写入0xdeadf00d
(很可能)未正确对齐的地址,并将在这方面“挑剔”的体系结构上生成总线错误。顺便说一下,Intel x86不是这样的架构。它将允许访问(尽管执行速度较慢)。
我刚刚在 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 上,它给出了总线错误。为什么?我真的不知道。
当根目录为 100% 时出现总线错误。
这取决于您的操作系统、CPU、编译器以及可能的其他因素。
一般来说,这意味着 CPU 总线无法完成命令或发生冲突,但这可能意味着一系列事情,具体取决于正在运行的环境和代码。
它通常意味着未对齐的访问。
尝试访问实际不存在的内存也会产生总线错误,但如果您使用带有 MMU 的处理器和没有错误的操作系统,您将不会看到这一点,因为您不会有任何非-existent 内存映射到您的进程的地址空间。
我在 Mac OS X 上出现总线错误的原因是我试图在堆栈上分配大约 1Mb。这在一个线程中运行良好,但是在使用 openMP 时,这会导致总线错误,因为 Mac OS X对于非主线程的堆栈大小非常有限。
首先,SIGBUS 和 SIGSEGV 不是特定类型的错误,而是错误组或错误系列。这就是为什么您通常会看到信号编号 (si_no) 和信号代码 (si_code)。
它们还取决于操作系统和体系结构,以了解究竟是什么导致了它们。
一般我们可以这么说。SIGSEGV 与内存映射(权限、无映射)有关,即 mmu 错误。
SIGBUS 是指内存映射成功并且您遇到底层内存系统问题(内存不足、该位置没有内存、对齐、smmu 阻止访问等),即总线错误..
如果文件从系统中消失,例如您将文件映射到可移动媒体上并且它被拔出,则 SIGBUS 也可以与映射文件一起使用。
在平台上查看的一个好地方是 siginfo.h 标头,以了解信号子类型。例如对于 linux 这个页面提供了一个概述。 https://elixir.bootlin.com/linux/latest/source/include/uapi/asm-generic/siginfo.h#L245
/*
* SIGSEGV si_codes
*/
#define SEGV_MAPERR 1 /* address not mapped to object */
#define SEGV_ACCERR 2 /* invalid permissions for mapped object */
#define SEGV_BNDERR 3 /* failed address bound checks */
#ifdef __ia64__
# define __SEGV_PSTKOVF 4 /* paragraph stack overflow */
#else
# define SEGV_PKUERR 4 /* failed protection key checks */
#endif
#define SEGV_ACCADI 5 /* ADI not enabled for mapped object */
#define SEGV_ADIDERR 6 /* Disrupting MCD error */
#define SEGV_ADIPERR 7 /* Precise MCD exception */
#define SEGV_MTEAERR 8 /* Asynchronous ARM MTE error */
#define SEGV_MTESERR 9 /* Synchronous ARM MTE exception */
#define NSIGSEGV 9
/*
* SIGBUS si_codes
*/
#define BUS_ADRALN 1 /* invalid address alignment */
#define BUS_ADRERR 2 /* non-existent physical address */
#define BUS_OBJERR 3 /* object specific hardware error */
/* hardware memory error consumed on a machine check: action required */
#define BUS_MCEERR_AR 4
/* hardware memory error detected in process but not consumed: action optional*/
#define BUS_MCEERR_AO 5
#define NSIGBUS 5
最后要注意的是,所有信号也可以是用户生成的,例如 kill。如果它是用户生成的,那么 si_code 就是 SI_USER。所以特殊来源得到负 si_codes。
/*
* si_code values
* Digital reserves positive values for kernel-generated signals.
*/
#define SI_USER 0 /* sent by kill, sigsend, raise */
#define SI_KERNEL 0x80 /* sent by the kernel from somewhere */
#define SI_QUEUE -1 /* sent by sigqueue */
#define SI_TIMER -2 /* sent by timer expiration */
#define SI_MESGQ -3 /* sent by real time mesq state change */
#define SI_ASYNCIO -4 /* sent by AIO completion */
#define SI_SIGIO -5 /* sent by queued SIGIO */
#define SI_TKILL -6 /* sent by tkill system call */
#define SI_DETHREAD -7 /* sent by execve() killing subsidiary threads */
#define SI_ASYNCNL -60 /* sent by glibc async name lookup completion */
#define SI_FROMUSER(siptr) ((siptr)->si_code <= 0)
#define SI_FROMKERNEL(siptr) ((siptr)->si_code > 0)
我刚刚发现在 ARMv7 处理器上你可以编写一些代码,在未优化时给你一个分段错误,但是在使用 -O2 编译时它会给你一个总线错误(优化更多)。
我正在使用来自 Ubuntu 64 位的 GCC ARM gnueabihf 交叉编译器。
对我来说,我没有声明我的程序集正在返回该.text
部分,从而意外触发了“总线错误”。这似乎很明显,但它让我难倒了一段时间。
例如。
.globl _myGlobal # Allocate a 64-bit global with the value 2
.data
.align 3
_myGlobal:
.quad 2
.globl _main # Main function code
_main:
push %rbp
从数据返回代码时缺少文本指令:
_myGlobal:
.quad 2
.text # <- This
.globl _main
_main:
希望这最终对某人有所帮助
一个值得注意的原因是,如果您尝试映射不允许用户空间访问的 /dev/mem 区域,则会返回 SIGBUS。
导致总线错误的典型缓冲区溢出是,
{
char buf[255];
sprintf(buf,"%s:%s\n", ifname, message);
}
在这里,如果双引号 ("") 中的字符串大小大于 buf 大小,则会出现总线错误。
为了补充上面 blxtd 的回答,当您的进程无法尝试访问特定 'variable' 的内存时,也会发生总线错误。
for (j = 0; i < n; j++) {
for (i =0; i < m; i++) {
a[n+1][j] += a[i][j];
}
}
注意第一个“for循环”中变量“i”的“无意”使用?这就是在这种情况下导致总线错误的原因。