在尝试从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()
结束了?
答案 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.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)
$
答案 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