C++和跳出动态生成代码的安全方式

C++ and a safe way to jump out of dynamically generated code

我的项目是用 C++ 编写的,它使用动态生成的代码将一些东西粘合在一起(使用 Fabrice Bellard 的 TCC 和一些手动生成的程序集 thunk)。动态生成的代码有时会跳转到用 C++ 实现的 "runtime helpers" 并返回。

有一个功能允许完全中止动态生成的代码,无论它在哪里,跳回到 C++(调用者)。为实现这一点,我只是使用 C++ 异常:一个运行时帮助程序(伪装成 C 函数)简单地抛出一个 C++ 异常,它通过生成的函数传播回 C++。我正在使用 SJLJ,到目前为止一切正常,但我不想依赖特定的实现(我读到只有 SJLJ 才安全)。

除了上面的中止方案,我的 C++ 代码主要在关键情况下使用异常,它不用于通用控制流。但是,我依靠 RAII 自动销毁堆栈上的对象。

我的问题是: 如果在调用动态生成的函数之前设置了 setjmp,并且如果 longjmp永远不会通过依赖 RAII 的 C++ 函数传播(我必须确保 none 在 C++ 中实现的运行时助手使用它)并且总是登陆 setjmp(在函数之前设置)?

或者 C++ 如此脆弱以至于不能保证它能正常工作并且会破坏某些东西?或者只有在抛出实际异常时 C++ 才会中断?如果异常在本地抛出并立即被捕获(在生成的程序集调用的运行时帮助器中),它安全吗?或者可能只是因为堆栈中有一些外部帧,它会拒绝工作?

EG:

jmp_buf buf; // thread-local
char* msg;   // thread-local

// ... some C++ code here, potentially some RAII thingy

GeneratedFunc func = (GeneratedFunc)compile_stuff();
if (!setjmp(buf)) {
    // somewhere deep inside, it calls longjmp to jump back to the top function in case a problem happens
    func();
} else {
    printf("error: %s\n", msg);
    // do something about the error here
}

// some other C++ code

Is it theoretically and practically safe to use longjmp/setjmp instead, provided setjmp is set right before calling a dynamically generated function, and provided that longjmp never propagates through C++ functions that rely on RAII (I must make sure that none of runtime helpers implemented in C++ make use of it) and always lands in setjmp (set right before the function)?

标准的 18.10/4 说:

...longjmp(jmp_buf jbuf, int val) has more restricted behavior in this International Standard. A setjmp/longjmp call pair has undefined behavior if replacing the setjmp and longjmp by catch and throw would invoke any non-trivial destructors for any automatic objects.

因此,它不仅是 RAII,而且任何堆栈托管对象都带有非平凡的析构函数(即 "resources" 可能会在 构造之后获得,但仍然需要在销毁期间释放,或者除了资源释放之外,销毁可能会有副作用,例如日志记录)。

Or C++ is so fragile that even this isn't guaranteed to work well and will corrupt something?

它应该在上面关于普通析构函数的警告(这是一个很大的限制)的情况下工作。

Or maybe C++ breaks only if actual exceptions are thrown? What if the exceptions are thrown locally and caught immediatelly (in runtime helpers called by generated assembly), is it safe?

这与 setjmp / longjmp 行为无关。如果您在正常的 C++ 编译器生成的代码中抛出并捕获,则执行(重新)输入动态生成的代码不应该有任何 residual/consequent 问题。相同的方法用于名为 to/from C 的包装 C++ 库;异常可能会在 C++ 库的边界上被捕获并转换为 C 可以处理的错误代码。