首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

查找嵌入式C语言程序/软件中的缺陷的多种技术(1)

查找嵌入式C语言程序/软件中的缺陷的多种技术(1)

基于模式的静态代码分析、运行时内存监测、单元测试以及数据流分析等软件验证技术是查找嵌入式C语言程序/软件缺陷行之有效的方法。上述技术中的每一种都能查找出某一类特定的错误。即便如此,如果用户仅采用上述技术中的一种或者几种来进行验证,这样的验证方法很有可能会漏过对程序中的一些缺陷的检查。解决此类问题的一种安全和有效的策略就是同时使用上述软件验证中的所有互补技术。这样就能建立起一个牢固的框架来帮助用户检查出可能会避开某种特定技术的缺陷。与此同时,用户也自然地建立起一个能检测出关键并且难以查找的功能性错误的环境。
本文将详尽阐述基于模式的静态代码分析、运行时内存错误检测、单元测试以及数据流分析等自动化技术共同使用时是如何查找出嵌入式C语言程序/软件中的缺陷的。本文中将以Parasoft C++test为例来演示上述各项技术。C++teST是一个经广泛的最佳实践证明能提升软件开发团队开发效率以及软件质量的自动化集成解决方案。
当读者在阅读本文以及任何时候思考查找到的缺陷时,关注文中的截图是很重要的。自动化检测例如内存崩溃和死锁的缺陷,毫无疑问对任何开发团队都是一项必不可少的任务。尽管如此,最致命的缺陷却是功能性错误,这往往是难以自动发现的。在本文的结论部分我们将简要地讨论一下查找这些缺陷的技术。
情景简介
为了给出一个具体的示例,我们将就一个我们最近遇到的案例来介绍以及演示我们所推荐的缺陷查找策略:一个运行在ARM 板上的简单传感器应用程序。
假设我们已经创建了该应用系统,但是当我们将程序上载到系统目标板上并试图运行该程序时,我们没有在LCD屏上看到所预期的输出。
我们尚不明确系统不能正常工作的原因,因此我们设法对系统进行调试,但是在目标板上进行调试是一件耗时而且烦人的事。因为我们不得不手动分析调试器的结果并试图人工判断出问题的真正原因。或者我们使用一些被证实能自动定位出错误的工具或技术来帮助我们减轻负担。
从这一点而言,我们要么期待使用调试器来调试程序能够带来好运,要么我们尝试使用一种自动化的测试策略来查找代码中所存在的错误。如果自动化技术仍然没有帮助我们查找到错误,那么我们不得不回到使用调试器作为最后的办法。
基于模式的静态代码分析
这里,我们假设仅在绝对必要的情况下才使用调试器进行调试,因此我们从运行基于模式的静态代码分析开始。它将查找到如下图所示的问题:
这是违反了 MISRA 的一个规则,此违规说明该处的赋值运算符存在一些可疑情况。的确,编程者此处的本意是使用比较运算符而不是赋值运算符。因此我们将此处检测到的冲突修改掉,并重新运行程序。
我们发现有了一些改善:一些输出被显示在了LCD屏上了。但是,由于一次访问违规,程序崩溃掉了。因此我们需要再次地做出选择。我们是应该使用调试器还是继续使用自动化的错误检测技术。由于经验告诉我们自动化错误检测技术能非常高效地检查出我们当前程序所遇到的内存崩溃这类问题,因此我们决定使用运行时内存监测来查找问题。
整个程序的运行时内存监测
为了进行运行时内存监测,我们使用 C++test 来插装应用程序。这样的插装是轻量级的,所以经过插装后的程序适合在目标板上运行。当我们把程序上载到目标板上并运行经过插装的程序后,我们将结果下载到PC上,如下的错误将被报告出来:
该结果指出在第48行代码处产生了一次读取数组越界的错误。显然,msgIndex变量的值肯定超过了数组的范围。如果我们随着堆栈追踪上一级的原因,我们将发现此处的打印信息所指示的值的确超出了数组的范围(因为在调用printMessage()函数前我们给出了一个错误的条件)。我们可以删除掉这个不必要的条件(value <= 20)以修改这个错误。
void handleSensorValue(int value)
{
initialize();
int index = -1;
if (value >= 0 && value <= 10) {
index = VALUE_LOW;
} else if ((value > 10) && (value <= 20)) {
index = VALUE_HIGH;
}
printMessage(index, value);
}
然后我们重新运行程序,将不会再报告任何内存错误。当我们把程序上载到目标板上时,它似乎如我们预期那么在工作了。尽管如此,我们仍然有一些担心。
我们仅查找到我们所执行的代码路径中的一个内存写溢出实例,我们凭什么能够断定我们尚未执行到的代码就不会有内存写溢出错误了呢?如果我们检查覆盖率分析,我们就会发现reportSensorFailure()这个函数从未被执行到。我们有必要对这个函数进行测试,但是具体如何进行呢?建立一个调用该函数的单元测试用例就是一个不错的办法。
在单元测试中使用运行时内存监测:我们使用C++test的测试用例向导来创建一个测试用例的框架,并向其中添加一些测试代码。然后运行该测试用例——以检查上面提到的未经测试的函数,同时打开运行时内存监测功能。使用C++teST,全过程大约只需要数秒钟。
结果标明该函数已经被覆盖到了,但同时也查找到了新的错误:
我们的测试用例查找到了更多的内存相关错误。很显然,当失败处理函数被调用时,我们的内存初始化存在问题(空指针)。通过更进一步的分析,我们发现在reportSensorValue()函数中存在函数调用顺序错误。
finalize()函数先于printMessage()函数被调用,但是finalize()函数中释放了printMessage()函数需要使用的内存。
void finalize()
{
if (messages) {
free(messages[0]);
free(messages[1]);
free(messages[2]);
}
free(messages);
}
将函数调用顺序进行修改后,我们重新运行程序。
这样我们就解决了上面报告中的第一个错误。现在我们再来分析报告中的第二个错误:即打印信息中的AccessViolatiONException。产生这个错误的原因是相应的消息列表未经初始化。为了解决该问题,我们在打印该信息前调用一次initialize()函数来对其进行初始化。经修改后的函数如下所示:
返回列表