使用Exception支持发出代码

时间:2011-11-16 00:33:48

标签: winapi exception seh

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

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,然后让它调用生成的处理程序。

1 个答案:

答案 0 :(得分: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,则将调用该处理程序两次。我的策略是在第一次调用中恢复原始异常处理程序。