提高后续警报()

时间:2018-01-02 13:05:17

标签: c signals posix

在尝试从asan重现一个完全不相关的假定的误报堆栈缓冲区溢出警告时,我注意到一些奇怪的事情。当我随后要求两个警报()信号时,第二个显然永远不会发射。那是为什么?

这是一个MWE:

#include <setjmp.h>
#include <signal.h>
#include <unistd.h>

static jmp_buf jump_buffer;

void f()
{
    while(true) {};
}

void handle_timeout(int)
{
    longjmp(jump_buffer, 1);
}

void test()
{
    if (setjmp(jump_buffer) == 0)
    {
            f();
    }
}

int main()
{
    signal (SIGALRM, handle_timeout);
    alarm(2);
    test();
    signal (SIGALRM, handle_timeout);
    alarm(2);
    test();
    return 0;
}

如果取消注释第二次调用test,程序将在2s后按预期终止,但因为它会永远运行。

根据gnu.org,我很清楚“在处理程序运行期间”信号会被自动阻止[...],但该时间是不是由longjump()结束了?

2 个答案:

答案 0 :(得分:2)

正如我在评论中所指出的,一般来说最好使用sigaction()而不是。{ signal()因为它可以让您更精确地控制信号处理的完成方式。使用pause()也更好 (至少在非线程应用程序中)等待一些信号到达,而不是让CPU在一个紧密的无限循环中旋转它。

Some programmer dude中注明comment,最好使用sigsetjmp()siglongjmp()而不是使用setjmp()longjmp()

然而,令我非常惊讶的是,我能够在macOS High Sierra 10.13.2上重现这个问题。我的检测版代码使用pause()代替自旋循环,并使用sigsetjmp()savemask参数0)和siglongjmp(),它从第一个警报中恢复,从不接收第二个警报。在Mac上,alarm()记录在第3节(功能)而不是第2节(系统调用)中。它应该没有区别,但是手册页表明setitimer()正在使用中。

当我删除了sigsetjmp() / siglongjmp()个来电时,第二个警报已经发出(它有效) - 所以看起来非本地的getos有效。我使用sigsetjmp()并将0作为最后一个参数。当我将其更改为1时,代码使用sigsetjmp() / siglongjmp()代码。因此,我认为这是非本地goto和信号掩码的某种组合会带来麻烦。

这是您的代码的变体,具有相当广泛的检测。它使用我首选的错误报告功能 可以在我的SOQ GitHub上找到(Stack 溢出问题)存储库作为文件stderr.cstderr.h src/libsoq 子目录。这些可以很容易地报告报告消息的时间等,这是有益的。

#include <assert.h>
#include <setjmp.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include "stderr.h"

static bool use_jmpbuf = false;
static int  save_mask  = 1;
static sigjmp_buf jump_buffer;

static void handle_timeout(int signum)
{
    assert(signum == SIGALRM);
    if (use_jmpbuf)
        siglongjmp(jump_buffer, 1);
}

static void handle_sigint(int signum)
{
    err_error("Got signal %d (SIGINT)\n", signum);
    /*NOTREACHED*/
}

static void test(void)
{
    err_remark("Entering %s()\n", __func__);
    if (use_jmpbuf)
    {
        if (sigsetjmp(jump_buffer, save_mask) == 0)
        {
            err_remark("Pausing in %s()\n", __func__);
            pause();
        }
    }
    else
    {
        err_remark("Pausing in %s()\n", __func__);
        pause();
    }
    err_remark("Leaving %s()\n", __func__);
}

static void set_sigalrm(void)
{
    void (*handler)(int) = signal(SIGALRM, handle_timeout);
    if (handler == SIG_ERR)
        err_syserr("signal failed: ");
    if (handler == SIG_IGN)
        err_remark("SIGALRM was ignored\n");
    else if (handler == SIG_DFL)
        err_remark("SIGALRM was defaulted\n");
    else
        err_remark("SIGALRM was being handled\n");
}

static const char optstr[] = "hjm";
static const char usestr[] = "[-hjm]";
static const char hlpstr[] =
    "  -h  Print this help information and exit\n"
    "  -j  Use sigsetjmp()\n"
    "  -m  Do not save signal mask when using sigsetjmp\n"
    ;

int main(int argc, char **argv)
{
    err_setarg0(argv[0]);
    int opt;
    while ((opt = getopt(argc, argv, optstr)) != -1)
    {
        switch (opt)
        {
        case 'h':
            err_help(usestr, hlpstr);
            /*NOTREACHED*/
        case 'j':
            use_jmpbuf = true;
            break;
        case 'm':
            use_jmpbuf = true;
            save_mask = 0;
            break;
        default:
            err_usage(usestr);
            /*NOTREACHED*/
        }
    }
    if (optind != argc)
        err_usage(usestr);

    signal(SIGINT, handle_sigint);
    err_setlogopts(ERR_MILLI);
    err_stderr(stdout);

    if (use_jmpbuf)
        err_remark("Config: using sigsetjmp() %s saving signal mask\n", save_mask ? "with" : "without");
    else
        err_remark("Config: no use of sigsetjmp\n");
    set_sigalrm();
    unsigned left;

    left = alarm(2);
    err_remark("Left over from previous alarm: %u\n", left);
    test();
    err_remark("In %s() once more\n", __func__);
    set_sigalrm();
    left = alarm(2);
    err_remark("Left over from previous alarm: %u\n", left);
    test();
    err_remark("Exiting %s() once more\n", __func__);
    return 0;
}

示例运行(程序名称alrm61):

$ alrm61 -h
Usage: alrm61 [-hjm]
  -h  Print this help information and exit
  -j  Use sigsetjmp()
  -m  Do not save signal mask when using sigsetjmp
$ alrm61
alrm61: 2018-01-02 21:34:01.893 - Config: no use of sigsetjmp
alrm61: 2018-01-02 21:34:01.894 - SIGALRM was defaulted
alrm61: 2018-01-02 21:34:01.894 - Left over from previous alarm: 0
alrm61: 2018-01-02 21:34:01.894 - Entering test()
alrm61: 2018-01-02 21:34:01.894 - Pausing in test()
alrm61: 2018-01-02 21:34:03.898 - Leaving test()
alrm61: 2018-01-02 21:34:03.898 - In main() once more
alrm61: 2018-01-02 21:34:03.898 - SIGALRM was being handled
alrm61: 2018-01-02 21:34:03.898 - Left over from previous alarm: 0
alrm61: 2018-01-02 21:34:03.898 - Entering test()
alrm61: 2018-01-02 21:34:03.898 - Pausing in test()
alrm61: 2018-01-02 21:34:05.902 - Leaving test()
alrm61: 2018-01-02 21:34:05.902 - Exiting main() once more
$ alrm61 -j
alrm61: 2018-01-02 21:34:23.103 - Config: using sigsetjmp() with saving signal mask
alrm61: 2018-01-02 21:34:23.104 - SIGALRM was defaulted
alrm61: 2018-01-02 21:34:23.104 - Left over from previous alarm: 0
alrm61: 2018-01-02 21:34:23.104 - Entering test()
alrm61: 2018-01-02 21:34:23.104 - Pausing in test()
alrm61: 2018-01-02 21:34:25.108 - Leaving test()
alrm61: 2018-01-02 21:34:25.108 - In main() once more
alrm61: 2018-01-02 21:34:25.108 - SIGALRM was being handled
alrm61: 2018-01-02 21:34:25.108 - Left over from previous alarm: 0
alrm61: 2018-01-02 21:34:25.109 - Entering test()
alrm61: 2018-01-02 21:34:25.109 - Pausing in test()
alrm61: 2018-01-02 21:34:27.112 - Leaving test()
alrm61: 2018-01-02 21:34:27.112 - Exiting main() once more
$ alrm61 -m
alrm61: 2018-01-02 21:34:37.578 - Config: using sigsetjmp() without saving signal mask
alrm61: 2018-01-02 21:34:37.578 - SIGALRM was defaulted
alrm61: 2018-01-02 21:34:37.578 - Left over from previous alarm: 0
alrm61: 2018-01-02 21:34:37.578 - Entering test()
alrm61: 2018-01-02 21:34:37.578 - Pausing in test()
alrm61: 2018-01-02 21:34:39.584 - Leaving test()
alrm61: 2018-01-02 21:34:39.584 - In main() once more
alrm61: 2018-01-02 21:34:39.584 - SIGALRM was being handled
alrm61: 2018-01-02 21:34:39.584 - Left over from previous alarm: 0
alrm61: 2018-01-02 21:34:39.584 - Entering test()
alrm61: 2018-01-02 21:34:39.584 - Pausing in test()
^Calrm61: 2018-01-02 21:35:00.638 - Got signal 2 (SIGINT)
$ 

答案 1 :(得分:2)

按照Jonathan Leffler的建议,用sigset*和非主动等待(pause)重写代码:

static sigjmp_buf jump_buffer;

void f() {
  pause();
}

void handle_timeout(int sig) {
  siglongjmp(jump_buffer, 1);
}

void test() {
  if (sigsetjmp(jump_buffer,0) == 0) // SAVE or NOT...
    {
      f();
    }
}

int main() {
  printf("1\n");
  signal (SIGALRM, handle_timeout);
  alarm(2);
  test();
  sigset_t m;
  sigprocmask(0,NULL,&m);
  printf("%d\n",m);
  printf("2\n");
  signal (SIGALRM, handle_timeout);
  alarm(2);
  test();
  return 0;
}

然后在第二部分阻止执行,因为使用jmp退出处理程序不会恢复掩码,并且signal阻止当前传递的信号,然后在第一次调用{{1信号掩码包含test(),然后被阻止,请参阅执行:

SIGALRM

现在,如果将值0更改为1(行注释为SAVE或NOT)作为$ ./test 1 8192 #SIGALRM 2 <-blocked 的文档说明:

  

sigsetjmp()/ siglongjmp()函数对保存并恢复   参数savemask为非零时的信号掩码;否则,只有登记册        设置并保存堆栈。

第一次调用测试后的信号掩码恢复,请参阅执行:

sigsetjmp