PVS-Studio包括对GNU Arm嵌入式工具链的支持

GNU Arm嵌入式工具链+ PVS-Studio

嵌入式系统已经长期牢固地进入了我们的生活。 对它们的稳定性和可靠性的要求非常高,并且纠错昂贵。 因此,对于嵌入式开发人员来说,定期使用专用工具来确保源代码的质量尤为重要。 本文将讨论PVS-Studio分析仪对GNU Arm嵌入式工具链的支持以及Mbed OS项目中发现的代码缺陷。

引言


PVS-Studio分析仪已经支持多种用于嵌入式系统的商业编译器,例如:


现在添加了另一个开发人员工具来支持-GNU嵌入式工具链。

GNU嵌入式工具链是Arm基于GNU编译器集合的编译器集合。 首次正式发布于2012年,此后该项目一直与GCC一起开发。

GNU嵌入式工具链的主要目的是生成在裸机上运行的代码,即直接在处理器上运行,而没有操作系统形式的中间层。 该软件包包括C和C ++的编译器,汇编器,一组GNU Binutils实用程序以及Newlib库。 所有组件的源代码都是完全开放的,并根据GNU GPL获得许可。 您可以从官方站点下载适用于Windows,Linux和macOS的版本。

Mbed OS


要测试分析仪,您需要尽可能多的源代码。 通常,这没有问题,但是当我们处理主要针对物联网中包含的设备的嵌入式开发时,很难找到足够数量的大型项目。 幸运的是,该问题已通过专用操作系统解决,该操作系统的源代码在大多数情况下是开放的。 此外,我们将讨论其中之一。

Mbed OS + PVS-Studio


尽管本文的主要目的是讨论对GNU嵌入式工具链的支持,但很难对此进行大量介绍。 此外,我们文章的读者可能正在等待描述一些有趣的错误。 好吧,我们不要欺骗他们的期望,而是在Mbed OS项目上运行分析仪。 这是在Arm的帮助下开发的开源操作系统。

官方网站: https//www.mbed.com/

源代码: https : //github.com/ARMmbed/mbed-os

Mbed OS上的选择并非偶然,这是作者描述该项目的方式:

Arm Mbed OS是专门为物联网中的“事物”设计的开源嵌入式操作系统。 它包含开发基于Arm Cortex-M微控制器的连接产品所需的所有功能,包括安全性,连接性,RTOS以及用于传感器和I / O设备的驱动程序。

这是使用GNU嵌入式工具链的理想构建项目,特别是考虑到Arm参与了其开发。 我会立即提出保留意见,因为我的目标不是发现并显示特定项目中尽可能多的错误,因此将对审阅结果进行简短地审阅。

失误


在验证Mbed OS代码期间,PVS-Studio分析仪生成693条警告,其中86条具有高优先级。 我不会详细讨论它们,尤其是因为其中许多重复或没有特别的意义。 例如,分析器生成了许多与相同代码段相关的警告V547 (表达始终为true / false)。 可以将分析器配置为显着减少错误和不感兴趣的响应的数量,但是在撰写文章时未设置此任务。 那些希望的人可以查看文章“ 使用EFL核心库示例的PVS-Studio分析仪规格,误报的10-15% ”中介绍的这种配置示例

对于本文,我选择了一些有趣的错误来演示分析仪的操作。

内存泄漏


让我们从C和C ++中常见的错误类别开始-内存泄漏。

分析器警告: V773 CWE-401在不释放'read_buf'指针的情况下退出了该函数。 可能发生内存泄漏。 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; } 

使用动态内存时的经典情况。 malloc分配的缓冲区仅在函数内部使用,并在退出前释放。 问题是,如果该功能提前停止工作,则不会发生这种情况。 注意if块中的相同代码。 作者很可能复制了最上面的片段,却忘记添加免费通话。

另一个示例与上一个类似。

分析器警告: V773 CWE-401在不释放“接口”指针的情况下退出了该功能。 可能发生内存泄漏。 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; } 

指向分配的内存的指针通过输出参数返回,但是仅当初始化调用成功时才发生,并且在发生错误的情况下,会发生泄漏,因为本地接口变量超出范围并且指针将丢失。 在这里,无论如何都应该调用delete ,或者至少将接口变量中存储的地址提供给外部,以便调用代码可以解决这一问题。

记忆集


使用memset函数通常会导致错误;与它相关的问题的示例可以在文章“ C / C ++世界中最危险的函数 ”中找到。

考虑以下分析器警告:

V575 CWE-628“ 内存集 ”功能处理“ 0”元素。 检查第三个论点。 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; .... } 

程序员打算重置由last_error_ctx结构占用的内存,但又混淆了第二个和第三个参数。 结果, 0个字节被sizeof(mbed_error_ctx)值填充。

上面的一百行出现了完全相同的错误:

V575 CWE-628“ 内存集 ”功能处理“ 0”元素。 检查第三个论点。 mbed_error.c 123

循环中的无条件“ return”语句


分析仪警告: V612 CWE-670循环内无条件的“返回”。 线程_网络_数据_存储.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; } 

在此代码段中, ns_list_foreach是扩展为for语句的宏。 由于调用将在函数的输出参数初始化所在的行之后立即返回,因此内部循环最多执行一次迭代。 也许这段代码可以按预期工作,但是在这种情况下使用内部循环看起来很奇怪。 最有可能的是,必须按条件执行rlocAddress的初始化和从函数中退出,否则您可以摆脱内部循环。

条件错误


就像我在上面说的那样,分析器生成了相当多的无趣的V547警告,因此我流利地研究了它们,并为本文只写了两种情况。

V547 CWE-570表达式'pcb-> state == LISTEN'始终为false。 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; } .... } 

分析器认为条件pcb->状态== LISTEN始终为假,让我们看看原因。

if语句之前,使用LWIP_ERROR宏,根据其操作的逻辑,该宏类似于assert 。 他的广告如下所示:

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

如果条件为假,则宏将报告错误并执行通过处理程序参数传递的代码,在此代码片段中,将使用goto进行无条件跳转。

在此示例中,条件'pcb-> state == CLOSED'被选中,也就是说,当pcb-> state具有任何其他值时,将转换为完成标签。 调用LWIP_ERROR之后的if语句将检查pcb->状态是否为LISTEN ,但是永远不会满足此条件,因为此行上的状态只能包含CLOSED值。

考虑与条件有关的另一个警告: V517 CWE-570检测到“ if(A){...} else if(A){...}”模式的使用。 存在逻辑错误的可能性。 检查行: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]; .... } } 

在此, ifelse if检查相同的条件,因此else if主体中的代码将永远不会执行。 使用复制粘贴方法编写代码时,经常会发生此类错误。

无主的表情


让我们看一段有趣的代码。

分析器警告: V607无主表达式'&discover_response_tlv'。 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); .... } 

现在,让我们看一下宏声明thread_extension_discover_rescoverse_tlv_write

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

宏将扩展为data参数,即在预处理变成表达式(&discover_response_tlv)之后,在thread_discovery_response_send函数内部对其进行调用。

等什么


我没有评论 这可能不是一个错误,但是这样的代码总是使我处于类似于图片中的状态:)。

结论


PVS-Studio支持的编译器列表已得到扩展。 如果您有一个打算使用GNU Arm嵌入式工具链进行组装的项目,建议您使用我们的分析仪对其进行测试。 在此处下载演示。 还请注意适用于某些小型项目的免费许可证选项。



如果您想与讲英语的读者分享这篇文章,请使用翻译链接:Yuri Minaev。 PVS-Studio现在支持GNU Arm嵌入式工具链

Source: https://habr.com/ru/post/zh-CN427447/


All Articles