mirror of
https://github.com/beyondx/Notes.git
synced 2026-06-16 06:58:20 +08:00
Add New Notes
This commit is contained in:
89
Zim/Utils/Graphviz/Graphviz介绍.txt
Normal file
89
Zim/Utils/Graphviz/Graphviz介绍.txt
Normal file
@@ -0,0 +1,89 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-19T22:02:53+08:00
|
||||
|
||||
====== Graphviz介绍 ======
|
||||
Created Sunday 19 February 2012
|
||||
http://abruzzi.iteye.com/blog/429042
|
||||
|
||||
graphviz是贝尔实验室几个计算机牛人设计的一个__开源的图表(计算机科学中数据结构中的图)可视化项目__,主要用C语言实现,主要实现了一些__图布局算法__。通过这些算法,可以将图中的节点在画布上比较均匀的分布,缩短节点之间的边长,并且尽量的减少边的交叉。
|
||||
|
||||
|
||||
graphviz提供命令式的绘图方式,它提供__一个dot语言__用来编写绘图脚本,然后对这个脚本进行解析,分析出其中的**定点,边以及子图**,然后根据__属性__进行绘制。具体的可以看一个例子,这个例子来自官方的文档。
|
||||
|
||||
|
||||
Dot代码 收藏代码
|
||||
|
||||
digraph G {
|
||||
main -> parse -> execute;
|
||||
main -> init;
|
||||
main -> cleanup;
|
||||
execute -> make_string;
|
||||
execute -> printf
|
||||
init -> make_string;
|
||||
main -> printf;
|
||||
execute -> compare;
|
||||
}
|
||||
|
||||
|
||||
digraph指定该图是一个有向图(directed graph),->表示一条边,main,parse,execute等是顶点,运行出来的效果很好看,如下图:
|
||||
|
||||
|
||||
需要注意的是,我在这个dot脚本中__没有指定任何的关于图的位置的信息__,布局器会自动的根据图形的类型进行布局,并最终展现出来。
|
||||
|
||||
再来看一个比较复杂,并且是程序员经常使用的功能,数据结构图:
|
||||
|
||||
digraph g {
|
||||
node [shape = record,height=.1];
|
||||
node0[label = "<f0> |<f1> G|<f2> "];
|
||||
node1[label = "<f0> |<f1> E|<f2> "];
|
||||
node2[label = "<f0> |<f1> B|<f2> "];
|
||||
node3[label = "<f0> |<f1> F|<f2> "];
|
||||
node4[label = "<f0> |<f1> R|<f2> "];
|
||||
node5[label = "<f0> |<f1> H|<f2> "];
|
||||
node6[label = "<f0> |<f1> Y|<f2> "];
|
||||
node7[label = "<f0> |<f1> A|<f2> "];
|
||||
node8[label = "<f0> |<f1> C|<f2> "];
|
||||
"node0":f2 -> "node4":f1;
|
||||
"node0":f0 -> "node1":f1;
|
||||
"node1":f0 -> "node2":f1;
|
||||
"node1":f2 -> "node3":f1;
|
||||
"node2":f2 -> "node8":f1;
|
||||
"node2":f0 -> "node7":f1;
|
||||
"node4":f2 -> "node6":f1;
|
||||
"node4":f0 -> "node5":f1;
|
||||
}
|
||||
|
||||
|
||||
运行后的效果如下图所示:
|
||||
|
||||
|
||||
不知道其他的程序员怎样,反正我对命令行情有独钟,比较喜欢这一类的工具。最早接触的计算机系统正是一个没有图形系统的BSD,由此对命令行的,没有界面的程序都特别感兴趣。
|
||||
|
||||
===== 相关的想法 =====
|
||||
|
||||
自从使用了graphviz以后,一直想着把这个好东西移植到java下来,大概的思想跟graphviz类似:
|
||||
|
||||
* 解析dot脚本,生成图的对象,这个图中包括**节点,边以及子图**等对象,这些对象上都绑定着相应的**属性**
|
||||
* 将解析出来的图对象发送给layout engine进行处理,**layout engine**可以选择__布局策略__,比如流布局等
|
||||
* 从layout engine中得到布局后的图对象,并交给**image engine**处理,得到最终结果,负责展示或者保存等
|
||||
|
||||
dot 的语法定义比较简单,我已经用javacc构造了一个dot的分析器,现在可以从dot文件中构建出图对象出来,不过还需要进一步完善,可以看看这个BNF定义:
|
||||
|
||||
graph -> [strict] (digraph|graph) id '{' stmt-list '}'
|
||||
stmt-list -> [stmt [';'] [stmt-list] ]
|
||||
stmt -> attr-stmt | node-stmt | edge-stmt | subgraph | id '=' id
|
||||
attr-stmt -> (graph | node | edge) attr-list
|
||||
attr-list -> '[' [a-list] ']' [attr-list]
|
||||
a-list -> id '=' id [','][a-list]
|
||||
node-stmt -> node-id [attr-list]
|
||||
node-id -> id [port]
|
||||
port -> port-location [port-angle] | port-angle [port-location]
|
||||
port-location -> ':' id | ':' '(' id ',' id ')'
|
||||
port-angle ->'@' id
|
||||
edge-stmt -> (node-id | subgraph) edgeRHS [attr-list]
|
||||
edgeRHS -> edgeop (node-id | subgraph) [edgeRHS]
|
||||
subgraph -> [subgraph id] '{' stmt-list '}' | subgraph id
|
||||
|
||||
|
||||
当然graphviz的功能不至于此,它提供一个lib,可以用来__将绘图引擎嵌入在自己的应用中__。这是一个很有意义的事,我们可以不必掌握布局部分的复杂算法,把精力放在业务逻辑部分,将最后的图对象交给这个引擎来处理即可。当然,如果你正好和我一样,想了解其神奇的布局算法,不妨翻翻它的源码,欢迎交流之至。
|
||||
234
Zim/Utils/Graphviz/用_Graphviz_可视化函数调用.txt
Normal file
234
Zim/Utils/Graphviz/用_Graphviz_可视化函数调用.txt
Normal file
@@ -0,0 +1,234 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-20T16:51:57+08:00
|
||||
|
||||
====== 用 Graphviz 可视化函数调用 ======
|
||||
Created Monday 20 February 2012
|
||||
|
||||
http://www.ibm.com/developerworks/cn/linux/l-graphvis/
|
||||
|
||||
使用开源软件来简化复杂调用结构
|
||||
M. Tim Jones, 资深软件工程师, Emulex
|
||||
|
||||
简介: 花一些时间遍历一下源代码,可以向您展现__所有的函数调用过程__;但是如果函数指针非常复杂,或者代码太长且晦涩难懂,那么这个过程就可能更加困难了。本文将向您介绍如何使用开源软件和一些定制的代码来__构建一个动态的图形函数调用生成器__。
|
||||
|
||||
|
||||
发布日期: 2005 年 7 月 11 日
|
||||
级别: 初级
|
||||
访问情况 : 9243 次浏览
|
||||
评论: 0 (查看 | 添加评论 - 登录)
|
||||
平均分 5 星 共 14 个评分 平均分 (14个评分)
|
||||
为本文评分
|
||||
|
||||
可以将__以图形形式查看应用程序的调用过程__看作是一个学习经历。这样做可以帮助您__理解应用程序的内部行为__,并获得有关__程序优化方面的信息__。例如,通过对那些经常调用的函数进行优化,您就可以用最少的努力来获得最佳的性能。另外,调用跟踪还可以判断用户函数的__最大调用深度__,这可以用来对调用栈使用的内存进行有效限制(在嵌入式系统中,这是非常重要的一个考虑因素)。
|
||||
|
||||
为了捕获并显示调用图,您需要 4 个元素:**GNU 编译器工具链、Addr2line 工具、定制的中间代码和一个名为 Graphviz 的代码**。Addr2line 工具可以识别函数、给定地址的源代码行数和可执行映像。定制的中间代码是一个非常简单的工具,它可以减少对图形规范的地址跟踪。Graphviz 工具可以生成图形映像。整个过程如图 1 所示。
|
||||
{{./figure1.gif}}
|
||||
图 1. 搜集、简化和可视化跟踪路径的过程
|
||||
|
||||
===== 跟踪过程 =====
|
||||
|
||||
==== 数据搜集:捕获函数调用路径 ====
|
||||
|
||||
要收集一个函数调用的踪迹,您需要__确定每个函数在应用程序中调用的时间__。在过去,都是通过在函数的**入口处和退出处**插入一个惟一的符号来手工检测每个函数的。这个过程非常繁琐,而且很容易出错,通常需要对源代码进行大量的修改。
|
||||
|
||||
幸运的是,GNU 编译器工具链(也称为 gcc)提供了一种__自动检测应用程序中的各个函数的方法__。在执行应用程序时,就可以收集相关的分析数据。您只需要**提供两个特殊的分析函数即可**。其中一个函数在每次执行想要跟踪的函数时都会调用;而另外一个函数则在每次退出想要跟踪的函数时调用(参见清单 1)。这两个函数都是特别指定的,因此,编译器可以识别它们。
|
||||
|
||||
清单 1. GNU 的入口和出口配置函数
|
||||
|
||||
|
||||
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));
|
||||
|
||||
|
||||
===== 避免使用特殊的检测函数 =====
|
||||
|
||||
您或许会产生疑惑,如果 gcc 就是我们需要的检测函数,那么为什么它**不检测 __cyg_* 分析函数呢**?gcc 的开发者曾思考过这个问题,他们提供了一个名为 __no_instrument_function 的函数属性__,这个函数属性可以应用于函数原型,__禁止对它们进行检测__。不要将这个函数属性应用到分析函数上,这样会导致无限递归分析循环和大量的无用数据。
|
||||
|
||||
在调用一个检测函数时,__cyg_profile_func_enter 同时也会被调用,并**以 func_address 形式传递调用的函数地址**,以及**从中调用该函数的 call_site 形式的地址**。反之,当一个函数退出时,也会调用 __cyg_profile_func_exit 函数,并传递 func_address 形式的函数地址,以及函数从中退出的真实地址,该地址的表示形式为 call_site。
|
||||
|
||||
在这些分析函数中,您可以**记录下地址对**,以供以后再进行分析使用。__要请求 gcc 所有的检测函数,每个文件都必须使用 -finstrument-functions 和 -g 选项进行编译,这样可以保留调试符号。__
|
||||
|
||||
因此,现在您就可以为 gcc 提供一些分析函数了,这些函数可以透明地插入应用程序中的函数入口点和函数退出点。但在调用分析函数时,又应该怎样处理所提供的地址呢?您有很多选择,但是为了简便起见,可以__将这个地址简单地写入一个文件__,要注意哪个地址是函数的入口地址,哪个地址是函数的出口地址(参见清单 2)。
|
||||
|
||||
注意:在清单 2 中并没有使用调用 Callsite 信息,因为这些信息对于分析程序来说是不必要的。
|
||||
|
||||
清单 2. 分析函数
|
||||
|
||||
|
||||
void __cyg_profile_func_enter( void *this, void *callsite )
|
||||
{
|
||||
/* Function Entry Address */
|
||||
fprintf(fp, "E__%p__\n", __(int *)this__);
|
||||
}
|
||||
void __cyg_profile_func_exit( void *this, void *callsite )
|
||||
{
|
||||
/* Function Exit Address */
|
||||
fprintf(fp, "X%p\n", (int *)this);
|
||||
}
|
||||
|
||||
|
||||
现在您可以搜集分析数据了,但是您应该**在什么地方打开或关闭您的跟踪输出文件**呢?到现在为止,还不需要为了进行分析而对源程序进行任何修改。因此,您该如何检测整个应用程序(包括 main 函数)而不用对分析数据的输出结果进行初始化呢?gcc 的开发者也考虑过这个问题,__它们为 main 函数的 constructor 函数和 destructor 函数提供了一些碰巧能够满足这个要求一些方法。__constructor 函数是在调用 main 函数之前调用的,而 destructor 函数则是在应用程序退出时调用的。
|
||||
|
||||
要创建 constructor 和 destructor 函数,则需要声明两个函数,然后**对这两个函数应用 constructor 和 destructor 函数属性**。在 constructor 函数中,会打开一个新的跟踪文件,分析数据的地址跟踪就是写入这个文件的;在 destructor 函数中,会关闭这个跟踪文件(参见清单 3)。
|
||||
|
||||
清单 3. 分析 constructor 和 destructor 函数
|
||||
|
||||
|
||||
/* Constructor and Destructor Prototypes */
|
||||
void main_constructor( void )
|
||||
__attribute__ ((no_instrument_function, **constructor**));
|
||||
void main_destructor( void )
|
||||
__attribute__ ((no_instrument_function, destructor));
|
||||
/* Output trace file pointer */
|
||||
static FILE *fp;
|
||||
void main_constructor( void )
|
||||
{
|
||||
fp = __fopen__( "trace.txt", "w" );
|
||||
if (fp == NULL) exit(-1);
|
||||
}
|
||||
void main_deconstructor( void )
|
||||
{
|
||||
fclose( fp );
|
||||
}
|
||||
|
||||
|
||||
如果编译分析函数(在 instrument.c)__并将它们与目标应用程序链接在一起__,然后再执行目标应用程序,结果会生成**一个应用程序的调用追踪**,追踪记录被写入 trace.txt 文件。跟踪文件与调用的应用程序处于相同的目录中。最终结果是,您可能会得到一个其中满是地址的非常大的文件。为了能够让这些数据更有意义,您可以使用一个不太出名的叫做 Addr2line 的 GNU 工具。
|
||||
|
||||
|
||||
===== 使用 Addr2line 将函数地址解析为函数名 =====
|
||||
Addr2line 工具(它是标准的 GNU Binutils 中的一部分)是一个__可以将指令的地址和可执行映像转换成文件名、函数名和源代码行数的工具__。这种功能对于将跟踪地址转换成更有意义的内容来说简直是太棒了。
|
||||
|
||||
要了解这个过程是怎样工作的,我们可以试验一个简单的交互式的例子。(我直接从 shell 中进行操作,因为这是最简单地展示这个过程的方法,如清单 4 所示。)这个示例 C 文件(test.c)是通过 cat 一个简单的应用程序实现的(也就是说,将标准输出的文本重定向到一个文件中)。然后使用 gcc 来编译这个文件,它会传递一些特殊的选项。首先,**要(使用 -Wl 选项)通知链接器生成一个映像文件**,并(使用 -g 选项)通知编译器__生成调试符号__。最终生成可执行文件 test。得到新的可执行应用程序之后,您就可以使用 grep 工具在映像文件中查找 main 来寻找它的地址了。使用这个地址和 Addr2line 工具,就可以判断出函数名(main)、源文件(/home/mtj/test/test.c)以及它在源文件中的行号(4)。
|
||||
|
||||
在调用 Addr2line 工具时,要使用 -e 选项来指定可执行映像是 test。通过使用 -f 选项,可以告诉工具输出函数名。
|
||||
|
||||
清单 4. addr2line 的一个交互式例子
|
||||
|
||||
$ cat >> test.c
|
||||
#include <stdio.h>
|
||||
int main()
|
||||
{
|
||||
printf("Hello World\n");
|
||||
return 0;
|
||||
}
|
||||
<ctld-d>
|
||||
$ gcc -Wl,__-Map=test.map__ -g -o test test.c //也可以不用指定-Wl,-Map参数
|
||||
$ grep **main** test.map //__使用nm命令查找所有的符号地址和名称。__
|
||||
0x08048258 __libc_start_main@@GLIBC_2.0
|
||||
0x08048258 main
|
||||
$ __addr2line 0x08048258 -e test -f__
|
||||
main
|
||||
/home/mtj/test/test.c:4
|
||||
$
|
||||
|
||||
=== Addr2line 和调试器 ===
|
||||
|
||||
Addr2line 工具提供了**基本的符号调试信息**,不过 GNU Debugger (GDB)使用的是其他一些内部方法。
|
||||
|
||||
|
||||
|
||||
===== 精简函数跟踪数据 =====
|
||||
|
||||
现在您有了一个可以搜集函数函数地址的追踪数据的方法,还可以使用 Addr2line 工具将地址转换为函数名。然而,从应用程序中产生大量的跟踪数据之后,如何__对这些数据进行精简,从而使其更有意义呢__?这就是使用一些定制的中间代码在开源工具之间建立联系的地方。本文提供了这个工具(Pvtrace)的带有注释的完整代码,包括如何编译和使用该工具的一些说明。(有关的更多信息,请参阅 下载 一节。)
|
||||
|
||||
回想一下图 1 中的内容,在执行设置了检测函数的应用程序时,会创建一个名为 trace.txt 的文本文件。这个人们可以读取的文件中包含了一系列地址信息 —— 每行一个地址,每行都有一个前缀字符。**如果前缀是 E,那么这个地址就是一个函数的入口地址(也就是说,您正在调用这个函数)。如果前缀是一个 X 字符,那么这个地址就是一个出口地址**(也就是说,您正在从这个函数中退出)。
|
||||
|
||||
因此,如果在跟踪文件中有一个入口地址(A)紧跟着另外一个入口地址(B),那么您就可以推断是 A 调用了 B。如果一个入口地址(A)后面跟着一个出口地址(A),那么就说明这个函数(A)被调用后就直接返回了。当涉及大量的调用链时,就很难分析究竟是谁调用了谁,因此,__一种简单的解决方案是维护一个整个地址的堆栈__。每次在跟踪文件中碰到一个入口地址时,就将其压入堆栈。栈顶的地址就代表最后一次被调用的函数(也就是当前的活动函数)。如果后面紧接着是另外一个入口地址,这说明堆栈中的地址调用了这个刚从跟踪文件处读出的地址。在碰到退出函数时,当前的活动函数就会返回,并释放栈顶元素。这会将上下文返到回前一个函数,由此,就可以产生正确的__调用链过程__。
|
||||
|
||||
图 2 介绍了这个概念,以及精简数据的方法。在分析跟踪文件中的调用链时,会__构建一个连通矩阵__,用来**表示哪个函数调用了其他哪些函数**。这个矩阵的行表示调用函数的地址,列表示被调用的地址。对于每个调用对来说,行与列的__交叉点不断进行累加__(调用次数)。当处理完整个跟踪文件时,其结果是该应用程序的整个调用历史的一个非常简单的表示,其中包含了调用的次数。
|
||||
{{./figure2.gif}}
|
||||
图 2. 对跟踪数据进行处理和精简,并生成矩阵格式
|
||||
|
||||
===== 精简过程 =====
|
||||
|
||||
=== 编译并安装工具 ===
|
||||
在下载并解压 Pvtrace 工具之后,只需在子目录中输入 make 命令,就可以编译 Pvtrace 工具了。也可以使用下面的代码将这个工具安装到 /usr/local/bin 目录中:
|
||||
|
||||
$ unzip pvtrace.zip -d pvtrace
|
||||
$ cd pvtrace
|
||||
$ make
|
||||
$ make install
|
||||
|
||||
现在我们已经构建了简化的函数连通性矩阵,接下来应该__构建图形的表示__了。让我们深入研究 Graphviz,了便理解如何从连通矩阵生成一个调用图。
|
||||
|
||||
===== 使用 Graphviz =====
|
||||
Graphviz 或 Graph Visualization 是由 AT&T 开发的一个__开源的图形可视化工具__。它提供了多种画图能力,但是我们重点关注的是它使用 Dot 语言直连图的能力。在本文中,我们将简单介绍如何使用 Dot 来创建一个图形,并展示如何将分析数据转换成 Graphviz 可以使用的规范。(请参阅 参考资料 一节,以获得有关下载这个开源软件的信息。)
|
||||
|
||||
===== Dot 使用的图形规范 =====
|
||||
|
||||
使用 Dot 语言,您可以指定三种对象:__图、节点和边__。为了让您理解这些对象的含义,我们将构建一个例子来展示这些元素的用法。
|
||||
|
||||
清单 5 给出了一个简单的__定向图__(directed graph),其中包含 3 个节点。第一行声明这个图为 G,并且声明了该图的类型(digraph)。接下来的三行代码用于创建该图的节点,这些节点分别名为 node1、node2 和 node3。节点是在它们的名字出现在图规范中时创建的。边是在在两个节点使用边操作(->)连接在一起时创建的,如第 6 行到第 8 行所示。我还对边使用了一个可选的属性 label,用它来表示边在图中的名称。最后,在第 9 行完成对该图规范的定义。
|
||||
|
||||
清单 5. 使用 Dot 符号表示的示例图(test.dot)
|
||||
|
||||
|
||||
1: digraph G {
|
||||
2: node1;
|
||||
3: node2;
|
||||
4: node3;
|
||||
5:
|
||||
6: node1 -> node2 [label="edge_1_2"];
|
||||
7: node1 -> node3 [label="edge_1_3"];
|
||||
8: node2 -> node3 [label="edge_2_3"];
|
||||
9: }
|
||||
|
||||
|
||||
要将这个 .dot 文件转换成一个图形映像,则需要使用 Dot 工具,这个工具是在 Graphviz 包中提供的。清单 6 介绍了这种转换。
|
||||
|
||||
清单 6. 使用 Dot 来创建 JPG 映像
|
||||
|
||||
$__ dot -Tjpg test.dot -o test.jpg__
|
||||
$
|
||||
|
||||
|
||||
在这段代码中,我告诉 Dot 使用 test.dot 图形规范,并生成一个 JPG 图像,将其保存在文件 test.jpg 中。所生成的图像如图 3 所示。在此处,我使用了 JPG 格式,但是 Dot 工具也可以支持其他格式,其中包括 GIF、PNG 和 postscript。
|
||||
{{./figure3.gif}}
|
||||
图 3. Dot 创建的示例图
|
||||
|
||||
===== Dot 创建的示例图 =====
|
||||
Dot 语言还可以支持其他一些选项,包括**外形、颜色和很多属性**。但是就我们想要实现的功能而言,这个选项就足够了。
|
||||
|
||||
|
||||
===== 综合 =====
|
||||
|
||||
现在我们已经看到了整个过程的各个阶段了,下面可以采用一个例子来展示如何将这些阶段合并在一起了。现在,您应该已经展开并安装了 Pvtrace 工具,然后还需要将 instrument.c 文件复制到**工作源代码目录中**。
|
||||
|
||||
在这个例子中,我使用了一个源文件 test.c 进行检测。清单 7 给出了整个过程。在第 3 行中,我使用__检测源(instrument.c)__来构建(编译并连接)应用程序。然后在第 4 行执行 test,再使用 ls 命令验证已经生成了 trace.txt 文件。在第 8 行,我调用了 Pvtrace 工具,并提供这个映像文件作为它惟一的参数。映像名是必需的,这样 Addr2line(在 Pvtrace 中调用)就可以访问这个映像中的调试信息。在第 9 行中,我又执行了一次 ls 命令,以确保 Pvtrace 生成了 graph.dot 文件。最后,在第 12 行,使用 Dot 将这个图形规范转换成一个 JPG 图形映像。
|
||||
|
||||
清单 7. 创建调用跟踪图的整个过程
|
||||
|
||||
1: $ ls
|
||||
2: instrument.c test.c
|
||||
3: $ __gcc -g -finstrument-functions test.c instrument.c -o test__
|
||||
4: $ ./test
|
||||
5: $ ls
|
||||
6: instrument.c test.c
|
||||
7: test trace.txt
|
||||
8: $ pvtrace test
|
||||
9: $ ls
|
||||
10: graph.dot test trace.txt
|
||||
11: instrument.c test.c
|
||||
12: $ dot -Tjpg graph.dot -o graph.jpg
|
||||
13: $ ls
|
||||
14: graph.dot instrument.c test.c
|
||||
15: graph.jpg test trace.txt
|
||||
16: $
|
||||
|
||||
这个过程的示例输出如图 4 所示。这个示例图是从使用 Q 学习的一个简单增强式学习应用程序中得到的。
|
||||
{{./figure4.gif}}
|
||||
|
||||
图 4. 示例应用程序的跟踪结果
|
||||
|
||||
===== 示例应用程序的跟踪结果 =====
|
||||
您也可以使用这种方法对更大的应用程序进行分析。我要展示的最后一个例子是 Gzip 工具。我简单地将 instrument.c 加入 Gzip 的 __Makefile__ 中,作为其依赖的一个源文件,然后编译 Gzip,并使用它生成一个跟踪文件。这个图形太大了,不太容易进行更详细的分析,但是下图表示了 Gzip 对一个小文件进行压缩时的处理过程。
|
||||
{{./figure5.jpg}}
|
||||
图 5. Gzip 跟踪结果
|
||||
Gzip 跟踪结果
|
||||
|
||||
===== 结束语 =====
|
||||
|
||||
使用开源软件和少量的中间代码,只需要花很少的时间就可以开发出非常有用的项目。通过使用对应用程序进行分析的几个 GNU 编译器扩展,可以使用 Addr2line 工具进行地址转换,并对 Graphviz 应用程序进行图形可视化,然后您就可以得到一个程序,该程序可以对应用程序进行分析,并展示一个说明调用链的定向图。通过图形来查看一个应用程序的调用链对于理解应用程序的内部行为来说非常重要。在正确了解调用链及其各自的频率之后,这些知识可能对调试和优化应用程序非常有用。
|
||||
116
Zim/Utils/Graphviz/用_Graphviz_可视化函数调用/c++.txt
Normal file
116
Zim/Utils/Graphviz/用_Graphviz_可视化函数调用/c++.txt
Normal file
@@ -0,0 +1,116 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-02-20T17:18:17+08:00
|
||||
|
||||
====== c++ ======
|
||||
Created Monday 20 February 2012
|
||||
|
||||
I can see the same behavior with gcc 3.4.1.
|
||||
|
||||
At least an obvious way to prevent that problem is
|
||||
to extract the __cyg_profile_func* stuff into a separated
|
||||
source file that is **compiled in C.**
|
||||
This object is then__ linked__ at the end with the C++ -compiled
|
||||
objects you can have to build the final binary.
|
||||
It works at least for my gcc 3.4.1.
|
||||
|
||||
|
||||
You can also tell g++ that t__his part of the code is C __by adding
|
||||
|
||||
注意:以上步骤都需加上-g __-finstrument-functions参数__
|
||||
extern "C"
|
||||
{
|
||||
/* the __cyg_profile_func... functions */
|
||||
|
||||
|
||||
}
|
||||
|
||||
Also tested with 3.4.1.
|
||||
|
||||
Regards,
|
||||
--
|
||||
Yannick Perret
|
||||
|
||||
|
||||
=======================================
|
||||
Dmitry Antipov wrote:
|
||||
|
||||
(This letter was being previously posted to gcc-help list, which is probably the
|
||||
better place to ask such questions. But since it wasn't answered there, I posted it here).
|
||||
|
||||
The following program
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
void __cyg_profile_func_enter (void *, void *) __attribute__((no_instrument_function));
|
||||
void __cyg_profile_func_exit (void *, void *) __attribute__((no_instrument_function));
|
||||
|
||||
int depth = -1;
|
||||
|
||||
void __cyg_profile_func_enter (void *func, void *caller)
|
||||
{
|
||||
int n;
|
||||
|
||||
|
||||
depth++;
|
||||
for (n = 0; n < depth; n++)
|
||||
printf (" ");
|
||||
printf ("-> %p\n", func);
|
||||
}
|
||||
|
||||
|
||||
void __cyg_profile_func_exit (void *func, void *caller)
|
||||
{
|
||||
int n;
|
||||
|
||||
|
||||
for (n = 0; n < depth; n++)
|
||||
printf (" ");
|
||||
printf ("<- %p\n", func);
|
||||
depth--;
|
||||
}
|
||||
|
||||
|
||||
void bar(void)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void foo (void)
|
||||
{
|
||||
int x;
|
||||
for (x = 0; x < 4; x++)
|
||||
bar ();
|
||||
}
|
||||
|
||||
|
||||
int main (int argc, char *argv[])
|
||||
{
|
||||
foo ();
|
||||
bar ();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
works as expected if it's compiled as a C program ('gcc -finstrument-functions self.c').
|
||||
But __cyg_* functions doesn't called if the program is compiled as C++ program
|
||||
('g++ -finstrument-fnctions self.cpp').
|
||||
|
||||
Looking through generated assembly shows that the calls of __cyg_* functions
|
||||
are emitted, but these functions itself are generated__ with mangled names.__
|
||||
Here is a piece of 'nm' output:
|
||||
|
||||
U __cyg_profile_func_enter
|
||||
U __cyg_profile_func_exit
|
||||
...
|
||||
00000050 T _Z23__cyg_profile_func_exitPvS_
|
||||
00000000 T _Z24__cyg_profile_func_enterPvS_
|
||||
...
|
||||
|
||||
|
||||
The documentation around '-finstrument-functions' says nothing about C vs. C++
|
||||
differences, so I'm confused why it doesn't work for C++ also.
|
||||
|
||||
GCC version is 3.4.3.
|
||||
|
||||
Thanks,
|
||||
BIN
Zim/Utils/Graphviz/用_Graphviz_可视化函数调用/figure1.gif
Normal file
BIN
Zim/Utils/Graphviz/用_Graphviz_可视化函数调用/figure1.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
Zim/Utils/Graphviz/用_Graphviz_可视化函数调用/figure2.gif
Normal file
BIN
Zim/Utils/Graphviz/用_Graphviz_可视化函数调用/figure2.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.1 KiB |
BIN
Zim/Utils/Graphviz/用_Graphviz_可视化函数调用/figure3.gif
Normal file
BIN
Zim/Utils/Graphviz/用_Graphviz_可视化函数调用/figure3.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
BIN
Zim/Utils/Graphviz/用_Graphviz_可视化函数调用/figure4.gif
Normal file
BIN
Zim/Utils/Graphviz/用_Graphviz_可视化函数调用/figure4.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.9 KiB |
BIN
Zim/Utils/Graphviz/用_Graphviz_可视化函数调用/figure5.jpg
Normal file
BIN
Zim/Utils/Graphviz/用_Graphviz_可视化函数调用/figure5.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
Reference in New Issue
Block a user