c - 引发后续警报()

标签 c signals posix

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

这是一个 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 的第二次调用的注释,程序将在 2 秒后按预期终止,但它会永远运行下去。

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

最佳答案

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

作为Some programmer dudecomment 中注明, 最好使用 sigsetjmp()siglongjmp()比使用 setjmp()longjmp() .

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

当我删除 sigsetjmp()/siglongjmp() 调用时,发出了第二个警报(有效)——因此看起来非本地 goto 有效果。我使用的是 sigsetjmp(),最后一个参数为 0。当我将其更改为 1 时,代码将使用 sigsetjmp()/siglongjmp() 代码。所以,我认为是非本地 goto 和信号掩码的某种组合造成了麻烦。

这是您的代码的变体,具有相当广泛的检测。它使用我首选的错误报告功能,这些功能是 在我的 GitHub 上可用 SOQ (堆 溢出问题)存储库作为文件 stderr.cstderr.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)
$ 

关于c - 引发后续警报(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48061567/

相关文章:

.net - 在Mac上编写的C代码移植到Windows

c - 如何在c中打印长十进制数?

c++ - 通过 posix_spawn 进行 Rsync

c - 这是糟糕的编码习惯吗?

c++ - 为什么 "long int"与 "int"大小相同?这个修改器到底有没有用?

shell - 如何在 Shell 脚本中捕获 exit 1 信号?

c - 多个信号到达处理程序

c - 子进程是否也应该解锁被阻塞的 SIGCHLD 信号?

c - mq_open errno 13 权限被拒绝

c - 使用 select() 调用文件 I/O 的 Pthread 生产者/消费者