|
![](https://www.pcbbar.com/data/attachment/common/cf/111433th11112012hdtlnn.png)
有天老板让我参与分析一个比较棘手的问题,问题不但不好复现,而且涉及到的函数调用非常错综复杂(就像屎山里那堆东西那样)。一整天没有很好的进展,渐渐地对着这堆屎山发起呆来,隐约中似乎被一股气息刺激到了一根神经,在想——是否存在一种技术可以记录C语言函数所有的集成过程?这个问题后面花了很长时间很大精力被找到了。时间过去很久了,但是那个“记录C语言函数调用全过程”这个方法,我一直都没找到。在每个函数里都打印个log吧,十个八个函数其实手动添加进去也没问题,可以像这样,不用的时候,还可以将宏定义定义成空:
#if FUNC_RECORD_USE #define FUNC_RECORD() printf("Enter function
", __FUNCTION__)#else #define FUNC_RECORD()#endif
void func_example(void){ FUNC_RECORD(); // ...}但是对于有成千上万函数的项目呢,特别是出问题的时候,也不好操作。后来,我想研究AUTOSAR中那些BSW组件的函数行为,于是在网上使劲地挖呀挖,突然发现了GCC有这玩意——-finstrument-functions!官网原始描述是这样的
4jz2dl4l45q64012976808.png
(截图内容来源于https://gcc.gnu.org/)也就是说,在gcc编译的命令里添加-finstrument-functions这个选项,就会产生函数entry和exit时的指令调用,即对应以下两个函数。
void __cyg_profile_func_enter (void *this_fn, void *call_site);void __cyg_profile_func_exit (void *this_fn, void *call_site);其中参数this_fn表示当前进入的函数地址,call_site表示调用该函数的那个函数的地址。不好理解?那么实战搞起来。#include
int add(int a, int b){ return a+b;}
int max(int a, int b){ return a>b? a:b;}
void __attribute__((no_instrument_function)) __cyg_profile_func_enter(void *this, void *call){ printf(" this: %p, call: %p
", this, call);} void __attribute__((no_instrument_function)) __cyg_profile_func_exit(void *this, void *call){ printf(" this: %p, call: %p
", this, call);}
int main(void){ printf("max=%d
", max(add(123,321),456)); return 0;}接下来,命令行输入 gcc -finstrument-functions -g main.c -o main,回车即可。然后运行看结果$ ./main this: 0x100401184, call: 0x7ffc95ee80c1 this: 0x100401080, call: 0x1004011b9 this: 0x100401080, call: 0x1004011b9 this: 0x1004010cf, call: 0x1004011c5 this: 0x1004010cf, call: 0x1004011c5max=456 this: 0x100401184, call: 0x7ffc95ee80c1呃?这怎么看呢?将函数地址转换成函数名字,还得借用一个工具——addr2line,像这样:$ addr2line -e main -a 0x100401080 -fps0x0000000100401080: add at main.c:4也就是说0x100401080表示main.c第4行的add函数。另外,以上命令行中的-e后接要查地址的可执行程序,案例就是编译后生成的main,-a后接要查询的地址了。而-fps呢?嘿嘿,我讲你也记不住,还不如自己逐个去掉它试试。
好了,以上的输出转换成函数后就是这样的了
this: main, call: ?? this: add, call: main this: add, call: add this: max, call: main this: max, call: mainmax=456 this: main, call: ??对着上面的函数源码看,这下子清晰了吧。
不知你有没有发现,我上面的案例源码中有两个__attribute__((no_instrument_function)),这表示告诉编译器这两个函数不要记录那个entry和exit的调用。
好了,完了吗?
还没,因为每个地址都靠addr2line一个个查,着实很麻烦。当然,程序员很喜欢写脚本的,写个shell或者python脚本遍历一遍这些地址不就完了么。
是的,领导给你指导解决问题的时候也许是这么认为的。但是当你要搞一堆很复杂的函数调用的时候,你会发现更深层次的问题。例如发现这玩意在Windows上(Windows上的Cygwin)非常慢,并不能麻烦完成领导给你的任务。
我测试了十几万行的log地址转换的时候,等得快要崩溃了!我换到了Linux上搞,好了那么一点,但还是很慢啊!我要的是秒上转换!
我突然又想到了一个办法,就是通过MAP文件查找。后续打算在我分析AUTOSAR BSW组件的时候详细介绍这个过程,以及分享这个脚本给大家。
这么好玩,你不打算试试吗?这文章你扔进收藏夹也是吃灰的,不如点击个转发、在看和赞!
猜你喜欢:
常用的 Git 提交规范!
分享一种灵活性很高的协议格式(附代码例子)
易懂 | 手把手教你编写你的第一个上位机 |
|