如何在 Linux 中捕获分段错误?

新手上路,请多包涵

我需要在第三方库清理操作中捕获分段错误。这有时会在我的程序退出之前发生,我无法解决这个问题的真正原因。在 Windows 编程中,我可以使用 __try - __catch 来做到这一点。是否有跨平台或特定于平台的方式来做同样的事情?我在 Linux 中需要这个,gcc。

原文由 Alex F 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.3k
2 个回答

在 Linux 上,我们也可以将这些作为例外。

通常,当您的程序执行分段错误时,会发送一个 SIGSEGV 信号。您可以为此信号设置自己的处理程序并减轻后果。当然,您应该真正确定自己 可以 从这种情况中恢复过来。在你的情况下,我认为你应该调试你的代码。

回到主题。我最近遇到 了一个将此类信号转换为异常的库简短手册),因此您可以编写如下代码:

 try
{
    *(int*) 0 = 0;
}
catch (std::exception& e)
{
    std::cerr << "Exception caught : " << e.what() << std::endl;
}

不过没查。适用于我的 x86-64 Gentoo 盒子。它有一个特定于平台的后端(借用自 gcc 的 java 实现),因此它可以在许多平台上工作。它仅支持开箱即用的 x86 和 x86-64,但您可以从位于 gcc 源中的 libjava 获取后端。

原文由 P Shved 发布,翻译遵循 CC BY-SA 4.0 许可协议

为了可移植性,可能应该使用标准 C++ 库中的 std::signal ,但是信号处理程序可以做什么有很多限制。不幸的是,如果不引入未定义的行为,就不可能 从 C++ 程序 中捕获 SIGSEGV,因为规范说:

  1. 从处理程序中调用任何库函数是未定义的行为,而不是标准库函数的非常狭窄的子集( abortexit ,一些原子函数,重新安装当前信号处理程序, memcpy , memmove , 类型特征, std::move , std::forward , —f1e5bf6b308c9853974f.
  2. 如果处理程序使用 throw 表达式,则这是未定义的行为。
  3. 如果处理程序在处理 SIGFPE、SIGILL、SIGSEGV 时返回,则这是未定义的行为

这证明使用严格标准和可移植的 C++ 从程序 中捕获 SIGSEGV 是不可能的。 SIGSEGV 仍然被操作系统捕获,并且通常在调用 等待 族函数时报告给父进程。

使用 POSIX 信号可能会遇到同样的问题,因为 2.4.3 Signal Actions 中有一个子句:

进程的行为在从信号捕获函数正常返回后未定义,该函数不是由 kill()sigqueue()raise()

关于 longjump s 的一句话。假设我们使用 POSIX 信号,使用 longjump 模拟堆栈展开将无济于事:

虽然 longjmp() 是一个异步信号安全函数,但如果从中断非异步信号安全函数或等效函数的信号处理程序调用它(例如等效于 exit() 的处理 --- 在从对 main() 的初始调用返回后执行),对非异步信号安全函数或等效函数的任何后续调用的行为都是未定义的。

这意味着调用 longjump 调用的延续不能可靠地调用通常有用的库函数,例如 printfmallocexit 或 return from main行为。因此,延续只能做有限的操作,可能只能通过一些异常终止机制退出。

简而言之,如果不引入未定义的行为,捕获 SIGSEGV 在可移植中恢复程序的执行可能是不可行的。即使您在可以访问结构化异常处理的 Windows 平台上工作,值得一提的是,MSDN 建议永远不要尝试处理硬件异常: Hardware Exceptions

最后但并非最不重要的一点是,在取消引用空值指针(或无效值指针)时是否会引发任何 SIGSEGV 不是标准的要求。因为通过空值指针或任何无效值指针的间接寻址是 未定义行为,这意味着编译器假定您的代码在运行时永远不会尝试这样的事情,编译器可以自由地进行代码转换以忽略此类未定义行为。例如,从 cppreference 中,

 int foo(int* p) {
    int x = *p;
    if(!p)
        return x; // Either undefined behavior above or this branch is never taken
    else
        return 0;
}

int main() {
    int* p = nullptr;
    std::cout << foo(p);
}

这里 if 的真实路径可以被编译器完全省略作为优化;只能保留 else 部分。否则,编译器推断 foo() 在运行时永远不会收到空值指针,因为它会导致未定义的行为。使用空值指针调用它,您可能会观察到值 0 打印到标准输出并且没有崩溃,您可能会观察到 SIGSEG 崩溃,实际上您可以观察到任何事情,因为对程序没有施加任何合理的要求并非没有未定义的行为。

原文由 Julien Villemure-Fréchette 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题