Pull to refresh

Как Чинить Программные Ошибки?

Level of difficultyEasy
Reading time6 min
Views4.3K

#ретроспектива

Программисты микроконтроллеров регулярно занимаются починкой багов. Более того 60%-80% работы программиста - это как правило починка багов. Часто программистов нанимают как раз только для того чтобы чинить чужие баги.

Терминология

Баг (bug) - ошибка в компьютерной программе. Несоответствие между реальным поведением программы и ожидаемым поведением программы.

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

В чём проблема?

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

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

Причины аппаратных ошибок

1

схемотехник забыл подать питание на микроконтроллер

2

в схему заложили микросхемы с инверсными логическими уровнями

3

монтажники припаяли квадратную микросхему не той ориентацией

4

перепутали ориентацию аккумуляторного разъёма на 180 градусов

5

поставили слишком маленькую емкость в цепи питания

6

в закупках оказалиcь десятки бракованных микросхем переходников UART-RS485

7

на плате так много микросхем, что при инициализации проседает напряжение и MCU перезагружается.

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

3--Ошибки которые редко воспроизводятся: Гейзенбаги.

При хорошем DevOps можно отловить много багов на стадии статического анализатора, или отработки скриптов сборки (Make, CMake). Потом можно отловить баги на фазе препроцессора (утилита cpp.exe), затем отловить баги на фазе компилятора (gcc.exe). Ошибки также показывает компоновщик (ld.exe). Часть ошибок могут отловить модульные тесты уже в run-time(е). Последний рубеж обороны от ошибок это интеграционные тесты.

Однако тем не менее ошибки просачиваются в релизные артефакты (*.elf, *.bin, *.hex ) и обнаруживаются только на фазе исполнения программы (run-time(е)).

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

#

Пример программной ошибки

1

Прошивка свалилась в Hard Fault ISR

2

Прошивка постоянно непрерывно перезагружается

3

Не проходит инициализация какого-то программного компонента. Например FatFs.

4

По BLE приходит хрипящий звук

5

На выходе расшифровщика не понятный текст, а кракозябры

6

LoRa радио трансивер не отвечает на команды

7

Подали питание и Heart Beat LED не мигает

8

Не отвечает UART-CLI

9

Печатаешь текст и на экране ничего не происходит. Через 20сек разом вываливается большой текст.

Вот некоторые типичные причины программных багов из реальной повседневной разработки программного обеспечения:

Причина программной ошибок

1

Произошло переполнение типа данных

2

Неверно подобранные приоритеты потоков в RTOS

3

Переполнение стековой памяти

4

Попытка прописать read only память (.rodata)

5

Произошло деление на ноль

6

Произошло обращение к нулевому указателю

7

Сработало прерывание для которого нет обработчика

8

Произошел выход за границы массива. Код перетер чужую RAM память за пределами массива. Глобальные переменные больше не валидные. Последствия непредсказуемы...

9

Неверный конфиг для программного компонента

10

Кто-то закомментировал кусок кода, который должен был исполняться

11

Программа застряла в нежелательном бесконечном цикле while(1)

12

Функция вычисления формулы вернула NaN вместо double

13

Неверный порядок инициализации

14

Использование неинициализированной переменной в которой случайное значение

15

Сработало прерывание SysTick таймера до инициализации планировщика RTOS

16

Возникло состояние гонки (race condition). Например, два потока пытаются одновременно прописать в on-chip NOR Flash.

Есть ли универсальная методичка на то, как чинить баги?

Да, я считаю, что можно в какой-то степени обобщить алгоритм поиска программной ошибки.

Фаза № 1. Подробно опишите программную ошибку

Подробно напишите, что именно не так. Не жалейте слов. В чем именно несоответствие. Что должно быть, а что происходит на самом деле. Добавьте скриншот. Добавьте кусок лога программы в этом месте. Заведите про эту ошибку текстовый файл или заметку в трекере задач. Это подобно тому как полиция делает фотографии на месте преступления. Составьте дефектную ведомость.

Фаза № 2. Научиться стабильно воспроизводить баг *

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

Чтобы отлаживать код, его надо исполнять. Если в коде баг и нет возможности исполнить код, то баг не исправить.

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

Чтобы лёд тронулся надо сперва научиться регулярно воспроизводить баг, желательно автоматически из какого-нибудь скрипта. Если Вам сказали, что обнаружен баг, то первый вопрос, который вы должны задать себе это: "Настолько стабильно баг воспроизводится?".

Фаза №3 Выдвинете предположения

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

Фаза №4. Делайте эксперименты

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

Фаза №5. Активировать более глубокий уровень логирования

Включите уровни логирования того компонента в котором возникла ошибка до уровня Debug или Paranoid. В хорошей прошивке, должен быть UART-CLI. CLI позволяет включать/отключать уровни логирования для каждого программного компонента. Вероятно баг вызван тем, что не выполняется какой-то важный код, который должен выполняться.

Фаза №6. Прогоните модульные тесты.

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

Фаза №7. Пройдите код пошаговым GDB отладчиком

Это крайняя мера. Проходите код пошаговым отладчиком. Каждую строчку. Анализируйте по какой ветви происходит исполнение кода. Правильные ли значения лежат в локальных и глобальных переменных? Вероятно не выполняется нужная ветвь кода. Пошаговая отладка это весьма утомительное мероприятие. Особенно когда приходится делать её из CLI.

Фаза №8. Прогоните код через статический анализатор.

Это скорее профилактическая мера. Есть такие утилиты, которые анализируют код без его исполнения. Это например CppCheck.exe. Они часто находят нелепые опечатки, которые не замечает компилятор. Минимум раз в месяц надо прогонять код репозитория через статический анализатор. Сами вы всё не уследите.

Фаза №9. Задайте вопрос на профессиональных форумах и сообществах.

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

Итоги

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

Чтобы найти причину программной ошибки надо научиться стабильно воспроизводить ошибку на фазе исполнения.

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

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

Если есть, что добавить, то пишите в комментариях.

Links

16 Способов Отладки и Диагностики FirmWare

Пошаговая GDB отладка ARM процессора из консоли в Win10

Модульное Тестирование в Embedded

Почему Нам Нужен UART-Shell?

11 Aтрибутов Хорошего Firmware

Модульное тестирование для микроконтроллеров https://www.youtube.com/watch?v=-hM38-JDt8c&t=1653s

Вопросы

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

Tags:
Hubs:
Total votes 11: ↑5 and ↓6+2
Comments40

Articles