Yuliya Khushnamova
Yuliya Khushnamova
0
Обложка: Статический анализ в разработке встраиваемых систем

Статический анализ в разработке встраиваемых систем

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

Одним из таких инструментов является статический анализатор кода. О статическом анализе и его пользе для встраиваемых систем и пойдёт речь в этой статье.

Статический анализ кода

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

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

  • на вход принимают исходный код проекта (желательно компилируемый);
  • преобразуют исходный код в специальную модель для дальнейшего анализа (AST, семантическая информация и так далее);
  • ищут дефектные места, применяя к модели набор диагностических правил, в основе которых лежат различные методологии;
  • сохраняют все полученные предупреждения в удобный для вас формат;
  • остаётся только изучить отчёт и исправить все дефектные места;
  • PROFIT!

Статические анализаторы применяются для широкого спектра задач. Можно выделить наиболее распространённые:

Поиск ошибок в программном коде (наиболее распространённое применение). В этом плане статический анализ здорово дополняет процесс обзора кода, позволяя найти и исправить ряд проблем до того, как засядете на долгое время с коллегой.

Улучшение качества кода в широком смысле этого слова. К качеству можно отнести читаемость, поддерживаемость, сложность кода, уровень связанности и другие аспекты, которые прямо или косвенно могут влиять на количество ошибок. Тем самым, статические анализаторы могут помогать следовать стандартам кодирования (как принятым внутри компании, так и общепринятым).

Анализ кода как часть механизма Quality Gates в CI/CD. Анализаторы могут не только сообщать о наличии возможных ошибок в коде, но и служить защитным механизмом, предотвращающим доставку кода, если уровень его качества не соответствует заданным требованиям. Такую роль могут выполнять анализаторы кода, расширяющие поведение компилятора и блокирующие сборку, если были обнаружены ошибки или несоответствия кода принятым стандартам;

Сбор метрик проекта, сбор статистики, построение графиков и диаграмм, отражающих общее «состояние здоровья» проекта.

Польза внедрения

Польза от внедрения статического анализатора в разработку встроенного ПО несомненна. Давайте рассмотрим самые очевидные положительные стороны использования статического анализа.

Использование статического анализа позволит уменьшить вероятность дорогой, а то и вовсе невозможной «перепрошивки» уже выпущенных устройств

Ошибки в ПО встраиваемых систем крайне неприятны. Неприятность в том, что ошибки невозможно или почти невозможно исправить, если началось серийное производство. Если уже выпущены сотни тысяч стиральных машин и они разъехались по магазинам, то что делать при обнаружении неадекватной работы машины в определённом режиме? В общем-то вопрос риторический и реальных вариантов два:

  1. Смириться и получать негативные отзывы клиентов на различных сайтах, портя свою репутацию. Можно, конечно, выпустить и разослать к инструкции приложение «не делайте вот так», но это тоже слабый вариант;
  2. Отозвать серию и заняться обновлением прошивок. Дорогое развлечение.

Далее, независимо от того, велик тираж устройств или мал, правка ошибок может быть проблемной или запоздалой. Ракета упала — ошибка обнаружена, но поздно. Пациенты умерли — ошибка обнаружена, но людей это не вернёт. Противоракетный комплекс начинает промахиваться — ошибка обнаружена, но ничего приятного в этой истории нет. Машины не тормозили, ошибки найдены, но пострадавшим с этого прока нет. Цена ошибки ужасает, не правда ли?

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

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

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

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

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

Наверняка у каждого разработчика была неудачная попытка «прошить» устройство, в результате чего, например, оно некорректно держало заданное напряжение или вовсе перегорало. Что произошло и где искать проблему? Ведь причиной этому может быть не только программная ошибка, но и ошибка в самом «железе» или в некачественном изготовлении макета. В результате процесс поиска ошибки может сильно затянуться. Самый обидный сценарий:

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

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

uchar Arr[3];
....
for (uchar idx = 0; idx != 4; idx++)
  avg += Arr[idx];
avg /= 3;

А предыстория этой ошибки такая. За основу были взяты наработки от предыдущего проекта, и код во многом написан методом Copy-Paste. По невнимательности разработчик в одном месте забыл заменить 4 на 3. Как итог, неопределённое поведение при обращении к индексу за пределами массива. Коварность такого кода в том, что при отладке программа может работать корректно, а в реальных условиях при многократном запуске у клиента может происходить сбой. Великолепно, если такую ошибку найдёт статический анализатор.

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

Использование статического анализа позволит подстраховать разработчика с недостаточной экспертизой

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

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

Другим примером может служить неопределённое поведение программы, которое возникает из-за неправильного использования операторов сдвига <</>>. Эти операторы очень широко используются в коде микроконтроллеров. К сожалению, программисты часто используют эти операторы крайне неаккуратно, делая программы ненадёжными и зависимыми от версии и настроек компилятора. При этом программа может работать, но вовсе не потому, что написана правильно, а потому, что везёт.

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

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

Для встраиваемых систем популярными языками программирования считаются C и С++. Именно для них были разработаны такие стандарты, как MISRA C, MISRA C++ и AUTOSAR C++. Каждый стандарт имеет немалое количество правил и рекомендаций (MISRA C: 143, MISRA C++: 228, AUTOSAR C++: более 350), придерживаться которых при кодировании просто невозможно без статических анализаторов кода.

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

Стандарты кодирования

История MISRA началась достаточно давно. В начале 90-x правительственная программа Великобритании под показательным названием «Safe IT» выделяла финансирование для различных проектов, так или иначе связанных с безопасностью электронных систем. Сам проект MISRA (Motor Industry Software Reliability Association) был основан ради создания руководства по разработке ПО для микроконтроллеров в наземных транспортных средствах – в машинах, в общем.

MISRA (как организация) – это содружество заинтересованных сторон из различных авто- и авиастроительных индустрий:

  • Bentley Motor Cars;
  • Ford Motor Company;
  • Jaguar Land Rover;
  • Delphi Diesel Systems;
  • HORIBA MIRA;
  • Protean Electric;
  • Visteon Engineering Services;
  • The University of Leeds;
  • Ricardo UK;
  • ZF TRW.

Весьма сильные игроки рынка. Неудивительно, что их первый связанный с языком стандарт — MISRA C — стал общепринятым среди разработчиков критически важных встраиваемых систем. Чуть позже появился и MISRA C++. Постепенно версии стандартов обновлялись и дорабатывались, чтобы охватывать новые возможности языков. На данный момент актуальными версиями являются MISRA C: 2012 и MISRA C++: 2008.

Отличительные черты MISRA: невероятное внимание к деталям и крайняя дотошность. Авторы не просто собрали в одном месте все пришедшие в голову «дыры» C и C++ (как, например, авторы CERT) — они тщательно проработали международные стандарты этих языков и выписали все возможные способы ошибиться. А потом взяли и сверху дописали правила и рекомендации про читаемость кода. Ведь в простом и легко читаемом коде тяжелее допустить ошибку и легче её обнаружить при Code-Review.

Зачастую у людей, которые впервые сталкиваются с MISRA, складывается мнение, что философия стандарта заключается в «запретить вон то и запретить вот это». На самом деле, это так, но лишь отчасти.

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

В MISRA C правила делятся на три основных категории: Mandatory, Required и Advisory. Mandatory — это правила, которые нельзя нарушать ни под каким предлогом. Например, в этот раздел входит правило «не используйте значение неинициализированной переменной». Required-правила менее строги: они допускают возможность отклонения, но только если эти отклонения тщательно документируются и письменно обосновываются. Остальные правила входят в категорию Advisory — это правила, которым следовать необязательно.

В MISRA C++ немного по-другому: там отсутствует категория Mandatory, и большинство прави принадлежит к категории Required. Поэтому, по сути, вы имеете право нарушить любое правило — только не забывайте документировать отклонения. Также там есть категория Document — это обязательные к выполнению правила (отклонения не допускаются), которые связаны с общими практиками вроде «Каждое использование ассемблера должно быть задокументировано» или «Подключаемая библиотека должна соответствовать MISRA C++».

Помимо описания проблематики, в стандарте содержится большое количество советов о том, что нужно знать перед началом работы: как наладить процесс разработки по MISRA; об использовании статических анализаторов для проверки кода на соответствие; какие документы нужно вести, как их заполнять и так далее.

На данный момент MISRA живёт и развивается. Например, в начале 2019 года был анонсирован The MISRA C:2012 Third Edition (First Revision) — обновлённый и дополненный новыми правилами стандарт 2012 года. Тогда же объявили о грядущем выходе MISRA C:2012 Amendment 2 — C11 Core — стандарта 2012 года, в который будут добавлены правила, впервые охватывающие версии языка Си 2011 и 2018 годов.

Не стоит на месте и MISRA C++. Как известно, последний стандарт MISRA C++ датируется 2008 годом, поэтому наиболее старшая версия языка, которую он охватывает — это C++03. Из-за этого появился ещё один стандарт, аналогичный MISRA, и называется он AUTOSAR C++. Он изначально задумывался как продолжение MISRA С++ и имел своей целью охватить более поздние версии языка. В отличие от своего вдохновителя, AUTOSAR C++ обновляется два раза в год и на данный момент поддерживает C++14. Планируется дальнейшее обновление до C++17, а затем C++20 и так далее.

Заключение

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

  • сократить время на поиск и устранение ошибок;
  • уменьшить вероятность критических ошибок;
  • уменьшить вероятность необходимости обновления прошивок;
  • контролировать общее качество кода;
  • контролировать качество работы новых членов команды;
  • строго следовать определённому стандарту разработки программного обеспечения;
  • контролировать качество кода сторонних модулей/библиотек.

Нужен ли вам статический анализ или нет, решать именно вам!