【问题标题】:Bus error vs Segmentation fault总线错误与分段错误
【发布时间】:2010-10-24 17:22:42
【问题描述】:

总线错误和分段错误之间的区别? 是否会发生程序第一次出现段错误并停止,第二次出现总线错误并退出的情况?

【问题讨论】:

标签: c segmentation-fault bus-error sigbus


【解决方案1】:

我假设您说的是 Posix 定义的 SIGSEGVSIGBUS 信号。

SIGSEGV 在程序引用无效地址时发生。 SIGBUS 是实现定义的硬件故障。这两个信号的默认动作是终止程序。

程序可以捕捉到这些信号,甚至可以忽略它们。

【讨论】:

    【解决方案2】:

    例如,当您的程序尝试执行硬件总线不支持的操作时,可能会导致总线错误。例如,在SPARCs 上,尝试从奇数地址读取多字节值(例如 int,32 位)会产生总线错误。

    例如,当您执行违反分段规则的访问时会发生分段错误,即尝试读取或写入您不拥有的内存。

    【讨论】:

    • 当您说“读取或写入您不拥有的内存”时是什么意思?当您执行 malloc 时,您分配了 5 个字节的内存。如果你读/写你不拥有的内存,它不会在 C 中给你一个 Seg 错误。
    • 相反,覆盖同一地址空间中某个其他对象拥有的内存会导致分段错误??
    • 操作系统级别的“您拥有的”通常比运行时向您展示的(例如通过 malloc)要多得多。因此,您拥有的内存空间很大,但仍然不应该访问,并且您可以读取但不能写入的大量地址空间(大多数映射库)以及写入保护内存区域的特定功能(mprotect) .
    • @Geek:操作系统无法知道“谁”在同一地址空间内进行写入。因此,它不能保护您不覆盖同一程序中的内存。这就是大多数安全漏洞起作用的原因。
    • 与 Pax 和 Bastien 相比,我显然很烂。 :) 但是,@Thunderboltz,正如其他评论者(和 P&B)所解释的那样,当您尝试访问不属于您的内存时会发生段错误。
    【解决方案3】:

    在我使用过的大多数架构上,区别在于:

    • 当您访问不该访问的内存时(例如,在您的地址空间之外)会导致 SEGV。
    • SIGBUS 是由 CPU 的对齐问题引起的(例如,尝试从不是 4 的倍数的地址中读取长整数)。

    【讨论】:

    • 内存映射文件也可以生成SIGBUS。
    • 如果您从非 4 字节对齐的地址读取浮点数,则可能会在 arm SIGBUS 上发生
    • 嘘,我很确定我的第二个要点涵盖了这一点。
    【解决方案4】:

    这将是 What is a bus error? 的重复,如果不是为了

    程序是否会出现第一次出现段错误并停止,第二次出现总线错误并退出的情况?

    问题的一部分。您应该能够使用此处找到的信息自己回答这个问题。


    精神错乱:一遍又一遍地做同样的事情,却期待不同的结果。
    ——阿尔伯特·爱因斯坦


    当然,从字面上理解这个问题......

    #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,一个程序可以在一次运行时因分段错误而退出,而在另一次运行时因总线错误而退出。

    【讨论】:

      【解决方案5】:

      将您的问题(可能是错误的)解释为“我间歇性地获得 SIGSEGV 或 SIGBUS,为什么不一致?”,值得注意的是,C 或 C++ 标准不保证用指针做不可靠的事情导致段错误;这只是“未定义的行为”,作为我曾经说过的教授,这意味着它可能会导致鳄鱼从地板上冒出来吃掉你。

      所以您的情况可能是您有两个错误,其中第一个发生有时会导致 SIGSEGV,而第二个(如果没有发生段错误并且程序仍在运行)会导致SIGBUS。

      我建议您使用调试器逐步完成,并注意鳄鱼。

      【讨论】:

        【解决方案6】:

        SIGBUS 如果您mmap() 一个文件并尝试访问超出文件末尾的映射缓冲区的一部分,以及出现空间不足等错误情况,也会引发SIGBUS。如果您使用sigaction() 注册信号处理程序并设置SA_SIGINFO,则可以让您的程序检查错误的内存地址并仅处理内存映射文件错误。

        【讨论】:

          【解决方案7】:

          程序是否会出现第一次出现段错误并停止,第二次出现总线错误并退出的情况?

          是的,即使对于一个相同的错误:这是一个来自 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 可能会设置具有不同页面限制的进程。我无法在调试会话中重现任何总线错误。因此,调试器可能是解决总线错误吐出二进制文件的一种解决方法。

          安德烈亚斯

          【讨论】:

            猜你喜欢
            • 2011-10-02
            • 2018-04-10
            • 2014-06-10
            • 1970-01-01
            • 1970-01-01
            • 2011-07-24
            • 2012-03-25
            • 2011-10-20
            • 2020-02-29
            相关资源
            最近更新 更多