软件缺陷定位☞获取函数调用序列

本文主要介绍利用pvtrace工具获取西门子测试集replace的函数调用关系图。

1、About:

西门子数据集的目录结构参见此篇博文,本篇博文主要介绍根据需求编写测试脚本利用pvtrace工具动态的将测试用例传入程序并获取函数调用关系图。由于在西门子的7个子测试集中,replace测试集含有的函数最多,因此本篇博文选用replace测试集作为例子,其他数据集也是一样操作。

2、测试脚本范例理解:

之前博文已经介绍replace数据集给我们提供了范例脚本,存储在scripts目录中,该脚本用于运行source.alt / source.orig 目录中被测程序的正确原型,但是研究软件缺陷定位不可能只运行正确的程序,将测试用例动态传入错误版本的程序并分析其运行结果更为重要,下面我们介绍如何利用套件中已经给出的范例脚本修改得到自己需要的运行脚本。

打开scripts目录下的runall.sh脚本,部分截图如下:

软件缺陷定位☞获取函数调用序列

可以看出,该脚本的功能:

1)遍历inputs文件夹下的所有测试用例(replace测试集的inputs文件夹下包含3个子文件夹,里面的测试用例共计5542个);

2)将每个测试用例依次传入被测程序中,并指定输出结果存放的路径

如果想要将该脚本修改为执行错误版本程序的脚本,我们需要修改:

1)可执行程序的路径

2)程序执行结果存放的路径

手工修改该脚本中5542个路径显然不可行,需要手工编写程序使其自动修改路径,生成新的脚本文件。

在实际情况中,仅仅获取被测程序的运行结果通过与否是不足够的,根据需求还应获取程序执行信息(这里我示范的是获取程序的函数调用关系图)。

我们还需要了解pvtrace工具的使用,这个工具可以获取程序的函数调用关系通过Graphviz 可以将函数调用关系可视化展示,下面介绍下pvtrace和Graphviz的安装:

1)安装pvtrace:

#进入fl文件目录下:
wget http://www.mtjones.com/developerworks/pvtrace.zip
#解压下载包,如果出现命令找不到,需下载压缩工具
yum install -y unzip zip 或者 sudo apt-get install zip
unzip pvtrace.zip
cd pvtrace
make
sudo make install

 2)安装Graphviz:

yum install graphviz

这里给出pvtrace和Graphviz的使用介绍链接:

http://www.voidcn.com/article/p-xlemghrq-xb.html 

https://www.ibm.com/developerworks/cn/linux/l-graphvis/

如果需要获取函数调用关系,为了避免路径出错,我们直接将pvtrace路径下的instrument.c文件拷贝到跟源码文件同级目录下,在GCC编译代码文件时需要加入指定编译选项即可:

gcc -g -finstrument-functions replace.c instrument.c -o replace.exe

3、简单获取函数动态调用关系图示例

这里先简单介绍下,如何获取函数动态调用关系图:

1)将pvtrace路径下的instrument.c文件拷贝到source.alt / source.orig目录下,然后将源程序代码replace.c和instrument.c一起编译,命令如下:

cd source.alt/source.orig
gcc -g -finstrument-functions replace.c instrument.c -o replace.exe

2)将scripts目录中的脚本进行修改(命名为runV1.sh),这里示例一个测试用例,后面写代码批量修改路径:

replace.exe '-?' 'a&'  < ../../inputs/temp-test/1.inp.1.1 > ../../outputs/t1
pvtrace replace.exe

3)执行脚本文件:

source ./runV1.sh

4)source.alt / source.orig目录下生成graph.dot文件,用graphviz将它可视化:

dot -Tpng graph.dot -o graph.png

执行命令后会报错,打开graph.dot文件将第一行的graph.dot改为graph即可,再执行上面命令;

5)执行完后,目录下会生成一个graph.png的图,这个图就是程序replace.c在传入t1这个测试用例生成的函数调用关系图如下:

软件缺陷定位☞获取函数调用序列

 6)从上图还可以看到函数之间调用的次数,当然,我们如果需要数据处理的话,利用graph.dot文件就够了,这个文件里包含函数之间调用关系,调用顺序,调用次数,这些信息根据需要自己进行处理;

4、生成测试脚本:

下面我参考此篇博文根据范例脚本生成错误版本执行的脚本,脚本生成使用lua语言实现。

1)首先初始化,指定子套件名称”replace”,测试用例个数5542和错误版本程序个数32;

SUITE_NAME = "replace"
CASE_NUM = 5542
VERSION_NUM = 32  --should be 32 in replace
--VARIABLES--
buff = {}
i = 0
j = 0
k = 0
line_num = 0
tmp = ""

2)接着我们要将范例给的的runall.sh 读入,以便在下一步的文本分析中可以按行进行文本匹配,下面的程序段完成了将范例给出的runall.sh 文本读入,并按行存储至一个缓冲字符串数组buff 中:

--SAVE ORIG SCRIPT INTO BUFFER--
file = io.open("runall.sh", "r")
for line in file:lines() do
	i = i + 1;
	buff[i] = line
end
line_num = i
file:close()

3)最后就是将buff 数组中的每一行进行文本匹配,将”/source.alt/source.orig/”字段全部替换为”/../../versions.alt/versions.orig/vX/”(其中”X”为相应的错误版本号),将”/inputs/”字段全部替换为”/../../inputs/”,将”/outputs/”字段全部替换为”/../../newoutputs/vX”(其中”X”为相应的错误版本号),其余保持不变,实现代码如下:

--GENERATE RUN SCRIPT FOR VERSIONS--
for i = 1, VERSION_NUM do
	k = 1
	file = io.open("run_v"..i..".sh", "w")
	for j = 1, line_num do
		tmp = buff[j]
		if string.find(tmp, "/source.alt/source.orig/") ~=nil then
			tmp = string.gsub(tmp,"/source.alt/source.orig/", "/../../versions.alt/versions.orig/v"..i.."/") --modefy paths--
			tmp = string.gsub(tmp,"/inputs/", "/../../inputs/")
			tmp = string.gsub(tmp,"/outputs/", "/../../newoutputs/v"..i.."/")
			tmp = tmp.."\ncd ../../../versions.alt/versions.orig/v"..i.." && pvtrace replace.exe"
			tmp = tmp.."\nmv trace.txt ./tra"..k.. " && mv graph.dot ./tra"..k
			k = k + 1
		end
		file:write(tmp.."\n")
		tmp = ""
	end
	file:close()
end

上面这段代码为每次错误版本都生成了一个名为run_vX.sh 的运行脚本,其中X为相应的错误版本号。并且在每个测试用例执行之后,进入错误版本目录下运行命令pvtrace replace.exe,生成函数调用关系trace.txt和graph.dot文件,因为每个测试用例运行生成的dot文件名都是graph、txt文件名都是trace,因此为了避免覆盖需要将各个版本的每个测试用例生成的dot和txt文件移入对应的文件夹(下面需要在每个版本文件夹下建立5542个名为traX的文件夹---X为相对应执行的测试用例);

#下面是用python代码在每个错误版本程序文件夹下生成5542个存放dot和txt的文件夹
#make_dir.py
import os
import shutil
dir = os.getcwd()
for index1 in range(1, 33):
	if index1 > 0:
		new_dir = dir + '/v'+ str(index1)	
		for index2 in range(5543):
			if index2 > 0:
				os.mkdir(new_dir + '/tra' + str(index2))
	pass

执行命令:python make_dir.py即可生成文件夹;

接着在versions.alt/versions.orig目录下建立一个存放shell脚本的文件夹sh,并将范例脚本runall.sh拷贝至sh目录下;

最后我们就可以执行前面的lua程序了,完整代码链接如下:

链接:https://pan.baidu.com/s/1aEO52uidTEVSKNJuzg2_mg    提取码:kqyz 

#运行make_sh.lua程序
lua make_sh.lua

运行完后,可以在sh目录下发现生成的错误版本相对应的32个sh脚本,如下图:

软件缺陷定位☞获取函数调用序列

 5、批量编译:

在执行错误版本的测试脚本之前,首先需要对错误版本的代码进行自动批量编译,在这里,我们使用makefile 进行自动编译,makefile的结构如下:

replace:
	gcc -g -finstrument-functions replace.c instrument.c -o replace.exe

由于我对makefile还不熟悉,因此采用的笨办法,将这个makefile文件在每个错误版本文件夹下拷贝一份,然后再写代码自动编译(在编译之前需要修改程序源码使其编译通过,修改方式参考这篇博文)。