在C中捕获段错误

时间:2009-02-16 18:40:02

标签: c macos error-handling segmentation-fault

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

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

2)这有多便携?

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

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

8 个答案:

答案 0 :(得分:21)

嗯,SIGSEGV是可以捕获的,这是POSIX,因此它在这个意义上是可移植的。

我担心您似乎想要处理段错误而不是解决导致段错误的问题。如果我不得不选择是故障操作系统还是我自己的代码,我知道我会选择哪个。我建议你找出那个bug,修复它,然后编写一个测试用例,确保它再也不会让你感到厌烦。

答案 1 :(得分:12)

您可以使用信号功能为信号安装新的信号处理程序:

   #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);
}

答案 2 :(得分:9)

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

答案 3 :(得分:5)

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

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

答案 4 :(得分:1)

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

我建议你重新考虑一下你的投入策略:为什么不可能对它进行消毒?最重要的是大小检查,为此,C stdlib具有适当的功能。当然,您必须检查有关内容的有效输入。是的,这可能会带来很多工作,但这是编写健壮程序的唯一方法。

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

答案 5 :(得分:1)

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

答案 6 :(得分:1)

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

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

您可以使用它来捕获您的段错误并进行清理,但要注意:您不应该在信号处理程序中执行太多操作,尤其是涉及调用malloc()之类的操作。有很多不是信号安全的电话,如果你从malloc内部调用malloc,你最终可能会在脚下开枪。

答案 7 :(得分:1)

信号处理在unix机器上(相对)可移植(包括mac和linux)。差异在于异常细节,它作为参数传递给信号处理例程。 Sorrty,但如果你想打印更合理的错误信息(例如故障发生在哪里以及由于哪个地址发生),你可能需要一堆#ifdef用于此...

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

#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
  • 中的异常信息中提取fault-address和pc
  • 打印合理的消息
  • 出口

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

问候