longjmp 和 RAII

发布于 2024-10-25 19:35:36 字数 1841 浏览 0 评论 0原文

所以我有一个库(不是我写的),不幸的是它使用 abort() 来处理某些错误。在应用程序级别,这些错误是可恢复的,因此我想处理它们而不是用户看到崩溃。所以我最终编写了这样的代码:

static jmp_buf abort_buffer;
static void abort_handler(int) {
    longjmp(abort_buffer, 1); // perhaps siglongjmp if available..
}

int function(int x, int y) {

    struct sigaction new_sa;
    struct sigaction old_sa;

    sigemptyset(&new_sa.sa_mask);
    new_sa.sa_handler = abort_handler;
    sigaction(SIGABRT, &new_sa, &old_sa);

    if(setjmp(abort_buffer)) {
        sigaction(SIGABRT, &old_sa, 0);
        return -1
    }

    // attempt to do some work here
    int result = f(x, y); // may call abort!

    sigaction(SIGABRT, &old_sa, 0);
    return result;
}

不是非常优雅的代码。由于此模式最终必须在代码的几个位置重复,因此我想稍微简化它,并可能将其包装在可重用的对象中。我的第一次尝试涉及使用 RAII 来处理信号处理程序的设置/拆卸(需要完成,因为每个函数需要不同的错误处理)。所以我想出了这个:

template <int N>
struct signal_guard {
    signal_guard(void (*f)(int)) {
        sigemptyset(&new_sa.sa_mask);
        new_sa.sa_handler = f;
        sigaction(N, &new_sa, &old_sa);
    }

    ~signal_guard() {
        sigaction(N, &old_sa, 0);
    }
private:
    struct sigaction new_sa;
    struct sigaction old_sa;
};


static jmp_buf abort_buffer;
static void abort_handler(int) {
    longjmp(abort_buffer, 1);
}

int function(int x, int y) {
    signal_guard<SIGABRT> sig_guard(abort_handler);

    if(setjmp(abort_buffer)) {
        return -1;
    }

    return f(x, y);
}

当然,这样函数的主体会更简单、更清晰,但今天早上我想到了一个想法。 这保证有效吗?这是我的想法:

  1. setjmp/longjmp 调用之间没有变量是易失性或变化的。
  2. longjmp 到与 setjmp 相同的堆栈帧中的位置并正常返回,因此我允许代码执行编译器在函数退出点发出的清理代码。
  3. 它似乎按预期工作。

但我仍然感觉这可能是未定义的行为。你们觉得怎么样?

So I have a library (not written by me) which unfortunately uses abort() to deal with certain errors. At the application level, these errors are recoverable so I would like to handle them instead of the user seeing a crash. So I end up writing code like this:

static jmp_buf abort_buffer;
static void abort_handler(int) {
    longjmp(abort_buffer, 1); // perhaps siglongjmp if available..
}

int function(int x, int y) {

    struct sigaction new_sa;
    struct sigaction old_sa;

    sigemptyset(&new_sa.sa_mask);
    new_sa.sa_handler = abort_handler;
    sigaction(SIGABRT, &new_sa, &old_sa);

    if(setjmp(abort_buffer)) {
        sigaction(SIGABRT, &old_sa, 0);
        return -1
    }

    // attempt to do some work here
    int result = f(x, y); // may call abort!

    sigaction(SIGABRT, &old_sa, 0);
    return result;
}

Not very elegant code. Since this pattern ends up having to be repeated in a few spots of the code, I would like to simplify it a little and possibly wrap it in a reusable object. My first attempt involves using RAII to handle the setup/teardown of the signal handler (needs to be done because each function needs different error handling). So I came up with this:

template <int N>
struct signal_guard {
    signal_guard(void (*f)(int)) {
        sigemptyset(&new_sa.sa_mask);
        new_sa.sa_handler = f;
        sigaction(N, &new_sa, &old_sa);
    }

    ~signal_guard() {
        sigaction(N, &old_sa, 0);
    }
private:
    struct sigaction new_sa;
    struct sigaction old_sa;
};


static jmp_buf abort_buffer;
static void abort_handler(int) {
    longjmp(abort_buffer, 1);
}

int function(int x, int y) {
    signal_guard<SIGABRT> sig_guard(abort_handler);

    if(setjmp(abort_buffer)) {
        return -1;
    }

    return f(x, y);
}

Certainly the body of function is much simpler and more clear this way, but this morning a thought occurred to me. Is this guaranteed to work? Here's my thoughts:

  1. No variables are volatile or change between calls to setjmp/longjmp.
  2. I am longjmping to a location in the same stack frame as the setjmp and returning normally, so I am allowing the code to execute the cleanup code that the compiler emitted at the exit points of the function.
  3. It appears to work as expected.

But I still get the feeling that this is likely undefined behavior. What do you guys think?

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

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

发布评论

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

评论(2

送你一个梦 2024-11-01 19:35:36

我假设 f 在第三方库/应用程序中,因为否则您可以修复它以不调用中止。鉴于此,并且 RAII 可能会也可能不会在所有平台/编译器上可靠地产生正确的结果,您有几个选择。

  • 创建一个定义abort的小型共享对象并对其进行LD_PRELOAD。然后您可以控制中止时发生的情况,而不是在信号处理程序中发生的情况。
  • 在子进程中运行 f。然后,您只需检查返回代码,如果失败,请使用更新的输入重试。
  • 无需使用 RAII,只需从多个调用点调用原始函数,并让它手动显式执行设置/拆卸即可。在这种情况下,它仍然消除了复制粘贴。

I assume that f is in a third party library/app, because otherwise you could just fix it to not call abort. Given that, and that RAII may or may not reliably produce the right results on all platforms/compilers, you have a few options.

  • Create a tiny shared object that defines abort and LD_PRELOAD it. Then you control what happens on abort, and NOT in a signal handler.
  • Run f within a subprocess. Then you just check the return code and if it failed try again with updated inputs.
  • Instead of using the RAII, just call your original function from multiple call points and let it manually do the setup/teardown explicitly. It still eliminates the copy-paste in that case.
瞎闹 2024-11-01 19:35:36

我实际上很喜欢您的解决方案,并且在测试工具中编写了类似的代码来检查目标函数 assert() 是否符合预期。

我看不出这段代码调用未定义行为的任​​何原因。 C 标准似乎祝福它:由 abort() 产生的处理程序不受从处理程序调用库函数的限制。 (警告:这是 C99 的 7.14.1.1(5) - 遗憾的是,我没有 C90 的副本,即 C++ 标准引用的版本)。

C++03 添加了进一步的限制:如果任何自动对象将通过将控制转移到程序中的另一个(目标)点的抛出异常而被销毁,则在抛出点调用 longjmp(jbuf, val)将控制转移到同一(目标)点具有未定义的行为。 我假设您的声明“在调用 setjmp/longjmp 之间没有变量是易失性或更改的”包括实例化任何自动 C++ 对象。 (我猜这是一些遗留的 C 库?)。

POSIX 异步信号安全(或缺乏)也不是问题 - abort() 与程序执行同步生成其 SIGABRT。

最大的担忧是破坏第 3 方代码的全局状态:作者不太可能在 abort() 之前煞费苦心地使状态保持一致。但是,如果您正确地认为没有变量发生变化,那么这不是问题。

如果对标准语言有更好理解的人可以证明我错了,我将不胜感激。

I actually like your solution, and have coded something similar in test harnesses to check that a target function assert()s as expected.

I can't see any reason for this code to invoke undefined behaviour. The C Standard seems to bless it: handlers resulting from an abort() are exempted from the restriction on calling library functions from a handler. (Caveat: this is 7.14.1.1(5) of C99 - sadly, I don't have a copy of C90, the version referenced by the C++ Standard).

C++03 adds a further restriction: If any automatic objects would be destroyed by a thrown exception transferring control to another (destination) point in the program, then a call to longjmp(jbuf, val) at the throw point that transfers control to the same (destination) point has undefined behavior. I'm supposing that your statement that 'No variables are volatile or change between calls to setjmp/longjmp' includes instantiating any automatic C++ objects. (I guess this is some legacy C library?).

Nor is POSIX async signal safety (or lack thereof) an issue - abort() generates its SIGABRT synchronously with program execution.

The biggest concern would be corrupting the global state of the 3rd party code: it's unlikely that the author will take pains to get the state consistent before an abort(). But, if you're correct that no variables change, then this isn't a problem.

If someone with a better understanding of the standardese can prove me wrong, I'd appreciate the enlightenment.

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