【问题标题】:C++ and a safe way to jump out of dynamically generated codeC++ 和跳出动态生成代码的安全方法
【发布时间】:2015-04-12 15:58:18
【问题描述】:

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

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

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

我的问题是: 使用 longjmp/setjmp 在理论上和实践上是否安全,前提是 setjmp 在调用动态生成的函数之前设置,并且 longjmp 永远不会通过依赖在 RAII 上(我必须确保在 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

【问题讨论】:

  • 是否可以勾勒出一些源代码/伪代码来说明您希望如何使用longjmp/setjmp
  • 这里是伪代码pastebin.com/fTE3brK4

标签: c++ exception jit setjmp


【解决方案1】:

使用 longjmp/setjmp 在理论上和实践上是否安全,前提是 setjmp 在调用动态生成的函数之前设置好,并且 longjmp 永远不会通过依赖 RAII 的 C++ 函数传播(我必须确保没有运行时用 C++ 实现的助手使用它)并且总是落在 setjmp(设置在函数之前)?

标准的 18.10/4 说:

...longjmp(jmp_buf jbuf, int val) 在本国际标准中有更多的限制行为。如果将setjmplongjmp 替换为catchthrow,则setjmp/longjmp 调用对会为任何自动对象调用任何重要的析构函数。

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

或者 C++ 太脆弱了,即使这样也不能保证能正常工作并且会破坏某些东西?

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

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

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

【讨论】:

  • 那么,这意味着 setjmp/longjmp 只有在它们之间的某个析构函数被忽略时才不安全?我担心析构函数出现在与 setjmp 相同的函数中的情况,并且不知何故它会破坏事物,因为谁知道使用哪种魔法来实现异常。
  • P.S.我所说的“析构函数”也指整个潜在的堆栈展开事物(为支持它而发出的本机内部代码)
  • @carsten “这意味着 setjmp/longjmp 只有在它们之间的某个析构函数被忽略时才不安全?” - 不是它的意思......析构函数将 如果您的意思是在跳跃期间“未执行”,则绝对被“忽略”,因此危险在于,当忽略它们时,不会执行必要的破坏操作,并且标准的操作保证非常薄弱:如果所有有问题的析构函数都不是微不足道的,那么行为是不确定的。请参阅 here 了解其含义。
  • “我担心析构函数存在于与 setjmp 相同的函数中的情况,并且不知何故它会破坏事物,因为谁知道使用哪种魔法来实现异常” i> - 这不应该是一个问题:由编译器确保如果/当你跳回那里时,本地作用域的析构函数正常运行。这是跳跃过程中遗漏的东西 - 而不是跳跃之后 - 这是一个潜在的问题。
  • “PS “析构函数”也是指整个潜在的堆栈展开的东西(为支持它而发出的本机内部代码)” - 如果堆栈状态在您这样做时有效longjmp,并且当您设置 jmp 时堆栈状态仍然有效(即您的动态生成的代码没有损坏它),然后 所有 堆栈展开操作将得到处理。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-17
  • 2015-04-04
  • 1970-01-01
相关资源
最近更新 更多