У меня теперь есть реализация, которая немного отличается от вышеупомянутой. На самом деле псевдокод выглядит так (в C ++ 11):
std::exception_ptr ex;
try {
//...
do_some_danger_things();
//...
} catch (...) {
ex = std::current_exception();
}
cleanup();
if(ex)rethrow_exception(ex);
Это не на 100% совпадает с приведенным выше эквивалентом C, поскольку вызов 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;
}
}
Хитрость в том, что этот обработчик не должен генерироваться во время выполнения, поэтому заглушка должна выяснить, где находится «настоящий» обработчик. Мы используем поток локального хранилища, чтобы сделать это. * * 1010
Теперь сгенерированный код будет получать цепочку обработчика исключений из 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, обработчик будет вызван дважды. Моя стратегия - просто восстановить оригинальный обработчик исключений в первом вызове.