【问题标题】:Abort function in CC中的中止函数
【发布时间】:2015-11-06 11:53:17
【问题描述】:

方案一:

#include<stdio.h>
#include<signal.h>
void handler(int sig);
void main()
{
    printf("PID: %d\n",getpid());
    signal(SIGABRT,handler);
    while(1){
        printf("Hai\n");
        sleep(1);
        abort();
    }
}

void handler(int sig)
{
    printf("Signal handled\n");
}

输出 1:

$ ./a.out 
PID: 32235
Hai
Signal handled
Aborted (core dumped)
$

根据参考资料,abort 函数的工作方式类似于 raise(SIGABRT)。因此,abort() 函数生成的信号是 SIGABRT。因此,我创建了上述程序。

在该程序中,处理 SIGABRT 信号。信号处理程序执行后,它不会返回到调用它的主函数。为什么handler完成后不返回main函数?

方案二:

#include<stdio.h>
#include<signal.h>
void handler(int sig);
void main()
{
    printf("PID: %d\n",getpid());
    signal(SIGABRT,handler);
    while(1){
        printf("Hai\n");
        sleep(1);
    }
}

void handler(int sig)
{
    printf("Signal handled\n");
}

输出 2:

$ ./a.out 
PID: 32247
Hai
Hai
Hai
Signal handled
Hai
Signal handled
Hai
Hai
^C
$ 

与程序 1 不同,程序 2 按预期执行。在上面的程序中,信号是通过命令行通过kill命令发送到进程的,如下所示。

$ kill -6 32247
$ kill -6 32247

所以一旦信号发生,处理函数就会执行,然后返回到主函数。但这在程序 1 中并没有发生。为什么会这样呢? abort 函数和 SIGABRT 有什么不同?

【问题讨论】:

  • 我想你想知道这两个电话之间的区别。 Possible duplicate here
  • 阅读signal(7)。不要在信号处理程序中调用printf
  • @BasileStarynkevitch:实际上,AIUI,如果您确定信号不是异步生成的,您可以从信号处理程序中调用printf()。对 abort() 的调用不是异步的,而来自 shell 的 kill -6 是异步的。

标签: c linux unix abort


【解决方案1】:

请参阅man 3 abort 的这篇文档:

这会导致进程异常终止,除非 SIGABRT 信号被捕获并且信号处理程序没有返回(请参阅longjmp(3))。

还有这个:

如果SIGABRT 信号被忽略,或被返回的处理程序捕获,abort() 函数仍将终止进程。它通过恢复SIGABRT 的默认处置然后再次发出信号来实现这一点。

因此,防止abort() 中止程序的唯一方法是通过信号处理程序中的longjmp()-ing。

【讨论】:

    【解决方案2】:

    Libc 实现了abort()。在他们的实现中,abort() 检查进程是否还活着,因为abort()raise(SIGABRT) 之后执行。如果是,那么它知道用户已经处理了SIGABRT。根据文档,没关系,因为进程仍然会退出:

    您可以在 GLIBC 源代码 (stdlib/abort.c) 中看到确切的实现:

    /* Cause an abnormal program termination with core-dump.  */
    void
    abort (void)
    {
      struct sigaction act;
      sigset_t sigs;
    
      /* First acquire the lock.  */
      __libc_lock_lock_recursive (lock);
    
      /* Now it's for sure we are alone.  But recursive calls are possible.  */
    
      /* Unlock SIGABRT.  */
      if (stage == 0)
        {
          ++stage;
          if (__sigemptyset (&sigs) == 0 &&
          __sigaddset (&sigs, SIGABRT) == 0)
        __sigprocmask (SIG_UNBLOCK, &sigs, (sigset_t *) NULL);
        }
    
      /* Flush all streams.  We cannot close them now because the user
         might have registered a handler for SIGABRT.  */
      if (stage == 1)
        {
          ++stage;
          fflush (NULL);
        }
    
      /* Send signal which possibly calls a user handler.  */
      if (stage == 2)
        {
          /* This stage is special: we must allow repeated calls of
         `abort' when a user defined handler for SIGABRT is installed.
         This is risky since the `raise' implementation might also
         fail but I don't see another possibility.  */
          int save_stage = stage;
    
          stage = 0;
          __libc_lock_unlock_recursive (lock);
    
          raise (SIGABRT);
    
          __libc_lock_lock_recursive (lock);
          stage = save_stage + 1;
        }
    
      /* There was a handler installed.  Now remove it.  */
      if (stage == 3)
        {
          ++stage;
          memset (&act, '\0', sizeof (struct sigaction));
          act.sa_handler = SIG_DFL;
          __sigfillset (&act.sa_mask);
          act.sa_flags = 0;
          __sigaction (SIGABRT, &act, NULL);
        }
    
      /* Now close the streams which also flushes the output the user
         defined handler might has produced.  */
      if (stage == 4)
        {
          ++stage;
          __fcloseall ();
        }
    
      /* Try again.  */
      if (stage == 5)
        {
          ++stage;
          raise (SIGABRT);
        }
    
      /* Now try to abort using the system specific command.  */
      if (stage == 6)
        {
          ++stage;
          ABORT_INSTRUCTION;
        }
    
      /* If we can't signal ourselves and the abort instruction failed, exit.  */
      if (stage == 7)
        {
          ++stage;
          _exit (127);
        }
    
      /* If even this fails try to use the provided instruction to crash
         or otherwise make sure we never return.  */
      while (1)
        /* Try for ever and ever.  */
        ABORT_INSTRUCTION;
    }
    

    【讨论】:

      【解决方案3】:

      abort 函数会发送 SIGABRT 信号,这是真的,但无论您是否捕获(或忽略)该信号,abort 函数都会仍然退出您的进程.

      来自链接的手册页:

      返回值

      abort() 函数永远不会返回。

      【讨论】:

        【解决方案4】:

        根据标准,如果你处理SIGABRT,它并没有完全指定会发生什么:

        abort函数导致程序异常终止, 除非信号 SIGABRT 被捕获并且信号处理程序确实 不回来。是否打开带有未写入缓冲数据的流 已刷新、关闭打开的流或删除临时文件是 实现定义。实现定义的状态形式 终止不成功的方式返回宿主环境 函数调用 raise(SIGABRT)。

        但是它指定了应该发生什么:

        abort 函数不会返回给它的调用者。

        所以正确的行为是确保发生“异常终止”。这是由abort 函数确保的,它最好异常终止程序,它通过尝试以各种方式终止来做到这一点,如果似乎没有任何作用,它会进入无限循环(并且至少确保它不会返回给调用者)。

        【讨论】:

          【解决方案5】:

          它们不一样。 abort 函数调用raise(SIGABRT) 两次。如果您为SIGABRT 定义了一个处理程序,它将首先调用您的处理程序,然后再调用默认处理程序。

          【讨论】:

            猜你喜欢
            • 2020-05-19
            • 2018-05-23
            • 1970-01-01
            • 1970-01-01
            • 2014-03-16
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多