вторник, 28 июля 2009 г.

typeinfo for int

Загадочный всетаки компилятор - gcc...

Начать реализацию обработки исключений я решил в простенькой конструкции в коде:

try {
throw 1;
} catch (...) {
}

Но наткнулся на ошибку - undefined reference to `typeinfo for int'.
И вот возникла непонятная задача - как же мне описать typeinfo for int?

gcc очень загадочен. О существовании типа std::type_info он знает безо всяких дополнительных объявлений, что не мешает описать его локально. Но в gcc этот класс чисто базовый, констуктор у него защищен. На это можно было бы наверное наплевать и объявить все по своему, если бы в этом был смысл.

Но необходимый мне typeinfo должен иметь другой тип: __fundamental_type_info. И мне нужно проинициализировать его статически, для чего я долго изголялся с размещающими new в статическом буфере...

Стоит отметить что до сих пор линкер исправно ругался на отсутсвие typeinfo. Но после этого неожиданно начал ругаться на отсутвие 'vtable for __cxxabiv1::__pointer_type_info', странно.

Исследование модуля objdump'ом показало в нем наличие большого количества typeinfo. Стал выяснять - откуда они взялись. И вот ведь загадочный gcc...

Стоит вставить следующий код:

namespace __cxxabiv1 {
class __fundamental_type_info : public std::type_info
{
virtual ~__fundamental_type_info() { }
};
}

Как все необходимые фундаментальные typeinfo появляются как по волшебству. И с размещающим new я изголялся зря. :)

А с исключениями пока еще далеко не все понятно, разбираюсь.

пятница, 24 июля 2009 г.

Исключительная ситуация

Нет, у меня все в порядке, дела идут и жизнь легка. Просто лето и отпуск, и я совершенно забросил свой блог. Но делать так ни в коем случае нельзя. У меня уже был подобный опыт по забрасыванию рассылки comp.soft.prog.asmos на Subscribe.ru, которая находится в дауне до сих пор. Чем больше времени проходит с момента последнего поста, тем проще ничего не писать.

А темой сегодняшнего поста я избрал исключения. Но я не стану расскзывать тривиальные вещи, про то, что исключения из деструктора не бросатся, это и так все знают. Мне вдруг стало интересно как это работает изнутри. Но пока здесь одни вопросы. Разбираться надо.

gcc очень наглый компилятор. Он может спокойно инлайнить функции, даже если используется -O0. Он может нормальные казалось бы вызовы стандартных функций заменять ассемблерными мнемониками. Но с исключениями все обстоит еще сложнее. Стандартный, казалось бы, С++ код превращается в нагромождения непонятных вызовов c которыми я и хотел бы разобраться, чтобы прикрутить исключения к своему ядру.

Правда, тут все зависит от цены, так сказать. Патч для ядра linux, позволяющий использовать C++ с исключениями занимает 600к, в то время как вся моя кроссплатформенная часть (для которой собственно рунтайм и тащится) занимает 200к. Неравноценный получается обмен.

В самом gcc все реализовано максимально переносимо, из за чего, вероятно, трудно все это понять. Большой уровень косвенности. Мне же переносимость в данном случае не нужна, нужна минимальная реализация для конкретной платформы.

Так как же работают исключения? gcc, не смортя на всю свою наглость, конечно молодец. Для корректной отработки деструкторов выделенных в стеке объектов в нем используются так называемые "landing pad", область приземления? или как это правильно перевести. Область приземления располагается в конце каждого программного блока, в котором требуется деинициализация. Причем gcc очеь грамотно задвигает все, что не требуется деинициализировать.

Координаты этих областей перечисленны в "exception handling tables". Эти таблицы формируются компилятором для каждой функции. Хотя может и не для каждой, точно сказать пока не могу.

При возникновении исключения управление передается в функцию __cxa_throw, и дальше начинается сплошной грязный хак. Управление обратно уже не возвращается. Происходит разворот стека, для каждой функции производится поиск таблиц исключений, и в зависимости от адреса, где вызывалась функция __cxa_throw, выбирается ближайший landing pad и управление передается на него.

Кстати, именно на функцию __cxa_throw надо ставить брекпоинт для программы, собранной с помощью gcc, если есть желание отловить момент возникновения исключения в отладчике.

Чтобы механизм разворота стека заработал дальше, в случае отсутствия обработчиков исключений, в конце функции ставится вызов _Unwind_Resume, в котором может быть инициирована дальнейшая раскрутка стека, если исключение имеет место быть. Но ставится он не в каждой функции. Иногда даже перед вызовами деструкторов. По какому принципу он расставляется - я пока не осознал.

Стандарт оговаривает и структуры, которые вращаются внутри __cxa_throw и других функций, связанных с исключениями, но для минимальной реализации на это можно забить. Главное - это понять логику разворота стека и связывания функций с соответствующими таблицами прерываний.

Так же для меня является загадкой, почему разработчики gcc так перемешали интерфейсы __cxa* и _Unwind*. Но в недрах реализации они переплетены столь плотно, что разорвать их практически нереально.

Если вдруг кто-то уже разбирался с обработкой исключений в gcc - отзовитесь, у меня много вопросов. :) Хотя постепенно осознание придет, если долго разбираться.