目录
3.2 程序插桩法
3.2.1 目标代码插桩
3.2.2 源代码插桩
小提示:HeisenBugs
黑盒测试和白盒测试的异同
程序插桩法是一种被广泛使用的软件测试技术,由J.C.Huang教授提出。简单来说,程序插桩就是往被测试程序中插入测试代码以达到测试目的的方法,插入的测试代码被称为探针。根据测试代码插入的时间可以将程序插桩法分为目标代码插桩和源代码插桩。
目标代码插桩是指向目标代码(二进制代码)插入测试代码获取程序运行信息的测试方法,也称为动态程序分析方法。
目标代码插桩对程序运行时的内存监控、指令跟踪、错误检测等有着重要意义。相比于逻辑覆盖法,目标代码插桩在测试过程中不需要代码重新编译或链接程序,并且目标代码的格式和具体的编程语言无关,主要和操作系统相关,因此目标代码插桩有着广泛的使用。
1. 目标代码插桩的原理
目标代码插桩法的原理是在程序运行平台和底层操作系统之间建立中间层,通过中间层检查执行程序、修改指令,开发人员、软件分析工程师等对运行的程序进行观察,判断程序是否被恶意攻击或者出现异常行为,从而提高程序的整体质量。
2. 目标代码插桩法的执行模式
由于目标代码是可执行的二进制程序,因此目标代码的插桩可分为两种情况:一种是对未运行的目标代码插桩,从头到尾插入测试代码,然后执行程序。这种方式适用于需要实现完整系统或仿真时进行的代码覆盖测试。另一种情况是向正在运行的程序插入测试代码,用来检测程序在特定时间的运行状态信息。
目标代码插桩具有以下3种执行模式:
(1)即时模式(Just-In-Time):原始的二进制或可执行文件没有被修改或执行,将修改部分的二进制代码生成文件副本存储在新的内存区域中,在测试时仅执行修改部分的目标代码。
(2)解释模式(Interpretation Mode):在解释模式中目标代码被视为数据,测试人员插入的测试代码作为目标代码指令的解释语言,每当执行一条目标代码指令,程序就会在测试代码中查找并执行相应的替代指令,测试通过替代指令的执行信息就可以获取程序的运行信息。
(3)探测模式(Probe Mode):探测模式使用新指令覆盖旧指令进行测试,这种模式在某些体系结构(如x86)中比较好用。
两种情况:对未运行的目标代码插桩,向正在运行的程序插入测试代码;
三种模式:即时模式,解释模式,探测模式。
3. 目标代码插桩工具
由于目标程序是可执行的二进制文件,人工插入代码是无法实现的,因此目标代码插桩一般通过相应的插桩工具实现,插桩工具提供的API可以为用户提供访问指令。常见的目标代码插桩工具主要有:
(1)Pin-Dynamic Binary Instrumentation Tools(简称Pin)
Pin是由Intel公司开发的免费框架,它可以用于二进制代码检测与源代码检测。Pin支持IA-32、x86-64、MIC体系,可以运行在Linux、Windows和Android平台。Pin具有基本块分析器、缓存模拟器、指令跟踪生成器等模块,使用该工具可以创建程序分析工具、监视程序运行的状态信息等。Pin非常稳定可靠,常用于大型程序测试,如Office办公软件、虚拟现实引擎等。
(2)DynamoRIO
DynamoRIO是一个许可的动态二进制代码检测框架,作为应用程序和操作系统的中间平台,它可以在程序执行时实现程序任何部分的代码转换。DynamoRIO支持IA-32、AMD64、Arch64体系,可以运行在Linux、Windows和Android平台。DynamoRIO包含内存调试工具、内存跟踪工具、指令跟踪工具等。
源代码插桩是指对源文件进行完整的词法、语法分析后,确认插桩的位置,植入探针代码。相比于目标代码插桩,源代码插桩具有针对性和精确性。源代码插桩模型:
从图中可以看出,源代码插桩是在程序执行之前完成的,因此源代码插桩在程序运行过程中会产生探针代码的开销。相比于目标代码插桩,源代码插桩实现复杂程度低。源代码插桩是源代码级别的测试技术,探针代码程序具有较好的通用性,使用同一种编程语言编写的程序可以使用一个探针代码程序来完成测试。
下面通过一个小案例来讲解源代码插桩。该案例是一个除法运算:
1 #include
2 #define ASSERT(y) if(y){ printf("出错文件:%s\n",__FILE__);\
3 printf(" 在第 %d行:\n",__LINE__\);
4 printf(" 提示:除数不能为0 !\n");\
5 } // 定义 ASSERT(y)
6 int main()
7 {
8 int x,y;
9 printf(" 请输入被除数:");
10 scanf("%d",&x);
11 printf(" 请输入除数:");
12 scanf("%d",&y);
13 ASSERT(y==0); // 插入的桩(即探针代码)
14 printf("%d",x/y);
15 return 0;
16 }
为了监视除法运算除数输入是否正确,在代码第13行插入宏函数ASSERT(y),当除数为0时打印错误原因、出错文件、出错行数等信息提示。宏函数ASSERT(y)中使用了C语言标准库的宏定义“__FILE__”提示出错文件、“__LINE__”提示文件出错位置。
程序运行后,提示输入被除数和除数,在输入除数后,程序宏函数ASSERT(y)判断除数是否为0,若除数为0则打印错误信息,程序运行结束;若除数不为0,则进行除法运算并打印计算结果。根据除法运算规则设计测试用例,如表:
对插桩后的C源程序进行编译、链接,生成可执行文件并运行,然后输入表中的测试用例数据,观察测试用例的实际执行结果与预期结果是否一致。
程序插桩测试方法有效地提高了代码测试覆盖率,但是插桩测试方法会带来代码膨胀、执行效率低下和HeisenBugs,在一般情况下插桩后的代码膨胀率在20%~40%,甚至能达到100%导致插桩测试失败。
HeisenBugs即海森堡Bug,它是一种软件缺陷,这种缺陷的重现率很低,当人们试图研究时Bug会消失或改变行为。实际开发软件测试中,这种缺陷也比较常见,例如,测试人员测试到一个缺陷提交给开发人员后,开发人员执行缺陷重现步骤却得不到报告的缺陷,因为缺陷已经消失或者出现了其他缺陷。
1. 黑盒测试和白盒测试比较
黑盒测试过程中不用考虑内部逻辑结构,仅仅需要验证软件外部功能是否符合用户实际需求。黑盒测试可以发现以下缺陷。(1)外部逻辑功能缺陷,如界面显示信息错误等。(2)兼容性错误,如系统版本支持、运行环境等。(3)性能问题,如运行速度、响应时间等。
白盒测试可以设计测试用例尽可能覆盖程序中的分支语句,分析程序内部结构。白盒测试常用于以下几种情况。(1)源程序中含有多个分支,在设计测试用例时要尽可能覆盖所有分支,提高测试覆盖率。(2)内存泄漏检查迅速,黑盒测试只能在程序长时间运行中发现内存泄漏问题,而白盒测试能立即发现内存泄漏问题。
2. 测试阶段
黑盒测试与白盒测试在不同的测试阶段使用情况也不同,两者在不同阶段的使用情况如:
在测试过程中,黑盒测试与白盒测试结合使用会大大提升软件测试质量。