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

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

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

void reportSensorFailure()
{
initialize();
printMessage(ERROR, 0);
finalize();
}
当我们再次运行该测试用例时,仅有一个任务被报告出来:未经验证的单元测试用例(an unvalidated unit test case),这其实并不算一条错误。我们只需对输出进行一下验证,以将该测试用例转换为回归测试。通过创建合适的断言,C++test会自动为我们完成这些步骤。
接下来我们再次运行整个程序。覆盖率分析告诉我们几乎整个程序都已经被覆盖到了,并且没有发现任何内存错误。
这样就结束了吗?其实不然。虽然我们运行了整个程序并为未覆盖到的函数创建了单元测试用例,但还是有一些路径是没有被覆盖到的。我们仍然可以继续创建单元测试用例,但是若指望通过这样的方法来覆盖程序中的所有路径将耗费相当长的时间。或者我们使用另外的方法,使用数据流分析来对这些路径进行模拟。
数据流分析
我们使用C++test的BugDetective来进行数据流分析,BugDetective能模拟系统中的不同路径并检查这些路径中是否存在潜在的问题。进行数据流分析后,我们得到如下结果:
仔细分析报告的结果,我们发现程序中存在一条未被覆盖到的潜在路径可能会造成在finalize()函数中出现两次free的操作。在程序中,reportSensorValue()函数调用了finalize()函数,然后finalize()函数调用了free()。同时,finalize()函数还会被mainLoop()函数调用。我们可以修改finalize()函数以使其更加智能化,从而修复这个问题,修改后的代码如下:
void finalize()
{
if (messages) {
free(messages[0]);
free(messages[1]);
free(messages[2]);
free(messages);
messages = 0;
}
}
现在我们再次运行数据流分析,得到的结果将只有两个问题:
这里我们可能使用了-1作为索引来访问了数组。这是由于整型变量index被设置的初始值为-1,并且存在一条可能通过if语句的路径在未将该整型变量正确的进行初始化之前便调用了printMessage()函数。运行时分析未检查到这样的一条路径,并且该路径很有可能在真实世界中永远不可能被执行到。这就是静态数据流分析相对于运真实运行时内存监测最主要的不足:数据流分析能检查出潜在的路径,这些路径可能包含在程序实际执行过程中不会执行到或不存在的路径。尽管如此,为了做到有备无患,我们删除了上述的不必要的条件(value>=0)以修改这个潜在的错误。
void handleSensorValue(int value)
{
initialize();
int index = -1;
if (value <= 10) {
index = VALUE_LOW;
} else {
index = VALUE_HIGH;
}
printMessage(index, value);
}
相同地,我们也对最后一个报告的错误进行相应的处理。现在我们再次运行数据流分析,将不会再有错误被报告出来。
为了确保程序运行一切正常,我们重新运行整个分析过程。首先,我们开启运行时内存监测并运行应用程序,一切表现正常。然后我们开启内存监测并运行单元测试,一个任务被报告出来:
我们的单元测试检测到reportSensorFailure()函数的行为已经发生了改变。这是由于我们已经对finalize()函数进行了修改—— 为了纠正之前报告的一个问题所做的修改。此处报告的任务是为了让我们注意此修改,并提示我们应该对测试用例进行相应的审查,并且确定是否应该对代码或者测试用例进行相应的修改,以表示这种新的行为实际上是我们所预期的行为。在检查完代码之后,我们发现后者(修改)是正确的并且应该更新断言的正确条件。
/* CPPtest_TEST_CASE_BEGIN test_reportSensorFailure */
/* CPPTEST_TEST_CASE_CONTEXT void reportSensorFailure(void) */
void sensor_tests_test_reportSensorFailure()
{
/* Pre-condition initialization */
/* Initializing global variable messages */
{
messages = 0 ;
}
{
/* Tested function call */
reportSensorFailure();
/* Post-condition check */
CPPTEST_ASSERT(0 == ( messages ));
}
}
/* CPPTEST_TEST_CASE_END test_reportSensorFailure */
作为最终的确认,我们需要独立地运行整个程序——在IDE中关闭掉运行时内存监测来对程序进行构建。结果显示一切如我们所预期一样运行。
总结
作为全文的结尾,让我们一起对上述各个步骤进行一个鸟瞰式的总结。
首先,我们开发的程序并未如我么所预期那样运行,我们不得不在两种解决方法中选择一种来查找程序中的错误:通过运行调试器或者使用自动错误检测技术。
如果我们使用调试器运行代码来查找错误,我们将会看到一些很奇怪的现象:程序中的一些变量总是被赋予了相同的值。基于这种现象我们不得不通过排除法来查找问题的原因——即在应该使用比较运算符的地方我们错误地使用了赋值运算符。而静态代码分析则能为我们自动地检查出该逻辑错误。运行时内存分析是不可能检查出这种错误的,因为这种错误与内存无关。数据流分析也很有可能找不到这类错误因为数据流分析仅仅是通过这些路径而不会验证这些条件的正确性。
当我们解决了这个问题后,程序可以运行了,但是仍然还有内存相关的问题。内存相关的问题是很难被调试器发现的;当用户使用调试器调试程序时,用户并不知道内存的实际大小。但是自动错误检查工具能够做到这点。因此,为了查找这些内存问题,我们将整个程序进行插装,并使用运行时内存分析工具来运行程序。这样我们就能知道到底是那一片内存发生了写溢出错误。
尽管如此,在审查覆盖率分析结果的时候,我们注意到在目标板上测试的时候,并不是全部代码都被覆盖到了。通过自动化的工具得到这样的覆盖率信息是简单的,因为工具会自动地跟踪覆盖率,但是,如果我们是通过调试器,就不得不判断哪一部分程序经过了验证。而这通常只能依靠我们人工记录的方式来实现。
返回列表