Search
Write a publication
Pull to refresh

Comments 17

Вы не сказали об этом явно, но из того, что написано, я делаю вывод, что при выбрасывании исключения в ядре его объект сразу же создается в памяти, доступной пользователю. А если пользователь может вызывать деструктор и освобождать память, рискну предположить, что у него и на запись туда есть право.

Возможно ли такое, что между выбрасыванием исключения в ядре и обработкой его в ядре пользовательский код в другом потоке его изменит? Если пользователь может только читать, этого хватит, чтобы прочитать данные ядра или другого процесса. В любом случае это уязвимость.

Или у вас переключение потоков запрещено во время обработки любых API-вызовов в ядре?

При выбрасывании исключения в ядре, оно и правда создаётся в памяти, доступной пользовательской программе на чтение и запись (деструктор, он, кстати, вызвать не может, потому что деструктор находится в коде ядра).

Такое, что между выбрасыванием исключения в ядре и обработкой его в ядре пользовательский код в другом потоке его изменит, похоже, возможно - это хорошее замечание, я об этом не подумал.

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

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

Перед выделением памяти в процессе надо обработать ситуацию, когда никакого процесса нет. Бывают же аппаратные прерывания и потоки, созданные самим ядром.

Аппаратные прерывания происходят в случайном контексте. Вы только представьте: сидишь себе, никого не трогаешь, складываешь и умножаешь числа, и тут на тебе "файл не найден"

А деструктор как вызовется? Перейдёт в режим ядра и выполнится?
Но тогда программа в блоке catch может переписать vptr и, если объект-исключение использует вирт. методы, в ядре можно выполнить что-то своё.

В хорошем дизайне FileAbsentException должно быть не только тэгом, что возникла ситуация «файл не найден», но и где-то внутри иметь подробности: имя файла, или InnerException. Все эти подробности должны генерироваться внутри ядра и опасность что-то поломать возрастает.

В моей реализации деструктор не вызывается никак.

Но в общем случае вызов деструктора устроен так:

  1. Когда вызывается __cxa_throw, ему передаётся указатель на деструктор.

  2. __cxa_throw записывает этот указатель в структуру __cxa_exception

  3. __cxa_end_catch, используя указатель на деструктор, вызывает его

Таким образом, кажется, что перезапись vptr не повлияет на то, что за деструктор вызовется.

То есть, чтобы вызывать деструктор в режиме ядра, можно было бы создать системный вызов, принимающий указатель на объект. При выбрасывании исключения ядро может где-то внутри запоминать адрес объекта и адрес его деструктора, а при выполнении этого системного вызова, соответственно, можно вызывать нужный деструктор.

В случае FileAbsentException, класс исключения может просто сожержать массив char, и никакой деструктор не будет нужен.

P. S. В реальности, если вы бросите экземпляр виртуального класса, там С++ не будет особо разбираться, какой реальный тип вы бросили: https://pastebin.com/n8uTCanQ

Таким образом, кажется, что перезапись vptr не повлияет на то, что за деструктор вызовется.
Если в объекте-исключении есть вложенные другие объекты (например, std::string), можно перезаписать их vptr (и не только vptr, а какие-то указатели и проэксплуатировать какие-то баги/фичи менеджера памяти в ядре. Например, чтобы free вызвался по адресу, который не принадлежит куче).
В случае FileAbsentException, класс исключения может просто сожержать массив char, и никакой деструктор не будет нужен.
Идея понятная — держать объекты-исключения как можно более простыми. Но для промышленного применения плохо подходит, когда код пишется большим числом людей, и каждый должен постоянно помнить, какие правила применяются в отношении классов-исключений.

Видимо, тогда решение - выделять исключения в памяти, которая доступна только на чтение.

Хорошее решение, но нужно не забывать про вложенные объекты, в какой памяти они выделены (ну, или отказать от вложенных объектов, требовать чтобы исключения были POD).
Но тут тоже свои подводные. Ядро Windows, например, разрешает длину путей к файлам до 32768 WCHAR-ов, т.е. на каждый exception надо держать буфер минимум 64КБ.
А ещё, если делать всё в парадигме C++, наследоваться от std::exception, там есть virtual what(), и virtual ~exception().
Интересно, да.
А вы не сравнивали при написании диплома этот ваш механизм с механизмом структурной обработки исключений в Windows (SEH)? Там хотя исключения — не совсем C++ (потому что механизм старый, он изначально был в NT ещё в начале 90-х), но выполняют сходную работу и используют сходные идеи (ту же двойную раскрутку стека, к примеру).
PS В Visual C++ для поддержки этого механизма есть специфические для производителя расширения.

Читал про SEH, но детально разобраться не успел.

Осталось написать еще одну ОС, но с Result из Rust. Думаю, это будет и быстрее и надежнее, чем исключения и числа (соответственно)

Для этого желательна поддержка в языке, тот же unwrap().
Проверять статус после каждого вызова утомительно, и ради этого применяют исключения, пусть и с потерей производительности в пессимистичной ветке.

Мне кажется, это будет очень сложно и непонятно, зачем нужно. Когда системный вызов возвращает число, то у вас всё просто.

А если вы хотите, чтобы системный вызов вернул какую-то структуру, то ядро должно выделить для него память в пространстве пользователя, а потом надо как-то очень аккуратно уничтожать этот объект. Либо пользователь может выделить память перед системным вызовом, но тогда это будет память какого-то фиксированного размера, и снова нужно очень аккуратно его уничтожать.

А если вы хотите, чтобы системный вызов вернул какую-то структуру...

Эта "структура" может занимать всего два регистра. А то и один, если возможно применить niche optimisation.

Размер тут вообще не главное. Если структура, значит (потенциально) есть конструктор-деструктор-методы-трейты. Кто ими владеет — ядро или userspace? Где находится код этих методов?
Sign up to leave a comment.

Articles