Когда я работал над своей курсовой про то, как передавать С++ исключения из ядра ОС пользовательским программам, я изучал, как работают исключения в С++ и в этом мне очень помогла эта серия статей с Хабра. Теперь я хочу дополнить некоторые моменты, которые недостают в этой серии статей.
Запуск кода автора изначальной серии статей
Первое, что вы заметите, если будете изучать работу исключений С++ по той серии - это то, что код, приведённый там, падает с 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 переменных.
