Longjmp 超出信号处理程序?

发布于 2024-12-03 12:14:34 字数 682 浏览 3 评论 0原文

从问题:

这是好的编程吗练习在 C 中使用 setjmp 和 longjmp 吗?

留下的两条评论说:

“你不能在信号处理程序中抛出异常,但你可以做一个 longjmp 安全——只要你知道自己在做什么。 – 迪特里希 艾普 8 月 31 日 19:57 @Dietrich:对你的评论+1。这是一个鲜为人知且 完全被低估的事实。存在很多问题 如果不使用 longjmp 则无法解决(令人讨厌的竞争条件) 信号处理程序。阻塞系统调用的异步中断是 经典例子。”

我的印象是,信号处理程序是在遇到异常情况(例如除以 0)时由内核调用的。此外,只有在您专门注册它们时才会调用它们。

这似乎意味着(对我来说)他们不是通过你的正常代码调用的。

继续这个想法...... setjmp 和 longjmp 据我了解,它们是为了将堆栈折叠到之前的点和状态,我不明白你怎么能这样做。当信号处理程序被调用时,堆栈会折叠起来调用,因为它是作为一次性情况从内核调用的,而不是从您自己的代码调用的。信号处理程序堆栈中的下一件事是什么!?

From the question:

Is it good programming practice to use setjmp and longjmp in C?

Two of the comments left said:

"You can't throw an exception in a signal handler, but you can do a
longjmp safely -- as long as you know what you are doing. – Dietrich
Epp Aug 31 at 19:57
@Dietrich: +1 to your comment. This is a little-known and
completely-under-appreciated fact. There are a number of problems that
cannot be solved (nasty race conditions) without using longjmp out of
signal handlers. Asynchronous interruption of blocking syscalls is the
classic example."

I was under the impression that signal handlers were called by the kernel when it encountered an exceptional condition (e.g. divide by 0). Also, that they're only called if you specifically register them.

This would seem to imply (to me) that they aren't called through your normal code.

Moving on with that thought... setjmp and longjmp as I understand them are for collapsing up the stack to a previous point and state. I don't understand how you can collapse up a stack when a signal handler is called since its called from the Kernel as a one-off circumstance rather than from your own code. What's the next thing up the stack from a signal handler!?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

深府石板幽径 2024-12-10 12:14:34

内核“调用”信号处理程序的方式是中断线程,将信号掩码和处理器状态保存在堆栈上的 ucontext_t 结构中,就在被中断的线程之外(下面,在向下增长的实现中)代码的堆栈指针,并在信号处理程序的地址处重新开始执行。内核不需要跟踪任何“此进程处于信号处理程序”状态;这完全是创建的新调用框架的结果。

如果被中断的线程正处于系统调用过程中,内核将退出内核空间代码并调整返回地址以重复系统调用(如果为信号设置了SA_RESTART,并且系统调用是可重新启动的)或将 EINTR 放入返回代码中(如果不可重新启动)。

应该注意的是,longjmp 是异步信号不安全的。这意味着如果信号中断了另一个异步信号不安全函数,则如果从信号处理程序调用它,它将调用未定义的行为。但只要被中断的代码不使用库函数,或者仅使用标记为异步信号安全的库函数,从信号处理程序调用 longjmp 就是合法的。

最后,我的答案基于 POSIX,因为问题被标记为 unix。如果问题只是关于纯 C,我怀疑答案会有所不同,但无论如何,如果没有 POSIX,信号就毫无用处......

The way the kernel "calls" a signal handler is by interrupting the thread, saving the signal mask and processor state in a ucontext_t structure on the stack just beyond (below, on grows-down implementations) the interrupted code's stack pointer, and restarting execution at the address of the signal handler. The kernel does not need to keep track of any "this process is in a signal handler" state; that's entirely a consequence of the new call frame that was created.

If the interrupted thread was in the middle of a system call, the kernel will back out of the kernelspace code and adjust the return address to repeat the system call (if SA_RESTART is set for the signal and the system call is a restartable one) or put EINTR in the return code (if not restartable).

It should be noted that longjmp is async-signal-unsafe. This means it invokes undefined behavior if you call it from a signal handler if the signal interrupted another async-signal-unsafe function. But as long as the interrupted code is not using library functions, or only using library functions that are marked async-signal-safe, it's legal to call longjmp from a signal handler.

Finally, my answer is based on POSIX since the question is tagged unix. If the question were just about pure C, I suspect the answer is somewhat different, but signals are rather useless without POSIX anyway...

简美 2024-12-10 12:14:34

longjmp 不执行正常的堆栈展开。相反,堆栈指针只是从 setjmp 保存的上下文中恢复。

这里有一个说明,说明它是如何咬人的您的代码中包含非异步安全的关键部分。建议在关键代码期间屏蔽违规信号。

longjmp does not perform normal stack unwinding. Instead, the stack pointer is simply restored from the context saved by setjmp.

Here is an illustration on how this can bite you with non-async-safe critical parts in your code. It is advisable to e.g. mask the offending signal during critical code.

殊姿 2024-12-10 12:14:34

这并没有回答这样做是否“好”的问题,但是
这是怎么做的。在我的应用程序中,我在自定义硬件、大页面、共享内存、NUMA 锁定内存等之间进行了复杂的交互,并且可能有看起来分配得当的内存,但当你触摸它时(在这种情况下写入),它会在应用程序中间抛出总线错误或 SEGV 错误。我想提出一种测试内存地址的方法,以确保共享内存没有被节点锁定到没有足够内存的节点,以便程序会提前失败并显示优雅的错误消息。因此,这些信号处理程序仅用于这一段代码(一小段 5 字节的 memcpy),而不用于在应用程序使用时对其进行救援。我认为这里很安全。

如果这不“正确”,我们深表歉意。请发表评论,我会修复它。我根据提示和一些不起作用的示例代码将其拼凑在一起。

#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

sigjmp_buf  JumpBuffer;

void handler(int);

int count = 0;

int main(void)
{
    struct sigaction sa;
    sa.sa_handler = handler;
    sigemptyset(&(sa.sa_mask));
    sigaddset(&(sa.sa_mask), SIGSEGV);
    sigaction(SIGSEGV, &sa, NULL);

    while (1) {
        int r = sigsetjmp(JumpBuffer,1);
        if (r == 0) {
            printf("Ready for memcpy, count=%d\n",count);
            usleep(1000000);
            char buffer[10];
#if 1
            char* dst = buffer; // this won't do bad
#else
            char* dst = nullptr; // this will cause a segfault
#endif
            memcpy(dst,"12345",5); // trigger seg fault here
            longjmp(JumpBuffer,2);
        }
        else if (r == 1)
        {
            printf("SEGV. count %d\n",count);
        }
        else if (r == 2)
        {
            printf("No segv. count %d\n",count);
        }
    }
    return 0;
}

void handler(int  sig)
{
    count++;
    siglongjmp(JumpBuffer, 1);
}

参考文献

This doesn't answer the question of whether or not it is "good" to do this, but
this is how to do it. In my application, I have a complicated interaction between custom hardware, huge page, shared memory, NUMA lock memory, etc, and it is possible to have memory that seems to be decently allocated but when you touch it (write in this case), it throws a BUS error or SEGV fault in the middle of the application. I wanted to come up with a way of testing memory addresses to make sure that the shared memory wasn't node locked to a node that didn't have enough memory, so that the program would fail early with graceful error messages. So these signal handlers are ONLY used for this one piece of code (a small memcpy of 5 bytes) and not used to rescue the app while it is in use. I think it is safe here.

Apologies if this is not "correct". Please comment and I'll fix it up. I cobbled it together based on hints and some sample code that didn't work.

#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

sigjmp_buf  JumpBuffer;

void handler(int);

int count = 0;

int main(void)
{
    struct sigaction sa;
    sa.sa_handler = handler;
    sigemptyset(&(sa.sa_mask));
    sigaddset(&(sa.sa_mask), SIGSEGV);
    sigaction(SIGSEGV, &sa, NULL);

    while (1) {
        int r = sigsetjmp(JumpBuffer,1);
        if (r == 0) {
            printf("Ready for memcpy, count=%d\n",count);
            usleep(1000000);
            char buffer[10];
#if 1
            char* dst = buffer; // this won't do bad
#else
            char* dst = nullptr; // this will cause a segfault
#endif
            memcpy(dst,"12345",5); // trigger seg fault here
            longjmp(JumpBuffer,2);
        }
        else if (r == 1)
        {
            printf("SEGV. count %d\n",count);
        }
        else if (r == 2)
        {
            printf("No segv. count %d\n",count);
        }
    }
    return 0;
}

void handler(int  sig)
{
    count++;
    siglongjmp(JumpBuffer, 1);
}

References

定格我的天空 2024-12-10 12:14:34

值得一读: http://man7.org/linux/man- pages/man2/sigreturn.2.html 关于 Linux 如何处理信号处理程序调用,以及在本例中它如何管理信号处理程序退出,我对此的阅读表明从信号处理程序执行 longjmp() (结果不调用 sigreturn()) 最多可能是“未定义”...还必须考虑在哪个线程(以及用户堆栈)上调用 setjmp() 以及在哪个线程(以及用户堆栈)上调用 setjmp() longjmp() 在随后也被调用!

worth reading this: http://man7.org/linux/man-pages/man2/sigreturn.2.html in regard to how Linux handles signal handler invocation, and in this case how it manages signal handler exit, my reading of this suggests that executing a longjmp() from a signal handler (resulting in no call of sigreturn()) might be at best "undefined"... also have to take into account on which thread (and thus user stack) the setjmp() was called, and on which thread (and thus user stack) longjmp() in subsequently called also!

最冷一天 2024-12-10 12:14:34

在大多数系统中,信号处理程序都有自己的堆栈,与主堆栈分开。这就是为什么你可以从处理程序中 longjmp 出来。我认为这不是明智之举。

In most systems a signal handler has it's own stack, separate from the main stack. That's why you could longjmp out of a handler. I think it's not a wise thing to do though.

银河中√捞星星 2024-12-10 12:14:34

您不能使用longjmp 退出信号处理程序。

原因是 setjmp 仅保存调用约定指定应通过普通函数调用保存的资源(进程寄存器)等。

当中断发生时,被中断的函数可能有一个更大的状态,并且无法通过longjmp正确恢复。

You can't use longjmp to get out of a signal handler.

The reason for this is that setjmp only saves the resources (process registers) etc. that the calling-convention specifies that should be saved over a plain function call.

When an interrupt occurs, the function being interrupted may have a much larger state, and it will not be restored correctly by longjmp.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文