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.
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-osLa 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;
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;
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) { ....
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) {
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); if (pcb->state == LISTEN) {
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)
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) .
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 .