0

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

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 映像中的代码。如果异常处理程序指向我生成的代码,它将永远不会被调用。我要做的是创建一个静态异常处理函数存根,然后让它调用生成的处理程序。

4

1 回答 1

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,该处理程序将被调用两次。我的策略是在第一次调用中恢复原始异常处理程序。

于 2011-11-17T23:43:38.207 回答