Испускаемый код с поддержкой исключений - PullRequest
0 голосов
/ 16 ноября 2011

Мне нужно генерировать код во время выполнения, который делает следующее:

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")."target, чтобы вернуться к вызывающей стороне).

Поскольку мы используем динамический машинный код, целевая платформа установлена ​​на WIN32 на процессоре x86, процессор x64 в настоящее время не в фокусе.

Для этого мы должны обработать любые исключения.В WIN32 C ++ исключение основано на SEH, поэтому мы должны это сделать.Проблема в том, что мы не можем найти способ сделать это и сделать его совместимым с другим кодом.Мы испробовали несколько решений, но ни одно из них не работает, иногда установленный пользователем обработчик исключений никогда не вызывался, иногда внешние обработчики исключений игнорировались, и мы получали ошибку «необработанное исключение».

UPDATE:

Похоже, что цепочка обработчиков исключений SEH поддерживает код только внутри образа EXE.Если обработчик исключений указал на мой сгенерированный код, он никогда не будет вызван.Что мне нужно сделать, так это создать статическую заглушку функции обработчика исключений, а затем позволить ей вызывать сгенерированный обработчик.

1 Ответ

1 голос
/ 18 ноября 2011

У меня теперь есть реализация, которая немного отличается от вышеупомянутой. На самом деле псевдокод выглядит так (в 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, обработчик будет вызван дважды. Моя стратегия - просто восстановить оригинальный обработчик исключений в первом вызове.

...