Comments 17
Вы не сказали об этом явно, но из того, что написано, я делаю вывод, что при выбрасывании исключения в ядре его объект сразу же создается в памяти, доступной пользователю. А если пользователь может вызывать деструктор и освобождать память, рискну предположить, что у него и на запись туда есть право.
Возможно ли такое, что между выбрасыванием исключения в ядре и обработкой его в ядре пользовательский код в другом потоке его изменит? Если пользователь может только читать, этого хватит, чтобы прочитать данные ядра или другого процесса. В любом случае это уязвимость.
Или у вас переключение потоков запрещено во время обработки любых API-вызовов в ядре?
При выбрасывании исключения в ядре, оно и правда создаётся в памяти, доступной пользовательской программе на чтение и запись (деструктор, он, кстати, вызвать не может, потому что деструктор находится в коде ядра).
Такое, что между выбрасыванием исключения в ядре и обработкой его в ядре пользовательский код в другом потоке его изменит, похоже, возможно - это хорошее замечание, я об этом не подумал.
А вот данные другого процесса читать вряд ли получится. Исключение будет создано в виртуальном пространстве процесса, выполнившего системный вызов, а другим процессам оно не доступно.
В любом случае, в полноценной реализации надо как-то аккуратнее реализовать размещение исключений в памяти - например, давать пользователю доступ к памяти, где оно лежит, только когда оно выбрасывается.
Перед выделением памяти в процессе надо обработать ситуацию, когда никакого процесса нет. Бывают же аппаратные прерывания и потоки, созданные самим ядром.
Аппаратные прерывания происходят в случайном контексте. Вы только представьте: сидишь себе, никого не трогаешь, складываешь и умножаешь числа, и тут на тебе "файл не найден"
Но тогда программа в блоке catch может переписать vptr и, если объект-исключение использует вирт. методы, в ядре можно выполнить что-то своё.
В хорошем дизайне FileAbsentException должно быть не только тэгом, что возникла ситуация «файл не найден», но и где-то внутри иметь подробности: имя файла, или InnerException. Все эти подробности должны генерироваться внутри ядра и опасность что-то поломать возрастает.
В моей реализации деструктор не вызывается никак.
Но в общем случае вызов деструктора устроен так:
Когда вызывается __cxa_throw, ему передаётся указатель на деструктор.
__cxa_throw записывает этот указатель в структуру __cxa_exception
__cxa_end_catch, используя указатель на деструктор, вызывает его
Таким образом, кажется, что перезапись vptr не повлияет на то, что за деструктор вызовется.
То есть, чтобы вызывать деструктор в режиме ядра, можно было бы создать системный вызов, принимающий указатель на объект. При выбрасывании исключения ядро может где-то внутри запоминать адрес объекта и адрес его деструктора, а при выполнении этого системного вызова, соответственно, можно вызывать нужный деструктор.
В случае FileAbsentException, класс исключения может просто сожержать массив char, и никакой деструктор не будет нужен.
P. S. В реальности, если вы бросите экземпляр виртуального класса, там С++ не будет особо разбираться, какой реальный тип вы бросили: https://pastebin.com/n8uTCanQ
Таким образом, кажется, что перезапись vptr не повлияет на то, что за деструктор вызовется.Если в объекте-исключении есть вложенные другие объекты (например, std::string), можно перезаписать их vptr (и не только vptr, а какие-то указатели и проэксплуатировать какие-то баги/фичи менеджера памяти в ядре. Например, чтобы free вызвался по адресу, который не принадлежит куче).
В случае FileAbsentException, класс исключения может просто сожержать массив char, и никакой деструктор не будет нужен.Идея понятная — держать объекты-исключения как можно более простыми. Но для промышленного применения плохо подходит, когда код пишется большим числом людей, и каждый должен постоянно помнить, какие правила применяются в отношении классов-исключений.
Видимо, тогда решение - выделять исключения в памяти, которая доступна только на чтение.
А вы не сравнивали при написании диплома этот ваш механизм с механизмом структурной обработки исключений в Windows (SEH)? Там хотя исключения — не совсем C++ (потому что механизм старый, он изначально был в NT ещё в начале 90-х), но выполняют сходную работу и используют сходные идеи (ту же двойную раскрутку стека, к примеру).
PS В Visual C++ для поддержки этого механизма есть специфические для производителя расширения.
Осталось написать еще одну ОС, но с Result
из Rust. Думаю, это будет и быстрее и надежнее, чем исключения и числа (соответственно)
Проверять статус после каждого вызова утомительно, и ради этого применяют исключения, пусть и с потерей производительности в пессимистичной ветке.
Мне кажется, это будет очень сложно и непонятно, зачем нужно. Когда системный вызов возвращает число, то у вас всё просто.
А если вы хотите, чтобы системный вызов вернул какую-то структуру, то ядро должно выделить для него память в пространстве пользователя, а потом надо как-то очень аккуратно уничтожать этот объект. Либо пользователь может выделить память перед системным вызовом, но тогда это будет память какого-то фиксированного размера, и снова нужно очень аккуратно его уничтожать.
А если вы хотите, чтобы системный вызов вернул какую-то структуру...
Эта "структура" может занимать всего два регистра. А то и один, если возможно применить niche optimisation.
Как научить операционную систему «выбрасывать» С++ исключения из системных вызовов и как это можно применять