动态函数调用追踪方法

 

 

上学期间研究了下软件执行网络的动态拓扑特性,实验了一些在函数粒度上可行的网络构造方法,这里总结下,感觉对分析开源软件代码有一定帮助。废话不多说,主要分为c/c++和java两种:

(1) 基于Gnu/Gprof运行时剖析工具

Gnu/Gprof是类Unix平台下对c/c++开源项目的一个profile分析工具,它能在程序运行过程中记录下函数间的调用关系,每个函数被调用的次数,每个函数消耗的时间等代码级信息。它的实现原理是通过编译和链接源程序的时候在gcc编译器的命令行参数中加入“-pg”调试选项,gcc编译器就会在程序的每个函数中加入一个名为“mcout(或“_mcount”,依赖于编译器或操作系统)的函数,该函数在内存中保存了一张函数调用图,可利用函数调用堆栈的形式查找子函数和父函数的地址,从而获得函数间的调用关系,以及每个函数调用次数、运行时间等信息。

下面给出一个小例子,说明如何利用Gnu/Gprof工具追踪程序运行过程中的函数调用,源程序文件名为test.c,其内容如下。


 【步骤1】使用gcc编译器的-pg选项对源程序进行编译和链接,输入如下命令:


 其中test.c文件存放在路径为/home/test,运行命令后会在该路径下生成一个默认名为“a.out”的可执行文件,当然也可以利用-o选项指定可执行文件的名字。本例的调试编译如上所示很简单,但对包含成千上万个源文件的大型开源项目进行编译时会相对复杂一些。首先需要进入项目主文件输入“./configure”命令进行编译配置检查,然后输入“make CFLAGS=-pg LDFLAGS=-pg”进行编译,最后还要输入“make install”安装项目。其中CFLAGSLDFLAGLS分别是编译和链接标志,它们都需要加入-pg选项,否则有可能无法追踪成功。

【步骤2】执行程序,使之生成一个名为“gmon.out”的二进制数据文件,输入如下命令:


 除上述运行结果外,还会在当前目录中生成一个名为“gmon.out”的文件。

【步骤3】使用gprof工具对步骤2中生成的数据文件进行分析,输入如下命令:


 

执行后会在控制台输出分析结果,下面是从中摘抄的一些详细信息。


 Gprof产生的字段信息解释如下所示:

 

字段

含义

时间统计信息中信息

% time

函数消耗的时间占所有时间的百分比

cumulative seconds

函数累计执行的时间(单位秒)

self seconds

函数本身所执行的时间(单位秒)

calls

函数被调用的次数

self Ts/call

调用一次函数的平均时间(不包括调用的函数,单位毫秒)

total Ts/call

调用一次函数的平均时间(包括调用的函数,单位毫秒)

name

函数名

Call graph中信息

index

索引值

% time

函数消耗的时间占所有时间的百分比

self

函数本身所执行的时间

children

父函数调用子函数时所花费的时间或执行子函数花费的时间

called

调用次数

name

函数名

从输出能明显看出,main函数调用了b函数,而b函数分别调用了ac函数。由于例子中的函数只是简单地输出一个字符串,所以每个函数的消耗时间都是0

 (2) 基于AOP面向程序切面编程

AOP(Aspect-Oriented Programming,面向切面编程),是OOP(Object-Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继续和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当需要对分散对象引入公共行为时,OOP就显得无能为力。也就是说,OOP适合描述从上到下的关系而不适合描述从左到右的关系。例如日志功能,记录代码需要水平分散在所有对象中,而与各对象的核心功能无关,在OOP中这些散布的代码导致了大量的重复,不利于模块的重用。AOP则是该类问题的良好解决方案,它使用一种“横切”技术,剖开对象的内部,将那些影响多个类的公共行为封装到一个可重用模块,即“切面”(Aspect)。所谓“切面”,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,以减少系统的重复代码,降低模块间的耦合度,并有利于未来的操作和维护。AOP的典型应用还包括安全性控制、性能统计、事务和异常处理等。

 

AspectJ是一个扩展Java语言并实现了上述技术的AOP框架,它作为Eclipse的一个插件可编程实现对Java项目中函数调用的动态追踪。具体的方法是在开源项目中定义如下的“切面”。


 在自定义切面中,切入点匹配那些在“com.example”及其子包中的所有公共函数,但排除类“CallLogger”和切面自身中的。通知“before”封装了在每个匹配切入点的函数执行之前的逻辑处理,即将函数压入堆栈并记录下函数的调用关系。通知“after”则在匹配函数执行后进行简单的出栈处理。类“CallLogger”维护了记录函数调用的堆栈并实现了动态生成函数调用关系等操作,源码参见附件。

相对于动态追踪,还有针对函数静态关系的分析工具,如Cflow、CodeViz(依赖于GraphViz)、Doxygen等