总线错误和分段错误之间的区别?是否会发生程序第一次出现段错误并停止而第二次出现总线错误并退出的情况?
7 回答
在我使用过的大多数架构上,区别在于:
- 当您访问您不打算访问的内存时(例如,在您的地址空间之外),会导致 SEGV。
- SIGBUS 是由于 CPU 的对齐问题引起的(例如,尝试从不是 4 的倍数的地址中读取 long)。
SIGBUS
mmap()
如果您是一个文件并尝试访问超出文件末尾的映射缓冲区的一部分,以及空间不足等错误情况,也会引发此问题。如果您使用 注册信号处理程序sigaction()
并设置SA_SIGINFO
,则可以让您的程序检查错误的内存地址并仅处理内存映射文件错误。
例如,当您的程序尝试执行硬件总线不支持的操作时,可能会导致总线错误。例如,在SPARC上,尝试从奇数地址读取多字节值(例如 int,32 位)会产生总线错误。
例如,当您执行违反分段规则的访问时,即尝试读取或写入您不拥有的内存时,就会发生分段错误。
将您的问题(可能不正确)解释为“我间歇性地获得 SIGSEGV 或 SIGBUS,为什么它不一致?”,值得注意的是,C 或 C++ 标准不保证用指针做狡猾的事情会导致段错误;这只是“未定义的行为”,作为我曾经说过的教授,这意味着它可能会导致鳄鱼从地板上出现并吃掉你。
因此,您的情况可能是您有两个错误,其中第一个错误有时会导致 SIGSEGV,而第二个错误(如果没有发生段错误并且程序仍在运行)会导致 SIGBUS。
我建议您使用调试器逐步完成,并注意鳄鱼。
我假设您正在谈论Posix 定义的SIGSEGV
and信号。SIGBUS
SIGSEGV
当程序引用无效地址时发生。SIGBUS
是实现定义的硬件故障。这两个信号的默认操作是终止程序。
程序可以捕捉到这些信号,甚至可以忽略它们。
程序第一次出现段错误并停止,第二次可能出现总线错误并退出,是否会发生这种情况?
是的,即使对于一个相同的错误:这是一个来自 macOS 的严重但简单的示例,它可以通过数组边界之外的索引以确定性的方式产生分段错误 (SIGSEGV) 和总线错误 (SIGBUS)。上面提到的未对齐访问不是 macOS 的问题。(这个例子不会导致任何 SIGBUS,如果它在调试器中运行,lldb
在我的例子中!)
bus_segv.c:
#include <stdlib.h>
char array[10];
int main(int argc, char *argv[]) {
return array[atol(argv[1])];
}
该示例从命令行获取一个整数,该整数用作数组的索引。这些是一些不会导致任何信号的索引值(甚至在数组之外)。(给出的所有值都取决于标准段/节大小。我使用 clang-902.0.39.1 在 High Sierra macOS 10.13.5、i5-4288U CPU @ 2.60GHz 上生成二进制文件。)
高于 77791 和低于 -4128 的索引将导致分段错误 (SIGSEGV)。24544 将导致总线错误 (SIGBUS)。这里是完整的地图:
$ ./bus_segv -4129
Segmentation fault: 11
$ ./bus_segv -4128
...
$ ./bus_segv 24543
$ ./bus_segv 24544
Bus error: 10
...
$ ./bus_segv 28639
Bus error: 10
$ ./bus_segv 28640
...
$ ./bus_segv 45023
$ ./bus_segv 45024
Bus error: 10
...
$ ./bus_segv 53215
Bus error: 10
$ ./bus_segv 53216
...
$ ./bus_segv 69599
$ ./bus_segv 69600
Bus error: 10
...
$ ./bus_segv 73695
Bus error: 10
$ ./bus_segv 73696
...
$ ./bus_segv 77791
$ ./bus_segv 77792
Segmentation fault: 11
如果您查看反汇编代码,您会发现存在总线错误的范围的边界并不像索引显示的那样奇怪:
$ otool -tv bus_segv
bus_segv:
(__TEXT,__text) section
_main:
0000000100000f60 pushq %rbp
0000000100000f61 movq %rsp, %rbp
0000000100000f64 subq $0x10, %rsp
0000000100000f68 movl $0x0, -0x4(%rbp)
0000000100000f6f movl %edi, -0x8(%rbp)
0000000100000f72 movq %rsi, -0x10(%rbp)
0000000100000f76 movq -0x10(%rbp), %rsi
0000000100000f7a movq 0x8(%rsi), %rdi
0000000100000f7e callq 0x100000f94 ## symbol stub for: _atol
0000000100000f83 leaq 0x96(%rip), %rsi
0000000100000f8a movsbl (%rsi,%rax), %eax
0000000100000f8e addq $0x10, %rsp
0000000100000f92 popq %rbp
0000000100000f93 retq
通过leaq 0x96(%rip), %rsi
, rsi 成为数组起始地址的(PC 相对确定的)地址:
rsi = 0x100000f8a + 0x96 = 0x100001020
rsi - 4128 = 0x100000000 (below segmentation fault)
rsi + 24544 = 0x100007000 (here and above bus error)
rsi + 28640 = 0x100008000 (below bus error)
rsi + 45024 = 0x10000c000 (here and above bus error)
rsi + 53216 = 0x10000e000 (below bus error)
rsi + 69600 = 0x100012000 (here and above bus error)
rsi + 73696 = 0x100013000 (below bus error)
rsi + 77792 = 0x100014000 (here and above segmentation fault)
lldb
可能会设置具有不同页面限制的过程。我无法在调试会话中重现任何总线错误。因此,调试器可能是解决总线错误吐出二进制文件的一种解决方法。
安德烈亚斯
这将是什么是总线错误?,如果不是因为
是否会发生程序第一次出现段错误并停止而第二次出现总线错误并退出的情况?
问题的一部分。您应该能够使用此处找到的信息自己回答这个问题。
精神错乱:一遍又一遍地做同样的事情,却期待不同的结果。
- 艾尔伯特爱因斯坦
当然,从字面上理解这个问题......
#include <signal.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
int main() {
srand(time(NULL));
if (rand() % 2)
kill(getpid(), SIGBUS);
else
kill(getpid(), SIGSEGV);
return 0;
}
Tada,一个程序可以在一次运行时出现分段错误并在另一次运行时出现总线错误退出。