依据形式的静态代码剖析、运转时内存监测、单元测验以及数据流剖析等软件验证技能是查找嵌入式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()函数来对其进行初始化。经修正后的函数如下所示:
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中封闭掉运转时内存监测来对程序进行构建。成果显现悉数如咱们所预期相同运转。
总结
作为全文的结束,让咱们一同对上述各个进程进行一个俯瞰式的总结。
首要,咱们开发的程序并未如我么所预期那样运转,咱们不得不在两种处理办法中挑选一种来查找程序中的过错:经过运转调试器或许运用主动过错检测技能。
假如咱们运用调试器运转代码来查找过错,咱们将会看到一些很古怪的现象:程序中的一些变量总是被赋予了相同的值。依据这种现象咱们不得不经过扫除法来查找问题的原因——即在应该运用比较运算符的当地咱们过错地运用了赋值运算符。而静态代码剖析则能为咱们主动地查看出该逻辑过错。运转时内存剖析是不或许查看出这种过错的,由于这种过错与内存无关。数据流剖析也很有或许找不到这类过错由于数据流剖析只是是经过这些途径而不会验证这些条件的正确性。
当咱们处理了这个问题后,程序能够运转了,可是依然还有内存相关的问题。内存相关的问题是很难被调试器发现的;当用户运用调试器调试程序时,用户并不知道内存的实践巨细。可是主动过错查看东西能够做到这点。因而,为了查找这些内存问题,咱们将整个程序进行插装,并运用运转时内存剖析东西来运转程序。这样咱们就能知道到底是那一片内存产生了写溢出过错。
尽管如此,在查看掩盖率剖析成果的时分,咱们留意到在方针板上测验的时分,并不是悉数代码都被掩盖到了。经过主动化的东西得到这样的掩盖率信息是简略的,由于东西会主动地盯梢掩盖率,可是,假如咱们是经过调试器,就不得不判别哪一部分程序经过了验证。而这一般只能依托咱们人工记载的办法来完结。
当东西提示咱们一些代码未被掩盖届时,咱们决议改动单元测验来额外地添加咱们测验履行的掩盖率。这就提示了程序中别的一些问题。在方针体系的正常测验中,掩盖悉数函数或许是不或许完结的使命,由于其间一些函数或许是硬件的失利处理函数或仅在某些小概率的特定状况下才会被调用的函数。而对这些函数的测验关于一些重视安全性的程序而言又是至关重要的。试想在飞机上用来处理速度传感器问题的程序中存在着代码过错:咱们会有体系溃散的风险,而不是导致某个设备为非作业状况。因而,经过创立单元测验用例来掩盖这类型的履行途径往往是对其进行有用测验的仅有办法。
接下来,咱们修正了东西查看到的悉数问题,一同经过验证相应的成果创立了一个回归测验用例(作为陈述的使命之一引导咱们完结)。然后咱们运转数据流剖析来掩盖在方针体系上即使运用单元测验也未履行到的途径。在此之前,咱们简直现已到达了100%的代码行掩盖率,可是咱们的途径掩盖率却未到达这个水平。BugDetective帮咱们发现了这些方面的一些潜在问题。这些问题或许并没有实践产生或许有或许永久不会产生。或许在实践运转时,这些问题只是会在当其条件满意的状况下才会呈现,而且在现实生活中,这些条件或许永久不或许满意。尽管如此,咱们不能保证跟着代码的晋级,运用程序不会履行到这些途径。
安全起见,咱们依然修正了所陈述的问题以扫除任何或许影响它的实践运用履行的风险。在修正代码的一同,咱们一同也引入了回归测验,当咱们再次运转单元测验时立即被检测到。在悉数的主动化过错检测办法中,回归测验是仅有能够协助咱们查看到代码是否产生了功用性的改动的办法,而且能验证出对代码进行的修正是否引入了功用性的过错以及不行预知的副效果。终究,咱们修正了回归测验套件,并从头测验代码,发现悉数运转正常。
正如读者所见,咱们运用的悉数测验办法——依据形式的静态代码剖析、内存剖析、单元测验、数据流剖析以及回归测验——并不是相互竞争的联系,恰好相反,它们是一种互补的联系。将上述东西结合运用,它们便是一套具有健壮效果的东西集,并为嵌入式C言语程序/软件供给一个无与伦比的主动化过错检测处理方案。
总而言之,经过主动地查找许多关于内存和其它编码的缺点,咱们成功地让程序运转起来了。尽管如此,值得留意的是,最风险的缺点却是实践的功用性过错:例如程序并未如所指定的要求运转。而不幸的是,这些过错往往是非常难以被发现的。
查找这类缺点的最好的一个办法便是经过同行代码查看来完结。即另指使至少一人来查看代码而且查看代码与需求内容的一致性,这样用户就能对实践程序是否会如预期那样运转有一个很好的*估。
别的一个非常有用的战略是环绕代码创立一个回归测验套件,这能协助用户方便地验证代码与标准的一致性。在本文所描绘的示例情形中,单元测验被用来强制履行运用程序级的运转时内存监测所未掩盖到的代码:它能掩盖到当时程序的功用性,在此之后,咱们对代码做了一些修正,它能提示咱们代码呈现的相应的功用性问题。事实上,这种单元测验用例应该被更早地创立起来:抱负状况下,当用户在完结程序的功用时就应该被创立起来。这样,用户就能得到更高的掩盖率并一同构建起一个更健壮的“安全网”来捕捉要害的功用性改动。
Parasoft的C++test能协助用户完结这两个使命:从主动化到办理同行代码查看流程,以及协助团队创立,继续地运转并保护一个高效的回归测验套件。
关于Parasoft C++test
Parasoft C++test是一个经广泛的最佳实践证明能提高软件开发团队开发功率以及软件质量的主动化集成处理方案。C++test能进行比如编码战略增强、静态代码剖析、运转时内存监测、主动同行代码查看以及单元和组件测验,然后为软件开发团队供给一种愈加有用的办法来保证其C以及C++程序能如所预期那样作业。C++test能够用于在通用开发IDE下的桌面平台中,以及在回归测验时经过命令行以批处理形式的办法运转。一同,C++test还集成了Parasoft的陈述体系,该体系能供给具有细分才能的依据Web 的仪表板,这使得开发团队依据C++test的测验成果和其他的一些要害进程方针来愈加方便地盯梢项目的状况和趋势。
经过在宿主机上进行很多的测验以及在方针体系中进行的滑润的验证,C++test能够协助软件开发团队削减花在嵌入式体系开发中的时刻、精力以及本钱。跟着代码在宿主机上的构建,C++test的主动化结构使得开发者能在方针硬件体系没有准备好的状况下就开端测验以提高代码质量。这大大地缩短了花在方针体系上测验的时刻。前期在宿主机上构建的测验套件能够被重用来在仿真器或实在的方针板上验证程序的功用性。
责任编辑:gt