1 |
<div contenteditable="false" class="HighLighter"><div contenteditable="false" class="dp-highlighter"><div class="bar"> </div><ol start="1" class="dp-cpp"><li class="alt"><span><span class="keyword">void</span><span> __cyg_profile_func_enter( </span><span class="keyword">void</span><span> *func_address, </span><span class="keyword">void</span><span> *call_site )</span></span></li><li class=""><span> __attribute__ ((no_instrument_function));</span></li><li class="alt"><span><span class="keyword">void</span><span> __cyg_profile_func_exit ( </span><span class="keyword">void</span><span> *func_address, </span><span class="keyword">void</span><span> *call_site )</span></span></li><li class=""><span> __attribute__ ((no_instrument_function));</span></li></ol></div></div>这两个函数是GNU编译器给安排的,如果定了,则,在函数的入口和出口分别会调用; 当然,这个是在编译期决定的。运行期就无法指定了。<br /><br /><strong>可简单定义如下</strong>:<br /><div contenteditable="false" class="HighLighter"><div contenteditable="false" class="dp-highlighter"><div class="bar"> </div><ol start="1" class="dp-cpp"><li class="alt"><span><span class="keyword">void</span><span> __cyg_profile_func_enter( </span><span class="keyword">void</span><span> *</span><span class="keyword">this</span><span>, </span><span class="keyword">void</span><span> *callsite )</span></span></li><li class=""><span>{</span></li><li class="alt"><span> <span class="comment">/* Function Entry Address */</span><span></span></span></li><li class=""><span> fprintf(fp, <span class="string">"E%p\n"</span><span>, (</span><span class="datatypes">int</span><span> *)</span><span class="keyword">this</span><span>);</span></span></li><li class="alt"><span>}</span></li><li class=""><span><span class="keyword">void</span><span> __cyg_profile_func_exit( </span><span class="keyword">void</span><span> *</span><span class="keyword">this</span><span>, </span><span class="keyword">void</span><span> *callsite )</span></span></li><li class="alt"><span>{</span></li><li class=""><span> <span class="comment">/* Function Exit Address */</span><span></span></span></li><li class="alt"><span> fprintf(fp, <span class="string">"X%p\n"</span><span>, (</span><span class="datatypes">int</span><span> *)</span><span class="keyword">this</span><span>);</span></span></li><li class=""><span>}</span></li></ol></div><div contenteditable="false" style="display:none" class="cpp"><pre>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); } |
这里,我们是在函数入口和出口将函数的地址记录下来,这里是记录到了文件中,该文件在什么时候打开呢?
gcc 的开发者也考虑过这个问题,它们为 main
函数的 constructor 函数和 destructor 函数提供了一些碰巧能够满足这个要求一些方法。
函数是在调用
constructormain
函数之前调用的,而 destructor
函数则是在应用程序退出时调用的。
要创建 constructor 和 destructor 函数,则需要声明两个函数,然后对这两个函数应用 constructor
和 destructor
函数属性。
在 constructor
函数中,会打开一个新的跟踪文件,分析数据的地址跟踪就是写入这个文件的;在 destructor
函数中,会关闭这个跟踪文件
这两个特殊函数的声明如下:
- /* Constructor and Destructor Prototypes */
- void main_constructor( void )
- __attribute__ ((no_instrument_function, constructor));
- void main_destructor( void )
- __attribute__ ((no_instrument_function, destructor));
在pvtrace中,定义如下:
- /* 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 );
- }
好了,将上面四个函数(都包含在pvtrace代码包中的instrument.c文件中)和要分析的源代码一起编译就行了;
运行编译后的程序就会在当前目录下生成 trace.txt 文件,文件内容类似于:
===== trace.txt =====
E0x8048538
E0x80484f4
E0x80484a8
X0x80484a8
X0x80484f4
X0x8048538
=====================
然后,使用pvtrace命令来分析该trace.txt, 分析的过程中涉及: 栈、符号两个概念,相关的文件为 stack.c symbols.c, 都比较简单,其中。
将地址转换为函数名使用的是GNU的 addr2line 命令; 整个分析过程的结果生成一个 dot文件,里面只使用到了几个简单的dot文件的语法。
最后,使用Graphviz 的dot命令将dot文件转换为图形。
参考: http://www.ibm.com/developerworks/cn/linux/l-graphvis/