作者在 2010-08-14 21:00:26 发布以下内容
logiscope要求被分析的程序是一个完整的可编译的项目。而我希望可以对数个,至多几十个,且不是一个完整项目的C代码进行函数调用关系图。logiscope无法完成。
logiscope要求完整的项目是因为它希望得到一个准确的call graph,而我只希望 可以方便的得到call graph,就所不太准确也没有关系。事实上,如果代码不是完整的项目,的确是会得到有错误的call graph。但对于一些临时性分析代码的任务,或者是无法得到一个很大项目全部代码时,进行部分代码的分析也是必要的。
部门的专业人员建议使用打桩的方法使部分代码变成一个仅可编译的,在编译层面上是完整的代码,以规避logiscope的严格要求。如果这样做,要花费较多的时间,得不偿失。
另外,etags可以产生一个函数表。不过,表里只有每个函数的声明和位置,并没有调用关系的数据。
那么,我自己做一个吧。
设想:
1. 使用python + regular expression从代码中分析出函数和调用关系。
2. 将调用关系存在
Graphviz 地址:http://www.graphviz.org/ 这个网站上,有Graphviz的源代码和WINDOWS二进制程序包,还有PDF格式的用户手册。
偶然的发现,开源软件Source Navigator支持函数调用关系图。经过使用,它的确支持生成函数调用关系图。而且,它可以对不完整的代码进行分析。但得到的函数调用关系图只能一个函数一个函数分别的VIEW。使用起来很不方便。不过它的界面设计还是很有新意的。
基本成型的代码。PYTHON写的。功能很简陋。下面是产生的Call Graph。callgraph.py
#!/usr/bin/python
from optparse import OptionParser
import re
#filename save every source file name
#fun_name save every function name of source file
#fun_body save function body of every function
#+++++++++++++++++++++++++++++++++++++++++++++++++
#show match result by a spie way
def re_show(pat, s):
print re.compile(pat, re.M).sub("{\g<0>}", s.rstrip()), '\n'
#+++++++++++++++++++++++++++++++++++++++++++++++++
#get function body
def get_fun_body(fun_name, source_code):
m = re.compile("\s+" + fun_name + "\(.*\)\s*\{").search(source_code)
pos1 = m.end()
pos2 = pos1
lbrace_count = 1
rbrace_count = 0
while pos2 < len(source_code) and lbrace_count > rbrace_count:
if source_code[pos2] == '{':
lbrace_count = lbrace_count + 1
if source_code[pos2] == '}':
rbrace_count = rbrace_count + 1
pos2 = pos2 + 1
if lbrace_count == rbrace_count:
#print source_code[pos1-1:pos2+1]
return source_code[pos1-1:pos2+1]
else:
print "fail"
return ""
#+++++++++++++++++++++++++++++++++++++++++++++++++
def removeall(arglist, argelement):
try:
while 1:
arglist.index(argelement)
arglist.remove(argelement)
except:
return
return
#+++++++++++++++++++++++++++++++++++++++++++++++
def genall(fun_name, fun_name_list, fun_body):
dot_file = open("callgraph.dot", "w")
dot_file.write("digraph G {\n")
color_file = open("color.dot", "w")
color_file.write("digraph C {\n")
i = 0
j = 0
for l in filename:
color_file.write(" \"" + l + "\"" + node_color[j])
for m in fun_name[j]:
dot_file.write(" " + m + node_color[j])
for n in fun_name_list:
if re.compile("[\s+|\(|+|-|*\/]" + n + "\(").search(fun_body[i]) != None:
dot_file.write(" " + m + " -> " + n + ";\n")
#print m, " call ", n
i = i + 1
j = j + 1
j = 0
for l in filename:
dot_file.write(" " + "subgraph \"" + l.rstrip("\r") + "\" {label=\"" + l + "\"; ")
for m in fun_name[j]:
dot_file.write(m + "; ")
dot_file.write("}\n")
j = j + 1
dot_file.write("}")
dot_file.close()
color_file.write("}")
color_file.close()
return
#++++++++++++++++++++++++++++++++++++++++++++++++
def gen_next_level(fun_name, fun_name_list, fun_body, spec_fun, dot_file, filename, last_fun):
i = 0
j = 0
for l in filename:
for m in fun_name[j]:
if m == spec_fun:
for n in fun_name_list:
if (re.compile("[\s+|\(|+|-|*\/]" + n + "\(").search(fun_body[i]) != None):
dot_file.write(" " + m + node_color[j])
dot_file.write(" " + m + " -> " + n + ";\n")
print "==", m, " call ", n
#防止代码函数递归调用
if m != n and last_fun.count(n)==0:
last_fun.append(n)
gen_next_level(fun_name, fun_name_list, fun_body, n, dot_file, filename, last_fun)
dot_file.write(" " + spec_fun + node_color[j])
return
i = i + 1
j = j + 1
return
#++++++++++++++++++++++++++++++++++++++++++++++++
def genfunction(fun_name, fun_name_list, fun_body, spec_fun):
last_fun = []
dot_file = open("callgraph_part.dot", "w")
dot_file.write("digraph G {\n")
color_file = open("color.dot", "w")
color_file.write("digraph C {\n")
i = 0
j = 0
for l in filename:
color_file.write(" \"" + l + "\"" + node_color[j])
for m in fun_name[j]:
for n in fun_name_list:
if re.compile("[\s+|\(|+|-|*\/]" + n + "\(").search(fun_body[i]) != None and (n == spec_fun or m == spec_fun):
dot_file.write(" " + m + node_color[j])
dot_file.write(" " + m + " -> " + n + ";\n")
print m, " call ", n
if m == spec_fun:
last_fun.append(spec_fun)
gen_next_level(fun_name, fun_name_list, fun_body, n, dot_file, filename, last_fun)
i = i + 1
j = j + 1
dot_file.write("}")
dot_file.close()
color_file.write("}")
color_file.close()
return
#++++++++++++++++++++++++++++++++++++++++++++++++
parser = OptionParser(version="%prog 0.1 ")
parser.add_option("-o", "--output", dest="DOT_FILE_NAME",
help="dot file name", metavar="FILE")
parser.add_option("-f", "--function", dest="FUN_NAME",
help="function name")
(options, args) = parser.parse_args()
#D = vars(options)
#for i in D:
# print i, "=", D[i]
#get source file name
filename = args
#print filename
#print options.DOT_FILE_NAME
#print options.FUN_NAME
#++++++++++++++++++++++++++++++++++++++++++++++++
#get source file context
code = []
i = 0
for l in filename:
code.append(open(filename[i].rstrip("\r")).read())
i = i + 1
#++++++++++++++++++++++++++++++++++++++++++++++++
#get all function of every source file
fun_name = []
rexpression = re.compile("\s+(\w+)\(.*\)\s*\{")
i = 0
for l in filename:
fun_name.append(rexpression.findall(code[i]))
for i in range(len(fun_name)):
removeall(fun_name[i], "if")
removeall(fun_name[i], "while")
removeall(fun_name[i], "for")
removeall(fun_name[i], "switch")
removeall(fun_name[i], "sizeof")
removeall(fun_name[i], "malloc")
removeall(fun_name[i], "close")
removeall(fun_name[i], "socket")
removeall(fun_name[i], "fopen")
removeall(fun_name[i], "changeState")
removeall(fun_name[i], "getLogModule")
i = i + 1
#+++++++++++++++++++++++++++++++++++++++++++++++
fun_name_list = []
fun_body = []
i = 0
for m in fun_name:
for n in m:
try:
fun_name_list.index(n)
fun_name[i].remove(n)
except:
fun_name_list.append(n)
fun_body.append(get_fun_body(n, code[i]))
i = i + 1
#print filename
#print fun_name #含重名的,二维的tuple,维度为不同的代码文件
#print fun_name_list #不含重名的,一维tuple
#print fun_body
global node_color
node_color = []
node_color.append(" [shape=box, style=filled, color=\".7 .3 5.5\"]\n")
node_color.append(" [shape=box, style=filled, color=\".9 .4 2.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".2 .4 1.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".3 .5 2.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".6 .8 1.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".7 .9 3.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".4 .4 3.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".6 .1 1.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".5 .1 2.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".4 .2 3.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".3 .3 4.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".2 .4 5.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".1 .5 6.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".6 .6 7.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".7 .7 8.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".8 .8 9.0\"]\n")
if options.FUN_NAME==None:
genall(fun_name, fun_name_list, fun_body)
else:
genfunction(fun_name, fun_name_list, fun_body, options.FUN_NAME)
#s = '''Mary had a little lamb And everywhere'''
#re_show('a', s)
logiscope要求完整的项目是因为它希望得到一个准确的call graph,而我只希望 可以方便的得到call graph,就所不太准确也没有关系。事实上,如果代码不是完整的项目,的确是会得到有错误的call graph。但对于一些临时性分析代码的任务,或者是无法得到一个很大项目全部代码时,进行部分代码的分析也是必要的。
部门的专业人员建议使用打桩的方法使部分代码变成一个仅可编译的,在编译层面上是完整的代码,以规避logiscope的严格要求。如果这样做,要花费较多的时间,得不偿失。
另外,etags可以产生一个函数表。不过,表里只有每个函数的声明和位置,并没有调用关系的数据。
那么,我自己做一个吧。
设想:
1. 使用python + regular expression从代码中分析出函数和调用关系。
2. 将调用关系存在
Graphviz 地址:http://www.graphviz.org/ 这个网站上,有Graphviz的源代码和WINDOWS二进制程序包,还有PDF格式的用户手册。
偶然的发现,开源软件Source Navigator支持函数调用关系图。经过使用,它的确支持生成函数调用关系图。而且,它可以对不完整的代码进行分析。但得到的函数调用关系图只能一个函数一个函数分别的VIEW。使用起来很不方便。不过它的界面设计还是很有新意的。
基本成型的代码。PYTHON写的。功能很简陋。下面是产生的Call Graph。callgraph.py
#!/usr/bin/python
from optparse import OptionParser
import re
#filename save every source file name
#fun_name save every function name of source file
#fun_body save function body of every function
#+++++++++++++++++++++++++++++++++++++++++++++++++
#show match result by a spie way
def re_show(pat, s):
print re.compile(pat, re.M).sub("{\g<0>}", s.rstrip()), '\n'
#+++++++++++++++++++++++++++++++++++++++++++++++++
#get function body
def get_fun_body(fun_name, source_code):
m = re.compile("\s+" + fun_name + "\(.*\)\s*\{").search(source_code)
pos1 = m.end()
pos2 = pos1
lbrace_count = 1
rbrace_count = 0
while pos2 < len(source_code) and lbrace_count > rbrace_count:
if source_code[pos2] == '{':
lbrace_count = lbrace_count + 1
if source_code[pos2] == '}':
rbrace_count = rbrace_count + 1
pos2 = pos2 + 1
if lbrace_count == rbrace_count:
#print source_code[pos1-1:pos2+1]
return source_code[pos1-1:pos2+1]
else:
print "fail"
return ""
#+++++++++++++++++++++++++++++++++++++++++++++++++
def removeall(arglist, argelement):
try:
while 1:
arglist.index(argelement)
arglist.remove(argelement)
except:
return
return
#+++++++++++++++++++++++++++++++++++++++++++++++
def genall(fun_name, fun_name_list, fun_body):
dot_file = open("callgraph.dot", "w")
dot_file.write("digraph G {\n")
color_file = open("color.dot", "w")
color_file.write("digraph C {\n")
i = 0
j = 0
for l in filename:
color_file.write(" \"" + l + "\"" + node_color[j])
for m in fun_name[j]:
dot_file.write(" " + m + node_color[j])
for n in fun_name_list:
if re.compile("[\s+|\(|+|-|*\/]" + n + "\(").search(fun_body[i]) != None:
dot_file.write(" " + m + " -> " + n + ";\n")
#print m, " call ", n
i = i + 1
j = j + 1
j = 0
for l in filename:
dot_file.write(" " + "subgraph \"" + l.rstrip("\r") + "\" {label=\"" + l + "\"; ")
for m in fun_name[j]:
dot_file.write(m + "; ")
dot_file.write("}\n")
j = j + 1
dot_file.write("}")
dot_file.close()
color_file.write("}")
color_file.close()
return
#++++++++++++++++++++++++++++++++++++++++++++++++
def gen_next_level(fun_name, fun_name_list, fun_body, spec_fun, dot_file, filename, last_fun):
i = 0
j = 0
for l in filename:
for m in fun_name[j]:
if m == spec_fun:
for n in fun_name_list:
if (re.compile("[\s+|\(|+|-|*\/]" + n + "\(").search(fun_body[i]) != None):
dot_file.write(" " + m + node_color[j])
dot_file.write(" " + m + " -> " + n + ";\n")
print "==", m, " call ", n
#防止代码函数递归调用
if m != n and last_fun.count(n)==0:
last_fun.append(n)
gen_next_level(fun_name, fun_name_list, fun_body, n, dot_file, filename, last_fun)
dot_file.write(" " + spec_fun + node_color[j])
return
i = i + 1
j = j + 1
return
#++++++++++++++++++++++++++++++++++++++++++++++++
def genfunction(fun_name, fun_name_list, fun_body, spec_fun):
last_fun = []
dot_file = open("callgraph_part.dot", "w")
dot_file.write("digraph G {\n")
color_file = open("color.dot", "w")
color_file.write("digraph C {\n")
i = 0
j = 0
for l in filename:
color_file.write(" \"" + l + "\"" + node_color[j])
for m in fun_name[j]:
for n in fun_name_list:
if re.compile("[\s+|\(|+|-|*\/]" + n + "\(").search(fun_body[i]) != None and (n == spec_fun or m == spec_fun):
dot_file.write(" " + m + node_color[j])
dot_file.write(" " + m + " -> " + n + ";\n")
print m, " call ", n
if m == spec_fun:
last_fun.append(spec_fun)
gen_next_level(fun_name, fun_name_list, fun_body, n, dot_file, filename, last_fun)
i = i + 1
j = j + 1
dot_file.write("}")
dot_file.close()
color_file.write("}")
color_file.close()
return
#++++++++++++++++++++++++++++++++++++++++++++++++
parser = OptionParser(version="%prog 0.1 ")
parser.add_option("-o", "--output", dest="DOT_FILE_NAME",
help="dot file name", metavar="FILE")
parser.add_option("-f", "--function", dest="FUN_NAME",
help="function name")
(options, args) = parser.parse_args()
#D = vars(options)
#for i in D:
# print i, "=", D[i]
#get source file name
filename = args
#print filename
#print options.DOT_FILE_NAME
#print options.FUN_NAME
#++++++++++++++++++++++++++++++++++++++++++++++++
#get source file context
code = []
i = 0
for l in filename:
code.append(open(filename[i].rstrip("\r")).read())
i = i + 1
#++++++++++++++++++++++++++++++++++++++++++++++++
#get all function of every source file
fun_name = []
rexpression = re.compile("\s+(\w+)\(.*\)\s*\{")
i = 0
for l in filename:
fun_name.append(rexpression.findall(code[i]))
for i in range(len(fun_name)):
removeall(fun_name[i], "if")
removeall(fun_name[i], "while")
removeall(fun_name[i], "for")
removeall(fun_name[i], "switch")
removeall(fun_name[i], "sizeof")
removeall(fun_name[i], "malloc")
removeall(fun_name[i], "close")
removeall(fun_name[i], "socket")
removeall(fun_name[i], "fopen")
removeall(fun_name[i], "changeState")
removeall(fun_name[i], "getLogModule")
i = i + 1
#+++++++++++++++++++++++++++++++++++++++++++++++
fun_name_list = []
fun_body = []
i = 0
for m in fun_name:
for n in m:
try:
fun_name_list.index(n)
fun_name[i].remove(n)
except:
fun_name_list.append(n)
fun_body.append(get_fun_body(n, code[i]))
i = i + 1
#print filename
#print fun_name #含重名的,二维的tuple,维度为不同的代码文件
#print fun_name_list #不含重名的,一维tuple
#print fun_body
global node_color
node_color = []
node_color.append(" [shape=box, style=filled, color=\".7 .3 5.5\"]\n")
node_color.append(" [shape=box, style=filled, color=\".9 .4 2.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".2 .4 1.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".3 .5 2.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".6 .8 1.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".7 .9 3.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".4 .4 3.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".6 .1 1.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".5 .1 2.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".4 .2 3.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".3 .3 4.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".2 .4 5.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".1 .5 6.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".6 .6 7.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".7 .7 8.0\"]\n")
node_color.append(" [shape=box, style=filled, color=\".8 .8 9.0\"]\n")
if options.FUN_NAME==None:
genall(fun_name, fun_name_list, fun_body)
else:
genfunction(fun_name, fun_name_list, fun_body, options.FUN_NAME)
#s = '''Mary had a little lamb And everywhere'''
#re_show('a', s)