提高后续警报()

问题描述 投票:1回答:2

在尝试从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()结束的吗?

c signals posix
2个回答
2
投票

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

正如Some programmer dudecomment中指出的那样,使用sigsetjmp()siglongjmp()比使用setjmp()longjmp()更好。

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

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

这是代码的变体,具有相当广泛的检测。它使用我首选的错误报告功能,这些功能在我的SOQ(Stack Overflow Questions)存储库中的GitHub上可用,作为stderr.c子目录中的文件stderr.hsrc/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)
$ 

2
投票

根据Jonathan Leffler的建议,用sigset*and非主动等待(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阻止当前传递的信号时,然后在第一次调用test()信号掩码包含然后被阻塞的SIGALRM之后,请参阅执行:

$ ./test
1
8192 #SIGALRM
2    <-blocked

现在,如果将值0更改为1(行注释为SAVE或NOT)作为有关sigsetjmp的文档说:

如果参数savemask不为零,则sigsetjmp()/ siglongjmp()函数对保存并恢复信号掩码;否则,只保存寄存器组和堆栈。

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

$ ./test
1
0
2
$
© www.soinside.com 2019 - 2024. All rights reserved.