PVS Studio и FreeBSD
И мы снова запускаем то что нельзя там где это невозможно: на арене цирка работа с анализатором кода PVS Studio на FreeBSD! Дичь, треш, пар, жесть и угар — все как вы любите.
Что это и зачем
Если вы никогда не слышали термин «анализатор кода» и ничего не знаете о проекте PVS Studio — вам это 100% не надо, забудьте как страшный сон и спите себе спокойно.
А для юных дев непричастных читателей, чей девственный мозг еще не поврежден профессиональной разработкой поясняю:
анализатор кода — это такая программа для поиска проблем в исходном коде других программ.
Нет, вы не можете ее нанять вместо ваших штатных быдлокодеров замечательных разработчиков, потому что для ее запуска и анализа результатов пока еще нужен живой человек.
А если серьезно, то вот выдержка из википедии, для лучшего понимания:
Стати́ческий ана́лиз ко́да (англ. static code analysis) — анализ программного обеспечения, производимый без реального выполнения исследуемых программ (в отличие от динамического анализа). В большинстве случаев анализ производится над исходным кодом, хотя, иногда анализу подвергается объектный код, например P-код или код на MSIL. Термин обычно применяют к анализу, производимому специальным программным обеспечением (ПО), тогда как ручной анализ называют «program understanding», «program comprehension» (пониманием или постижением программы).
Вообщем это очень специфичный и сложный инструмент, для опытных программистов, прошедших определенный путь в своем развитии.
И прямо сейчас мы будем его запускать, прямо вот этими руками да.
Запуск на FreeBSD
Разумеется FreeBSD не поддерживается создателями «PVS-Studio» (ну кто бы сомневался), поэтому будем использовать бинарник версии для Линукса.
Скачиваются все версии PVS Studio по этой ссылке.
Для тех кто не знает ассемблер — сей софт платный и для работы нужен ключ. Триальный ключ (на неделю работы) можно получить вот тут, сам ключ приходит на почту.
В статье речь пойдет разумеется о самом хардкорном варианте анализатора — для языков Си и C++
Про Java и C# версии — в следующий раз, благо они куда более простые в плане поддержки всяких диких операционных систем.
cd /opt/app tar xvf ~/Downloads/pvs-studio-7.27.75620.346-x86_64.tgz
Как видно по выдаче команды file, бинарники являются 64-битными, со статичной сборкой:
cd pvs-studio-7.27.75620.346-x86_64/ [alex@zodiac /opt/app/pvs-studio-7.27.75620.346-x86_64]$ file ./bin/pvs-studio-analyzer ./bin/pvs-studio-analyzer: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, for GNU/Li nux 3.2.0, BuildID[xxHash]=328d76629f3a5b28, not stripped
Что дает отличный шанс на их использование во FreeBSD через эмулятор линукса:
В FreeBSD уже давно есть слой эмуляции для запуска линуксового софта, называется Linuxulator. По нему написано огромное количество статей и всяких гайдов, поэтому фокусироваться на нем не буду, а просто кратко пройдусь по необходимым шагам для установки, в рамках задачи запуска анализатора.
pkg install linux_base-c7
Прописываем в /etc/fstab линуксовые /proc и /sys виртуальные файловые системы:
linprocfs /compat/linux/proc linprocfs rw 0 0 linsysfs /compat/linux/sys linsysfs rw 0 0
Если надо чтоб работало без перезапуска, выполняем:
mount -a
Прописываем в /etc/rc.conf активацию сервиса эмуляции:
linux_enable="YES" linux64_enable="YES"
Либо перезагружаем систему либо запускаем вручную сервис эмуляции:
service linux start
Теперь возвращаемся к «PVS Studio», если вы все настроили и включили правильно, то сможете запустить бинарник анализатора:
Минутка внутреннего устройства
Немного расскажу как оно вообще все работает:
Существуют специальные инструменты трассировки, которые записывают в лог-файл все вызовы компилятора, со всеми ключами, заголовочными файлами и библиотеками.
Вот по такому сформированному логу вызовов и происходит анализ проекта. Выглядит это как-то так (фрагмент):
[ { "arguments": [ "/usr/bin/clang++", "-c", "-pipe", "-std=c++11", "-O2", "-std=gnu++11", "-Wall", "-Wextra", "-pthread", "-fPIC", "-DQT_NO_DEBUG", "-DQT_WIDGETS_LIB", "-DQT_MULTIMEDIA_LIB", "-DQT_GUI_LIB", "-DQT_SCRIPT_LIB", "-DQT_TESTLIB_LIB", "-DQT_NETWORK_LIB", "-DQT_CORE_LIB", "-DQT_TESTCASE_BUILDDIR=\"/opt/src/ukncbtl-qt/emulator\"", "-I.", "-I/usr/local/include/qt5", "-I/usr/local/include/qt5/QtWidgets", "-I/usr/local/include/qt5/QtMultimedia", "-I/usr/local/include/qt5/QtGui", "-I/usr/local/include/qt5/QtScript", "-I/usr/local/include/qt5/QtTest", "-I/usr/local/include/qt5/QtNetwork", "-I/usr/local/include/qt5/QtCore", "-I.", "-I/usr/local/include", "-I.", "-I/usr/local/include", "-I/usr/local/lib/qt5/mkspecs/freebsd-clang", "-o", "main.o", "main.cpp" ], "directory": "/opt/src/ukncbtl-qt/emulator", "file": "/opt/src/ukncbtl-qt/emulator/main.cpp", "output": "/opt/src/ukncbtl-qt/emulator/main.o" }, ...
Вообщем самая первая задача заключается в том чтобы сформировать такой замечательный файл, используя инструментарий сборки проекта (см. ниже).
Следующая стадия это запуск анализатора и формирование промежуточного файла с результатом, предназначенного для машинной обработки.
Очень похоже на Perl, поэтому думаю для Perl-программиста не будет особой проблемы его прочитать:
Следующей стадией является преобразование этого промежуточного файла в формат, который смогут прочитать уже обычные программисты:
pvs-studio.com/en/docs/warnings/ 1 err Help: The documentation for all analyzer warnings is available here: https://pvs-studio.com/en/docs/warnings/. /opt/src/ukncbtl-qt/emulator/emubase/Memory.h 101 err V536 Be advised that the utilized constant value is represented by an octal form. Oct: 0160000, Dec: 57344. /opt/src/ukncbtl-qt/emulator/emubase/Memory.h 143 err V536 Be advised that the utilized constant value is represented by an octal form. Oct: 0160000, Dec: 57344. /opt/src/ukncbtl-qt/emulator/emubase/Processor.h 289 err V536 Be advised that the utilized constant value is represented by an octal form. Oct: 0600, Dec: 384. /opt/src/ukncbtl-qt/emulator/emubase/Processor.h 294 err V536 Be advised that the utilized constant value is represented by an octal form. Oct: 0600, Dec: 384. /opt/src/ukncbtl-qt/emulator/emubase/Processor.h 299 err V536 Be advised that the utilized constant value is represented by an octal form. Oct: 0600, Dec: 384. /opt/src/ukncbtl-qt/emulator/emubase/Processor.h 304 err V536 Be advised that the utilized constant value is represented by an octal form. Oct: 0600, Dec: 384. /opt/src/ukncbtl-qt/emulator/emubase/Processor.h 311 err V536 Be advised that the utilized constant value is represented by an octal form. Oct: 0600, Dec: 384.
Тут уж совсем все просто и очевидно:
конкретный файл, номер строки, уровень п#здеца, код предупреждения PS-Studio, текст сообщения.
Все для дебилов (владеющих английским).
Тестовые проекты
Я взял три разных открытых проекта с разными системами сборки, для иллюстрации всех основных вариантов использования: cmake, qt5 и чистый make.
Что характерно — ни один из проектов (кроме последнего) официально не поддерживается на FreeBSD.
Хотя когда это меня останавливало.
Начнем с самого наверное популярного варианта — с cmake.
Проект на cmake: 86Box
Достаточно взрослый и серьезный проект эмулятора разного устаревшего x86 оборудования, который я неоднократно использовал для запуска всякой дичи из далекого прошлого.
Пропустим описание функционала и примеры использования, в этот раз фокус только на коде и работе анализатора.
git clone https://github.com/86Box/86Box
Запускаем первую стадию сборки с записью команд компилятора:
mkdir build & cd build cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=On ..
В каталоге build должен появиться файл compile_commands.json:
Теперь запускаем стадию анализа:
/opt/app/pvs-studio-7.27.75620.346-x86_64/bin/pvs-studio-analyzer analyze
Выполняться будет достаточно долго:
Когда закончит, в каталоге build появится файл PVS-Studio.log с найденными в проекте проблемами и ошибками:
Если вы не являетесь Perl-разработчиком, то придется все же сконвертировать полученный файл в формат доступный для чтения обычными живыми программистами:
/opt/app/pvs-studio-7.27.75620.346-x86_64/bin/plog-converter -a GA:1,2 -t tasklist -o le-putain.txt PVS-Studio.log
Если все прошло хорошо, то будет создан файл «le-putain.txt» с читабельными результатами работы анализатора:
Название файла — отсылка к любимой фразе моего бывшего CTO родом из Франции: некая непереводимая игра слов про продажную любовь и женское коварство.
В моем случае получилось 1624 строки, каждая (кроме первой) с информацией о потенциальной жопе проблеме в проекте:
Чтобы не быть голословным давайте откроем несколько найденных проблем.
Начнем с простого и очевидного:
/opt/src/86Box/src/86box.c 571 warn V755 Copying potentially tainted data from 'argv' to buffer 'log_path'. Buffer overflow is possible.
Видим потенциальное переполение буфера, поскольку длина входящего аргумента неизвестна:
If the copied data size exceeds the buffer size, the buffer overflows. To avoid this, pre-calculate the required amount of memory:
Спросите что такое «переполнение буфера»?
Ну как минимум программа «упадет» — перестанет функционировать, как максимум — можно получить уязвимость, ту самую через которые злые хакеры к вам залезут и все украдут.
Теперь более критичный пример:
/opt/src/86Box/src/chipset/via_apollo.c 548 err V547 Expression is always true.
А не так тут тот простой факт что проверяются границы значения dev->id, по константам:
А чуть выше по коду происходит разовая инициализация dev->id:
Проще говоря это как выражение: «1 меньше 2 и 2 меньше 3» зашитое в код. Очевидно что такое выражение использованное в виде условия всегда будет верным (возвращать true)
Скорее всего сие есть результат долгой истории изменений и поддержки проекта, наверняка когда-то в прошлом этот код не был бесполезным поскольку id принимал несколько разных значений.
Вообщем-то подобных предупреждений в коде еще очень много, но размер статьи не позволяет разобрать существенное их количество.
Поэтому плавно переходим ко второму тестовому проекту.
Проект на QT5: UKNCBTL – эмулятор УКНЦ
Следующий проект это тоже эмулятор, только в этот раз старого советского компьютера:
«Электроника МС 0511» (также известный под названием «УК НЦ») — персональная микроЭВМ, советский учебный компьютер (УК), разработанный для учебных классов. Входит в состав КУВТ «Электроника МС 0202»[⇨].
Разработан в НПО «Научный центр», г. Зеленоград. Главный конструктор — А. Е. Абрамов, зам. ГК А. Н. Полосин[1], ведущие разработчики: Н. Г. Карпинский, А. И. Половянюк, О. Л. Семичастнов, Б. Г. Бекетов, А. Д. Развязнев, И. О. Лозовой, М. И. Дябин, В. Л. Сафонов, И. Н. Селянко, В. Н. Дронов и др.[2]
Он тоже сам по-себе ооочень интересный и достоин отдельной статьи, которую я когда-нибудь обязательно напишу. Но сейчас сфокусируемся только на исходниках и сборке.
Забираем исходный код проекта:
git clone https://github.com/nzeemin/ukncbtl-qt.git
С этим проектом есть один нюанс:
в проекте активно используется модуль «Qt Scripting», который был убран из текущей 6й версии Qt Framework, на который перевели основную среду разработки для QT — QT Creator.
Вообщем собрать из среды разработки наверное как-то можно, но поскольку речь про анализ кода, а не саму разработку, я не стал заморачиваться — вместо функционала QT Creator:
To generate 'compile_commands.json' in the project that uses qmake, you can use IDE QtCreator version 4.8 or higher. Open the desired project and select 'Build->Generate Compilation Database for %project_name%' in the menu bar:
использовал внешнюю утилиту Bear.
Что в некотором смысле даже ближе к реальному применению в боевых проектах.
Утилита Bear есть в FreeBSD в виде готового пакета, поэтому для установки достаточно выполнить:
pkg install bear
cd ukncbtl-qt/emulator /usr/local/bin/qmake-qt5
Следующим шагом запускаем сборку, но через bear для трассировки вызовов компилятора:
bear — gmake
В результате должен сформироваться готовый бинарник в этом же каталоге и все тот же файл compile_commands.json:
Дальше все та же стадия анализа:
Готовый результат (для Perl-программистов) и необходимая конвертация в человеческий:
Получилось 96 строк с описанием проблем, сильно меньше чем в предыдущем, но тут и сам проект куда скромнее.
Разберем пару найденных предупреждений.
/opt/src/ukncbtl-qt/emulator/Emulator.cpp 604 warn V728 An excessive check can be simplified. The '||' operator is surrounded by opposite expressions '!okCursorType' and 'okCursorType'.
Вся проблема вот в этом выражении:
!okCursorType || (okCursorType && bit == cursorAddress)
где получилась бессмысленная проверка на okCursorType, поскольку проверяются оба варианта булевой переменной.
По логике этого выражения надо оставить лишь bit == cursorAddress, убрав проверку на okCursorType.
Опять же, самая частая причина появления такого кода — рефакторинг, когда из-за цепочки правок теряется изначальный смысл.
/opt/src/ukncbtl-qt/emulator/emubase/Memory.cpp 470 warn V1037 Two or more case-branches perform the same actions. Check lines: 470, 484, 487
И ниже еще два условия case, первое:
case 0176644: case 0176645: SetPortWord(address, word); break; ..
case 0176646: case 0176647: SetPortWord(address, word); break; ..
Смысл тут в том что получилось три разных условия (5 на самом деле) с абсолютно одинаковой логикой внутри, что и не понравилось анализатору.
Правильный (с точки зрения анализатора же) вариант вот такой:
case 0176641: case 0176644: case 0176645: case 0176646: case 0176647: SetPortWord(address, word); break; ..
Выглядит конечно монструозно, но что поделать )
И на самое сладкое - третий тестовый проект с чистой make сборкой.
Проект с чистым make: ядро FreeBSD
Разумеется я не мог упустить такого шанса и загнал в анализатор исходники cамого ядра FreeBSD. Заодно устроил стресс-тест анализатору.
Сейчас будет весело, поехали :)
git clone -b releng/14.0 --depth 1 https://git.freebsd.org/src.git /usr/src
Cкачиваем только релизную ветку текущей 14й версии и без истории, поскольку полная версия репозитория занимает ~2.5Гб и скачивается достаточно долго.
В FreeBSD нет разделения на ядро и саму ОС с окружением, вся операционная система разрабатывается в одном репозитории.
Но разумеется компоненты можно собирать отдельно, что мы и сделаем. Запускаем сборку ядра с записью вызовов компилятора:
cd /usr/src bear — make buildkernel
Работать будет долго, сильно дольше чем без трассировки:
В результате получился достаточно объемный compile_commands.json:
/opt/app/pvs-studio-7.27.75620.346-x86_64/bin/pvs-studio-analyzer analyze
Затем конвертируем полученный результат:
/opt/app/pvs-studio-7.27.75620.346-x86_64/bin/plog-converter -a GA:1,2 -t tasklis t -o le-putain.txt PVS-Studio.log
Получилось 4.3Мб всякого интересного:
Давайте разберем пару-тройку-десятку найденных проблем:
/usr/src/sys/amd64/amd64/machdep.c 1061 err V547 Expression 'page_bad == 1' is always false.
Скажу честно, скорее всего мои выводы будут неверны, поскольку код достаточно сложный:
page_bad = FALSE; if (memtest == 0) goto skip_memtest; /* * Print a "." every GB to show we're making * progress. */ page_counter++; if ((page_counter % PAGES_PER_GB) == 0) printf("."); /* * map page into kernel: valid, read/write,non-cacheable */ *pte = pa | PG_V | PG_RW | PG_NC_PWT | PG_NC_PCD; invltlb(); tmp = *(int *)ptr; /* * Test for alternating 1's and 0's */ *(volatile int *)ptr = 0xaaaaaaaa; if (*(volatile int *)ptr != 0xaaaaaaaa) page_bad = TRUE; /* * Test for alternating 0's and 1's */ *(volatile int *)ptr = 0x55555555; if (*(volatile int *)ptr != 0x55555555) page_bad = TRUE; /* * Test for all 1's */ *(volatile int *)ptr = 0xffffffff; if (*(volatile int *)ptr != 0xffffffff) page_bad = TRUE; /* * Test for all 0's */ *(volatile int *)ptr = 0x0; if (*(volatile int *)ptr != 0x0) page_bad = TRUE; /* * Restore original value. */ *(int *)ptr = tmp; skip_memtest: /* * Adjust array of valid/good pages. */ if (page_bad == TRUE) continue;
Как видите, тут вначале ставится значение FALSE, затем проверяются условия, которые могут его поменять на TRUE.
Но скорее всего причина в другом, чуть выше по коду есть вот такое:
/* * The boot memory test is disabled by default, as it takes a * significant amount of time on large-memory systems, and is * unfriendly to virtual machines as it unnecessarily touches all * pages. * * A general name is used as the code may be extended to support * additional tests beyond the current "page present" test. */ memtest = 0; TUNABLE_ULONG_FETCH("hw.memtest.tests", &memtest);
Те тут ставится значение memtest в 0, затем происходит проверка настройки sysctl, если она есть — значение меняется.
А при значении 0 происходит пропуск всего блока условий и переход по метке:
if (memtest == 0) goto skip_memtest;
Где уже есть та самая подозрительная (с точки зрения анализатора) проверка:
skip_memtest: /* * Adjust array of valid/good pages. */ if (page_bad == TRUE) continue;
Скорее всего анализатор не смог разобрать вариант с возможным изменением настройки через sysctl, поэтому и посчитал это место проблемным.
/usr/src/sys/kern/uipc_mqueue.c 670 warn V666 Consider inspecting seventh argument of the function 'uma_zcreate'. It is possible that the value does not correspond with the length of a string which was passed with the first argument.
Ну как я мог пройти мимо предупреждения с номером 666 еще и в ядре FreeBSD?
Вообщем увы, но тут полный фальстарт:
The analyzer suspects that an incorrect argument has been passed into a function. An argument whose numerical value doesn't coincide with the string length found in the previous argument is considered incorrect. The analyzer draws this conclusion examining pairs of arguments consisting of a string literal and an integer constant. Analysis is performed over all the function calls of the same name.
Анализатор принял вот этот макрос:
/* Definitions for align */ #define UMA_ALIGN_PTR (sizeof(void *) - 1) /* Alignment fit for ptr */
за длину строковой константы, которая передается первым аргументом. С кем не бывает.
Что мы говорим богу рефакторинга?
Не сегодня PVS-Studio, не сегодня ;)
Чтобы вы не подумали будто я все манипуляции проводил на виртуалке, выкладываю фото того самого ноутбука на котором все это делал: