C-RUN:运行时代码动态分析工具
在《在日常开发中使用IAR C-STAT进行静态分析,提高代码质量》中,重点探讨了IAR C-STAT静态代码分析工具的设计理念,特点和使用。但正如在《应用编码标准和自动化工具,提高代码质量》提到的,静态代码分析工具基于源代码分析,擅长发现一些未定义行为的缺陷,检查编码标准的符合性,而有些缺陷,比如缓冲区溢出,边界问题,内存泄漏等,往往只有在程序运行时才会被触发。因此,动态分析工具也是非常重要的自动化检测工具。本文将介绍IAR C-RUN动态分析工具的设计理念和特点,以及如何在日常开发过程中,快速上手使用C-RUN进行动态分析来提高代码质量。
C-RUN设计理念和特点
尽早部署,改善Bug曲线
动态分析的基本原理是:在构建过程中,往程序中注入一些检测指令,或者替换某些库函数,对程序的执行情况进行监控,并把有可能出错的地方检测出来。正是因为这种检测是在程序运行的时候执行的,所以称之为动态分析。
与静态分析工具相比,动态分析工具更容易检测到实际运行中可能发生的错误,因为动态分析工具是直接在程序运行时进行检测,而不是像静态分析工具那样,基于源代码的基础上推测程序有可能会执行什么样的代码。这就意味着动态分析工具所发现的错误,几乎不太可能是误报。按照在前文《在日常开发中使用IAR C-STAT进行静态分析,提高代码质量》中关于Bug曲线的讨论,对于这样的错误,我们越早发现,越容易修复,修复的成本也越低。因此尽可能早的部署动态分析工具,那么对于提高代码质量是至关重要的,同时也可以有效的减少项目的时间和成本。
IAR C-RUN 动态分析工具在设计之初,就考虑到这个需求,把它作为一个插件集成到IAR Embedded Workbench中,开发人员在日常开发中可以很方便地部署使用,这样可以在调试的时候尽早地、更多地发现代码运行时的Bug,提高代码质量,降低Bug修复成本,减少项目开发时间。
完全与IAR IDE集成,融入日常开发流程
与静态分析工具类似,大多数第三方的动态分析工具本质上需要与构建工具链(编译/链接等)相同的关于代码和构建环境的信息,因此要适应不同项目的软件环境和需求,往往需要专门的人员针对项目进行安装,配置,管理和和维护,额外增加了不少工作量,这在一定程度上降低了开发人员使用动态分析工具的意愿,从而导致某些运行时的Bug漏到了后期测试,甚至发布阶段。
IAR C-RUN无缝集成到IAR Embedded Workbench中,无需任何的安装和集成工作,只需根据项目的需求,在IAR Embedded Workbench IDE中进行简单的配置(如下图),即可工作,实现开箱即用。这极大的降低了动态分析工具的使用门槛,使得开发人员乐于使用它并从中得益。
实际上,从某种程度可以认为C-RUN是IAR的编译器和调试器技术的一个自然的扩展,典型的使用流程仅仅包括三步:
- 在IDE中设置所需的选项;
- 重新构建项目;
- 在调试器中运行检查问题。如果发现问题,调试器提供相关信息;
如下图所示:
从这个典型用例可以看出,C-RUN完全融入到日常开发流程中,成为自然组成部分,而无需为动态分析来单独增加/修改流程。
灵活的规则设置,支持算术检查/边界检查/堆检查
在文章《应用编码标准和自动化工具,提高代码质量》中探讨过C/C++作为不安全的语言,充满了未定义的行为,这引出了对于编码标准和静态代码分析的需求。但如果进一步来看C/C++这两种广泛应用于嵌入式开发中的高级语言,还可以发现:
- 为了灵活,支持显示/隐式类型转换,带来了整型溢出,移位溢出值改变等算术问题
- 为了高效,引入了指针,带来了访问越界的问题
- 为了资源的最大利用,支持堆动态内存分配,带来了内存泄漏问题
这几类问题,由于是C/C++语言的本身特性,符合语法,在编译或者静态分析中都是合法的,只有在运行中触发某些条件才会被发现。
IAR C-RUN专门针对这几类问题,通过在IDE中简单勾选的方式,即可灵活配置算术检查,边界检查和堆检查。如下图所示:
算术检查
检查整数溢出、错误移位、除零、值更改转换以及switch语句中未处理的情况。通常,算术检查的开销不是特别高,并且可以逐个模块启用或禁用算术检查,不会造成复杂情况。
简单示例:
int i = 5;
void conv(void)
{
ch = i * 100; /* Implicit integer conversion failure */
}
边界检查
检查通过指针进行的访问是否在所指向对象的边界内。边界检查涉及到对代码进行检测以跟踪指针边界,在代码大小和速度方面都有相对较高的成本。还需要一个用于间接访问指针的全局边界表。可以对每个模块或函数禁用跟踪,或者只检查,但是所有代码都不能跟踪指针边界的任何配置通常都需要一些源代码适应。
简单示例:
int arr[10] = {0};
int f(int i)
{
return arr[i];
}
int g(void)
{
return f(20); /* arr[20] is out of bounds */
}
堆检查
检查堆内存使用中的错误。堆检查可以发现对堆内存的错误写访问、双重释放、不匹配的分配和释放,以及显式调用时泄漏的堆块。使用检查堆会增加每个堆块的内存大小,这可能意味着必须增加堆大小,并且堆操作花费的时间可能比使用普通堆要长得多。
简单示例:
void HeapFunc1()
{
char Temp[10];
char *c1 = (char *)malloc(10);
char *c2 = Temp;
c1++;
free(c1); /* not the start of a block */
free(c2); /* non-matched new and free */
}
C-RUN快速上手
选择规则
右键点击对应项目工程文件,选择并点击”Options-> Runtime Checking“,在C-RUN Runtime Checking中,勾选Enable,即可进行检查规则的选择。
对文件和组的配置规则
C-RUN除了支持按照项目进行配置外,对于算术检查,还支持对单个文件或者组进行分析。右键点击某文件或某组,选择”Options-> Runtime Checking,并选中“Override inherited settings”,即可对算术检查部分进行配置,如下图所示。
运行代码
配置好C-RUN后,编译代码并下载运行调试,当程序运行时检测到Error,对应的错误信息会显示在“C-RUN Messages”窗口。选择对应的Message,会跳转到相应的源代码。
分析结果
在C-RUN Messages窗口中,可以看Message的详细信息,包括Error类型,源代码位置,PC指针,发生在哪个CPU核(如果是多核),调用栈信息等,如下图所示:
创建Message规则
如果需要,可以使用C-RUN Messages Rules窗口创建规则过滤特定Message。
- 在C-RUN Messages窗口中,选择要创建规则的Message;
- 右键点击Message,可以对该Message不同范围(此处,文件,或者所有Message类型)进行规则定义;
- 在C-RUN Message Rules窗口中,右键点击规则,可以自定义后续的处理:Stop/Log/Ignore;并可以根据需要,把规则保存到文件,供后续调试时加载使用;
总结
动态分析工具可以帮助检测到实际运行中可能发生的错误,IAR的C-RUN动态分析工具集成在IAR Embedded Workbench中,通过简单的配置、重新编译、运行,即可实现算术检查,边界检查和堆检查。使用C-RUN无需对现有流程做任何修改,可作为日常开发工作流程的自然组成部分,非常适合开发人员在日常的开发和调试过程中使用,尽早发现代码中的Bug,提高代码质量,提高开发效率,降低开发成本。
参考文献:
- https://www.iar.com/products/c-run/
- https://github.com/iarsystems/crun-evaluation-guide
- Part 2. Analyzing your application -> C-RUN runtime error checking, IAR C-SPY Debugging Guide.