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

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

本文关键字:代码 安全 方法 动态 C++      更新时间:2023-10-16

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

有一个功能可以完全中止动态生成的代码,无论它在哪里,都可以跳回C++(调用方)。为了实现这一点,我只使用C++异常:运行时助手(冒充C函数)只抛出一个C++异常,它通过生成的函数传播回C++。我使用SJLJ,到目前为止一切正常,但我不想依赖于特定的实现(我读到只有SJLJ才安全)。

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

我的问题是:如果在调用动态生成的函数之前设置了setjmp,那么使用longjmp/setjmp在理论上和实践上都安全吗,并且假设longjmp从不通过依赖RAII的C++函数传播(我必须确保在C++中实现的运行时助手都没有使用它),并且始终位于setjmp(在函数之前设置)?

或者C++太脆弱了,甚至不能保证它能很好地工作,并且会破坏一些东西?或者,C++只有在抛出实际异常时才会中断?如果异常在本地抛出并立即捕获(在生成的程序集调用的运行时帮助程序中),那么它安全吗?或者仅仅因为堆栈中有几个外国帧,它就会拒绝工作?

例如:

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: %sn", msg);
    // do something about the error here
}
// some other C++ code

如果setjmp是在调用动态生成的函数之前设置的,并且longjmp从不通过依赖RAII的C++函数传播(我必须确保C++中实现的运行时助手都没有使用它),并且总是位于setjmp(在函数之前设置),那么使用longjmp/setjmp在理论上和实践上都安全吗?

标准的18.10/4说:

CCD_ 1在本国际标准中具有更多的限制行为。如果用catchthrow替换setjmplongjmp将调用任何自动对象的任何非平凡析构函数,则setjmp/longjmp调用对具有未定义的行为。

因此,不仅仅是RAII,任何托管的堆栈都会受到非平凡析构函数的反对(即"资源"可能在构建后被获取,但在销毁过程中仍然需要释放,或者除了资源释放之外,销毁可能会有副作用,如日志记录)。

或者C++太脆弱了,甚至不能保证它能很好地工作,并且会破坏一些东西?

它的工作应该遵循上面关于琐碎析构函数的警告(这是一个相当大的限制)。

或者,C++只有在抛出实际异常时才会中断?如果异常在本地抛出并立即捕获(在生成的程序集调用的运行时帮助程序中),那么它安全吗?

这与setjmp/longjmp的行为无关。如果在普通C++编译器生成的代码中抛出并捕获,那么在执行(重新)输入动态生成的代码时不应该有任何残留/后续问题。同样的方法也用于被调用到C或从C调用的封装C++库;异常可能在C++库的边界上被捕获,并被转换为C可以处理的错误代码。