【问题标题】:Emiting code with Exception support发出带有异常支持的代码
【发布时间】:2011-12-30 00:25:54
【问题描述】:

我需要在运行时生成执行以下操作的代码:

auto v_cleanup = std::shared_ptr<void>(nullptr, [](void *){ cleanup(); });

//...
do_some_danger_thing();
//...

或 C 等价物:

__try {
    //...
    do_some_danger_thing();
    //...
} __finally {
    cleanup();
}

cleanup() 函数保证没有异常,但是 do_some_danger_thing() 可能会抛出异常。此运行时代码必须不使用堆栈,这意味着在调用 do_some_danger_thing() 时,堆栈必须处于与我们进入运行时代码时相同的状态,除了返回地址设置为运行时代码(原始值保存到“jmp " 目标,为了返回给调用者)。

因为我们使用的是动态机器码,所以目标平台在 x86 CPU 上固定为 WIN32,x64 CPU 目前不在焦点范围内。

为此,我们必须处理任何异常。在 WIN32 C++ 中,异常是基于 SEH 的,所以我们必须处理它。问题是我们无法找到一种方法来做到这一点并使其与其他代码兼容。我们尝试了几种解决方案,但都不起作用,有时用户安装的异常处理程序从未被调用,有时外部异常处理程序被绕过,我们收到“未处理的异常”错误。

更新:

似乎 SEH 异常处理程序链仅支持 EXE 映像中的代码。如果异常处理程序指向我生成的代码,它将永远不会被调用。我要做的就是创建一个静态异常处理函数stub,然后让它调用生成的处理函数。

【问题讨论】:

  • 这两个代码 sn-ps 不一样 - 一方面,如果抛出异常,您的第一个 sn-p 只会调用 cleanup();您的第二个 sn-p 将始终调用它。另外,为什么没有堆栈,或者它可以恢复,或者......?为什么要实现自己的异常处理?我认为这个问题需要更多关于您要实现的目标的详细信息。请说明您这样做的目的、原因等。
  • 你是正确的代码 sn-ps... 我会修复它。对于您关于“为什么要实现自己的”的问题,我相信我已经解释过:我将在运行时生成机器代码,异常处理只是故事的一部分。是的,您可以在调用 do_some_danger_thing 之前恢复堆栈,如果您决定这样做,您必须支付更多生成代码的成本。

标签: winapi exception seh


【解决方案1】:

我现在有一个与上面略有不同的实现。实际上,伪代码看起来像(在 C++11 中):

std::exception_ptr ex;
try {
    //...
    do_some_danger_things();
    //...
} catch (...) {
    ex = std::current_exception();
}
cleanup();
if(ex)rethrow_exception(ex);

这与上面的 C 等效项不是 100% 相同,因为 cleanup() 的调用发生在堆栈展开之前,通常这不是问题,但可能会丢失确切的异常上下文。

我实现了一个内部异常处理程序作为辅助函数,如下所示:

_declspec(thread) void *real_handler = nullptr;

void **get_real_handler_addr(){
    return &real_handler;
}

__declspec(naked) int exception_handler(...){
    __asm {
        call get_real_handler_addr;
        mov eax, [eax];
        jmp eax;
    }
}

这里的技巧是这个处理程序不能在运行时生成,所以存根必须找出“真正的”处理程序在哪里。我们使用线程本地存储来执行此操作。

现在生成的代码将从 FS:[0] 获取异常处理程序链。但是,链必须是基于堆栈的,因此我使用以下代码替换处理程序:

void **exception_chain;
__asm {
    mov eax, fs:[0]
    mov exception_chain, eax
}
//...
void *saved_handler = exception_chain[1];
exception_chain[1] = exception_handler;
*get_real_handler_addr() = generated_code->get_exception_handler();

然后生成的异常处理程序可以进行清理。但是,如果任何当前异常处理程序返回 EXCEPTION_CONTINUE_SEARCH,该处理程序将被调用两次。我的策略是在第一次调用中恢复原始异常处理程序。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-02
    • 2014-09-20
    • 1970-01-01
    • 2020-06-29
    相关资源
    最近更新 更多