Trace and profile function calls with GCC

本文介绍如何使用GCC的-finstrument-functions选项来跟踪和剖析函数调用。通过编写简单的监测代码,可以记录函数的进入和退出时间及调用者信息,进而帮助理解程序执行流程和定位性能瓶颈。

http://balau82.wordpress.com/2010/10/06/trace-and-profile-function-calls-with-gcc/

Trace and profile function calls with GCC

Posted on 2010/10/06    

42


Software debugging is a complex task. There is always the need to collect all available information, in order to detect and understand the problem fast and to think of a proper solution. Sometimes it’s more convenient to debug step-by-step, sometimes it’s better to make the program run completely, and then trace the execution flow “offline”.

Another important step in software development is profiling. GNU offers “gprof” as a tool to analyze the execution time of functions. The working principle of gprof is that it polls the program state with a small sampling interval and notes the function that is executing. In this case small functions could also not appear in the profiling data because their execution time is smaller than an interval.

I recently tried to use a feature of GNU GCC that can be of some help both for tracing and for profiling. It’s the following option (from its GNU GCC Manual section):

-finstrument-functions
Generate instrumentation  calls for entry and exit to functions.  Just after function entry and just before function exit, the following profiling functions will be called with the address of the current function and its call site. [...]

          void __cyg_profile_func_enter (void *this_fn,
                                         void *call_site);
          void __cyg_profile_func_exit  (void *this_fn,
                                         void *call_site);

[...]

The execution flow can be traced implementing these monitoring points, for example writing on file some useful information.

Suppose you have to analyze the following code:

#include <stdio.h>

void foo() {
 printf("foo\n");
}

int main() {

 foo();

 return 0;
}

Create a file called “trace.c” with the following content:

#include <stdio.h>
#include <time.h>

static FILE *fp_trace;

void
__attribute__ ((constructor))
trace_begin (void)
{
 fp_trace = fopen("trace.out", "w");
}

void
__attribute__ ((destructor))
trace_end (void)
{
 if(fp_trace != NULL) {
 fclose(fp_trace);
 }
}

void
__cyg_profile_func_enter (void *func,  void *caller)
{
 if(fp_trace != NULL) {
 fprintf(fp_trace, "e %p %p %lu\n", func, caller, time(NULL) );
 }
}

void
__cyg_profile_func_exit (void *func, void *caller)
{
 if(fp_trace != NULL) {
 fprintf(fp_trace, "x %p %p %lu\n", func, caller, time(NULL));
 }
}

The idea is to write into a log (in our case “trace.out“) the function addresses, the address of the call and the execution time. To do so, a file needs to be open at the beginning of execution. The GCC-specific attribute “constructor” helps in defining a function that is executed before “main”. In the same way the attribute “destructor” specifies that a function must be executed when the program is going to exit.

To compile and execute the program, the command-line is:

$ gcc -finstrument-functions -g -c -o main.o main.c
$ gcc -c -o trace.o trace.c
$ gcc main.o trace.o -o main
$ ./main
foo
$ cat trace.out
e 0x400679 0x394281c40b 1286372153
e 0x400648 0x40069a 1286372153
x 0x400648 0x40069a 1286372153
x 0x400679 0x394281c40b 1286372153

To understand the addresses, the “addr2line” tool can be used: it’s a tool included in “binutils” package that, given an executable with debug information, maps an execution address to a source code file and line. I put together an executable shell script (“readtracelog.sh“) that uses addr2line to print the trace log into a readable format:

#!/bin/sh
if test ! -f "$1"
then
 echo "Error: executable $1 does not exist."
 exit 1
fi
if test ! -f "$2"
then
 echo "Error: trace log $2 does not exist."
 exit 1
fi
EXECUTABLE="$1"
TRACELOG="$2"
while read LINETYPE FADDR CADDR CTIME; do
 FNAME="$(addr2line -f -e ${EXECUTABLE} ${FADDR}|head -1)"
 CDATE="$(date -Iseconds -d @${CTIME})"
 if test "${LINETYPE}" = "e"
 then
 CNAME="$(addr2line -f -e ${EXECUTABLE} ${CADDR}|head -1)"
 CLINE="$(addr2line -s -e ${EXECUTABLE} ${CADDR})"
 echo "Enter ${FNAME} at ${CDATE}, called from ${CNAME} (${CLINE})"
 fi
 if test "${LINETYPE}" = "x"
 then
 echo "Exit  ${FNAME} at ${CDATE}"
 fi
done < "${TRACELOG}"

Testing the script with the previous output, the result is:

$ ./readtracelog.sh main trace.out
Enter main at 2010-10-06T15:35:53+0200, called from ?? (??:0)
Enter foo at 2010-10-06T15:35:53+0200, called from main (main.c:9)
Exit  foo at 2010-10-06T15:35:53+0200
Exit  main at 2010-10-06T15:35:53+0200

The “??” symbol indicates that addr2line has no debug information on that address: in fact it should belong to C runtime libraries that initialize the program and call the main function. In this case the execution time was very small (less than a second) but in more complex scenarios the execution time can be useful to detect where the application spends the most time. It is also a good idea to use the most precise timer on the system, such as gettimeofday in Linux that returns also fractions of a second.

Some thoughts for embedded platforms:

  • It is possible to have fine-grain timing information if the platform contains an internal hardware timer, counting even single clock cycles. It will become then important to reduce the overhead of the entry and exit functions to measure the real function execution time.
  • The trace information can be sent to the serial port (for example in binary form), and then interpreted by a program running on PC.
  • The entry and exit functions can be used to monitor also the state of other hardware peripherals, such as a temperature sensor.

See also:

### 视图及其子视图中的函数调用关系分析 在 Android 开发中,视图及其子视图的函数调用关系可以通过多种方式追踪和分析。以下是几个常见的方法和技术: #### 使用调试工具 Android 提供了一系列内置的调试工具来帮助开发者跟踪视图层次结构以及其中的方法调用。例如,`Hierarchy Viewer` 是一种可视化工具,能够展示当前界面的视图树,并提供关于每个视图的信息[^1]。 ```bash adb shell dumpsys window windows | grep -E 'mCurrentFocus|FocusedApp' ``` 上述命令可以帮助定位到具体的窗口焦点信息,从而进一步通过 `dumpView()` 或其他 API 获取更详细的视图数据。 #### 自定义日志记录 可以在关键位置插入日志语句以便于观察不同阶段的行为。比如,在自定义视图类里覆盖生命周期回调方法并打印相关信息: ```java @Override protected void onDraw(Canvas canvas) { Log.d("CustomView", "onDraw called"); super.onDraw(canvas); } ``` 这种方法简单有效,尤其适用于小型项目或者初步排查问题时使用。 #### 利用反射机制探索内部实现细节 如果需要深入了解某个特定框架(如 RecyclerView 的 ViewHolder 模式)是如何工作的,则可以借助 Java 反射技术访问私有成员变量或方法。不过需要注意的是,这种方式可能违反封装原则并且增加维护难度: ```java Field field = findViewById(R.id.some_view).getClass().getDeclaredField("somePrivateField"); field.setAccessible(true); Object value = field.get(findViewById(R.id.some_view)); Log.i("ReflectionTest", String.valueOf(value)); ``` #### 性能剖析与优化建议 当涉及到复杂布局时,应优先考虑采用ViewHolder模式以及其他形式的视图缓存策略以降低频繁实例化带来的开销。此外,合理运用多线程处理耗时任务也是提高整体流畅性的关键因素之一[^4]。 #### 数据筛选与呈现效率改进措施 对于大规模的数据集而言,仅加载必要的部分而非一次性全部渲染显得尤为重要[^2]。这不仅减少了内存占用量还加快了页面响应速度。同时配合高效的过滤算法可以让用户体验更加顺畅[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值