嵌入式系统已经长期牢固地进入了我们的生活。 对它们的稳定性和可靠性的要求非常高,并且纠错昂贵。 因此,对于嵌入式开发人员来说,定期使用专用工具来确保源代码的质量尤为重要。 本文将讨论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
要测试分析仪,您需要尽可能多的源代码。 通常,这没有问题,但是当我们处理主要针对物联网中包含的设备的嵌入式开发时,很难找到足够数量的大型项目。 幸运的是,该问题已通过专用操作系统解决,该操作系统的源代码在大多数情况下是开放的。 此外,我们将讨论其中之一。
尽管本文的主要目的是讨论对GNU嵌入式工具链的支持,但很难对此进行大量介绍。 此外,我们文章的读者可能正在等待描述一些有趣的错误。 好吧,我们不要欺骗他们的期望,而是在Mbed OS项目上运行分析仪。 这是在Arm的帮助下开发的开源操作系统。
官方网站:
https :
//www.mbed.com/源代码:
https :
//github.com/ARMmbed/mbed-osMbed 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;
使用动态内存时的经典情况。
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;
指向分配的内存的指针通过输出参数返回,但是仅当
初始化调用成功时才发生,并且在发生错误的情况下,会发生泄漏,因为本地
接口变量超出范围并且指针将丢失。 在这里,无论如何都应该调用
delete ,或者至少将
接口变量中存储的地址提供给外部,以便调用代码可以解决这一问题。
记忆集
使用
memset函数通常会导致错误;与它相关的问题的示例可以在文章“
C / C ++世界中最危险的函数 ”中找到。
考虑以下分析器警告:
V575 CWE-628“
内存集 ”功能处理“ 0”元素。 检查第三个论点。 mbed_error.c 282
mbed_error_status_t mbed_clear_all_errors(void) { ....
程序员打算重置由
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) {
在此代码段中,
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); if (pcb->state == LISTEN) {
分析器认为条件
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)
在此,
if和
else 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嵌入式工具链 。