9.1 基础知识

数据包过滤机制在具体的实现上与数据包的协议类型并无多大关系,它只是简单地把数据包看成了1字节数组,实际过滤是根据具体的协议把谓词判断映射到数组特定位置的值上。例如判断ARP类型的数据包,只需要判断数组中第13、14个字节(以太网数据包头中的协议类型)是否为0X0806。从理论研究上看,数据包过滤机制是一个数学问题,或者说是一个算法问题,其中心任务是如何使用最少的判断操作、最少的时间完成过滤处理,从而提高过滤效率。下面将介绍理解WinPcap数据包过滤实现机制所必需的一些基础知识。

注意 本节难度较大,可先粗略地浏览一遍,看完本章其他小节后再回过头来仔细阅读本节。

9.1.1 flex和bison简介

一般语言的编译器在处理语言时,通常会分为词法分析和语法分析两个阶段。词法分析阶段识别低级对象,比如数、操作符及特殊符号等。语法分析阶段识别高级对象,如程序设计语言中的语句。无论是词法分析还是语法分析,用手工去编写程序都是十分烦琐的,因此在UNIX系统与类UNIX系统中,都提供了lex(lexical analyzer)和yacc(Yet another Compiler Compiler)等软件工具来实现该功能。使用这些工具,不仅可以显著地简化词法分析和语法分析程序的开发工作,还可以帮助用户完成许多其他的任务。

在GNU[1]工程中,lex和yacc的对应工具为flex和bison,同时这两个工具也可以在Win32平台上与Microsoft Visual Studio C++结合使用。flex和bison的功能非常强大,非常适合开发词法和语法解析器,尤其是语言编译器和解释器。

1.flex和bison的程序格式

(1)flex

flex程序分为三个段,这些段以%%作为分界。第一段是C和flex的全局声明,第二段是规则,第三段是补充的C函数。flex的程序结构如下:

<定义段>、<规则段>和<子程序段>

<定义段>的内容主要是C语言说明,即括在“%{”和“%}”之间的内容。所有C语言的说明语句和预处理语句都可以放在这一部分中,flex将把“%{”和“%}”之间的内容不加改变地抄写到它所生成的词法分析程序中去。

<规则段>中的每一条规则都可分为正则表达式部分和C程序(或动作部分)。当输入流中出现与正则表达式相匹配的字符串时,就执行后面的动作。为了便于描述词法分析程序的动作,flex还提供了许多变量、子程序和宏供用户使用,详细可以参见用户手册。

<子程序段>可以定义词法分析程序所需的各类过程和函数,比如主程序main函数或yywrap函数。而子程序段里的内容会由flex原封不动地复制到它所生成的词法分析程序里面。

(2)bison

bison源程序结构也分为三个段:

<说明段>、<规则段>和<子程序段>

<说明段>中的说明包含两方面的内容:一个是C程序说明;另一个是bison说明。C程序说明必须用配对的“%{”和“%}”括在一起,它们将原封不动地复制到bison所生成的语法分析程序中去。bison说明有多种形式,关键字%token的作用是说明终结符,没有在%token语句中出现的标识符都被认为是非终结符,每个非终结符必须至少在产生式的左部出现一次。在所有的非终结符中,描述最一般结构的产生式左部的非终结符称为开始符。%right、%left和%nonassoc的作用是说明算符的结合性,以解决语法的二义性问题。

<规则段>由一条或多条规则组成,不包含任何规则的<规则段>是不合法的,每条规则以分号结尾。在逻辑上一条规则可分为两部分,一部分是一条巴科斯范式(BNF),另一部分是一段C程序(称为一个动作)。当从词法分析程序得来的单词序列可按某个BNF进行归类时,就可执行与该BNF相对应的动作了。

在<子程序段>里,用户可以定义自己所需的各种子程序,比如说错误处理程序yyerror。子程序中的内容都会被bison如实地复制到bison所生成的语法分析程序中去。

2.应用flex和bison编程的基本方法

在用flex和bison开发语言处理程序时,用户会先编写一个flex源程序和一个bison源程序。

注意 flex、bison和C/C++是强耦合的。

flex源程序经flex工具处理后,在文件lex.yy.c中生成一个用C语言描述的词法分析子程序yylex(),文件lex.yy.c再经C编译器编译,就可以得到能与其他模块链接的目标文件lex.yy.o或可直接运行的命令文件了。

bison源程序经bison工具处理后,可在文件y.tab.c中得到一个用C语言描述的语法分析子程序yyparse()。文件y.tab.c再经C编译器编译,则会得到能与其他模块链接的目标文件y.tab.o或可直接运行的命令文件。

flex和bison对源程序文件的名字没有特殊要求,但以.l作为flex源文件的扩展名(如frame.l),.y作为bison的扩展名(如frame.y),是一个良好的习惯。

flex编程可以分为三个步骤。

第一步:编写词法分析文件,以flex可以理解的格式指定与模式相关的动作。

第二步:运行flex工具,对该词法分析文件生成扫描器需要的C代码。

第三步:编译和链接该C代码,生成可执行的扫描器。

注意 如果扫描器是用bison开发的解析器的一部分,只需要进行第一步和第二步。

用bison创建一个编译器分为四个步骤。

第一步:编写一个扩展名为.y的语法文件,具体要求如下:

❑说明在这里要进行的动作;

❑编写一个词法分析器来处理输入,并将标记传递给解析器,这通常可以使用flex来完成;

❑编写一个函数,通过调用yyparse函数开始解析;

❑编写错误处理例程(如yyerror函数)。

第二步:运行bison工具为该语法文件生成一个解析器。

第三步:编译bison生成的代码以及其他相关的源文件。

第四步:将目标文件链接到适当的可执行解析器中。

3.flex和bison之间的接口

如果要用flex所生成的词法分析程序和bison生成的语法分析程序共同组成一个完整的语言处理系统,那么,就需要使它们的函数名、参数和返回值一致。恰巧bison所生成的语法分析程序yyparse()调用的词法分析程序与flex所生成的程序都叫yylex(),所以它们在函数名上是相吻合的,不存在矛盾。而且因为yylex()是无参函数,也不存在参数传递问题,语法分析程序yyparse()会要求词法分析程序为它返回单词的编码。

在flex源程序中,同一个单词在词法分析程序和语法分析程序中必须代表相同的值。在bison所生成的文件y.tab.c中,每一个终结符、标识符都会用#define语句定义为一个整数,这个整数的值就是该终结符的编码。第一个出现在bison源程序%token说明中的终结符的编码为257,其余终结符的编码按照它们在%token说明中出现的顺序依次增加。文字型终结符以它们的ASCII码值(0~255)作为自身的单词编码。yylval是一个由bison负责定义的变量,不必在源程序中进行说明。但如果需要重新定义,就需要在.y文件的说明段指出。

4.使用flex与bison的实例

下面介绍使用flex与bison的实例,下面的实例将会在两种操作系统环境中进行演示。该实例用于实现一个简单的四则运算计算器。

第一种环境为Redhat Enterprise Advance server 5(2.6.18内核)操作系统,开发环境使用操作系统自带的gcc编译器,flex的版本为2.5.4,bison的版本为2.3。

词法文件frame.l的内容如下:


%{

include<stdlib.h>

include"frame.tab.h"

void yyerror(char*);

%}

%%

[0-9]+{yylval=atoi(yytext);

return INTEGER;}

[-+/\n]{returnyytext;}

[\t];

.yyerror("invalid character");

%%

int yywrap(void)

{

return 1;

}


语法文件frame.y的内容如下:


%{

include<stdio.h>

int yylex(void);

void yyerror(char*);

%}

%token INTEGER

%%

program:

program expr'\n'{printf("%d\n",$2);}

|

;

expr:INTEGER{$$=$1;}

|expr'+'expr{$$=$1+$3;}

|expr'-'expr{$$=$1-$3;}

|expr''expr{$$=$1$3;}

|expr'/'expr{$$=$1/$3;}

;

%%

void yyerror(char*s){

printf("%s\n",s);

return;

}

int main(void){

yyparse();

return 0;

}


编译过程如下:


$fex frame.l

$bison-d frame.y

$g++-o cal frame.tab.c lex.yy.c

$ls

cal frame.l frame.tab.c frame.tab.h frame.y lex.yy.c


运行结果如下:


$./cal

7=8

invalid character

syntax error

$./cal

7+8

15

8*6

48

9/4

2

7*8

56


第二种环境为Windows XP操作系统,开发环境为Visual Studio 2005,flex的版本为2.5.4,bison的版本为1.28[2]

词法文件frame.l的内容如下:


%{

include<stdlib.h>

include<io.h>

include"frame.tab.h"

void yyerror(char*);

%}

%%

[0-9]+{yylval=atoi(yytext);

return INTEGER;}

[-+/\n]{returnyytext;}

[\t];

.yyerror("invalid character");

%%

int yywrap(void)

{

return 1;

}


语法文件frame.y的内容如下:


%{

include<stdlib.h>

include<stdio.h>

int yylex(void);

void yyerror(char*);

%}

%token INTEGER

%%

program:

program expr'\n'{printf("%d\n",$2);}

|

;

expr:INTEGER{$$=$1;}

|expr'+'expr{$$=$1+$3;}

|expr'-'expr{$$=$1-$3;}

|expr''expr{$$=$1$3;}

|expr'/'expr{$$=$1/$3;}

;

%%

void yyerror(char*s){

printf("%s\n",s);

return;

}

int main(void){

yyparse();

return 0;

}


编译过程如下:


>fex frame.l

>bison-d frame.y


在Visual Studio 2005中创建cal_vc2005工程,把生成的frame.tab.c、frame.tab.h与lex.yy.c文件添加到该工程中。

注意,应把lex.yy.c文件的第25行代码(如下所示)屏蔽掉,否则编译会出错,因为这是UNIX系统或类UNIX系统的头文件。


//#include<unistd.h>


至此,构建成功。

注意 如果源文件中使用了C++的特性,则需要进行文件改名,否则编译器会依据扩展名把这些源文件当作C文件处理,编译将会出错。

把C语言的文件改名为C++语言的文件名的操作如下:


>mv frame.tab.c frame.tab.cpp

>mv lex.yy.c lex.yy.cpp


Windows XP环境下开发的这个计算器程序的运行结果如图9-1所示。

9.1 基础知识 - 图1

图 9-1 计算器的运行结果

[1]GNU(Gnu's Not UNIX)项目由Stallman于1983年创建,这是自由软件基金会的工程,主要工作是创建开放源代码的类UNIX操作系统及其相应的应用软件。

[2]可从因特网上获得UnxUtils工具套件,其中包含flex与bison工具。