在 Linux 上使用 C++ 记录进程终止的原因

Log the reason for process termination with C++ on Linux

本文关键字:终止 进程 记录 C++ Linux      更新时间:2023-10-16

有没有办法记录、处理或以其他方式留下一些关于进程终止原因的线索,涵盖尽可能多的导致终止的事件?

我的应用程序有一个日志记录工具,每分钟记录许多消息。 我在一个超级 try-catch 块中运行几乎整个程序,所以我可以记录任何未经处理的异常。 我最近还尝试为可能终止进程的各种进程信号注册处理程序。 但是,该应用程序每天仍然崩溃几次,我不知道为什么。

我可能无法记录或处理多少其他致命事件? 我希望有一种正确的方法来做到这一点,而不是在这个过程因我还不知道的某种新型事件而死亡时一直被蒙在鼓里。

非常感谢。

您可以注册一个函数来处理意外异常:

set_unexpected()

如果不是delt,将导致应用程序调用terminat()。

您可以注册一个函数来记录终止时的事情:

set_terminate()

您可以添加自己的 atexit() 日志记录函数来执行操作(设置一个标志,以便它仅在退出异常发生时才执行操作,然后在离开 main 之前设置标志)。

信号处理程序可能很棘手(特别是如果您希望它们是可移植的)。如果你使用它们,你在里面可以安全地做的事情受到限制,所以我通常限制自己设置一个全局标志,以便它们可以通过普通代码处理(当然,如果你要终止,那么这是非常有限的)。

拥有超级try/catch块意味着可捕获的异常并非无法处理。请注意,所有已启动的线程都需要这些块。

除此之外,您还可以使用signal来捕获终止信号。这些是:

  • SIGABRT(信号中止)异常终止,例如由中止功能启动。
  • SIGFPE(信号浮点异常)错误的算术运算,例如零除法或导致溢出的运算(不一定使用浮点运算)。
  • SIGILL(信号非法指令)无效的功能图像,例如非法指令。这通常是由于代码损坏或尝试执行数据。
  • SIGINT(信号中断)交互式注意力信号。通常由应用程序用户生成。
  • SIGSEGV(信号分割违规)对存储的无效访问:当程序尝试在内存之外读取或写入时,会为其分配它。SIGTERM(信号终止)终止请求发送到程序。
  • 由实现定义的信号,但大多数崩溃原因应由这些内容涵盖。

此外,程序可能没有崩溃,而是通过从 main 返回(但我想您已经覆盖了)或通过调用 exit 来终止。在这种情况下,您可以检查程序的返回值并记录该值。

这是我在我的程序中使用的,它对我有用....每当我的程序崩溃时,它都会将崩溃站点的堆栈跟踪打印到 stdout(大概会重定向到文件等,您可以在以后读取它)。

请注意,您可能需要在 Makefile 中的 CXXFLAGS 和/或 LFLAGS 中传递 -rdynamic 作为标志,以确保堆栈跟踪包含人类可读的函数名称。

#include <stdio.h>
#include <signal.h>
#include <execinfo.h>
void PrintStackTrace()
{
   void *array[256];
   size_t size = backtrace(array, 256);
   char ** strings = backtrace_symbols(array, 256);
   if (strings)
   {
      printf("--Stack trace follows (%zd frames):n", size);
      for (size_t i = 0; i < size; i++) printf("  %sn", strings[i]);
      printf("--End Stack tracen");
      free(strings);
   }
   else printf("PrintStackTrace:  Error, could not generate stack trace!n");
}
static void CrashSignalHandler(int sig)
{
   // Uninstall this handler, to avoid the possibility of an infinite regress
   signal(SIGSEGV, SIG_DFL);
   signal(SIGBUS,  SIG_DFL);
   signal(SIGILL,  SIG_DFL);
   signal(SIGABRT, SIG_DFL);
   signal(SIGFPE,  SIG_DFL);
   printf("CrashSignalHandler called with signal %i... I'm going to print a stack trace, then kill the process.n", sig);
   PrintStackTrace();
   printf("Crashed process aborting now.... bye!n");
   fflush(stdout);
   abort();
}
int main(int argc, char ** argv)
{
   signal(SIGSEGV, CrashSignalHandler);
   signal(SIGBUS,  CrashSignalHandler);
   signal(SIGILL,  CrashSignalHandler);
   signal(SIGABRT, CrashSignalHandler);
   signal(SIGFPE,  CrashSignalHandler);
   [...remainder of your program goes here...]
}

一个代码胜过很多个字:

#include <iostream>
#include <signal.h>
sigint_handler(int s) {
    std::cout<<"signal caught: "<<s<<std::endl;
    ::exit(-1);
}
void setup_signal() {
    struct sigaction sigIntHandler;
    sigIntHandler.sa_handler = sigint_handler;
    sigemptyset(&sigIntHandler.sa_mask);
    sigIntHandler.sa_flags = 0;
    sigaction(SIGINT, &sigIntHandler, NULL);
    sigaction(SIGTERM, &sigIntHandler, NULL);
}
int main() {
    setup_signal();
    /* do stuff */
    return 0;
}

当然,这只处理SIGINT/SIGTERM信号。您还必须使用所有atexit(),set_terminate,超级尝试/捕获等来更新此代码。你可以找到。如果您遇到段错误/总线错误/其他什么...好吧,你注定要:)

看看这个问题。如何在 unix 上找到没有日志文件的死进程的原因?

在那里,您将看到使用 bash 获取进程的退出代码比使用信号处理程序或任何类型的退出回调要容易得多。