"竹杖芒鞋轻胜马, 一蓑烟雨任平生"

Do not deify machine learning,it is not that cool

使用Flex制作查看函数调用链的工具

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"
#include 
int 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
#include 
void 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_symbol
ritekiMacBook-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_chain
ritekiMacBook-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)
comments powered by Disqus