在尝试重现来自 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 dude在 comment 中注明, 最好使用 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.c
和 stderr.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)
$
关于c - 引发后续警报(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48061567/