可以以囑Ş形式查看应用E序的调用过E看作是一个学?fn)经历。这样做可以帮助(zhn)理解应用程序的内部行ؓ(f)Qƈ获得有关E序优化斚w的信息。例如,通过寚w些经常调用的函数q行优化Q?zhn)可以用最的努力来获得最佳的性能。另外,调用跟踪q可以判断用户函数的最大调用深度,q可以用来对调用栈用的内存q行有效限制Q在嵌入式系l中Q这是非帔R要的一个考虑因素Q?/p>
Z捕获q显C用图Q?zhn)需?4 个元素:(x)GNU ~译器工具链、Addr2line 工具、定制的中间代码和一个名?Graphviz 的代码。Addr2line 工具可以识别函数、给定地址的源代码行数和可执行映像。定制的中间代码是一个非常简单的工具Q它可以减少对图形规范的地址跟踪。Graphviz 工具可以生成囑Ş映像。整个过E如?1 所C?/p>
?1. 搜集、简化和可视化跟t\径的q程
要收集一个函数调用的t迹Q?zhn)需要确定每个函数在应用E序中调用的旉。在q去Q都是通过在函数的入口处和退出处插入一个惟一的符h手工(g)每个函数的。这个过E非常繁琐,而且很容易出错,通常需要对源代码进行大量的修改?/p>
q运的是QGNU ~译器工具链Q也UCؓ(f) gccQ提供了一U自动检应用程序中的各个函数的Ҏ(gu)。在执行应用E序Ӟ可以收集相关的分析数据。?zhn)只需要提供两个特D的分析函数卛_。其中一个函数在每次执行惌跟踪的函数时都会(x)调用Q而另外一个函数则在每ơ退出想要跟t的函数时调用(参见清单 1Q。这两个函数都是特别指定的,因此Q编译器可以识别它们?/p>
清单 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)); |
避免使用Ҏ(gu)的检函?/h2>(zhn)或怼(x)产生疑惑Q如?gcc 是我们需要的(g)函敎ͼ那么Z么它不检?code style="font-size: small !important; ">__cyg_* 分析函数呢?gcc 的开发者曾思考过q个问题Q他们提供了一个名?code style="font-size: small !important; ">no_instrument_function 的函数属性,q个函数属性可以应用于函数原型Q禁止对它们q行(g)。不要将q个函数属性应用到分析函数上,q样?x)导致无限递归分析循环和大量的无用数据?/p>
(zhn)或怼(x)产生疑惑Q如?gcc 是我们需要的(g)函敎ͼ那么Z么它不检?code style="font-size: small !important; ">__cyg_* 分析函数呢?gcc 的开发者曾思考过q个问题Q他们提供了一个名?code style="font-size: small !important; ">no_instrument_function 的函数属性,q个函数属性可以应用于函数原型Q禁止对它们q行(g)。不要将q个函数属性应用到分析函数上,q样?x)导致无限递归分析循环和大量的无用数据?/p>
在调用一个检函数时Q?code style="font-size: small !important; ">__cyg_profile_func_enter 同时也会(x)被调用,q以 func_address
形式传递调用的函数地址Q以?qing)从中调用该函数?nbsp;call_site
形式的地址。反之,当一个函数退出时Q也?x)调?code style="font-size: small !important; ">__cyg_profile_func_exit 函数Qƈ传?nbsp;func_address
形式的函数地址Q以?qing)函C中退出的真实地址Q该地址的表CŞ式ؓ(f) call_site
?/p>
在这些分析函CQ?zhn)可以记录下地址对,以供以后再进行分析用。要h gcc 所有的(g)函敎ͼ每个文g都必M?nbsp;-finstrument-functions
?nbsp;-g
选项q行~译Q这样可以保留调试符受?/p>
因此Q现在?zhn)可以?f) gcc 提供一些分析函CQ这些函数可以透明地插入应用程序中的函数入口点和函数退出点。但在调用分析函数时Q又应该怎样处理所提供的地址呢?(zhn)有很多选择Q但是ؓ(f)了简便v见,可以这个地址单地写入一个文Ӟ要注意哪个地址是函数的入口地址Q哪个地址是函数的出口地址Q参见清?2Q?/p>
注意Q?/strong>在清?2 中ƈ没有使用调用 Callsite 信息Q因些信息对于分析程序来说是不必要的?/p> 现在(zhn)可以搜集分析数据了Q但是?zhn)应该在什么地Ҏ(gu)开或关闭?zhn)的跟t输出文件呢Q到现在为止Q还不需要ؓ(f)了进行分析而对源程序进行Q何修攏V因此,(zhn)该如何(g)整个应用程序(包括 要创?constructor ?destructor 函数Q则需要声明两个函敎ͼ然后对这两个函数应用 如果~译分析函数Q在 instrument.cQƈ它们与目标应用E序链接在一P然后再执行目标应用程序,l果?x)生成一个应用程序的调用q踪Q追t记录被写入 trace.txt 文g。跟t文件与调用的应用程序处于相同的目录中。最l结果是Q?zhn)可能会(x)得C个其中满是地址的非常大的文件。ؓ(f)了能够让q些数据更有意义Q?zhn)可以使用一个不太出名的叫做 Addr2line ?GNU 工具?/p> Addr2line 工具Q它是标准的 GNU Binutils 中的一部分Q是一个可以将指o(h)的地址和可执行映像转换成文件名、函数名和源代码行数的工兗这U功能对于将跟踪地址转换成更有意义的内容来说直是太棒了?/p> 要了解这个过E是怎样工作的,我们可以试验一个简单的交互式的例子。(我直接从 shell 中进行操作,因ؓ(f)q是最单地展示q个q程的方法,如清?4 所C。)q个CZ C 文gQtest.cQ是通过 在调?Addr2line 工具Ӟ要?nbsp; Addr2line 工具提供了基本的W号调试信息Q不q?GNU Debugger QGDBQ用的是其他一些内部方法?/p> 现在(zhn)有了一个可以搜集函数函数地址的追t数据的Ҏ(gu)Q还可以使用 Addr2line 工具地址转换为函数名。然而,从应用程序中产生大量的跟t数据之后,如何对这些数据进行精Q从而其更有意义呢Q这是使用一些定制的中间代码在开源工具之间徏立联pȝ地方。本文提供了q个工具QPvtraceQ的带有注释的完整代码,包括如何~译和用该工具的一些说明。(有关的更多信息,请参?nbsp;下蝲 一节。) 回想一下图 1 中的内容Q在执行讄了检函数的应用E序Ӟ?x)创Z个名?nbsp;trace.txt 的文本文件。这个h们可以读取的文g中包含了一pd地址信息 —— 每行一个地址Q每行都有一个前~字符。如果前~?nbsp;EQ那么这个地址是一个函数的入口地址Q也是_(d)(zhn)正在调用这个函敎ͼ。如果前~是一?nbsp;X 字符Q那么这个地址是一个出口地址Q也是_(d)(zhn)正在从q个函数中退出)?/p> 因此Q如果在跟踪文g中有一个入口地址QAQ紧跟着另外一个入口地址QBQ,那么(zhn)就可以推断?A 调用?B。如果一个入口地址QAQ后面跟着一个出口地址QAQ,那么p明这个函敎ͼAQ被调用后就直接q回了。当涉及(qing)大量的调用链Ӟ很隑ֈ析究竟是谁调用了谁,因此Q一U简单的解决Ҏ(gu)是维护一个整个地址的堆栈。每ơ在跟踪文g中碰C个入口地址Ӟ将其压入堆栈。栈的地址׃表最后一ơ被调用的函敎ͼ也就是当前的zd函数Q。如果后面紧接着是另外一个入口地址Q这说明堆栈中的地址调用了这个刚从跟t文件处d的地址。在到退出函数时Q当前的zd函数׃(x)q回Qƈ释放栈顶元素。这?x)将上下文返到回前一个函敎ͼ由此Q就可以产生正确的调用链q程?/p> ?2 介绍了这个概念,以及(qing)_数据的方法。在分析跟踪文g中的调用链时Q会(x)构徏一个连通矩阵,用来表示哪个函数调用了其他哪些函数。这个矩늚行表C用函数的地址Q列表示被调用的地址。对于每个调用对来说Q行与列的交叉点不断q行累加Q调用次敎ͼ。当处理完整个跟t文件时Q其l果是该应用E序的整个调用历史的一个非常简单的表示Q其中包含了调用的次数?/p> 在下载ƈ解压 Pvtrace 工具之后Q只需在子目录中输?nbsp; $ unzip pvtrace.zip -d pvtrace $ cd pvtrace $ make $ make install 现在我们已经构徏了简化的函数q通性矩阵,接下来应该构建图形的表示了。让我们深入研究 GraphvizQ了便理解如何从q通矩는成一个调用图?/p> Graphviz ?Graph Visualization 是由 AT&T 开发的一个开源的囑Ş可视化工兗它提供了多U画图能力,但是我们重点x的是它?Dot 语言直连囄能力。在本文中,我们简单介l如何?Dot 来创Z个图形,q展C如何将分析数据转换?Graphviz 可以使用的规范。(请参?nbsp;参考资?/a> 一节,以获得有关下载这个开源Y件的信息。) 使用 Dot 语言Q?zhn)可以指定三种对象Q图、节点和辏Vؓ(f)了让(zhn)理解这些对象的含义Q我们将构徏一个例子来展示q些元素的用法?/p> 清单 5 l出了一个简单的定向图(directed graphQ,其中包含 3 个节炏V第一行声明这个图?nbsp;GQƈ且声明了该图的类型(digraphQ。接下来的三行代码用于创囄节点Q这些节点分别名?nbsp;node1?em>node2 ?nbsp;node3。节Ҏ(gu)在它们的名字出现在图规范中时创徏的。边是在在两个节点用边操作Q?code style="font-size: small !important; ">->Q连接在一h创徏的,如第 6 行到W?8 行所C。我q对边用了一个可选的属?nbsp; 要将q个 .dot 文g转换成一个图形映像,则需要?Dot 工具Q这个工h?Graphviz 包中提供的。清?6 介绍了这U{换?/p> 在这D代码中Q我告诉 Dot 使用 test.dot 囑Ş规范Qƈ生成一?JPG 囑փQ将其保存在文g test.jpg 中。所生成的图像如?3 所C。在此处Q我使用?JPG 格式Q但?Dot 工具也可以支持其他格式,其中包括 GIF、PNG ?postscript?/p> Dot 语言q可以支持其他一些选项Q包括外形、颜色和很多属性。但是就我们惌实现的功能而言Q这个选项p够了?/p> 现在我们已经看到了整个过E的各个阶段了,下面可以采用一个例子来展示如何这些阶D合q在一起了。现在,(zhn)应该已l展开q安装了 Pvtrace 工具Q然后还需要将 instrument.c 文g复制到工作源代码目录中?/p> 在这个例子中Q我使用了一个源文g test.c q行(g)。清?7 l出了整个过E。在W?3 行中Q我使用(g)源Qinstrument.cQ来构徏Q编译ƈq接Q应用程序。然后在W?4 行执?code style="font-size: small !important; ">testQ再使用 q个q程的示例输出如?4 所C。这个示例图是从使用 Q 学习(fn)的一个简单增强式学习(fn)应用E序中得到的?/p> (zhn)也可以使用q种Ҏ(gu)Ҏ(gu)大的应用E序q行分析。我要展C的最后一个例子是 Gzip 工具。我单地?instrument.c 加入 Gzip ?Makefile 中,作ؓ(f)其依赖的一个源文gQ然后编?GzipQƈ使用它生成一个跟t文件。这个图形太大了Q不太容易进行更详细的分析,但是下图表示?Gzip 对一个小文gq行压羃时的处理q程?/p> 使用开源Y件和量的中间代码,只需要花很少的时间就可以开发出非常有用的项目。通过使用对应用程序进行分析的几个 GNU ~译器扩展,可以使用 Addr2line 工具q行地址转换Qƈ?Graphviz 应用E序q行囑Ş可视化,然后(zhn)就可以得到一个程序,该程序可以对应用E序q行分析Qƈ展示一个说明调用链的定向图。通过囑Ş来查看一个应用程序的调用铑֯于理解应用程序的内部行ؓ(f)来说非常重要。在正确了解调用铑֏(qing)其各自的频率之后Q这些知识可能对调试和优化应用程序非常有用?/p> 下蝲
清单 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
函数Q而不用对分析数据的输出结果进行初始化呢?gcc 的开发者也考虑q这个问题,它们?nbsp;main
函数?constructor 函数?destructor 函数提供了一些碰巧能够满个要求一些方法?code style="font-size: small !important; ">constructor 函数是在调用 main
函数之前调用的,?nbsp;destructor
函数则是在应用程序退出时调用的?/p>constructor
?nbsp;destructor
函数属性。在 constructor
函数中,?x)打开一个新的跟t文Ӟ分析数据的地址跟踪是写入q个文g的;?nbsp;destructor
函数中,?x)关闭这个跟t文Ӟ参见清单 3Q?/p>
清单 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 ); }
cat
一个简单的应用E序实现的(也就是说Q将标准输出的文本重定向C个文件中Q。然后?gcc 来编译这个文Ӟ它会(x)传递一些特D的选项。首先,要(使用 -Wl
选项Q通知链接器生成一个映像文ӞqӞ使用 -g
选项Q通知~译器生成调试符受最l生成可执行文g test。得到新的可执行应用E序之后Q?zhn)可以?nbsp;grep
工具在映像文件中查找 main
来寻扑֮的地址了。用这个地址?Addr2line 工具Q就可以判断出函数名Q?code style="font-size: small !important; ">mainQ、源文gQ?home/mtj/test/test.cQ以?qing)它在源文g中的行号Q?Q?/p>-e
选项来指定可执行映像?nbsp;test
。通过使用 -f
选项Q可以告诉工兯出函数名?/p>
清单 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 $ grep main test.map 0x08048258 __libc_start_main@@GLIBC_2.0 0x08048258 main $ addr2line 0x08048258 -e test -f main /home/mtj/test/test.c:4 $
Addr2line 和调试器
?2. 对跟t数据进行处理和_Qƈ生成矩阵格式
~译q安装工?/h2>
make
命o(h)Q就可以~译 Pvtrace 工具了。也可以使用下面的代码将q个工具安装?/usr/local/bin 目录中:(x)label
Q用它来表示边在图中的名U。最后,在第 9 行完成对该图规范的定义?/p>
清单 5. 使用 Dot W号表示的示例图Qtest.dotQ?/strong> 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: }
清单 6. 使用 Dot 来创?JPG 映像 $ dot -Tjpg test.dot -o test.jpg $
?3. Dot 创徏的示例图
ls
命o(h)验证已经生成?trace.txt 文g。在W?8 行,我调用了 Pvtrace 工具Qƈ提供q个映像文g作ؓ(f)它惟一的参数。映像名是必需的,q样 Addr2lineQ在 Pvtrace 中调用)可以访问这个映像中的调试信息。在W?9 行中Q我又执行了一?nbsp;ls
命o(h)Q以保 Pvtrace 生成?graph.dot 文g。最后,在第 12 行,使用 Dot 这个图形规范{换成一?JPG 囑Ş映像?/p>
清单 7. 创徏调用跟踪囄整个q程 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. CZ应用E序的跟t结?/strong>
?5. Gzip 跟踪l果
描述 名字 大小 下蝲Ҏ(gu) Instrumentation source and Pvtrace source pvtrace.zip 4 KB HTTP