转换GDB调用栈到流程图

简介: 如果你想在GDB调试时把调用堆栈保存下来归档,那下面这个脚本就方便你了。原理是将调用堆栈的函数抽取出来,再完成调用关系就可以了。首先你要安装dot (Mac OS下安装Graphviz), 如果你想转为文本格式,就可安装Perl的Graph::Easy包(命令行:sudo perl -MCPAN -e 'install Graph::Easy', Ubuntu下直接安装libgraph-easy-perl)。

如果你想在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.txt

import sys
import re
import argparse
import os

function_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 = 4
MAX_STYLE_COUNT = len(Styles)

FirstNodeAttr = ' style="rounded,filled" fillcolor=\"#00bb0050\"'
HighlightAttr = ' style="rounded,filled" fillcolor=\"yellow\"'

blockNum = 0    
nodeNo = 0

nodeList={}
nodeOrderList={}
firstNodeList={}
nodeAttr={}

outputText = ''
callingStack = ''
newBlock=True

willCommit=False #For filtering purpose
blockBackTrace = ''
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 strBlockNodeText


def 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 className

def 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 nodeName


def 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] = True


def 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脚本简化打印复杂数据的操作



相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
目录
相关文章
|
11月前
|
编译器 Linux C语言
函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)(上)
函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)
|
6月前
|
Linux C++
【代码片段】Linux C++打印当前函数调用堆栈
【代码片段】Linux C++打印当前函数调用堆栈
165 0
|
6月前
|
Linux
使用backtrace打印程序crash堆栈
使用backtrace打印程序crash堆栈
67 0
|
6月前
|
NoSQL Shell C语言
GDB调试学习(一):单步执行和跟踪函数调用
GDB调试学习(一):单步执行和跟踪函数调用
138 1
|
11月前
|
编译器 C语言
函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)(下)
函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)
|
12月前
|
C++
汇编--C++/c返回值代码分析
汇编--C++/c返回值代码分析
54 0
|
存储 运维 安全
基于VS调试分析 + 堆栈观察问题代码段
面对眼前两段有问题的代码,你会通过什么去解决这个问题?本文将通过调试进行逐步分析💻,带你步步观察程序的运行逻辑
21380 0
基于VS调试分析 + 堆栈观察问题代码段