Научная статья на тему 'Проект РуСи для обучения и создания высоконадежных программных систем'

Проект РуСи для обучения и создания высоконадежных программных систем Текст научной статьи по специальности «Компьютерные и информационные науки»

CC BY
704
171
i Надоели баннеры? Вы всегда можете отключить рекламу.
Ключевые слова
ЯЗЫК СИ / ЗАЩИЩЕННОЕ ПРОГРАММИРОВАНИЕ / КОНТРОЛЬ ТИПОВ / ДИНАМИЧЕСКИЙ КОНТРОЛЬ / СРЕДЫ ПРОГРАММИРОВАНИЯ / C LANGUAGE / COMPILERS / PROTECTED SOFTWARE DEVELOPMENT / TYPE CHECKING / RUNTIME CHECK / TOOLKITS / RUNTIME ENVIRONMENT

Аннотация научной статьи по компьютерным и информационным наукам, автор научной работы — Терехов Андрей Николаевич, Терехов Михаил Андреевич

Проект РуСи преследует две цели облегчить изучение программирования школьниками и студентами и создать среду для разработки высоконадежного программного обеспечения, максимально защищенного от попыток проникновения вирусов и других атак. Эти цели достигаются путем введения ограничений на входной язык и существенным расширением статического и динамического контроля среды программирования. Хотя ограничений (и расширений тоже!) входного языка довольно много, в основе лежит широко распространенный язык Си, что позволяет надеяться на широкое применение проекта РуСи.

i Надоели баннеры? Вы всегда можете отключить рекламу.
iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.
i Надоели баннеры? Вы всегда можете отключить рекламу.

RuC PROJECT FOR EDUCATION AND RELIABLE SOFTWARE SYSTEMS DEVELOPMENT

RuC project has two goals to make software education most easily understood and to create a toolkit for highly reliable software development, which is protected from viruses penetration and other attacks. These goals could be obtained by introduction of restrictions on input language and substantial widening of toolkit static and dynamic checking. Despite of the fact that there are many restrictions and new features of input language, the widely used C-language is the founding element. That fact gives us an opportunity to believe in wide application of our approach.

Текст научной работы на тему «Проект РуСи для обучения и создания высоконадежных программных систем»

УДК 004.42 DOI: 10.17213/0321-2653-2017-3-70-75

ПРОЕКТ РуСи ДЛЯ ОБУЧЕНИЯ И СОЗДАНИЯ ВЫСОКОНАДЕЖНЫХ

ПРОГРАММНЫХ СИСТЕМ

© 2017 г. А.Н. Терехов, М.А. Терехов

Санкт-Петербургский государственный университет, г. Санкт-Петербург, Россия

RuC PROJECT FOR EDUCATION AND RELIABLE SOFTWARE

SYSTEMS DEVELOPMENT

A.N. Terekhov, M.A. Terekhov

Saint-Petersburg State University, Saint-Petersburg, Russia

Терехов Андрей Николаевич - д-р физ.-мат. наук, профессор, зав. кафедрой «Системное программирование», Санкт-Петербургский государственный университет, г. Санкт-Петербург, Россия. E-mail: a.terekhov@spbu.ru

Терехов Михаил Андреевич - студент, Санкт-Петербургский государственный университет, г. Санкт-Петербург, Россия. E-mail: st054464@student.spbu.ru

Terekhov Andrey Nickolaevich - Doctor of Physical and Mathematical Sciences, professor, department head of the «Software Engineering», Saint-Petersburg State University, Saint-Petersburg, Russia. E-mail: a.terekhov@spbu.ru

Terekhov Mikhail Andreevich - student, Saint-Petersburg State University, Saint-Petersburg, Russia. E-mail: st054464@student. spbu.ru

Проект РуСи преследует две цели - облегчить изучение программирования школьниками и студентами и создать среду для разработки высоконадежного программного обеспечения, максимально защищенного от попыток проникновения вирусов и других атак. Эти цели достигаются путем введения ограничений на входной язык и существенным расширением статического и динамического контроля среды программирования. Хотя ограничений (и расширений тоже!) входного языка довольно много, в основе лежит широко распространенный язык Си, что позволяет надеяться на широкое применение проекта РуСи.

Ключевые слова: язык Си; защищенное программирование; контроль типов; динамический контроль; среды программирования.

RuC project has two goals - to make software education most easily understood and to create a toolkit for highly reliable software development, which is protected from viruses penetration and other attacks. These goals could be obtained by introduction of restrictions on input language and substantial widening of toolkit static and dynamic checking. Despite of the fact that there are many restrictions and new features of input language, the widely used C-language is the founding element. That fact gives us an opportunity to believe in wide application of our approach.

Keywords: C language; compilers; protected software development; type checking; runtime check; toolkits; runtime environment.

Введение

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

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

ISSN 0321-2653 IZVESTIYA VUZOV. SEVERO-KAVKAZSKIIREGION.

языки и системы программирования, ориентированные на создание программ с максимально возможным контролем ошибок как периода компиляции (статические), так и периода счета (динамические). Упомянем для примера языки Оберон Никлауса Вирта [1] и Эйфель Бертрана Мейера [2], однако, как и многие другие языки, созданные с той же целью, эти так и не стали массовыми. К сожалению, в программировании так часто бывает, и причины надо искать в областях инженерной психологии и экономики (сколько и кем было вложено средств в продвижение языка). Самым известным примером в ряду таких не принятых обществом программистов языков является Алгол 68 [3] - первый в истории язык высокого уровня с не только точно определенным синтаксисом (сейчас этим никого не удивить), но и семантикой, а это и сегодня для многих языков является проблемой. Многие языковые черты, впервые появившиеся в Алголе 68, например полный видовой контроль периода компиляции, последовательные предложения и условные выражения, выдающие значение, операторы с присваиванием типа +:=, рекурсивные типы и т.д., затем с успехом были применены в Паскале, Си, Ада и других языках, появившихся позднее.

В данном проекте мы пошли другим путем. Не стали выдумывать новый язык, а взяли за основу широко распространенный язык Си [4], слегка, на наш взгляд, изменив его с целью повышения защищенности от ошибок пользователя, и выполнили собственную его реализацию, причем не только компилятора, но и полноценной среды разработки. Этот проект получил название РуСи.

Первоначально проект возник из потребностей преподавателей школьных кружков робототехники. Уже довольно давно они применяют нашу графическую технологию ТРИК-студия, особенно удачно она подходит для школьников младших и средних классов, но при этом хотелось бы, чтобы школьники могли прочитать те программы, которые генерируются из графических диаграмм, т. е. образовать своеобразный «мостик» к урокам информатики. Сейчас во многих странах популярен подход к программированию на основе графических моделей с автоматической генерацией кода на целевом языке. Традиционно для этого применяется язык Си, но нельзя забывать, что школьники младших и средних классов практически не знают английского языка. И так-то учиться программировать

TECHNICAL SCIENCE. 2017. No 3

тяжело, а тут - незнакомые слова, незнакомые сообщения и т. п. Поэтому возникла идея разработать компилятор языка Си в коды выдуманной нами виртуальной машины с русскими сообщениями, ключевыми словами и идентификаторами и интерпретатор этой машины (но никто не запрещает использовать и английские ключевые слова и идентификаторы). Виртуальная машина обеспечивает легкую переносимость на любые платформы и, хотя работает несколько медленней за счет накладных расходов на дешифрацию кодов, но, если хорошо продумать ее архитектуру, то эти накладные расходы становятся незначительными. Кроме того, появляется хорошая возможность отладки, которая не во всем возможна на реальных ЭВМ без специальной аппаратной поддержки - например, остановка по записи в какую-то ячейку и т.п.

Как компилятор, так и интерпретатор реализованы на стандартном Си, поэтому легко переносятся на все платформы, в частности, интерпретатор перенесен на конструктор роботов ТРИК [5, 6], который разрабатывается сотрудниками и студентами кафедр системного программирования и теоретической кибернетики мате-матико-механического факультета СПбГУ.

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

Контекст работы

Введение ограничений на входной язык при реализации компилятора - это плохой тон, свидетельство низкой квалификации авторов реализации, поэтому мы с некоторым трепетом рассматривали варианты ограничений языка Си с целью повышения защищенности авторов программ от их собственных ошибок. Однако оказалось, что мы на этом пути далеко не первые. Многие авторы пытались «улучшить» старый добрый Си, назовем хотя бы языки D [7],

Cyclone [8], много ссылок на такие работы есть в классической книге Роберта Сикорда [9]. Все эти языки и системы программирования преследуют несколько иную по сравнению с нами цель -предотвратить проникновение вредоносного кода в программы, написанные на языке Си. Мы же ставим перед собой несколько более скромную задачу - по мере возможности не давать программисту совершать многие типичные ошибки и облегчить локализацию и исправление тех, которые ему все же удалось сделать.

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

Чтобы пояснить, о чем идет речь, приведем несколько характерных черт языков D и Cyclone, добавленных ради повышения защищенности программ на языке Си:

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

- нет приведений типов, не являющихся указателями, к типу указателя и наоборот;

- нет арифметики над указателями;

- нет ассемблерных вставок;

- не используются адреса локальных переменных и параметров безопасной функции;

- внутри безопасных функций нет вызовов небезопасных, в частности, стандартных библиотечных функций.

2. В языке Cyclone предусмотрено несколько ограничений и расширений языка:

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

- арифметические операции разрешены только для специального класса (так называемых «толстых») указателей, которые содержат в себе не только адрес, но и возможные границы значений;

- вводятся дополнительные проверки на отсутствие «висячих» указателей, ссылающихся на уже освобожденную память;

- нельзя передать управление по goto внутрь составных конструкций (циклы, переключатели, условные операторы).

Что мы изменили в языке Си

1. Мы отказались от union в структурах. Во-первых, это редко используемая черта языка Си, а во-вторых, она приводит к понижению уровня безопасности.

2. В РуСи нет арифметики указателей. Пользователь может описать переменную-указатель, создать указатель с помощью операции &, присвоить указатель переменной подходящего типа, но не может, например, прибавить к указателю константу и что-либо записать/ прочитать по полученному адресу. Все операции динамического отведения памяти имеют своим операндом тип значения, для которого отводится память. Таким образом, любой указатель знает тип значения, на который он ссылается.

3. Разыменование указателя (т. е. получение значения по его адресу) в момент, когда он равен NULL, - одна из частых и весьма болезненных ошибок, с другой стороны, вставка проверки на NULL при каждом обращении к указателю резко ухудшает эффективность кода. Несколько лет назад было придумано, как обойти эту трудность. В дополнение к обычному типу указателя (например, int *) вводится еще один тип указателя (например, int @). Второй тип принято называть Never Null Pointer, т.е. указатель, который никогда не принимает значение NULL. Переменные такого типа всегда должны инициализироваться, причем значением, отличным от NULL. Использование таких указателей не требует проверки на NULL, если переменной такого типа присвоить обычный указатель, то компилятор автоматически вставит проверку на NULL. В результате, например, цепные списки будут реализовываться с помощью обычных указателей, но там и так проверка на NULL необходима для определения конца списка, а, например, файлы после открытия будут иметь второй тип указателя, поэтому в массовых операциях типа scanf или printf файлами можно пользоваться безопасно и без проверки на NULL.

4. Массив РуСи - это нормальный языковый объект, хотя в базовом языке Си это совсем не так. Его можно описать, в том числе с динамически вычисляемыми границами (в Си границы только статические), вырезать из него элемент, например a[i][j], или даже a[i] для многомерного массива, присвоить другому массиву,

ISSN 0321-2653 IZVESTIYA VUZOV. SEVERO-KAVKAZSKIIREGION.

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

5. За долгие годы существования языка Си появилось большое количество способов описания формальных параметров функций. Мы выбрали один, наиболее выразительный, на наш взгляд, и ужесточили проверки соответствия фактических параметров формальным:

int f (int a, float b) { •••}

Если функция имеет параметром другую функцию, то идентификаторы её параметров не указываются. Ни один известный нам транслятор с языка Си не проверяет соответствие этих идентификаторов у функций, переданных фактическими параметрами:

int f (int a, int(* g) (int, float)) { ...}

6. Обеспечена подробная и понятная система выдачи ошибок с привязкой к месту возникновения ошибки.

Среда программирования РуСи

Изменения коснулись не только языка, но и всей системы программирования. Транслятор проекта РуСи производит лексический, синтаксический и семантический анализ исходной программы и строит промежуточное представление в виде линейной развертки дерева разбора. По этому промежуточному представлению может быть сгенерирован код придуманной нами виртуальной машины с широкими возможностями отладки, которых трудно достичь, работая на обычной ЭВМ, а может быть сгенерирован код LLVM - стандарта де-факто для промежуточных языков, этот код может быть откомпилирован в практически любую архитектуру ЭВМ.

Первый вариант компилятора был реализован на платформе MacOS в среде Xcode в консольном режиме. Для пропуска одиночных тестов этого вполне хватало, но при первом же использовании компилятора школьниками и студентами стало ясно, что необходима нормальная IDE (Integrated Development Environment),

TECHNICAL SCIENCE. 2017. No 3

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

Изначально планировалось сделать её кроссплатформенной и для этого была выбрана легковесная и простая в использовании библиотека элементов интерфейса FLTK [10], поддерживающая и MacOS, и Windows, и Linux. Но почти сразу стало ясно, что нельзя просто взять и запустить один и тот же код на разных системах. Дело в том, что по ряду причин компилятор, интерпретатор и сама среда должны быть скомпилированы в разные файлы. Назовем эти причины:

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

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

3. Наконец, среда, компилятор и интерпретатор решают абсолютно разные и практически независимые задачи, так что разбиение на отдельные исполняемые файлы необходимо для стройного построения проекта, деления его на логически завершенные части.

Но вместе с делением всего РуСи на отдельные исполняемые файлы возникает необходимость «общения» этих файлов между собой: например, среда должна выводить на экран сообщения компилятора или вывод интерпретатора виртуальной машины.

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

наблюдать, что программа выводит, параллельно с её исполнением. А вот со вводом таким образом поступить не получится. Если требуется прочитать значение, интерпретатор не может продолжать работу, пока это значение не будет получено, а точное количество информации, которое ему потребуется, неизвестно до начала работы. Более того, пользователь может написать интерактивную программу и в зависимости от ее вывода вводить различные данные, так что обработка ввода/вывода в режиме оффлайн неприемлема.

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

Механизмы обмена информацией между различными запущенными исполняемыми файлами уже не могут быть независимыми от платформы. В системе Windows они реализуются посредством так называемых именованных каналов (named pipes), из которых можно читать записанные туда сообщения или записывать туда что-либо. Для взаимодействия среды с компилятором удобно использовать односторонний канал, так как нам нужно только выводить сообщения РуСи. Однако для взаимодействия с интерпретатором, как было показано, требуется обмен данными в обе стороны. Подобные механизмы есть и в других системах, но они, конечно, обладают другой семантикой.

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

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

Заключение

Первые изменения языка Си имели только одну цель - облегчить поиск ошибок обучаю-

щимися (например, отказ от арифметики указателей и обязательный контроль индексов массивов) и упростить написание программ (например, оператор print с аргументом произвольного типа вместо громоздкой форматной печати). Но когда об этом проекте узнали заказчики ООО «Ланит-Терком», жизненно заинтересованные в надежности и защищенности программного обеспечения критически важных систем, пришлось пересмотреть цели и задачи проекта РуСи. Именно тогда мы стали работать над гарантированно ненулевыми указателями, сделали обязательным указание типов в процедурах динамического захвата памяти, расширили список статических и динамических проверок. Данная статья является первой попыткой описания результатов работы в этом направлении.

Литература

1. Niklaus Wirth, Jurg Gutknecht. Project Oberon The Design of an Operating System and Compiler, Edition 2005, URL: http ://www. ethoberon. ethz. ch/WirthPubl/ProjectObero n.pdf (дата обращения 01.05.2017)

2. Bertrand Meyer. Touch of Class, Learning to Program Well with Objects and Contracts. Springer-Verlag Berlin; Heidelberg, 2009, 876 р. (русский перевод: Бертран Мейер, Почувствуй класс, Учимся программировать хорошо с объектами и контрактами. М.: Интуит, Бином, 2011. 775 c.)

3. Revised Report on the Algorithmic Language ALGOL 68, Springer Berlin Heidelberg, 1976 (русский перевод: Пересмотренное сообщение об Алголе 68. М.: Мир, 1979. 533 с.)

4. Kernighan B.W., Ritchie D.M. The C Programming Language. Englewood Cliffs, NJ: Prentice Hall, 1978, 2nd edition 1988 (русский перевод: Брайан Керниган, Деннис Ритчи. Язык программирования C. М.: Вильямс. 2006. 304 с.)

5. Terekhov A., Luchin R., Filippov S. Educational Cybernetical Construction Set for Schools and Universities, Advances in Control Education, Vol. 9. Part. 1

6. Блог проекта ТРИК URL: http://blog.trikset.com/ (дата обращения 01.05.2017)

7. Alexandrescu A. The D programming language, Boston: Adison-Wesley, 2010 (русский перевод: Андрей Алексан-дреску, Язык программирования D, СПб.; М.: Символ, 2012. 536 с.)

8. Grossman D., Hicks M., Trevor J., Morrisett G. «Cyclone: A Type-Safe Dialect of C.» C/C++ Users Journal 23. № 1 (2005): 6 - 13.

9. Robert C. Seacord. Secure Coding in C and C++, Second Edition, Addison-Wesley, 2013 (русский перевод: Роберт С. Сикорд, Безопасное программирование на С и С++: 2-е изд. М.: Вильямс. 2016. 496 с.)

10. Набор инструментов для создания интерфейсов URL:http://www.fltk.org/ (дата обращения 01.05.2017)

ISSN 0321-2653 IZVESTIYA VUZOV. SEVERO-KAVKAZSKIIREGION.

TECHNICAL SCIENCE. 2017. No 3

References

1. Niklaus Wirth, Jurg Gutknecht, Project Oberon The Design of an Operating System and Compiler, Edition 2005. Available at: http://www.ethoberon.ethz.ch/WirthPubl/ProjectOberon.pdf (accessed 01.05.2017)

2. Bertrand Meyer, Touch of Class, Learning to Program Well with Objects and Contracts, Springer-Verlag Berlin Heidelberg, 2009, 876 p.

3. Revised Report on the Algorithmic Language ALGOL 68, Springer Berlin Heidelberg, 1976.

4. Kernighan B.W., Ritchie D.M. The C Programming Language. Englewood Cliffs, NJ: Prentice Hall, 1978, 2nd edition. 1988.

5. Terekhov Andrey, Luchin Roman, Filippov Sergey Educational Cybernetical Construction Set for Schools and Universities, Advances in Control Education, Vol. 9. Part 1.

6. Blog proekta TRIK [Blog of the TRIK project]. Available at: http://blog.trikset.com/ (accessed 01.05.2017)

7. Alexandrescu A. The D programming language, Boston. Adison-Wesley, 2010.

8. D. Grossman, M. Hicks, J. Trevor, G. Morrisett. «Cyclone: A Type-Safe Dialect of C.» C/C++ Users Journal 23, 2005. No 1. 6-13.

9. Robert C. Seacord, Secure Coding in C and C++, Second Edition, Addison-Wesley, 2013.

10. Nabor instrumentov dlya sozdaniya interfeisov [Tool kit for creation of interfaces]. Available at: http://www.fltk.org/ (accessed

01.05.2017)

Поступила в редакцию /Received

iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.

03 мая 2017 г. /May 03, 2017

i Надоели баннеры? Вы всегда можете отключить рекламу.