PVS-Studio incluye soporte para GNU Arm Embedded Toolchain

GNU Arm Embedded Toolchain + PVS-Studio

Los sistemas integrados han entrado en nuestras vidas por mucho tiempo y con firmeza. Los requisitos para su estabilidad y confiabilidad son muy altos, y la corrección de errores es costosa. Por lo tanto, es especialmente importante que los desarrolladores integrados utilicen regularmente herramientas especializadas para garantizar la calidad del código fuente. Este artículo hablará sobre la apariencia de soporte para GNU Arm Embedded Toolchain en el analizador PVS-Studio y los defectos de código encontrados en el proyecto Mbed OS.

Introduccion


El analizador PVS-Studio ya admite varios compiladores comerciales para sistemas integrados, por ejemplo:


Ahora se ha agregado otra herramienta de desarrollador para admitir: GNU Embedded Toolchain.

GNU Embedded Toolchain es una colección de compiladores de Arm basada en la Colección de compiladores GNU. El primer lanzamiento oficial tuvo lugar en 2012, y desde entonces el proyecto se ha desarrollado junto con el CCG.

El objetivo principal de GNU Embedded Toolchain es generar código que se ejecute en metal desnudo, es decir, directamente en el procesador sin una capa intermedia en forma de sistema operativo. El paquete incluye compiladores para C y C ++, ensamblador, un conjunto de utilidades GNU Binutils y la biblioteca Newlib . El código fuente para todos los componentes está completamente abierto y con licencia bajo la GNU GPL. Desde el sitio oficial puede descargar versiones para Windows, Linux y macOS.

Mbed OS


Para probar el analizador, necesita la mayor cantidad de código fuente posible. Por lo general, no hay problemas con esto, pero cuando se trata de un desarrollo integrado dirigido principalmente a dispositivos incluidos en IoT, encontrar un número suficiente de proyectos grandes puede ser difícil. Afortunadamente, este problema fue resuelto por sistemas operativos especializados, cuyo código fuente en la mayoría de los casos está abierto. Además hablaremos de uno de ellos.

Mbed OS + PVS-Studio


Aunque el objetivo principal de este artículo es hablar sobre el soporte para GNU Embedded Toolchain, es difícil escribir mucho al respecto. Además, los lectores de nuestros artículos probablemente estén esperando una descripción de algunos errores interesantes. Bueno, no engañemos sus expectativas y ejecutemos el analizador en el proyecto Mbed OS. Este es un sistema operativo de código abierto desarrollado con la asistencia de Arm.

Sitio web oficial: https://www.mbed.com/

Código fuente: https://github.com/ARMmbed/mbed-os

La elección en Mbed OS no fue accidental, así es como los autores describen el proyecto:

Arm Mbed OS es un sistema operativo integrado de código abierto diseñado específicamente para las "cosas" en Internet de las cosas. Incluye todas las características que necesita para desarrollar un producto conectado basado en un microcontrolador Arm Cortex-M, que incluye seguridad, conectividad, un RTOS y controladores para sensores y dispositivos de E / S.

Este es un proyecto de compilación ideal que utiliza GNU Embedded Toolchain, especialmente dada la participación de Arm en su desarrollo. Inmediatamente haré una reserva que no tenía el objetivo de encontrar y mostrar tantos errores como sea posible en un proyecto específico, por lo que los resultados de la revisión se revisan brevemente.

Errores


Durante la verificación del código del sistema operativo Mbed, el analizador PVS-Studio generó 693 advertencias, 86 de ellas con alta prioridad. No los consideraré en detalle, especialmente porque muchos de ellos se repiten o no son de particular interés. Por ejemplo, el analizador generó muchas advertencias V547 (La expresión siempre es verdadera / falsa) relacionadas con los mismos fragmentos de código. El analizador se puede configurar para reducir significativamente el número de respuestas falsas y poco interesantes, pero esta tarea no se planteó al escribir un artículo. Aquellos que lo deseen pueden ver un ejemplo de dicha configuración descrita en el artículo " Especificaciones del analizador PVS-Studio utilizando el ejemplo EFL Core Libraries, 10-15% de falsos positivos ".

Para el artículo, seleccioné algunos errores interesantes para demostrar el funcionamiento del analizador.

Fugas de memoria


Comencemos con la clase común de errores en C y C ++: pérdidas de memoria.

Advertencia del analizador: V773 CWE-401 La función se cerró sin liberar el puntero 'read_buf'. Una pérdida de memoria es posible. cfstore_test.c 565

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; } 

La situación clásica cuando se trabaja con memoria dinámica. El búfer asignado por malloc- se usa solo dentro de la función y se libera antes de salir. El problema es que esto no sucede si la función deja de funcionar antes. Tenga en cuenta el mismo código en los bloques if . Lo más probable es que el autor copie el fragmento superior y olvidó agregar una llamada gratuita .

Otro ejemplo similar al anterior.

Advertencia del analizador: V773 CWE-401 La función se cerró sin soltar el puntero de 'interfaz'. Una pérdida de memoria es posible. nanostackemacinterface.cpp 204

 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; } 

El puntero a la memoria asignada se devuelve a través del parámetro de salida, pero solo si la llamada de inicialización se realizó correctamente y, en caso de error, se produce una fuga porque la variable de interfaz local queda fuera de alcance y el puntero simplemente se pierde. Aquí, en cualquier caso, se debe llamar a eliminar o al menos dar la dirección almacenada en la variable de interfaz al exterior, de modo que el código de llamada pueda encargarse de esto.

Memset


El uso de la función memset a menudo conduce a errores; se pueden encontrar ejemplos de los problemas asociados con él en el artículo " La función más peligrosa en el mundo C / C ++ ".

Considere la siguiente advertencia del analizador:

V575 CWE-628 La función 'memset' procesa elementos '0'. Inspeccione el tercer argumento. mbed_error.c 282

 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; .... } 

El programador pretendía restablecer la memoria ocupada por la estructura last_error_ctx , pero mezcló el segundo y el tercer argumento. Como resultado, 0 bytes se llenan con el valor sizeof (mbed_error_ctx) .

Exactamente el mismo error está presente cien líneas arriba:

V575 CWE-628 La función 'memset' procesa elementos '0'. Inspeccione el tercer argumento. mbed_error.c 123

Declaración incondicional de 'retorno' en un bucle


Advertencia del analizador: V612 CWE-670 Un 'retorno' incondicional dentro de un bucle. thread_network_data_storage.c 2348

 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; } 

En este fragmento, ns_list_foreach es la macro que se expande en la instrucción for . El bucle interno no realiza más de una iteración debido a la llamada a regresar inmediatamente después de la línea en la que se inicializa el parámetro de salida de la función. Quizás este código funcione como se esperaba, pero el uso del bucle interno parece bastante extraño en este contexto. Lo más probable es que la inicialización y la salida de rlocAddress de la función se deben realizar por condición, o puede deshacerse del bucle interno.

Errores en las condiciones.


Como dije anteriormente, el analizador generó una gran cantidad de advertencias V547 poco interesantes, así que las estudié con fluidez y escribí solo dos casos para el artículo.

V547 CWE-570 La expresión 'pcb-> state == LISTEN' siempre es falsa. lwip_tcp.c 689

 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; } .... } 

El analizador considera que la condición pcb-> state == LISTEN siempre es falsa, veamos por qué.

Antes de la instrucción if , se usa la macro LWIP_ERROR , que, de acuerdo con la lógica de su operación, se asemeja a la afirmación . Su anuncio se ve así:

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

Si la condición es falsa, la macro informa un error y ejecuta el código pasado a través del parámetro del controlador , en este fragmento de código hay un salto incondicional usando goto .

En este ejemplo, la condición 'pcb-> state == CLOSED' está marcada, es decir, la transición a la etiqueta realizada ocurre cuando pcb-> state tiene cualquier otro valor. La instrucción if que sigue a la llamada a LWIP_ERROR verifica el estado pcb-> para la igualdad de ESCUCHAR , pero esta condición nunca se cumple, porque el estado en esta línea solo puede contener el valor CERRADO .

Considere una advertencia más relacionada con las condiciones: V517 CWE-570 El uso de 'if (A) {...} else if (A) {...}' se detectó el patrón. Hay una probabilidad de presencia de error lógico. Verifique las líneas: 62, 65. libdhcpv6_server.c 62

 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]; .... } } 

Aquí, if y else if verifican la misma condición, como resultado de lo cual el código en el else if body nunca se ejecuta. Tales errores a menudo ocurren cuando se escribe código usando el método de copiar y pegar .

Expresión sin dueño


Echemos un vistazo a un código divertido.

Advertencia del analizador: V607 Expresión sin propietario '& discover_response_tlv'. thread_discovery.c 562

 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); .... } 

Ahora echemos un vistazo a la macro declaración thread_extension_discover_response_tlv_write :

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

La macro se expande en el argumento de datos, es decir, su llamada dentro de la función thread_discovery_response_send después del preprocesamiento se convierte en una expresión (& discover_response_tlv) .

Espera que


No tengo comentarios Esto probablemente no sea un error, pero ese código siempre me pone en un estado similar a la imagen de la imagen :).

Conclusión


La lista de compiladores compatibles con PVS-Studio se ha ampliado. Si tiene un proyecto destinado a ensamblar utilizando GNU Arm Embedded Toolchain, sugiero que intente probarlo con nuestro analizador. Descargue la demo aquí . También preste atención a la opción de licencia gratuita , que es adecuada para algunos proyectos pequeños.



Si desea compartir este artículo con una audiencia de habla inglesa, utilice el enlace a la traducción: Yuri Minaev. PVS-Studio ahora es compatible con GNU Arm Embedded Toolchain .

Source: https://habr.com/ru/post/es427447/


All Articles