从信号处理程序中抛出异常

问题描述 投票:21回答:5

我们有一个库,可以处理错误报告的许多方面。我的任务是将此库移植到Linux。当我的小测试套件运行时,其中一个测试失败了。测试的简化版本如下所示。

// Compiler: 4.1.1 20070105 RedHat 4.1.1-52
// Output: Terminate called after throwing an instance of 'int' abort

#include <iostream>
#include <csignal>
using namespace std;

void catch_signal(int signalNumber)
{
    signal(SIGINT, SIG_DFL);
    throw(signalNumber);
}

int test_signal()
{
    signal(SIGINT, catch_signal);

    try
    {
        raise(SIGINT);
    }
    catch (int &z)
    {
        cerr << "Caught exception: " << z << endl;
    }
    return 0;
}

int main()
{
    try
    {
        test_signal();
    }
    catch (int &z)
    {
        cerr << "Caught unexpected exception: " << z << endl;
    }
    return 0;
}

我的期望是将显示Caught exception:消息。实际发生的是程序终止,因为抛出的int似乎没有出现catch处理程序。

关于SO的一些问题似乎有关。我找到了许多相关的Google网页。 “智慧”似乎归结为。

  1. Ya不能从信号处理程序中抛出异常,因为信号处理程序使用自己的堆栈运行,因此没有定义处理程序。
  2. Ya可以从信号处理程序中抛出异常,只需在堆栈上重建一个假帧,你就可以了。
  3. 呀,我们一直这样做。它适用于我在平台X上
  4. 雅,曾经可以使用gcc,但似乎不再工作了。尝试-fnon-call-exceptions选项,也许这样可行 代码在AIX / TRU64 / MSVC编译器/环境中按预期工作。它在我们的Linux环境中失败了。

我正在寻找有助于解决此问题的建议,因此Linux上的库行为将与我的其他平台或可能实现相同功能的某种类型或解决方法相匹配。 让程序核心转储信号,不是一个可行的选择。

c++ exception signals
5个回答
14
投票

信号与C ++异常完全不同。您不能使用C ++ try / catch块来处理信号。具体来说,信号是POSIX概念,而不是C ++语言概念。信号由内核异步传递给您的应用程序,而C ++异常是由C ++标准定义的同步事件。

您在POSIX信号处理程序中可以移植的内容非常有限。一个常见的策略是使用sig_atomic_t类型的全局标志,它将在信号处理程序中设置为1,然后可能将longjmp设置为适当的执行路径。

请参阅here以获取有关编写正确信号处理程序的帮助。


9
投票

此代码演示了一种将异常抛出信号处理程序的技术。感谢Charles的想法。

#include <iostream>
#include <csignal>
#include <csetjmp>

using namespace std;

jmp_buf gBuffer;        // A buffer to hold info on where to jump to

void catch_signal(int signalNumber)
{
    //signal(SIGINT, SIG_DFL);          // Switch to default handling
    signal(SIGINT, catch_signal);       // Reactivate this handler.

    longjmp             // Jump back into the normal flow of the program
    (
        gBuffer,        // using this context to say where to jump to
        signalNumber    // and passing back the value of the signal.
    );
}


int test_signal()
{
    signal(SIGINT, catch_signal);

    try
    {
        int sig;
        if ((sig = setjmp(gBuffer)) == 0) 
        {
            cout << "before raise\n";
            raise(SIGINT);
            cout << "after raise\n";

        }
        else
        {
            // This path implies that a signal was thrown, and
            // that the setjmp function returned the signal
            // which puts use at this point.

            // Now that we are out of the signal handler it is
            // normally safe to throw what ever sort of exception we want.
            throw(sig);
        }
    }
    catch (int &z)
    {
        cerr << "Caught exception: " << z << endl;
    }

    return 0;
}

int main()
{
    try
    {
        test_signal();
    }
    catch (int &z)
    {
        cerr << "Caught unexpected exception: " << z << endl;
    }
    return 0;
}

5
投票

我将屏蔽每个线程中的所有信号,除了用sigwait ()等待信号的信号。该线程可以无限制地处理信号,例如,抛出异常或使用其他通信机制。


4
投票

抛出信号处理程序可能不是一个好主意,因为堆栈没有必要以与函数调用相同的方式设置,因此从信号处理程序展开可能无法按预期工作。

对于C ++ ABI使用的任何由信号处理机制保存和重用的寄存器,必须注意。


3
投票

谷歌g ++选项

-fnon-call-exceptions

这基本上就是你想要的。我认为这是由于苹果对其操作系统的压力而产生的。我不确定它对LINUX的支持程度如何。而且我不确定是否可以捕获SIGINT - 但是所有CPU触发的信号(例外情况)都可以被捕获。需要此功能的编码器(并不关心意识形态)应该给LINUX开发人员社区带来一些压力,以便有一天能够在LINUX上支持它 - 自从近二十年来在Windows上得到支持之后。


1
投票

这是一个潜在的解决方案。它实现起来可能相当复杂,当然至少部分需要根据CPU架构和操作系统和/或C库组合重新实现:

在信号处理程序中,堆栈包含已中断代码的所有寄存器的已保存副本。一旦信号处理程序退出,您可以操纵它来修改程序状态。你想在处理程序中做这样的事情:

1)在内存中向下移动堆栈的底部(当前堆栈帧,内核保存的CPU状态,处理程序返回内核所需的任何内容)。

2)在堆栈中间的备用空间中,发明一个新的堆栈帧,好像在引发信号时执行了一些“异常调用”功能。该帧的布局应与中断代码以正常方式调用此函数的方式相同。

3)修改保存的CPU状态的PC以指向此“异常调用”功能。

4)退出信号处理程序。

信号处理程序将返回内核。内核将返回到这个新的堆栈帧(“异常调用”函数)而不是原始代码。这个“异常调用”函数应该简单地引发你想要引发的任何异常。

这里可能有一些细节;例如。:

1)“异常调用”函数可能需要将一堆寄存器保存到堆栈中,而这通常不会;即被中断的代码可能已经使用的所有被调用者保存的寄存器。您可能需要在程序集中编写(部分?)“异常调用”函数来协助此处。也许上面的步骤2可以将寄存器保存为设置堆栈帧的一部分。

2)信号处理程序正在搞乱堆栈。这会使编译器生成的代码混淆不清。您可能必须编写异常处理程序(或者只是它调用的某个函数,这需要移动更多的堆栈帧)才能使其工作。

3)您可能需要手动生成一些C ++异常处理程序展开信息,以便C ++异常处理代码知道如何从这个“异常调用”函数中展开堆栈。如果你可以用C ++编写函数,可能不是。如果你不能,那几乎可以肯定。

4)可能是我忽略的各种令人讨厌的细节:-)


0
投票

至少在Ubuntu 16.04 x86-64中,抛出信号处理程序似乎工作正常。无论这是设计(即保证工作,而不是以某种方式意外工作),我还没有研究过。我使用g++ -o sig-throw sig-throw.cpp编译了以下程序:

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

extern "C" void handler(int sig, siginfo_t *info, void *xxx)
{
    throw "Foo";
}

int main(int argc, char **argv)
{
    struct sigaction sa = {0};

    sa.sa_sigaction = handler;
    sigaction(SIGALRM, &sa, NULL);

    alarm(3);

    try {
        printf("Sleeping...\n");
        sleep(10);
        printf("Awoke\n"); // syscall interrupted
    }
    catch (...) {
        printf("Exception!\n");
    }

    return 0;
}

这是运行:

[swarren@swarren-lx1 sig-throw]$ ./sig-throw 
Sleeping...
Exception!

以供参考:

[swarren@swarren-lx1 sig-throw]$ lsb_release -a
...
Description:    Ubuntu 16.04.6 LTS
...

[swarren@swarren-lx1 sig-throw]$ dpkg -l libc6
...
ii  libc6:amd64  2.23-0ubuntu11  amd64  GNU C Library: Shared libraries

[swarren@swarren-lx1 sig-throw]$ g++ --version
g++ (Ubuntu 5.4.0-6ubuntu1~16.04.11) 5.4.0 20160609
© www.soinside.com 2019 - 2024. All rights reserved.