引发后续警报()
Raising subsequent alarm()
在尝试重现来自 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的"signal is automatically blocked [...] during the time the handler is running",但那个时间不是longjump()
结束的吗?
正如我在评论中指出的那样,通常最好使用 sigaction()
而不是
signal()
because it gives you more precise control over how the signal handling is done. It is also better to use pause()
(至少在非线程应用程序中)等待某个信号到达而不是让 CPU 在一个紧密的无限循环中旋转它的轮子。
作为 Some programmer dude noted in a , it is better to use sigsetjmp()
和
siglongjmp()
than to use setjmp()
和
longjmp()
.
然而,令我相当惊讶的是,我能够在 macOS High Sierra 10.13.2 上重现该问题。我的代码检测版本使用 pause()
代替自旋循环,并使用 sigsetjmp()
(带有 0
的 savemask
参数)和 siglongjmp()
,它从第一个警报中恢复并且从未收到第二个警报。在 Mac 上,alarm()
is documented in section 3 (functions) and not section 2 (system calls). It shouldn't make a difference, but the man page indicates that setitimer()
正在幕后使用。
当我删除 sigsetjmp()
/siglongjmp()
调用时,发出了第二个警报(有效)——因此看来非本地 goto 有影响。我使用 sigsetjmp()
和 0 作为最后一个参数。当我将其更改为 1 时,代码将使用 sigsetjmp()
/siglongjmp()
代码。所以,我认为是非本地 goto 和信号掩码的某种组合造成了麻烦。
这是您的代码的一个变体,具有相当广泛的检测。它使用我首选的错误报告功能,这些功能是
在我的 SOQ 中的 GitHub 上可用(堆栈
溢出问题)存储库作为文件 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)
$
按照 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
在第一次调用 test()
信号掩码包含 SIGALRM
然后被阻止,见执行:
$ ./test
1
8192 #SIGALRM
2 <-blocked
现在,如果将值 0 更改为 1(注释为保存或不保存的行),如有关 sigsetjmp
的文档所述:
The sigsetjmp()/siglongjmp() function pairs save and restore the
signal mask if the argument savemask is non-zero; otherwise, only the register
set and the stack are saved.
恢复第一次调用test后的信号掩码,见执行:
$ ./test
1
0
2
$
在尝试重现来自 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的"signal is automatically blocked [...] during the time the handler is running",但那个时间不是longjump()
结束的吗?
正如我在评论中指出的那样,通常最好使用 sigaction()
而不是
signal()
because it gives you more precise control over how the signal handling is done. It is also better to use pause()
(至少在非线程应用程序中)等待某个信号到达而不是让 CPU 在一个紧密的无限循环中旋转它的轮子。
作为 Some programmer dude noted in a sigsetjmp()
和
siglongjmp()
than to use setjmp()
和
longjmp()
.
然而,令我相当惊讶的是,我能够在 macOS High Sierra 10.13.2 上重现该问题。我的代码检测版本使用 pause()
代替自旋循环,并使用 sigsetjmp()
(带有 0
的 savemask
参数)和 siglongjmp()
,它从第一个警报中恢复并且从未收到第二个警报。在 Mac 上,alarm()
is documented in section 3 (functions) and not section 2 (system calls). It shouldn't make a difference, but the man page indicates that setitimer()
正在幕后使用。
当我删除 sigsetjmp()
/siglongjmp()
调用时,发出了第二个警报(有效)——因此看来非本地 goto 有影响。我使用 sigsetjmp()
和 0 作为最后一个参数。当我将其更改为 1 时,代码将使用 sigsetjmp()
/siglongjmp()
代码。所以,我认为是非本地 goto 和信号掩码的某种组合造成了麻烦。
这是您的代码的一个变体,具有相当广泛的检测。它使用我首选的错误报告功能,这些功能是
在我的 SOQ 中的 GitHub 上可用(堆栈
溢出问题)存储库作为文件 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)
$
按照 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
在第一次调用 test()
信号掩码包含 SIGALRM
然后被阻止,见执行:
$ ./test
1
8192 #SIGALRM
2 <-blocked
现在,如果将值 0 更改为 1(注释为保存或不保存的行),如有关 sigsetjmp
的文档所述:
The sigsetjmp()/siglongjmp() function pairs save and restore the signal mask if the argument savemask is non-zero; otherwise, only the register set and the stack are saved.
恢复第一次调用test后的信号掩码,见执行:
$ ./test
1
0
2
$