【问题标题】:Catching segfaults in C在 C 中捕获段错误
【发布时间】:2010-10-07 22:28:32
【问题描述】:

我有一个程序有时会从指针算术中出现段错误。我知道会发生这种情况,但我不能轻易地提前检查它是否存在段错误 - 我可以“预扫描”输入数据以查看它是否会导致段错误(这可能无法确定),或者我可以修改它以不使用指针算术,这需要大量的工作,或者我可以尝试捕获段错误。所以我的问题:

1) 在 C 语言中,我如何捕获段错误?我知道操作系统中的某些东西会导致段错误,但是如果 C 程序在段错误中比Segmentation fault 更优雅地死掉,那么它可以做什么?

2) 这有多便携?

我想这是一个非常不可移植的行为,所以如果您发布任何代码来捕获段错误,请告诉我它的工作原理。我在 Mac OS X 上,但我希望我的程序可以在尽可能多的平台上运行,我想看看我的选择是什么。

别担心——基本上我要做的就是打印一个更用户友好的错误消息并释放一些malloc()ed 内存,然后死掉。我不打算只是忽略我遇到的所有段错误并继续前进。

【问题讨论】:

  • 打印错误信息;不要释放内存,因为当段错误发生时,您的内存系统很可能会被打乱。

标签: c macos error-handling segmentation-fault


【解决方案1】:

您必须定义一个信号处理程序。这是在 Unix 系统上使用函数sigaction 完成的。我在 Fedora 64 位和 32 位以及 Sun Solaris 上使用相同的代码完成了这项工作。

【讨论】:

    【解决方案2】:

    嗯,SIGSEGV 是可捕获的,这是 POSIX,所以从这个意义上说它是可移植的。

    但我担心您似乎想要处理段错误而不是修复导致段错误的问题。如果我必须选择是操作系统有问题还是我自己的代码有问题,我知道我会选择哪一个。我建议你找到那个错误,修复它,然后编写一个测试用例以确保它不会再咬你。

    【讨论】:

    • 无需深入研究细节,默认模式是安全的,可以保护用户永远不会耗尽内存(它使用链表)。他们必须明确选择使用“不太安全”的版本,并且应该意识到这样做的后果。
    • 提供“不太安全”版本是为了与其他程序兼容,这些程序不像我的那样提供无限链表。另外,如果不出意外,我会学到一些新东西。
    • 我认为他的意思是“某些东西”捕获了操作系统中的违规行为,而不是操作系统是故障的原因。
    【解决方案3】:

    我认为您正在尝试解决一个不存在的问题。至少你的工作是错误的。您将无法捕获分段错误,因为此错误/异常是由操作系统引发的(它由您的程序引起,操作系统只是 抓住它)。

    我建议您重新考虑有关输入的策略:为什么无法对其进行消毒?最重要的是大小检查,为此 C 标准库具有适当的功能。然后,您当然必须检查有关内容的有效输入。是的,这可能会导致大量工作,但这是编写健壮程序的唯一方法。

    编辑:我不是 C 专家,不知道即使是分段错误也可以由信号处理程序处理。尽管如此,由于上述原因,我认为这不是正确的方法。

    【讨论】:

    • 大小检查不是问题——用户输入一个程序(在brainf*ck 中),我的程序运行它。检查有效输入会遇到停止问题。另外,某些程序有时只会出现段错误(即输入更多数据),并且它们是否会取决于用户输入。
    • @Chris Lutz:啊……在这种情况下,您的问题看起来更合理。我现在明白你为什么想要——事实上,为什么这是你唯一可行的选择——捕捉 SIGSEGV。
    • 这不是唯一的“可行”选项。我可以重写它以使用一个数组和一个指向该数组元素的变量,然后轻松检查指针是否超出范围(即 MAX),但这看起来很均匀更可怕(从我站的地方)。
    【解决方案4】:

    您可以使用函数 signal 为信号安装一个新的信号处理程序:

       #include <signal.h>
       void (*signal(int signum, void (*sighandler)(int)))(int);
    

    类似于以下代码:

    signal(SIGINT , clean_exit_on_sig);
    signal(SIGABRT , clean_exit_on_sig);
    signal(SIGILL , clean_exit_on_sig);
    signal(SIGFPE , clean_exit_on_sig);
    signal(SIGSEGV, clean_exit_on_sig); // <-- this one is for segmentation fault
    signal(SIGTERM , clean_exit_on_sig);
    
    void 
    clean_exit_on_sig(int sig_num)
    {
            printf ("\n Signal %d received",sig_num);
    }
    

    【讨论】:

    • ...请注意,当您到达 SEGV 时 - 指针读/写只是 碰巧 遇到了无法访问的内存 - 很可能它已经设法踩过分配的、可访问的内存,其中包含您的数据和空闲块列表。所以不要指望 alloc 可以工作,也不要指望内存中的任何数据都是正常的。
    【解决方案5】:

    您需要提供一个 SIGSEGV 处理程序,this one looks quite decent.

    【讨论】:

      【解决方案6】:

      这里有一个如何使用 glibc 的 backtrace() 捕获 SIGSEGV 并打印堆栈跟踪的示例:

      how to generate a stacktrace when my C++ app crashes

      您可以使用它来捕获您的段错误并进行清理,但请注意:您不应该在信号处理程序中做太多事情,尤其是涉及像 malloc() 这样的调用的事情。有很多调用不是信号安全的,如果你从 malloc 中调用 malloc,你最终可能会自取其辱。

      【讨论】:

      • 链接已失效,请考虑更新或删除此答案
      【解决方案7】:

      信号处理(相对)可跨 unix 机器(包括 mac 和 linux)移植。最大的区别在于异常细节,它作为参数传递给信号处理例程。抱歉,但如果您想打印更合理的错误消息(例如故障发生的位置和原因),您可能需要一堆 #ifdefs ...

      好的,这里有一个代码片段供您开始:

      #include <signal.h>
      
      /* reached when a segv occurrs */
      void
      SEGVFunction( SIGARGS )
      {
           ...
      }
      
      ...
      main(...) {
          signal(SIGSEGV, SEGVFunction); /* tell the OS, where to go in case... */
          ...
          ... do your work ...
      }
      

      你的任务是:

      • 检查什么是 SIGARGS(取决于操作系统,所以使用 ifdef)
      • 查看如何从 sigArgs 中的异常信息中提取故障地址和 pc
      • 打印合理的信息
      • 退出

      理论上,您甚至可以在信号处理程序中修补 pc(在错误指令之后),然后继续。但是,典型的信号处理程序要么 exit() 要么将 longjmp() 返回到 main 中的保存位置。

      问候

      【讨论】:

        【解决方案8】:

        信号处理程序中的安全操作非常有限。调用任何不知道可重入的库函数是不安全的,例如,free()printf() 将排除在外。最佳做法是设置一个变量并返回,但这对您帮助不大。使用write()等系统调用也是安全的。

        请注意,在此处给出的两个回溯示例中,backtrace_symbols_fd() 函数将是安全的,因为它直接使用原始 fd,但对 fprintf() 的调用不正确,应替换为 write() .

        【讨论】:

        • 哇,这太粗糙了。我会牢记这些严苛的规则。也许我真的不应该试图抓住段错误......
        • 我当然同意其他 cmets 建议正确的做法是修复导致 segv 的错误。
        猜你喜欢
        • 2012-10-10
        • 1970-01-01
        • 1970-01-01
        • 2012-09-18
        • 2012-01-16
        • 2014-10-29
        • 2014-10-30
        • 2010-11-23
        • 1970-01-01
        相关资源
        最近更新 更多