若你熟悉Clang,你可以利用Clang来做source-to-source转换,也就是去访问每一个函数的定义,然后插入你的代码。
但是,如
@陈硕所言,对于function trace,我们不需要这么麻烦,因为编译器其实有帮你做这件事情,也就是-finstrument-functions。有了这个编译选项后,编译器会在每一个函数开始处插入__cyg_profile_func_enter,退出的地方插入__cyg_profile_func_exit,你所需要做的就是实现这两个函数,而不用动代码的其它地方。
我举一个简单的例子,首先我们有用于跟踪函数的func_trace.c
#include <stdio.h> static FILE *fp_trace; void __attribute__((constructor)) traceBegin(void) { fp_trace = fopen("func_trace.out", "w"); } void __attribute__((destructor)) traceEnd(void) { if (fp_trace != NULL) { fclose(fp_trace); } } void __cyg_profile_func_enter(void *func, void *caller) { if (fp_trace != NULL) { fprintf(fp_trace, "entry %p %p
", func, caller); } } void __cyg_profile_func_exit(void *func, void *caller) { if (fp_trace != NULL) { fprintf(fp_trace, "exit %p %p
", func, caller); } }
使用 gcc func_trace.c -c 产生目标文件
随后我们编写一个简单的测试代码main.c
#include <stdio.h> int foo(void) { return 2; } int bar(void) { zoo(); return 1; } void zoo(void) { foo(); } int main(int argc, char **argv) { bar(); }
随后将main.c与func_trace.o一起编译,并且记得加上-finstrument-functions, 即:
gcc main.c func_trace.o -finstrument-functions
然后运行./a.out,就会产生func_trace.out,里面的文件内容会类似这样:
entry 0x4006d6 0x7f60c11a7ec5
entry 0x400666 0x4006fb
entry 0x4006a9 0x40068a
entry 0x40062d 0x4006c3
exit 0x40062d 0x4006c3
exit 0x4006a9 0x40068a
exit 0x400666 0x4006fb
exit 0x4006d6 0x7f60c11a7ec5
那么接下来就是处理这个跟踪文件的数据了,你可以利用addr2line达到这一点: addr2line -f -e $EXE $ADDR 即可以显示出来函数名字了,所以我们可以写一个小的Shell脚本来达到目的:
#!/bin/bash EXECUTABLE="$1" TRACELOG="$2" while read TRACEFLAG FADDR CADDR; do FNAME="$(addr2line -f -e ${EXECUTABLE} ${FADDR}|head -1)" if test "${TRACEFLAG}" = "entry" then CNAME="$(addr2line -f -e ${EXECUTABLE} ${CADDR}|head -1)" CLINE="$(addr2line -s -e ${EXECUTABLE} ${CADDR})" echo "Enter ${FNAME} called from ${CNAME} (${CLINE})" fi if test "${TRACEFLAG}" = "exit" then echo "Exit ${FNAME}" fi done < "${TRACELOG}"
然后使用方法很简单 ./func_trace.sh a.out func_trace.out
就会出现函数的调用关系,而 main called from ?? 的 ?? 是 C 库的东西。而后面的 ?? : ? 则是代表没有debug信息,如果你想查看的话,那么你需要编译的时候加上 -g,如下所示:
其实,若有了这样的数据,可以继续往下做下去,如可以利用graphviz建立可视化调用关系图,其实就是 IDE 的函数调用关系图,你不如考虑直接使用IDE?:-)