Когда я работал над своей курсовой про то, как передавать С++ исключения из ядра ОС пользовательским программам, я изучал, как работают исключения в С++ и в этом мне очень помогла эта серия статей с Хабра. Теперь я хочу дополнить некоторые моменты, которые недостают в этой серии статей.
Запуск кода автора изначальной серии статей
Первое, что вы заметите, если будете изучать работу исключений С++ по той серии - это то, что код, приведённый там, падает с Segmentation Fault, если вы используете 64-ёх битный процессор.
Дело в том, что в 64-ёх битном режиме компилятор g++ пишет указатель на структуру std::type_info в совершенно диком виде. Чтобы его раскодировать, нужен такой код:
void* read_int_tbltype(const int* ptr, uint8_t type_encoding) {
if (type_encoding == 3) {
return (void *) *ptr; // случай кодировки здорового человека
// так было в 32-ух битном gcc и в clang
} else if (type_encoding == 0x9b) { // 64-ёх битный режим курильщика (gcc)
uintptr_t result = *ptr;
if (result == 0) {
return nullptr;
}
result += (uintptr_t) ptr;
result = *((uintptr_t*)result);
return (void *)result;
} else {
exit(0);
}
}
Дополнения к функциям C++ ABI, чтобы сделать реализацию более корректной
В репозитории автора изначальной серии статей функции __cxa_begin_catch
и __cxa_end_catch
не делают ничего, кроме отладочного вывода.
В реальности у этих двух функций две задачи: во-первых,__cxa_begin_catch
должна получить по указателю структуру _Unwind_Exception
и вернуть указатель на выброшенное исключение, чтобы код catch блока мог получить к нему доступ - иначе читать его он не сможет. Во-вторых, эти две функции должны обеспечить вызов деструктора выброшенного исключения и освобождение памяти, выделенной для исключения и информацию об исключении. Для этого первая функция должна положить это исключение на стек исключений, а вторая - вызвать его деструктор, если надо, а потом осводобить память. Стек исключений нужен на случай, если одновременно в обработке будут два исключения. Он должен быть thread_local. Итоговый код получается такой:
// for freeing exceptions
thread_local __cxa_exception* last_exception;
void* __cxa_begin_catch(void* raw_unwind_exception) throw() {
auto unwind_exception = (_Unwind_Exception *)raw_unwind_exception;
__cxa_exception* exception_header = (__cxa_exception*)(unwind_exception + 1) - 1;
exception_header->nextException = last_exception;
last_exception = exception_header;
return exception_header + 1;
}
void __cxa_end_catch() {
// we need do destroy the last exception
__cxa_exception* exception_header = last_exception;
last_exception = exception_header->nextException;
if (exception_header->exceptionDestructor) {
(*exception_header->exceptionDestructor)(exception_header + 1);
}
free(exception_header->exceptionTypeName);
__cxa_free_exception((char *)(exception_header + 1));
}
Ещё есть функция __cxa_get_exception_ptr
она вызывается в том случае, если исключение ловится по ссылке, поэтому код из репозитория автора оригинальной статьи компилируется без неё. Эта функция в основном аналогична функции __cxa_begin_catch и возвращает указатель на оригинальное исключение.
void* __cxa_get_exception_ptr(_Unwind_Exception* unwind_exception) {
__cxa_exception* exception_header = (__cxa_exception*)(unwind_exception + 1) - 1;
return exception_header + 1;
}
Кажется, это всё, что я дополнил к реализации из репозитория автора оригинальной статьи. Мой код можно посмотреть здесь. Единственное (кажется), что там не реализовано - это то, что catch блок может поймать не только то исключение, которое указано у него, но и те, что от него унаследованы.
Если интересно, в следующий раз я могу написать про реализацию поддержки thread_local переменных.
Justlexa
Это формат кодировки указателей в отладочной информации DWARF.
vda19999 Автор
А почему нельзя писать как есть, как делает clang?
Tuxman
Особенно доставляет, когда начинают какие-то оптимизации придумывать на основе знаний, где и что конкретный компилятор положит, или хитрое использование UB. Например, почему нельзя просто взять и собрать Linux kernel не GCC, а clang? Или забавные интервьюеры ещё попадаются, которые очень каверзные вопросы про C++ спрашивают, думают, что если закастить указатель на объект на что-то там, и получить доступ к v-table, и что-то там ещё хитро съоптимизировать.