转换GDB调用栈到流程图

如果你想在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脚本简化打印复杂数据的操作

时间: 2024-12-03 21:18:51

转换GDB调用栈到流程图的相关文章

在C/C++程序里打印调用栈信息

我们知道,GDB的backtrace命令可以查看堆栈信息.但很多时候,GDB根本用不上.比如说,在线上环境中可能没有GDB,即使有,也不太可能让我们直接在上面调试.如果能让程序自己输出调用栈,那是最好不过了.本文介绍和调用椎栈相关的几个函数.   NAME       backtrace, backtrace_symbols, backtrace_symbols_fd - support for application self-debugging SYNOPSIS       #include

GDB查看栈信息

当程序被停住了,你需要做的第一件事就是查看程序是在哪里停住的.当你的程序调用了一个函数,函数的地址,函数参数,函数内的局部变量都会被压入"栈"(Stack)中.你可以用GDB命令来查看当前的栈中的信息. 下面是一些查看函数调用栈信息的GDB命令: backtracebt 打印当前的函数调用栈的所有信息.如:&http://www.aliyun.com/zixun/aggregation/37954.html">nbsp;(gdb) bt#0  func (n=2

Android群英传笔记——第八章:Activity与Activity调用栈分析

Android群英传笔记--第八章:Activity与Activity调用栈分析 开篇,我们陈述一下Activity,Activity是整个应用用户交互的核心组件,了解Activity的工作模式,生命周期和管理方式,是了解Android的基础,本节主讲 Activity的生命周期与工作模式 Activity调用栈管理 一.Activity Activity作为四大组建出现平率最高的组件,我们在哪里都能看到他,就让我们一起先来了解一下他的生命周期 1.起源 Activity是用户交互的第一接口,他

《C语言程序设计进阶教程》一2.7 在DDD(命令行调试程序)上检测调用栈

2.7 在DDD(命令行调试程序)上检测调用栈 本文讲的是C语言程序设计进阶教程一2.7 在DDD(命令行调试程序)上检测调用栈,在编辑器中输入下面的程序,并把名字存为p1.c不要担心还不能完全理解main函数中的argv,这将会在之后讨论.在Linux终端下使用下面的命令创建可执行文件:这里使用gcc将C程序(p1.c)的源文件转化为计算机可以理解的可执行文件.添加-g启用调试,这样我们可以检验调用栈.添加-Wall和-Wshadow启用警告信息.影子变量将会在4.1节中进行讲解.警告信息有时

小览CallStack(调用栈)(三)-用调试器脚本查看调用栈信息

在这一系列之前的两篇文章中,我介绍了如何在windbg中查看调用栈的相关 信息(详见小览call stack(调用栈)(一)),以及调用约定(详见小览call stack(调用栈) (二)--调用约定).今天的这篇博客在二者的基础 之上,介绍如何使用调式器脚本程序来观察调用栈.对CallStack感兴趣的朋友 可以在此基础上开发更加详尽的脚本来观察CallStack的信息:对调试感兴趣的 朋友则可以看一下DScript的用处. 我们先来看一个例子,下面的程序并不是一个优美的程序片段,但是它能够

小览call stack(调用栈) (二)——调用约定

在上一篇博客中小览call stack(调用栈) (一)中,我展示了如何在windbg中 观察调用栈的相关信息:函数的返回地址,参数,返回值.这些信息都按照一定 的规则存储在固定的地方.这个规则就是调用约定(calling convention). 调用约定在计算机界不是什么新鲜的概念,已经有许多相关的文献给予详细 的介绍.比较全面的介绍可以参见wikipedia上的相关页面.然而,如果你和我 一样,在第一次接触调用约定的时候,觉得这个概念是个高深神秘的冬冬,那么 就请跟随我一起,在这篇博客中看

小览call stack(调用栈) (一)

栈在计算机领域中是个经常提到的名词,数据结构中有栈:网络传输中有协 议栈.今天我们讨论的调用栈(call stack),指的是在程序的执行过程中存储函 数调用信息的动态数据结构. 这个定义可能太抽象了一些,在给出具体的例子之前,请大家先思考一个问 题,哪些信息是函数调用过程中所需要的?或者这么问,一个编译器,在面对一 个函数的调用指令时,该生成哪些代码? 首先,函数的返回地址要保存下来.就好像你和你的小狗玩仍飞碟游戏,每 一个函数调用好比扔一个飞碟,当你的狗狗哼兹哼兹的捡来飞碟,函数完执行的 时

关于 调用栈 的请教

问题描述 昨天惊闻调用栈,以前听过栈,还真没有听过调用栈,高手们灌水吧,什么是调用栈,和栈什么区别啊 解决方案 解决方案二:调用栈?解决方案三:是啊,什么东东,高手不吝赐教

《C语言程序设计进阶教程》一2.5.2 绘制调用栈II

2.5.2 绘制调用栈II本文讲的是C语言程序设计进阶教程一2.5.2 绘制调用栈II绘制调用栈:当程序已经输入f1并完成了第4行.k和m的值是多少呢?当程序完成了第6行,且在f1的栈帧出栈之前.k和m的值是多少呢?当程序完成了f1且f1的栈帧已经出栈时,a和c的值是多少呢?2.5.3 地址程序员怎么才能控制一个变量的地址呢?如果相同的程序运行多次,相同变量的地址会是相同的吗?一个数组元素的地址是连续的还是离散的? 原文标题:C语言程序设计进阶教程一2.5.2 绘制调用栈II