分析函数调用关系图(call graph)的几种方法

时间:2023-02-13 10:57:24

绘制函数调用关系图对理解大型程序大有帮助。我想大家都有过一边读源码(并在头脑中维护一个调用栈),一边在纸上画函数调用关系,然后整理成图的经历。如果运气好一点,借助调试器的单步跟踪功能和call stack窗口,能节约一些脑力。不过如果要分析的是脚本语言的代码,那多半只好老老实实用第一种方法了。如果在读代码之前,手边就有一份调用图,岂不妙哉?下面举出我知道的几种免费的分析C/C++函数调用关系的工具。

函数调用关系图(call graph)是图(graph),而且是有向图,多半还是无环图(无圈图)——如果代码中没有直接或间接的递归的话。Graphviz是专门绘制有向图和无向图的工具,所以很多call graph分析工具都以它为后端(back end)。那么前端呢?就看各家各显神通了。

调用图的分析分析大致可分为“静态”和“动态”两种,所谓静态分析是指在不运行待分析的程序的前提下进行分析,那么动态分析自然就是记录程序实际运行时的函数调用情况了。

静态分析又有两种方法,一是分析源码,二是分析编译后的目标文件。

分析源码获得的调用图的质量取决于分析工具对编程语言的理解程度,比如能不能找出正确的C++重载函数。Doxygen是源码文档化工具,也能绘制调用图,它似乎是自己分析源码获得函数调用关系的。GNU cflow也是类似的工具,不过它似乎偏重分析流程图(flowchart)。

对编程语言的理解程度最好的当然是编译器了,所以有人想出给编译器打补丁,让它在编译时顺便记录函数调用关系。CodeViz(其灵感来自Martin Devera (Devik) 的工具)就属于此类,它(1.0.9版)给GCC 3.4.1打了个补丁。另外一个工具egypt的思路更巧妙,不用大动干戈地给编译器打补丁,而是让编译器自己dump出调用关系,然后分析分析,交给Graphviz去绘图。不过也有人另起炉灶,自己写个C语言编译器(ncc),专门分析调用图,勇气可嘉。不如要是对C++语言也这么干,成本不免太高了。分析C++的调用图,还是借助编译器比较实在。

分析目标文件听起来挺高深,其实不然,反汇编的工作交给binutils的objdump去做,只要分析一下反汇编出来的文本文件就行了。下面是Cygwin下objdump -d a.exe的部分结果:

00401050 <_main>:
  401050:       55                      push   %ebp
  401051:       89 e5                   mov    %esp,%ebp
  401053:       83 ec 18                sub    $0x18,%esp
   ......
 40107a:       c7 44 24 04 00 20 40    movl   $0x402000,0x4(%esp)
  401081:       00
  401082:       c7 04 24 02 20 40 00    movl   $0x402002,(%esp)
  401089:       e8 f2 00 00 00          call   401180 <_fopen>

从中可以看出,main()调用了fopen()。CodeViz带有分析目标文件的功能。

动态分析是在程序运行时记录函数的调用,然后整理成调用图。与静态分析相比,它能获得更多的信息,比如函数调用的先后顺序和次数;不过也有一定的缺点,比如程序中语句的某些分支可能没有执行到,这些分支中调用的函数自然就没有记录下来。

动态分析也有两种方法,一是借助gprof的call graph功能(参数-q),二是利用GCC的 -finstrument-functions参数。

gprof生成的输出如下:

index % time    self  children    called     name
                0.00    0.00       4/4           foo [4]
[3]      0.0    0.00    0.00       4         bar [3]
-----------------------------------------------
                0.00    0.00       1/2           init [5]
                0.00    0.00       1/2           main [45]
[4]      0.0    0.00    0.00       2         foo [4]
                0.00    0.00       4/4           bar [3]
-----------------------------------------------
                0.00    0.00       1/1           main [45]
[5]      0.0    0.00    0.00       1         init [5]
                0.00    0.00       1/2           foo [4]
-----------------------------------------------

从中可以看出,bar()被foo()调用了4次,foo()被init()和main()各调用了一次,init()被main()调用了一次。用Perl脚本分析gprof的输出,生成Graphviz的dot输入,就能绘制call graph了。这样的脚本不止一个人写过:http://www.graphviz.org/Resources.phphttp://www.ioplex.com/~miallen/

GCC的-finstrument-functions 参数的作用是在程序中加入hook,让它在每次进入和退出函数的时候分别调用下面这两个函数:

void __cyg_profile_func_enter( void *func_address, void *call_site )
                                __attribute__ ((no_instrument_function));

void __cyg_profile_func_exit ( void *func_address, void *call_site )
                                __attribute__ ((no_instrument_function));

当然,这两个函数本身不能被钩住(使用no_instrument_function这个__attribute__),不然就反反复复万世不竭了:) 这里获得的是函数地址,需要用binutils中的addr2line这个小工具转换为函数名,如果是C++函数,还要用c++filt进行name demangle。具体方法在《用Graphviz 可视化函数调用》中有详细介绍,这里不再赘述。

从适应能力上看,源码分析法是最强的,即便源码中有语法错,头文件不全也没关系,它照样能分析个八九不离十。而基于编译器的分析法对源码的要求要高一些,至少能编译通过(gcc 参数 -c)——能产生object file,不一定要链接得到可执行文件。这至少要求源码没有语法错,其中调用的函数不一定有定义(definition),但要有声明(declaration),也就是说头文件要齐全。当然,真的不全也没关系,自己放几个函数声明在前面就能糊弄编译器:) 至于动态分析,要求最高——程序需得运行起来。如果你要分析的是操作系统中某一部分,比如内存管理或网络协议栈,那么这里提到的两种动态分析法恐怕都不适用了。

我发现前面列举的所有免费工具几乎都和GCC、GNU Binutils脱不了干系。这里在把它们整理一下,用Graphviz绘成图:

分析函数调用关系图(call graph)的几种方法

分析函数调用关系图(call graph)的几种方法的更多相关文章

  1. c语言分析函数调用关系图&lpar;call graph&rpar;的几种方法

    一.基于 Doxygen或 lxr 的API形式的文档系统. 二.基于CodeViz, CodeViz是<Understanding The Linux Virtual Memory Manag ...

  2. 用CodeViz绘制函数调用关系图&lpar;call graph&rpar;

    CodeViz是<Understanding The Linux Virtual Memory Manager>(at Amazon,下载地址在页尾)的作者 Mel Gorman 写的一款 ...

  3. 用callgraph生成的函数调用关系图

    Wu Zhangjin 创作于 2015/04/05 评论打赏 By Falcon of TinyLab.org 2015/04/03 1 故事缘由 源码分析是程序员离不开的话题.无论是研究开源项目, ...

  4. 用callgraph生成的两张函数调用关系图

    参考这里,感觉很Cool吧. Linux-0.11函数调用关系图: QEMU函数调用关系图:

  5. python函数调用关系图(python call graph)

    由于要重构项目的部分代码,要整理好主要的函数调用关系,不想自己看代码慢慢画出结构,想找出一种通用的,节省人力的方法得出函数间的调用关系图,于是发现以下几个工具.(内网没装好graphviz,还没真正用 ...

  6. &lbrack;转&rsqb; 使用CodeViz生成C&sol;C&plus;&plus;函数调用关系图

    运行环境:虚拟机下的Ubuntu 11.04 结合Graphviz工具,使用CodeViz可以生成直观和漂亮的C/C++程序函数之间的调用关系图. 1.安装graphviz 在安装CodeViz之前, ...

  7. 使用Idea当中的快捷键快速查看继承关系或其图表的两种方法

    一.Idea当中有两种方法可以查看继承关系 在Idea当中选中一个类,然后按Ctrl+H,可以快速查看当前所选类的继承关系,如下图: ​ 同样选中一个类,按CTRL+ALT+U,即可生成当前类的继承关 ...

  8. 源码分析:静态分析 C 程序函数调用关系图

    http://www.tinylab.org/callgraph-draw-the-calltree-of-c-functions/

  9. 使用egypt&plus;graphviz生成函数调用关系图示例

    总结: make  (-fdump-rtl-expand)  去除编译优化,比如-O3 egypt test.c.128r.expand >test.dot  可以手动打开dot文件去除一些孤立 ...

随机推荐

  1. git文件迁移到新架构

    环境: ubuntu16.04 代码托管地址:git.oschina.net 迁移原因: git上某工程是一堆静态页面html,因为在ubuntu下缺乏git图形客户端,想使用eclipse集成的gi ...

  2. DataTable 中Distinct操作

    DataTable dt = ds.Tables[]; DataView dataView = dt.DefaultView; DataTable dtDistinct = dataView.ToTa ...

  3. Java学习笔记之:Java String类

    一.引言 字符串广泛应用在Java编程中,在Java中字符串属于对象,Java提供了String类来创建和操作字符串. 创建字符串最简单的方式如下: String str= "Hello w ...

  4. Adobe Edge Animate –使用EdgeCommons加载和播放音频

    Adobe Edge Animate –使用EdgeCommons加载和播放音频 版权声明: 本文版权属于 北京联友天下科技发展有限公司. 转载的时候请注明版权和原文地址. 在Edge中,可以new一 ...

  5. Java学习----构造方法的重载

    一个类中有多个同名的参数不一样(参数的个数,参数的类型,参数的顺序)的构造方法 public class Student { public Student() { System.out.println ...

  6. 数据结构:最小生成树--Prim算法

    最小生成树:Prim算法 最小生成树 给定一无向带权图.顶点数是n,要使图连通仅仅需n-1条边.若这n-1条边的权值和最小,则称有这n个顶点和n-1条边构成了图的最小生成树(minimum-cost ...

  7. Codeforces 435B&period; Pasha Maximizes

    简单贪心.... B. Pasha Maximizes time limit per test 1 second memory limit per test 256 megabytes input s ...

  8. SQL Server最大内存设为0后的处置办法

    故障说明: 远程调整实例内存时疏忽,将实例最大内存调整为了0,因此最大内存变成了128MB的最小值. 解决方式: 1.正常关闭SQL Server服务,如果是集群,需要先关停止集群角色防止故障转移,然 ...

  9. node安装及配置之windows版

    一.下载地址 https://nodejs.org/zh-cn/download/ https://nodejs.org/zh-cn/download/releases/ 二.安装步骤 1.双击“no ...

  10. 使用GraphHttpClient调用Microsoft Graph接口 - PATCH

    博客地址:http://blog.csdn.net/FoxDave 通过前两讲的阐述我们应该大致了解了使用GraphHttpClient调用Microsoft Graph接口的模式,并介绍了使用get ...