﻿# В PVS\-Studio появилась поддержка GNU Arm Embedded Toolchain

Встраиваемые системы давно и прочно вошли в нашу жизнь\. Требования к их стабильности и надежности очень высоки, а исправление ошибок обходится дорого\. Поэтому для embedded разработчиков особенно актуально регулярное использование специализированных инструментов для обеспечения качества исходного кода\. Эта статья расскажет о появлении поддержки GNU Arm Embedded Toolchain в анализаторе PVS\-Studio и дефектах кода, найденных в проекте Mbed OS\.

![0588_GNU_Embedded_ru/image1.png](https://import.viva64.com/docx/blog/0588_GNU_Embedded_ru/image1.png)

## Введение

Анализатор PVS\-Studio уже поддерживает несколько коммерческих компиляторов для встраиваемых систем, например:

* [IAR Embedded Workbench](https://www.iar.com/)
* [Keil Embedded Development Tools for Arm](https://www.keil.com/)
* [TI ARM Code Generation Tools](http://www.ti.com/)

Теперь к поддержке добавлен еще один инструмент разработчика \- GNU Embedded Toolchain\.

[GNU Embedded Toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm) \- коллекция компиляторов от компании Arm, основанная на GNU Compiler Collection\. Первый официальный релиз состоялся в 2012 году, и с тех пор проект развивается вместе с GCC\.

Основное предназначение GNU Embedded Toolchain \- генерация кода, работающего на "голом железе" \(bare metal\), то есть напрямую на процессоре без прослойки в виде операционной системы\. В комплект поставки входят компиляторы для C и C\+\+, ассемблер, набор утилит GNU Binutils и библиотека [Newlib](https://sourceware.org/newlib/)\. Исходный код всех компонентов полностью открыт и распространяется по лицензии GNU GPL\. С официального сайта можно скачать версии под Windows, Linux и macOS\.

## Mbed OS

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

![0588_GNU_Embedded_ru/image2.png](https://import.viva64.com/docx/blog/0588_GNU_Embedded_ru/image2.png)

Хотя основной целью статьи является рассказать о поддержке GNU Embedded Toolchain, много про это написать сложно\. Тем более, что читатели наших статей наверняка ждут описания каких\-то интересных ошибок\. Что же, не будем обманывать их ожидания и запустим анализатор на проекте Mbed OS\. Это операционная система с открытым исходным кодом, которая разрабатывается при участии компании Arm\.

Официальный сайт: [https://www\.mbed\.com/](https://os.mbed.com/)

Исходный код: [https://github\.com/ARMmbed/mbed\-os](https://github.com/ARMmbed/mbed-os)

Выбор на Mbed OS пал не случайно, вот как описывают проект его авторы:

Arm Mbed OS is an open source embedded operating system designed specifically for the "things" in the Internet of Things\. It includes all the features you need to develop a connected product based on an Arm Cortex\-M microcontroller, including security, connectivity, an RTOS and drivers for sensors and I/O devices\.

Это идеальный проект для сборки с помощью GNU Embedded Toolchain, особенно с учетом участия Arm в его разработке\. Сразу оговорюсь, что цели найти и показать как можно больше ошибок в конкретном проекте у меня не было, поэтому результаты проверки рассмотрены кратко\.

## Ошибки

В ходе проверки кода Mbed OS анализатор PVS\-Studio выдал 693 предупреждения, 86 из них \- с приоритетом high\. Я не буду подробно рассматривать их все, тем более что многие из них повторяются или не представляют особого интереса\. Например, анализатор выдал много предупреждений [V547](https://pvs-studio.ru/ru/docs/warnings/v547/) \(Expression is always true/false\), относящихся к однотипным фрагментам кода\. Анализатор можно настроить, чтобы существенно сократить количество ложных и неинтересных срабатываний, но такой задачи при написании статьи не ставилось\. Желающие могут посмотреть пример подобной настройки, описанной в статье "[Характеристики анализатора PVS\-Studio на примере EFL Core Libraries, 10\-15% ложных срабатываний](https://pvs-studio.ru/ru/blog/posts/cpp/0523/)"\.

Для статьи я отобрал несколько интересных ошибок, чтобы продемонстрировать работу анализатора\.

## Утечки памяти

Начнем с распространенного класса ошибок в C и C\+\+ \- утечек памяти\.

Предупреждение анализатора: [V773](https://pvs-studio.ru/ru/docs/warnings/v773/) CWE\-401 The function was exited without releasing the 'read\_buf' pointer\. A memory leak is possible\. cfstore\_test\.c 565

```cpp
int32_t cfstore_test_init_1(void)
{
   ....
  read_buf = (char*) malloc(max_len);
  if(read_buf == NULL) {
    CFSTORE_ERRLOG(....);
    return ret;
  }
  ....
  while(node->key_name != NULL)
  {
    ....
    ret = drv->Create(....);
    if(ret < ARM_DRIVER_OK){
      CFSTORE_ERRLOG(....);
      return ret;              // <=
    }
  ....
  free(read_buf);
  return ret;
}
```

Классическая ситуация при работе с динамической памятью\. Выделенный с помощью _malloc_ буфер используется только внутри функции и освобождается перед выходом\. Проблема в том, что этого не происходит, если функция прекращает работу досрочно\. Обратите внимание на одинаковый код в блоках _if_\. Скорее всего, автор скопировал верхний фрагмент и забыл добавить вызов _free_\.

Еще пример, аналогичный предыдущему\.

Предупреждение анализатора: [V773](https://pvs-studio.ru/ru/docs/warnings/v773/) CWE\-401 The function was exited without releasing the 'interface' pointer\. A memory leak is possible\. nanostackemacinterface\.cpp 204

```cpp
nsapi_error_t Nanostack::add_ethernet_interface(
    EMAC &emac,
    bool default_if,
    Nanostack::EthernetInterface **interface_out,
    const uint8_t *mac_addr)
{
  ....
  Nanostack::EthernetInterface *interface;
  interface = new (nothrow) Nanostack::EthernetInterface(*single_phy);
  if (!interface) {
    return NSAPI_ERROR_NO_MEMORY;
  }

  nsapi_error_t err = interface->initialize();
  if (err) {
    return err;              // <=
  }

  *interface_out = interface;
  return NSAPI_ERROR_OK;
}
```

Указатель на выделенную память возвращается через выходной параметр, но только если вызов _initialize_ прошел успешно, а в случае ошибки происходит утечка, потому что локальная переменная _interface_ выходит из области видимости, и указатель попросту теряется\. Здесь следовало бы либо вызвать _delete_, либо хотя бы отдать хранящийся в переменной _interface_ адрес наружу в любом случае, чтобы об этом мог позаботиться вызывающий код\.

## Memset

Использование функции _memset_ часто приводит к ошибкам, примеры связанных с ней проблем можно посмотреть в статье "[Самая опасная функция в мире С/С\+\+](https://pvs-studio.ru/ru/blog/posts/cpp/0360/)"\.

Рассмотрим такое предупреждение анализатора:

[V575](https://pvs-studio.ru/ru/docs/warnings/v575/) CWE\-628 The 'memset' function processes '0' elements\. Inspect the third argument\. mbed\_error\.c 282

```cpp
mbed_error_status_t mbed_clear_all_errors(void)
{
    ....
    //Clear the error and context capturing buffer
    memset(&last_error_ctx, sizeof(mbed_error_ctx), 0);
    //reset error count to 0
    error_count = 0;
    ....
}
```

Программист намеревался обнулить память, занимаемую структурой _last\_error\_ctx_, но перепутал местами второй и третий аргумент\. В результате _0_ байт заполняется значением _sizeof\(mbed\_error\_ctx\)_\.

Точно такая же ошибка присутствует сотней строк выше:

[V575](https://pvs-studio.ru/ru/docs/warnings/v575/) CWE\-628 The 'memset' function processes '0' elements\. Inspect the third argument\. mbed\_error\.c 123

## Безусловный оператор 'return' в цикле

Предупреждение анализатора: [V612](https://pvs-studio.ru/ru/docs/warnings/v612/) CWE\-670 An unconditional 'return' within a loop\. thread\_network\_data\_storage\.c 2348

```cpp
bool thread_nd_service_anycast_address_mapping_from_network_data (
          thread_network_data_cache_entry_t *networkDataList,
          uint16_t *rlocAddress,
          uint8_t S_id)
{
  ns_list_foreach(thread_network_data_service_cache_entry_t,
                  curService, &networkDataList->service_list) {
    // Go through all services
    if (curService->S_id != S_id) {
      continue;
    }
    ns_list_foreach(thread_network_data_service_server_entry_t,
                    curServiceServer, &curService->server_list) {
      *rlocAddress = curServiceServer->router_id;
      return true;                     // <=
    }
  }
  return false;
}
```

В этом фрагменте _ns\_list\_foreach_ \- это макрос, который раскрывается в оператор _for_\. Внутренний цикл выполняет не больше одной итерации из\-за вызова _return_ сразу после строки, в которой инициализируется выходной параметр функции\. Возможно, этот код работает так, как задумано, но использование внутреннего цикла выглядит в этом контексте довольно странно\. Скорее всего, инициализация _rlocAddress_ и выход из функции должны выполняться по условию, или от внутреннего цикла можно избавиться\.

## Ошибки в условиях

Как я говорил выше, анализатор выдал довольно большое количество неинтересных предупреждений [V547](https://pvs-studio.ru/ru/docs/warnings/v547/), поэтому я изучал их бегло и выписал для статьи только два случая\.

[CWE\-570 Expression 'pcb\-\>state \=\= LISTEN' is always false](https://pvs-studio.ru/ru/docs/warnings/v547/)\. lwip\_tcp\.c 689

```cpp
enum tcp_state {
  CLOSED      = 0,
  LISTEN      = 1,
  ....
};

struct tcp_pcb *
tcp_listen_with_backlog_and_err(struct tcp_pcb *pcb, u8_t backlog, err_t *err)
{
  ....
  LWIP_ERROR("tcp_listen: pcb already connected",
             pcb->state == CLOSED,
             res = ERR_CLSD; goto done);

  /* already listening? */
  if (pcb->state == LISTEN) {               // <=
    lpcb = (struct tcp_pcb_listen*)pcb;
    res = ERR_ALREADY;
    goto done;
  }
  ....
}
```

Анализатор считает, что условие _pcb\-\>state \=\= LISTEN_ всегда ложно, давайте разберемся, почему\. 

Перед оператором _if_ используется макрос _LWIP\_ERROR_, который по логике своей работы напоминает _assert_\. Его объявление выглядит так:

```cpp
#define LWIP_ERROR(message, expression, handler) do { if (!(expression)) { \
  LWIP_PLATFORM_ERROR(message); handler;}} while(0)
```

Если условие ложно, макрос сообщает об ошибке и выполняет код, переданный через параметр _handler_, в этом фрагменте кода \- безусловный переход с использованием _goto_\.

В данном примере проверяется условие 'pcb\-\>state \=\= CLOSED', то есть переход на метку _done_ происходит в случае, когда _pcb\-\>state_ имеет любое другое значение\. Оператор _if_, следующий за вызовом _LWIP\_ERROR_, проверяет _pcb\-\>state_ на равенство _LISTEN_, но это условие никогда не выполняется, потому что _state_ в этой строке может содержать только значение _CLOSED_\.

Рассмотрим еще одно предупреждение, связанное с условиями: [V517](https://pvs-studio.ru/ru/docs/warnings/v517/) CWE\-570 The use of 'if \(A\) \{\.\.\.\} else if \(A\) \{\.\.\.\}' pattern was detected\. There is a probability of logical error presence\. Check lines: 62, 65\. libdhcpv6\_server\.c 62

```cpp
static void libdhcpv6_address_generate(....)
{
  ....
  if (entry->linkType == DHCPV6_DUID_HARDWARE_EUI64_TYPE) // <=
  {
    memcpy(ptr, entry->linkId, 8);
   *ptr ^= 2;
  }
  else if (entry->linkType == DHCPV6_DUID_HARDWARE_EUI64_TYPE)// <=
  {
    *ptr++  = entry->linkId[0] ^ 2;
    *ptr++  = entry->linkId[1];
  ....
  }
}
```

Здесь _if_ и _else if_ проверяют одно и то же условие, в результате чего код в теле _else if_ никогда не выполняется\. Такие ошибки часто возникают при написании кода методом '[copy\-paste](https://pvs-studio.ru/ru/blog/terms/0068/)'\.

## Ownerless expression

Посмотрим напоследок на забавный фрагмент кода\.

Предупреждение анализатора: [V607](https://pvs-studio.ru/ru/docs/warnings/v607/) Ownerless expression '& discover\_response\_tlv'\. thread\_discovery\.c 562

```cpp
static int thread_discovery_response_send(
                        thread_discovery_class_t *class,
                        thread_discovery_response_msg_t *msg_buffers)
{
  ....
  thread_extension_discover_response_tlv_write(
             &discover_response_tlv, class->version,
             linkConfiguration->securityPolicy);
  ....
}
```

А теперь давайте взглянем на объявление макроса _thread\_extension\_discover\_response\_tlv\_write_:

```cpp
#define thread_extension_discover_response_tlv_write \
( data, version, extension_bit)\
(data)
```

Макрос раскрывается в аргумент data, то есть его вызов внутри функции _thread\_discovery\_response\_send_ после препроцессирования превращается в выражение _\(&discover\_response\_tlv\)_\.

![0588_GNU_Embedded_ru/image3.png](https://import.viva64.com/docx/blog/0588_GNU_Embedded_ru/image3.png)

У меня комментариев нет\. Наверное, это не ошибка, но такой код всегда вводит меня в состояние, подобному изображению на картинке :\)\.

## Заключение

Список поддерживаемых в PVS\-Studio компиляторов пополнился\. Если у вас есть проект, предназначенный для сборки с помощью GNU Arm Embedded Toolchain, предлагаю попробовать проверить его с помощью нашего анализатора\. Скачать демонстрационную версию можно [здесь](https://pvs-studio.ru/ru/pvs-studio/download/)\. Обратите также внимание на вариант с [бесплатной лицензией](https://pvs-studio.ru/ru/blog/posts/0457/), который подходит для некоторых небольших проектов\.