Do not deify machine learning,it is not that cool
15 Dec 2013
flex在词法解析方面功能强大,有了它,我们做词法解析的工作就退化成了写!正!则!于是可以借题发挥搞很多好玩的东西。比如今天的查看函数调用链的工具。
##原理
首先,用flex编写一个提取源码变量和函数的工具,但是由于我们只要看函数调用关系,所以忽略变量。原理类似于ctags,扫一遍源码,和相关的被include进来的文件,生成符号表(symbol table),符号表内容格式如下:
1 _123:b.c:3 2 cccc:src/c.c:1 3 check:test.c:29 4 dis:test.c:6 5 dishelloworld:b.c:6 6 disp:a.h:6 7 fprintf:lib:-1 8 hehe:a.h:4 9 helloworld:b.c:10 10 main:test.c:44 11 pk:test.c:16 12 printf:lib:-1 13 refval:test.c:38 14 strcmp:lib:-1 15 strlen:lib:-1 16 strncpy:lib:-1 17 thisFunc:test.c:34
第一列是函数名,第二列是包含相应函数定义的文件名,第三列是函数定义在文件中的行数。第二列为lib的表示是一个库函数(标准库和第三方库)。
指定你要看的函数和相应的文件名,根据符号表,程序可以生成以指定函数为入口的所有调用链,具体做法是:
对于函数A,如果扫描发现其调用了函数B,那么查找符号表找到B的定义位置,把当前上下文(文件名,行数,函数名神马的)压栈,然后进入
B函数,如果B中又调了C,重复上述过程,如果B扫描完了,出栈恢复到进入B之前的状态。大概原理就是这个样子的,细节还是比较繁琐,目测有很多bug啊!
目前功能还很原始,只是把所有的调用路径都打印出来,没有逻辑判断输出,需要手工加上。
###作用
除了可以在阅读别人代码时借助这个东西查看指定函数的调用路径;也可以用于自己的代码, 当需要给别人讲解代码逻辑脉络而不需要深入代码细节时,可以先用此工具生成入口函数的调用路径,然后填上逻辑分条件,即可拿去给别人看。
##缺点
不支持源文件(.c)放在单独目录下的情况,弱爆了,后续考虑加上。
##demo
假设我们有源文件如下:
// test.c #include "a.h" #include "b.h" #include "src/c.h" #includeint dis() { char disa = 'a'; int disb = 1; while(1 == 2) { printf("3=4"); } printf("%c\n", disa); return disb; } void pk(int a, int b) { if (a > b) { a -= b; } else { b -= a; } } enum TYPE { INT, FLOAT } t; void check(char *a, char b[]) { printf("check: %d\n", strcmp(a, b)); printf("check: %d\n", a == b); } void thisFunc() { dis(); } void refval(char *chr, int len) { strncpy(chr, "1234kk", len); thisFunc(); } int main(void) { printf("%s\n", __FUNCTION__); char strt[] = "fun1(int ak, int bk), fun2()"; fprintf(stderr, "%s\n", strt); refval(strt, strlen(strt)); fprintf(stderr, "%s\n", strt); int kc = 100; int kb = 100; pk(kb, kc); dis(); char a[] = "abc"; char *b = "abc"; printf("%d\n", a == b); check(a, b); disp(); thisFunc(); return 0; } </pre> // a.h #include#include "b.h" void hehe(); void disp() { printf("hello world\n"); hehe(); helloworld(); } void hehe() { int abc = 0; } </pre> // b.h #includevoid helloworld(); </pre> // b.c #include "b.h" int _123(int _1, int _2) { return _1 + _2; } void dishelloworld() { _123(2, 0); } void helloworld() { dishelloworld(); }// src/c.h void cccc() { }假设现在我们想看test.c的main函数执行路径都有哪些,按照如下步骤: ### step 1:编译flex生成输出symbol的工具code_symbolritekiMacBook-Air:tool jean$ flex code_symbol.l && cc -o code_symbol -g -Wall lex.yy.c lex.yy.c:1892:17: warning: unused function 'yyunput' [-Wunused-function] static void yyunput (int c, register char * yy_bp ) ^ 1 warning generated.编译负责生成symbol文件的flex代码,有个警告,忽略之。 ### step 2:用code_symbol生成symbol文件ritekiMacBook-Air:tool jean$ ./code_symbol test.symbol test.c ritekiMacBook-Air:tool jean$运行code_symbol,指定输出的symbol文件名和需要扫描的源文件。 ### step 3: 编译flex生成查看函数执行路径的工具code_chainritekiMacBook-Air:tool jean$ flex code_chain.l && cc -o code_chain -g -Wall lex.yy.c lex.yy.c:1854:17: warning: unused function 'yyunput' [-Wunused-function] static void yyunput (int c, register char * yy_bp ) ^ lex.yy.c:1899:16: warning: function 'input' is not needed and will not be emitted [-Wunneeded-internal-declaration] static int input (void) ^ 2 warnings generated.编译警告忽略,此时由flex生成了查看函数调用链的工具code_chain ### step 4:查看指定文件中某函数的调用链, 输入参数为刚才生成的symbol文件名,文件名,函数名ritekiMacBook-Air:tool jean$ ./code_chain test.symbol test.c main | |--main | |--printf* | |--fprintf* | |--refval | |--strncpy* | |--thisFunc | |--dis | |--printf* | |--printf* | |--strlen* | |--fprintf* | |--pk | |--dis | |--printf* | |--printf* | |--printf* | |--check | |--printf* | |--strcmp* | |--printf* | |--disp | |--printf* | |--hehe | |--helloworld | |--dishelloworld | |--_123 | |--thisFunc | |--dis | |--printf* | |--printf*打印出了main函数所有可能的执行路径,其中加*的是库函数。 另外,运行code_chain时加上-f(fulltext)选项,会同时打印出函数所在文件和行数。 ___ ##项目地址 [https://github.com/zuojie/Flex_Bison/tree/master/CodeInvokeChain](https://github.com/zuojie/Flex_Bison/tree/master/CodeInvokeChain)