转换GDB调用栈到流程图

来源:互联网 发布:学数据库安全 编辑:程序博客网 时间:2024/05/16 06:42

如果你想在GDB调试时把调用堆栈保存下来归档,那下面这个脚本就方便你了。原理是将调用堆栈的函数抽取出来,再完成调用关系就可以了。


首先你要安装dot (Mac OS下安装Graphviz), 如果你想转为文本格式,就可安装Perl的Graph::Easy包(命令行:sudo perl -MCPAN -e 'install Graph::Easy', Ubuntu下直接安装libgraph-easy-perl)。


然后按需要执行脚本就可以了, 假定如下的调用栈:

   #0  WebCore::FrameLoader::FunctionA (this=0x2a7d91f8) at /FrameLoader.cpp

   #1  0x4efd2514 in WebCore::FrameLoader::FunctionB (this=0x2a7d91f8) at /FrameLoader.cpp:553

   #2  0x4efd1918 in FunctionC ()at /mainFile.cpp:100


1. 转为图片

python convertStackToDot.pystack.txt|dot -Tpng>output.png


默认由上到下排列,如果你想改变,可以通过在脚本的参数中增加dot设定调整,比如:

python convertStackToDot.py stack.txt 'rankdir=LR;'|dot -Tpng>output.png

就会变成横排:



2. 转为文本

有时你希望转为文本,还好有Graph Easy包,不然你就要使用asciio自己画了。

python convertStackToDot.py stack.txt ‘rankdir=LR;’|graph-easy -as_ascii>output.txt


效果如下:

+-----------+     +------------------------+     +------------------------+

| FunctionC | --> | FrameLoader::FunctionB | --> | FrameLoader::FunctionA |

+-----------+     +------------------------+     +------------------------+


3. 多个调用栈的解析

如果有多个调用栈一起组成完整的流程,只要在各个调用栈中间加入空格就可以了。比如下面的堆栈:

#0  WebCore::FrameLoader::FunctionF (this=0x2a7d90f8,at /FrameLoader.cpp
#1  WebCore::FrameLoader::FunctionA (this=0x2a7d90f8,at /FrameLoader.cpp
#2  0x4ffd2514 in WebCore::FrameLoader::FunctionB (this=0x2a7d90f8, at /FrameLoader.cpp:553
#3  0x4ffd1918 in FunctionC (this=0x2a7d90f8 at /mainFile.cpp:100


#0  WebCore::FrameLoader::FunctionE (this=0x2a7d90f8,at /FrameLoader.cpp
#1  0x4ffd2514 in WebCore::FrameLoader::FunctionB (this=0x2a7d90f8, at /FrameLoader.cpp:553
#2  0x4ffd1918 in FunctionC (this=0x2a7d90f8 at /mainFile.cpp:100


输出结果如下, 线段上的数字代码的堆栈序号:



转载请注明出处: http://blog.csdn.net/horkychen


再次修改了一下,对于重复的调用,可以使用-d选项选择是否全部显示出来,同时可以允许指定一个正则表达式将某部分节点高亮显示,如允许显示重复调用路径的效果:

  python convertStackToDot.py -d -l "A|PolicyChecker" stack.txt|dot -Tpng>output.png




脚本如下:


#!/usr/bin/python#coding=utf-8#python convertStackToDot.py stack.txt|dot -Tpng>output.png#To generate ascii flow chart, graph_easy should be installed:#  sudo apt-get install libgraph-easy-perl#The use below command line:#python convertStackToDot.py stack.txt|graph-easy -as_ascii>output.txtimport sysimport reimport argparseimport osfunction_name_str = r"[a-zA-Z][a-zA-Z0-9]*::[~a-zA-Z0-9\_\-<: >=]*\(|[a-zA-Z0-9_\-<>]* \("class_name_str = r"::[a-zA-Z][a-zA-Z0-9]*::"known_namespaces = []libCName="/system/lib/libc.so"Colors=["#000000","#ff0000","#00aa00","#0000ff","#800080","#daa520","#ff00b4","#d2691e","#00bfff",        "#D0A020","#006000","#305050","#101870"]Styles=["solid","dashed","dotted"]MAX_COLOR_COUNT = len(Colors)MAX_LINE_WIDTH_COUNT = 4MAX_STYLE_COUNT = len(Styles)FirstNodeAttr = ' style="rounded,filled" fillcolor=\"#00bb0050\"'HighlightAttr = ' style="rounded,filled" fillcolor=\"yellow\"'blockNum = 0    nodeNo = 0nodeList={}nodeOrderList={}firstNodeList={}nodeAttr={}outputText = ''callingStack = ''newBlock=TruewillCommit=False #For filtering purposeblockBackTrace = ''blockNodeList={}def getTextOfBlockNodeList(lastNodeName,lastNodeLabel):    global firstNodeList    strBlockNodeText = ''    for key in blockNodeList.keys():        if not nodeList.has_key(key):            name = blockNodeList[key]            strBlockNodeText = strBlockNodeText + name + nodeAttr[name]+'\n'            nodeList[key] = name    #Replace the attribute of the last node    if len(lastNodeName)>0 and not firstNodeList.has_key(lastNodeName):        oldStr = lastNodeName+'[label="'+lastNodeLabel+'" shape=box ];';        newStr = lastNodeName+'[label="'+lastNodeLabel+'" shape=box '+FirstNodeAttr+' ];'        strBlockNodeText = strBlockNodeText.replace(oldStr,newStr,1)        firstNodeList[lastNodeName] = True    return strBlockNodeTextdef submitAndResetForNewBlock(args,lastNodeName,lastNodeLabel):    global blockBackTrace,newBlock,callingStack    global blockNodeList,willCommit,outputText    newBlock = True    if willCommit and len(blockBackTrace)>0:        callingStack = blockBackTrace + '\n' + callingStack        blockNodeText = getTextOfBlockNodeList(lastNodeName,lastNodeLabel)        outputText = outputText+blockNodeText    blockNodeList = {}    blockBackTrace = ''    willCommit = (len(args.filter)==0)def getClassName(text):    m = re.search(class_name_str,text)    if m:        className=text[0:m.end()-2]    elif not text[:text.find('::')] in known_namespaces:        className = text[:text.find('::')]    else:        className = text    return classNamedef getNodeName(text,nodeNo,args):    global willCommit,blockNodeList,newBlock    processText = text    if len(args.ignore)>0 and re.search(args.ignore,text):        return ''     if args.onlyClass:        processText = getClassName(text)    if nodeList.has_key(processText):        nodeName = nodeList[processText]    elif blockNodeList.has_key(processText):        nodeName = blockNodeList[processText]    else:        nodeName = 'Node'+str(nodeNo)        blockNodeList[processText]=nodeName        extraAttr = ''        try:            if len(args.highlight)>0 and re.search(args.highlight,processText):                extraAttr = HighlightAttr        except:            extraAttr = ''        nodeAttr[nodeName] = '[label="'+processText+'" shape=box '+extraAttr+'];'        if len(args.filter)>0 and re.search(args.filter,text):        willCommit = True    return nodeNamedef createNewRelation(nodeName,lastNodeName,blockNum):    global blockBackTrace    tempKey = "%s_%s"%(nodeName,lastNodeName)    if args.duplicate or not nodeOrderList.has_key(tempKey):        lineColor = Colors[(blockNum-1)%MAX_COLOR_COUNT]        linePenWidth = str((int((blockNum-1)/MAX_COLOR_COUNT)%MAX_LINE_WIDTH_COUNT)+1)        lineStyle = Styles[((blockNum-1)/(MAX_COLOR_COUNT*MAX_LINE_WIDTH_COUNT))%MAX_STYLE_COUNT]        if nodeOrderList.has_key(tempKey):            linePenWidth = '1'            lineColor = lineColor+'50' #Set alpha value        blockBackTrace = nodeName+'->'+lastNodeName+'[label='+str(blockNum)+\                    ',color=\"'+lineColor+'\"'+\                    ',style=\"'+lineStyle+'\"'+\                    ',penwidth='+linePenWidth+']\n'+ \                    blockBackTrace    nodeOrderList[tempKey] = Truedef combineOutputText():    global outputText,callingStack    if len(callingStack)>0:        outputText = outputText+callingStack+"\n}"        return outputText    else:        return ''def initialize(args):    global outputText,callingStack    outputText = "digraph backtrace{ \nnode [style=rounded  fontname=\"Helvetica Bold\"];\n" + args.extraDotOptions +"\n"def convertToDot(file,args):    global willCommit,outputText,newBlock,blockNum,nodeNo    global outputText,callingStack,blockBackTrace        lastNodeName = ''    lastNodeLabel = ''    willCommit = (len(args.filter)==0) #To specify the initial value according to the filter.    f = open(file, 'r')    for line in f:        line = line.strip()        if(len(line)==0) or line.startswith("#0  ") or line.startswith("#00 "):            if not newBlock:                #Start with new block here.                submitAndResetForNewBlock(args, lastNodeName, lastNodeLabel)            if(len(line.strip())==0):                continue        if not line.startswith("#"):            continue        text = ""        m = re.search(function_name_str, line)        if m:            nodeNo = nodeNo+1            text=m.group(0).strip()            text = text[:-1]            text = text.strip()        elif line.find(libCName)>0:            nodeNo = nodeNo+1            text='FunctionInLibC'        if(len(text)==0):            continue        #Get the existing node or create new one. Anyway, just ask for the name.        nodeName = getNodeName(text,nodeNo,args)        #To throw it away if no valid name was returned according to your arguments.        if(len(nodeName)==0):            continue        if newBlock:            newBlock = False            blockNum = blockNum + 1        else:            createNewRelation(nodeName,lastNodeName,blockNum)        lastNodeName = nodeName        lastNodeLabel = text        if args.onlyClass:            lastNodeLabel = getClassName(text)    if len(blockBackTrace)>0:        #Wow, one block was built successfully, sumit it.        submitAndResetForNewBlock(args, lastNodeName, lastNodeLabel)    f.close()if __name__=="__main__":    parser = argparse.ArgumentParser()    parser.add_argument('file', type=str, help='The text file which contains GDB call stacks.')    parser.add_argument('-e','--extraDotOptions', default='', help='Extra graph options. For example: rankdir=LR; That means to show functions in horizontal.')    parser.add_argument('-l','--highlight', default='', help='Regular Expression Pattern. Nodes are highlighted whose name match the pattern.')    parser.add_argument('-f','--filter', default='', help='Regular Expression Pattern. The calling stack are shown only if which include the matched nodes.')    parser.add_argument('-d','--duplicate', action='store_true', default=False, help='Leave duplicated callings.')    parser.add_argument('-i','--ignore', default='', help='To hide some nodes, try this.')    parser.add_argument('-c','--onlyClass', action='store_true', default=False, help='To simplify the output with less nodes, only Class node will be listed.')    if len(sys.argv)<=1:        parser.print_help()        print "  Any comment, please feel free to contact horky.chen@gmail.com."        quit()    args = parser.parse_args()        if args.file is None:        quit()    initialize(args)    if os.path.isfile(args.file):        convertToDot(args.file,args)    else:        filenames = os.listdir(args.file)        for filename in filenames:            convertToDot(os.path.join(args.file,filename),args)    resultDotString = combineOutputText()    if len(resultDotString)>0:        print(resultDotString)



参考:

  1. GDB扩展之Command File - 提高调试效率

  2. [WebKit]C++类的数据结构及在反汇编上的应用

  3. 使用LLDB脚本简化打印复杂数据的操作



5 0
原创粉丝点击