【问题标题】:Throwing a C++ exception after an inline-asm jump在 inline-asm 跳转后引发 C++ 异常
【发布时间】:2010-04-15 02:30:29
【问题描述】:

我有一些奇怪的自我修改代码,但其根源是一个非常简单的问题:我希望能够执行 jmp(或 call)然后从那个任意点抛出异常并让它被包含jmp/call 的try/catch 块捕获。

但是当我这样做时(在 gcc 4.4.1 x86_64 中),异常会导致 terminate(),就像从 try/catch 之外抛出异常一样。我真的不明白这与从某个遥远的库中抛出异常有何不同,但这显然是因为它不起作用。

如何执行jmpcall 但仍将异常抛出回原来的try/catch?为什么这个 try/catch 不能像正常调用函数一样继续处理这些异常?

代码:

#include <iostream>
#include <stdexcept>

using namespace std;

void thrower()
{
    cout << "Inside thrower" << endl;
    throw runtime_error("some exception");
}

int main()
{
    cout << "Top of main" << endl;  

    try {
        asm volatile (
            "jmp *%0" // same thing happens with a call instead of a jmp
            :
            : "r"((long)thrower)
            :
        );
    } catch (exception &e) {
        cout << "Caught : " << e.what() << endl;
    }
    cout << "Bottom of main" << endl << endl;
}

预期输出:

Top of main 
Inside thrower 
Caught : some exception
Bottom of main

实际输出:

Top of main
Inside thrower
terminate called after throwing an instance of 'std::runtime_error'
  what():  some exception
Aborted

【问题讨论】:

  • 最初我在 linux 中的信号处理程序上下文中问了一个类似的问题。那真的只是让问题变得模糊不清,所以我删除了那个并询问了这个更简化的版本。整个信号处理程序只是掩盖了我真正的问题。
  • 实现异常处理的方式大致有两种。一种是修饰代码和调用站点,以便到处推送上下文,另一种是索引调用和返回点,以便异常处理程序可以查找到哪里。无论哪种方式,内联汇编都缺少一些东西。

标签: c++ assembly exception


【解决方案1】:

您是否查看过您的实现如何处理异常?它涉及在表格中查找 PC 地址,以了解程序在特定的投掷点上做了什么,以及所有调用者都在做什么。至少在 Mac OS X GCC 上。

我看过的唯一其他系统,Metrowerks Codewarrior for Mac(很久以前)使用了类似的系统,尽管更加透明。编译器可以为任何类型的异常上下文更改透明地插入函数。

你并没有希望让这个便携。

【讨论】:

  • 有趣...但是,如果我在同一个 try/catch 中嵌入对“thrower()”的合法有效调用,则 inline-asm 版本仍然不起作用,这听起来像你'说它应该。我猜异常处理程序可能正在查看调用本身的地址,这不是它所知道的。这是迄今为止最有希望的答案....
【解决方案2】:

如果您在 x86-64 linux 上使用 gcc 4.4.7(及更高版本),带有 dwarf 异常处理机制(可能是默认的),我有办法解决这个问题。

假设你的内联汇编代码是一个函数inline_add。它将调用另一个函数add,这可能会引发异常。代码如下:

extern "C" int add(int a, int b) {
    throw "in add";
}

int inline_add(int a, int b) {
    int r = 0;
    __asm__ __volatile__ (
        "movl %1, %%edi\n\t"
        "movl %2, %%esi\n\t"
        "call add\n\t"
        "movl %%eax, %0\n\t"
        :"=r"(r)
        :"r"(a), "r"(b)
        :"%eax"
    );
    return r;
}

如果你像这样打电话给inline_add

try {
    inline_add(1, 1);
} catch (...) {
    std::cout << "in catch" << std::endl;
}

它会崩溃,因为 gcc 没有为inline_add 提供异常帧。当遇到异常时,它必须崩溃。 (有关“与 C 的兼容性”,请参阅 here

所以我们需要为它伪造一个异常帧,但是用 gcc 汇编很难破解,我们只是使用带有适当异常帧的函数来包围它

我们这样定义一个函数:

void build_exception_frame(bool b) {
    if (b) {
        throw 0;
    }
}

然后像这样调用inline_add

try {
    inline_add(1, 1);
    build_exception_frame(false);
} catch (...) {
    std::cout << "in catch" << std::endl;
}

它只是工作。

build_exception_frame 应该在通话之后出现,否则它将不起作用

此外,为了防止优化 gcc 可能占用build_exception_frame,我们需要添加以下内容:

void build_exception_frame(bool b) __attribute__((optimize("O0")));

您可以查看 gcc 生成的汇编代码来验证代码。

gcc 似乎为整个try/catch 提供了异常框架,只要有一个函数可能会抛出,并且位置很重要。

稍后需要看看 gcc 是如何工作的。

如果有人知道,请告诉我。谢谢。

【讨论】:

    【解决方案3】:

    执行内联汇编后,您就有了实现定义的行为 (7.4/1)。

    您应该尝试设置一个堆栈帧;细节是平台特定的,我不知道如何在 x86_64 上做到这一点。

    【讨论】:

      【解决方案4】:

      如果您的 try {} 块仅包含一个函数调用,您是否查看过 gcc 生成的汇编代码?我很确定 C++ 编译器在这一点上所做的不仅仅是跳转,因为它需要能够在发生异常时回溯堆栈。

      如果您能够模仿 gcc 在构造函数调用时所采取的步骤,您的代码可能是可行的。

      更新:this question 可能会提供更多信息。特别是,Itanium ABI 的投掷和接球部分可能会有用:link

      【讨论】:

      • 这个问题没有提供更多信息。我对理解异常处理(在 Mac OS X 上)的微弱尝试导致我遇到了一些奇怪的 libunwind 代码,我找不到它的开源代码。它是用 C++(!) 编写的,并且有 Apple 命名约定。重点是,它不是 GCC 的严格功能。查看 GCC 源代码,您会发现它依赖于操作系统提供的 libunwind。
      • 不,看起来 asm 只是对函数的调用(我在调用周围放置了 inline-asm cmets,除了调用本身之外,cmets 之间没有任何指令)。
      • @SoapBox:你需要比较一下异常调度表,看看在try{throw处插入的函数在操作什么。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-10
      • 2014-01-10
      • 1970-01-01
      • 1970-01-01
      • 2020-09-03
      相关资源
      最近更新 更多